一、串口通讯概述
1、广义的串口
广义的串口是针对并口来说的。串口是指设备之间通过一根数据信号线按数据位形式一位一位地传输数据的通讯端口,同一时刻只能传输一位(bit)数据。并口则是指用多条数据线进行传输的通讯方式,可以同一时刻并排传输多个数据位的数据端口。
2、狭义的串口
狭义的串口我们就特指COM口,或者称做UART口。硬件上常用的两大电压标准有TTL(只有正电压)和RS232标准(有正负电压),STM32单片机上基本都使用的TTL标准,判别电压为基准供电电压的一半。我们看到的单片机上的串口一般分为两种,一种叫USART,通用同步异步收发器(Universal Synchronous Asynchronous Receiver and Transmitter),另一种为UART,通用异步收发器(Universal Asynchronous Receiver and Transmitter),唯一的区别很明显,就是有和没有同步通讯方式。通常硬件至少要有一对Tx和Rx加上一个GND地,有了这三个引脚就可以工作了,其它同步引脚等其它引脚可以不使用。
3、串口数据定义
完整的侦结构见下图,从一个起始位开始,接着是着多位有效数据,一般常规定义为8位,后面的就效验位,为可以选,最后一个就是停止标志位,也就是说8位有效数据的时候一个完整的帧结构就需要10位。
波特率是描述串口通讯的速度指标,有很多种,双方约定好就可以正常通讯,我们常用的有9600、115200、921600,定位bps,位每秒。
4、串口通讯应用
串口是我们使用的最基础的通信方式之一,因为引脚少,连接方便,它经常用来打印log,或作为短距离较少数据的通信方式,也可以替代I2C等偏低速通信方式。
二、STM32串口工程标准库实现
1、串口的初始化
在STM32工程main的最开始,大while()循环之前,我们的各种外设都需要初始化,如下面串口的初始化代码, 它是用来驱动串口的初始化函数uart1_init(),输入参数为波特率baud_rate,从注释中可以看到,初始化要进行以下操作步骤:
1、使能时钟;
2、自动复用的引脚,
3、配置引脚的初始化状态方式;
4、配置串口的通信属性;
5、使能串口;
6、初始化嵌套向量中断控制器中的相关中断参数,为接收做准备。
//初始化IO 串口1
//bound:波特率
void uart1_init(u32 baud_rate)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//#if EN_USART1_RX
NVIC_InitTypeDef NVIC_InitStructure;
//#endif
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化
//USART1 初始化串口属性设置
USART_InitStructure.USART_BaudRate = baud_rate;//波特率设置
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_Cmd(USART1, ENABLE); //使能串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
}
2、串口数据发送.
(1) 代码实现发送给串口1较为简单,一句话搞定:
Usart_SendString( USART1, send_data);
- 参数USART1为串口的名称,它对应着外设的内存地址映射。
- 参数send_data则是我们要发送的字符串数据,或者说是字符串指针。
(2) 重定向printf()到USART1:
- 重定向打印输出到串口1,需要定义fputc(),将打印数据的状态寄存器和数据寄存器映射到串口1,这样串口1就是打印数据口了。
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
/***************** 发送一个字节 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 发送一个字节数据到USART */
USART_SendData(pUSARTx,ch);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/***************** 发送字符串 **********************/
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
{}
}
//重定向printf到USART1
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
3、串口的数据接收
因为单片机串口接收是一个字节一个的接收的,接收到一个字节就会进入到中断服务函数中。我们可以在中断中进行字节判断,如下面的代码实例,也可以接收一段字符后做字符串匹配判断。这里需要注意几点:
- 中断服务程序的命名是有严格要求的,请注意不要写错,否者写错了也不给报错,很难找到问题原因。
- 中断服务程序中运行的时间越短越好,也就是说代码,尽量少做判断越精简越好。
u16 num1 = 0;//数据累加计数
char USART_RX1_BUF[HOST_REC_LEN];//接收缓冲字节.
// 串口中断服务函数
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)//enum{RESET = 0, SET = !RESET} FlagStatus
{
USART_RX1_BUF[num1] = (uint8_t)USART_ReceiveData(USART1);
// 当接收到的值等于\r\n结尾时截取数据
if(num1 > 2 && USART_RX1_BUF[num1-1] == '\r' && USART_RX1_BUF[num1] == '\n')
{
USART_RX1_BUF[num1+1]='\0';
SetUARTFlag(1);//设置UART1标志
num1 = 0;
return;
}
//超出长度溢出时截断数据
else if(num1 >= LimitSize_1)
{
USART_RX1_BUF[num1]=0x0d;//回车
USART_RX1_BUF[num1+1]=0x0a;//换行
USART_RX1_BUF[num1+2]='\0';
SetUARTFlag(1);//设置UART1标志
num1 = 0;
}
//正常累加,判断异常数据
else
{
if(USART_RX1_BUF[0]!=0)
num1 ++;
else
num1 = 0;
}
}
}