【STM32】知识点介绍八:UART/USART串口功能

文章目录


一、简介

1.定义

串口通信是一种设备间非常常用的串行接口 ,以比特位的形式发送或接收数据,电子工程师经常使用这种方式来调试数据。串口,也称串行接口或者串行通信接口(通常指COM口),是一种采用串行通信方式的扩展接口。是实现数据一位一位按顺序的传输,具有通信线路简单,成本低但传输速度慢的特点。只要一对传输线,串口就可以实现双向通信(全双工)。

串口通信的接口类型包括TTL,CMOS,RS-232和RS-485等,他们代表了不同的电平标准。

  • TTL电平:逻辑1:5v,逻辑0:0v。
  • CMOS电平:逻辑1:提供的电压最大值,逻辑0:0v。
  • RS-232:逻辑1:-3 ~ -15v,逻辑0:3~15v。
  • RS-485:采用的是差分信号,这种采用差分信号,可以有效避免外界的干扰,影响到电平
    • 逻辑1:两线间电压的差值为+(0.2 - 6)v。
    • 逻辑0:两线间电压的差值为-(0.2 - 6)v。

2.通信类型

1.串行通信和并行通信

  • 串行通信:数据按位顺序一次传输一个bit,即一次只传输一个bit的数据
  • 并行通信:多个bit同时传输,即一次可以传输多个bit的数据


2.全双工,半双工以及单工通信

  • 单工:数据只能沿着一个方向传输
  • 半双工:数据可以沿着两个方向传输,但是需要分时进行,也就是说同一时间只能有一个方向在传输数据
  • 全双工:数据可以同时沿着两个方向传输,不需要分时进行


3.同步通信和异步通信

  • 同步通信:发送方和接收方必要按照预定的时钟节拍进行数据的发送和接收,双方的操作严格同步
  • 异步通信:双方不需要严格的时钟同步,每个数据块之间通过特定的起始位和停止位进行分隔,接收方可以独立地识别每个数据块



二、串口通信协议

1.帧格式

串口发送一帧数据的组成,由起始位,数据位,校验位(可以没有),停止位组成。MSB是Most Significant Bit的缩写,指最高有效位;LSB是Least Significant Bit的缩写,指最低有效位。

(1)起始位:起始位为低电平时,告诉接收方数据传输即将开始,准备接收。在通信开始的时候,发送端首先会发送一个起始位,他是一个逻辑0(低电平)信号,用于同步发送和接收设备之间的时钟,之后会开始准备接收后续数据位。

(2)数据位:数据位是由一系列二进制组成,用于传输或者接收实际数据。数据位的数量可以决定传输的不同二进制值的数量,常见的有5位,6位,7位,8位(常见是8位),LSB在前,HSB在后。数据位紧随着起始位之后,包括了要传输的实际信息。

(3)检验位:校验位用于检测数据的完整性,以确保传输过程中没有出现错误。常见的校验位选项中有None(无校验位),Odd(奇校验位)和Even(偶校验位)。在发送数据时,校验位会根据数据中的1的个数进行计算,并加入到数据中一起传输。接收端则会根据校验的值进行校验,以判断数据是否存在错误。

(4)停止位:停止位是一个逻辑高电平 ,用于指示数据传输的结束。当停止位出现时,接收端知道数据传输已经完成,并且可以开始处理接收到的数据。停止位位于数据位和校验位之后,它的作用是确保接收端有足够时间来识别数据帧的结束,并为下一个数据帧的到来做好准备。

2.流控

数据在两个串口之间进行通讯的时候常常会出现丢失数据的现象,比如两台计算机或者是一台计算机和一个单片机之间进行通讯,当接收端的数据缓冲区已经满了,这个时候如果还有数据发送过来,因为接收端没有时间进行处理,那这样的数据就有可能会丢失。在工业现场或者其他领域,经常会遇到这种问题,本质原因是速度不匹配、处理能力不匹配。比如单片机的主频只有20M或30M,ARM的处理能力可能是200M,PC机的处理能力是几个G,这种处理能力的不匹配造成了传输的时候数据容易丢失。

硬件流控就是来解决这个速度匹配的问题。它的基本含义非常简单,当接收端接收到的数据处理不过来时,就向发送端发送不再接收的信号,发送端接收到这个信号之后就会停止发送,直到收到可以继续发送的信号再继续发送。因此流控本身是可以控制数据传输的进度,进而防止数据丢失。

