目录
前言
RT-thread近年来频繁出现在嵌入式开发者的视野中,他们的大力推广以及技术支持受到了很多人的广泛支持与关注。本人在去年(还是一个职业小白)也参加了rt的教学实验,体验过env工具的方便以及如何结合stm32cubemx实现快速建立rt工程。不过,rt官方主要打造针对一些较高资源的ic的开发生态环境,比如stm32f407或者stm32f103zet6等高ram和高rom的ic(品牌开发板标配)。但是实际工作中,对于我们公司开发的ic并不需要很大资源的ic去开发,比如物联网项目我们往往是只用stm32f030或者很高资源用的stm32f103RB等,其最大rom保持在128k以下,甚至更低,而ram更加是只需要20k。可是基于rt的开发生态通过env工具建立工程时,当移植完部分组件之后还未开发应用时便几乎超过了20k的ram了。因此往往我们还是拿rtthread的nano精简版本进行开发。原因是其配合stm32cubemx开发太方便了。
准备
软件:IAR、stm32cubeMX
硬件:nucleo-g070RB开发板(参加rt峰会送的,随便拿一个开发板来搭建环境)
nucleo-g070RB开发板的简单介绍
该开发板板载了stlink烧录调试器,因此我们只需要准备一个miniusb数据线即可实现电脑pc和开发板的调试对接。又因为其stlinkv2-1版本又支持扩展串口功能,这个开发板实现了stlink拓展串口连接到stm32g070rb主控芯片的uart2串口的引脚。因此我们不仅不需要自备烧录器,也不需要自备ch340等串口硬件工具,只需要直接准备一根micro数据线完成烧录、调试及串口通讯。其他具体资源介绍可参考以下链接:nucleo-g070rb开发板数据手册
发牢骚、这里本人想吐槽一下:给那些职业小白和大学生谈的话,很多开发板(正点、野火等那些配套齐全的开发板)因为帮忙做好了太多资源,导致许多大学生理解不了硬件接线原理,实际上开发板不可能用作产品而使用,产品是不需要板载烧录调试器的,产品往往是由最小系统根据需求不断拓展功能而设计的,而最小系统肯定不需要烧录器,因为批量生产上,不是拿着电脑给一个又一个硬件烧录的,而是有专门的烧录编辑器(通过电脑端把程序导入到烧录编辑器中)去烧录的,同时烧录编辑器还能附有烧录id的功能(说白了就是能够选择一个flash地址烧写id,本质和烧录程序一样,需要保证程序flash不覆盖你写的id地址的位置),给每个设备赋予一个唯一id标识,不少大学生玩了正点的f3,f4系列带有配套齐全的开发板后,理解不了烧录原理,有些是st-link烧录、有些又是jlink烧录、jlink又分有swd和jtag烧录方式。(本人就是个例子)。工作了才明白中间实际有很多需要我们去熟悉的。具体原理和区别就不多说了,网上很多也有。
回归正题,上面说过,只需要一根数据线就可以开发了。插到电脑上如下图:
设备管理器已经检测到了stlink硬件。
cube配置
- 打开cube新建工程,本人选择stm32f070rb型号
- 工程预览
- 时钟源选择,外部时钟源本人全部失能,准备启用内部时钟源配置。
-
SW调试使能,勾选Serial Wire选项,基础时钟选择系统滴答systick
- 串口异步配置,默认配置。
- 添加RT软件包,找到软件软件包选项点击,然后在选择组件
- 本人已经下载好了rt组件,勾选其组件,点击ok退出。
rt组件就出现在cube配置下,勾选它然后如下配置(根据需求参考)
- 再到NVIC配置取消掉硬件错误中断,因为rt组件会帮你配置,不消掉iar工程编译会报错,也可以iar工程注释掉其中断。
- 时钟树配置,内部高速时钟源,选择最快的时钟频率64Mhz,不考虑低功耗。
- 工程管理,添加工程名
- 代码生成配置,勾选如下图所示的箭头,这样驱动文件更加模块化分配到各个c文件中。
- 预设置,把两个初始化去掉,这样main里就不会生成这两个初始化调用,因为到时rt配置的时候会重复,不勾的话最终在工程还是要删除掉,否则程序功能不正常。
- 点击生成工程GENEEATE CODE,生成IAR工程。
- 打开open project,即可开始配置IAR工程。
IAR工程配置
第一步,修改Options配置,较新的版本cube默认不配置选型。配置选择STM32G070RB,之后即可编译。
但是这样直接编译运行,是没有如官方所示会打印RT logo的。原因是还没实现finsh接口,实现finsh接口的函数有:
void rt_hw_console_output(const char *str) //输出接口,finsh里面一些debug信息依赖这个接口打印。
char rt_hw_console_getchar(void) //输入接口,串口接收实现,看他返回的是一个char说明只需要实现读取串口一个数据的功能
官方推荐rt_hw_console_output驱动如下:
void rt_hw_console_output(const char *str) {
rt_size_t i = 0, size = 0;
char a = '\r';
__HAL_UNLOCK(&huartx);
size = rt_strlen(str);
for (i = 0; i < size; i++)
{
if (*(str + i) == '\n') {
HAL_UART_Transmit(&huartx, (uint8_t *)&a, 1, 1);
}
HAL_UART_Transmit(&huartx, (uint8_t *)(str + i), 1, 1);
}
}
接收部分rt_hw_console_getchar官方两个例程一个是用查询方法来接收、一个是中断结合信号量接收。
中断结合信号量接收参考官方链接如下:
但是,本人觉得两者都存在缺陷:查询方法的缺陷,实时系统多线程的环境下,可能会有查漏的情况,所以在越复杂的功能环境下越可能漏接数据。而中断信号量结合的方法,需要用到回环缓冲区,类似fifo缓冲区。代码量较大,实际上信号量加缓冲区的功能不就是消息队列功能吗,所以本人再次基础上做了一个翻新的改动。这样代码则更加简洁清晰。代码如下:
char rt_hw_console_getchar(void) {
char ch ;
//rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER); //接 收 信 号 量
//ch = huartx.Instance->DR & 0xff; //读 取 数 据
if(rt_mq_recv(&shell_rx_mq, &ch,1, RT_WAITING_FOREVER) == RT_EOK) {
}
return ch;
}
void USARTx_IRQHandler(void) {
int ch = -1;
if((__HAL_UART_GET_FLAG(&huartx, UART_FLAG_RXNE) != RESET) &&
(__HAL_UART_GET_IT_SOURCE(&(huartx), UART_IT_RXNE) != RESET)) //接 收 中 断
{
__HAL_UART_CLEAR_FLAG(&(huartx), UART_FLAG_RXNE); //清 除 中 断
__HAL_UART_CLEAR_PEFLAG(&(huartx)) ;
#if defined(STM32G0xx_HAL_H)
ch = huartx.Instance->RDR & 0xff; //读 取 数 据
#elif defined(__STM32F1xx_HAL_H)
ch = huartx.Instance->DR & 0xff; //读 取 数 据
#else
#error no sure ic type
#endif
rt_mq_send(&shell_rx_mq, &ch,1);
}
__HAL_UART_CLEAR_PEFLAG(&huartx) ;
}
对比官方驱动,我们不需要实现ringbuf环缓冲区等功能。
上面代码中的shell_rx_mq消息队列需要初始化,实现finsh驱动函数之后,需要对串口进行初始化实现如下(直接通过cube的生成usart.c代码中复制出来):
void lc_shell_uart_init(void) {
//rt_sem_init(&(shell_rx_sem), "shell_rx", 0, 0);
rt_mq_init(&shell_rx_mq,
"shell_rx",
shell_rx_buf, /* 内存池指向 shell_rx_buf */
1, /* 每个消息的大小是 1 字节 */
sizeof(shell_rx_buf), /* 内存池的大小是 shell_rx_buf 的大小 */
RT_IPC_FLAG_FIFO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */
MX_USART2_UART_Init();
}
void MX_USART2_UART_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = DEBUG_TX_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DEBUG_TX_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = DEBUG_RX_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(DEBUG_RX_GPIO_Port, &GPIO_InitStruct);
huartx.Instance = USART2;
huartx.Init.BaudRate = 115200;
huartx.Init.WordLength = UART_WORDLENGTH_8B;
huartx.Init.StopBits = UART_STOPBITS_1;
huartx.Init.Parity = UART_PARITY_NONE;
huartx.Init.Mode = UART_MODE_TX_RX;
huartx.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huartx.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huartx) != HAL_OK)
{
// Error_Handler();
}
__HAL_UART_ENABLE_IT(&huartx, UART_IT_RXNE);//中断使能
HAL_NVIC_EnableIRQ(USART2_IRQn);
HAL_NVIC_SetPriority(USART2_IRQn, 3, 3);
}
然后在board.c中的初始化rt_hw_board_init函数添加lc_shell_uart_init()函数即可。
cube生成RT组件配置的潜规则:前面提到时钟源及时钟配置初始化SystemClock_Config()就是需要替换掉rt_hw_board_init()里的systemcoreclockupdate()函数的。同时需要把主函数里的HAL_Init()移动到rt_hw_board_init()里来。因为实际上main函数并不是启动入口,IAR启动入口会定位到__low_level_init(void)这里。
rtthread_startup其调用结构大致如下:
rtthread_startup()
{
rt_hw_interrupt_disable();
rt_hw_board_init();//因此时钟初始化需要在里面重新配置(用cube生成配置代替其rt的默认配置),同时HAL_init硬件初始化从main中也搬运过来。
。。。
rt_application_init();----》main线程
。。。
rt_system_scheduler_start();//启用系统调度器
}
最后还需要在rtconfig.h头文件添加一个RT_USING_FINSH宏
最后在main主函数里需要添加delay延时,保证让出运行线程给其他任务执行(finsh任务)
执行程序
当有些时候想在其他串口助手调试也可以,只是没那么可观了,但是其他串口工具有保存指令功能,做项目时备份指令比较方便。
整个移植工作总结
1.完成cube配置生成工程,包含串口、时钟、调试使能、RT启用、硬件中断弃用。
2.IAR工程:添加RT_USING_FINSH宏、实现finsh的输出输入函数两个函数且还需实现串口接收中断函数、实现串口初始化函数。
3.时钟配置、硬件初始化搬运到board.c中,添加串口初始化函数。
当然模块化配置还是需要有的,因此本人已经将finsh实现的内容模块化成一个组件中
同时配有移植使用说明:
需要用时,将这5个文件添加到项目中,现已实现了stm32f103xx或者stm32g0xx的支持(实际添加支持特别简单,改动特别少,一般只改头文件包含和部分寄存器,比如g070的串口接收寄存器和f103的接收寄存器变量名不同,代码中已经体现了),只需要修改lc_shell_cfg.h文件配置之后,在应用中添加lc_shell_uart_init()即可快速移植成功。链接如下:
https://download.csdn.net/download/fangjiaze444/12858497