Rust/C 嵌入式固件大小对比

Rust 和 C 都是系统编程语言,它们在性能方面有各自的特点和优势。

  • C 语言作为一款底层、灵活且高效的编程语言,丰富的生态,有着许多优秀的轮子可以使用,是目前国内绝大部分嵌入式开发者的首选编程语言,同时绝大部分的 MCU 芯片厂商和嵌入式操作系统都使用的是 C 语言编写。

  • Rust 以其惊人的速度和高效的内存利用率而著称,同时函数式的编程方式,在嵌入式开发中有着惊人的表现力,让开发者使用 Rust 驱动更加安全和简单。

目前针对传统的 C与新生力量 Rust,究竟两者的对比效果如何呢。本文将基于国产的低成本单片机 PY32F030(普冉单片机) 来对比 Rust 与 C 开发的优势。

PY32F030 的硬件资源

PY32F030微控制器采用高性能的 32 位ARM® Cortex®-M0+内核,宽电压工作范围的MCU。嵌入高达64Kbytes flash和8Kbytes SRAM存储器,最高工作频率48MHz。包含多种不同封装类型多款产品。芯片集成多路I2C、SPI、USART等通讯外设,1路12bit ADC,5个16bit定时器,以及2路比较器。

开发环境

由于笔者使用的是 Mac 电脑,无法安装 IAR 或 Keil 等IDE,因此选择使用 Gcc 工具链来编译官方C 版本驱动的 GPIO 例程。因为官方没有 Rust 的支持库,因此笔者还需要自己实现一套 Rust 驱动 GPIO。

GPIO 例程

在测试中,例程将初始化基本的时钟、GPIO 外设、翻转 GPIO 操作,本文将在对比C 版本(HAL/LL)和 Rust 在 Debug 和 Release 模式下的固件大小和开发难度。

C版本例程

在 C 驱动选用 HAL 版本的驱动库,官方提供的软件包中有一个GPIO_Toggle的例程。主函数如下:

#include "main.h"

static void APP_GpioConfig(void);

int main(void)
{
  /* Reset of all peripherals, Initializes the Systick */
  HAL_Init();

  /* Initialize GPIO */
  APP_GpioConfig();

  while (1)
  {
    /* Delay 250ms */
    HAL_Delay(250);

    /* Toggle LED */
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_11);
  }
}

static void APP_GpioConfig(void)
{
  GPIO_InitTypeDef  GPIO_InitStruct = {0};

  __HAL_RCC_GPIOA_CLK_ENABLE();                          /* Enable GPIOA clock */

  GPIO_InitStruct.Pin = GPIO_PIN_11;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;            /* Push-pull output */
  GPIO_InitStruct.Pull = GPIO_PULLUP;                    /* Enable pull-up */
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;          /* GPIO speed */
  /* GPIO Initialization */
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

使用 Debug 模式编译后大固件大小为 1876B(1.8K)

$ arm-none-eabi-size build/Project/Project.elf
   text    data     bss     dec     hex filename
   1664     212    1060    2936     b78 build/Project/Project.elf

使用 Release 模式编译后固件大小为1772 (1.7K)

arm-none-eabi-size build/Project/Project.elf
   text    data     bss     dec     hex filename
   1560     212    1060    2832     b10 build/Project/Project.elf

如果使用 LL 版本的库,例程代码如下:

#include "main.h"
#include "py32f030xx_ll_Start_Kit.h"

static void APP_SystemClockConfig(void);
static void APP_GpioConfig(void);

int main(void)
{
  /* Configure system clock */
  APP_SystemClockConfig();

  /* Configure LED pins */
  APP_GpioConfig();

  /* Turn off LED */
  LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_11);

  while (1){
    /* LED blinking */
    LL_mDelay(100);
    LL_GPIO_TogglePin(GPIOA, LL_GPIO_PIN_11);
  }
}

static void APP_SystemClockConfig(void)
{
  /* Enable HSI */
  LL_RCC_HSI_Enable();
  while(LL_RCC_HSI_IsReady() != 1){}

  /* Set AHB prescaler */
  LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);

  /* Configure HSISYS as system clock source */
  LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSISYS);
  while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSISYS){}

  /* Set APB1 prescaler */
  LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
  LL_Init1msTick(8000000);

  /* Update system clock global variable SystemCoreClock (can also be updated by calling SystemCoreClockUpdate function) */
  LL_SetSystemCoreClock(8000000);
}

static void APP_GpioConfig(void)
{
  /* Enable clock */
  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);

  /* Configure PA11 pin as output */
  LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_11, LL_GPIO_MODE_OUTPUT);
}

Debug 模式下编译的大小为1308 B(1.3K),

arm-none-eabi-size build/Project/Project.elf
   text    data     bss     dec     hex filename
   1104     204    1052    2360     938 build/Project/Project.elf

Release 模式下编译大小为1268B (1.2K)

arm-none-eabi-size build/Project/Project.elf
   text    data     bss     dec     hex filename
   1064     204    1052    2320     910 build/Project/Project.elf

Rust 版本例程

rust 版本例程则简单很多,完成GPIO 驱动层和时钟相关的驱动后,提供较为友好的上层接口,用户则可以非常简单的使用 GPIO 外设。

#![no_std]
#![no_main]

use embedded_hal::digital::v2::ToggleableOutputPin;
use hal::gpio::{Output, PinIoType, PinSpeed};
use py32f030_hal as hal;

use panic_halt as _;

#[cortex_m_rt::entry]
fn main() -> ! {
    let p = hal::init(Default::default());
    let gpioa = p.GPIOA.split();
    let mut led = Output::new(gpioa.PA0, PinIoType::Pullup, PinSpeed::Low);

    loop {
        let _ = led.toggle();
        cortex_m::asm::delay(10000000);
    }
}

在 Debug 模式中固件大小为 1708(1.6K)

$ arm-none-eabi-size target/thumbv6m-none-eabi/debug/examples/blinky
   text    data     bss     dec     hex filename
   1692      16      32    1740     6cc target/thumbv6m-none-eabi/debug/examples/blinky

在 Release 模式中,大小为1344(1.3K)

$ arm-none-eabi-size target/thumbv6m-none-eabi/release/examples/blinky
   text    data     bss     dec     hex filename
   1328      16      32    1376     560 target/thumbv6m-none-eabi/release/examples/blinky

总结

在以上的结果中,数据整理如下:

语言/驱动版本 Debug 模式 Release 模式
Rust 1708 1344
C(Hal) 1876 1772
C(LL) 1308 1268

通过对比,在Debug模式下,Rust 生成的固件大小比 C 的 Hal 版本更加小 8%,比 LL 库大23%。在 Release 模式下则比 Hal 版小 24%,比 LL 版本则大5%。

在实现相同的功能时,Rust 的代码则显得更加简洁优雅。

Rust 实现安全保障的同时,没有牺牲太多的固件空间,也许这一点大家可以相信 Rust 在代码空间资源方面的优化也是非常棒了,因此对于小 Flash 的芯片也比较适合使用Rust开发。




请使用浏览器的分享功能分享到微信等