November 30, 2013

A ball-chasing robot

 
The current version of my robot uses some basic image detection to find a blue ping pong ball. Here you can see it in action:


And here a slightly modified version of the software, where the robot follows me while I am wearing a blue jersey:



The webcam (a Logitech ) is connected to a Beaglebone Black, which does the image processing. The Beaglebone is connected via SPI (which I bitbang on the Beaglebone) to an Atmega 328 hidden inside the robot which is responsible for all the menial tasks like controlling the motors and for the infrared remote. The robot also has a infrared distance sensor at the front so that it does not run into a wall and gets stuck.

This is a typical picture the webcam on the robot shoots:






The non-roundness of the ball on the left stems from me stepping onto it at some point, not from a camera failure.


The Beaglebone uses the Opencv Image processing library to process such a picture. The following is basically the image processing code the Beaglebone runs:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <opencv2/core/core_c.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui_c.h>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <unistd.h>
using namespace std;
using namespace cv;

int main(){

   VideoCapture cap(1);
   cap.set(CV_CAP_PROP_FRAME_WIDTH, 320);
   cap.set(CV_CAP_PROP_FRAME_HEIGHT, 240);
   Mat frame,  hsvframe;
   cap.read(frame);
   cap.read(frame);
   cap.read(frame);
   sleep(1);
   cap.read(frame);
   hsvframe = Mat::zeros(frame.size(), CV_8UC3);
   cvtColor(frame, hsvframe, CV_BGR2HSV);
   Mat imgthreshed =  Mat::zeros(frame.size(), CV_8UC3);
   inRange(hsvframe, Scalar(80, 50,20), Scalar(120, 255,255), imgthreshed);
   vector< vector<Point> > contours;
   vector<Vec4i> hierarchy;
   Mat img  = imgthreshed.clone();
   findContours(img, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0,0));
   Rect boundrect;
   Mat drawing = Mat::zeros(frame.size(), CV_8UC3);
   int area = 10; //this controls which size an object must have to be detected
   int IdLargestContour = -1;
   int areabuffer;
   for(unsigned int i = 0; i < contours.size(); i++){
     areabuffer = contourArea(contours[i]);
     if( areabuffer > area){
        area = areabuffer;
        IdLargestContour = i;
     }
  }

    if(IdLargestContour == -1){
        cout << "Nothing to see here" <<endl;
        imwrite("purepicture.jpg", frame);

    }
    else{
        cout << "I see something!"<<endl;
        boundrect = boundingRect(Mat(contours[IdLargestContour]));
        imwrite("purepicture.jpg", frame);
        rectangle(frame, boundrect, Scalar(0,255,0), 1,8,0);
        imwrite("picturewithrect.jpg", frame);
        imwrite("thresh.jpg", imgthreshed);


    }
    return 0;
}


For compilation, you have to link against the DLLs of the included libraries - they should be easy to find online. I am using the C++ bindings of Opencv here; there are also C bindings, but the two are largely incompatible, so be wary when you google for examples.

The first 3 lines of the program launch the webcam and set the resolution to 320x240 - the actual program on the Beaglebone only uses 160x120 for speed reasons. The argument to cap is the number of the webcam you want to access - if you only have a single webcam, use 0; my laptop also has an internal one, so I use webcam 1, the external one.

Mat is the basic image format of Opencv, so we define two of those and save a snapshot from the webcam via cap.read(frame) into frame. I am doing this several times here since the first 2 or 3 pictures from the webcam always end up being way too dark for some reason. In line 24, we convert the image to the HSV color space, which is the preferable format for color-based image processing. Line 26 turns the HSV image into a black-and-white image: The blue pixels (those with a hue between 80 and 120) are white, all others black. The results is the following thresholded image:

In line 30, we search for the various white components of the picture (there is probably only one such component here). In line 36 and following, we search for the largest component among those we have just found. Finally, in line 53, we draw a green rectangle around the largest component into our original picture and save all images:



The Beaglebone is fast enough to perform these calculations relatively quickly;the actual bottleneck is that the drivers for the webcam buffer the image of the webcam several times so that you get an outdated picture if you only request a single snapshot. I am currently "solving" this problem by requesting 5 pictures in a row and then only using the last for the image processing, but this takes some additional time.




November 11, 2013

The PRU of the Beaglebone Black

For timing-critical tasks, the Beaglebone Black has two built-in microprocessors, the PRUs (Processing real-time units). It is not obvious how to use the PRUs; in this post, I try to put together some information on the PRUs.

