使用CubeMX移植RT-Thread

使用CubeMX移植RT-Thread_rtthread cubemax-CSDN博客

目录

一. 准备工作

1.Nano Pack安装

2.创建基础工程

二.添加RT-Thread Nano到工程

1.选择Nano组件

2.配置Nano

3. 配置MCU()

(1)配置RCC与Debug

(2)选择GPIO引脚与功能

(3)配置GPIO

(4)时钟配置

 4.工程管理

三.适配RT-Thread Nano

1.终端与异常处理

2.系统时钟配置

3.内存堆初始化

四.编写一个应用

五.出现过的问题以及解决办法

1.cubemx没有生成.s文件

2.有的地方得注释(原因不清楚)

3.又有地方得注释(原因不清楚)

4.systemclock Config报错

5.生成的board.c中串口初始化报错

6.board.c里面没有进行串口引脚初始化,还是无法使用串口

7.移植后发现第使用keil5发现没有芯片并且下载芯片时Install+是灰色

六.结果

七.RT-Thread程序执行流程分析

1.RT-Thread 入口

2.应用线程入口

七.参考链接:


我们要实现移植RT-Thread,并且用led和串口证明移植成功。

一. 准备工作

1.Nano Pack安装

要获取 RT-Thread Nano 软件包,需要在 CubeMX 中添加 https://www.rt-thread.org/download/cube/RealThread.RT-Thread.pdsc 。

具体步骤:进入打开 CubeMX,从菜单栏 help 进入 Manage embedded software packages 界面,点击 From Url 按钮,进入 User Defined Packs Manager 界面,其次点击 new,填入上述网址,然后点击 check,如下图所示:

 check 通过后,点击 OK 回到 User Defined Packs Manager 界面,再次点击 OK,CubeMX 自动连接服务器,获取包描述文件。回到 Manage embedded software packages 界面,就会发现 RT-Thread Nano 3.1.5 软件包,选择该软件包,点击 Install Now,如下图所示:

点击安装之后,弹出 Licensing Agreement ,同意协议,点击 Finish,如下图所示:

 等待安装完成,成功安装后,版本前面的小蓝色框变成填充的黄绿色,现象如下图所示:

 至此,RT-Thread Nano 软件包安装完毕,退出 Manage embedded software packages 界面,进入 CubeMX 主界面。

2.创建基础工程

在 CubeMX 主界面的菜单栏中 File 选择 New Project,如下图所示

新建工程之后,在弹出界面芯片型号中输入某一芯片型号,方便锁定查找需要的芯片,双击被选中的芯片,如下图所示

时钟树的配置直接使用默认即可,然后还需要配置下载方式。 

二.添加RT-Thread Nano到工程

1.选择Nano组件

选中芯片型号之后,点击 Softwares Packages->Select Components,进入组件配置界面,选择 RealThread, 然后根据需求选择 RT-Thread 组件,然后点击 OK 按钮,如下图所示:

 
  1. 注意:RT-Thread Nano 软件包中包含 kernel, shell 和 device 三个部分,

  2. 仅选择 kernel 表示只使用 RT-Thread 内核,工程中会添加内核代码;

  3. 选择 kernel 与 shell 表示在使用 RT-Thread Nano 的基础上使用

  4. FinSH Shell 组件,工程中会添加内核代码与 FinSH 组件的代码,FinSH

  5. 的移植详见 《在 RT-Thread Nano 上添加控制台与 FinSH》。再选择

  6. device 表示使用 rt-thread 的 device 框架,用户基于此框架编写

  7. 外设驱动并注册后,就可以使用 device 统一接口操作外设。

2.配置Nano

选择组件之后,对组件参数进行配置。在工程界面 Pinout & Configuration 中,进入所选组件参数配置区,按照下图进行配置

3. 配置MCU()

可以按照自己需求来配置,下面是我对MCU的配置

(1)配置RCC与Debug

进入System Core-SYS设置,选择Debug为Serial wire。其他参数默认

进入System Core-RCC设置,分别配置HSE为Crystal/Ceramic Resonator(晶体/陶瓷谐振器)。其他参数默认 

(2)选择GPIO引脚与功能

在右侧的单片机上选择连接有LED灯,比如我的单片机系统的PA0连接了LED灯
配置输出引脚。
在连接了LED灯的引脚上单击左键,选择Output功能。

在PA0上右键,选择Enter User Label,键入别名,输入LED。。

(3)配置GPIO

