Pages

Saturday, June 7, 2014

ChibiOS/RT, I2C on the Raspberry Pi Rev. 2 board

As an alternative to loading Linux on the Raspberry Pi, you maybe looking for a real-time kernel with a small footprint where you can run your real-time applications. A real-time os might be appropriate for applications that need low latency and real-time response to events. By real-time response, I meant predictable response - a non-real time OS doesn't guarantee that. There were a variety of real-time kernels out there including FreeRTOS and ChibiOS/RT, the later being one of my favorites.

There was already a very good starting point where ChibiOS/RT is ported to the raspberry pi:

http://www.stevebate.net/chibios-rpi/GettingStarted.html

However, Steve's implementation uses a raspberry pi that is older than the version that I have. The older version of the raspberry pi is using i2c bus 0 on the header, the raspberry pi revision 2 boards uses i2c bus 1 instead. And so because of this, we need to make some revisions to the initial port.

We would also need an appropriate cross compiler tool chain that targets ARM bare metal binaries. I am on Fedora 19, the cross compiler tool chain that we are going to use will be from the Fedora distribution itself. There's another Linaro toolchain that I could use, but opted to use Fedora instead since its readily available. To download the tool chain, you could use yum:


[root@vcodev vco]# yum install arm-none-eabi*

That should download all the tool necessary for this experiment.

Once we have the tools necessary, you should be able to follow the steps below:

1) Clone steve's chibios sources


[vco@vcodev Projects]$ git clone https://github.com/steve-bate/ChibiOS-RPi
Cloning into 'ChibiOS-RPi'...
remote: Counting objects: 56983, done.
remote: Compressing objects: 100% (10494/10494), done.
remote: Total 56983 (delta 45384), reused 56983 (delta 45384)
Receiving objects: 100% (56983/56983), 29.76 MiB | 1.55 MiB/s, done.
Resolving deltas: 100% (45384/45384), done.
Checking connectivity... done.
[vco@vcodev Projects]$

2) Once downloaded, navigate into the HAL drivers directory. This contains hardware device drivers for the BCM2835 chip (raspberry pi).


[vco@vcodev Projects]$ cd ChibiOS-RPi/os/hal/platforms/BCM2835/
[vco@vcodev BCM2835]$

3) We will then make changes to three files i2c_lld.c, i2c_lld.h, and hal_lld.c. The changes that we are going to do is to make the I2C bus 1 (in raspberry pi revision 2 board) available in the headers. For convenience, I've provided a patch file so you won't have to edit the sources manually. The patch file is located here

You can apply the patch with


[vco@vcodev ChibiOS-RPi]$ patch -p0 < patchfile
patching file os/hal/platforms/BCM2835/hal_lld.c
patching file os/hal/platforms/BCM2835/i2c_lld.c
patching file os/hal/platforms/BCM2835/i2c_lld.h
[vco@vcodev ChibiOS-RPi]$

To get an idea of what has been changed, here's a summary of changes from the i2c_lld.c file.


 38 /*===========================================================================*/
 39 /* Driver exported variables.                                                */
 40 /*===========================================================================*/
 41
 42 I2CDriver I2C0;
 43 I2CDriver I2C1;

Line 43 above simply diclares I2C1 driver for bus 1. 


136 /**
137  * @brief   Low level I2C driver initialization.
138  *
139  * @notapi
140  */
141 void i2c_lld_init(void) {
142   I2C0.device = BSC0_ADDR;
143   i2cObjectInit(&I2C0);
144
145   I2C1.device = BSC1_ADDR;
146   i2cObjectInit(&I2C1);
147 }

Lines 145 and 146 initializes the bus driver.


149 /**
150  * @brief   Configures and activates the I2C peripheral.
151  *
152  * @param[in] i2cp      pointer to the @p I2CDriver object
153  *
154  * @notapi
155  */
156 void i2c_lld_start(I2CDriver *i2cp) {
157   /* Set up GPIO pins for I2C */
158   bcm2835_gpio_fnsel(GPIO0_PAD, GPFN_ALT0);
159   bcm2835_gpio_fnsel(GPIO1_PAD, GPFN_ALT0);
160
161   /* Set up GPIO pins for I2C on Rev. 2 boards*/
162   bcm2835_gpio_fnsel(GPIO2_PAD, GPFN_ALT0);
163   bcm2835_gpio_fnsel(GPIO3_PAD, GPFN_ALT0);

Lines 162 and 163 selects an alternate function for I2C on the raspberry pi header. Based on the specs for revision 2, GPIO2 and GPIO3 are now exported on the header instead of GPIO0 and GPIO1.


4) Now that our ChibiOS sources are patched. We now create a ChibiOS project where we can use the i2c bus for our experiments. For this example, we will be writing to the I2C bus - to device address 0x21 which holds our LED's via the MCP23017 I2C IO extender chip.