一般常用的流控方式有两种:硬件流控和软件流控

(1)硬件流控:

如果使能了硬件流控,在三线串口通信模式增加两根控制线,一根叫 CTS(Clear To Send 为输入信号,一根叫 RTS(Require To Send 为输出信号)。一个是接收控制,一个是发送控制。

从硬件连接原理图中我们可以看到,如果从 USART 1 向 USART 2 发送的话,USART 1 的 TX 和 USART 2 的 RX 相连,USART 1 的 CTS 和 USART 2 的 RTS 相连,数据的方向是从 TX 到 RX,从串口1到串口2,流控是从 RTS 到 CTS 也就是从串口2到串口1。

  • RTS(Require To Send,发送请求)为输出信号,用于指示本设备准备好可接收数据,低电平有效,低电平说明本设备可以接收数据。
  • CTS(Clear To Send,发送允许)为输入信号,用于判断是否可以向对方发送数据,低电平有效,低电平说明本设备可以向对方发送数据。

(2)软件流控:

由于电缆线的限制,我们在普通的控制通讯中一般不用硬件流控制,而用软件流控制。一般通过xon/xoff来实现软件流控制。常用方法是:当接收端的输入缓冲区内数据量超过设定的高位时,就向数据发送端发出xoff字符(十进制的19或十六进制的0x13或control-s,设备编程说明书应该有详细阐述),发送端收到 xoff字符后就立即停止发送数据;当接收端的输入缓冲区内数据量低于设定的低位时,就向数据发送端发出xon字符(十进制的17或十六进制的0x11或control-q),发送端收到xon字符后就立即开始发送数据。一般可以从设备配套源程序中找到发送的是什么字符。

应该注意,若传输的是二进制数据,标志字符也有可能在数据流中出现而引起误操作,这是软件流控制的缺陷,而硬件流控制不会有这个问题。

3.STM32的USART

  • USART是Universal synchronous asynchronous receiver transmitter的简写,是通用同步异步 收发器的意思。
  • UART是Universal asynchronous receiver transmitter的简写,是通用异步收发器。
  • 全双工通信:USART支持全双工的通信,即数据可以在两个方向上同时传输(A->B且B->A)。这使得USART能够满足许多需要双向通信的场景
  • 同步与异步传输:尽管USART的"S"代表同步,但在实际应用中,USART更常用于异步通信。然而,它也支持同步通信模式,只是这种模式通常用于兼容其他协议或特殊模式,并且两个USART设备不能通过同步模式进行直接通信。
  • 波特率发生器:USART自带波特率发生器,最高可达4.5Mbits/s,可以根据需要,配置不同的波特率。
  • 硬件流控制:USART支持硬件流控制,通过特定的信号线(如RTS/CTS)实现数据的可靠传输。当接收端没有准备好接收数据时,可以通过RTS信号通知发送端暂停发送;当接收端准备好接收数据时,再通过CTS信号通知发送端恢复发送。

三、串口功能

1.串口实现一个字符收发

(1)uart1.c

c 复制代码
#include "uart1.h"
 
UART_HandleTypeDef uart1_handle = {0};//串口的句柄
 
//串口初始化
void uart1_init(uint32_t baudrate)
{
    uart1_handle.Instance = USART1;//选择串口1
    uart1_handle.Init.BaudRate = baudrate;
    uart1_handle.Init.Mode = UART_MODE_TX_RX;
    uart1_handle.Init.Parity = UART_PARITY_NONE;//不打开检验位
    uart1_handle.Init.StopBits = UART_STOPBITS_1;//1个停止位
    uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;//传输字长为8位
    uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;//硬件流不打开
    //OverSampling过采样,减少误差误差。使用的是16倍。这里不用配置
    HAL_UART_Init(&uart1_handle);
}
 
 
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        GPIO_InitTypeDef gpio_init = {0};
        __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO时钟
        __HAL_RCC_USART1_CLK_ENABLE();
        gpio_init.Mode = GPIO_MODE_AF_PP;//TX线是看GPIO外设配置为复用推挽
        //tx和rx查芯片手册就可以。
        gpio_init.Pin = GPIO_PIN_9;
        gpio_init.Pull = GPIO_PULLUP;//默认上拉
        gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        gpio_init.Mode = GPIO_MODE_INPUT;//RX线是看GPIO外设配置为上拉输入
        gpio_init.Pin = GPIO_PIN_10;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        HAL_NVIC_SetPriority(USART1_IRQn,2,2);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
        __HAL_UART_ENABLE_IT(huart,UART_IT_RXNE);//使能RXNE中断
    }
}
 