进入System Core-GPIO设置,上方选择GPIO引脚设置。
配置输出引脚PA0.。选中上方的PA0。

 
  1. 1. 引脚上电时的默认状态。(高电平/低电平) 因我的LED灯的阴极连接的单片机引脚,所以选择高电平,表示默认熄灭。

  2. 2. 引脚模式。(推挽输出/开漏输出) 这里选择推挽输出。

  3. 3. 开启引脚外部上拉或下拉。(浮空/上拉/下拉) 这里选择上拉。

  4. 4. 引脚输出速度。(低/中/高/很高) 默认即可。

  5. 5. 引脚别名。 这里之前选择GPIO时已经配置过就不用在配置。

(4)时钟配置

进入时钟配置界面。根据单片机系统中采用的晶振频率设置HSE,我的单片机系统采用的8M晶振。这里必须使能System Core-RCC中的HSE才可以设置。刚才我们已经配置过了。

配置系统主频,时钟源选择HSE,系统主频选择PLLCLK,再在HCLK框中输入系统推荐的主频,点击回车,软件即可自动配置各个分频器的值。(对于我的stm32f407vet6的时钟配置)

 4.工程管理

给工程取名、选择代码存放位置、选择生成代码的 Toolchain/IDECube MX 不仅能够生成 Keil4/Keil5 的工程,而且还能够生成 IAR7/IAR8 等 IDE 的工程,功能强大,本文从下拉框中选择 MDK5,操作如图所示

左侧选择Code Generator设置,选择仅复制需要的库文件,勾选外设初始化生成独立的.c/.h文件。这样生成的工程文件比较小并且后期容易修改。 

三.适配RT-Thread Nano

1.终端与异常处理

RT-Thread 操作系统重定义 HardFault_HandlerPendSV_HandlerSysTick_Handler 中断函数,为了避免重复定义的问题,在生成工程之前,需要在中断配置中,代码生成的选项中,取消选择三个中断函数(对应注释选项是 Hard fault interruptPendable requestTime base :System tick timer),最后点击生成代码,具体操作如下图 所示:

等待工程生成完毕,点击打开工程,如下图所示,即可进入 MDK5 工程中。 

 2.系统时钟配置

需要在 board.c 中实现 系统时钟配置(为 MCU、外设提供工作时钟)与 OS Tick 的配置 (为操作系统提供心跳 / 节拍)。