The PRUs can currently only be programmed in assembler. This is not as bad as it sounds: the assembler instruction set is fairly easy to use, and since it is straightforward to share data between the PRU and the Beaglebone, we only have to write the time-critical code parts in assembler and can write the glue logic in a normal C or C++ program on the Beaglebone black.

At first, you have to install the assembler and the C-library for communication between the PRU and the Beaglebone. You can find an instruction at https://npmjs.org/package/pru under "Driver Library and Assembler". The install also comes with a few example programs.

After installation, you have to enable the PRU via a device tree overlay. On your Beaglebone, navigate to /lib/firmware, create a file called BB-BONE-PRU-00A0.dts and copy the following into the file:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/dts-v1/;
/plugin/;

/ {
  compatible = "ti,beaglebone", "ti,beaglebone-black";

 
  part-number = "BB-BONE-PRU";
  version = "00A0";

  exclusive-use =
    "P8.12";

  fragment@0 {
    target = <&am33xx_pinmux>;
    __overlay__ {
      mygpio: pinmux_mygpio{
        pinctrl-single,pins = <
          0x30 0x06
          >;
      };
    };
  };

  fragment@1 {
    target = <&ocp>;
    __overlay__ {
      test_helper: helper {
        compatible = "bone-pinmux-helper";
        pinctrl-names = "default";
        pinctrl-0 = <&mygpio>;
        status = "okay";
      };
    };
  };

  fragment@2{
  target = <&pruss>;
    __overlay__ {
      status = "okay";
    };
  };
};

This enables the PRU and gives the PRU direct access to the GPIO1_12-pin. This is achieved with the lines 14-23; in particular with the line 0x30 0x06. Where do these numbers come from? This is actually fairly non-obvious since both the reference manual and the technical reference manual are silent on this question.

Have a look at selsinork's table in post 5 at http://www.element14.com/community/thread/23952?tstart=0.
Oddly, a similar, but less informative table appears in the reference manual, which you may find at http://www.farnell.com/datasheets/1701090.pdf, and I do not know in which official argument the table linked above appears - it seems to be correct, however. You  should see columns up to mode 7; if not, you find a PDF version of the table in the post after the table.

Looking at the columns mode 6 and mode 7, we see that mode 6 of the gpio1_12 pin is pr1_pru0_pru_r30_14.  In this mode, we can control the behaviour of that particular pin directly in the assembler code of the PRU. This is why we have the 0x06 in the above code: it tells the Beaglebone that we want to switch a pin to mode 6. The other number, 0x30, tells the Beaglebone which pin to switch. The helpful table linked above actually gives the memory address of the pin: it has an offset of 0x830. This is the offset of the pin conf_gpmc_ad12 in the Control module part of the memory of the Beaglebone, as you can find in the technical reference manual http://elinux.org/images/6/65/Spruh73c.pdf of the (processor used in the) Beaglebone in Chapter 9, Control module. Consulting selsinork's table, we see that the pin conf_gpmc_ad12 is indeed GPIO1, pin 12. Since the registers actually controlling the behaviour of the pins start at 0x800, we only pass the offset after this memory address, which is 0x30. 

Now we have to compile the device tree overlay. In /lib/firmware, the command

dtc -O dtb -o BB-BONE-PRU-00A0.dtb -b 0 BB-BONE-PRU-00A0.dts

compiles the .dts file to a file usable by Linux. Should the compiler not be installed on your system, you can find instructions at https://npmjs.org/package/pru under "Device tree" .To enable the overlay, go to /sys/devices/bone_capemgr.8 (or maybe 9, depending on your version of Linux) and load the device tree overlay:


echo BB-BONE-PRU > slots

It should now appear at the end of the list you get with

cat slots

Now you have enabled the PRU. This is not a permanent way to enable the device tree overlay - you will have to do it again after each reboot; forgetting to load the overlay is usually the reason for weird error messages you get when starting the PRU. Some googling should give you good results if you want to make this overlay permanent.

The device tree stuff is unfortunately quite confusing, but you can forget about all this right now. But while we are at the command line, we also enable the PRU drivers via

modprobe uio_pruss

I think they are only required if you actually want to access the memory of the Beaglebone via the PRUs, but safe is safe.

