• 【STM32】串口和printf


    1.数据通信的基本知识

    1.串行/并行通信

    2.单工/半双工/全双工通信

    类似于【广播  对讲 电话】

    不是有两根线就是全双工,而是输入和输出都有对应的数据线。

    3.同步/异步通信

    区分同步/异步通信的根本:判断是否有时钟信号(时钟线)。如果有时钟线则是同步通信,如果没有时钟线则是异步通信。

    4.波特率  VS  比特率

    5.常见的串行通信接口

    2.串口(RS-232)

    1.什么是串口

    实际上就是按位来对数据进行发送和接收

    2.RS232电平和CMOS/TTL电平对比

    3.设备间的RS232通信示意图

    MAX323:将RS232电平---》TTL/CMOS电平

     4.STM32串口与电脑USB口通信示意图

    USB/串口转换电路CH340C:USB电平---》TTL/CMOS电平

    5.RS232异步通信协议

    注意:异步通信协议中是没有使用到时钟线(SCLK)

    3.STM32USART

    1.STM32USART简介

    USART虽然是同步和异步都可以进行使用,但是我们大多数情况下使用的都是异步通信

    2.STM32USART主要特征

    3.在选型手册中查看USART/UART对应的引脚定义

    4.STM32F1的USART框图

    1.引脚说明

    2.接收/发送数据的存放/处理位置

    数据要先放在DR寄存器中,然后再通过DR寄存器操作CPU

    3.波特率的处理

    5.框图简化版

    4.设置USART/UART波特率

    1.计算公式

    baud:波特率是用户自己定义的,然后求出USARTDIV,然后分为整数部分和小数部分,在存储到USART_BRR寄存器

    2.波特比率寄存器(BRR)

    3.举个例子

    4.公式推导

    因为小数部分和整数部分都要往左移动4位,所以将整个值*16

    5.USART寄存器

    1.控制寄存器 1(USART_CR1)

    2.控制寄存器 2(USART_CR2)

    3. 控制寄存器 3(USART_CR3)

    4. 数据寄存器(USART_DR)

    1)具体传输多少位取决于寄存器USART_CR1中的位12M【字长】

    2)设置好控制和波特率寄存器后,往该寄存器写入数据即可发送,接收数据则读该寄存器

    5.状态寄存器(USART_SR)

    根据TC位(发送完成位)可以知道能否发数据,根据RXNE位(读数据寄存器非空)知道是否收到数据。

    6.需要配置的时序总结

    6.USART/UART异步通信配置步骤

    1.HAL_USART_Init

    对USART进行初始化

    2.HAL_USART_Receive_IT

    USART的中断使能函数

    这个函数是非阻塞式的,没有执行完也可以出来

    3.HAL_USART_Receive

    没有开启中断的UASRT

    这个函数是阻塞式的,没有执行完不可以出来

    7.通过串口接收或者发送一个字符

    1.连接注意点

    2.原理图分析

    3.代码编写

    usart_init()

    相关的设置参数和开启usart的中断

    hal_usart_mspinit()

    1)使能USART1和对应的IO时钟

    2)初始化IO

    3)使能USART1中断,设置NVIC优先级

    usart1_IRQHandler

    中断回调服务函数

    2)HAL_UART_iRQHandler()会清除中断

    HAL_USART_RxCpltCallback

    串口接收数据的回调函数

    1.CubeMX和HAL库的串口实战

    1、CubeMX中打开并设置串口

    1.设置对应的RCC

    2.查看原理图上对应的RX和TX

    注意点:我们的串口对应的应该是RX--》TX,TX--》RX

    我们要先查看原理图中是否已经帮我们接反了,如果没有则需要我们自己手动接反。

    我们使用的STM32F103C8xx直接接入USB口即可

    由上面分析可知,我们使能PA9和PA10即可

    3.设置相关的USART

    4.设置相关的时钟

    2、串口操作

    (1)阻塞模式串口发送:CPU不做其他事情

    MCU的CPU一个字节一个字节的将要发送的内容丢给串口模块,然后看着串口模块将这个字节发送出去,然后CPU再去拿下一个字节来丢给串口模块。直到本次要发送的所有字节全部发完,CPU才会去做其他事。

    (2)中断模式串口发送:CPU轮询式查询

    MCU的CPU向串口模块丢一个字节,然后串口模块慢慢发,CPU丢完这个字节后会跳出去做其他事情,等串口模块发完这个字节后会生成一个中断,中断会通知CPU过来继续丢下一个字节。

    2.源码分析和串口发送的实现

    1、阻塞式串口发送/接收

    1.1 阻塞式发送:HAL_UART_Transmit(常用)

    1. HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
    2. {
    3. uint16_t *tmp;
    4. uint32_t tickstart = 0U;
    5. /* Check that a Tx process is not already ongoing */
    6. //查看状态
    7. if (huart->gState == HAL_UART_STATE_READY)
    8. {
    9. if ((pData == NULL) || (Size == 0U))
    10. {
    11. return HAL_ERROR;
    12. }
    13. /* Process Locked */
    14. //将串口模块锁住了,将变量的值设置为"HAL_LOCKED"
    15. __HAL_LOCK(huart);
    16. huart->ErrorCode = HAL_UART_ERROR_NONE;
    17. huart->gState = HAL_UART_STATE_BUSY_TX;
    18. /* Init tickstart for timeout managment */
    19. //获取当前的时候,用于判断是否超时
    20. tickstart = HAL_GetTick();
    21. huart->TxXferSize = Size;//表示要发送的个数
    22. huart->TxXferCount = Size;//还要发送的个数=总个数-已经发送
    23. while (huart->TxXferCount > 0U)
    24. {
    25. huart->TxXferCount--;
    26. if (huart->Init.WordLength == UART_WORDLENGTH_9B)
    27. {
    28. //等待标志发生,查看是否超时
    29. if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
    30. {
    31. return HAL_TIMEOUT;//超时
    32. }
    33. tmp = (uint16_t *) pData;
    34. huart->Instance->DR = (*tmp & (uint16_t)0x01FF);
    35. if (huart->Init.Parity == UART_PARITY_NONE)
    36. {
    37. pData += 2U;
    38. }
    39. else
    40. {
    41. pData += 1U;
    42. }
    43. }
    44. else
    45. {
    46. if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
    47. {
    48. return HAL_TIMEOUT;
    49. }
    50. //真正执行操作的代码
    51. huart->Instance->DR = (*pData++ & (uint8_t)0xFF);
    52. }
    53. }
    54. //查看发送是否已经完成【阻塞等待串口发完】
    55. if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
    56. {
    57. return HAL_TIMEOUT;
    58. }
    59. /* At end of Tx process, restore huart->gState to Ready */
    60. huart->gState = HAL_UART_STATE_READY;
    61. /* Process Unlocked */
    62. //解锁
    63. __HAL_UNLOCK(huart);
    64. return HAL_OK;
    65. }
    66. else
    67. {
    68. return HAL_BUSY;
    69. }
    70. }

    1.2 阻塞式接收:HAL_UART_Receive(不常用)

    1. HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
    2. {
    3. uint16_t *tmp;
    4. uint32_t tickstart = 0U;
    5. /* Check that a Rx process is not already ongoing */
    6. if (huart->RxState == HAL_UART_STATE_READY)
    7. {
    8. if ((pData == NULL) || (Size == 0U))
    9. {
    10. return HAL_ERROR;
    11. }
    12. /* Process Locked */
    13. __HAL_LOCK(huart);
    14. huart->ErrorCode = HAL_UART_ERROR_NONE;
    15. huart->RxState = HAL_UART_STATE_BUSY_RX;
    16. /* Init tickstart for timeout managment */
    17. tickstart = HAL_GetTick();
    18. huart->RxXferSize = Size;
    19. huart->RxXferCount = Size;
    20. /* Check the remain data to be received */
    21. while (huart->RxXferCount > 0U)
    22. {
    23. huart->RxXferCount--;
    24. if (huart->Init.WordLength == UART_WORDLENGTH_9B)
    25. {
    26. //检查UART_FLAG_RXNE是否为空,如果为非空,表示接收到数据
    27. if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
    28. {
    29. return HAL_TIMEOUT;
    30. }
    31. tmp = (uint16_t *) pData;
    32. if (huart->Init.Parity == UART_PARITY_NONE)
    33. {
    34. *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
    35. pData += 2U;
    36. }
    37. else
    38. {
    39. *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
    40. pData += 1U;
    41. }
    42. }
    43. else
    44. {
    45. if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
    46. {
    47. return HAL_TIMEOUT;
    48. }
    49. if (huart->Init.Parity == UART_PARITY_NONE)
    50. {
    51. *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
    52. }
    53. else
    54. {
    55. *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
    56. }
    57. }
    58. }
    59. /* At end of Rx process, restore huart->RxState to Ready */
    60. huart->RxState = HAL_UART_STATE_READY;
    61. /* Process Unlocked */
    62. __HAL_UNLOCK(huart);
    63. return HAL_OK;
    64. }

    3.延时时间的设置

    如果我们设置的延时时间太长,则我们可能会在延时的时候错过一些字符的发送和接收。

     

    4.总结

    1. int main(void)
    2. {
    3. //定义一个要进行发送的数据
    4. uint8_t sbuf[8]="stm32";
    5. //定义一个要进行接收的数据
    6. uint8_t rbuf[20]="";
    7. HAL_Init();
    8. SystemClock_Config();
    9. MX_GPIO_Init();
    10. MX_USART1_UART_Init();
    11. while (1)
    12. {
    13. //第一个参数要传入的是地址
    14. //size:要发送的大小
    15. //0x0000ffff:超时时间
    16. // HAL_UART_Transmit(&huart1,sbuf,5,0x0000ffff);
    17. // HAL_Delay(1000);
    18. //阻塞式的接收:接收一个后马上接着下一个接收
    19. //此方法最好一次发送一个bit
    20. HAL_UART_Receive(&huart1,rbuf,1,0x0000ffff);
    21. //将接收到的数值发送出来
    22. HAL_UART_Transmit(&huart1,rbuf,1,0x0000ffff);
    23. //这个延时时间不能太久,要不然可能会在延时的时候接收了一个字符,从而错过这个
    24. //HAL_Delay(100);
    25. }
    26. }

    阻塞式的发送实际用的很多,因为编程简单。缺陷是浪费高速CPU的部分性能,没有追求到串口发送和整个系统性能的最高。

    2、非阻塞式(中断)串口发送

    1.HAL_UART_Transmit_IT

    这个函数没有超时时间,因为我们不用等。

    注意点::要打开中断

    1. HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
    2. {
    3. /* Check that a Tx process is not already ongoing */
    4. if (huart->gState == HAL_UART_STATE_READY)
    5. {
    6. if ((pData == NULL) || (Size == 0U))
    7. {
    8. return HAL_ERROR;
    9. }
    10. /* Process Locked */
    11. __HAL_LOCK(huart);
    12. //因为我们使用中断方式,则需要在串口内部定义一个buf,使得buf指向data的地址
    13. huart->pTxBuffPtr = pData;
    14. huart->TxXferSize = Size;
    15. huart->TxXferCount = Size;
    16. huart->ErrorCode = HAL_UART_ERROR_NONE;
    17. huart->gState = HAL_UART_STATE_BUSY_TX;
    18. /* Process Unlocked */
    19. __HAL_UNLOCK(huart);
    20. /* Enable the UART Transmit data register empty Interrupt */
    21. //使能UART发送数据寄存器空中断
    22. __HAL_UART_ENABLE_IT(huart, UART_IT_TXE);
    23. return HAL_OK;
    24. }
    25. else
    26. {
    27. return HAL_BUSY;
    28. }
    29. }

    2.注意点 

    我们使用这个中断式的应该打开Usart的中断

    1. int main(void)
    2. {
    3. //定义一个要进行发送的数据
    4. uint8_t sbuf[8]="stm32";
    5. //定义一个要进行接收的数据
    6. uint8_t rbuf[20]="";
    7. HAL_Init();
    8. SystemClock_Config();
    9. MX_GPIO_Init();
    10. MX_USART1_UART_Init();
    11. while (1)
    12. {
    13. /**
    14. 中断式发送和接收
    15. */
    16. HAL_UART_Transmit_IT(&huart1,sbuf,5);
    17. HAL_Delay(100);
    18. }
    19. }

    3.printf的实现

    printf的实现其实就是重载fputc函数

    1.原始代码 

    1. #ifdef __GNUC__
    2. #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
    3. #else
    4. #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
    5. #endif
    6. /**
    7. * @brief Retargets the C library printf function to the USART.
    8. * @param None
    9. * @retval None
    10. */
    11. PUTCHAR_PROTOTYPE
    12. {
    13. /* Place your implementation of fputc here */
    14. /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
    15. HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
    16. return ch;
    17. }

    2.注意点1: 

    一定一定要勾选User MicroLIB

    3.注意点2:

    我们使用到了fputc,其中FILE中是定义在中的宏定义

    定义在usart.h中

    1. int main(void)
    2. {
    3. /* USER CODE BEGIN 1 */
    4. //定义一个要进行发送的数据
    5. uint8_t sbuf[8]="stm32";
    6. //定义一个要进行接收的数据
    7. uint8_t rbuf[20]="";
    8. HAL_Init();
    9. SystemClock_Config();
    10. MX_GPIO_Init();
    11. MX_USART1_UART_Init();
    12. while (1)
    13. {
    14. float a=4.532;
    15. printf("a=%f\n",a);
    16. printf("test\r");
    17. }
    18. }

    4.串口接收编程实战

    1、阻塞式串口接收

    CPU等着过来。所以当CPU临时去处理其他事情时,可能会错过一些串口的输出。所以我们基本上不用。如果真的要使用,则要协调好延时和串口的接收和发送。

    2、中断式串口接收

    使用中断的方式向串口发送数据并且输出

    1.先开启uart中断

    2.写中断处理函数:HAL_UART_RxCpltCallback

    在main函数中

    1. //rc用来暂存和处理串口接收到的字节内容的
    2. uint8_t receive_char;
    3. //这个就是HAL库对接的中断处理程序
    4. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    5. {
    6. //判断是否为usart1
    7. if(huart->Instance == USART1)
    8. {
    9. //这里就是真正的中断处理代码
    10. //我们这里的处理就是接收到一个字节后原封不动的发回去
    11. HAL_UART_Transmit(&huart1, &receive_char, 1,0xFF);//发送字符
    12. //等待本次接收完毕,同时开启下一次接收【更新receive_char】
    13. //HAL_UART_Receive_IT:开启中断处理流程
    14. while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
    15. }
    16. }

    3.注册中断

    由上面分析可以知道,我们在进入中断处理函数中,是将接收到的字符发送出去,所以我们需要在外部先接收到字符【触发中断】,才可以发送。所以我们需要在其他模块初始化的位置先进入一次中断,接收串口发送的数据。

    3.测试代码

    此代码:在我们不向串口发送字符时,每3s发送一次a,当我们向串口发送数据时,会马上在串口输出。

    1. int main(void)
    2. {
    3. HAL_Init();
    4. SystemClock_Config();
    5. MX_GPIO_Init();
    6. MX_USART1_UART_Init();
    7. //从此处进入中断处理程序
    8. HAL_UART_Receive_IT(&huart1, &receive_char, 1);
    9. while (1)
    10. {
    11. float a=4.532;
    12. printf("a=%f\n",a);
    13. HAL_Delay(3000);
    14. }
    15. }
    16. //这个就是HAL库对接的中断处理程序
    17. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    18. {
    19. //判断是否为usart1
    20. if(huart->Instance == USART1)
    21. {
    22. //这里就是真正的中断处理代码
    23. //我们这里的处理就是接收到一个字节后原封不动的发回去
    24. HAL_UART_Transmit(&huart1, &receive_char, 1,0xFF);//发送字符
    25. //等待本次接收完毕,同时开启下一次接收【更新receive_char】
    26. //HAL_UART_Receive_IT:开启中断处理流程
    27. while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
    28. }
    29. }

    3.处理串口中断的流程

    在初始化部分已经将中断连接起来,到时候产生中断则直接进入【HAL_UART_RxCpltCallback】这个函数

    5.案例1:基于串口的命令shell实现

    命令shell:发送一个命令返回一个回应

    1.协议自定义

    (1)指令集:指令1:add    指令2:sub
    (2)指令结束符:';'【定义普通指令中不可能出现的】
    (3)指令中遇到回车和空格、Tab等特殊字符怎么办

    2.实现思路分析

    1.定义一个缓冲区,存储接到的数据

    2.定义一个索引值

    值存储再buf中的第几个字节

    3.代码编写

    1. /* USER CODE BEGIN 4 */
    2. //这个就是HAL库对接的中断处理程序
    3. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    4. {
    5. //判断是否为usart1
    6. if(huart->Instance == USART1)
    7. {
    8. //将receive_char的数值存放再buf中
    9. rev_buf[tindex]=receive_char;//暂存
    10. tindex++;//指向下一个
    11. while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
    12. }
    13. }

    4.main函数编写

    1)判断指令

    2)buf和index要记得清空

    6.串口实验

    1.串口接收数据过程

    2.串口发送数据过程

  • 相关阅读:
    【zookeeper】基于Linux环境安装zookeeper集群
    20231027 基于STM32mp157a 的内核与应用层通过子系统控制led灯,以及计时器功能
    wpf中EventHandler的使用
    机器学习笔记之最优化理论与方法(四) 凸函数:定义与基本性质
    【JAVA】SpringMVC(下)—— SSM整合&异常处理器
    Linux内核设计与实现 第十一章 定时器和时间管理
    PHP- PHP中与HTML标签的联合使用。注意:<?php 之后的需要有一个空格?>
    leetcode43 字符串相乘
    判断点是否在点组成的封闭区域内c++
    幽默直观的文档作者注释
  • 原文地址:https://blog.csdn.net/m0_63077733/article/details/134345845