如下代码所示, HAL_Init() 初始化 HAL 库, SystemClock_Config() 配置了系统时钟, SystemCoreClockUpdate() 对系统时钟进行更新,_SysTick_Config() 配置了 OS Tick。此处 OS Tick 使用滴答定时器 systick 实现,需要用户在 board.c 中实现 SysTick_Handler() 中断服务例程,调用 RT-Thread 提供的 rt_tick_increase() ,如下图所示。

 
/* board.c */
void rt_hw_board_init()
{
    HAL_Init();
    SystemClock_Config();
 
    /* System Clock Update */
    SystemCoreClockUpdate();
 
    /* System Tick Configuration */
    _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
 
    /* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif
 
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}

3.内存堆初始化

系统内存堆的初始化在 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() 将被调用,如下所示:

初始化内存堆需要堆的起始地址与结束地址这两个参数,系统中默认使用数组作为 heap,并获取了 heap 的起始地址与结束地址,该数组大小可手动更改,如下所示: 

注意:开启 heap 动态内存功能后,heap 默认值较小,在使用的时候需要改大,否则可能会有申请内存失败或者创建线程失败的情况,修改方法有以下两种:

  • 可以直接修改数组中定义的 RT_HEAP_SIZE 的大小,至少大于各个动态申请内存大小之和,但要小于芯片 RAM 总大小。
  • 也可以参考《RT-Thread Nano 移植原理》——实现动态内存堆 章节进行修改,使用 RAM ZI 段结尾处作为 HEAP 的起始地址,使用 RAM 的结尾地址作为 HEAP 的结尾地址,这是 heap 能设置的最大值的方法。

四.编写一个应用

移植好 RT-Thread Nano 之后,则可以开始编写第一个应用代码。此时 main() 函数就转变成 RT-Thread 操作系统的一个线程,现在可以在 main() 函数中实现第一个应用:LED 指示灯闪烁。

  1. 首先在文件首部包含 RT-Thread 的相关头文件 <rtthread.h> 。
  2. 在 main() 函数中(也就是在 main 线程中)写 LED 闪烁代码:初始化 LED 引脚、在循环中点亮 / 熄灭 LED。
  3. 延时函数使用 RT-Thread 提供的延时函数 rt_thread_mdelay(),该函数会引起系统调度,切换到其他线程运行,体现了线程实时性的特点。

编译程序之后下载到芯片就可以看到基于 RT-Thread 的程序运行起来了,LED 正常闪烁。 

 注:当添加 RT-Thread 之后,裸机中的 main() 函数会自动变成 RT-Thread 系统中 main 线程 的入口函数。由于线程不能一直独占 CPU,所以此时在 main() 中使用 while(1) 时,需要有让出 CPU 的动作,比如使用 rt_thread_mdelay() 系列的函数让出 CPU。

与裸机 LED 闪烁应用代码的不同

1). 延时函数不同: RT-Thread 提供的 rt_thread_mdelay() 函数可以引起操作系统进行调度,当调用该函数进行延时时,本线程将不占用 CPU,调度器切换到系统的其他线程开始运行。而裸机的 delay 函数是一直占用 CPU 运行的。

2). 初始化系统时钟的位置不同:移植好 RT-Thread Nano 之后,不需要再在 main() 中做相应的系统配置(如 hal 初始化、时钟初始化等),这是因为 RT-Thread 在系统启动时,已经做好了系统时钟初始化等的配置。

五.出现过的问题以及解决办法

1.cubemx没有生成.s文件

cubemx生成的工程一定不能在中文路径下,并且工程文件夹名不能是中文,需要自己把.s添加进去

2.有的地方得注释(原因不清楚)

3.又有地方得注释(原因不清楚)

4.systemclock Config报错

extern void SystemClock_Config(void)​​​放里面不行,得放到外面才可以(在board.c中)

5.生成的board.c中串口初始化报错

因为我们之前勾选外设初始化生成独立的.c/.h文件,所以里面是没有串口的库函数的,我们得自己添加串口的.c.h文件,并在stm32f4xx_hal_cont.h中引用uart库函数的头文件。

6.board.c里面没有进行串口引脚初始化,还是无法使用串口

得自己写串口的引脚初始化,以串口1为例.HAL_UART_MspInit被HAL_UART_Init自动调用。

 
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
GPIO_InitTypeDef GPIO_Initure;
	
	if(huart->Instance==USART1)//如果是串口1,进行串口1 MSP初始化
	{
		__HAL_RCC_GPIOA_CLK_ENABLE();			//使能GPIOA时钟
		__HAL_RCC_USART1_CLK_ENABLE();			//使能USART1时钟
	
		GPIO_Initure.Pin=GPIO_PIN_9;			//PA9
		GPIO_Initure.Mode=GPIO_MODE_AF_PP;		//复用推挽输出
		GPIO_Initure.Pull=GPIO_PULLUP;			//上拉
		GPIO_Initure.Speed=GPIO_SPEED_FAST;		//高速
		GPIO_Initure.Alternate=GPIO_AF7_USART1;	//复用为USART1
		HAL_GPIO_Init(GPIOA,&GPIO_Initure);	   	//初始化PA9
 
		GPIO_Initure.Pin=GPIO_PIN_10;			//PA10
		HAL_GPIO_Init(GPIOA,&GPIO_Initure);	   	//初始化PA10
		
#if EN_USART1_RX
		HAL_NVIC_EnableIRQ(USART1_IRQn);				//使能USART1中断通道
		HAL_NVIC_SetPriority(USART1_IRQn,3,3);			//抢占优先级3,子优先级3
#endif	
	}
}

7.移植后发现第使用keil5发现没有芯片并且下载芯片时Install+是灰色

解决办法

1.点击如图所示

2.选择你需要的芯片,点击右边的install 

8.board.c文件中hal生成的rt_hw_console_output函数有问题

按照如下更改即可

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);
	
    //API:进入临界区,退出前系统不会发生任务调度
    rt_enter_critical();
		
    for (i = 0; i < size; i++)
    {
          //如下注释掉
//        if (*(str + i) == '\n')
//        {
//            HAL_UART_Transmit(&UartHandle, (uint8_t *)&a, 1, 1);
//        }
        HAL_UART_Transmit(&UartHandle, (uint8_t *)(str + i), 1, 1);
    }
		
    //API:退出临界区
    rt_exit_critical();
}
#endif

六.结果

串口打印

七.RT-Thread程序执行流程分析

1.RT-Thread 入口

我们可以在 components.c 文件的 131 行看到#ifdef RT_USING_USER_MAIN 宏定义判断,这个宏是定义在 rtconfig.h 文件内的,而且处于开启状态。同时 我们可以在 137 行看到#if defined (__CC_ARM)的宏定义判断,__CC_ARM 就是 指 keil 的交叉编译器名称。 我们可以在这里看到定义了 2 个函数:$Sub

main()和$Super����()和$�����

main()函数; 这里通过$Sub

main()函数在程序就如主程序之前插入一个例程,实现在不改变源代码的情况下扩展函数功能。链接器通过调用$Sub����()函数在程序就如主程序之前插入一个例程,实现在不改变源代码的情况下扩展函数功能。链接器通过调用$���

Main()函数取代 main(), 然后通过$Super$$main 再次回到 main()

#if defined(__CC_ARM) || defined(__CLANG_ARM)
extern int $Super$$main(void);
/* re-define main function */
int $Sub$$main(void)
{
rtthread_startup();
return 0;
}

在这里,$Sub

main函数仅调用了rtthreadstartup()函数。RT−Thread支持多种平台和多种编译器,而rtthreadstartup()函数是RT−Thread规定的统一入口点,所以$Sub����函数仅调用了���ℎ�����������()函数。��−�ℎ����支持多种平台和多种编译器,而���ℎ�����������()函数是��−�ℎ����规定的统一入口点,所以$���

main 函数只需调用 rtthread_startup() 函数即可。例如采用 GNU GCC 编译器编译的 RT-Thread,就是直接从汇编启动代码部分跳转到 rtthread_startup() 函数中,并开始第一个 C 代码的执行的。在 components.c的 代码中找到 rtthread_startup() 函数,我们将可以看到 RT-Thread 的启动流程 

int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* board level initialization
* NOTE: please initialize heap inside board initialization.
*/
rt_hw_board_init();
/* show RT-Thread version */
rt_show_version();
/* timer system initialization */
rt_system_timer_init();
/* scheduler system initialization */
rt_system_scheduler_init();
/* create init_thread */
rt_application_init();
/* timer thread initialization */
rt_system_timer_thread_init();
/* idle thread initialization */
rt_thread_idle_init();
/* start scheduler */
rt_system_scheduler_start();
/* never reach here */
return 0;
}
 

