目录
简介
串口通讯协议的物理层
电平标准
协议层
USART框图
总结
hal库代码
标准库代码
简介
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器。USART是STM32内部集成的硬件外设,STM32F103系列最多有3个通用同步异步收发器(USART), 2个通用异步收发器( UART)。 USART和UART的主要区别在于, USART支持同步通信,该模式有一根时钟线提供时钟。串口在嵌入式中经常使用,一般使用UART就足够了
串口通讯协议的物理层
串口通讯的物理层有很多标准及变种,经常提到TTL、 RS232、 RS422、 RS485等标准,简单的说,就是为了适应不同的环境条件,使用了不同的电平标准。一般就使用TTL电平,引脚直接连接即可,假如微处理器在工业现场,需要连接一个几十米外的装置,则应该考虑将TTL电平转为RS232、 RS422、 RS485
RS-232 电平等标准的信号不能直接被控制器直接识别,所以这些信号会经过一个“电平转换芯片”转换成控制器能识别的“TTL 标准”的电平信号,才能实现通讯
电平标准
一般常使用 TTL 的电平标准,为了加大传输距离,可以增加电压、差分传输等方式
TTL接口的串口,硬件连接如下
TX:数据发送;RX:数据接收;
协议层
串口通信的数据包由发送设备的 TXD 接口传输到接收设备的 RXD 接口。在串口通信的协
议层中,规定了数据包的内容,它由起始位、主体数据、校验位以及停止位组成,通讯双方的
数据包格式要约定一致才能正常收发数据。
波特率:一般选波特率都会有9600,19200,115200等选项。其实意思就是每秒传输这么多个比特位数(bit);
起始位: 先发出一个逻辑”0”的信号,表示传输数据的开始;
数据位:可以是5~8位逻辑”0”或”1”,先传输bit 0,在传输bit 1,依次类推;
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。 校验位是可选的,可以不传输;
停止位:它是一个字符数据的结束标志,数据线变回逻辑”1”;
一般使用的是8位数据位,1位起始位,1位停止位,无校验位
USART框图
① 引脚:主要是tx\rx数据发收,CK:在同步模式时,用于输出时钟;SW_RX:在单线和智能卡模式下接收数据,属于内部引脚,没有具体外部引脚;在硬件流控制时,RTS用于指示本设备准备好可接收数据,低电平说明本设备可以接收数据,CTS则是在低电平说明可以发数据
② 波特率发生器:计算波特率公式:波特率=外设时钟频率/USART_BRR寄存器的值*16
USART1挂载APB2上, USART2/3和USART4/5挂载APB1上。APB1时钟最大为36MHz, APB2时钟最大为72MHz。所以要配置USART1波特率为115200则寄存器USART_BRR为72000000/(115200*16)=39.0625
③发送器/接收器控制单元:向控制寄存器CR1、 CR2、 CR3和状态寄存器SR写入相应的位,可实现对USART数据的发送和接收控制。
④数据收发寄存器单元:发送移位寄存和接收移位寄存器,分别负责将发送数据并串转换和接收数据串并转换,从而实现数据在传输时,是一位一位的发送和接收。
总结
比如发送一字节数据“ A”? “A”的ASCII值是0x41, 二进制就是01000001。事先双方约定好波特率、数据格式,如115200,数据位是8,停止位是1,不设校验位和流控
初始电平为1(也就是高电平),发送方输出逻辑0(也就是低电平),并保持1位的时间,接收方检测到逻辑0,就知道对方准备发送数据了;
发送方根据数据的bit 0-7设置引脚电平,并保持1位的时间,接收方读取引脚电平,得到bit 0-7;
如果设置校验位还需要计算校验值,这里不设置可以省略这步
最后发送方输出逻辑1,并保持1位的时间;接收方读取引脚电平, 直到数据传输结束
hal库代码
#define UARTx USART1
#define UARTx_TX_PIN GPIO_PIN_9
#define UARTx_RX_PIN GPIO_PIN_10
#define UARTx_PORT GPIOA
/*使能 USARTx 的输入输出引脚(GPIO)的时钟*/
#define USARTx_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE()
/*使能 USARTx 的时钟*/
#define USARTx_CLK_EN() __HAL_RCC_USART1_CLK_ENABLE()
#define USARTx_CLK_DIS() __HAL_RCC_USART1_CLK_DISABLE()
uint8_t buf[1]={0};
/*UART句柄*/
UART_HandleTypeDef g_uart1_hanle;
/*UART1初始化函数*/
void uart1_init(uint32_t baudrate)
{
/*USART寄存器及地址:选择 USART1*/
g_uart1_hanle.Instance = UARTx;
/*配置波特率*/
g_uart1_hanle.Init.BaudRate = baudrate;
/*每次发送的字节长度:配置数据有效位为 8bit*/
g_uart1_hanle.Init.WordLength = UART_WORDLENGTH_8B;
/*每次发送后停止位长度: 一位停止位*/
g_uart1_hanle.Init.StopBits = UART_STOPBITS_1;
/*奇偶校验:不设校验位*/
g_uart1_hanle.Init.Parity = UART_PARITY_NONE;
/*流控设置:通常设置为None*/
g_uart1_hanle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
/*收发模式:可收可发*/
g_uart1_hanle.Init.Mode = UART_MODE_TX_RX;
/*使用库函数初始化 USART1 的参数*/
if(HAL_UART_Init(&g_uart1_hanle) != HAL_OK)
{
Error_Handler();
}
/*开启中断异步接收*/
HAL_UART_Receive_IT(&g_uart1_hanle,(uint8_t *)buf,1);
}
/*重定义串口MSP回调函数,使能 USART1 的时钟,使能引脚时钟,并配置引脚的复用功能*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
/*保存GPIO参数设置;*/
GPIO_InitTypeDef gpio_init_uart_struct = {0};
/*判断是否想要的为USARTx(宏定义对应USART1)*/
if(huart->Instance == UARTx)
{
/*使能 USART1 的时钟*/
USARTx_CLK_EN();
/*使能 USART1 的输入输出引脚的时钟*/
USARTx_GPIO_CLK_EN();
/*USART1 GPIO配置*/
gpio_init_uart_struct.Pin = UARTx_TX_PIN;/*选择 USART1 的 TX 引脚*/
gpio_init_uart_struct.Mode = GPIO_MODE_AF_PP;/*配置为复用推挽功能*/
gpio_init_uart_struct.Speed = GPIO_SPEED_FREQ_HIGH;/*引脚翻转速率快*/
HAL_GPIO_Init(UARTx_PORT, &gpio_init_uart_struct);
gpio_init_uart_struct.Pin = UARTx_RX_PIN;/*选择 RX 引脚*/
gpio_init_uart_struct.Pull = GPIO_PULLUP;/*时序空闲时候为高电平,所以上拉*/
gpio_init_uart_struct.Mode = GPIO_MODE_AF_INPUT;/*配置为复用输入功能*/
HAL_GPIO_Init(UARTx_PORT, &gpio_init_uart_struct);
/*中断优先级,使能中断*/
HAL_NVIC_SetPriority(USART1_IRQn,0,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
/*中断服务函数*/
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&g_uart1_hanle);
HAL_UART_Receive_IT(&g_uart1_hanle,(uint8_t *)buf,1);
}
/*回调函数*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
printf("\r\ninput: %c\r\n",buf[0]);
}
//printf重定义
/*printf和scanf会分别调用“ fputc()”和“ fgetc()”,因此这里重写这两个函数
*使用HAL提供的函数实现收发数据
- huart:UART外设的句柄,用于标识具体的UART外设。
- pData:一个指向待发送数据的缓冲区的指针。
- Size:待发送数据的长度,以字节为单位。
- Timeout:发送超时时间,如果数据在超时时间内没有发送完成,则函数会返回错误。
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&g_uart1_hanle,(uint8_t*)&ch, 1, 10);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&g_uart1_hanle, (uint8_t*)&ch, 1, 10);
return (int)ch;
}
标准库代码
// 串口1-USART1
#define USART_BAUDRATE 115200
#define USARTx USART1
#define USARTx_CLK RCC_APB2Periph_USART1
#define USARTx_GPIO_CLK (RCC_APB2Periph_GPIOA)
//apb时钟线使能函数
#define USARTx_APBxClkCmd RCC_APB2PeriphClockCmd
#define USARTx_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
USART GPIO 引脚宏定义
#define USART_TX_GPIO_PIN GPIO_Pin_9
#define USART_RX_GPIO_PIN GPIO_Pin_10
#define USART_RX_GPIO_PORT GPIOA
//中断相关
#define USARTx_IRQ USART1_IRQn
#define USARTx_IRQHandler USART1_IRQHandler
uint8_t rx_data[1]={0};
void uart_init(void)
{
/*开启时钟*/
USARTx_APBxClkCmd(USARTx_CLK,ENABLE);
USARTx_GPIO_APBxClkCmd(USARTx_GPIO_CLK,ENABLE);
/*初始化GPIO*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;/*发送引脚,输出模式:复用推挽输出*/
GPIO_InitStruct.GPIO_Pin = USART_TX_GPIO_PIN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(USART_RX_GPIO_PORT,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;/*接收引脚,输入模式:上拉输入*/
GPIO_InitStruct.GPIO_Pin = USART_RX_GPIO_PIN;
GPIO_Init(USART_RX_GPIO_PORT,&GPIO_InitStruct);
/*初始化uart接收*/
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = USART_BAUDRATE;/*波特率*/
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;/*不使用硬件流控*/
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;/*收发模式*/
USART_InitStruct.USART_Parity = USART_Parity_No;/*不使用校验*/
USART_InitStruct.USART_StopBits = USART_StopBits_1;/*一位停止位*/
USART_InitStruct.USART_WordLength = USART_WordLength_8b;/*8位字长*/
USART_Init(USARTx,&USART_InitStruct);
/*使能中断,开启EX标志位到NVIC的输出*/
USART_ITConfig(USARTx, USART_IT_RXNE,ENABLE);
/*配置NVIC*/
/*分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/*初始化NVIC的USART通道*/
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USARTx_IRQ;/*中断通道*/
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;/* 使能中断 */
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; /* 抢断优先级*/
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; /* 子优先级 */
NVIC_Init(&NVIC_InitStruct);
/*开启USART*/
USART_Cmd(USARTx,ENABLE);
}
/*中断服务函数*/
void USARTx_IRQHandler(void)
{
if(USART_GetITStatus(USARTx,USART_IT_RXNE) == SET)
{
rx_data[0] = USART_ReceiveData(USARTx);
printf("\r\ninput: %c\r\n",rx_data[0]);
/*清除中断标志位*/
USART_ClearITPendingBit(USARTx,USART_IT_RXNE);
}
}
//重定位printf
/*printf和scanf会分别调用“ fputc()”和“ fgetc()”,因此这里重写这两个函数
*通过printf输出到串口打印,默认打印到屏幕,但是只支持一个串口使用*/
int fputc(int ch, FILE *f)
{
uart_send_byte(ch);
return ch;
}
int fgetc(FILE *f)
{
return USART_ReceiveData(USARTx);
}