前置文章:选读:NXP freescale 开发环境搭建:https://blog.csdn.net/jiladahe1997/article/details/105966658
本文使用的芯片 : NXP MK66FX1
本文使用的开发环境 : MDK KEIL + RT-thread + RT-thread FINSH
在搭建好开发环境之后,准备使用一款RTOS来做作为基础框架,rtos在嵌入式系统的作用类似于nodejs的express,JavaScript的vue、react一样,是嵌入式开发的底层框架,可以极大的帮助开发者进行开发。
常见的RTOS有,freeRTOS、RT-thread、腾讯的tiny-os,阿里的AliOS等,综合比较之下,我选择了RT-thread,主要原因是中文文档上手快,自带巨好用的finsh控制台。 但是比较是小公司背景,虽然历史悠久,但是N年以后是否还能和阿里腾讯或者亚马逊的freeRTOS抗衡,还很难说。
打个广告: 推荐我自己开发的嵌入式软件的镜像站:https://embeddedproxy.cn/,上面有MDK Keil的安装包、Keil的STM32 PACK、STM32CubeMX、micropython等一系列嵌入式相关软件的国内加速镜像,每日和官方进行一次同步,欢迎试用。
请参照官方的移植步骤:https://www.rt-thread.org/document/site/tutorial/nano/nano-port-keil/an0039-nano-port-keil/
Note:记得在 rtconfig.h 文件最上面中加上这几行. 官方文档写的比较隐晦。
简单讲一下这几行的作用:
RT_USING_FINSH : (必须) 开启FINSH线程。
FINSH_USING_DESCRIPTION :(可选) 使FINSH线程在按TAB输出帮助时,可以输出函数描述
FINSH_USING_SYMTAB: (可选)使FINSH线程在按TAB输出帮助时,会自动换行
#define RT_USING_FINSH
#define FINSH_USING_DESCRIPTION
#define FINSH_USING_SYMTAB
这一步是因为前置文章中生成的nxp工程默认使用 Arm Clang6 进行编译,but rt-thread只能用 Arm Clang5 进行编译。
在按照上一步官方的步骤添加完rt-thread会报以下错误,如果你没有报这个错,说明你的工程使用的是 Arm Clang5 进行编译,无需关注本步骤。
首先将全局的汇编编译器设置为Arm Clang 5
进行上述步骤之后,会报下面的错:
这是因为,上面有说到,NXP工程默认的汇编编译器是 Arm Clang6 ,这是有原因的,因为NXP的startup文件只支持Arm Clang6。 现在为了适配rt-thread,将编译器改成了Arm Clang5,当然会报错。 所以还需要 再单独的把startup文件的编译器设置为 Arm Clang 6.
Note :注意这个勾,默认是灰色的,代表不生效;点击一次后勾会取消掉;再点击一次后,才会变成黑色的勾
手动添加BOARD_InitPeripherals函数 这一步就是在config tool上点点点,操作一下生成代码,类似于STM32CubeMX
1.先配置引脚
2.然后开启外设 peripheral,最后生成代码即可,记得关掉 TDR 和TC中断
Note : 记得关掉 TDR 和TC中断!发送中断一般是发送数据时才开启,发送完就关闭,这里一上来默认就给你打开,注意踩坑
Note:生成外设peripheral 后并不会自动调用初始化外设的代码 peripheral ,这一点很坑,记得手动加上
由于RT-thread在main函数之前,就会执行打印操作,所以上面图中的一系列操作不能放在main函数中执行,而是要在main函数之前,在RT-thread执行打印操作之前就进行。
所以需要调用RT-thread提供的 INIT_BOARD_EXPORT 函数,操作一下。
从Keil5.27升级到Keil5.29后,下面问题已消失
Note:由于 INIT_BOARD_EXPORT 用了C语言中比较高深的宏定义技巧,必须要在链接属性里面加点东西,才能保证括号里的函数不被 linker链接器优化掉,加上这一句 --keep=(.rti_fn.) 这是因为INIT_BOARD_EXPORT 会自动给调用的函数加上 .rti_fn. arrtibute属性,具体原理请看这篇文章https://www.rt-thread.org/qa/thread-5679-1-1.html
有时候生成的工程会自动加上一行,自动加了就不用手动加了。
由于硬件平台不同,rt-thread中集成的两个功能:console控制台输出 和 finsh 是无法使用的。根据rt-thread版本的不同,你可能会遇到两种情况:
RT_WEAK void rt_hw_console_output(const char *str)
{
/* empty console output */
}
我们需要做的就是根据我们使用的MCU和电路板,以及使用的串口,完成这两个函数。
官方实现和具体内容可以参见这篇文章:https://www.rt-thread.org/document/site/tutorial/nano/finsh-port/an0045-finsh-port/
我认为官方的方法过于繁琐了,下面我介绍一下我个人的移植方法。
rt_hw_console_output 函数,这个函数往串口输出数据。直接copy官方的写法:但是官方用的STM32的库,我这里需要简单的替换成NXP的库,直接替换一下 HAL_UART_Transmit -> UART_WriteBlocking
#include "peripherals.h"
void rt_hw_console_output(const char *str)
{
rt_size_t i = 0, size = 0;
char a = '\r';
_//_HAL_UNLOCK(&UartHandle);
size = rt_strlen(str);
for (i = 0; i < size; i++)
{
if (*(str + i) == '\n')
{
//HAL_UART_Transmit(&UartHandle, (uint8_t *)&a, 1, 1);
UART_WriteBlocking(UART4_PERIPHERAL, (uint8_t *)&a, 1);
}
//HAL_UART_Transmit(&UartHandle, (uint8_t *)(str + i), 1, 1);
UART_WriteBlocking(UART4_PERIPHERAL, (uint8_t *)(str + i), 1);
}
}
rt_hw_console_getchar 函数,这个函数在一个finsh线程中轮询获取一个字符,然后finsh线程会自动保存并解析获取到的字符。
finsh的原理如下:
官方的移植方法考虑到了finsh处理速度慢于串口的情况,所以用缓冲区实现的比较麻烦,实际上可以使用消息队列更简单的实现,主要原理就是利用消息队列在串口中断和finsh线程之间传递收到的字符。
/* 使用消息队列 */
struct rt_messagequeue mq;
static rt_uint8_t msg_pool[512];
char rt_hw_console_getchar(void)
{
char ch = 0;
rt_mq_recv(&mq, &ch, sizeof(ch), RT_WAITING_FOREVER);
return ch;
}
/* 串口中断 */
void UART4_SERIAL_RX_TX_IRQHANDLER(void) {
uint32_t flag = UART_GetStatusFlags(UART4_PERIPHERAL);
if ((kUART_RxDataRegFullFlag | kUART_RxOverrunFlag) & flag)
{
uint8_t rx_data = UART_ReadByte(UART4_PERIPHERAL);
rt_mq_send(&mq, &rx_data ,1);
}
}
/* 初始化消息队列 */
void initMQ(){
rt_err_t result;
result = rt_mq_init(&mq,"mqt",&msg_pool[0],1,sizeof(msg_pool),RT_IPC_FLAG_FIFO);
if (result != RT_EOK)
{
rt_kprintf("finsh shell init message queue failed.\n");
}
}
/* 将消息队列初始化添加到RT-thread启动 */
INIT_APP_EXPORT(initMQ);