Finally, we can program our PRU. The following two programs will ask the user for an input and move a servo motor connected to GPIO1_12 accordingly. This already requires timing in the lower microseconds range, which is really tricky to reach with direct GPIO manipulations via Linux, leading to a jittery servo. It could probably also be done with a pwm pin, but getting pwm to work also seems to be tricky.
 
To play it safe, you should use a separate battery for the servo motor if you try this (remember to connect the grounds!) since the servo can pull quite a lot of peak current. I  would also advice to use a 10k resistor in the line from the pin to the servo, just in case something goes wrong.

The assembler code compiles to a bin-file; the following C-program shows how to upload the bin-file to the PRU.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include<iostream>
#include<bitset>
#include <string.h>
// Driver header file
#include <prussdrv.h>
#include <pruss_intc_mapping.h>
#include <iostream> 
 /*****************************************************************************
* Explicit External Declarations                                             *
*****************************************************************************/

/*****************************************************************************
* Local Macro Declarations                                                   *
*****************************************************************************/
#define PRU_NUM  0
#define ADDEND1  0x0010F012u
#define ADDEND2  0x0000567Au
#define OFFSET_DDR  0x00001000 
#define PRU_ADDR 0x4A300000
#define SHAREDRAM_OFFSET 0x00012000
#define PRUSS0_SHARED_DATARAM    4
#define AM33XX
int main(){
     int fd = open("/dev/mem",O_RDWR | O_SYNC);
     ulong* prusharedmemory = (ulong*) mmap(NULL, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, PRU_ADDR+SHAREDRAM_OFFSET);
     prusharedmemory[0]= 0b100011111;
     unsigned int ret;
     tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA;
    
    printf("\nINFO: Starting %s example.\r\n", "Servo");
    /* Initialize the PRU */
    prussdrv_init ();  
    

    ret = prussdrv_open(PRU_EVTOUT_0);
    if (ret)
    {
        printf("prussdrv_open open failed\n");
        return (ret);
    }
    
    /* Get the interrupt initialized */
    prussdrv_pruintc_init(&pruss_intc_initdata);



     int a;
/*load program into the pru*/
     prussdrv_exec_program (PRU_NUM, "./servo.bin");
    while(1){
          std::cin >> a;
           if(a == 255){
              prusharedmemory[0] = a;
              break;
           }
           a = 90+(100*a)/180;
           prusharedmemory[0] = a;

    }
    prussdrv_pru_wait_event (PRU_EVTOUT_0);
    printf("\tINFO: PRU completed transfer.\r\n");
    prussdrv_pru_clear_event (PRU0_ARM_INTERRUPT);
    /* Disable PRU and close memory mapping*/
    prussdrv_pru_disable (PRU_NUM);
    prussdrv_exit ();
    return 1;
}

We start by defining a memory map into that part of the memory which can be directly accessed from both the PRU and the Beaglebone. The first part of that memory is for storing the sourcecode; offset 0x12000 is in the shared RAM of the PRUs. We can now write something to this place in memory and easily read it into the PRU. The pruss library is supposed to have its own function doing that, but I could not get it to work, and the memory map approach is more transparent anyway.

After that, we initiliaze the PRU interrupt, which allows the PRU to notify our C-program once it is finished, and load the bin file in line 58. After that, we read in a number representing the angle we want to move the servo to. If this number is 255, we pass 255 to the PRU and break the loop; otherwise, we pass a rough estimate of the length of the pulses needed to reach the desired servo position. Finally, we wait until the PRU tells us he is done and do some cleanup. Depending on your servo, you probably have to tweak the numbers a bit.

To compile the code, you need to link to several libraries. From the command line,


g++ YourFileName.c -o YourExecutableName -lpthread -lprussdrv

does the job.

Now for the assembler code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
.setcallreg r29.w0
.origin 0
.entrypoint START

#define AM33XX
#define PRU0_PRU1_INTERRUPT     17
#define PRU1_PRU0_INTERRUPT     18
#define PRU0_ARM_INTERRUPT      19
#define PRU1_ARM_INTERRUPT      20
#define ARM_PRU0_INTERRUPT      21
#define ARM_PRU1_INTERRUPT      22

#define CONST_PRUDRAM   C24
#define CONST_L3RAM     C30
#define CONST_DDR       C31
#define GPIO1 0x4804c000
#define GPIO_CLEARDATAOUT 0x190
#define GPIO_SETDATAOUT 0x194
#define CONST_PRUSHAREDRAM   C28
#define CTBIR_0         0x22020
// Address for the Constant table Block Index Register (CTBIR)
#define CTBIR          0x22020