void USART1_IRQHandler(void)
{
    uint8_t receive_data = 0;
    if(__HAL_UART_GET_FLAG(&uart1_handle,UART_FLAG_RXNE) != RESET){//看是不是被置1了
        
        HAL_UART_Receive(&uart1_handle,&receive_data,1,1000);//接收数据
        HAL_UART_Transmit(&uart1_handle,&receive_data,1,1000);//发送数据
    }
}

(2)main.c

c 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
 
 
int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); 	  /* 设置时钟, 72Mhz */
    uart1_init(115200);
    while(1)//流水灯实验
    { 
        delay_ms(500);//没隔500ms闪烁
    }
}

2.串口接收下·不定长数据(接收中断)

(1)uart.c

c 复制代码
#include "uart1.h"
#include "stdio.h"
#include "string.h"
UART_HandleTypeDef uart1_handle = {0};//串口的句柄
uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];//定义接收的数据存放位置
uint16_t uart1_rx_len = 0;//uart1_rx_buf数据的长度
uint16_t uart1_cnt = 0;//计数器,表示接收了多少个数
uint16_t uart1_cntPre = 0;//保存接收前一个数是第几个数
 
//串口初始化
void uart1_init(uint32_t baudrate)
{
    uart1_handle.Instance = USART1;//选择串口1
    uart1_handle.Init.BaudRate = baudrate;
    uart1_handle.Init.Mode = UART_MODE_TX_RX;
    uart1_handle.Init.Parity = UART_PARITY_NONE;//不打开检验位
    uart1_handle.Init.StopBits = UART_STOPBITS_1;//1个停止位
    uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;//传输字长为8位
    uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;//硬件流不打开
    HAL_UART_Init(&uart1_handle);
}
 
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        GPIO_InitTypeDef gpio_init = {0};
        __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO时钟
        __HAL_RCC_USART1_CLK_ENABLE();
        gpio_init.Mode = GPIO_MODE_AF_PP;//TX线是看GPIO外设配置为复用推挽
        gpio_init.Pin = GPIO_PIN_9;
        gpio_init.Pull = GPIO_PULLUP;//默认上拉
        gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        gpio_init.Mode = GPIO_MODE_INPUT;//RX线是看GPIO外设配置为上拉输入
        gpio_init.Pin = GPIO_PIN_10;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        HAL_NVIC_SetPriority(USART1_IRQn,2,2);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
        __HAL_UART_ENABLE_IT(huart,UART_IT_RXNE);//使能RXNE中断
    }
}
int fputc(int ch,FILE *f)
{//重定向printf函数,到串口助手
    while((USART1->SR & 0x40) == 0);//一直等 发送数据寄存器不为空 
    USART1->DR = (uint8_t)ch;
    return ch;
}
 
uint8_t uart1_wait_receive()
{//统计是第几个数的函数,什么时候发送完(类似计数器)
    if(uart1_cnt == 0)//如果此时的索引uart1_cnt还是等于0,说明是出错了
        return UART1_ERROR;
    
    if(uart1_cnt == uart1_cntPre){//如果表示前一个数uart1_cntPre等于uart1_cnt说明已经接收完了。
        uart1_cnt = 0;//索引清0
        return UART1_EOK;
    }
    uart1_cntPre = uart1_cnt;//此时正在接收
    return UART1_ERROR;
    
}
void uart1_clear()
{//清0数组
    memset(uart1_rx_buf,0,sizeof(uart1_rx_buf));
    uart1_rx_len = 0;
}
void uart1_test()
{//测试函数
    if(uart1_wait_receive() == UART1_EOK){//如果等于,就说明接收完了
        printf("recv: %s\r\n",uart1_rx_buf);
        uart1_clear();
    }
    
}
 
