Sunday, December 8, 2013

UDOO: Adding IR and Building LIRC Kernel Module

I wanted to use inexpensive Adafruit's IR to integrate Udoo with a remote control and audio/video applications such as MPD,  XMBC and simply shut it down by pressing a button on a remote control.

It was not clear how to do that with this IR chip and what driver to use. Since I didn't find any LIRC kernel modules in the default Ubuntu distro for Udoo Dual, I've decided to build them.

My first attempt was around known lirc_serial driver that I tried to use with  iMX6's serial port /dev/ttymxc3 that is normally used for SAM3X (Arduino) to iMX6 communications and programming SAMX3. This port has its Rx and Tx interfaces exposed through Arduino's GPIO #0 and #1.

However, all attempts to make it working this way were fruitless - something didn't allow the signals to go through from Arduino's GPIO #0 to iMX6 (either with or without jumper 18 that controls this port).

The second obvious approach was to change known lirc_rpi driver that I've successfully used with RaspberryPI and make it suitable for Udoo device.

Fortunately, the changes were not that big and I got my brand new lirc_udoo driver working well with the IR in a couple of days. This is how my hardware solution looks like:


The IR receiver is in the upper right corner, the red LED on the right is an activity monitor that I was already writing about in the previous blog.

If you use a device of the same configuration with a stock Ubuntu kernel, you might need only binary modules - lirc_dev and lirc_udoo.

If you want to compile the LIRC modules yourself, you'll need to install Udoo kernel sources and lirc_udoo sources (lirc-udoo-3.0.35-src.tgz). Download them to a local directory on your Udoo device and run the following commands:

cd <kernel-src>
7z x  Kernel_Unico-master.zip
tar xzvf lirc-udoo-3.0.35.tgz
make oldconfig
chmod +x /root/kernel/Kernel_Unico-master/scripts/setsecoversion (I think this is a bug)
make prepare 
make modulesmake modules_install

You'll also need to make links from /lib/modules/3.0.35 to kernel's 'build' and 'source' dirs:


cd /lib/modules/3.0.35/
ln -s <kernel-src>/Kernel_Unico-master build
ln -s <kernel-src>/Kernel_Unico-master source

Finally, to check if your newly created kernel module works run:

modprobe lirc_udoo gpio_in_pin=<N> debug=1

<N> is a iMX6's GPIO input pin number used as an IR's signal receiver. By default it's 92 and this is what you could see on the picture above.

If everything was built correctly you should see something like this in your /var/log/syslog file:

Dec  9 00:58:00 imx6-qsdl kernel: [   83.145090] lirc_udoo: module is from the staging directory, the quality is unknown, you have been warned.
Dec  9 00:58:00 imx6-qsdl kernel: [   83.147076] lirc_udoo: lirc_udoo_init: in=92 out=-1 softcarrier=1 sense=-1
Dec  9 00:58:00 imx6-qsdl kernel: [   83.154221] lirc_udoo lirc_udoo.0: lirc_dev: driver lirc_udoo registered at minor = 0
Dec  9 00:58:00 imx6-qsdl kernel: [   83.154253] lirc_udoo: driver registered 0!
Dec  9 00:58:00 imx6-qsdl kernel: [   83.154298] lirc_udoo: is_right_chip gpio-2 64 92 00000000 00000000
Dec  9 00:58:00 imx6-qsdl kernel: [   83.154326] lirc_udoo: cant claim input gpio pin 92
Dec  9 00:58:00 imx6-qsdl kernel: [   83.154343] to_irq 348
Dec  9 00:58:00 imx6-qsdl kernel: [   83.234761] lirc_udoo: Interrupt 348 obtained 0


After verifying that everything works as expected, you can remove 'debug' parameter in 'modprobe' to reduce the number of messages in syslog.

Installing all other LIRC tools (lircd, irw, irexec, mode2, etc.) is simple and doesn't differ from the RaspberryPI case.
 

Saturday, November 30, 2013

Big Sound for small RaspberryPI with IR: Adding Favorites



As soon as I had created my music center with RaspberryPI controlled by an Onkyo remote, I've realized that I needed a few dedicated "favorites" buttons and a more complex configuration file for the remote to make sure that I could persist them and make available after a reboot.

