说起通信,我们都知道通信分为并行通信和串行通信。并行通信速率快但是占用引脚数多,串行通信速度慢但是占用引脚数很少。
今天我们主要来说串行通信
串行通信还可以分为同步通信和异步通信。
同步通信:带时钟同步信号传输,栗子:SPI、IIC
异步通信:不带时钟同步信号,栗子:UART、单总线
对于同步通信来说,通信双方是通过同步时钟信号进行发送和接收数据的,即每来一个时钟信号,发送方就发送一位数据,这样接收方也可以通过时钟信号来进行数据的解析。
对于异步通信来说,通信双方之间并没有同步时钟信号,为了使接收方能够准确地把发送方发送的信息解析出来,通信双方在通信之前要约定好一个东西,我们叫它波特率,即发送方按照一定的频率去发送数据,接收方也根据这个频率来每隔固定的时间就去读取信号线的电平状态,从而实现了对发送数据的解析。
简单的列几个通信方式:
UART(通用异步收发器):全双工
USART(通用同步异步收发器):全双工
SPI:同步通信 全双工
IIC:同步通信 半双工
单总线:异步通信 半双工
接下来说一下STM32F103中的串口通信
在F103中,串口1连接的时钟是PCLK2(72MHz),串口2—4连接的时钟是PCLK1(36MHz),这在一会我们计算波特率的时候会用到
STM32F103串口异步通信需要配置的参数
起始位
数据位(8位或者9位)
奇偶校验位(第9位)
停止位(1,15,2位)
波特率设置
USART逻辑框图(看一下,了解一下即可)
常用函数
void USART_Init(); //串口初始化:波特率,数据字长,奇偶校验,硬件流控以及收发使能
void USART_Cmd();//使能串口
void USART_ITConfig();//使能相关中断
void USART_SendData();//发送数据到串口,DR
uint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据
FlagStatus USART_GetFlagStatus();//获取状态标志位
void USART_ClearFlag();//清除状态标志位
ITStatus USART_GetITStatus();//获取中断状态标志位
void USART_ClearITPendingBit();//清除中断状态标志位
关于这个波特率的计算我简单的说一下:
STM32F103是支持设置分数波特率的,即用来存储波特率数值的寄存器的低四位用来存储小数部分,高12位用来存储整数部分。但是小数部分的数值也不是随便取的,它只能够取十六分之一的整数倍,即1/16、2/16、3/16…
所以说我们平常所常用的波特率并不是连续的数值,我们一般用的有4800、9600、115200等等,根据上面那个例子,假如说我们现在需要设置波特率为115200,那么把115200带入到官方手册中提供的公式中,即
式子中的Tx/Rx baud就是我们要的波特率115200,然后fck就是我们上面说的PCLK1和PCLK2了,PCLK1(36MHz)PCLK2(72MHz),然后USARTDIV代表的就是我们要往相应的寄存器中存储的数值。我们把115200带入到式子中求得USARTDIV=39.625,那么我们就直接把整数部分的39转换成16进制存入到相应寄存器(相应的寄存器为波特比率寄存器USART_BRR)的高12位,然后我们再对小数部分进行处理,我们是直接让小数乘以16,这其实就是十进制小数和十六进制小数之间的转换,我们把十进制的0.625转换成十六进制就是0.1,那么我们就把1存入到相应寄存器的低4位。再比如如果我们算出来小数部分是0.75,那么0.75转换成十六进制就是0.11,也就是0.C,那么我们就把0X0C存入到相应寄存器的低4位。大概的操作就是这样的,如果我们是用库函数来编写代码的话,这些内部计算我们不需要关注,我们只需要调用库函数USART_Init()
,然后传入参数就行,参数就是我们要设置的波特率。
波特比率寄存器
串口配置一般步骤
1、串口时钟使能,GPIO时钟使能:RCC_APB2PeriphClockCmd();
2、串口复位:USART_DeInit(); 这一步不是必须的
3、GPIO端口模式设置:GPIO_Init(); 串口发送引脚配置成复用推挽输出 接收引脚配置成浮空输入
4、串口参数初始化:USART_Init();
5、开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤)
NVIC_Init();
USART_ITConfig();
6、使能串口:USART_Cmd();
7、编写中断处理函数:USARTx_IRQHandler();
8、串口数据收发:
void USART_SendData();//发送数据到串口,DR
uint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据
9、串口传输状态获取:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
部分代码
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1
}
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
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;//如果在接收到0x0d之后,没有紧接着就接收到0z0a,那么就是接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
在这里面,也没啥东西,这种处理方法和我之前博客里写的输入捕获有点相似,大家可以去看一下,输入捕获比这个还要稍微复杂一点点。
我们主要就是注意一下它里面是通过一个变量USART_RX_STA
来控制数据的接收与处理的。
USART_RX_STA的低14位是用来记录我们接收到的有效数据个数的,即每接收到一个有效数据,它的值就加一,如果接收到了0X0D,那么就把这个变量USART_RX_STA的次高位置1,如果我们紧接着又接收到了0X0A的话,那么就把这个变量USART_RX_STA的最高位置1,此时就代表着我们这次的传输完成了。
注意:0x0D和0X0A是ASCII中的控制字符,0x0D含义是Carriage return(回车键),0X0A含义是Line feed(换行键),串口发送后的0d 0a表示回车换行
主函数部分代码
int main(void)
{
u16 t;
u16 len;
u16 times=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n\r\n");
for(t=0;t<len;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%200==0)printf("请输入数据,以回车键结束\n");
delay_ms(10);
}
}
}
注意配置串口初始化函数的入口参数就是我们要设置的波特率
uart_init(115200); //串口初始化,波特率设置为115200
开启串口接收中断(即接收缓冲器非空 USART_IT_RXNE)
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
判断产生的中断是否是接收中断
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)