Projects & Blog | STM-ChibiOS

STM32F4xx And ChibiOS

Getting Started

This is a quick guide on how to start with a STM32 eval board and ChibiOS. In this example I started a small project with a STM32F429 discovery board and the CibiOS version 18.2.1. The tutorial should be applicable with other boards and also newer ChibiOS versions.

First of all, what is ChibiOS? From their website:
“ChibiOS is a complete development environment for embedded applications including RTOS, an HAL, peripheral drivers, support files and tools. ChibiOS also integrates external Open Source components in order to offer a complete solution for embedded devices. The ChibiOS components are available under Open Source licenses, GPL3 or Apache 2.0.” (http://www.chibios.org/)

Let's get it started, the folder structure I use:

.
├── chconf.h
├── halconf.h
├── main.c
├── Makefile
├── mcuconf.h
├── os
└── src
    ├── io
    ├── lcd
    ├── usb
    ├── util
    └── pendelum

In the sub-folder "os" the ChibiOS ressources are located (download them here: https://osdn.net/projects/chibios/releases). The files halconf.h, chconf.h and mcuconf.h are also provided by ChibiOS. In those files the user can configure which sub-systems and drivers of ChibiOS are needed / shall be included in the build. In the subfolder "src" are all of the user sources of my application.

Sources - Makefile

# user source modules / subfolders in src here
MODULES := io pendelum lcd usb util

# General settings
    OPT_OS                  = chibios
    OPT_THUMB               = yes
    OPT_LINK_OPTIMIZE       = no
    OPT_CPU                 = stm32m4

# Compiler options here.
ifeq ($(USE_OPT),)
    USE_OPT = -O2 -ggdb -fomit-frame-pointer -falign-functions=16 -lm
endif

# C specific options here (added to USE_OPT).
ifeq ($(USE_COPT),)
    USE_COPT =
endif

# C++ specific options here (added to USE_OPT).
ifeq ($(USE_CPPOPT),)
    USE_CPPOPT = -fno-rtti
endif

# Enable this if you want the linker to remove unused code and data
ifeq ($(USE_LINK_GC),)
    USE_LINK_GC = yes
endif

# Linker extra options here.
ifeq ($(USE_LDOPT),)
    USE_LDOPT = 
endif

# Enable this if you want link time optimizations (LTO)
ifeq ($(USE_LTO),)
    USE_LTO = yes
endif

# If enabled, this option allows to compile the application in THUMB mode.
ifeq ($(USE_THUMB),)
    USE_THUMB = yes
endif

# Enable this if you want to see the full log while compiling.
ifeq ($(USE_VERBOSE_COMPILE),)
    USE_VERBOSE_COMPILE = no
endif

# If enabled, this option makes the build process faster by not compiling
# modules not used in the current configuration.
ifeq ($(USE_SMART_BUILD),)
    USE_SMART_BUILD = yes
endif

#
# Build global options
##############################################################################

##############################################################################
# Architecture or project specific options
#

# Stack size to be allocated to the Cortex-M process stack. This stack is
# the stack used by the main() thread.
ifeq ($(USE_PROCESS_STACKSIZE),)
    USE_PROCESS_STACKSIZE = 0x400
endif

# Stack size to the allocated to the Cortex-M main/exceptions stack. This
# stack is used for processing interrupts and exceptions.
ifeq ($(USE_EXCEPTIONS_STACKSIZE),)
    USE_EXCEPTIONS_STACKSIZE = 0x400
endif

# Enables the use of FPU (no, softfp, hard).
ifeq ($(USE_FPU),)
    USE_FPU = no
endif

#
# Architecture or project specific options
##############################################################################

##############################################################################
# Project, sources and paths
#

# Define project name here
PROJECT = pendel

# Imported source files and paths
CHIBIOS = ./

# Licensing files.
include $(CHIBIOS)/os/license/license.mk
# Startup files.
include $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC/mk/startup_stm32f4xx.mk
# HAL-OSAL files (optional).
include $(CHIBIOS)/os/hal/hal.mk
include $(CHIBIOS)/os/hal/ports/STM32/STM32F4xx/platform.mk
include $(CHIBIOS)/os/hal/boards/ST_STM32F429I_DISCOVERY/board.mk
include $(CHIBIOS)/os/hal/osal/rt/osal.mk
# RTOS files (optional).
include $(CHIBIOS)/os/rt/rt.mk
include $(CHIBIOS)/os/common/ports/ARMCMx/compilers/GCC/mk/port_v7m.mk
# Other files (optional).
include $(CHIBIOS)/os/hal/lib/streams/streams.mk
include $(CHIBIOS)/os/various/shell/shell.mk

# Define linker script file here
LDSCRIPT= $(STARTUPLD)/STM32F429xI.ld

# C sources that can be compiled in ARM or THUMB mode depending on the global
# setting.
CSRC = $(ALLCSRC) $(CSRCS) \
$(TESTSRC) \
main.c

# C++ sources that can be compiled in ARM or THUMB mode depending on the global
# setting.
CPPSRC = $(ALLCPPSRC)

# C sources to be compiled in ARM mode regardless of the global setting.
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
#       option that results in lower performance and larger code size.
SRC_DIR   := $(addprefix src/,$(MODULES))
SRC       := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c))
# INCLUDES  := $(SRC_DIR) # add without prefix
INCLUDES  := $(addprefix -I,$(SRC_DIR)) # add with prefix

ACSRC = $(SRC)
USERINCDIR = $(INCLUDES)

# C++ sources to be compiled in ARM mode regardless of the global setting.
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
#       option that results in lower performance and larger code size.
ACPPSRC = $(ALLCPPSRC)