Fortunately, Onkyo remote has a lot of buttons that I don't normally use, so I've dedicated the whole Deck B panel to favorites and used Group Preset Up/Down buttons to navigate between different favorites.



Now I have 7 pre-programmed buttons, which are sufficient to remember all my favorite Internet radio-stations and local MPD's playlists.  Since I might want to re-program some favorites on fly, I've decided to use a combination of Stop+<FavoriteN> buttons to assign a new playlist to a button <FavoriteN>.

You can find all those new features in the new IR configuration file below. I think, it can be re-used by anyone who has the same model of Onkyo's remote - RC287S .

/etc/lirc/lircd.conf:


# Please make this file available to others
# by sending it to <lirc@bartelmus.de>
#
# this config file was automatically generated
# using lirc-0.9.0-pre1(default) on Sat Nov 16 03:37:07 2013
#
# contributed by
#
# brand:                       lircd.conf.conf
# model no. of remote control:
# devices being controlled by this remote:
#

begin remote

  name  ONKYO-RC-287S
  flags RAW_CODES|CONST_LENGTH
  eps            30
  aeps          100

  gap          108090

      begin raw_codes

          name KEY_POWER
             9017    4480     624     509     579    1663
              581     555     576     529     600    1670
              577     530     600    1667     579    1662
              583    1661     598     537     571    1670
              599    1668     579     531     598    1678
              569    1666     572     562     576     530
              600     536     572    1669     598     536
              581     555     574     531     600     534
              582     554     576    1664     625    1618
              598     536     584    1658     599    1675
              576    1667     571    1672     592    1677
              574

          name KEY_LEFT
             9054    4447     601     533     575    1667
              601     533     625     509     579    1664
              625     509     578    1672     616    1617
              609    1659     579     538     604    1664
              570    1676     622     501     574    1668
              581    1661     607     527     581    1660
              606    1673     565    1667     631    1610
              606     530     577     556     574    1677
              570     556     573     540     605     526
              577     556     573     539     603    1666
              569    1672     626     508     583    1652
              631

          name KEY_RIGHT
             9040    4461     600     534     582    1658
              601     535     572     562     578    1663
              573     562     577    1663     588    1659
              606    1660     576     531     609    1659
              578    1665     591     543     576    1666
              581    1661     608     526     581     553
              576    1666     583    1660     606    1660
              589     525     604     527     580    1667
              600     540     569    1659     608     529
              579     554     575     533     606    1662
              575    1675     572     555     574    1667
              579

          name KEY_PLAY
             9016    4485     628     505     581    1660
              575     559     581     528     609    1657
              583     527     601    1670     579    1666
              570    1665     603     532     575    1665
              604    1667     581     526     601    1666
              582    1661     576     558     580     528
              602     533     574     559     581     527
              602    1667     580     527     603    1667
              580     526     603    1672     581    1673
              563    1666     603    1668     577     526
              601    1673     572     527     602    1665
              581

          name KEY_PAUSE
             9072    4439     569     555     574    1666
              594     541     576     532     606    1664
              574     532     606    1662     576    1677
              570    1663     604     530     579    1661
              607    1663     575     533     605    1672
              565    1671     577     553     575    1668
              579     555     574    1676     571    1663
              606     529     579     554     575    1668
              580     554     608     498     608    1669
              567     534     605     530     579    1661
              606    1662     576     532     616    1653
              574

          name KEY_LEFTALT
             9015    4488     573     561     579    1660
              575     560     579     528     601    1677
              571     527     601    1669     578    1663
              574    1665     603     533     583    1660
              599    1668     579     528     601    1669
              578    1665     574     562     575     530
              599    1668     580     529     599     536
              572    1670     598     536     571    1669
              612     528     598    1649     600     526
              581    1660     607    1672     565     532
              608    1661     576     531     608    1659
              578

          name KEY_RIGHTALT
             9044    4466     594     529     579    1664
              603     529     579     556     573    1669
              579     556     572    1669     587    1656
              609    1667     567     538     600    1670
              566    1676     583     546     572    1668
              577    1665     601     533     573    1669
              600     534     574     561     579     528
              601    1668     577     531     600    1668
              580     528     600     544     564    1668
              598    1670     579    1664     573     561
              578    1672     563     561     579    1664
              572

          name KEY_VOLUMEUP
             9036    4464     597     537     579    1663
              602     532     575     560     578    1662
              573     567     573    1674     569    1663
              599    1668     578     531     605    1665
              571    1671     572     561     576    1667
              579    1662     603     541     566     560
              578    1661     573     563     575     534
              603     530     576     558     580    1673
              561     562     575    1668     577     556
              572    1670     573    1668     597    1673
              573    1669     574     560     577    1663
              582

          name KEY_VOLUMEDOWN
             9032    4470     603     528     579    1666
              605     529     574     559     580    1673
              562     563     576    1615     627    1668
              595    1677     567     533     603    1663
              571    1672     573     575     563    1666
              578    1662     603     533     574    1667
              598    1679     565     534     603     538
              569     559     579     528     598    1670
              574     534     605     529     604     541
              558    1674     572    1669     606    1662
              572    1678     569     562     578    1661
              579

          name BTN_1
             9009    4501     568     556     573    1668
              579     557     572     536     603    1672
              565     537     602    1662     574    1671
              576    1666     601     533     584    1656
              604    1666     580     528     602    1666
              581    1679     567     549     580     529
              599    1668     580    1665     571     571
              567    1664     584     551     577     530
              599     536     573    1669     597     538
              571     566     576    1682     552     562
              577    1665     570    1673     607    1661
              576

          name BTN_2
             9018    4480     569     536     604    1666
              571     537     602     542     565    1667
              601     533     576    1666     599    1669
              579    1671     566     560     579    1663
              573    1668     602     537     572    1674
              592    1668     577     531     600     535
              572     562     578     529     600    1669
              578    1671     566     560     577     530
              598     537     572    1670     598    1670
              577    1665     572     563     576     533
              596    1671     576    1668     579    1663
              614

          name BTN_3
             9058    4442     600     534     572    1670
              599     536     572     562     576    1665
              573     562     576    1666     572    1680
              587    1672     573     535     606    1663
              574    1666     581     562     566    1666
              581    1663     605     529     578     556
              573    1669     577     566     564    1669
              578    1665     603     531     577     557
              586     523     602    1674     562     537
              602    1667     580     527     608     530
              571    1667     601    1675     572    1662
              574

          name BTN_4
             9015    4479     582     551     576    1666
              581     553     577     532     607    1659
              578     533     606    1662     574    1667
              580    1661     607     528     579    1663
              605    1668     575     529     602    1667
              572    1669     578     556     582    1660
              577     556     584    1660     576     568
              571    1662     574     558     580     527
              602     533     575     559     581    1662
              583     552     579    1661     575     560
              579    1663     574    1669     599    1668
              579

          name BTN_5
             9019    4480     583     526     601    1668
              589     518     603     533     573    1667
              607     528     574    1668     600    1668
              579    1662     574     561     579    1662
              573    1669     599     535     572    1673
              597    1670     577     531     604    1669
              578    1659     572     576     563     531
              597    1672     575     532     608     527
              579     556     575     532     606     528
              580    1669     598    1663     573     536
              604    1663     573    1669     579    1664
              602

          name BTN_6
             9021    4481     572     533     606    1664
              581     527     603     532     576    1665
              603     530     579    1665     602    1666
              579    1662     576     557     572    1672
              575    1666     603     532     575    1685
              581    1669     580     528     601    1668
              579     529     600     534     572    1678
              593    1667     578     529     601     534
              573     560     577     531     600    1668
              580    1663     573     561     578     530
              598    1671     577    1664     572    1679
              590

          name BTN_7
             9013    4481     573     560     576    1666
              573     561     577     530     599    1670
              577     532     596    1671     576    1665
              583    1663     606     527     580    1661
              607    1661     575     534     606    1672
              564    1667     580     554     573     535
              603    1666     572     536     603    1665
              585     524     603     531     576    1666
              602     532     576    1666     602     532
              586    1655     602     534     575    1667
              601    1667     580     527     600    1677
              572

          name BTN_8
             9024    4484     570     529     599    1680
              566     530     598     538     570    1670
              598     535     573    1672     596    1669
              578    1666     571     563     575    1667
              571    1670     608     526     591    1652
              605    1664     573     534     606     533
              574     582     547     560     578     556
              556     554     573     560     578     556
              553     562     565    1670     580    1661
              605    1675     562    1670     578    1663
              604    1665     572    1679     567    1665
              603

          name BTN_9
             9043    4469     587     536     572    1670
              598     536     570     565     576    1666
              622     513     574    1666     570    1671
              607    1668     590     521     598    1666
              572    1666     579     555     573    1670
              576    1665     603     532     579    1662
              603     532     576     558     571     538
              600     533     576     559     579     527
              602     534     581     553     579    1663
              626    1616     600    1667     580    1662
              634    1608     599    1668     579    1665
              624

      end raw_codes

