目录
UART是通用异步串行接口(Universal Asynchronous Receiver/Transmitter)的缩写。它是一种用于串行异步全双工通信的通信协议,常用于将计算机和外部设备(如传感器、显示器、模块等)进行串行数据传输。
UART使用一对数据线(一条用于发送数据,一条用于接收数据)来进行通信,通信过程中不需要时钟信号,所以称为异步。发送和接收的数据以字节为单位进行传输,并通过起始位、数据位、校验位和停止位进行格式化,需要注意的是主从之间的接收和发射端口要反接,就是主机TX端要接从机RX端。
UART具有简单、易于实现和广泛应用的特点,适用于短距离、低速度的数据传输。在计算机和外设之间的串行通信中,UART常常作为主设备和从设备之间的接口。
UART的实现细节通常分为硬件和软件两个方面。
硬件实现细节
1. 发送器(Transmitter):负责将要发送的数据位逐位发送出去。通常由移位寄存器(Shift Register)实现,通过时钟信号将数据逐位移出。
2. 接收器(Receiver):负责接收传输线上的数据位,将其逐位存储在接收缓冲器中。通常也由移位寄存器实现,通过时钟信号将数据逐位移入。
3. 波特率发生器(Baud Rate Generator):用于生成发送和接收时钟信号,控制每个比特的持续时间,从而确定通信的速率。波特率发生器通常由计数器和时钟源构成。
4. 控制逻辑(Control Logic):负责控制发送器、接收器和波特率发生器的操作,包括启动传输、停止传输、校验计算等。
5. 缓冲器(Buffer):用于暂存发送和接收的数据,确保数据的顺序传输和正确接收。
软件实现细节
1. 数据格式和协议:确定数据的格式,如数据位数、校验方式等。常见的协议包括8位数据位、无校验、1位停止位(8N1)。
2. 发送操作:将要发送的数据写入发送缓冲器,并通过控制逻辑控制发送器将数据逐位发送出去。
3. 接收操作:通过控制逻辑控制接收器将接收到的数据位逐个读取并存入接收缓冲器。
4. 中断处理:UART通常支持中断机制,在发送或接收完成后产生中断,通知CPU进行相应的处理。
5. 错误检测和处理:校验位的计算和校验、数据位的解析和处理,以及错误处理(如重发、错误指示等)。
UART传输的过程
1. 起始位(Start Bit):传输开始前,发送线发送一个低电平作为起始位,表示数据的开始。接收线检测到低电平后,开始接收数据。
2. 数据位(Data Bits):发送端将数据位逐位发送,从低位到高位,以二进制形式传输数据。接收端在时钟的边沿进行采样,以确定数据位的值。
3. 校验位(Parity Bit):可选项,用于检测数据位传输过程中的错误。发送端在数据位后面发送校验位,其值与数据位的校验规则相关。接收端通过校验位来验证数据的正确性。
4. 停止位(Stop Bit):传输完成后,发送线发送一个或多个高电平作为停止位,表示数据的结束。接收线检测到高电平后,完成数据的接收。
UART的传输速率由波特率(Baud Rate)决定,表示每秒传输的比特数。波特率确定了每个比特的持续时间,从而影响了数据的传输速度。并且发送端和接收端的波特率必须一致,才能正确地接收和解析数据。否则,数据可能会被错误地解析或丢失。
奇偶校验
上面讲到的校验位就是使用奇偶校验方法对uart传输的数据进行校验,奇偶校验是一种在UART通信中常用的校验方式,用于检测数据位传输过程中的错误。它可以通过校验位来验证数据的正确性。
奇偶校验的实现方式如下:
1. 奇校验(Odd Parity):发送端在每个数据字节中已发送的数据位之后,追加一个校验位,使得整个数据字节中的1的数量(包括数据位和校验位)为奇数。例如,如果数据位中有偶数个1,发送端会在校验位中插入一个1,使得总数为奇数。
2. 偶校验(Even Parity):发送端在每个数据字节中已发送的数据位之后,追加一个校验位,使得整个数据字节中的1的数量(包括数据位和校验位)为偶数。例如,如果数据位中有奇数个1,发送端会在校验位中插入一个0,使得总数为偶数。
在接收端,接收器会对接收到的数据进行校验,计算数据位和校验位中1的数量,然后与校验方式进行比较。如果接收到的1的数量与校验方式不匹配,则认为数据出现错误。
奇偶校验的原理是通过增加一个校验位来帮助检测单个位错误的可能性。当接收到的数据位发生错误时,校验位的值将发生变化,接收端可以通过比较校验位与数据位的奇偶性来检测错误。
奇偶校验可以提高数据传输的可靠性,但它只能检测出一位的错误,并不能纠正错误。如果发生多位错误,奇偶校验无法检测到或纠正。因此,在实际应用中,通常需要结合其他更强大的错误检测和纠正机制来保证数据的可靠性。
stm32单片机上代码实现
HAL库开发
#include "stm32f4xx_hal.h" // 包含HAL库的头文件
UART_HandleTypeDef huart2; // UART句柄对象
void SystemClock_Config(void);
int main(void) {
// 初始化HAL库
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 使能串口时钟
__HAL_RCC_USART2_CLK_ENABLE();
// 配置串口参数
huart2.Instance = USART2;
huart2.Init.BaudRate = 9600;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&huart2);
// 发送数据
char txData[] = "Hello, World!";
HAL_UART_Transmit(&huart2, (uint8_t*)txData, strlen(txData), HAL_MAX_DELAY);
// 接收数据
char rxData[50]; // 接收缓冲区
HAL_UART_Receive(&huart2, (uint8_t*)rxData, sizeof(rxData), HAL_MAX_DELAY);
while (1) {
// 运行其他任务
}
}
// 配置系统时钟
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 16;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
RCC_OscInitStruct.PLL.PLLQ = 7;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1
|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}
上述代码使用了STM32的HAL库来简化UART通信的操作。首先,通过初始化HAL库和配置系统时钟来准备系统。然后,通过使能串口时钟、配置串口参数和初始化UART句柄对象来配置UART模块。接下来,可以使用`HAL_UART_Transmit()`函数发送数据,使用`HAL_UART_Receive()`函数接收数据。
标准库开发
#include "stm32f4xx.h" // 包含STM32标准库的头文件
void USART2_Init(void);
void USART2_Write(char ch);
char USART2_Read(void);
int main(void) {
USART2_Init(); // 初始化USART2
// 发送数据
USART2_Write('H');
USART2_Write('e');
USART2_Write('l');
USART2_Write('l');
USART2_Write('o');
// 接收数据
char received_data;
while (1) {
received_data = USART2_Read();
// 处理接收到的数据
}
}
// USART2初始化
void USART2_Init(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 使能USART2时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 使能GPIOA时钟
// 配置USART2的引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; // PA2为USART2的TX引脚,PA3为USART2的RX引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // 复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // GPIO速度为50MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIOA
// 将引脚映射到USART2
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); // 将PA2引脚映射到USART2的TX引脚
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2); // 将PA3引脚映射到USART2的RX引脚
// 配置USART2的参数
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600; // 波特率为9600
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 数据位为8位
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 停止位为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(USART2, &USART_InitStructure); // 初始化USART2
USART_Cmd(USART2, ENABLE); // 使能USART2
}
// 发送数据到USART2
void USART2_Write(char ch) {
while (!(USART2->SR & USART_FLAG_TXE)); // 等待发送缓冲区为空
USART2->DR = (ch & 0xFF);
}
// 从USART2接收数据
char USART2_Read(void) {
while (!(USART2->SR & USART_FLAG_RXNE)); // 等待接收缓冲区有数据
return (char)(USART2->DR & 0xFF);
}
上述代码通过调用相应的库函数和使用相关的寄存器配置来初始化USART2,并实现了通过USART2发送和接收数据的功能。
在嵌入式系统、通信设备、物联网等领域,UART被广泛使用于串口通信。许多微控制器和单片机芯片都集成了UART硬件模块,简化了串口通信的实现。