1、串口概述
UART:Universal Asynchronous Receiver/Transmitter
通用异步收发器
USART:Universal Synchronous Asynchronous Receiver/Transmitter
通用同步异步收发器
串口一般用于通信。
通信要满足什么条件才可以通信:
物理层面-->物理介质
软件层面-->协议
通信双方有没有共同的时钟线用来作为同步时钟使用。
如果有共同的时钟线即同步通信,如果没有则为异步通信。
时钟信号可以作为通信时的同步信号,如:
1) 在时钟信号的低跳变的时候,改变信号-->发送方发送数据
2) 在时钟信号的高跳变的时候,采样信号-->接受方接受数据
同步:发送方发送数据之后,等接收方发回响应之后,才会发送下一个数据的通讯方式。
异步:发送方发送数据之后,不等接收方发回响应,接着发送下一个数据的通信方式。
串口是单片机中属于最常见的、也是最简单的串行数据传输协议。
串口只需要两根线,就可以实现全双工的串行通信。
两根数据线分别为(Tx和Rx就是由GPIO口复用而来):
Tx:发送数据线 用于向对方发送数据
Rx:接收数据线 用于从对方那接收数据
全双工:两个设备的接收端和发送端是相互独立的,互不干扰,接收数据不会干扰到
发送数据,所以两个设备可以同时收/发。
串行:数据的传输只有一根电线,每次就只能传输1个bit,当多个数据需要发送的时候,
只能一个bit接着一个bit的发送/接收。
2、串口协议
作用:规定了串口发送和接收数据的形式,必须以帧(Frame)为单位。
1帧(Frame) = 1bit (起始位) + 8~9bit 数据位 + 0bit/1bit 校验位 + (0.5/1/1.5/2bit)停止位
起始位:一个周期的低电平信号
所以串口的数据线Tx在空闲的时候应该要永远保持高电平。
8~9bit 数据位:
通信的正文。具体是多少个bit,由双方协议。
并且是先发送最低位(LSB),最后传送最高位(MSB)
0bit/1bit 校验位: 表示是否需要校验
0bit 没有校验位
1bit 有校验
奇校验 要保证前面的数据位加上校验位的bit值为1的位的个数要为奇数个。
偶校验 要保证前面的数据位加上校验位的bit值为1的位的个数要为偶数个。
停止位:高电平 1/1.5/2bit
具体持续多久的高电平,由双方协定。
但是由于UART异步串口,没有时钟进行同步,因此光靠帧格式传输数据还是不能准确收发。
因此串口规定了另外一个东西来确保数据准确收发。
Baudrate : 波特率--->UART的传输速率,决定1Frame的传输周期
单位:bps(bit per second)
波特率的设置双方必须一致。
UART协议:帧格式 + 波特率
3、STM32F4xx串口控制器
存在的意义在于:有时候发送方通过Tx往外发送数据的时候,如果对方还没有准备好,
发送的数据将会被丢弃,所以在硬件上加两根硬件流控的信号。
RTS:Request To Send 请求发送信号
告知对方,可以向我发送数据啦
CTS:Clear To Send 清除发送信号
对方告知我,它要向发送数据啦。
不一定需要使用RTS/CTS
CR(Control Register):控制寄存器,用于控制串口的一些行为
SR(Status Register):状态寄存器,用来指示串口控制器的一些状态。
读(接收数据):
外设将数据往Rx引脚上发送,因为串口协议规定数据是一个bit一个bit 的发送的,
所以没来一个bit,需要进行移位或操作之后先将其保存在接收移位寄存器中,
当数据来齐之后,再将数据挪入接收数据寄存器(Receiver Data Register)。
此时CPU就应该要及时将RDR寄存器中的数据读走,否则会被下一次发送过来的数据
给覆盖掉。
在串口读取数据的过程中需要用到SR寄存器中的标志位:
RXNE:Rx Not Empty 接收数据寄存器不为空
如果RXNE被设置,说明RDR中已经存入了一个字节的数据(从对方接收来的),
CPU就可以去读取RDR以获取接收到的数据。
写(发送数据):
只需要将要发送出去的数据存放在发送数据寄存器(TDR)中,发送控制器将要发送的
数据自动填充到发送移位寄存器中,在设定好的波特率影响下,将数据1bit接1bit的
通过Tx发送出去。
TXE:Tx Data Register Empty 发送数据寄存器为空
如果TXE被设置,说明TDR中的数据被发送出去啦
但是并不能表示已经发送完成,因为TDR中的数据可能只是被转移到发送移位寄存器中
TC:Transmit Complete发送完成标志
表示发送移位寄存器中的数据,都已经从Tx引脚发送出去啦。
发送数据之前,必须确保TDR寄存器为空(TXE被设置),否则,上一个发送的数据
可能没有发送完成,那么就会被覆盖。
接收数据需要等RXNE被设置,才能去接收。
因此,一般使用串口中断来完成串口数据的接收。
CR寄存器中中断使能位:IE --> Interrupt Enable
TXEIE:TXE中断使能位,这个标志位当串口TDR为空时会触发中断
如果TXEIE被设置为1时,当TXE被置1时(发送数据寄存器为空),就会产生一个串口中断
如果TXEIE被设置为0时,就算TXE被置1时,也不会产生串口中断
TCIE:如果TCIE被设置,当串口数据都发送完成,就会触发串口中断
如果TCIE被设置为1时,当TC被置1时(数据发送完成),就会产生一个串口中断
如果TCIE被设置为0时,就算TC被置1时,也不会产生串口中断
RXNEIE:串口接收数据寄存器不为空中断使能位
如果RXNEIE被设置为1时,当RXNE被置1(接收数据寄存器不为空)时,
就会产生一个串口中断--->意味我们需要去中断服务函数中将别人发送过来的数据读取出来
如果RXNEIE被设置为0时,就算RXNE被置1时,也不会产生串口中断。
4、STM32F4xx串口代码流程
UART的Tx和Rx引脚都是GPIO口复用而来。
USART1_Tx-->PA9
USART1_Rx-->PA10
1)串口GPIO配置
a. 使能GPIO分组时钟
b. 初始化GPIO口-->AF模式
c. 配置GPIO复用功能
如:
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
2)USART配置
a. 使能串口的时钟
b. 初始化配置USART
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
3)中断配置
a. 中断源的控制
产生串口中断的事件或者标志有很多,如 TXE TC RXNE ...
这些事件需要中断控制位使能才能产生中断。void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
b. NVIC控制
串口中断使能
4)使能串口
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
串口数据的收发:
收:
一般的,在串口通信中,接收数据都是在串口中断服务函数中进行:
//串口1中断服务函数
void USART1_IRQHandler(void)
{
//有多个事件(TXE,TC,RXNE...)可以引发串口中断
//所以在串口中断服务函数中一般要判断到底是什么事件引起的串口中断
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
{
//去读取数据
unsigned char data = USART_ReceiveData(USART1);
if(data == 0)
{
}
else if(data == 1)
{
}
//清除中断标志
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
//FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
//ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
//uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
//void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG)
//void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT)
发:发是一种主动的形式,一般在main中进行
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
stm32 可以将printf重定向到串口
1. 初始化USART1
2. 在调用printf的文件中添加头文件stdio.h,同时需要选中 MicroLIB
3. printf实际上是调用fputc来实现输出的
int fputc(int c,FILE *stream)
{
USART_SendData(USART1,c & 0xFF);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
return 0;
}
/*
USART1_Init:串口1的初始化函数
@baudrate:波特率
USART_Tx --> PA9
USART_Rx --> PA10
*/
void USART1_Init(int baudrate)
{
//1.串口GPIO配置
//使能GPIO分组时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
//初始化GPIO口
GPIO_InitTypeDef g;
g.GPIO_Mode = GPIO_Mode_AF; //复用功能
g.GPIO_OType = GPIO_OType_PP;
g.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
g.GPIO_PuPd = GPIO_PuPd_NOPULL;
g.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &g);
//设置复用功能
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
//2.USART配置
//使能串口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//初始化USART配置
USART_InitTypeDef u;
u.USART_BaudRate = baudrate;
u.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件流控
u.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
u.USART_Parity = USART_Parity_No; //无校验
u.USART_StopBits = USART_StopBits_1;
u.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &u);
//3.中断配置
//中断源控制
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//NVIC控制
NVIC_InitTypeDef n;
n.NVIC_IRQChannel = USART1_IRQn;
n.NVIC_IRQChannelCmd = ENABLE;
n.NVIC_IRQChannelPreemptionPriority = 2;
n.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&n);
//4.使能串口
USART_Cmd(USART1, ENABLE);
}
void USART1_IRQHandler(void)
{
//有多个事件(TXE,TC,RXNE...)可以引发串口中断
//所以在串口中断服务函数中一般要判断到底是什么事件引起的串口中断
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
{
//去读取数据
unsigned char data = USART_ReceiveData(USART1);
if(data == 0)
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);
}
else if(data == 1)
{
GPIO_SetBits(GPIOF,GPIO_Pin_8);
}
//清除中断标志
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
/*
USART_Send_Data:通过串口将数据发出去
@USARTx:通过哪个串口将数据发出去
@str:要发送的数据
@len:要发送的数据长度
*/
void USART_Send_Data(USART_TypeDef *USARTx,unsigned char *str,int len)
{
int i;
for(i = 0;i < len;i++)
{
//在发送数据之前,必须确保TDR寄存器为空
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) != SET);
USART_SendData(USARTx, str[i]);
}
}
int main()
{
//串口发送实验
USART1_Init(9600);
unsigned char str[] = {"hello world\r\n"};
while(1)
{
USART_Send_Data(USART1, str, sizeof(str));
Delay_ms(1000);
}
return 0;
}