从os-cli看串口操作

cli,主要是实现输出命令行,控制设备。再就是查询设备状态的作用。

实现过程分析

实现串口初始化

这块是由cube自动生成的。

cli任务初始化

在cli_task.c中执行module_init("cli", cli_task_init); 这样就会在程序启动初始化时,会自动调用cli_task_init();

static void cli_task_init(void)
{
    cli_port_t p = {tty.write, tty.read};           /*读写接口 直接映射到底层的操作*/   
    cli_init(&cli, &p);                             /*初始化命令行对象 */     
    cli_enable(&cli);  
    cli_exec_cmd(&cli,"sysinfo");                   /*找sysinfo这个命令有的话就调用里面的函数就打印出来   这个命令是在user/cmd下cmd_devinfo.c中实现的,通过cmd_regiset注册*/
}

cli_init()初始化实现

void cli_init(cli_obj_t *obj, const cli_port_t *p)
{
    obj->read   = p->read;
    obj->write  = p->write;
    obj->print  = cli_print;//这个是在cli函数中定义的,也就和init在同一个.c文件中,实际是实现可变参数的打印输出
    obj->enable = true;
}

任务执行

除了先前的按键、灯的任务就是这个任务了。cli_process,命令处理程序,也是在cli.c中实现

void cli_process(cli_obj_t *obj)
{    
    int i;
    if (!obj->read || !obj->enable)
        return;
    i = obj->recvcnt;
    obj->recvcnt += obj->read(&obj->recvbuf[i], CLI_MAX_CMD_LEN - i);
    while (i < obj->recvcnt) {
        if (obj->recvbuf[i] == '\r' || obj->recvbuf[i] == '\n') {    /*读取1行*/
            obj->recvbuf[i] = '\0';
            process_line(obj);//这个就是找有没有这个注册的命令,有的话就执行命令
            obj->recvcnt = 0;
        }
        i++;
    }
    if (obj->recvcnt >= CLI_MAX_CMD_LEN) /*缓冲区满之后强制清空*/
        obj->recvcnt = 0;
}

os默认是带了help及?两个命令的。只要串口调试器端输入help就会有反应。所以关键移植的关键还是实现初始化后的底层的读写。

串口操作

cli初始化时,tty.write()做了些什么呢?

static unsigned int uart_write(const void *buf, unsigned int len)
{   
    unsigned int ret;
    ret = ring_buf_put(&rbsend, (unsigned char *)buf, len);  //将数据放到发送缓冲里,由环形队列实现
    USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//启动发送中断
    return ret; 
}

从这里可以分析到:应该是先由串口调试器,发送命令给OS,os一看有这个命令,便会跳到这个命令函数处,执行里面的内容。命令函数里便会调用cli_print(),调用这里的write,发送相应的内容。
tty.read();//更简单了,只是ring_buf_get(&rbrecv, (unsigned char *)buf, len);//从缓冲区拿数据就可以了。

串口中断的实现

void USART1_IRQHandler(void)
{     
    unsigned char data;
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        data = USART_ReceiveData(USART1);
        ring_buf_put(&rbrecv, &data, 1);           /*按字节接收数据,将数据放入接收缓冲区*/             
    }
    if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET) {
        if (ring_buf_get(&rbsend, &data, 1))      /*从缓冲区中取出数据---*/
            USART_SendData(USART1, data);   //发送数据         
        else{
            USART_ITConfig(USART1, USART_IT_TXE, DISABLE);    
        }
    }
    if (USART_GetITStatus(USART1, USART_IT_ORE_RX) != RESET) {
        data = USART_ReceiveData(USART1);        
    }
}

hal库串口操作

关于hal阻塞式发送和接收函数

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);//串口发送,发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);//串口接收,接收指定长度的数据。如果超时没接收完成,则不再接收数据到指定缓冲区,返回超时标志(HAL_TIMEOUT)。

发送就等着发送完,HAL中阻塞式发送函数的第4个参数Timeout,可以设置一个超时时间,超时后没发完就不再阻塞。
对应的就是标准库里的

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)

中断式发送与接收函数 所谓的非阻塞式

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);//串口中断发送,以中断方式发送指定长度的数据。
大致过程是,把 发送缓冲区指针 指向 要发送的数据,设置 发送长度,发送计数器初值,然后使能串口发送中断,触发串口中断。
再然后,串口中断函数处理,直到数据发送完成,而后关闭中断,不再发送数据,串口发送完成回调函数。

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);//串口中断接收,以中断方式接收指定长度数据。
大致过程是,把 接收缓冲区指针 指向 要存放接收数据的数组,设置 接收长度,接收计数器初值,然后使能串口接收中断。接收到数据时,会触发串口中断。
再然后,串口中断函数处理,直到接收到指定长度数据,而后关闭中断,不再触发接收中断,调用串口接收完成回调函数。

在HAL中,打开串口和中断合并成了一个函数:发送就打开发送中断,接收就打开接收中断

发送数据

串口初始化完成后,
使能USARTx_CR1的TE位使能发送器。
HAL_UART_Init(UART_HandleTypeDef *huart) 调用以下
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)实现的操作为:

  1. 向发送数据寄存器TDR写入要发送的数据(对于M3,发送和接收共用DR寄存器)。
  2. 向TRD寄存器写入最后一个数据后,等待状态寄存器USARTx_SR(ISR)的TC位置1,传输完成。这种状态属于阻塞式发送

