Air32F103试玩-移植LVGL+FreeRTOS
上个月合宙推出了一款Air32F103
芯片,保持很低价格的同时,兼容STM32,并最高拥有64k
RAM+256k
ROM,并最高可以跑到216MHz
。本文介绍如何将STM32标准库工程升级到Air32F103
工程,移植FreeRTOS与LVGL,并分析其性能表现。5元钱的MCU能否流畅运行LVGL?
Air32F103体验
Air32F103
是合宙推出的MCU,该MCU同样是以直接兼容STM32作为卖点,但相对于之前测试的AT32F403A,它在某些外设上并不能做到完全兼容,这在其官方文档中也有所提及。但是,对于不使用不兼容外设的场合,该芯片同样能够直接当作STM32进行开发,并且还在多个方面相对STM32进行了升级。
Air32
系列芯片也有许多容量型号可选,我选择了QFP48
封装所能提供最大存储容量的Air32F103CC
,该芯片相对于STM32F103大容量型号,主要有以下几个升级点:
216MHz
的Cortex-M3内核(可以稳定超频运行在256MHz
),且运行在216MHz
下时,全部Flash区域仅需1个等待周期;- 新增了一些外设,包括
ADC3
TRNG
TIM9-TIM14
等; - 对现有外设进行了增强,例如内置USB上拉电阻、GPIO可以独立上下拉等;
移植FreeRTOS与LVGL
由于Air32F103
能够兼容STM32,因此本代码基于STM32F1
的标准库,并增加了Air32专有的代码。
本工程针对Air32F103CCT6
芯片,使用硬件SPI+DMA的方式驱动GC9306X
控制器的320x240
LCD屏幕,并支持双缓冲模式,几乎榨干了Air32所有的性能。同时,使用RTOS保证了DMA传输过程中CPU能够进入休眠,降低系统功耗。
请使用较新版本的Keil,旧版本的Keil可能会出现编译出的程序运行错误的问题。
本工程经实测可以使用Keil5.36正常编译。
工程组件
本文所使用的工程组件均来自原汁原味的官方最新版,除了配置文件之外绝无任何魔改。
- FreeRTOS来自FreeRTOS官网中最新的
LTS 202012.04
版本; - LVGL来自官网LVGL官方GitHub仓库中最新的
LVGL 8.3.1
版本。
注意事项
中断优先级
Air32的NVIC
中断优先级只有3位,而不是STM32的4位。若想使用STM32的标准库,则需要在FreeRTOSConfig.h
头文件中修改__NVIC_PRIO_BITS
默认的值。
#ifndef __FREERTOS_CONFIG_H
#define __FREERTOS_CONFIG_H
#include "stm32f10x.h"
#undef __NVIC_PRIO_BITS
#define __NVIC_PRIO_BITS 3
Air32专用PLL库
Air32可以支持到比STM32默认72MHz
更高的时钟频率,但是需要来自合宙的闭源PLL库。为此,我将STM32原版的system_stm32f10x.c
排除编译,然后实现了自己的SystemInit()
函数。该函数可以从合宙官方的SDK中获取。
void SystemInit(void)
{
RCC_DeInit(); //复位RCC寄存器
RCC_HSEConfig(RCC_HSE_ON); //使能HSE
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); //等待HSE就绪
RCC_PLLCmd(DISABLE); //关闭PLL
AIR_RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_32, 1); //配置PLL, 8*32=256MHz
RCC_PLLCmd(ENABLE); //使能PLL
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //等待PLL就绪
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择PLL作为系统时钟
RCC_HCLKConfig(RCC_SYSCLK_Div1); //配置AHB时钟
RCC_PCLK1Config(RCC_HCLK_Div2); //配置APB1时钟
RCC_PCLK2Config(RCC_HCLK_Div1); //配置APB2时钟
RCC_LSICmd(ENABLE); //使能内部低速时钟
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); //等待LSI就绪
RCC_HSICmd(ENABLE); //使能内部高速时钟
while (RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET); //等待HSI就绪
}
然后,将air.lib
加入工程中,并自己定义AIR_RCC_PLLConfig()
函数的原型与SystemCoreClock
的值。Air32支持比STM32标准库更高的RCC_PLLMul_xx
值,因此还需要将air32f10x.h
中新增的PLL值复制到自己的代码中,这样可以在使用熟悉的STM32标准库的同时,使用到Air32的增强功能。
#define RCC_PLLMul_17 ((uint32_t)0x10000000)
#define RCC_PLLMul_18 ((uint32_t)0x10040000)
#define RCC_PLLMul_19 ((uint32_t)0x10080000)
#define RCC_PLLMul_20 ((uint32_t)0x100C0000)
#define RCC_PLLMul_21 ((uint32_t)0x10100000)
#define RCC_PLLMul_22 ((uint32_t)0x10140000)
#define RCC_PLLMul_23 ((uint32_t)0x10180000)
#define RCC_PLLMul_24 ((uint32_t)0x101C0000)
#define RCC_PLLMul_25 ((uint32_t)0x10200000)
#define RCC_PLLMul_26 ((uint32_t)0x10240000)
#define RCC_PLLMul_27 ((uint32_t)0x10280000)
#define RCC_PLLMul_28 ((uint32_t)0x102C0000)
#define RCC_PLLMul_29 ((uint32_t)0x10300000)
#define RCC_PLLMul_30 ((uint32_t)0x10340000)
#define RCC_PLLMul_31 ((uint32_t)0x10380000)
#define RCC_PLLMul_32 ((uint32_t)0x103C0000)
uint32_t SystemCoreClock = 256000000;
uint32_t AIR_RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul, uint8_t Latency);
Keil编译器设置
LVGL需要最低支持C99的编译器才能正确编译,因此需要开启Keil AC5的C99模式。同时,为了减少生成固件的体积,建议选择最高级别的-O3
优化。
组件库的裁剪与优化
由于芯片的RAM空间有限,因此需要对芯片的RAM空间进行一定的规划与优化。程序中占用RAM较大的部分与相应的规划如下:
- 系统栈:由于使用了
FreeRTOS
,各个Task有其自己的任务栈,因此系统栈只有ISR与main
函数使用。因此,在startup_stm32f10x_hd.s
中将Stack_Size
改为0x00000100
,即256字节。 - 任务栈:目前的代码中只有三个Task,分别是
LVGL Task
LED Task
与IDLE Task
。其中,LED Task
与IDLE Task
都非常简单,为它们设置128
字节的任务栈;LVGL Task
较为复杂,根据官方推荐的2-8k范围,设置为4k
。 - LVGL堆:LVGL的所有的句柄都是动态内存,因此其自己维护了一个堆空间。堆空间的大小可以在
lv_conf.h
中的LV_MEM_SIZE
中修改,您可以根据自己使用的UI复杂度对其进行修改。对于benchmark
demo,12k
即可满足要求。 - LVGL缓冲区:LVGL需要将画面渲染到缓冲区中,之后再刷新到屏幕上。本工程支持单缓冲与双缓冲模式(可以在Keil的Target中选择),单缓冲模式使用1个
240x40
像素的缓冲区,双缓冲模式则使用2个240x40
像素的缓冲区。使用双缓冲模式可以在DMA控制器向屏幕写入一个缓冲区的数据时,CPU继续渲染到另一个缓冲区中,提升渲染效率,但会占用双倍的缓冲区。
同时,由于芯片的ROM空间也有限,因此我裁剪了一些LVGL
与FreeRTOS
的功能。您可以在lv_conf.h
与FreeRTOSConfig.h
中自行开关它们。benchmark
demo中包含了大量的字体与图像,因此导致最终编译生成的bin文件较大。只使用FreeRTOS
与LVGL
内核时,ROM占用约120k
。使用常用的空间后,还能剩余约100k
空间给用户开发自己的应用。
运行效果
单缓冲模式的存储空间占用情况如下:
Total RO Size (Code + RO Data) 230904 ( 225.49kB)
Total RW Size (RW Data + ZI Data) 40768 ( 39.81kB)
Total ROM Size (Code + RO Data + RW Data) 231472 ( 226.05kB)
跑分结果如下:
双缓冲模式的存储空间占用情况如下:
Total RO Size (Code + RO Data) 230928 ( 225.52kB)
Total RW Size (RW Data + ZI Data) 59968 ( 58.56kB)
Total ROM Size (Code + RO Data + RW Data) 231496 ( 226.07kB)
跑分结果如下:
可以看出,单缓冲模式相比双缓冲模式节约了大量的RAM,帧数却只下降了25%,因此单缓冲模式更具有实用意义。这主要是因为,在复杂UI界面下,瓶颈主要在CPU的运算速度上,而不是向屏幕写入缓冲区的IO操作上。
总结
5元钱的Air32F103确实具有了流畅运行LVGL的能力,并且还有100k左右的ROM和超过20k的RAM空间可用,这使得在Air103上使用LVGL
+FreeRTOS
的同时开发复杂的用户程序成为了可能,我们又多了一个高性价比的国产MCU选择。