MCU移植控制台

前言:本移植首先得益于RTT系统(国产开源!力推!)
笔者偏爱该操作系统的主要原因就是因为其Finsh组件,在该组件中,可以很好地移植MSH控制台,使得在MCU上进行开发调参更加地容易,实验数据也更容易可视化

在该篇博客中,相比官网的移植来说,使用了更加高效的中断接收的形式

RTT-Nano移植工作

首先,就要把这个RT-Thread移植到MCU上,本例直接在上个UART-DMA实验配置基础上进行移植,移植操作如下:

  1. 在CubeMX上打开勾选起第三方软件RT-Thread

    在CubeMX上如何配置RTT包可以参看官方的样例文档使用CubeMX移植RTT-Nano

    启用RTT

  2. 取消部分CubeMX自动生成的代码

    • 硬件错误中断服务函数(Hard fault interrupt)
    • 内存管理错误服务函数(Memory management fault)
    • 系统服务的未决请求函数(Pendable request for system service)
    • sysTick中断服务函数(Time base:System tick timer)

    取消部分自动生成代码

    因为这4个服务函数现在由RTT负责管理了

  3. 在NVIC中开启UART1的全局中断

    串口中断

  4. 可以生成工程了

适配控制台底层驱动

其实适配移植该控制台非常简单,主要就是实现该控制台和串口收发关系逻辑

  • 首先是控制台的输出,所有的控制台输出字符都是通过调用该接口

    1
    2
    3
    4
    5
    6
    7
    8
    /*
    * @brief 控制台输出字符接口(RTT标准)
    * @arg1 输出的字符串
    */
    void rt_hw_console_output(const char *str)
    {
    HAL_UART_Transmit(&huart1,(uint8_t *)str,rt_strlen(str),0xff);
    }

    笔者尝试使用DMA来替代轮询,但是控制台的发送数据多次调用的情况下,若不使用逻辑控制则会导致DMA输出乱码(相当于数据还没有发好,新的字符串就覆盖上去了)

    若使用信号量控制,有两个问题:开机的时候信号量调度是系统还没有初始化(启动)的;因为这是一个接口函数,会多次被调用,则系统的压力会增高

  • 接下来是(不定长)输入,这是最难的part了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    char rt_hw_console_getchar(void)
    {
    char ch = 0;

    /* 从 ringbuffer 中拿出数据 */
    while (rt_ringbuffer_getchar(&uart_rxcb, (rt_uint8_t *)&ch) != 1) //若没有接收到
    {
    rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER); //永久等待字符接收信号量
    }
    return ch; //返回字符
    }

    对于接收不定长数据,若采用轮询的方式,太浪费资源了;所以采用了中断接收的方式。核心思想是开辟一个环形缓冲区,在接收中断到来时,不断将数据填入到缓冲区,并且释放一次信号量;在输入接口处采用信号量的方式挂起。在输人接口使用信号量,不会有输出时未初始化和多次调用的问题

    串口中断服务函数如下:

    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
    void FinshUartIRQHandler(void)  //函数名笔者使用了宏定义进行可移植性扩展
    {
    int ch = -1;
    /* enter interrupt */
    rt_interrupt_enter(); //在中断中一定要调用这对函数,进入中断

    if ((__HAL_UART_GET_FLAG(&(FinshUartHandle), UART_FLAG_RXNE) != RESET) &&
    (__HAL_UART_GET_IT_SOURCE(&(FinshUartHandle), UART_IT_RXNE) != RESET))
    {
    while (1)
    {
    ch = -1;
    if (__HAL_UART_GET_FLAG(&(FinshUartHandle), UART_FLAG_RXNE) != RESET)
    {
    ch = FinshUartHandle.Instance->RDR & 0xff;
    }
    if (ch == -1)
    {
    break;
    }
    /* 读取到数据,将数据存入 ringbuffer */
    rt_ringbuffer_putchar(&uart_rxcb, ch);
    }
    rt_sem_release(&shell_rx_sem);
    }

    /* leave interrupt */
    rt_interrupt_leave(); //在中断中一定要调用这对函数,离开中断
    }

    环形缓冲队列是嫖的RTT的,此处给出接口如下:

    1
    2
    3
    4
    5
    rt_inline enum rt_ringbuffer_state rt_ringbuffer_status(struct rt_ringbuffer *rb);  //缓冲队列状态
    rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb); //有效数据长度
    void rt_ringbuffer_init(struct rt_ringbuffer *rb,rt_uint8_t *pool,rt_int16_t size); //初始化缓冲器
    rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch); //向缓冲器中添加字符
    rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch); //从缓冲器中取出字符
  • 控制台IO初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    rt_uint8_t uart_rx_buf[FinshUartRxBufLen] = {0};
    struct rt_ringbuffer uart_rxcb; /* 定义一个 ringbuffer cb */
    static struct rt_semaphore shell_rx_sem; /* 定义一个静态信号量 */

    /*
    * @brief 初始化控制台IO
    */
    void FinshIoInit()
    {
    rt_ringbuffer_init(&uart_rxcb, uart_rx_buf, FinshUartRxBufLen); //初始化缓冲器

    rt_sem_init(&(shell_rx_sem), "shell_rx", 0, 0); //初始化信号量

    __HAL_UART_ENABLE_IT(&FinshUartHandle, UART_IT_RXNE); //开启串口接收中断
    }
  • RTT板级初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    void rt_hw_board_init()
    {
    HAL_Init(); //HAL库初始化
    MX_GPIO_Init(); //GPIO口初始化
    MX_DMA_Init(); //DMA接口初始化
    MX_USART1_UART_Init(); //串口初始化

    FinshIoInit(); //控制台接口驱动初始化
    /* 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
    }
  • 自定义控制台功能命令

    1
    2
    3
    4
    5
    6
    void toggleLED1(void)   //LED灯状态翻转
    {
    HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
    }

    MSH_CMD_EXPORT(toggleLED1,toggle The LED Light); //导出命令接口

    笔者的开发板上有LED的,大家可以灵活改成自己的接口

  • 附:其他移植性定义

    1
    2
    3
    #define FinshUartHandle huart1   //适配为使用的UART句柄
    #define FinshUartIRQHandler USART1_IRQHandler //相应的UART中断
    #define FinshUartRxBufLen 16 //适配为需要的缓存空间大小

    这是笔者在写HAL库驱动时的一套个人爱好,所有的句柄都使用宏定义可以使得移植性更加优秀

源代码

上述代码主要节选出为了理清逻辑用,具体的工程源代码已经上传到Github仓库:Finsh-RTT,工程结构说明如下:

  • 文件组织

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    --+ Finsh-RTT   //工程根目录
    --+ BSP //BSP文件夹
    - uartFinshCharIO.c //串口控制台驱动C
    - uartFinshCharIO.h //串口控制台接口H
    --+ Thread //用户自定义线程文件夹
    - ShellPort.c //自定义的控制台功能线程
    --+ Middlewares\Third_Party\RT-Thread\bsp
    - board.c //RTT的基础bsp初始化文件
    - README.md //帮助文件
    ...

测试结果

开机测试

  • 系统初始化成功
  • help显示正常
  • 自定义功能函数调用正常
  • 线程列写正确

开心!😝以后可以用这个控制台调参了

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022-2023 RY.J
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信