• MCU常见通信总线串讲(一)—— UART和USART


    🙌秋名山码民的主页
    😂oi退役选手,Java、大数据、单片机、IoT均有所涉猎,热爱技术,技术无罪
    🎉欢迎关注🔎点赞👍收藏⭐️留言📝
    获取源码,添加WX


    前言

    首先明确一个概念,关于MCU中通信总线和通信协议,通信总线是一种用于连接各种外设和模块的物理接口,它可以传输数据和控制信息。通信协议则是指在通信总线上传输数据时所遵循的规则和约定,以确保不同设备之间能够正确地交换信息,我们也可以把他叫做通信总线协议。

    系列文章,主要讲解以下几个总线协议,读者可以按需选择:

    1. UART和USART
    2. RS232、RS485总线
    3. IIC总线
    4. SPI总线
    5. CAN总线
    6. USB总线

    一、简介

    UART:Universal Asynchronous Receiver/Transmitter,通用异步收发器。
    USART:Universal Synchronous/Asynchronous Receiver/Transmitter,通用同步/异步串行接收/发送器。

    UART是一种通用的异步串行通信协议,它使用起始位、数据位、校验位和停止位来传输数据。在UART通信中,数据的传输是通过固定的波特率进行的,发送和接收端需要事先约定好波特率、数据位、校验位和停止位等参数。UART通常用于连接微控制器、传感器、外围设备等,并且在计算机系统中也广泛应用于串口通信。

    USART是一种更为复杂的串行通信协议,它同时支持同步和异步的数据传输方式。与UART不同的是,USART可以选择同步或者异步模式进行通信,并且提供了更多的控制选项,比如硬件流控制、双向通信等。USART通常用于对数据传输速度要求较高、需要双向通信或者需要更灵活控制的场景。

    二、帧格式

    UART:
    在这里插入图片描述

    1. 起始位(Start Bit):起始位指示数据帧的开始。它始终是逻辑低电平,并且标志着数据的传输即将开始

    2. 数据位(Data Bits):数据位是实际的数据传输部分。它表示要传输的数据,可以是5位、6位、7位或8位,取决于所选择的数据位长度。

    3. 校验位(Parity Bit):校验位是可选的,用于检测传输过程中的错误。常见的校验方式包括奇校验、偶校验或者不使用校验。校验位的选择取决于通信双方事先约定好的校验规则。

    4. 停止位(Stop Bit):停止位标志着数据帧的结束。它始终是逻辑高电平,用于告知接收端该数据帧已经传输完成

    发送过程中,发送方和接收方的波特率需要保持一致,为了减少累计的误差,最多发送1个字节,也就是发送的数据位,最多为8位。

    USART支持同步模式,因此USART 需要同步始终信号USART_CK,一般在单片机里面同步信号很少使用,所以USART和UART使用方式是一样的,都使用异步模式。

    三、硬件连接

    USART和UART在硬件连接上有一些差别,主要是因为USART支持同步通信而UART不支持。下面是它们的硬件连接方式:

    1. UART的硬件连接:
    • UART通常使用三根线进行连接:TX(发送端)、RX(接收端)和地线(GND)。发送端的TX线连接到接收端的RX线,接收端的TX线连接到发送端的RX线。此外,两端的地线需要连接在一起,以确保信号的参考电位相同。
    • 在单片机或者嵌入式系统中,UART通常通过芯片上的引脚来连接,例如MCU的TX引脚连接到外部设备的RX引脚,MCU的RX引脚连接到外部设备的TX引脚。

    在这里插入图片描述

    1. USART的硬件连接:
    • USART的连接方式与UART类似,但是在同步模式下还需要连接一个时钟线(CLK)。因此,USART在同步模式下通常使用四根线进行连接:TX、RX、CLK和GND。
    • 在使用USART进行同步通信时,发送端和接收端需要共享一个时钟信号,因此需要额外的时钟线来进行连接。

    总的来说,UART和USART在硬件连接上的主要区别在于是否需要连接时钟线。

    四、工作模式

    • 单工模式(Simplex Communication)的数据传输是单向的。通信双方中,一方固定为发送端,一方则固定为接收端。信息只能沿一个方向传输,使用一根传输线。
    • 半双工模式(Half Duplex)通信使用同一根传输线,既可以发送数据又可以接收数据,但不能同时进行发送和接收。数据传输允许数据在两个方向上传输,但是,在任何时刻只能由其中的一方发送数据,另一方接收数据。因此半双工模式既可以使用一条数据线,也可以使用两条数据线。半双工通信中每端需有一个收发切换电子开关,通过切换来决定数据向哪个方向传输。因为有切换,所以会产生时间延迟,信息传输效率低些。
    • 全双工模式(Full Duplex)通信允许数据同时在两个方向上传输。因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。在全双工模式中,每一端都有发送器和接收器,有两条传输线,信息传输效率高。

    显然,在其它参数都一样的情况下,全双工比半双工传输速度要快,效率要高。

    五、使用流程

    1. 初始化:首先需要初始化UART模块,包括设置波特率(Baud Rate)、数据位长度、校验位和停止位等参数。这些参数需要与通信的对端设备相匹配,以确保正常的数据传输。

    2. 发送数据:要发送数据,首先将要发送的数据写入到UART发送缓冲区。通过编程方式将数据写入发送缓冲区后,UART模块会自动将数据发送出去。在发送数据之前,需要检查发送缓冲区是否为空,以确保可以安全地写入新的数据。

    3. 接收数据:接收数据时,需要检查接收缓冲区中是否有新的数据可供读取。如果接收缓冲区中有数据可读,可以通过编程方式读取数据并进行处理。

    4. 错误处理:在UART通信过程中,可能会发生一些错误,比如校验错误或者帧错误。在接收数据时,需要及时检查错误标志位,以便进行相应的错误处理和恢复。

    5. 中断处理:为了提高系统的响应速度和效率,通常会使用UART中断来处理接收和发送数据。在使用中断的情况下,需要编写相应的中断服务程序(ISR),以处理接收到的新数据或者发送缓冲区为空的情况。

    6. 关闭和清理:在程序结束或者不再需要使用UART时,需要关闭UART模块,并进行相应的资源清理工作,以释放相关的资源和关闭相应的中断。

    具体示例(MCU为STM32F103)

    #include "bsp_usart.h"
     /**
      * @brief  配置嵌套向量中断控制器NVIC
      * @param  无
      * @retval 无
      */
    static void NVIC_Configuration(void)
    {
      NVIC_InitTypeDef NVIC_InitStructure;
      
      /* 嵌套向量中断控制器组选择 */
      NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
      
      /* 配置USART为中断源 */
      NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
      /* 抢断优先级*/
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
      /* 子优先级 */
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
      /* 使能中断 */
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
      /* 初始化配置NVIC */
      NVIC_Init(&NVIC_InitStructure);
    }
    
     /**
      * @brief  USART GPIO 配置,工作参数配置
      * @param  无
      * @retval 无
      */
    void USART_Config(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    
    	// 打开串口GPIO的时钟
    	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
    	
    	// 打开串口外设的时钟
    	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
    
    	// 将USART Tx的GPIO配置为推挽复用模式
    	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
    
      // 将USART Rx的GPIO配置为浮空输入模式
    	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
    	
    	// 配置串口的工作参数
    	// 配置波特率
    	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
    	// 配置 针数据字长
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    	// 配置停止位
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;
    	// 配置校验位
    	USART_InitStructure.USART_Parity = USART_Parity_No ;
    	// 配置硬件流控制
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    	// 配置工作模式,收发一起
    	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    	// 完成串口的初始化配置
    	USART_Init(DEBUG_USARTx, &USART_InitStructure);
    	
    	// 串口中断优先级配置
    	NVIC_Configuration();
    	
    	// 使能串口接收中断
    	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);	
    	
    	// 使能串口
    	USART_Cmd(DEBUG_USARTx, ENABLE);		
    
      // 清除发送完成标志
    	//USART_ClearFlag(USART1, USART_FLAG_TC);     
    }
    
    /*****************  发送一个字符 **********************/
    void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
    {
    	/* 发送一个字节数据到USART */
    	USART_SendData(pUSARTx,ch);
    		
    	/* 等待发送数据寄存器为空 */
    	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
    }
    
    /*****************  发送字符串 **********************/
    void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
    {
    	unsigned int k=0;
      do 
      {
          Usart_SendByte( pUSARTx, *(str + k) );
          k++;
      } while(*(str + k)!='\0');
      
      /* 等待发送完成 */
      while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
      {}
    }
    
    /*****************  发送一个16位数 **********************/
    void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
    {
    	uint8_t temp_h, temp_l;
    	
    	/* 取出高八位 */
    	temp_h = (ch&0XFF00)>>8;
    	/* 取出低八位 */
    	temp_l = ch&0XFF;
    	
    	/* 发送高八位 */
    	USART_SendData(pUSARTx,temp_h);	
    	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
    	
    	/* 发送低八位 */
    	USART_SendData(pUSARTx,temp_l);	
    	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
    }
    
    ///重定向c库函数printf到串口,重定向后可使用printf函数
    int fputc(int ch, FILE *f)
    {
    		/* 发送一个字节数据到串口 */
    		USART_SendData(DEBUG_USARTx, (uint8_t) ch);
    		
    		/* 等待发送完毕 */
    		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		
    	
    		return (ch);
    }
    
    ///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
    int fgetc(FILE *f)
    {
    		/* 等待串口输入数据 */
    		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
    
    		return (int)USART_ReceiveData(DEBUG_USARTx);
    }
    
    • 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
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145

    最后

    如果本文对你有所帮助,还请三连支持一下博主!
    请添加图片描述

  • 相关阅读:
    8.1.2 创建数据表时指定主键
    SystemServer是如何启动AMS的
    全球GNSS频段划分
    Clickhouse通过命令导入导出文件(在Linux命令窗口)
    python 第五章
    基于SpringBoot+Vue的旅游管理系统
    go演示GRPC的用法
    跨语言指令调优深度探索
    ElasticSearch学习
    Spark Streaming系列-1、什么是Spark Streaming?
  • 原文地址:https://blog.csdn.net/weixin_45920495/article/details/134264711