接收数据

初始化完成后,使能USARTx_CR1的RE位为1使能接收器。
如果要使能接收中断(接收到数据后产生中断),使能USARTx_CR1的RXNEIE位为1。
HAL_UART_Init(UART_HandleTypeDef \*huart)
/* 配置7步骤:开启接收中断,并且设置接收缓冲剂最大接收数据量 */
HAL_UART_Receive_IT(UART_HandleTypeDef \*huart, uint8_t *pData, uint16_t Size)

串口接收到数据时

USARTx_SR(ISR)的RXNE位置1。表明移位寄存器内容已经传输到RDR(DR)寄存器。已经接收到数据并且等待读取。
如果开启了接收数据中断(USARTx_CR1寄存器的RXNEIE位为1),则会产生中断。(程序上会执行中断服务函数)
如果开启了其他中断(帧错误等),相应标志位会置1。
读取USARTx_TDR(DR)寄存器的值,该操作会自动将RXNE位清零,等待下次接收后置位。
步骤为:

/* 步骤1 ,获取状态标志位通过标识符实现 */
__HAL_UART_GET_FLAG              //判断状态标志位
__HAL_UART_GET_IT_SOURCE   		//判断中断标志位  
/* 步骤2~3,中断服务函数 */
void USARTx_IRQHandler(void) ;
/* 步骤4,读取接收数据 */
HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

网友总结的已经很好了:

在HAL中void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);这个库函数帮我们完成了中断类型判断和清除标志位,我们只需要在具体的函数中写逻辑即可。

扩展 关于串口的DMA方式相关函数

HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
串口DMA发送,以DMA方式发送指定长度的数据。
过程是,把 发送缓冲区指针 指向 要发送的数据,设置 发送长度,发送计数器初值,设置 DMA传输完成中断的回调函数,使能DMA控制器中断,使能DMA控制器传输,使能UART的DMA传输请求。
然后,UART便会发送数据,直到发送完成,触发DMA中断。
DMA中断处理,如果 DMA模式 是 循环模式,则 直接 调用 DMA传输完成中断的回调函数。
如果 DMA模式 是 正常模式,则 先 关闭DMA传输完成中断,不再触发DMA中断,再 调用 DMA传输完成中断的回调函数。
DMA传输完成中断的回调函数处理过程,如果 DMA模式 是 循环模式,则 直接 调用 串口发送完成回调函数。
如果 DMA模式 是 正常模式,则 先关闭 UART的DMA传输请求, 再 使能串口传输完成中断,直到传输完成,触发中断。
串口传输完成中断处理,关闭中断,调用串口发送完成回调函数。

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
串口DMA接收,以DMA方式接收指定长度的数据。
过程是,把 接收缓冲区指针 指向 要存放接收数据的数组,设置 接收长度,接收计数器初值,设置 DMA传输完成中断的回调函数,使能DMA控制器中断,使能DMA控制器传输,使能UART的DMA传输请求。
然后,UART接收到数据,便会通过DMA把数据存到接收缓冲区,直到接收到指定长度数据,触发DMA中断。
DMA中断处理,如果 DMA模式 是 循环模式,则 直接 调用 DMA传输完成中断的回调函数。
如果 DMA模式 是 正常模式,则 先 关闭DMA传输完成中断,不再触发DMA中断,再 调用 DMA传输完成中断的回调函数。
DMA传输完成中断的回调函数处理过程,如果 DMA模式 是 循环模式,则 直接 调用 串口接收完成回调函数。
如果 DMA模式 是 正常模式,则 先关闭 UART的DMA传输请求, 再 调用 串口接收完成回调函数。
模式,则 先 关闭DMA传输完成中断,不再触发DMA中断,再 调用 DMA传输完成中断的回调函数。
DMA传输完成中断的回调函数处理过程,如果 DMA模式 是 循环模式,则 直接 调用 串口接收完成回调函数。

版权声明:本文为CSDN博主「guangod」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/guangod/article/details/121760991

guangod

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

串口通信小试牛刀

串口通信小试牛刀 目录 串口通信小试牛刀一、串口通信协议:RS-232 1.串口通信协议2. RS-2323.RS232电平与TTL电平的区别4.USB/TTL转232模块(以CH340芯片模块为例&#xff09

STM32F103寄存器方式点亮LED流水灯

STM32F103寄存器方式点亮LED流水灯 1、学习和理解STM32F103系列芯片的地址映射和寄存器映射原理;了解GPIO端口的初始化设置三步骤(时钟配置、输入输出模式设置、最大速率设置)。 2、

STM32硬件CRC的使用

STM32硬件CRC的使用 STM32硬件的CRC不占用MCU的计算资源,和软件查表计算消耗的存储空间。但其结果与平常使用的CRC不一样,导致很多人还是在用软件计算CRC。 其实结果的差别,只是由于计

电脑不识别STM32的USB虚拟串口

电脑不识别STM32的USB虚拟串口 现象 板子和电脑联调的时候发现,USB线插入板子以后电脑不识别虚拟串口,通过禁用设备再启用,可以正常工作。也可以按一下复位键才能识别。 以前似乎没有这个问题&#