end remote

I've also changed a config file format for storing favorites and a current playlist in a JSON file and  added support for playlists created at soundcloud.com. Internet radiostations are supported through traditional m3u files that MPD understands. 

{
   "current": "PlayList1", 
   "soundcloud": [
      "soundcloud://playlist/xxxxxxxx"
   ], 
   "favorites": [
      "soundcloud://playlist/xxxxxxxx", 
      "InternetRadio1", 
      "InternetRadio2", 
      "InternetRadio3", 
      "InternetRadio4", 
      "LocalPLayList1", 
      "LocalPLayList2"
   ]
}

where

"current" - the currently playing playlist
"soundcloud" - a list of soundclud URL's
"favorites" - a list of all favorites assigned to IR buttons 


Finally, I had to change /etc/lirc/lircrc configuration file and Python script that is called when an IR button or combination of buttons are pressed.

/etc/lirc/lircrc:

begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_POWER
        config = sudo shutdown -h now
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_RIGHT
        config = /usr/bin/mpc next
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_LEFT
        config = /usr/bin/mpc prev
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_PLAY
        config = /usr/bin/mpc toggle
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_PAUSE
    button = BTN_1
        config = /home/pi/pls_man.py -s 1
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_PAUSE
    button = BTN_2
        config = /home/pi/pls_man.py -s 2
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_PAUSE
    button = BTN_3
        config = /home/pi/pls_man.py -s 3
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_PAUSE
    button = BTN_4
        config = /home/pi/pls_man.py -s 4
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_PAUSE
    button = BTN_5
        config = /home/pi/pls_man.py -s 5
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_PAUSE
    button = BTN_6
        config = /home/pi/pls_man.py -s 6
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_PAUSE
    button = BTN_7
        config = /home/pi/pls_man.py -s 7
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = BTN_1
        config = /home/pi/pls_man.py -f 1
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = BTN_2
        config = /home/pi/pls_man.py -f 2
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = BTN_3
        config = /home/pi/pls_man.py -f 3
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = BTN_4
        config = /home/pi/pls_man.py -f 4
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = BTN_5
        config = /home/pi/pls_man.py -f 5
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = BTN_6
        config = /home/pi/pls_man.py -f 6
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = BTN_7
        config = /home/pi/pls_man.py -f 7
        flags = quit
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_LEFTALT
        config = /home/pi/pls_man.py -p
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_RIGHTALT
        config = /home/pi/pls_man.py -n
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = BTN_8
        config = /home/pi/pls_man.py -P
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = BTN_9
        config = /home/pi/pls_man.py -N
