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开发。