如何在在HK32F030MF4P6上移植RT-Thread Nano

这是一个航顺 hk32f030 的 rt-thread nano 移植示例,记录了在 keil 裸机工程的基础上进行 rt-thread nano 移植的全过程。在按文档中心的指导进行移植的过程中基本没有遇到问题,只是由于 hk32f030 的ram 较小,无法启用 finsh。移植工程已经分享在gitee rt-thread-nano-hk32f030。
开源地址:https://gitee.com/crazttnspt/rt-thread-nano-hk32-f030
(请复制至外部浏览器打开)
硬件信息:
mcu: 航顺 hk32f030mf4p6 , ram: 2kb, rom:16kb
开发板:hk32f030 - 立创eda (https://lceda.cn/whj4674672/hk32f030)由 @whj467467222 设计
参考文档
rt-thread nano 简介与下载(https://docs.rt-thread.org/#/rt-thread-version/rt-thread-nano/an0038-nano-introduction)
0.准备移植
在移植 rt-thread nano 之前,需要准备一个能正常运行的裸机工程。航顺的库文件包 hk32f030mxx_library_v1.1.4.7z 中提供了 hk32f030 的标准库、启动文件等,还有一个裸机工程模板,整理后得到这里移植使用的裸机工程。
编译后烧录,看到led闪烁,裸机程序正常运行。实测可以使用jlink 或 cmsis-dap 烧录调试,而使用 st-link 无法识别到 hk32f030。
之后就可以开始 rt-thread nano 的移植了。
1.nano pack 安装
nano pack 可以在 keil mdk ide 内进行安装,也可以手动安装。这里选择手动安装pack,从官网下载安装文件:rt-thread nano 离线安装包,下载结束后双击文件进行安装。
然后将 rt-thread nano 添加到工程中。点击 manage run-time environment
在 manage rum-time environment 内打开 rtos 栏,勾选 kernal,点击 ok 后就将 rt-thread 内核加入到工程中了。
现在能在 keil 的 project 栏看到 rtos,展开后可以看到 rt-thread nano 的文件已经加入了工程。
2.适配 rt-thread nano
中断与异常处理
rt-thread 会接管异常处理函数 hardfault_handler() 和悬挂处理函数 pendsv_handler(),这两个函数已由 rt-thread 实现,所以需要删除工程里中断服务例程文件 drivers/hk32f030m_it.c 中的这两个函数,避免在编译时产生重复定义。如果此时对工程进行编译,没有出现函数重复定义的错误,则不用做修改。
系统时钟配置
现在需要在 rtos/board.c 中实现 系统时钟配置(为 mcu、外设提供工作时钟)与 os tick 的配置 (为操作系统提供心跳 / 节拍)。
1void rt_hw_board_init()
2{
3 /* system clock update */ 4 systemcoreclockupdate();
5 6 /* system tick configuration */ 7 _systick_config(systemcoreclock / rt_tick_per_second);
8 9 /* call components board initial (use init_board_export()) */10#ifdef rt_using_components_init11 rt_components_board_init();
12#endif1314#if defined(rt_using_user_main) && defined(rt_using_heap)15 rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
16#endif17}
上面的代码中,systemcoreclockupdate() 对系统时钟进行更新,_systick_config() 配置了 os tick。此处 os tick 使用滴答定时器 systick 实现,需要在 board.c 中实现 systick_handler() 中断服务例程,调用 rt-thread 提供的 rt_tick_increase() ,如下:
1void systick_handler(void)
2{
3 /* enter interrupt */ 4 rt_interrupt_enter();
5 6 rt_tick_increase();
7 8 /* leave interrupt */ 9 rt_interrupt_leave();
10}
由于 systick_handler() 中断服务例程由用户在 board.c 中重新实现,作为系统 os tick,所以还需要删除工程里中原本已经实现的 systick_handler() ,避免在编译时产生重复定义。如果此时对工程进行编译,没有出现函数重复定义的错误,则不用做修改。
内存堆初始化
系统内存堆的初始化在 board.c 中的 rt_hw_board_init() 函数中完成,内存堆功能是否使用取决于宏 rt_using_heap 是否开启,rt-thread nano 默认不开启内存堆功能,这样可以保持一个较小的体积,不用为内存堆开辟空间。开启系统 heap 将可以使用动态内存功能,如使用 rt_malloc、rt_free 以及各种系统动态创建对象的 api。若需要使用系统内存堆功能,则打开 rt_using_heap 宏定义即可,此时内存堆初始化函数 rt_system_heap_init() 将被调用。
3.编写第一个应用
移植好 rt-thread nano 之后,则可以开始编写第一个应用代码验证移植结果。此时 main() 函数就转变成 rt-thread 操作系统的一个线程,现在可以在 main() 函数中实现第一个应用:板载 led 指示灯闪烁。
首先在文件首部增加 rt-thread 的相关头文件 《rtthread.h》 。
在 main() 函数中(也就是在 main 线程中)实现 led 闪烁代码:初始化 led 引脚、在循环中点亮 / 熄灭 led。
将延时函数替换为 rt-thread 提供的延时函数 rt_thread_mdelay()。该函数会发起系统调度,切换到其他线程运行,体现了线程的实时性。
此时可以看到 led 闪烁,虽然现象与裸机程序一致,但 rt-thread 已经在 hk32f030 上成功运行。
使用 rtos 造成固件变大后,通过cmsis-dap 烧录程序可能出现失败现象
将 cmsis-dap 的 sw 调试速度调低为 500khz 后烧录成功
4.移植控制台 finsh
由于 ram 的大小有限,这里 finsh 的移植未能完成
在 nano 上添加 uart 控制台
在 rt-thread nano 上添加 uart 控制台打印功能后,就可以在代码中使用 rt-thread 提供的打印函数 rt_kprintf() 进行信息打印,从而获取自定义的打印信息,方便定位代码 bug 或者获取系统当前运行状态等。实现控制台打印(需要确认 rtconfig.h 中已使能 rt_using_console 宏定义),需要完成基本的硬件初始化,以及对接一个系统输出字符的函数,本小节将详细说明。
实现串口初始化
使用串口对接控制台的打印,首先需要初始化串口,如引脚、波特率等。 uart_init() 需要在 board.c 中的 rt_hw_board_init() 函数中调用。
1static int uart_init(void);
示例代码:如下是基于 hk32 库的 hk32f030 串口初始化程序,参考航顺例程编写。实现了串口的发送并配置了串口中断接收。
1#define usart1_tx_port gpioa 2#define usart1_tx_pin gpio_pin_3 3#define usart1_tx_io_clk_en() rcc_ahbperiphclockcmd(rcc_ahbperiph_gpioa, enable) 4 5#define usart1_rx_port gpiod 6#define usart1_rx_pin gpio_pin_6 7#define usart1_rx_io_clk_en() rcc_ahbperiphclockcmd(rcc_ahbperiph_gpiod, enable) 8 9static void usart_gpio_configurature(void);
10static void usart_nvic_configurature(void);
11static int uart_init(void);
1213static int uart_init(void)
14{
15 usart_inittypedef m_usart;
1617 usart_gpio_configurature();
1819 rcc_apb2periphclockcmd(rcc_apb2periph_usart1, enable);
20 m_usart.usart_baudrate = 115200;
21 m_usart.usart_hardwareflowcontrol = usart_hardwareflowcontrol_none;
22 m_usart.usart_mode = usart_mode_rx | usart_mode_tx;
23 m_usart.usart_parity = usart_parity_no;
24 m_usart.usart_stopbits = usart_stopbits_1;
25 m_usart.usart_wordlength = usart_wordlength_8b;
26 usart_init(usart1, &m_usart);
27 usart_cmd(usart1, enable);
2829 usart_itconfig(usart1, usart_it_rxne, enable);
3031 usart_nvic_configurature();
3233 return 0;
34}
35//init_board_export(uart_init);3637static void usart_gpio_configurature(void)
38{
39 gpio_inittypedef m_gpio;
4041 usart1_tx_io_clk_en();
42 usart1_rx_io_clk_en();
4344 m_gpio.gpio_mode = gpio_mode_af;
45 m_gpio.gpio_otype = gpio_otype_pp;
46 m_gpio.gpio_pin = usart1_tx_pin;
47 m_gpio.gpio_pupd = gpio_pupd_nopull;
48 m_gpio.gpio_speed = gpio_speed_10mhz;
49 gpio_init(usart1_tx_port, &m_gpio);
50 gpio_pinafconfig(usart1_tx_port,gpio_pinsource3,gpio_af_1);
5152 m_gpio.gpio_pin = usart1_rx_pin;
53 gpio_init(usart1_rx_port, &m_gpio);
54 gpio_pinafconfig(usart1_rx_port,gpio_pinsource6,gpio_af_1);
55}
56static void usart_nvic_configurature(void)
57{
58 nvic_setpriority(usart1_irqn,0);
59 nvic_enableirq(usart1_irqn);
60}
实现 rt_hw_console_output
实现 finsh 组件输出一个字符,即实现 uart 输出一个字符:
注:注意:rt-thread 系统中已有的打印均以 结尾,而并非 ,所以在字符输出时,需要在输出 之前输出 ,完成回车与换行,否则系统打印出来的信息将只有换行。
示例代码:如下是基于hk32 库实现的串口驱动对接 rt_hw_console_output()
1void usart1_sendbyte(uint8_t ch)
2{
3 while((usart1-》isr & usart_isr_txe) == 0);
4 usart1-》tdr = ch;
5}
6void rt_hw_console_output(const char *str)
7{
8 rt_size_t i = 0, size = 0;
9 char a = ‘
’;
1011 size = rt_strlen(str);
12 for (i = 0; i 《 size; i++)
13 {
14 if (*(str + i) == ‘
’)
15 {
16 usart1_sendbyte((uint8_t)a);
17 }
18 usart1_sendbyte(*(str + i));
19 }
20}
将对应代码加入 board.c ,编译并烧录后,可以看到终端(或串口助手中输出的rtt logo):
至此就可以使用 rt_kprintf() 打印调试信息了。
在 nano 上添加 finsh 组件
rt-thread finsh 是 rt-thread 的命令行组件(shell),提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。它可以使用串口 / 以太网 / usb 等与 pc 机进行通信。这里使用串口方式,在 nano 上实现 finsh 功能。
keil 添加 finsh 源码
打开 manage run-environment。
勾选 shell 然后点击ok,将 finsh 组件的源码到工程。
这时看到 rtos group 中加入了以下 finsh 文件。
实现 rt_hw_console_getchar()
要实现 finsh 组件功能(既可以打印也能输入命令进行调试),需要在 board.c 中对接控制台输入函数,实现字符输入:
rt_hw_console_getchar():控制台获取一个字符,即在该函数中实现 uart 获取字符,可以使用查询方式获取(注意不要死等,在未获取到字符时,需要让出 cpu),也可以使用中断方式获取。
示例代码:(未实现)
1char rt_hw_console_getchar(void)
2{
3 int ch = -1;
4 // 接收一个字符5 。。.
6 return ch;
7}
加入 finsh 后 ram 空间不足
这时编译会出现报错:
看编译输出应该是存储空间不足,超出ram大小154 bytes,尝试将编译器优化等级调高至 level2 ,但仍会报错。
然后尝试在 rtconfig.h 中调小 rt_consolebuf_size 与 finsh_thread_stack_size ,编译成功。可以看此时的内存占用:program size: code=9458 ro-data=922 rw-data=144 zi-data=1832 ,rom占用为 code+ro+rw=10524 byte,ram 占用为 rw+zi=1976 byte,ram即将耗尽。同时因为调小了线程运行栈,程序运行时会产生 hard fault,因此不再考虑将 finsh 移植至nano上。
为了正常使用,应当关闭 finsh 组件,在rte_components.h中注释 rte_using_finsh,此时程序大小为:program size: code=5756 ro-data=572 rw-data=120 zi-data=1264 ,rom占用为 code+ro+rw=6448 byte,ram 占用为 rw+zi=1384 byte,剩余空间较为充裕。也可以通过在 manage run-environment 中关闭 shell,移除 finsh 组件。
至此,在 hk32f030mf4p6 上的 rt-thread nano 移植工作就完成了。


全面讲解⾯向对象编程三⼤特性 2
Senodia深迪六轴陀螺仪方案应用
华为云OBS,助力企业海量、安全、高可靠、低成本数据存储
为什么可以说vivo和oppo再火 也永远比不上华为!
中芯国际揭露产能满载、芯片缺货的2大因素
如何在在HK32F030MF4P6上移植RT-Thread Nano
苏州移动与亨通通信产业集团正式签订了5G+智能制造战略合作协议
长电科技携手 SEMICON China 2021,以先进封装助力智慧生活
有源滤波器在船舶电力推进系统中的影响
如何利用5G确定性网络加速数字化转型
砥砺前行、不忘初心——鼎阳科技亮相第五十届全国高教仪器展示会
Oculus旨在利用虚拟现实技术培训专业医疗人员
步进电机驱动电路解析,步进电机驱动电路原理图、电路性能比较及电路实例
微雪电子ZIGBEE开发板 CC2530 Eval Kit简介
传闻德州仪器欲出164亿美元收购AMD,行业分析师:不靠谱
澳大利亚:墨尔本皇家理工第一个推出开放式区块链课程
基于PIC24FJ64的智能光伏汇流采集装置应用设计
AlN/AIGaN/GaN MIS-HEMT器件制作
LabVIEW分割条控件的设置和界面设计实例
汽车零配件激光焊接机产品的特点及场景应用分析