end
begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_VOLUMEUP
        repeat = 1
        config = mpc volume +1
end

begin
    remote = ONKYO-RC-287S
        prog   = irexec
    button = KEY_VOLUMEDOWN
        repeat = 1
        config = mpc volume -1
end
pls_man.py script is provided below

#!/usr/bin/env python
# encoding: utf-8
'''
pls_man -- Manage Play List

pls_man is a module for controlling play lists through an IR remote


@author:     ogryb

@copyright:  2013 Oleg Gryb. All rights reserved.

@license:    GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luna_mech.  If not, see <http://www.gnu.org/licenses/>.


@contact:    oleg@gryb.info
@deffield    updated: 2013.11.18
'''

import sys
import os
import re
import json

from optparse import OptionParser

__all__ = []
__version__ = 0.1
__date__ = '2013-11-18'
__updated__ = '2013-11-18'

DEBUG = 0
TESTRUN = 0
PROFILE = 0

SOUND_CLOUD = ['soundcloud://playlist/12692664']

def main(argv=None):
    '''Command line options.'''

    program_name = os.path.basename(sys.argv[0])
    program_version = "v0.1"
    program_build_date = "%s" % __updated__

    program_version_string = '%%prog %s (%s)' % (program_version, program_build_date)
    #program_usage = '''usage: spam two eggs''' # optional - will be autogenerated by optparse
    program_longdesc = '''''' # optional - give further explanation about what the program does
    program_license = "Copyright 2013 Oleg Gryb                                            \
                Licensed under the GPL\nhttp://www.gnu.org/licenses/"

    if argv is None:
        argv = sys.argv[1:]
    try:
        # setup option parser
        parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
        parser.add_option("-c", "--config", dest="config", help="config json file [default: %default]", metavar="FILE")
        parser.add_option("-v", "--verbose", dest="verbose", action="count", help="set verbosity level [default: %default]")

        parser.add_option("-n", "--next", dest="nextpl",
                  action="store_true", default=False,
                  help="Next playlist")

        parser.add_option("-p", "--prev", dest="prevpl",
                  action="store_true", default=False,
                  help="Previous playlist")

        parser.add_option("-N", "--next-fav", dest="nextfav",
                  action="store_true", default=False,
                  help="Next favorites")

        parser.add_option("-P", "--prev-fav", dest="prevfav",
                  action="store_true", default=False,
                  help="Previous favorites")
       
        parser.add_option("-s", "--store-fav", dest="storenum",
                  help="Store favorite", default=0, type="int", metavar="NUMBER")

        parser.add_option("-f", "--play-fav", dest="playnum",
                  help="Store favorite", default=0, type="int", metavar="NUMBER")

        # set defaults
        parser.set_defaults(config="~/.pls_man/config", verbosity=0)

        # process options
        (opts, args) = parser.parse_args(argv)
       
        cf = os.path.expanduser(opts.config)
        if not os.path.isfile(cf):
            if not os.path.isdir(os.path.dirname(cf)):
                os.system("mkdir -p %s" % os.path.dirname(cf))
            cfj = dict()
            cfj['current'] = ''
            cfj['favorites'] = ["","","","","","",""]
            cfj['soundcloud'] = SOUND_CLOUD
            with open(cf, 'w') as out:
                json.dump(cfj, out)

        if not os.path.isfile(cf):
            sys.exit("Can's create file %s" % cf)  

        io = open(cf)
        cfj = json.load(io)
        io.close()
       
        if not cfj:
            cfj = dict()
       
        ind = 0

        if (opts.nextpl or opts.prevpl):
            if 'current' in cfj:
                s = cfj['current']
                lsa = []
                if 'soundcloud' in cfj:
                    lsa = cfj['soundcloud']
                ls = os.popen('mpc lsplaylists').read()
                lsa = lsa + ls.split("\n")
                del lsa [-1]
                for i in range(0,len(lsa)-1):
                    if (s == lsa[i]):
                        if opts.nextpl:
                            ind = i+1
                        else:
                            ind = i-1
                        break
                if ind < 0:
                    ind = len(lsa) - 1
                if ind >= len(lsa):
                    ind = 0
                if ind < 0:
                    ind = 0
                if ind < len(lsa):
                    os.system("mpc clear")
                    loaded = os.system("mpc load %s" % lsa[ind])
                    if loaded == 0:
                        os.system("mpc play")
                        cfj['current'] = lsa[ind]
                        with open(cf, 'w') as out:
                            json.dump(cfj, out)
  
        if (opts.nextfav or opts.prevfav):
            if 'current' in cfj:
                s = cfj['current']
                lsa = []
                if 'favorites' in cfj:
                    lsa = cfj['favorites']
                    for i in range(0,len(lsa)-1):
                        if (s == lsa[i]):
                            if opts.nextfav:
                                ind = i+1
                                while ind < len(lsa) and ((not lsa[ind]) or len(lsa[ind]) == 0):
                                    ind += 1
                            else:
                                ind = i-1
                                while ind >= 0 and ((not lsa[ind]) or len(lsa[ind]) == 0):
                                    ind -= 1
                            break
                    if ind < 0:
                        ind = len(lsa) - 1
                        while ind >= 0 and ((not lsa[ind]) or len(lsa[ind]) == 0):
                            ind -= 1
                    if ind >= len(lsa):
                        ind = 0
                        while ind < len(lsa) and ((not lsa[ind]) or len(lsa[ind]) == 0):
                            ind += 1

                    if 0 <= ind and ind < len(lsa):
                        os.system("mpc clear")
                        loaded = os.system("mpc load %s" % lsa[ind])
                        if loaded == 0:
                            os.system("mpc play")
                            cfj['current'] = lsa[ind]
                            with open(cf, 'w') as out:
                                json.dump(cfj, out)

        if ( 0 < opts.storenum and opts.storenum <= 7 ):
            if not 'favorites' in cfj:
                cfj['favorites'] = ["","","","","","",""]
            if 'current' in cfj:
                s = cfj['current']
                cfj['favorites'][opts.storenum-1] = s
                with open(cf, 'w') as out:
                    json.dump(cfj, out)
  
        if ( 0 < opts.playnum and opts.playnum <= 7 ):
            ind = opts.playnum - 1
            if 'favorites' in cfj and ind < len(cfj['favorites']):
                fav = cfj['favorites'][ind]
                if (len(fav) > 0):
                    os.system("mpc clear")
                    loaded = os.system("mpc load %s" % fav)
                    if loaded == 0:
                        os.system("mpc play")
                        cfj['current'] = fav
                        with open(cf, 'w') as out:
                            json.dump(cfj, out)

    except Exception, e:
        indent = len(program_name) * " "
        sys.stderr.write(program_name + ": " + repr(e) + "\n")
        sys.stderr.write(indent + "  for help use --help")
        return 2


