• STM32在STM32CubeIDE平台下的RT-Thread Nano移植


    一、软硬件版本

    STM32:STM32F103C8T6
    RT-Thread:RT-Thread Nano 3.15
    STM32CubeIDE:v1.6.0

    RT-Thread Nano
    RT-Thread Nano 是一个极简版的硬实时内核,它是由 C 语言开发,采用面向对象的编程思维,具有良好的代码风格,是一款可裁剪的、抢占式实时多任务的 RTOS。其内存资源占用极小,功能包括任务处理、软件定时器、信号量、邮箱和实时调度等相对完整的实时操作系统特性。适用于家电、消费电子、医疗设备、工控等领域大量使用的 32 位 ARM 入门级 MCU 的场合。

    在这里插入图片描述

    RT-Thread Nano 以软件包的方式集成在 Keil MDK 与 CubeMX 中,可以直接在软件中下载 Nano 软件包获取源码。

    与 RT-Thread 完整版不同的是,Nano 不含 Scons 构建系统,不需要 Kconfig 以及 Env 配置工具,也去除了完整版特有的 device 框架和组件,仅是一个纯净的内核。

    由于 Nano 的极简特性,使 Nano 的移植过程变得极为简单。添加 Nano 源码到工程,就已完成 90% 的移植工作。在 Keil MDK 与 Cube MX 中还提供了 Nano 的软件包,可以一键下载加入到工程。另外,在 RT-Thread Studio 中可以基于 Nano 创建工程直接使用。

    CubeMax下的Nano移植比较多,但我习惯在STM32CubeIDE下编程,但存在一个问题,那就是通过软件包的方式移植rtthread Nano后,修改过rtthread相关的配置文件后再配置ioc文件,rtthread相关的配置又会初始化,所以本例不以软件包的形式移植rtthread Nano。

    二、基本配置

    2.1 时钟配置

    本例使用外部高速时钟(HSE),并配置好时钟树。
    在这里插入图片描述
    在这里插入图片描述

    2.2 打开调SYS中的Debug选项

    在这里插入图片描述

    2.3 时基修改

    本例修改为了TIM3
    SYS Timebase Source 是STM32的HAL库中的新增部分,主要用于实现 HAL_Delay() 以及作为各种 timeout 的时钟基准。
    在使用了OS(操作系统)之后,OS的运行也需要一个时钟基准(简称“时基”),来对任务和时间等进行管理。而OS的这个时基一般也都是通过 SysTick(滴答定时器) 来维护的,这时就需要考虑 “HAL的时基” 和 “OS的时基” 是否要共用 SysTick(滴答定时器) 了。

    由于共用时基可能出现未知问题,加上STM32有多个时钟源资源,故选用TIM3作为HAL时基。

    选用TIM3作为HAL时基后,会在工程下生成一个stm32f1xx_hal_timebase_tim.c文件
    在这里插入图片描述
    该文件下的HAL_InitTick会覆盖掉库文件里的弱函数HAL_InitTick,从而使得TIM3替代掉原来的SysTick

    /**
      * @brief  This function configures the TIM3 as a time base source.
      *         The time source is configured  to have 1ms time base with a dedicated
      *         Tick interrupt priority.
      * @note   This function is called  automatically at the beginning of program after
      *         reset by HAL_Init() or at any time when clock is configured, by HAL_RCC_ClockConfig().
      * @param  TickPriority: Tick interrupt priority.
      * @retval HAL status
      */
    HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.4 串口配置

    选用usart1作为rtt的console 输出口
    在这里插入图片描述

    2.5 配置适配 RT-Thread Nano

    中断与异常处理
    RT-Thread 操作系统重定义 HardFault_Handler、PendSV_Handler、SysTick_Handler 中断函数,为了避免重复定义的问题,在生成工程之前,需要在中断配置中,代码生成的选项中,取消选择三个中断函数(对应注释选项是 Hard fault interrupt, Pendable request, Time base :System tick timer),最后点击生成代码。
    在这里插入图片描述

    三、RT-Thread Nano移植

    3.1 下载源码

    https://github.com/RT-Thread/rtthread-nano

    3.2 整理源码

    • bsp:只需保留bsp/_template/cubemx_config/下的board.crtconfig.h
    • compnents:用到的组件,目前不需要,可以不用,后续可以把finsh组件添加进来
    • include:全部包含
    • libcpu:只选用arm下的cortex-m3
    • src:全部包含
      在这里插入图片描述

    3.3 文件添加到工程

    在这里插入图片描述
    在这里插入图片描述

    3.4 配置 board.c

    #include 
    #include 
    
    #include "main.h"
    #include "usart.h"
    #include "gpio.h"
    
    #if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    /*
     * Please modify RT_HEAP_SIZE if you enable RT_USING_HEAP
     * the RT_HEAP_SIZE max value = (sram size - ZI size), 1024 means 1024 bytes
     */
    #define RT_HEAP_SIZE (10*1024)
    static rt_uint8_t rt_heap[RT_HEAP_SIZE];
    
    RT_WEAK void *rt_heap_begin_get(void)
    {
        return rt_heap;
    }
    
    RT_WEAK void *rt_heap_end_get(void)
    {
        return rt_heap + RT_HEAP_SIZE;
    }
    #endif
    
    void SysTick_Handler(void)
    {
        rt_interrupt_enter();
        
        rt_tick_increase();
    
        rt_interrupt_leave();
    }
    
    /**
     * This function will initial your board.
     */
    void rt_hw_board_init(void)
    {
    	extern void SystemClock_Config(void);
    
    	//第一部分:系统初始化,系统时钟配置等
    	//系统时钟是给各个硬件模块提供工作时钟的基础,在 rt_hw_board_init() 函数中完成,可以调用库函数实现配置,也可以自行实现
    	HAL_Init(); 						// 一些系统层初始化,若需要则增加此部分
    	SystemClock_Config();				// 配置系统时钟
    	SystemCoreClockUpdate();			// 更新系统时钟频率 SystemCoreClock
    
    	//第二部分:配置 OS Tick的频率,实现 OS节拍(并在中断服务例程中实现 OS Tick 递增)
    	HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / RT_TICK_PER_SECOND);
    
        //第三部分:初始化硬件外设,若有需要,则放在此处调用 ,注意,uart_init() 或者其他的外设初始化函数,若已经使用了宏 INIT_BOARD_EXPORT() 进行初始化,则不需要在此进行显式调用。两种初始化方法选择一种即可。
    	MX_GPIO_Init();
    	MX_USART1_UART_Init();
    
        /* 第四部分:系统动态内存堆初始化 */
    	#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
    		rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
    	#endif
    
        /* 第五部分:使用 INIT_BOARD_EXPORT() 进行的初始化 Call components board initial (use INIT_BOARD_EXPORT())  */
    	#ifdef RT_USING_COMPONENTS_INIT
    		rt_components_board_init();
    	#endif
    
        /* 第六部分:其他初始化 */
    
    }
    
    
    #ifdef RT_USING_CONSOLE
    	void rt_hw_console_output(const char *str)
    	{
    		rt_size_t i = 0, size = 0;
    		char a = '\r';
    
    		__HAL_UNLOCK(&huart1);
    
    		size = rt_strlen(str);
    
    		for (i = 0; i < size; i++)
    		{
    			if (*(str + i) == '\n')
    			{
    				HAL_UART_Transmit(&huart1, (uint8_t *)&a, 1, 1);
    			}
    			HAL_UART_Transmit(&huart1, (uint8_t *)(str + i), 1, 1);
    		}
    	}
    #endif
    
    #ifdef RT_USING_FINSH
    	char rt_hw_console_getchar(void)
    	{
    		/* Note: the initial value of ch must < 0 */
    		int ch = -1;
    
    		if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
    		{
    			ch = huart1.Instance->DR & 0xff;
    		}
    		else
    		{
    	        if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_ORE) != RESET)
    	        {
    	            __HAL_UART_CLEAR_OREFLAG(&huart1);
    	        }
    			rt_thread_mdelay(10);
    		}
    		return ch;
    	}
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112

    系统时钟配置说明

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

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

    uart_init( )说明

    默认的写法如下:

    static UART_HandleTypeDef UartHandle;
    static int uart_init(void)
    {
        /* TODO: Please modify the UART port number according to your needs */
        UartHandle.Instance = USART2;
        UartHandle.Init.BaudRate = 115200;
        UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
        UartHandle.Init.StopBits = UART_STOPBITS_1;
        UartHandle.Init.Parity = UART_PARITY_NONE;
        UartHandle.Init.Mode = UART_MODE_TX_RX;
        UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
        UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
    
        if (HAL_UART_Init(&UartHandle) != HAL_OK)
        {
            while (1);
        }
        return 0;
    }
    INIT_BOARD_EXPORT(uart_init);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    串口的初始化有两种,官方默认使用宏 INIT_BOARD_EXPORT() 进行自动初始化,自己改下串口号也可以用。

    也可以使用显式调用,需要在 board.c 中的 rt_hw_board_init() 函数中调用uart_init() 。

    由于CubeIDE已自动生成了串口的初始化函数,所以本例采用的是直接自己初始化,不去写uart_init() 函数,个人觉得更简单易读。

    	MX_USART1_UART_Init();
    
    • 1

    rt_hw_console_output() 是rrt的 rt_kprintf() 函数会调用到的,实现控制台字符输出。

    #ifdef RT_USING_CONSOLE
    	void rt_hw_console_output(const char *str)
    	{
    		rt_size_t i = 0, size = 0;
    		char a = '\r';
    
    		__HAL_UNLOCK(&huart1);
    
    		size = rt_strlen(str);
    
    		for (i = 0; i < size; i++)
    		{
    			if (*(str + i) == '\n')
    			{
    				HAL_UART_Transmit(&huart1, (uint8_t *)&a, 1, 1);
    			}
    			HAL_UART_Transmit(&huart1, (uint8_t *)(str + i), 1, 1);
    		}
    	}
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    动态内存堆说明

    开启 RT_USING_HEAP 将可以使用动态内存功能,即可以使用 rt_malloc、rt_free 以及各种系统动态创建对象的 API。动态内存堆管理功能的初始化是通过 rt_system_heap_init() 函数完成的,动态内存堆的初始化需要指定堆内存的起始地址和结束地址,函数原型如下:

    void rt_system_heap_init(void *begin_addr, void *end_addr)
    
    • 1

    开启 RT_USING_HEAP 后,系统默认使用数组作为 heap,heap 的起始地址与结束地址作为参数传入 heap 初始化函数,heap 初始化函数 rt_system_heap_init() 将在 rt_hw_board_init() 中被调用。

    开启 heap 后,系统中默认使用数组作为 heap(heap 默认较小,实际使用时请根据芯片 RAM 情况改大),获得的 heap 的起始地址与结束地址,作为参数传入 heap 初始化函数:

    #define RT_HEAP_SIZE 1024
    static uint32_t rt_heap[RT_HEAP_SIZE];
    RT_WEAK void *rt_heap_begin_get(void)
    {
        return rt_heap;
    }
    
    RT_WEAK void *rt_heap_end_get(void)
    {
        return rt_heap + RT_HEAP_SIZE;
    }
    
    void rt_hw_board_init(void)
    {
        ....
    #if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
        rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());    //传入 heap 的起始地址与结束地址
    #endif
        ....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.5 startup()的跳转设置

    系统的运行应该是先去启动rtos,然后再在os里去执行生成的main函数,所以涉及到这个设置;

    芯片在 KEIL MDK 与 IAR 下的启动文件一般不用做修改,会自动转到 RT-Thread 系统启动函数 rtthread_startup() 。

    而GCC 下的启动文件则需要修改,让其跳转到 RT-Thread 提供的 entry() 函数。

    修改 startup_stm32f103c8tx.s文件,将main改为entry。

    /* Call the application's entry point.*/
      bl main
      bx lr
    
    • 1
    • 2
    • 3
    /* Call the application's entry point.*/
      bl entry
      bx lr
    
    • 1
    • 2
    • 3

    3.6 main函数的修改

    包含 rtthread.h

    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "rtthread.h"
    
    • 1
    • 2
    • 3

    编写main

    int main(void)
    {
      /* USER CODE BEGIN 1 */
    #ifndef RT_USING_USER_MAIN
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
    #endif
    
    	MX_RT_Thread_Init();
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    
    	  rt_kprintf("main_test\r\n");
    	  rt_thread_mdelay(500);
      }
      /* USER CODE END 3 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    注意: 由于相关的初始化工作已在board文件中初始化完毕,故在此处使用RT_USING_USER_MAIN宏来取消这部分的代码的编译,该宏默认开启,后续即使对图形化配置更新后,也不会覆盖掉此设定。

    3.7 再建立一个线程

    建立文件app_rt_thread.c

    #include "rtthread.h"
    #include "main.h"
    #include "stdio.h"
    
    /* 定义线程控制块 */
    void MX_RT_Thread_Init(void);
    //添加LED闪烁线程
    static struct rt_thread led_thread;
    static char led_thread_stack[256];
    static void led_thread_entry(void *parameter);
    
    void MX_RT_Thread_Init(void)
    {
    	//初始化线程
    	rt_err_t rst;
    	rst = rt_thread_init(&led_thread,
    						(const char *)"ledshine",  /* 线程名字 */
    						led_thread_entry,  /* 线程入口函数 */
    						RT_NULL,           /* 线程入口函数参数 */
    						&led_thread_stack[0],
    						sizeof(led_thread_stack),   /* 线程栈大小 */
    						RT_THREAD_PRIORITY_MAX-2,  /* 线程的优先级 */
    						20); /* 线程时间片 */
    	if(rst == RT_EOK)
    	{///* 启动线程,开启调度 */
    		rt_thread_startup(&led_thread);
    	}
    }
    
    static void led_thread_entry(void *parameter)
    {
    	while(1)
    	{
    		rt_kprintf("led_test\r\n");
    		rt_thread_mdelay(500);
    	}
    }
    //MSH_CMD_EXPORT(led_thread_entry,run_led_thread);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    在main函数中while之前初始化

    int main(void)
    {
        ......
    	MX_RT_Thread_Init();
    	while (1)
      	{
    	  rt_kprintf("main_test\r\n");
    	  rt_thread_mdelay(500);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    编译下载代码,打开串口助手,可以在串口助手看到交替打印2个线程的rt_kprintf信息;

    四、添加Finsh组件

    RT-Thread FinSH 是 RT-Thread 的命令行组件(shell),提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。它可以使用串口 / 以太网 / USB 等与 PC 机进行通信,使用 FinSH 组件基本命令的效果图如下所示:
    在这里插入图片描述
    在 RT-Thread Nano 上添加 FinSH 组件,以串口 UART 作为 FinSH 的输入输出端口与 PC 进行通信,实现 FinSH 功能的步骤主要如下:

    4.1 添加 FinSH 源码到工程

    把finsh的源码拷贝到components下。
    在这里插入图片描述

    4.2 更改配置

    然后在 rtconfig.h 中打开 finsh 相关选项

    #include "finsh_config.h"
    
    • 1

    finsh_port.c 中去掉2个error

    #include 
    #include 
    
    #ifndef RT_USING_FINSH
    //#error Please uncomment the line <#include "finsh_config.h"> in the rtconfig.h
    #endif
    
    #ifdef RT_USING_FINSH
    
    RT_WEAK char rt_hw_console_getchar(void)
    {
        /* Note: the initial value of ch must < 0 */
        int ch = -1;
    
    //#error "TODO 4: Read a char from the uart and assign it to 'ch'."
    
        return ch;
    }
    
    #endif /* RT_USING_FINSH */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    确保之前的 board.crt_hw_console_getchar函数已设置

    要实现 FinSH 组件功能:既可以打印也能输入命令进行调试,控制台已经实现了打印功能,现在还需要在 board.c 中对接控制台输入函数,实现字符输入。

    #ifdef RT_USING_FINSH
    	char rt_hw_console_getchar(void)
    	{
    		/* Note: the initial value of ch must < 0 */
    		int ch = -1;
    
    		if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
    		{
    			ch = huart1.Instance->DR & 0xff;
    		}
    		else
    		{
    	        if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_ORE) != RESET)
    	        {
    	            __HAL_UART_CLEAR_OREFLAG(&huart1);
    	        }
    			//rt_thread_mdelay(10);
    		}
    		return ch;
    	}
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4.3 链接脚本设置

    由于finsh也是作为线程,在系统初始化时自动初始化,但CubeIDE出现过未初始化成功的情况,后发现需要在STM32F103C8TX_FLASH.ld 链接脚本下添加如下内容:

        /* section information for finsh shell */
    	 . = ALIGN(4);
    	 __fsymtab_start = .;
    	 KEEP(*(FSymTab))
    	 __fsymtab_end = .;
    	 . = ALIGN(4);
    	 __vsymtab_start = .;
    	 KEEP(*(VSymTab))
    	 __vsymtab_end = .;
    	 . = ALIGN(4);
    	 
    	 /* section information for initial. */
    	 . = ALIGN(4);
    	 __rt_init_start = .;
    	 KEEP(*(SORT(.rti_fn*)))
    	 __rt_init_end = .;
    	 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    位置如下:

      /* The program code and other data into "FLASH" Rom type memory */
      .text :
      {
        . = ALIGN(4);
        *(.text)           /* .text sections (code) */
        *(.text*)          /* .text* sections (code) */
        *(.glue_7)         /* glue arm to thumb code */
        *(.glue_7t)        /* glue thumb to arm code */
        *(.eh_frame)
    
        KEEP (*(.init))
        KEEP (*(.fini))
    
        . = ALIGN(4);
        _etext = .;        /* define a global symbols at end of code */
        
        /* section information for finsh shell */
    	 . = ALIGN(4);
    	 __fsymtab_start = .;
    	 KEEP(*(FSymTab))
    	 __fsymtab_end = .;
    	 . = ALIGN(4);
    	 __vsymtab_start = .;
    	 KEEP(*(VSymTab))
    	 __vsymtab_end = .;
    	 . = ALIGN(4);
    	 
    	 /* section information for initial. */
    	 . = ALIGN(4);
    	 __rt_init_start = .;
    	 KEEP(*(SORT(.rti_fn*)))
    	 __rt_init_end = .;
    	 
      } >FLASH
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    4.4 程序更改

    由于既存在着打印信息又需要输入命令,这里先把main下列代码注释掉

    MX_RT_Thread_Init();
    
    rt_kprintf("main_test\r\n");
    
    • 1
    • 2
    • 3

    将led_thread线程添加至终端,去掉app_rt_thread.c文件MSH_CMD_EXPORT的注释:

    int main(void)
    {
    	//MX_RT_Thread_Init();
    	while (1)
      	{
    	  //rt_kprintf("main_test\r\n");
    	  rt_thread_mdelay(500);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    #include "rtthread.h"
    #include "main.h"
    #include "stdio.h"
    
    /* 定义线程控制块 */
    void MX_RT_Thread_Init(void);
    //添加LED闪烁线程
    static struct rt_thread led_thread;
    static char led_thread_stack[256];
    static void led_thread_entry(void *parameter);
    
    void MX_RT_Thread_Init(void)
    {
    	//初始化线程
    	rt_err_t rst;
    	rst = rt_thread_init(&led_thread,
    						(const char *)"ledshine",  /* 线程名字 */
    						led_thread_entry,  /* 线程入口函数 */
    						RT_NULL,           /* 线程入口函数参数 */
    						&led_thread_stack[0],
    						sizeof(led_thread_stack),   /* 线程栈大小 */
    						RT_THREAD_PRIORITY_MAX-2,  /* 线程的优先级 */
    						20); /* 线程时间片 */
    	if(rst == RT_EOK)
    	{///* 启动线程,开启调度 */
    		rt_thread_startup(&led_thread);
    	}
    }
    
    static void led_thread_entry(void *parameter)
    {
    	while(1)
    	{
    		rt_kprintf("led_test\r\n");
    		rt_thread_mdelay(500);
    	}
    }
    MSH_CMD_EXPORT(led_thread_entry,run_led_thread);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    编译下载代码,打开串口助手,可以在串口助手中打印输入 help 命令,回车查看系统支持的命令。

    在这里插入图片描述
    可以发现led_thread_entry函数已经可以作为命令了,可通过终端执行。

    五、部分问题

    5.1 问题1:低优先级的线程不运行

    低优先级的线程不运行,可能是使用Finsh造成的;

    查看shell.c文件可发现 finsh_thread_entry中有while(1)循环,其中的finsh_getchar 会调用到rt_hw_console_getchar函数。finsh需要适当的让出CPU资源。

    void finsh_thread_entry(void *parameter)
    {
        ....
         while (1)
        {
            ch = finsh_getchar();
            if (ch < 0)
            {
                continue;
            }
         ....
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    方法1:把rt_hw_console_getchar函数里的rt_thread_mdelay(10)加上,但这有可能造成终端输入命令时不连续。

    #ifdef RT_USING_FINSH
    	char rt_hw_console_getchar(void)
    	{
    		/* Note: the initial value of ch must < 0 */
    		int ch = -1;
    
    		if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
    		{
    			ch = huart1.Instance->DR & 0xff;
    		}
    		else
    		{
    	        if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_ORE) != RESET)
    	        {
    	            __HAL_UART_CLEAR_OREFLAG(&huart1);
    	        }
    			rt_thread_mdelay(10);//在未获取到字符时,需要让出 CPU
    		}
    		return ch;
    	}
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    方法2:rt_hw_console_getchar函数采用中断得方式实现。原理是,在 uart 接收到数据时产生中断,在中断中把数据存入 ringbuffer 缓冲区,然后释放信号量,tshell 线程接收信号量,然后读取存在 ringbuffer 中的数据。这部分官方有说明。

  • 相关阅读:
    STM32 外部中断
    Zookeeper集群 + Kafka集群
    selenium+python实现基本自动化测试
    海康/大华/华为等摄像头或者录像机无法通过GB28181注册到国标平台LiveGBS的问题排查方法...
    Dubbo 3 StateRouter:下一代微服务高效流量路由
    Mac安装rabbitmq延迟队列插件
    MySQL之数据库和表的创建与管理
    睿趣科技:未来抖音开网店还有前景吗
    面向未来的融合:产品管理和 DevOps 如何重新绘制数字蓝图
    VUE模板语法1
  • 原文地址:https://blog.csdn.net/a183635870/article/details/126556614