通信的两种方式:
并行通信
-传输原理:数据各个位同时传输。
-优点:速度快
-缺点:占用引脚资源多
串行通信
-传输原理:数据按位顺序传输。
-优点:占用引脚资源少
-缺点:速度相对较慢
串行通信分类(按照数据传送方向)
单工(a):
数据传输只支持数据在一个方向上传输
半双工(b):
允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;
全双工(c):
允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。
串行通信的通信方式
同步通信(带时钟同步信号传输):
SPI,IIC通信接口
异步通信(不带时钟同步信号传输):
UART(通用异步收发器),单总线 约定传输速度
STM32的串口通信接口
UART:通用异步收发器。
USART:通用同步异步收发器。
大容量STM32F10x系列芯片,包含3个USART和2个UART
UART异步通信方式引脚连接方法:
-RXD:数据输入引脚。数据接受。
-TXD:数据发送引脚。数据发送。
UART异步通信方式特点:
全双工异步通信。
分数波特率发生器系统,提供精确的波特率。
-发送和接受共用的可编程波特率,最高可达4.5Mbits/s
可编程的数据字长度(8位或者9位);
可配置的停止位(支持1或者2位停止位);
可配置的使用DMA多缓冲器通信。
单独的发送器和接收器使能位。
检测标志:① 接受缓冲器 ②发送缓冲器空 ③传输结束标志
多个带标志的中断源。触发中断。
其他:校验控制,四个错误检测标志。
串口通信过程
发送数据:通过总线往USARTx控制器中的DR寄存器(TDR),写入数据,USARTx控制器会自动通过Tx管脚发送出去。
接收数据:USARTx控制通过Rx管脚接收到位数据,组合成8位数据,存在DR寄存器(RDR),直接读取DR寄存器。
STM32串口异步通信需要定义的参数
1 起始位
2 数据位(8位或者9位)
3 奇偶校验位(第9位)
4 停止位(1,15,2位)
5 波特率设置
协议规定了什么?硬件层、电平标准、通信数据格式、传输速率
开始位 +数据位 +奇偶校验位 +停止位
位数 1 5~8 0~1 1
电平 0 0/1 0/1 1
开始位:低电平 -- 设备检测下降沿,代表开始
数据位:5 -- 0000 0101 5~8位 -- 8位
奇偶校验位:校验一帧数据是否完整
奇偶校验:数据位中1的个数+奇偶位中1的个数之和。
如果是奇校验:个数之和必须为奇数。
如果是偶校验:个数之和必须为偶数。
例如:发送方:0110 0011 -- 0x63 采用奇校验 奇偶校验位为1
接收方:0110 0011 1 --- 正确
0100 0011 1 --- 错误
0000 0011 1 --- 奇偶校验正确,数据错误 -- 现在采用CRC校验。
停止位:高电平 -- 总线空闲状态为高电平
常用的帧格式:1+8+0+1 -- 1个开始位+8个数据位+0个奇偶校验位+1个停止位
通信速率:波特率 bps 每秒钟发送的位数,常见的波特率:9600 115200等等
进行通信的两个设备,波特率必须一样。
常用的串口相关寄存器
USART_SR状态寄存器
USART_DR数据寄存器
USART_BRR波特率寄存器 (填写下面计算后的数值)
USART_CR1控制寄存器
根据该图可了解串行通信的相关配置。根据下半图,可得到波特率的计算方法:
串口操作相关库函数
void USART_Init(); //串口初始化:波特率,数据字长,奇偶校验,硬件流控以及收发使能 void USART_Cmd();//使能串口 void USART_ITConfig();//使能相关中断 void USART_SendData();//发送数据到串口,DR uint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据 FlagStatus USART_GetFlagStatus();//获取状态标志位,SR void USART_ClearFlag();//清除状态标志位,SR ITStatus USART_GetITStatus();//获取中断状态标志位,SR void USART_ClearITPendingBit();//清除中断状态标志位,SR
实验4串口实验的代码中FWLib文件夹下stm32f10x_usart.c下的stm32f10x_usart.h下,可找到相关函数的声明与定义,然后可再进行追溯。
串口配置一般步骤
①串口时钟使能,GPIO时钟使能:RCC_APB2PeriphClockCmd();
②串口复位:USART_DeInit(); 这一步不是必须的
③GPIO端口模式设置:GPIO_Init(); 模式设置为GPIO_Mode_AF_PP
④串口参数初始化:USART_Init();
⑤开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤)
NVIC_Init();
USART_ITConfig();
⑥使能串口:USART_Cmd();
⑦编写中断处理函数:USARTx_IRQHandler();
⑧串口数据收发:
void USART_SendData();//发送数据到串口,DR
uint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据
⑨串口传输状态获取:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
具体实现
第一步:新建模版,并使能串口时钟和GPIO时钟。
跟之前一样,先建立一个简单的模版。
在USER文件夹下找到system_stm32f10x.c下的stm32f10x_rcc.h.中找到void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);之前就知道这个函数是用来使能的,然后先Go To xxx找到函数的定义后,在函数中assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
查看函数中的第一个参数RCC_APB2Periph
Go To xxx后我们可以看到,该函数即可使能GPIOA又可使能USART1。故使能语句可这么编写
void My_USART1_Init(){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE); }
串口复位非必须,故可省略。
第二步:GPIO端口模式设置
在USER文件夹下找到system_stm32f10x.c下的stm32f10x_gpio.h.中找到void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);该函数之前已经讲解过了,程序可修改为:
另外一个USARTX_RX是输入 ,根据上面的图知道设为上拉输入或者浮空
//2. 配置模式 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_Init(GPIOA, &GPIO_InitStructure);
void My_USART1_Init(){ GPIO_InitTypeDef GPIO_InitStructure; //定义结构体 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE); //使能串口时钟 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP; //设置为复用推挽输出 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_9; //引脚9 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz; //速度10MHz GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIOA模式设置 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_10; //引脚10 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz; //速度10MHz GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIOA模式设置 }
第三步:串口参数初始化和使能串口
在USER文件夹下找到system_stm32f10x.c下的stm32f10x_usart.h.中找到void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);、void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);,程序可修改为:
/* 配置USART模式 */ //1. 时钟使能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //2. 模式配置 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = brr; //波特率 (数据传输速度) USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //非硬件流 (如何决定收发的时机) USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 (全双工) USART_InitStructure.USART_WordLength = USART_WordLength_8b; //RS232协议(数据格式):数据位 8个 USART_InitStructure.USART_Parity = USART_Parity_No; //RS232协议(数据格式):奇偶校验位 0个 USART_InitStructure.USART_StopBits = USART_StopBits_1; //RS232协议(数据格式):停止位 1个 USART_Init(USART1, &USART_InitStructure);
void My_USART1_Init(){ GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO结构体 USART_InitTypeDef USART_InitStructure;//定义USART结构体 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE); //使能串口时钟 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP; //设置为复用推挽输出 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_9; //引脚9 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz; //速度10MHz GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIOA模式设置 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_10; //引脚10 GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz; //速度10MHz GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIOA模式设置 USART_InitStructure.USART_BaudRate=115200;//波特率 USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None ;//硬件流:无 USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;//TX和RX都使能故使用| USART_InitStructure.USART_Parity=USART_Parity_No;//奇偶校验位:无 USART_InitStructure.USART_StopBits=USART_StopBits_1;//停止位:1 USART_InitStructure.USART_WordLength=USART_WordLength_8b;//字长:8位 USART_Init(USART1,&USART_InitStructure);//串口参数初始化 USART_Cmd(USART1,ENABLE); //使能串口 }
第四步:开启中断并且初始化NVIC
在FWLIB文件夹下找到misc下找到void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup),函数中找到;assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));,在Go To xxx,查看填写格式,选择NVIC_PriorityGroup_2,即:两位响应优先级和两位抢占优先级。
在USER文件夹下找到system_stm32f10x.c下的stm32f10x_usart.h.中找到void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)
写NVIC函数
//3. 配置接收中断(串口回显时注释掉) USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //接收中断打开 NVIC_InitTypeDef NVIC_InitStruct = {0}; NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_Init(&NVIC_InitStruct);
//4. 初始状态 -- 使能 USART_Cmd(USART1, ENABLE);
第五步:编写中断处理函数
在文件stm32f10x_usart.h文件下打开:ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART1_IRQHandler(void){ u8 res; if(USART_GetITStatus(USART1,USART_IT_RXNE)){//若是接收到中断 res=USART_ReceiveData(USART1);//接收数据 USART_SendData(USART1,res);//接收到在发送数据,才可以在串口监视器中看到数据 } }
最后编译就通过了,注意要把模版中文件夹SYSTEM中的uart删除,因为定义重复了。
之后就可以上传程序,然后利用串口调试器,注意调试器中的设置要跟程序一样,波特率为115200,停止位1,等等。最终的实验现象就是发送什么,最后在调试软件中就会看到什么。
SYSTEM文件夹下,usart.c文件中,可看到以下代码(实验4串口实验):
main函数中调用函数
void USART1_IRQHandler(void) //串口1中断服务程序 { u8 Res; #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS. OSIntEnter(); #endif if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res =USART_ReceiveData(USART1); //读取接收到的数据 if((USART_RX_STA&0x8000)==0)//接收未完成,判断最高位是不是0,是0表示接收未完成,则往下执行。若为1则不往下执行(上一次接收没清空) { if(USART_RX_STA&0x4000)//接收到了0x0d,若第二位为1则表示接收到0x0d,在往下判断下一位是不是0x0a,若不是重新开始,是的话则则给第一位置1 { if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始 else USART_RX_STA|=0x8000; //接收完成了 } else //还没收到0X0D,第二位不是1,则判断什么时候接收到0x0d时,将第二位置为1 { if(Res==0x0d)USART_RX_STA|=0x4000; else//若一直没收到的话,则一直讲Res变量中的数值给变量,且通过位与来判断该位是否溢出(若第1、2位有数据给他清零) { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收 } } } } #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS. OSIntExit(); #endif } #endif
实验4代码详细解释
usart.h
#ifndef __USART_H #define __USART_H #include "stdio.h" #include "sys.h" ////////////////////////////////////////////////////////////////////////////////// //本程序只供学习使用,未经作者许可,不得用于其它任何用途 //ALIENTEK STM32开发板 //串口1初始化 //正点原子@ALIENTEK //技术论坛:www.openedv.com //修改日期:2012/8/18 //版本:V1.5 //版权所有,盗版必究。 //Copyright(C) 广州市星翼电子科技有限公司 2009-2019 //All rights reserved //******************************************************************************** //V1.3修改说明 //支持适应不同频率下的串口波特率设置. //加入了对printf的支持 //增加了串口接收命令功能. //修正了printf第一个字符丢失的bug //V1.4修改说明 //1,修改串口初始化IO的bug //2,修改了USART_RX_STA,使得串口最大接收字节数为2的14次方 //3,增加了USART_REC_LEN,用于定义串口最大允许接收的字节数(不大于2的14次方) //4,修改了EN_USART1_RX的使能方式 //V1.5修改说明 //1,增加了对UCOSII的支持 #define USART_REC_LEN 200 //定义最大接收字节数 200 #define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收 extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 extern u16 USART_RX_STA; //接收状态标记 //如果想串口中断接收,请不要注释以下宏定义 void uart_init(u32 bound); #endif
bound 是波特率
extern 定义了一些变量 外部变量
接收从电脑传来的数据存入buf
0x0D=回车 0x0A=换行 二个结束符
接收完成 bit15-1 bit14-1 bit13~0 ----接收的位数
void USART1_IRQHandler(void) //串口1中断服务程序 { u8 Res; #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS. OSIntEnter(); #endif if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res =USART_ReceiveData(USART1); //读取接收到的数据 if((USART_RX_STA&0x8000)==0)//接收未完成 { if(USART_RX_STA&0x4000)//接收到了0x0d { if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始 else USART_RX_STA|=0x8000; //接收完成了 } else //还没收到0X0D { if(Res==0x0d)USART_RX_STA|=0x4000; else { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;//=0x3fff最多2^13-1数据 USART_RX_STA++;//有效数据个数++ if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收 } //有效的数据个数不能大于定义的数据个数 } } } #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS. OSIntExit(); #endif } #endif
工程文件可供参考:
https://wwa.lanzoui.com/iG84Jvh1loj