if __name__ == "__main__":
    if DEBUG:
        sys.argv.append("-h")
    if TESTRUN:
        import doctest
        doctest.testmod()
    if PROFILE:
        import cProfile
        import pstats
        profile_filename = 'pls_man_profile.txt'
        cProfile.run('main()', profile_filename)
        statsfile = open("profile_stats.txt", "wb")
        p = pstats.Stats(profile_filename, stream=statsfile)
        stats = p.strip_dirs().sort_stats('cumulative')
        stats.print_stats()
        statsfile.close()
        sys.exit(0)
    sys.exit(main())


Everything works as expected so far, switching between Internet radio-stations and local MPD's playlists stored on Raspberry's is very easy now.
         


Sunday, November 24, 2013

UDOO - RaspberryPI Talks to Arduino

Communication Schemes

There are two loosely coupled components in UDOO device - one is built around  Atmel's SAM3X8E processor and the second one around ARM v7 Cortex A9, i.MX6 series processor. Since the first one resembles traditional Arduino's  functionality and the second one is very similar to RaspberryPI, it would be fair to say that we're dealing with Arduino + RaspberryPI hybrid here.

It would be interesting to learn, of course, how these two loosely coupled components can talk to each other. One of the obvious way of doing this would be by utilizing GPIO's available on both sides.

