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.
# 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
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);
}