一、USART与UART
1、区别
- 同步通信:USART在同步通信时需要时钟来触发数据传输,能够提供主动时钟,这使得通信双方可以共享一个时钟信号来采样数据线。
- 异步通信:在异步通信中,USART与UART没有区别,因为两者都不需要共享时钟信号,设备间的数据传输速
2、特点
UART:
- 全双工通信
- 简单易用
- 异步通信
- 传输速率可调
USART:
- 全双工通信
- 支持同步和异步通信
- 支持多种高级功能,如硬件流控制、DMA
- 自带波特率发生器,最高达4.5Mbps/s
- 具有多种错误检测标志:包括帧错误、溢出错误、噪声检测、奇偶校验错误
二、USART
一、概述
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器
- USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
- 自带波特率发生器,最高达4.5Mbits/s
- 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2) 可选校验位(无校验/奇校验/偶校验)
- 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
STM32F103C8T6 USART资源: USART1、 USART2、 USART3。
硬件流控制,就是防止接收设备处理数据速度慢,而导致数据丢失的问题,比如A设备Tx向B设备的Rx发送数据,A设备一直在发,发的太快了,B设备处理不过来,如果没有硬件流控制,那B设备就只能抛弃新数据或者覆盖原数据了。如果有硬件流控制,在硬件电路上,会多出一根线,如果B没有准备好接收,就置高电平,如果准备好了,就置低电平。
二、USART框图
如下:
发送器控制者发送移位寄存器,接收器控制接收移位寄存器。
硬件数据流控制:nRTS(Require to Send,n表示低电平,发送),nCTS(Clear to Send,n表示低电平,接收)。
如下,设备A向设备B发送数据,设备A需要根据设备B上nRTS发送来的电平进行判断,如果是低电平设备A可以发送数据,检测到高电平就停止发送数据。
当我们向TDR写数据时,等待发送移位寄存器移完数据后,TDR向发送移位寄存器转移数据,这时TXE(TX Empty)标志位会置1,我们可以写入新的数据。
接收寄存器类似,当我们接收数据时,数据会从高位往低位这个方向移动,也就是向右移,当接收一个字节后,会向RDR寄存器转运,同时,RXNE(RX Not empty,接收数据寄存器非空)会置1,者样我们就可以读走数据了,
SCLK(同步时钟)用于同步通信,只支持发送,不支持接收。
唤醒单元就是挂载多个设备。
USART1挂载在APB2总线上,所以就是PCLK2的时钟,一般是72MHz。其他USART挂载在APB1,就是PCLK1的时钟,一般36MHz。
TE(TX Enable)置1就是发送器使能,发送部分的波特率就有效了。RE(RX Enable)置1就是接收器使能,接收部分的波特率就有效了。
三、USART基本结构
四、数据帧
停止位就是控制高平时间。
五、起始位侦测
每个码元都要侦测。
手册上写的说明:
六、波特率发生器
发送器和接收器的波特率由波特率寄存器BRR里的DIV确定 ,计算公式:波特率 = fPCLK2/1 / (16 bote。DIV就是分频器。
三、串口发送代码
可以使用下面的函数可以重定向到串口,第一个函数可以用printf();第二个直接调用函数。
cpp
//重定向到串口
int fputc(int ch,FILE *f)
{
Serial_SendByte(ch);
return ch;
}
//重定向到串口
void Serial_Printf(char *format,...)
{
char String[100];
va_list arg;
va_start(arg,format);
vsprintf(String,format,arg);//都添加到string字符串里
va_end(arg);
Serial_SendString(String);
}
发送汉字时,要加一点东西,在魔法棒------>C/C++------>Misc Controls上加--no-multibyte-chars
可以在如下所示修改适合的编码形式。
Serial.c:
cpp
#include "stm32f10x.h" // Device header
#include "stdio.h"
#include "stdarg.h"
void Serial_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //如果有错误,就把这个函数放在最前面
USART_InitTypeDef USART_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //推挽输出
// GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
//Polarity 极性
USART_InitStruct.USART_Parity=USART_Parity_No ; //奇偶
USART_InitStruct.USART_BaudRate=9600;
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode=USART_Mode_Tx; //作为发送器,同时收发或上
USART_InitStruct.USART_StopBits=USART_StopBits_1 ;
USART_InitStruct.USART_WordLength=USART_WordLength_8b ;
USART_Init(USART1,&USART_InitStruct);
USART_Cmd(USART1,ENABLE);
}
void Serial_SendByte(uint8_t Byte)
{
//每次写入数据时,硬件会自动将TXE标志位清零
USART_SendData(USART1, Byte);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
}
void Serial_SendArray(uint8_t *Array,uint8_t length)
{
uint16_t i;
for(i=0;i<length;i++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *Array)
{
while(*Array!='\0')
{
Serial_SendByte(*Array);
Array++;
}
}
uint32_t Serial_Pow(uint16_t x,uint16_t y)
{
uint32_t Result=1;
while(y--)
{
Result*=x;
}
return Result;
}
void Serial_SendNumber(uint32_t Number,uint8_t length)
{
uint16_t i;
for(i=0;i<length;i++)
{
Serial_SendByte(Number/Serial_Pow(10,length-1-i)%10+0x30);
}
}
//重定向到串口
int fputc(int ch,FILE *f)
{
Serial_SendByte(ch);
return ch;
}
void Serial_Printf(char *format,...)
{
char String[100];
va_list arg;
va_start(arg,format);
vsprintf(String,format,arg);
va_end(arg);
Serial_SendString(String);
}
Serial.h:
cpp
#ifndef _SERIAL_H
#define _SERIAL_H
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendString(char *Array);
void Serial_SendArray(uint8_t *Array,uint8_t length);
void Serial_SendNumber(uint32_t Number,uint8_t length);
void Serial_Printf(char *format,...);
#endif
main.c:
cpp
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "delay.h"
#include "Serial.h"
#include "stdio.h"
int main(void)
{
// uint8_t MyArray[]={0x41,0x43,0x44,0x45};
//uint8_t MyArray[]="ABCDE11111111111";
OLED_Init();
Serial_Init();
Serial_SendByte(0x41);
//Serial_SendString(MyArray);
//Serial_SendArray(MyArray,4);
//Serial_SendNumber(12345,5);
//printf("Num=%d\r\n",666);
//Serial_Printf("Num=%d\r\n",666);
Serial_Printf("你好,世界");
while(1)
{
}
}
四、串口接收
Serial.c:
cpp
#include "stm32f10x.h" // Device header
#include "stdio.h"
#include "stdarg.h"
uint16_t Rxdata;
char String[16]={0};
uint16_t Flag;
uint16_t Flag_String;
void Serial_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //如果有错误,就把这个函数放在最前面
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStructure ;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//Polarity 极性
USART_InitStruct.USART_Parity=USART_Parity_No ; //奇偶
USART_InitStruct.USART_BaudRate=9600;
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //作为发送器,同时收发或上
USART_InitStruct.USART_StopBits=USART_StopBits_1 ;
USART_InitStruct.USART_WordLength=USART_WordLength_8b ;
USART_Init(USART1,&USART_InitStruct);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1,ENABLE);
}
void Serial_SendByte(uint8_t Byte)
{
//每次写入数据时,硬件会自动将TXE标志位清零
USART_SendData(USART1, Byte);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
}
void Serial_SendArray(uint8_t *Array,uint8_t length)
{
uint16_t i;
for(i=0;i<length;i++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *Array)
{
while(*Array!='\0')
{
Serial_SendByte(*Array);
Array++;
}
}
uint32_t Serial_Pow(uint16_t x,uint16_t y)
{
uint32_t Result=1;
while(y--)
{
Result*=x;
}
return Result;
}
void Serial_SendNumber(uint32_t Number,uint8_t length)
{
uint16_t i;
for(i=0;i<length;i++)
{
Serial_SendByte(Number/Serial_Pow(10,length-1-i)%10+0x30);
}
}
//重定向到串口
int fputc(int ch,FILE *f)
{
Serial_SendByte(ch);
return ch;
}
void Serial_Printf(char *format,...)
{
char String[100];
va_list arg;
va_start(arg,format);
vsprintf(String,format,arg);
va_end(arg);
Serial_SendString(String);
}
uint8_t Serial_GetRxFlag(void)
{
if(Flag==1)
{
Flag=0;
return 1;
}
return 0;
}
uint8_t Serial_GetRxData(void)
{
return Rxdata;
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
{
Rxdata=USART_ReceiveData(USART1);
Flag=1;
Flag_String++;
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
Serial.h:
cpp
#ifndef _SERIAL_H
#define _SERIAL_H
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendString(char *Array);
void Serial_SendArray(uint8_t *Array,uint8_t length);
void Serial_SendNumber(uint32_t Number,uint8_t length);
void Serial_Printf(char *format,...);
uint8_t Serial_GetRxData(void);
uint8_t Serial_GetRxFlag(void);
#endif
main.c:
cpp
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "delay.h"
#include "Serial.h"
#include "stdio.h"
extern char String[16];
uint16_t Serial_Rxdata;
int main(void)
{
OLED_Init();
Serial_Init();
OLED_ShowString(1,1,"RXData:");
while(1)
{
if(Serial_GetRxFlag()==1)
{
// Serial_Rxdata=Serial_GetRxData();
// Serial_SendByte(Serial_Rxdata);
// OLED_ShowHexNum(1,8,Serial_Rxdata,2);
OLED_ShowString(1,8,String);
}
}
}
五、串口收发-数据包
1、数据模式
显示汉字时/字符时,需要两端的编码相同,否则会产生错误。
2、HEX数据包
数据的包头包尾可以自己随便设置,但最好不要设置与传输的数据一样,否则会出现判错。