// Address for the Constant table Programmable Pointer Register 0(CTPPR_0) 
#define CTPPR_0         0x22028 

// Address for the Constant table Programmable Pointer Register 1(CTPPR_1) 
#define CTPPR_1         0x2202C  
#define sp r24
#define lr r23


START:
LBCO      r0, C4, 4, 4
CLR     r0, r0, 4         // Clear SYSCFG[STANDBY_INIT] to enable OCP master port
SBCO      r0, C4, 4, 4
LOOPN:
MOV r3, 0x00012000
LBBO r4, r3, 0, 4
QBEQ EXIT, r4, 255
SET r30, 14
MOV r2, r4
CALL DEL
CLR r30, 14
MOV r2, 2000
CALL DEL
JMP LOOPN



DEL: //delay r2 * 10 us
MOV r5, 1000
INDEL:
SUB r5, r5, 1
QBNE INDEL, r5, 0
SUB r2, r2, 1
QBNE DEL, r2, 0
RET
EXIT:
MOV R31.b0, PRU0_ARM_INTERRUPT+16
HALT 

You should save this in a .p file - at least, that is what the examples do. It can be compiled via

pasm -p YourFileName.p

and you should change the C-code in line 58 to reflect your filename.

You can find a description of all the instructions at http://processors.wiki.ti.com/index.php/PRU_Assembly_Instructions.

In the very first line, we change the register used for storing return addresses of function calls: By default, it is r30, which interferes with our program since r30 also is the register we use to control the pin. More precisely, bit 14 of r30 now controls the gpio1_12 pin thanks to our device tree overlay; you can see it set and cleared in lines 41 and 44.

The lines 34-36 enable access of the PRU to the actual memory of the Beaglebone, not only the parts dedicated to the PRU. We do not actually use it here; you should be a bit careful with direct manipulation of the Beaglebone memory.You may find the information at http://hipstercircuits.com/beaglebone-pru-ddr-memory-access-the-right-way/ useful if you are interested in this.

In line 39, the LBCO command loads the content of memory at the location stored in the register r3 into the register r4. In our case, this is the same location (0x12000) our memory map in the preceding C-program pointed to, so this command loads the value we have stored there (and before that, passed to the C-program via the command line) into register r4.If this value is 255, we jump to the end of the program.  Everything else should be fairly straightforward if you have programmed any assembler before or can be easily found in the instruction set wiki linked above. Line 60 enables the interrupt, notifying the C-program that it can quit now.

Hoepfully, this is useful for someone - it likely will be useful for me in 6 months once I have forgotten everything again.

October 29, 2013

Register access to the GPIOs of the Beaglebone via memory mapping

On the Beaglebone Black, Linux abstracts the GPIO pins as files: To control a given pin, you can write to certain files. For example, if you want to turn off the blue LED on the far side of the board, navigate via command line to /sys/class/leds/beaglebone:green:usr0. The trigger file in this directory controls the behaviour of the LED. By default, it is set to heartbeat.The command

echo none > trigger

turns the LED off, whereas

echo default-on > trigger

turns it on. You can find more information at many places, for example here.
However, this method is fairly slow and -at least for me - somewhat unsatisfying since you do not see at all what is going on at the hardware level. The achievable toggle speed of a GPIO pin via the Linux file method is something like 5 kHZ, which is painfully slow. To achieve higher speeds and work closer to the hardware, we can use memory mapping and directly access the GPIO registers. For the following sample program, you should connect a LED to Pin 28 on GPIO board 1, which is pin 12 on Header 9. Make sure not to connect the LED directly - use a transistor instead; the GPIO pins can only supply 4 mA. Also make sure that the usr0-LED is off as explained above. The program will first blink the LED at pin 28 and the usr0 LED 5 times; then it turns pin 28 into an input which is polled once per second for 20 seconds, and the usr0-LED is switched on and off accordingly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <iostream>
#define OE_ADDR 0x134
#define GPIO_DATAOUT 0x13C
#define GPIO_DATAIN 0x138
#define GPIO0_ADDR 0x44E07000
#define GPIO1_ADDR 0x4804C000
#define GPIO2_ADDR 0x481AC000
#define GPIO3_ADDR 0x481AF000
using namespace std;