5) To get around this quickly, we will simply have to copy an existing example and modify it rather than having to build it from scratch. Navigate to the ChibiOS-RPi/demos directory. 

There's an existing ARM11-BCM2835-GCC project that we can use as a template for this experiment. You may want to copy this to a new directory or edit the project in place. This project already enables I2C on the build (see halconf.h), so we don't have to make necessary adjustments. The only thing we need to do is to define a thread in ChibiOS that will continually write a value to the I2C bus (in this case a counter value).

6) Edit the file main.c and add the following lines before the beginning of the main() function.


static const uint8_t i2cled_address = 0x21;
static const uint8_t i2cled_writereg = 0x14;
static const uint8_t i2cled_confreg = 0x00;

static msg_t i2cled_write(uint8_t device_address,
        uint8_t register_address,
        uint8_t data)
{
    uint8_t request[2];
    request[0] = register_address;
    request[1] = data;

    i2cAcquireBus(&I2C1);
    msg_t status = i2cMasterTransmitTimeout(
            &I2C1, device_address, request, 2,
            NULL, 0, MS2ST(1000));
    i2cReleaseBus(&I2C1);

    if (status != RDY_OK)
        chprintf((BaseSequentialStream *)&SD1, "Error while writing to i2cled: %d\r\n", status);

    return status;
}

static void i2cled_init(uint8_t device_address, uint8_t dirmask)
{
    msg_t status = i2cled_write(device_address,
            i2cled_confreg, // direction register.
            dirmask);

    if (status != RDY_OK)
        chprintf((BaseSequentialStream *)&SD1, "Error while setting direction mask: %d\r\n", status);
}

static WORKING_AREA(waI2CCounterThread, 128);
static msg_t I2CCounterThread(void *p)
{
    (void)p;
    chRegSetThreadName("counter");
    i2cled_init(i2cled_address, 0x00);
    uint8_t count = 0;
    while (TRUE)
    {
        i2cled_write(i2cled_address, i2cled_writereg, count);
        chThdSleepMilliseconds(100);
        count++;
    }

    return 0;
}

7) Now at the main function, add the following lines below before the event servicing loop of the chibios rtos begins:


  /*
   * I2C Initialization
   */
  I2CConfig i2cConfig;
  i2cStart(&I2C1, &i2cConfig);

  /*
   * Create the i2c led counter thread
   */
  chThdCreateStatic(waI2CCounterThread, sizeof(waI2CCounterThread), NORMALPRIO, I2CCounterThread, NULL);

8) Now that we have created the counter thread, we can go ahead and compile our chibios, to do that simply invoke make from the demos/ARM11-BCM2835-GCC directory:


[vco@vcodev ARM11-BCM2835-GCC]$ make
Compiler Options
arm-none-eabi-gcc -c -mcpu=arm1176jz-s -O2 -ggdb -fomit-frame-pointer -ffunction-sections -fdata-sections -Wall -Wextra -Wstrict-prototypes -Wa,-alms=build/lst/ -mno-thumb-interwork -MD -MP -MF .dep/build.d -I. -I../../os/ports/GCC/ARM -I../../os/ports/GCC/ARM/BCM2835 -I../../os/kernel/include -I../../test -I../../os/hal/include -I../../os/hal/platforms/BCM2835 -I../../boards/RASPBERRYPI_MODB -I../../os/various main.c -o main.o

