Hi everybody,
I share with you the experience I had in order to run FreeRTOS on an embedded device (stm32) with Zig.
Here is the repository with some blinky leds examples (with and without freertos)
Context
These examples utilize the STM32CubeMX code generator as well as the drivers provided by ST.
I wanted to see how well the Zig language can be integrated into an existing C codebase that use theses and the complexity of integrating it
Run Zig
The Code Generator will initialize any necessary peripheral system and then run our application. I then want to start my code in Zig. To do this, nothing could be simpler.
Add this to the main.c
:
extern void zig_entrypoint(void);
...
zig_entrypoint(); //This function never return
here, the main.zig
need :
export fn zig_entrypoint() void {
//code here (don't return)
}
Now, letâs do some C with zig
HAL Drivers
To blinker the led, we will use the HAL library provided by ST. Letâs import them!
const c = @cImport({
@cDefine("USE_HAL_DRIVER", {});
@cDefine("STM32L476xx", {});
@cInclude("main.h"); //It will include HAL and some pin definition from nucleo
});
First problem :
stm32l476_nucleo_blinky_freertos_zig/.zig-cache/o/cb530c3ca305f0acc6cbaafe4137f394/cimport.zig:182:36: error: use of undeclared identifier '__copy_table_t'
extern const __copy_table_start__: __copy_table_t;
^~~~~~~~~~~~~~
stm32l476_nucleo_blinky_freertos_zig/.zig-cache/o/cb530c3ca305f0acc6cbaafe4137f394/cimport.zig:183:34: error: use of undeclared identifier '__copy_table_t'
extern const __copy_table_end__: __copy_table_t;
^~~~~~~~~~~~~~
stm32l476_nucleo_blinky_freertos_zig/.zig-cache/o/cb530c3ca305f0acc6cbaafe4137f394/cimport.zig:184:36: error: use of undeclared identifier '__zero_table_t'
extern const __zero_table_start__: __zero_table_t;
^~~~~~~~~~~~~~
stm32l476_nucleo_blinky_freertos_zig/.zig-cache/o/cb530c3ca305f0acc6cbaafe4137f394/cimport.zig:185:34: error: use of undeclared identifier '__zero_table_t'
extern const __zero_table_end__: __zero_table_t;
After looking the issue here: Failed to include `cmsis_gcc.h` because c-translation puts extern variables outside of inline function. ¡ Issue #19687 ¡ ziglang/zig ¡ GitHub
It is seem that some C feature are not yet well translated when we import C code.
In this issue we got a workaround : It will disable the generation of the problematic code. We donât need it because this code has already been executed long before entering the zig
function
- Import HAL v2
const c = @cImport({
@cDefine("USE_HAL_DRIVER", {});
@cDefine("STM32L476xx", {});
@cDefine("__PROGRAM_START", {}); //bug: https://github.com/ziglang/zig/issues/19687
@cInclude("main.h");
});
Letâs implement FreeRTOS now.
FreeRTOS
Same as for the drivers, I take what is provided by the manufacturer. Iâm adding that to my build.zig
. Letâs compile the program
stm32l476_nucleo_blinky_freertos_zig/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c:445:3: error: instruction requires: fp registers
" vstmdbeq r0!, {s16-s31} \n"
^
<inline asm>:9:2: note: instantiated into assembly here
stm32l476_nucleo_blinky_freertos_zig/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c:447:3: error: incorrect condition in IT block; got 'al', but expected 'eq'
" stmdb r0!, {r4-r11, r14} \n" /* Save the core registers. */
^
<inline asm>:11:7: note: instantiated into assembly here
...
Damn, it seems that zig (clang?) doesnât recognize these assembly instructions. However, I donât have a problem with gcc. Letâs dig :
- CPU features are not passed to clang ¡ Issue #10411 ¡ ziglang/zig ¡ GitHub
- Some arm32 targets are broken ¡ Issue #19894 ¡ ziglang/zig ¡ GitHub
- Can't compile anything to Armv7 host. Not identifying host CPU correctly? ¡ Issue #5110 ¡ ziglang/zig ¡ GitHub
- -target armv6m-freestanding-none does not compile ¡ Issue #4266 ¡ ziglang/zig ¡ GitHub
I donât know if the previous issues are all related, but it seems that the CPU settings have not been properly passed to clang. I tested to compile the source code (without zig) with clang-18 with the help of this DropinZigccMakefile. I just replaced zig cc
with clang-18
. The compilation worksâŚ
Well I still want to add freeRTOS! If zig canât compile the code, then letâs gcc or clang do it. For this I will use the cmake script that I use to compile my stm32 programs, then I will generate a library. The script will generate a libfreertos.a
that I will include in my zig project.
Now it is compiling. Lesât import the library in my main.zig
const os = @cImport({
@cInclude("FreeRTOS.h");
@cInclude("task.h");
});
No problem!
Blinky code
Let try everything together now
export fn zig_task(params: ?*anyopaque) callconv(.C) void {
_ = params;
while (true) {
c.HAL_GPIO_WritePin(c.LD2_GPIO_Port, c.LD2_Pin, c.GPIO_PIN_RESET);
os.vTaskDelay(200);
c.HAL_GPIO_WritePin(c.LD2_GPIO_Port, c.LD2_Pin, c.GPIO_PIN_SET);
os.vTaskDelay(200);
}
}
export fn zig_entrypoint() void {
_ = os.xTaskCreate(zig_task, "Blinky", 256, null, 15, null);
os.vTaskStartScheduler(); //Start the app
unreachable;
}
It is working !!! What a nice blinky led !
Conclusion
Iâve been working for a long time in embedded systems on microcontrollers.
Iâve always done C and give up the idea of doing C++ for the additional problems it brought me without exceptional additions.
Despite the bugs and not knowing the language zig well yet, I found it simple and intuitive to integrate it into C code and vice versa, not to mention all the advantages brought by the modernity of the language.
Now that I understand how to generate programs for microcontrollers, Iâm going to start learning the language very seriously!
Bonus and notes
- Not investigate it yet but if I want to use these macros:
os.portTICK_PERIOD_MS
orpdMS_TO_TICKS()
example :os.vTaskDelay(os.pdMS_TO_TICKS(200));
, I got this error:
zig-linux-x86_64-0.13.0/lib/std/zig/c_translation.zig:448:13: error: Cannot promote `u32`; a C ABI type is required
@compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
zig-linux-x86_64-0.13.0/lib/std/zig/c_translation.zig:484:39: note: called from here
const A_Promoted = PromotedIntType(A);
~~~~~~~~~~~~~~~^~~
zig-linux-x86_64-0.13.0/lib/std/zig/c_translation.zig:542:60: note: called from here
pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) {
Problem when translating C to zig ?
- If I couldnât compile the assembly code with the specific FPU instructions, I imagine any operation with the floats wonât use them?
This part seem relevant to it :
.cpu_features_add = std.Target.arm.featureSet(&[_]std.Target.arm.Feature{std.Target.arm.Feature.}),
I donât seem to have seen the clang equivalent of these parameters: -mfpu=fpv4-sp-d16 -mfloat-abi=hard