int main(){
    int fd = open("/dev/mem",O_RDWR | O_SYNC);
    ulong* pinconf1 =  (ulong*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_ADDR);
    pinconf1[OE_ADDR/4] &= (0xFFFFFFFF ^ (1 << 28));
    for(int i = 0 ; i < 5; i++){
     pinconf1[GPIO_DATAOUT/4]  |= (1 << 28);
     pinconf1[GPIO_DATAOUT/4]  ^= (1 << 21);
     sleep(1);
     pinconf1[GPIO_DATAOUT/4]  ^= (1 << 28);
     pinconf1[GPIO_DATAOUT/4]  |= (1 << 21);
     sleep(1);
    }
    pinconf1[GPIO_DATAOUT/4]  &= (0xFFFFFFFF ^ ((1 << 21) | (1 << 28)));
    pinconf1[OE_ADDR/4] |= ( 1 << 28);
    for(int i = 0; i < 20; i++){
     cout << pinconf1[GPIO_DATAIN/4]  << endl;
     if(pinconf1[GPIO_DATAIN/4] & (1  << 28)){
      cout << "on" <<endl;
      pinconf1[GPIO_DATAOUT/4]  |= (1 << 21);
     }
     else{
      cout << "off" <<endl;
      pinconf1[GPIO_DATAOUT/4] &= (0xFFFFFFFF ^ (1 << 21));
     }
     sleep(1);
    }
    pinconf1[GPIO_DATAOUT/4]  ^= (1 << 21);
    return 0;
}
Now, what precisely does this program do? The magic numbers appearing in the defines are the memory addresses of the registers controlling the GPIO pins, so we have a look into the technical reference manual of the AM335x-processor used in the Beaglebone Black, which happens to be a 4600 pages document. In Chapter 2, Memory Map, you find a long list with the memory addresses of the various registers controlling the behaviour of the processor. Scrolling down a few pages, you find the register address of GPIO1 is 0x4804C000, which we defined to be GPIO1_ADDR in oyr program. In my version of the reference manual, you find GPIO1 on page 211, but that may change in future versions of the reference manual.

So the registers at and directly after the address 0x4804C000 control the behaviour of the pins of GPIO1. This alone is not yet helpful, so we turn to chapter 25, General Purpose Input/Output, for more details. In the GPIO registers subsection, we find the addresses and descriptions of the various registers. The addresses given there are relative to the beginning of the GPIO1 registers, respectively the beginning of the GPIO0 and GPIO2 registers. For example, the GPIO_OE register has a relative address of 0x134, so the GPIO_OE register for GPIO1 will be at 0x4804C000+0x134 = 0x4804C134. Going back to the memory map in chapter 2 of the reference manual, we find that the GPIO0 registers start at 0x44E07000, so the GPIO_OE for GPIO0 will be at 0x44E07134, and similarly for GPIO2.

The GPIO_OE register is the register controlling whether a pin is an input or output. It is a 32bit register whose k-th bit corresponds to pin k of GPIO1. If this bit is 1 (as it is by default), the pin is an input; if it is zero, the pin is an output. The GPIO_DATAIN register is for reading the value of an input pin: If pin k is an input, its k-th bit is 0 if pin k is low and 1 if it is high. If pin k is an output, GPIO_DATAOUT is for setting the pin: Writing a 0 to bit k sets pin k low, writing 1 sets it high. Alternatively, you can use the GPIO_CLEARDATAOUT and GPIO_SETDATAOUT registers: writing a 1 to bit k clears respectively sets pin k.

Now we know which registers we have to control, but how can we actually control them? We use memory mapping to get a pointer which points to the beginning of GPIO1. This is achieved in the lines

int fd = open("/dev/mem",O_RDWR | O_SYNC);
ulong* pinconf1 = (ulong*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_ADDR);

This "maps the memory" at the physical location GPIO1_ADDR to the pointer pinconf1; we make it a pointer to ulong since a long happens to be 32bit on the Beaglebone and the registers are 32 bit. Note that the offset addresses of the various registers are in bytes, not in registers: This is why we have to divide by 4 later on in the code  since an ulong is 4 byte. For all practical purposes, pinconf1 is now a pointer pointing to the address GPIO1_ADDR; however, a "normal" pointer can only point to the memory allocated to the process, so we have to use memory mapping. The NULL argument to mmap specifies that Linux is free to put the memory map wherver it wants in the memory of the process; PROT_READ | PROT_WRITE specify that we can both read and write to the memoryand map_shared says that we actually access the underlying memory and not only the map in the memory of the process when we write to the memory map. The file /dev/mem we pass via the variable fd happens to be the place where Linux abstracts the phzsical memory as a file. The GPIO1_ADDR specifies where the memory map starts and the number 0x1000 = 4096 specifies its length - in this case, we map the 4 kB following 0x4804c000. If you consult the technical reference manual again, you will see that the GPIO registers for each GPIO board total 4kB, so we have precisely mapped all the registers for GPIO1.

