• RT-Thread Nano移植FinSH控制台


    RT-Thread Nano 提供了shell 控制台组件 FinSH ,对于 Nano 版本的 FinSH 组件,没有使用 RT-Thread 的设备驱动框架,所以移植起来比较简单,只要提供控制台的打印输出函数 rt_hw_console_output 和输入函数 rt_hw_console_getchar 即可。

    1. 实现Nano控制台的串口输出函数

    我使用的硬件平台是 STM32F407ZGT6 ,移植前先准备可以正常运行 RT-Thread Nano 的工程源码。

    1.1 初始化串口外设

    对于控制台的输入输出硬件接口,我使用的是串口,所以需要对串口外设进行基本的初始化,并且开启串口外设的接收中断(输入使用串口中断方式)。

    对于串口外设的初始化的代码,我使用的是CubeMX生成的初始化代码。我发现 CubeMX 生成的初始化代码,只是开启了使用的那个串口的总中断,还要自己单独写代码开启串口的接收中断的,不知道是不是我配置 CubeMX 的问题,反正花了点时间才发现问题。

    另外,实现控制台打印需要确认 rtconfig.h 文件中已经定义了 RT_USING_CONSOLE 这个宏。

    串口外设初始化代码如下:

    void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
    {
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        if(uartHandle->Instance==USART1)
        {
            /* 初始化串口接收数据的信号量 */
            rt_sem_init(&(shell_rx_sem), "shell_rx", 0, 0);
    
            /* USER CODE END USART1_MspInit 0 */
            /* USART1 clock enable */
            __HAL_RCC_USART1_CLK_ENABLE();
            __HAL_RCC_GPIOA_CLK_ENABLE();
            
            /**USART1 GPIO Configuration
        	   PA9     ------> USART1_TX
               PA10     ------> USART1_RX
            */
            GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
            GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
            GPIO_InitStruct.Pull = GPIO_NOPULL;
            GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
            GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
            HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
            /* USART1 interrupt Init */
            HAL_NVIC_SetPriority(USART1_IRQn, 4, 0);		// 中断优先级配置
            HAL_NVIC_EnableIRQ(USART1_IRQn);				// 使能串口1中断
            huart1.Instance->SR &= ~(USART_SR_RXNE);		// 清除RXNE标志位
            __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);    // 使能串口接收中断
        }
    }
    
    • 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

    1.2 实现控制台输出函数 rt_hw_console_output

    rt_hw_console_output 是控制台输出的一个函数,函数名需要保持不变。

    UART_HandleTypeDef huart1;
    
    void rt_hw_console_output(const char *str)
    {
        unsigned int i = 0, size = 0;
        char a = '\r';
    
        __HAL_UNLOCK(&huart1);
    
        size = strlen(str);
        for (i = 0; i < size; i++)
        {
            if (*(str + i) == '\n')
            {
                /* 当输出\n字符时,在\n字符之前先输出\r */
                HAL_UART_Transmit(&huart1, (uint8_t *)&a, 1, 1);
            }
            HAL_UART_Transmit(&huart1, (uint8_t *)(str + i), 1, 1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    当实现了这个函数之后,启动 RT-Thread Nano 时,就可以在串口终端打印出启动是的 logo 和版本信息等内容了。如下图:

    在这里插入图片描述

    2. 实现命令输入

    2.1 添加 FinSH 源码到 keil

    把 RT-Thread Nano 目录下的 components/finsh 目录,复制到 keil 工程目录中,然后把下面4个 C 文件添加到 keil 工程。

    在这里插入图片描述

    另外,rtconfig.h 配置文件中,需要使能 #define RT_USING_FINSH 宏定义,才能使用 FinSH 组件。

    2.2 实现控制台输入函数rt_hw_console_getchar

    rt_hw_console_getchar 函数功能,就是控制台从串口中获取一个字符。

    获取一个字符的方式可以采用查询方式。当然查询方式不能死等了(查询不到可读字符时,调用延时函数休眠10ms),因为需要让出CPU给其他线程运行的机会。

    也可以采用中断方式(我这里采用中断方式),可以更有效的利用CPU。

    中断方式实现原理

    当串口接收到数据产生接收中断时,在中断中把数据存入 ringbuffer 缓冲区中,然后释放信号量。而 shell 线程去获取信号量,可以获取到信号量则读取 ringbuffer 中的数据,没有数据则一直阻塞等待获取串口接收中断释放的信号量。

    2.2.1 实现ringbuffer读写

    先把环形缓冲区的读写代码先实现了。

    #define BUFFER_SIZE 1024        /* 环形缓冲区的大小 */
    typedef struct
    {
    	volatile unsigned int pR;           /* 读地址 */
    	volatile unsigned int pW;           /* 写地址 */   
        unsigned char buffer[BUFFER_SIZE];  /* 缓冲区空间 */    
    } ring_buffer;
    
    /*
     *  函数名:void ring_buffer_init(ring_buffer *dst_buf)
     *  输入参数:dst_buf --> 指向目标缓冲区
     *  输出参数:无
     *  返回值:无
     *  函数作用:初始化缓冲区
    */
    void ring_buffer_init(ring_buffer *dst_buf)
    {
        dst_buf->pW = 0;
        dst_buf->pR = 0;
    }
    
    /*
     *  函数名:void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
     *  输入参数:c --> 要写入的数据
     *            dst_buf --> 指向目标缓冲区
     *  输出参数:无
     *  返回值:无
     *  函数作用:向目标缓冲区写入一个字节的数据,如果缓冲区满了就丢掉此数据
    */
    void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
    {
        int i = (dst_buf->pW + 1) % BUFFER_SIZE;
        if(i != dst_buf->pR)    // 环形缓冲区没有写满
        {
            dst_buf->buffer[dst_buf->pW] = c;
            dst_buf->pW = i;
        }
    }
    
    /*
     *  函数名:int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
     *  输入参数:c --> 指向将读到的数据保存到内存中的地址
     *            dst_buf --> 指向目标缓冲区
     *  输出参数:无
     *  返回值:读到数据返回0,否则返回-1
     *  函数作用:从目标缓冲区读取一个字节的数据,如果缓冲区空了返回-1表明读取失败
    */
    int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
    {
        if(dst_buf->pR == dst_buf->pW)
        {
            return -1;
        }
        else
        {
            *c = dst_buf->buffer[dst_buf->pR];
            dst_buf->pR = (dst_buf->pR + 1) % BUFFER_SIZE;
            return 0;
        }
    }
    
    • 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

    2.2.2 串口接收中断

    串口接收中断要做的事情就是:把读取到的数据存放进 ringbuffer 中,然后释放信号量唤醒 shell 线程。

    static ring_buffer uart1_rx_buf = {0, 0, {0}};		/* 串口接收 ringbuffer */
    static struct rt_semaphore shell_rx_sem; 			/* 定义一个信号量 */
    
    void USART1_IRQHandler(void)
    {
        int ch = -1;
    
        /* enter interrupt */
        rt_interrupt_enter();          //在中断中一定要调用这对函数,进入中断
    
        if ((__HAL_UART_GET_FLAG(&(huart1), UART_FLAG_RXNE) != RESET) &&
            (__HAL_UART_GET_IT_SOURCE(&(huart1), UART_IT_RXNE) != RESET))
        {
            while (1)
            {
                ch = -1;
                if (__HAL_UART_GET_FLAG(&(huart1), UART_FLAG_RXNE) != RESET)
                {
                    ch =  huart1.Instance->DR & 0xff;
                }
                if (ch == -1)
                {
                    break;
                }
                /* 读取到数据,将数据存入 ringbuffer */
                ring_buffer_write(ch, &uart1_rx_buf);
            }
            rt_sem_release(&shell_rx_sem);				// 释放信号量唤醒 shell 线程
        }
    
        /* leave interrupt */
        rt_interrupt_leave();    //在中断中一定要调用这对函数,离开中断
    }
    
    • 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

    2.2.3 实现rt_hw_console_getchar

    rt_hw_console_getchar 函数是 shell 线程接收一个字符函数,从而实现命令行交互功能的。代码如下:

    char rt_hw_console_getchar(void)
    {
        char ch = 0;
    	
        /* 从 ringbuffer 中拿出数据 */
        while (ring_buffer_read((unsigned char *)&ch, &uart1_rx_buf) != 0)
        {	
            /* 没有读取到数据,则一直阻塞等待 */
            rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER);
        }	
        return ch;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    实现完上面的代码之后,就可以实现命令行交互功能了。自己可以在串口终端软件输入 help 命令,可以查看到列出了系统支持的所有命令,说明移植 shell 成功了。

  • 相关阅读:
    HTML<var> 标签
    二进制搜索树(BSTs) 和AVL 树
    抖音seo源码--开源,支持二开不加密
    推测肿瘤细胞拷贝数
    深入理解React中的useEffect钩子函数
    基本分页存储管理
    端到端数据保护浅析
    好用的dns服务器工具有哪些?
    java每日一记 —— 谈谈反射
    MySQL8.0优化 - 事务的隔离级别
  • 原文地址:https://blog.csdn.net/luobeihai/article/details/126456955