这部分启动代码,大致可以分为四个部分: 初始化与系统相关的硬件; 初始化系统内核对象,例如定时器,调度器; 初始化系统设备,这个主要是为 RT-Thread 的设备框架做的初始化; 初始化各个应用线程,并启动调度器。 rt_hw_board_init():该函数定义在 board.c 文件内,需要修改 systick 配置 rt_system_timer_init()/rt_system_timer_thread_init():timer 初始化/启 动 rt_thread_idle_init():idle 任务创建 rt_application_init():应用线程初始化 rt_system_scheduler_start():调度器启动 

2.应用线程入口

void rt_application_init(void)
{
rt_thread_t tid;
#ifdef RT_USING_HEAP
tid = rt_thread_create("main", main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY,
20);
RT_ASSERT(tid != RT_NULL);
#else
rt_err_t result;
tid = &main_thread;
result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY,
20);
RT_ASSERT(result == RT_EOK);
/* if not define RT_USING_HEAP, using to eliminate the warning */
(void)result;
#endif
rt_thread_startup(tid);
}

 在这里,我们可以看到应用线程创建了一个名为 main_thread_entry 的任务, 并且已经启动了该任务。我们再次来看一下 main_thread_entry 任务。

/* the system main thread */
void main_thread_entry(void *parameter)
{
extern int main(void);
extern int $Super$$main(void);
#ifdef RT_USING_COMPONENTS_INIT
/* RT-Thread components initialization */
rt_components_init();
#endif
/* invoke system main function */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
$Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
main();
#endif
}

main_thread_entry 任务完成了 2 个工作:调用 rt_components_init()、进入 应用代码真正的 main 函数。 在这里我们看到了

Super�����

main()的调用,在前 面我们讲了调用该函数可用来回到 main()的。 

从以上分析可以,正是由于在 rtconfig.h 内开启了 RT_USING_USER_MAIN 选 项,编译器在 main 之前插入了

Sub���

main(),完成了 RT-Thread 初始化及调 度器启动工作。并且通过创建 main_thread_entry 任务,并通过

Super�����

main() 回到 main()函数。这样看来 main()函数其实只是 RT-Thread 的一个任务,该 任务的优先级为 RT_THREAD_PRIORITY_MAX / 3,任务栈为 RT_MAIN_THREAD_STACK_SIZE。 

七.参考链接:

1. RT-Thread 文档中心

2.STM32CubeMX系列教程1:GPIO输入与输出_cube中读取gpio口上拉输入-CSDN博客

3.gitee工程链接:

计设-智能停车场: 计算机设计大赛项目 (gitee.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值