The program is hopefully fairly self-explanatory now: pinconf1[OE_ADDR/4] is the memory at address 0x4804c000 (where our mmap begins) + 0x134 (the offset of OE). Remember that we have to divide by 4 since pinconf1 is an ulong pointer and the offsets are in bytes. Now pinconf1[OE_ADDR/4] &= (0xFFFFFFFF ^ (1 << 28)) sets pin 28 of GPIO1 to be an output and all other to inputs. Then pinconf1[GPIO_DATAOUT/4] |= (1 << 28) sets pin 28 to high. The register pinconf1[GPIO_DATAIN/4] stores the input values of the various pins; we read bit 28 from this register, which means reading the input of pin 28, and process the result accordingly.
All in all, this is a much neater and faster way to access the GPIO pins of the Beaglebone. Be aware, however, that not all pins can be accessed directly; some of the pins are by default reserved for non-GPIO stuff and have to be enabled via the device tree, which is a story for another day.

October 9, 2013

Building your own Infrared-controlled car, Part 1: Receiving infrared signals


Well, technically not a car, but a tracked vehicle. Here you can see it in action:

Now, why do I write about this? After all, you can easily build such a car purely from Lego parts! That is true. However, later on - when this vehicle is upgraded to something you could call a robot - I want to connect my own little computer and some sensors, and this is not really possible with pure Lego parts.

First, we need a remote. We could build our own, but the result would be rather clumsy, so we use an existing remote. I use the Lego IR remote, but in principle you could use your TV remote - which, of course, would not only control the car, but also still control your TV...

Before we can use the remote, we have to read out the infrared protocol used - we have to know what the remote sends when we press, say, button 1 on the remote. You might luck out and find a complete instruction set online for your remote, but this is no sure thing, and the work needed for the readout is basically the same work we have to do anyway to receive infrared signals at all, so it comes basically for free.

The infrared LED in the remote has two states: It is either plain off, or it will flash at a high frequency, typically 38 kHZ. It will flash for some time, then it is off for some time, then it will flash again. The message we want to send is either encoded into the time it flashes before turning off again, or - more commonly - into the time between two sets of flashes. The point of the flashing is that a 38 kHz flashing signal is much less likely to be of natural origin compared to non-flashing infrared light, which makes receiving less error-prone.

The most convenient way for receiving the signal is using a receiver IC like the TSOP312xx. There are several different ones, depending on the carrier frequency: The 31238 is for 38 kHz, the 31236 for 36 kHz and so on. The 38 kHz frequency is the most common one, and the TSOP31238 should also work for 36 kHZ remotes, with somewhat decreased range.

The TSOP has three pins, two of which are for the power supply. The third is for the output of the demodulated signal: If the IC sees the remote flashing, it will pull the output pin low; otherwise, it is high. On the output pin, we hence do not see the carrier frequency at all, which makes reading the signal much easier.

Now we can connect the TSOP to some microcontroller and measure when the output line goes low. I will use an Arduino since it is easy to connect to a PC. A bare AVR can also easily receive the signal, but getting the data onto your PC is more difficult. The setup looks as follows:

Not much to see here: The orange output line goes to pin 2 on the Arduino, which is the pin for the external interrupt 0, the other two wires are ground and 5 volt. You should check the exact wiring in the data sheet of your receiver - it may be different, and a wrong wiring will probably destroy the receiver. Also, you might want to add a capacitator over ground and 5 volt, but it works for me without. Whenever the output line goes low, the interrupt routine saves the time passed since the last interrupt or (in case of the Arduino code) just the time in microseconds since startup.

Below you find the sourcecode in plain C and for the Arduino; but you should be careful with the Arduino code: the interrupt routine has lots of overhead, and this seems to be the outer limit of what is achievable in this way - I crashed it a few times when the program had a longer interrupt routine, so this approach is probably out for use on the car since there the Arduino has to do other stuff as well.

The source-code in Arduino-speak:

