ChibiOS kernel on Olimex STM32-P152

Posted on 2014/05/02

0


I wanted to try the ChibiOS/RT real-time operating system on my micro-controller board that mounts an STM32L152VBT6.

ChibiOS is one of many alternative real-time operating systems such as FreeRTOS, RTEMS, eCos and NuttX. It supports many platforms and it’s actively developed by Giovanni Di Sirio (full disclosure: we both work at STMicroelectronics).

I downloaded the 2.6.3 stable version and started exploring the content of the source code. Basically these are the main parts of ChibiOS code:

  • The kernel, containing thread management, synchronization, power modes, etc.
  • The Hardware Abstraction Layer (HAL) which exports a generic API for many widespread peripherals and hides the implementation details.
  • The startup code/makefiles/linker scripts to make it all work

Then there are demos, test code, external libraries and other stuff such as C++ wrappings. I discovered that my board is not directly supported, but the STM32L1xx is. This means I can use the kernel and startup code but not the HAL until someone (maybe me? I’ll see) writes the code to support it.

The source tree is full of demos that are specific to particular architectures and boards. I got the demo called ARMCM3-GENERIC-KERNEL and copied it as ARMCM3-STM32L152-OLIMEX. Then I modified the Makefile because it pointed to STM32F103xB files. I substituted the relavant lines with:

include $(CHIBIOS)/os/ports/GCC/ARMCMx/STM32L1xx/port.mk

LDSCRIPT= $(PORTLD)/STM32L152xB.ld

And I ran “make“. The compilation went well, but the program seems to do nothing externally visible so I didn’t even try to flash it. Now I needed to do something such as LED blinking to see if it works on my board. Here’s the “main.c” that I quickly wrote based on the blink example of this previous post (most of the information needed to write this code comes from ChibiOS docs, Olimex board datasheet and STM32L152 reference manual).

#include "ch.h"

#if !defined(SYSTEM_CLOCK)
#define SYSTEM_CLOCK 8000000
#endif

#define REG32(addr) (*(volatile uint32_t *)(addr))

#define GPIOE_BASE   0x40021000
#define GPIOE_MODER  REG32(GPIOE_BASE + 0x00)
#define GPIOE_OTYPER REG32(GPIOE_BASE + 0x04)
#define GPIOE_ODR    REG32(GPIOE_BASE + 0x14)

#define RCC_BASE 0x40023800
#define RCC_AHBENR REG32(RCC_BASE + 0x1C)
#define RCC_AHBENR_GPIOEEN 0x10

#define STAT3_PIN 10
#define STAT3_PIN_MASK (1UL<<STAT3_PIN)
#define STAT4_PIN 11
#define STAT4_PIN_MASK (1UL<<STAT4_PIN)

#define RCC_CR REG32(RCC_BASE + 0x00)
#define RCC_CR_HSEON 0x10000
#define RCC_CR_HSERDY 0x20000
#define RCC_CFGR REG32(RCC_BASE + 0x08)
#define RCC_CFGR_SW 0x3
#define RCC_CFGR_SW_HSE 0x2
#define RCC_CFGR_SWS 0xC
#define RCC_CFGR_SWS_HSE 0x8

static void set_gpioe_moder(int pin, int mode)
{
    uint32_t moder;
    uint32_t moder_pin_pos;
    uint32_t moder_pin_mask;

    moder_pin_pos = pin*2; // 2 bits per pin
    moder_pin_mask = 0x3UL << moder_pin_pos;

    moder = GPIOE_MODER; // read from register
    moder &= ~moder_pin_mask; // clear moder pin field
    moder |= (mode << moder_pin_pos); // set moder pin field
    GPIOE_MODER = moder; // write to register
}

static void hw_init(void)
{
    uint32_t cfgr;

    // Set HSE as 8MHz system clock
    RCC_CR |= RCC_CR_HSEON; // Enable High Speed Internal clock
    while(!(RCC_CR & RCC_CR_HSERDY)) // Wait for HSE ready
    {
        continue;
    }
    cfgr = RCC_CFGR;
    cfgr &= ~RCC_CFGR_SW;
    cfgr |= RCC_CFGR_SW_HSE; // Select HSE as system clock
    RCC_CFGR = cfgr;
    while((RCC_CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSE) // Wait for HSE as system clock
    {
        continue;
    }

    // Set SysTick as 1000 system clock cycles
    STBase->RVR = SYSTEM_CLOCK / CH_FREQUENCY - 1;
    STBase->CVR = 0;
    STBase->CSR = CLKSOURCE_CORE_BITS | ENABLE_ON_BITS | TICKINT_ENABLED_BITS;

    // STAT3 and STAT4 LEDs
    RCC_AHBENR |= RCC_AHBENR_GPIOEEN; // enable GPIOE clock
    GPIOE_OTYPER |= STAT3_PIN_MASK; // open-drain
    set_gpioe_moder(STAT3_PIN, 1); // general purpose output
    GPIOE_OTYPER |= STAT4_PIN_MASK; // open-drain
    set_gpioe_moder(STAT4_PIN, 1); // general purpose output
}

static WORKING_AREA(waThread1, 128);
static msg_t Thread1(void *arg)
{
    while (TRUE)
    {
        GPIOE_ODR |= STAT3_PIN_MASK; // output pin low -> LED ON
        chThdSleepMilliseconds(1000);
        GPIOE_ODR &= ~STAT3_PIN_MASK; // output pin high-z -> LED OFF
        chThdSleepMilliseconds(1000);
    }
}

static WORKING_AREA(waThread2, 128);
static msg_t Thread2(void *arg)
{
    while (TRUE)
    {
        GPIOE_ODR |= STAT4_PIN_MASK; // output pin low -> LED ON
        chThdSleepMilliseconds(1100);
        GPIOE_ODR &= ~STAT4_PIN_MASK; // output pin high-z -> LED OFF
        chThdSleepMilliseconds(1100);
    }
}

int main(void)
{
    hw_init();
    chSysInit();

    chThdCreateStatic(
          waThread1, sizeof(waThread1),
          NORMALPRIO, Thread1, NULL);
    chThdCreateStatic(
          waThread2, sizeof(waThread2),
          NORMALPRIO, Thread2, NULL);

    while (TRUE) {
        chThdSleepSeconds(TIME_INFINITE);
    }
}

Basically the program does the following:

  • Choose the external oscillator (HSE) as system clock, so that it uses the 8MHz quartz mounted on board.
  • Configure the System Tick interrupt that is used by ChibiOS kernel to manage events, threads and synchronization.
  • Initialize GPIOs to drive the STAT3/STAT4 LEDs.
  • Run two parallel threads, each of them make one LED blink at a certain different frequency.
  • The main thread goes to sleep and does nothing forever.

I ran “make” again, compiling the program, and then I flashed it on board using the same method in the same previous blog post. After the reset, the result is that the red LED STAT3 and the green LED STAT4 start blinking at a speed that, for my naked eye, seem correct (about 1 second to toggle between on and off).

Next possible steps could be to write something for the HAL and use it to drive various things such as the serial port, buttons and ADC.

Posted in: Embedded