void USART1_IRQHandler()
{
    uint8_t receive_data = 0;
    if(__HAL_UART_GET_FLAG(&uart1_handle,UART_FLAG_RXNE) != RESET){//看是不是被置1了
        if(uart1_cnt >= sizeof(uart1_rx_buf))
            uart1_cnt = 0;//接收前需要判断一下,,uart1_cnt是否超出总长度UART1_RX_BUF_SIZE
        HAL_UART_Receive(&uart1_handle,&receive_data,1,1000);//接收数据
        uart1_rx_buf[uart1_cnt++] = receive_data;//把接收到的数据放进数组
        
        //HAL_UART_Transmit(&uart1_handle,&receive_data,1,1000);
    }
}

(2)main.c

c 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
 
 
int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); 	  /* 设置时钟, 72Mhz */
    uart1_init(115200);
    while(1)//流水灯实验
    { 
        uart1_test();
        delay_ms(20);
    }
}

3.串口接收不定长数据(空闲中断)

(1)uart.c

c 复制代码
#include "uart1.h"
#include "stdio.h"
#include "string.h"
UART_HandleTypeDef uart1_handle = {0};//串口的句柄
uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];//定义接收的数据存放位置
uint16_t uart1_rx_len = 0;//计数器,表示接收了多少个数
 
 
//串口初始化
void uart1_init(uint32_t baudrate)
{
    uart1_handle.Instance = USART1;//选择串口1
    uart1_handle.Init.BaudRate = baudrate;
    uart1_handle.Init.Mode = UART_MODE_TX_RX;
    uart1_handle.Init.Parity = UART_PARITY_NONE;//不打开检验位
    uart1_handle.Init.StopBits = UART_STOPBITS_1;//1个停止位
    uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;//传输字长为8位
    uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;//硬件流不打开
    HAL_UART_Init(&uart1_handle);
}
 
//串口初始化
void uart1_init(uint32_t baudrate)
{
    uart1_handle.Instance = USART1;//选择串口1
    uart1_handle.Init.BaudRate = baudrate;
    uart1_handle.Init.Mode = UART_MODE_TX_RX;
    uart1_handle.Init.Parity = UART_PARITY_NONE;//不打开检验位
    uart1_handle.Init.StopBits = UART_STOPBITS_1;//1个停止位
    uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;//传输字长为8位
    uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;//硬件流不打开
    HAL_UART_Init(&uart1_handle);
}
 
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1){
        GPIO_InitTypeDef gpio_init = {0};
        __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO时钟
        __HAL_RCC_USART1_CLK_ENABLE();
        gpio_init.Mode = GPIO_MODE_AF_PP;//TX线是看GPIO外设配置为复用推挽
        gpio_init.Pin = GPIO_PIN_9;
        gpio_init.Pull = GPIO_PULLUP;//默认上拉
        gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        gpio_init.Mode = GPIO_MODE_INPUT;//RX线是看GPIO外设配置为上拉输入
        gpio_init.Pin = GPIO_PIN_10;
        HAL_GPIO_Init(GPIOA,&gpio_init);//初始化gpio
        
        HAL_NVIC_SetPriority(USART1_IRQn,2,2);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
        __HAL_UART_ENABLE_IT(huart,UART_IT_RXNE);//使能RXNE中断
        __HAL_UART_ENABLE_IT(huart,UART_IT_IDLE);//使能空闲中断
        //当总线接收完之后,执行空闲中断
    }
}
int fputc(int ch,FILE *f)
{//重定向printf函数
    while((USART1->SR & 0x40) == 0);//一直等 发送数据寄存器不为空 
    USART1->DR = (uint8_t)ch;
    return ch;
}
 
 
void uart1_clear()
{
    memset(uart1_rx_buf,0,sizeof(uart1_rx_buf));
    uart1_rx_len = 0;
}
 
 
void USART1_IRQHandler()
{
    uint8_t receive_data = 0;
    if(__HAL_UART_GET_FLAG(&uart1_handle,UART_FLAG_RXNE) != RESET)
    {//看是不是被置1了
        if(uart1_rx_len >= sizeof(uart1_rx_buf))
            uart1_rx_len = 0;//接收前需要判断一下,,uart1_cnt是否超出总长度UART1_RX_BUF_SIZE
        HAL_UART_Receive(&uart1_handle,&receive_data,1,1000);//接收数据
        uart1_rx_buf[uart1_rx_len++] = receive_data;//把接收到的数据放进数组
        
        //HAL_UART_Transmit(&uart1_handle,&receive_data,1,1000);
    }
    if(__HAL_UART_GET_FLAG(&uart1_handle,UART_FLAG_IDLE) != RESET)
    {//接收完,就触发了空闲中断。
        __HAL_UART_CLEAR_IDLEFLAG(&uart1_handle);//清除空闲位
        printf("recv: %s\r\n",uart1_rx_buf);
        uart1_clear();//在这里,已经把buf清空了
    }
}