Possible connectivity scenarios are provided by UDOO in their "UDOO Starting Manual":


It's obvious from this diagram that I could take a signal from i.MX6's output GPIO and feed it to an SAM3X8E's input GPIO.

Since UDOO has only one green led, which simply stays on and never blinks, I've decided to add my own custom led on SAM3X8E that would monitor a status and network activity on i.MX6 side. If i.MX6 is running, it will send a pulse signal every few seconds to GPIO 134. Network activity signals will be sent to the same GPIO when a TCP packet received or send by i.MX6's wireless card. I monitor wireless interface because it's my primary one, but it can be easily changed to wired or any other network interface if necessary.

Solution Details


This is the final hardware solution:

  
GPIO 134 is connected to Arduino's A6, light diode's cathode is connected to the ground on J8 and anode to Arduino's DAC0.

The rest of that is coding. You'll need an activity monitor running on i.MX6 and a code for Arduino's controller that reads A6 and sends an analog signal to DAC0.

I've "demonized" my activity monitor by creating an init script and storing it in /etc/init.d/udoo-activity file:
 
#!/bin/bash
### BEGIN INIT INFO
# Provides: udoo-activity
# Required-Start: $remote_fs $all
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: Sends TCP activity and udoo pulse to a GPIO 134
### END INIT INFO

. /lib/lsb/init-functions

