Embedding Rust in STM32 C Projects

Rust has excellent C language interoperability. A little Rust with your C introduces how to embed Rust code into C projects.

Simply put, you need to convert Rust types to C types, then generate libraries and header files to include and use in C projects.

But in microcontrollers, static compilation is needed, so you need:

crate-type = ["staticlib"]

Here we use the cargo command to create a simple lib:

cargo new lib_demo --lib
cd lib_demo

Then add to Cargo.toml:

[lib]
name = "lib_demo"
crate-type = ["staticlib"]

Add simple arithmetic operations (addition, subtraction, multiplication, division):

#![allow(unused)]
#![no_std]
fn main() {
/// Addition operation
/// Provide panic handler function for no_std environment
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

/// Subtraction operation
#[no_mangle]
pub extern "C" fn subtract(a: i32, b: i32) -> i32 {
    a - b
}

/// Multiplication operation
#[no_mangle]
pub extern "C" fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

/// Division operation (returns safe result, returns preset safe result 0 when dividing by zero)
#[no_mangle]
pub extern "C" fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        0 // Prevent divide by zero error
    } else {
        a / b
    }
}

/// Floating point division
#[no_mangle]
pub extern "C" fn divide_float(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        0.0 // Prevent divide by zero error
    } else {
        a / b
    }
}
}

Then write a calling header file:

#ifndef LIB_DEMO_H
#define LIB_DEMO_H

#ifdef __cplusplus
extern "C" {
#endif

// Integer operations
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);

// Floating point operations
double divide_float(double a, double b);

#ifdef __cplusplus
}
#endif

#endif // LIB_DEMO_H 

Now you can call the written library from C language. Recommend supplementary reading ffi.

Embedding in STM32CubeIDE

STM32CubeIDE is the official development environment provided by ST. Here we use a CubeIDE project as an example. For STM32CubeIDE introduction, refer to STM32CubeIDE Quick Start Guide.

First, we open CubeIDE and select to create a new project:

Enter our chip stm32g8u6 and select:

Enter project name and click finish to complete creation:

For this simple example, we use USART output, so only initialize USART2. Select USART2 on the left and configure mode to Asynchronous mode, which will automatically configure pins:

Then CTRL+S to save, CubeIDE will automatically generate code:

Add the following code to main.c to support printf:

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1,HAL_MAX_DELAY);
  return ch;

}

Add floating point printf support in Project/Properties/C/C++ Build/Settings/Tool Settings:

Now an STM32 C template is complete. Add the lib_demo library. First compile lib_demo:

cargo build --release --target thumbv6m-none-eabi

Find the lib_demo.a file and add it along with lib_demo.h file to the Src and Inc directories of the STM32CubeIDE project:

Right-click refresh in CUBEIDE to see the added files:

Finally, you need to add this library to the project:

In Project/Properties, select and add the library:

Add to main.c:

#include "lib_demo.h"
...
int main(void)
{
...

  /* USER CODE BEGIN 2 */
  int a = 10, b = 5;
  printf("Integer operations:\n");
  printf("%d + %d = %d\n", a, b, add(a, b));
  printf("%d - %d = %d\n", a, b, subtract(a, b));
  printf("%d * %d = %d\n", a, b, multiply(a, b));
  printf("%d / %d = %d\n", a, b, divide(a, b));
  // Test divide by zero
  printf("%d / %d = %d (divide by zero protection)\n", a, 0, divide(a, 0));
  // Test floating point division
  double x = 10.5, y = 2.5;
  printf("\nFloating point operations:\n");
  printf("%.2f / %.2f = %.2f\n", x, y, divide_float(x, y));
  printf("%.2f / %.2f = %.2f (divide by zero protection)\n", x, 0.0, divide_float(x, 0.0));
  /* USER CODE END 2 */
...
}

Click debug and run to see the output in the serial terminal:

This completes embedding a Rust library into an STM32 C language project.

Summary

This is just using the simplest example for illustration. In actual applications, you can implement complex logic that requires safety guarantees in Rust, then embed it into C projects. For example, protocol encoding/decoding, calculations, etc.