(2)main.c

c 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
 
 
int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); 	  /* 设置时钟, 72Mhz */
    uart1_init(115200);
    while(1)//流水灯实验
    { 
    }
}

四、RS232通信

1.RS-232详解

RS-232是美国电子工业联盟制定的串行数据通信接口标准,原始编号全称是EIA-RS-232(简称RS232),它被广泛用于DCE(Data Communication Equipment ) 和 DTE(Data Terminal Equipment) 之间的连接。DCE可以理解为数据通信端,比如modem设备;DTE可以理解为数据终端,比如电脑。最早的台式电脑都会保留9针的232接口,用于串口通信,目前基本被USB接口取代。现在RS232接口常用于仪器仪表设备,PLC以及嵌入式领域当作调试口来使用。

2.接口形态

按照引脚数量分类有两种:25pin--DB25 协议标准推荐的接口类型。9pin---DE9(通常误叫做DB9) 自IBM PC/AT开始改用9针连接器起,目前是主流接口形态。9针RS232接口按照接口类型,又可以分为:

  • 公头(Male):带针脚

  • 母座(Female):带孔座

3.接口定义

下图是公头9针RS232接口详细定义:

其中DTR/DSR和RTS/CTS用于硬件流控。

DTR/DSR状态表明DTE和DCE处于可用状态,有时候设备上电这两个信号即有效,表示设备本身可用使用,但要开始进行数据传输通讯,则需要RTS/CTS流控信号。

RTS即DTE发送数据时,该信号有效(ON),向DCE请求发送数据;CTS是对请求发送RTS信号的响应,当DCE已经准备好接收数据时,该信号有效(ON),通知DTE可用使用TXD发送数据了。比如A向B发送数据简单如下逻辑:

  • .A先设置RTS为1,表示要发数据给B
  • B检测到RTS为1,先看看自己是否准备好:
    • 如果准备好,就设置CTS为1表示A可用发数据给B了
    • 如果没有准备好,继续处理自己的数据。弄完了,再将CTS设置为1,让A发数据
  • A发现CTS置1了,将数据通过TXD信号线发送出去
  • A每发送一次数据给B之前,都会继续上面的逻辑
  • A发送完数据后,就将RTS置0,表示数据发送完毕



4.电平及时序

RS232采用负逻辑电平,定义如下:

下图是使用RS232接口按照UART串口协议进行传输,UART协议具体请参考 UART详解。图中传输的数据为0100 1011b 即0x4B,按照1bit开始位,8bit数据位,1bit停止位格式传输。

5.最大通讯距离及速率

协议最初规定设备最大速率为20kbps,对于16550A UART最大速率为1.5Mbps。码元畸变小于4%的情况下,DTE 和 DCE 之间最大传输距离为 15m(50 英尺)。可见这个最大的距离是在码元畸变小于 4%的前提下给出的。为了保证码元畸变小于 4%的要求,接口标准在电气特性中规定,驱动器的负载电容应小于 2500pF。对于普通导线,其电容值约为 170pF/m,则允许距离 L=2500pF/(170pF/m)=15m。当速率下降时,传输距离会成倍增加,下表是Texas Instruments 在不同速率下对应的传输线缆长度:


五、RS485通信

1.定义

RS-485有两线制和四线制两种接线,四线制只能实现点对点的通信方式,现很少采用,多采用的是两线制接线方式,这种接线方式为总线式拓扑结构,在同一总线上最多可以挂接32个节点。


2.信号电平

RS-485能够进行远距离传输主要得益于使用差分信号进行传输,当有噪声干扰时仍可以使用线路上两者差值进行判断,使传输数据不受噪声干扰。

