注意:本文参考STM32F10XXX数据手册
串口通信简介
USART为通用同步异步接收/发送器,我们常用的是异步通信,下面也重点讲解异步通信
同步异步有什么区别呢?
具体区别可以问度娘,大致记住同步是要求时钟同步,那怎么保证时钟同步呢?撇开网络不谈,意法的解决办法是专门提供一个叫做USARTy_CK的引脚接口,也就是说如果采用同步通信这个引脚必须要和设备连上,异步通信就简单了,只需接受RX与发送TX两条线就可以了
异步通信怎样连线?
直接连接:

注意:USART只是一种通信协议根据不同电平分为TTL、RS232、RS485等等
直接连接,采用TTL电平通信,距离十分受限,最远通信距离大概2M
转RS232连接:

RS232连接方式,通信协议依旧没变,实际就是将TTL电平转换成了232电平,目的自然是为了获得更远的通信距离,最远通信距离大概20M
采用RS232电平的连线接头如下:

相信大家也不陌生,生活中还是用得比较多
转RS485连接:

采用485电平连接方式,与232类似,通信距离大幅度提升,多用在工业上,最远通信距离大概1000M
对于STM32F10XXX来说USART1位于高速总线APB2上,其通信速率高达4.5兆位/秒,快于其他串口,且支持DMA操作(对于DMA后续会详说,开始坑!)
编程代码
/*
* @Author: ExclusiveTP
* @Date: 2021-01-26 21:15:15
* @Last Modified by: ExclusiveTP
* @Last Modified time: 2021-01-26 21:15:15
*/
#include "usart.h"
#include "stdio.h"
//printf输出与USART2关联,能够传参
#if 1
#pragma import(__use_no_semihosting) /* 确保没有从 C 库链接使用半主机的函数 */
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
USART_SendData(USART2,(uint8_t)ch);
return ch;
}
#endif
//串口初始化函数
void Usart_Init(u32 Baudrate){
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
//GPIO初始化
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_2;//TX复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_3;//RX浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//Usart2初始化
USART_InitStructure.USART_BaudRate =Baudrate;//波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//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(USART2, &USART_InitStructure);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//使能串口接收中断
USART_Cmd(USART2, ENABLE);//使能串口
//Usart2 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
}
//串口打印函数,不能传参
void Usart_Printf(u8 *str){
u8 data=0;
do{
USART_SendData(USART2,str[data]);//发送一个字
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);//等待单字发送完成
data++;
}
while(str[data]!=0); //判断数据是否发送完成
}
//接收中断服务函数
void USART2_IRQHandler(void){ //串口2中断服务程序(固定的函数名不能修改)
u8 a;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET){ //接收中断(接收到的数据必须是0x0d 0x0a结尾)
a =USART_ReceiveData(USART2);//读取接收到的数据
printf("%c",a); //把收到的数据发送回电脑
}
}
简单说下代码,上面代码大致分为四块:printf与USART关联、串口初始化、Usart_Printf函数、串口中断服务函数
(1)printf是C标准库函数,必须将其与相应的串口关联才能使用,优点是能够传参,缺点是只能关联一个串口,当有多个串口使用时其中一个使用了printf函数,剩下的就只有老老实实写了
#if 1
#pragma import(__use_no_semihosting) /* 确保没有从 C 库链接使用半主机的函数 */
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while(USART_GetFlagStatus(USARTy,USART_FLAG_TC)==RESET);
USART_SendData(USARTy,(uint8_t)ch);
return ch;
}
#endif
这部分关联的代码没什么技巧,更换USARTy就行了,不多说
(2)串口初始化也都给了备注,大致捋一下:
①定义GPIO和USART两个结构体,这是必不可少的,至于NVIC中断优先级配置如果没有用到中断就不需要
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//使能串口接收中断
也就是说不要上面这句代码就不用配置NVIC
②配置TX引脚为复用推挽输出、RX为浮空或上拉输入,至于为什么直接上图,记住就好了
③进行协议的配置,Baudrate通过调用函数传参实现设置,8位数据位、1位停止位、不进行奇偶校验、无硬件流控制、接收发送模式,这是比较常用的,可以根据需要自行修改(这里不再详说了,因为每一点展开来都可以写成一篇文章了)
④中断优先级配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
这里注意不要把中断 USART2_IRQn入口地址写错了,当然你用到哪个中断入口就写哪个,然后就是中断优先级,抢占优先级高于响应优先级(具体什么关系,怎样设置后面说中断再详说,有一坑!)一般在不用到多个中断或者中断嵌套的情况下可以不管,这里都写0就行了
(3)串口打印函数Usart_Printf,这个函数是自己写的,就是当前面说的printf函数已经和其他串口关联后,我们就得自己写串口打印函数了,这个函数缺点是不能够传参,也就只能像这样用用
Usart_Printf("ExclusiveTP is handsome!")
具体代码是怎样实现的,我后面会专门来详说(再一坑!)
(4)串口接收中断
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//使能串口接收中断
前面要有了这句代码,串口在接收到数据后才会进入此中断,至于进入中断干什么完全由你自己书写,我给的代码例子是将串口接收到的东西再发送回去
几乎所有的中断服务函数都是这个大致的框架
void XXXXX_IRQHandler(void){
if(USART_GetITStatus(XXX, XXX) != RESET){
}
XXX_ClearITPendingBit(XXX,XXX);//清楚中断标志位
}