PATH=/sbin:/usr/sbin:/bin:/usr/bin
ACTIVITYPID=/var/run/udoo-activity.pid
PULSEPID=/var/run/udoo-pulse.pid
GPIODIR=/sys/class/gpio/gpio134



[ -f ${GPIODIR}/direction ] || exit 0
[ -f ${GPIODIR}/value ] || exit 0


echo out >${GPIODIR}/directionu
echo 0 >${GPIODIR}/value

do_stop () {

log_daemon_msg "Stopping UDOO activity monitors" "udoo-activity"
if [ -f "$PULSEPID" ]; then
kill -9 `cat $PULSEPID` >/dev/null 2>&1
fi
if [ -f "$ACTIVITYPID" ]; then
kill -9 `cat $ACTIVITYPID` >/dev/null 2>&1
fi
log_end_msg $?
echo 0 >${GPIODIR}/value
}

do_start () {

# Pulse Loop
while true; do
sleep 5
echo 1 >${GPIODIR}/value
sleep 0.0015
echo 0 >${GPIODIR}/value
done &
echo $! >$PULSEPID

# TCP Dump Loop
while true; do
echo 0 >${GPIODIR}/value
tcpdump -q -i wlan6 -n tcp -c 1 >/dev/null 2>&1
echo 1 >${GPIODIR}/value
sleep 1
done &

echo $! >$ACTIVITYPID

}

case "$1" in
start)
do_start
;;
stop)
do_stop
;;
force-reload|restart)
$0 stop
$0 start
;;
*)
echo "Usage: /etc/init.d/udoo-activity {start|stop|restart|force-reload}"
exit 1
;;
esac

exit 0


To start the monitor at i.MX6's boot, you'll need to update your init.d scripts:

update-rc.d udoo-activity defaults

Finally, the code for controlling Arduino's part:



// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  analogWriteResolution(12);
  analogWrite(6, 0);
  pinMode(6, INPUT);
  analogWrite(6, 0);
  pinMode(DAC0, OUTPUT);
  analogWrite(DAC0, 0);
}

int BLINK_DURATION=50;
int IDLE_DURATION=2500;
int IDLE_DURATION_LIGHT=100;
int PULSE_DURATION=5500;
int LOOP_DURATION=10;
int idle_count = 0;
int light_on = 0;
int pulse_count = 0;


// the loop routine runs over and over again forever:

void loop() {
 
  int level = analogRead(6);
  if (level >= 900) {
     blink(BLINK_DURATION);
     pulse_count = 0;
     idle_count = 0;
     Serial.println(level);
  }
  else {
    idle_count += 1;
    if (pulse_count*LOOP_DURATION >= PULSE_DURATION) {
         analogWrite(DAC0,0);
         idle_count = 0;
         light_on = 0;
    }
    else {
      pulse_count += 1;
      if (idle_count*LOOP_DURATION >= IDLE_DURATION && !light_on) {
        analogWrite(DAC0,2048);
        light_on = 1;
        idle_count = 0;
      }
      else {
        if (idle_count*LOOP_DURATION >= IDLE_DURATION_LIGHT && light_on) {
           analogWrite(DAC0,0);
           idle_count = 0;
           light_on = 0;
        }
      }
    }
  }
 
  // print out the value you read:
  //Serial.println(sensorValue);
  delay(LOOP_DURATION);        // delay in between reads for stability
}

void blink(int duration) {
  analogWrite(DAC0, 2048);
  delay(duration);
  analogWrite(DAC0, 0);
}


When there is no any network activity, the led will be slowly blinking, when the packets are received or sent  by wlan6, it will blink fast. If i.MX6 is off or doesn't send a pulse, the led will be turned off. An apparent benefit of the last state (led is off) is that it can be used to determine when shutdown on i.MX6 is complete and when it's safe to turn the power off.

Yet another interesting thing that I've learned about UDOO is that even if you completely shut down i.MX6 9 (e.g. by running 'init 0') Arduino part of it will still be running.

Demo

At the video below you can see the led in two states: during the boot when the wireless interface is active trying to acquire a connection and after the boot is completed (starting @ 15th second) when there is no network activity and the led is blinking slowly because of the pulse signal sent by the i.MX6 board once in a few seconds.