RS-485差分线路包括以下2个信号:

A:非反向(non-inverting)信号

B:反向(inverting)信号

也可能会有第3个信号,为了平衡线路正常动作要求所有平衡线路上有一个共同参考点,称为SC或者G。该信号可以限制接收端收到的共模信号,收发器会以此信号作为基准值来测量AB线路上的电压。RS-485标准中提到:

若是MARK (逻辑1),线路A信号电压比线路B高。

若是SPACE(逻辑0),线路B信号电压比线路A高。

根据RS-485标准,当485总线差分电压大于+200mV时,485收发器输出高电平;当485总线差分电压小于-200mV时,485收发器输出低电平;当485总线上的电压在-200mV~+200mV时,485收发器可能输出高电平也可能输出低电平,但一般总处于一种电平状态,若485收发器的输出低电平,这对于UART通信来说是一个起始位,此时通信会不正常。

3.差分信号与共模信号

差分传输是一种信号传输的技术,区别于传统的一根信号线一根地线的做法,差分传输在这两根线上都传输信号,这两个信号的振幅相等,相位相反。在这两根线上传输的信号就是差分信号,也称差模信号。

差分信号又称差模信号,是相对共模信号而言的。

共模信号是信号线对地的电压,差模信号是信号线之间的电压。放大电路是一个双口网络,每个端口有两个端子。当两个输入端子的输入信号分别为U1和U2时,两信号的差值称为差模信号,而两信号的算术平均值称为共模信号。


特点:

从严格意义上来讲,所有电压信号都是差分的,因为一个电压只能是相对于另一个电压而言的。

在某些系统里,"系统地"被用作电压基准点。当'地'当作电压测量基准时,这种信号规划被称之为单端的。我们使用该术语是因为信号是用单个导体上的电压来表示的。另一方面,一个差分信号作用在两个导体上。信号值是两个导体间的电压差。尽管不是非常必要,这两个电压的平均值还是会经常保持一致。

可以想象,这两个导体上被同时加入的一个相等的电压,也就是所谓共模信号,对一个差分放大系统来说是没有作用的,也就是说,尽管一个差分放大器的输入有效信号幅度只需要几毫伏,但它却可以对一个高达几伏特的共模信号无动于衷。

这个指标叫做差分放大器的共模抑制比(CMRR),一般的运算放大器可以达到90db以上,高精度运放甚至达到120db。因为干扰信号一般是以共模信号的形式存在,所以差分信号的应用极大地提高了放大器系统的信噪比。

优点

  • 抗干扰能力强,因为两根差分走线之间的耦合很好,当外界存在噪声干扰时,几乎是同时被耦合到两条线上,而接收端关心的只是两信号的差值,所以外界的共模噪声可以被完全抵消。
  • 能有效抑制EMI(Electromagnetic Interference,电磁干扰),同样的道理,由于两根信号的极性相反,他们对外辐射的电磁场可以相互抵消,耦合的越紧密,泄放到外界的电磁能量越少。
  • 时序定位精确,由于差分信号的开关变化是位于两个信号的交点,而不像普通单端信号依靠高低两个阈值电压判断,因而受工艺,温度的影响小,能降低时序上的误差,同时也更适合于低幅度信号的电路。

4.为什么要抑制共模信号

任何信号都可以分解为共模信号(Common mode signal)和差模信号(Differential mode signal)。

共模信号又称为对地感应信号或不对称信号,作用在差分放大器或仪表放大器两个输入端的相同信号,通常是由于线路传导和空间磁场干扰产生的,不携带有效信息,是不希望出现的信号。

主要表现为:

  • 单线传输时,地电位差异引起的共模信号,会叠加在信号上形成共模干扰,造成原始信号失真。
  • 双线传输时,有效信号是差模信号,共模信号是无效信号。如果共模信号被放大很多,会影响到真正需要放大的差模信号。


5.共模干扰是如何产生的

实际应用中,温度的变化各种环境噪声的影响都可以视作为共模干扰,共模干扰产生的原因很多。主要原因有以下四点:

  • 电网串入共模干扰电压。
  • 辐射干扰(如雷电,设备电弧,附近电台,大功率辐射源) 在信号线上感应出共模干扰。
  • 接地电压不一样,也就是说地电位差异引入共模干扰。
  • 设备内部电线对电源线的影响。