mkdir -p build/obj
mkdir -p build/lst
Compiling ../../os/ports/GCC/ARM/crt0.s
Compiling ../../os/ports/GCC/ARM/chcoreasm.s
Compiling ../../os/ports/GCC/ARM/BCM2835/vectors.s
Compiling ../../os/ports/GCC/ARM/chcore.c
Compiling ../../os/kernel/src/chsys.c
Compiling ../../os/kernel/src/chdebug.c
Compiling ../../os/kernel/src/chlists.c
Compiling ../../os/kernel/src/chvt.c
Compiling ../../os/kernel/src/chschd.c
Compiling ../../os/kernel/src/chthreads.c
Compiling ../../os/kernel/src/chdynamic.c
Compiling ../../os/kernel/src/chregistry.c
Compiling ../../os/kernel/src/chsem.c
Compiling ../../os/kernel/src/chmtx.c
Compiling ../../os/kernel/src/chcond.c
Compiling ../../os/kernel/src/chevents.c
Compiling ../../os/kernel/src/chmsg.c
Compiling ../../os/kernel/src/chmboxes.c
Compiling ../../os/kernel/src/chqueues.c
Compiling ../../os/kernel/src/chmemcore.c
Compiling ../../os/kernel/src/chheap.c
Compiling ../../os/kernel/src/chmempools.c
Compiling ../../test/test.c
Compiling ../../test/testthd.c
Compiling ../../test/testsem.c
Compiling ../../test/testmtx.c
Compiling ../../test/testmsg.c
Compiling ../../test/testmbox.c
Compiling ../../test/testevt.c
Compiling ../../test/testheap.c
Compiling ../../test/testpools.c
Compiling ../../test/testdyn.c
Compiling ../../test/testqueues.c
Compiling ../../test/testbmk.c
Compiling ../../os/hal/src/hal.c
Compiling ../../os/hal/src/adc.c
Compiling ../../os/hal/src/can.c
Compiling ../../os/hal/src/ext.c
Compiling ../../os/hal/src/gpt.c
Compiling ../../os/hal/src/i2c.c
Compiling ../../os/hal/src/icu.c
Compiling ../../os/hal/src/mac.c
Compiling ../../os/hal/src/mmc_spi.c
Compiling ../../os/hal/src/mmcsd.c
Compiling ../../os/hal/src/pal.c
Compiling ../../os/hal/src/pwm.c
Compiling ../../os/hal/src/rtc.c
Compiling ../../os/hal/src/sdc.c
Compiling ../../os/hal/src/serial.c
Compiling ../../os/hal/src/serial_usb.c
Compiling ../../os/hal/src/spi.c
Compiling ../../os/hal/src/tm.c
Compiling ../../os/hal/src/uart.c
Compiling ../../os/hal/src/usb.c
Compiling ../../os/hal/platforms/BCM2835/hal_lld.c
Compiling ../../os/hal/platforms/BCM2835/pal_lld.c
Compiling ../../os/hal/platforms/BCM2835/serial_lld.c
Compiling ../../os/hal/platforms/BCM2835/i2c_lld.c
Compiling ../../os/hal/platforms/BCM2835/spi_lld.c
Compiling ../../os/hal/platforms/BCM2835/gpt_lld.c
Compiling ../../os/hal/platforms/BCM2835/pwm_lld.c
Compiling ../../os/hal/platforms/BCM2835/bcm2835.c
Compiling ../../boards/RASPBERRYPI_MODB/board.c
Compiling ../../os/various/shell.c
Compiling ../../os/various/chprintf.c
Compiling main.c
Linking build/ch.elf
Creating build/ch.hex
Creating build/ch.bin
Creating build/ch.dmp
Done
[vco@vcodev ARM11-BCM2835-GCC2]$

9) After compiling, the os image is now stored in build/ch.bin. Remember in the Raspberry pi, the kernel image is stored in kernel.img on your SD card. To boot chibios, simply replace the kernel.img on your SD card with ch.bin (in my case the sd card's fat partition is mounted to /media/chibifat directory):



[vco@vcodev ARM11-BCM2835-GCC2]$ cd build
[vco@vcodev build]$ ls
ch.bin  ch.dmp  ch.elf  ch.hex  ch.map  lst  obj
[vco@vcodev build]$ cp ch.bin /media/chibifat/kernel.img

10) Now we have a bootable chibios image that we can run on our raspberry pi. The ChibiOS for raspberry pi also includes a shell which is available via the UART on the header. If you happen to have the FTDI pi to TTL adapter, go ahead and connect those, as they'll become important during debugging. Take note that the raw RX and TX pins on the raspberry pi breakout header are NOT TTL 5V, connecting it directly to your comm port may fry your raspberry pi board. 




The only downside on ChibiOS/RT on the raspberry pi is that the RTOS might be over dressed for such hardware. Note that ChibiOS/RT doesn't yet support USB host though, it does have USB device and CDC. But the most important hardware on the raspberry pi that I would really love an ChibiOS to access is ethernet. Unfortunately the ethernet hardware on the raspberry pi is implemented on top of the USB host controller chip - something that ChibiOS/RT does not support. I guess I'll just have to wait ...

No comments: