传输原理:数据各个位同时传输。
优点:速度快
缺点:占用引脚资源多
传输原理:数据按位顺序传输
优点:占用引脚资源少
缺点:速度相对较慢
按照数据传送方向,分为:
单工:数据传输只支持数据在一个方向上传输;
半双工:允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;
全双工:允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。
同步通信:带时钟同步信号传输。如SPI,lIC通信接口
异步通信:不带时钟同步信号。如UART(通用异步收发器),单总线
UART:通用异步收发器
USART:通用同步异步收发器
大容量STM32F10x系列芯片,如STM32F103ZET6,包含3个USART和2个UART
可以在芯片的数据手册中查到各串口所接的引脚
当通信线路上电平状态为1,表示当前线路上没有数据传送,串口处于空闲。
1.起始位:先发出一个逻辑"0"的信号,表示传输字符的开始。
2.数据位(8位或者9位):紧接着起始位之后,数据位的个数可以是4、5、6、7、8等,构成一个字符,从最低位开始传送。
3.奇偶校验位(第9位):数据位加上这一位后,使得"1"的位数应为偶数(偶校验)或奇数(奇校验),以此来校验资料传送的正确性。可有可无,CRC校验更准确
4.停止位(1,15,2位):它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
5.波特率设置
波特率是每秒钟传输的比特位,一般为4800、9600、115200,波特率越大,则数据传输速度越快,但这样就需要接收端也要快速的接收,同时容易受到干扰;波特率越小则传输越慢,抗干扰能力加强;
一般工业领域使用9600比较多,如果是单片机与模块之间的通信,如WiFi模块,则用115200,能及时通信
USART_SR状态寄存器
USART_ DR数据寄存器
USART_BRR波特率寄存器
寄存器的详细描述在stm32中文参考手册中25.6节USART寄存器描述中可以查看到
在STM32中文参考手册的USART框图中,这一部分是波特率设置示意图
公式:
上式中,fPCLKx是给串口的时钟(PCLK1用于USART2、3、4、5,PCLK2用于USART1);USARTDIV是一个无符号定点数。我们只要得到USARTDIV的值,就可以得到串口波特率寄存器USART1->BRR的值,反过来,我们得到USART1->BRR的值,也可以推导出USARTDIV的值。但我们更关心的是如何从 USARTDIV的值得到USART_BRR的值,因为一般我们知道的是波特率,和 fPCLKx的时钟,要求的就是USART_BRR的值。
注意
只有USART1使用PCLK2(最高72MHz),其它USART使用PCLK1(最高36MHz)。
参考手册中的波特率计算表
该寄存器的TXE、TC、RXNE位比较重要,这些位分别与发送数据寄存器(TDR)、发送移位寄存器、接收数据寄存器(RDR)相关
注意:TXE和TC的复位值为1,RXNE的复位值为0!
TXE为1: TDR里的数据全部移到了移位寄存器,且没有新的数据进TDR。
TXE为0: TDR里有数据,未空,则TXE=0。
在串口中使用USART_SendData()函数发送数据后,最好用USART_GetFlagStatus()函数判断TXE位是否为1,为1才表示数据已经被转移到移位寄存器中,如果一直往发送数据寄存器写入数据,没有等待数据转移到移位寄存器中的话,先写入的数据会被后写入的数据覆盖
TC为1:从TDR过来的数据全部被移送到TX引脚,且TDR里也没有新的数据
TC为0:从TDR里过来的数据还没有全部移过来,或者之前TDR里的数据被移走了,但TDR里又来了新的数据
TC标志位的清除是要先读USART_SR,再写入USART_DR,顺序不能反
这里有个串口发送的例子解析TC标志位的影响
上图程序先调用USART_SendData函数往DR寄存器放入一个‘H’字符,再判断TC标志位是否为1,为1则表示‘H’字符从串口TX发送了出去,上面程序最后结果应该是在串口助手上显示‘HKG’,但实际是显示‘KG’,这是为什么呢?
首先这里有两个问题,1:为什么’H‘字符没有发送出去;2:为什么后面KG都正常发送
解答1:需要注意的是,USART_SR寄存器的复位值是0x00C0,这说明上电复位时TXE和TC位默认是高电平的,系统刚上电,程序执行完了第10行,把‘H’字符放入到了DR寄存器中,但由于TC位上电默认高电平,所以11行的whlie循环就没有起到延时等待效果,就立马执行了12行,又往DR寄存器里放入了’K‘字符,此时’H‘字符还没来得及送到移位寄存器就被’K‘字符覆盖了,所以造成了’H‘字符的发送丢失
解答2:因为TC标志位清除的条件是先读USART_SR,再写入USART_DR,而程序中的第10、11行是先写入USART_SR,再读USART_SR,这顺序明显反了,所以不会清除TC标志位,而程序执行完11行再执行12行后,正好是先读USART_SR,再写入USART_SR,所以满足了TC清零的条件,TC在12行后被清零,所以在13行的whlie循环就起到了延时等待TC标志位再次被置1的作用,当TC被置1,退出循环,'K’字符就被发送了出去,后面的’G‘字符同理
修改方法:
1.在USART_SendData(USART1,‘H’)前加上读USART_SR的操作即可,也就是加上一条while循环,加上后串口助手接到的就是’HKG’;
2.也可以在发送’H‘字符前,调用USART_ClearFlag(USART1,USART_FLAG_TC),手动清除上电时TC标志位默认的高电平,同样能发送’HKG’
5、6、7位与状态寄存器的有点类似,这里主要是控制中断使能
void USART_Init(); //串口初始化:波特率,数据字长,奇偶校验,硬件流控以及收发使能
void USART_Cmd(); //使能串口
void USART_ITConfig(); //使能相关中断
操作DR数据寄存器:
void USART_SendData(); //发送数据到串口, 放入DR
uint16_t USART_ReceiveData(); //接受数据,从DR读取接收到的数据
操作SR状态寄存器
FlagStatus USART_GetFlagStatus(); //获取状态标志位
void USART_ClearFlag(); //清除状态标志位
ITStatus USART_GetITStatus(); //获取中断状态标志位
void USART_ClearITPendingBit(); //清除中断状态标志位
共同点:都是访问串口的SR状态寄存器。
不同点:
USART_GetITStatus():
该函数不仅会判断标志位是否置1,同时还会判断是否使能了相应的中断。所以在串口中断函数中,通常使用该函数
返回值:ITStatus,ITStatus的值为1,表示可以读取数值了,ITStatus的值为0,表示还不可以读取数值
USART_GetFlagStatus() :
该函数只判断标志位。在没有使能相应的中断时,通常使用该函数来判断标志位是否置1。
串口中断根据需要开启
TX引脚模式设置为推挽复用输出(GPIO_Mode_AF_PP)
RX引脚模式设置为浮空输入或带上拉输入(GPIO_Mode_IN_FLOATING)
先对串口1所接的引脚PA9和PA10初始化,再对串口初始化,要开启中断的话,要对NVIC进行初始化
/**
* @name USART1_Init
* @brief 串口1初始化
* @param bound:波特率
* @retval None
*/
void USART1_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//GPIO时钟,串口1时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);
//串口复位(可无)
USART_DeInit(USART1);
//USART1_TX PA9
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA9接TX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//PA9引脚初始化
GPIO_Init(GPIOA,&GPIO_InitStructure);
//USART1_RX PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PA10接RX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//PA10引脚初始化
GPIO_Init(GPIOA,&GPIO_InitStructure);
//USART1
USART_InitStructure.USART_BaudRate = bound; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx; //设置发送和接收模式
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
//USART1初始化
USART_Init(USART1,&USART_InitStructure);
//开启中断并初始化NVIC
//USART1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //串口1全局中断
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级为3
//根据指定的参数初始化NVIC寄存器
NVIC_Init(&NVIC_InitStructure);
//开启中断,接收到数据就进入中断
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
//使能串口
USART_Cmd(USART1,ENABLE);
}
//0x0d是回车键的十六进制值
//0x0a是换行/n的十六进制值
//中断处理判断数据结束标准是:回车+换行
#define USART_REC_LEN 200
u16 USART_RX_STA = 0; //接收状态标记
u8 USART_RX_BUF[USART_REC_LEN]; //接收数组,最大USART_REC_LEN个字节
/**
* @name USART1_IRQHandler
* @brief 串口1中断处理函数
* @param None
* @retval None
*/
void USART1_IRQHandler(void) //名字不能随便起
{
u8 Res;
if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res = USART_ReceiveData(USART1); //读取到的数据,函数返回的是USART1->DR寄存器的[8:0]位
/*STA最高位为接收完成标志位,接收完一串数据后才被置位,会被主函数或其他地方清零;
这里判断该位是否为0,为0表示一串数据正在接收,没接收完,或者是其他地方清零了标志位,准备下一串数据的接收*/
if((USART_RX_STA & 0x8000) == 0) //接收未完成
{
/*判断接收0x0d标志位是否为1,如果上一个字节是0x0d,那该标志位就会被置位,这里取出该位判断,为1则进入里面继续判断本 次接收的字节是否为0x0a(换行),是则表示一串数据接收完毕,最高位接收完成标志位置1,否则清除STA标志,重新接收数据*/
if(USART_RX_STA & 0x4000)
{
if(Res != 0x0a)USART_RX_STA = 0; //接收错误,重新开始
else USART_RX_STA |= 0x8000; //接收完成了,接收到回车后的换行字符,接收完成标志位置位
}
else //没接收到0x0d,说明数据还没结束,继续接收
{
if(Res == 0x0d) //如果这次收到的数据是0x0d
{
USART_RX_STA |= 0x4000; //接收到0x0d标志位置位
}
else //如果这次收到的数据不是0x0d,说明是普通数据,进行保存
{
/*将接收的数据存入数组中,STA的0 ~ 13位为接收到数据的个数,因为STA 0 ~ 13位一开始都是0,
0&1=0,所以[USART_RX_STA & 0x3FFF]为0,这里相当于下标的作用
*/
USART_RX_BUF[USART_RX_STA & 0x3FFF] = Res;
USART_RX_STA++; //长度+1,放下一个数据
if(USART_RX_STA>(USART_REC_LEN-1)) //如果数据个数(下标)大于接收数组长度,则清零STA
{
USART_RX_STA = 0; //STA清零,重新接收数据
}
}
}
}
}
}
#include "delay.h"
#include "USART1.h"
int main()
{
u16 i;
u16 len;
u16 time = 0;
//延时函数初始化
delay_Init();
//设置NVIC中断分组2:2位抢占优先级,2位响应优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//串口初始化,波特率9600
USART1_Init(9600);
while(1)
{
if(USART_RX_STA & 0x8000) //判断接收完成标志位是否为1
{
len = USART_RX_STA & 0x3FFF; //得到此次接收到的数据长度
for(i=0;i<len;i++)
{
USART_SendData(USART1,USART_RX_BUF[i]); //往串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) != SET); //等待发送结束
}
printf("\r\n\r\n"); //换行
USART_RX_STA = 0; //清零STA
}
else //接收完成标志位为0,说明stm32没有接收到数据
{
time++;
if(time%500 == 0){printf("请输入数据,以回车键结束\r\n");}
if(time%2000 == 0){printf("USART1串口实验\r\n");}
delay_ms(10);
}
}
}
在串口初始化的源文件中加入下面的代码,重写printf函数,让其往串口发送
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
仿照USART_SendData函数进行重写
void USART_SendByte(USART_TypeDef* USARTx,uint8_t Data)
{
assert_param(IS_USART_ALL_PERIPH(USARTx));
assert_param(IS_USART_DATA(Data));
USARTx->DR = (Data & (uint16_t)0x01FF);
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET); //等待发送完成
}
依次发送字符串中字符,每发送一个检查下TXE标志位,发送完全部字符以后,最后检查TC标志位。
void USART_SendString( USART_TypeDef *USARTx, char*str)
{
while(*str!=0)
{
USART_SendByte( USARTx,*str++ );
}
while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET);
}