# C sources to be compiled in THUMB mode regardless of the global setting.
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
#       option that results in lower performance and larger code size.
TCSRC =

# C sources to be compiled in THUMB mode regardless of the global setting.
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
#       option that results in lower performance and larger code size.
TCPPSRC =

# List ASM source files here
ASMSRC = $(ALLASMSRC)
ASMXSRC = $(ALLXASMSRC)

INCDIR = $(ALLINC) $(TESTINC) $(USERINCDIR) $(INCLVGL)

#
# Project, sources and paths
##############################################################################

##############################################################################
# Compiler settings
#

MCU  = cortex-m4

#TRGT = arm-elf-
TRGT = arm-none-eabi-
CC   = $(TRGT)gcc
CPPC = $(TRGT)g++
# Enable loading with g++ only if you need C++ runtime support.
# NOTE: You can use C++ even without C++ support if you are careful. C++
#       runtime support makes code size explode.
LD   = $(TRGT)gcc
#LD   = $(TRGT)g++
CP   = $(TRGT)objcopy
AS   = $(TRGT)gcc -x assembler-with-cpp
AR   = $(TRGT)ar
OD   = $(TRGT)objdump
SZ   = $(TRGT)size
HEX  = $(CP) -O ihex
BIN  = $(CP) -O binary

# ARM-specific options here
AOPT =

# THUMB-specific options here
TOPT = -mthumb -DTHUMB

# Define C warning options here
CWARN = -Wall -Wextra -Wundef -Wstrict-prototypes

# Define C++ warning options here
CPPWARN = -Wall -Wextra -Wundef

#
# Compiler settings
##############################################################################

##############################################################################
# Start of user section
#

# List all user C define here, like -D_DEBUG=1
UDEFS =

# Define ASM defines here
UADEFS =

# List all user directories here
UINCDIR =

# List the user directory to look for the libraries here
ULIBDIR =

# List all user libraries here
ULIBS =

#
# End of user defines
##############################################################################

RULESPATH = $(CHIBIOS)/os/common/startup/ARMCMx/compilers/GCC
include $(RULESPATH)/rules.mk

.PHONY: flash
flash: 
    st-flash write ./build/pendel.bin 0x8000000

test:
    echo "building"
    @$(MAKE) 
    echo "flashing"
    @$(MAKE) flash

Sources - main.c

The main.c is the entry point of the application. It initializes the operating system, the HAL and then starts two user chibiOS worker threads.

thread_ctrl_init, main_task and io_task are methods defined in the user code in "./src/pendelum/threadctrl.h".

#include "ch.h"
#include "hal.h"

#include "./src/pendelum/threadctrl.h"

static THD_WORKING_AREA(waThread1, 128);
static THD_WORKING_AREA(waThread2, 1024);

/**
 * Application entry point. Inits main system and starts all sub threads
 */
int main(void) 
{
    /*
     * System initializations.
     * - HAL initialization, this also initializes the configured device drivers
     *   and performs the board-specific initializations.
     * - Kernel initialization, the main() function becomes a thread and the
     *   RTOS is active.
     */
    halInit();
    chSysInit();

    // Initialize Thread control, the thread ctrl also handles the main context
    thread_ctrl_init();

    // Creating the worker threads
    chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 10, main_task, NULL);
    chThdCreateStatic(waThread2, sizeof(waThread2), NORMALPRIO + 10, io_task, NULL);

    return 0;
}

After including the ChibiOS and hal headers various usefull os features can be used.

#include "ch.h"
#include "hal.h"

e.g. reading a GPIO pin:

button_pressed = palReadPad(pad, pad_bit);

or using the ADC:

static adcsample_t irSamples[IR_ADC_GRP1_NUM_CHANNELS * IR_ADC_GRP1_BUF_DEPTH];

void adc_callback(ADCDriver *adcp, const adcsample_t *buffer, size_t n)
{
    // using / buffering adc data here
}

static const ADCConversionGroup adcgrpcfg1 = 
{
        FALSE, // circular buffer mode
        IR_ADC_GRP1_NUM_CHANNELS, // number of the analog channels
        (adccallback_t) &adc_callback, // callback function
        NULL, // error callback
        0, // CR1
        ADC_CR2_SWSTART, // CR2
        ADC_SMPR1_SMP_AN11(ADC_SAMPLE_15) |
        ADC_SMPR1_SMP_AN14(ADC_SAMPLE_15) |
        ADC_SMPR1_SMP_AN15(ADC_SAMPLE_15),
        0,// sample times for channel 0-9
        ADC_SQR1_NUM_CH(IR_ADC_GRP1_NUM_CHANNELS),// ADC SQR1 Conversion group sequence 13-16 + sequence length.
        0, // ADC SQR2 Conversion group sequence 7-12
        ADC_SQR3_SQ1_N(ADC_CHANNEL_IN11) |
        ADC_SQR3_SQ2_N(ADC_CHANNEL_IN14) |
        ADC_SQR3_SQ3_N(ADC_CHANNEL_IN15)
};

void adcInit()
{
    palSetGroupMode(GPIOC, PAL_PORT_BIT(1), 0, PAL_MODE_INPUT_ANALOG); //Pin PC1
    palSetGroupMode(GPIOC, PAL_PORT_BIT(4), 0, PAL_MODE_INPUT_ANALOG); //Pin PC4
    palSetGroupMode(GPIOC, PAL_PORT_BIT(5), 0, PAL_MODE_INPUT_ANALOG); //Pin PC5
    adcStart(&ADCD1, NULL);
    adcStartConversion(&ADCD1, &adcgrpcfg1, irSamples, IR_ADC_GRP1_BUF_DEPTH);
}