volatile unsigned long timevalues[150];
volatile int i = 0;
int finished = 0;
void setup(){
   attachInterrupt(0, external, FALLING); 
   Serial.begin(9600); 
   Serial.println("starting program");
}
void loop(){

  if(finished == 0){
 if(i > 140){
   finished = 1;
   for(int j = 1; j < 140; j++){
  Serial.println(timevalues[j]-timevalues[j-1]);
     
       }
    
 }
  
  
}
}
void external(){
  //if(timecounter > 1){
    timevalues[i] = micros();
    i++;
 //} 

}
and in AVR-GCC:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define BAUDRATE 9600
#define BAUD_PRESCALLER (((F_CPU / (BAUDRATE * 16UL))) - 1)

//Declaration of our functions
void USART_init(void);
void USART_send( unsigned char data);





volatile int i = 0;
volatile int timecounter = 0;
uint8_t finished = 0;
volatile uint8_t timevalues[150];

int main(void){
 USART_init();        //Call the USART initialization code
 EICRA = (1 << ISC01); //interrupt on falling edge
 EIMSK = (1 << INT0); //enable INT0-interrupt
 TCCR0A = (1 << WGM01); //clear timer on compare match
 
 TCCR0B = (1 << CS01) | ( 1 << CS00); //prescaler 64
 OCR0A = 16; //one timer interrupt every 16 ticks of the clock, which here means every 64 microseconds
 TIMSK0 |= (1 << OCIE0A); //turn timer interrupt A on
 sei();
 while(1){        //Infinite loop
 if(finished == 0){
  if(i > 100){
   cli();
   finished = 1;
   for(int j = 0; j < 100; j++){
    USART_send(timevalues[j]);
    
   }
   
  }
 }
 
 
}

return 0;
}

ISR(INT0_vect){
 if(timecounter > 1){
 timevalues[i] = timecounter;
 i++;
 timecounter = 0;
 } 
}

ISR(TIMER0_COMPA_vect){
 timecounter++;
} 


void USART_init(void){
 
 UBRR0H = (uint8_t)(BAUD_PRESCALLER>>8);
 UBRR0L = (uint8_t)(BAUD_PRESCALLER);
 UCSR0B = (1<<RXEN0)|(1<<TXEN0);
 UCSR0C = (3<<UCSZ00);
}

void USART_send( unsigned char data){
 
 while(!(UCSR0A & (1<<UDRE0)));
 UDR0 = data;
 
}
The principle is the same both times: Wait for 100 received signals and then send the time intervals over UART. The output of the Arduino program is the time in microseconds between flashes; the output of the C-code (for which you will need a serial terminal, for example Realterm) is in 64 microseconds, i.e. a "10" means roughly 640 microseconds. The precision is still sufficient, and it saves memory and processor cycles.

Part of the output when I press 1 on my TV remote for the Arduino code is

13464 1124 1156 1100 1124 1156 1100 2240 1156 2216 2244 2240 2244 2244 2244 1152 2216 2244 1152 1128 1128 1100 1152 1100 1128 1152 2216 2248 2240 2240 2244 2244 2244 40312 11208 96448 11208 96452 11208 96452 11208 96448 11208 96452 11208 96452 11208 96452 11208 96448 11212 96448 11208 96452 11208 96452 11208 96452 11208 96452 11208 96448 11208 96452 11208 96452 11208 96452 11208 96452 11208 96452 11208 96448 11212

It uses the NEC protocol, and you will find all the numbers above also in the linked document. We start with a pause of 13 ms, signifying the beginning of the transmission. Then we send the code 00000010111111011000000001111111 - a 0 for the short pause of 1.1 ms, a 1 for the long break of 2.2 ms. Then the remote starts a "continue" pattern, which is also described in the link: This signifies the receiver that the button keeps getting pressed.

I have a blog


Finally, I managed to get my own blog.

I intend to blog about my microcontroller and robotic projects. Why do I do this? I hope that some people will find my future posts useful and/or interesting, and I would like to have write-ups of my own projects anyway - and this blog will hopefully act as a source of motivation for actually creating these write-ups.

My current project is a Robot made from Lego, a microcontroller, and a Beaglebone Black respectively a Raspberry Pi computer for some basic image processing. The first step will be making the electronics for an infrared-controlled vehicle. This could be done purely with Lego parts; however, we later on want to steer everything via a computer, and hence will need to interface a computer into the circuit.