六、常见问题与解答

1.简单介绍一下USART和UART的区别

UART(通用异步收发器)是USART(通用同步异步收发器)的子集。USART除了支持UART的异步模式外,还支持同步模式。在同步模式下,USART会提供一个时钟引脚(CK)来同步数据传输

2.如何理解波特率和比特率

在串口通信中,由于每传输一个符号只携带1比特的信息,所以两者的数值通常是相等的。但严格来说,波特率是指信号每秒变化的次数,而比特率是指每秒传输的比特数

3.请介绍几种串口数据的收发方式,并说明其优缺点

  • 轮询方式:CPU不断查询标志位(如TXE)来判断是否可以发送或接收数据。优点是简单,但会阻塞CPU,效率极低,不适合实时系统
  • 中断方式:发送时,CPU只需将数据写入DR寄存器,完成后触发发送完成中断;接收时,收到数据即触发中断,在中断服务函数中处理数据。这种方式提高了CPU利用率
  • DMA方式:直接内存访问,是最高效的方式。配置好DMA后,DMA控制器会自动将数据从内存搬运到串口发送,或将串口接收的数据搬运到内存,整个过程完全不需要CPU干预,适合大批量、高速数据传输

4.如果串口接收到的数据量很大,或者数据帧不连续,你怎么处理才能保证数据不丢失

引入"环形缓冲区"。

  • 原理:在内存中开辟一块连续的存储空间作为缓冲区,并维护读指针和写指针。中断服务程序只负责把收到的数据快速放入缓冲区(写数据),而主循环或任务则从缓冲区中取出数据(读数据)进行解析处理
  • 优点:这种方式解耦了数据接收和数据处理,避免了因处理耗时导致的数据覆盖,是实现稳定、高效串口驱动的常用方法

5.串口发送数据时,TXE和TC这两个标志位有什么区别

  • TXE (发送数据寄存器空):当该位置1时,表示数据已经从DR寄存器移到了移位寄存器,DR寄存器已经空了,CPU可以继续向DR写入下一个要发送的数据
  • TC (发送完成):当该位置1时,表示移位寄存器中的数据也已经被全部发送出去了。如果你想在发送完最后一个字节后,关闭串口或进入低功耗模式,就必须等待TC标志位

6.在实际项目中,如果遇到串口通信乱码或完全不工作,你会如何排查

  • 检查硬件连接:确保TX和RX交叉连接,GND共地,并检查是否有虚焊、短路
  • 核对参数配置:重点核对通信双方的波特率、数据位、停止位、校验位是否完全一致
  • 检查时钟配置:确保串口外设的时钟已经正确使能,并且系统时钟频率配置无误,这会直接影响波特率的计算精度
  • 检查GPIO模式:确认TX引脚配置为复用推挽输出,RX引脚配置为浮空输入或带上拉输入
  • 分步测试:可以先只让单片机一直发送固定的数据(如0x55或0xAA),用逻辑分析仪或示波器观察TX引脚的波形,从物理层确认数据发送是否正常

相关推荐
国家一级保护废物...2 小时前
51单片机day1
单片机·嵌入式硬件·51单片机
小白学电子_2 小时前
STM32常用HAL常见库函数快速运用和讲解
stm32·单片机·嵌入式硬件
woshihonghonga2 小时前
解决Eclipse的Copilot终端依赖问题
stm32·mcu·eclipse·copilot·ai编程
busideyang2 小时前
STM32中__weak(弱定义)函数核心总结
c语言·stm32·单片机·嵌入式硬件·嵌入式
可乐鸡翅好好吃2 小时前
RTC时钟源及其低功耗应用
单片机·嵌入式硬件·实时音视频
senijusene2 小时前
51单片机:硬件基础、开发工具与核心外设详解
单片机·嵌入式硬件·51单片机
forAllforMe2 小时前
用STM32+LAN9252的etherCAT 从站实现传感器数据采集
stm32·单片机·嵌入式硬件
G***技2 小时前
物流自动化迈入边缘智能,杰和科技AR707成为关键引擎
人工智能·嵌入式硬件·机器人·边缘计算盒
雨洛lhw2 小时前
压控晶振学习笔记
嵌入式硬件·晶振