学习是一个简单的过程,只要有善于发掘的眼睛,总能学到新知识,然而如何坚持不懈的学习却很困难,对我亦如此,生活中有太多的诱惑,最后只想说一句勿忘初心。闲话不多扯,本篇讲诉的是异步串行口的输入输出,串口在外设中属于比较简单的通讯模式,但是在大型项目调试中又十分重要,理解该外设模块对于以后的通讯协议学习以及软件调试都有重要意义。
通讯协议是指双方实体完成通信或服务所必须遵循的规则和约定,对于串口来说,包含波特率,数据位长度,停止位和数据校验位,当stm32芯片和客户端具有相同的协议约定时即能够正确的接收数据,因此串口外设的配置正是这些参数的设定。通过前面章节的学习,以USART1为例,在外设设置之前,我们应该有大致流程:
1. 串口外设外设和其占用的GPIO端口都要配置,且占用的GPIO端口为PA9(USART1_TX), PA10(USART1_RX).
2. 外设对应时钟都要配置,且要在初始化外设和GPIO端口配置之前
3. USART外设的配置主要是协议相关参数配置
串口外设配置
至于GPIO端口配置的参数参考<stm32F系列微控制器参考手册>(以后以手册简称)110页表21如下图
至于外设所在时钟区域,请参考RCC章节,如此外设的初始化如下:
头文件定义:
#define RCC_USART1 RCC_APB2Periph_GPIOA |RCC_APB2Periph_USART1 |RCC_APB2Periph_AFIO #define USART1_RX GPIOA #define USART1_TX GPIOA #define USART1_RX_Pin GPIO_Pin_10 #define USART1_TX_Pin GPIO_Pin_9
初始化代码:
USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; USART_DeInit(USART1); RCC_APB2PeriphClockCmd(RCC_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin = USART1_TX_Pin; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //配置串口1输出端为复用推挽输出 GPIO_Init(USART1_TX,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = USART1_RX_Pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //配置串口1输入端为浮空输入 GPIO_Init(USART1_RX, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 115200; //设置串口波特率为9600 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; //串口1启动输入输出 USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); //使能串口
如此,便完成了外设的初始化。
这里有几个知识点要讲:
1.波特率 每秒发送的bit个数。对于一定长度的数据,在已知配置的情况下传输时间计算:
(以2k数据b,每帧8位数据,1位停止位,波特率9600,传输完成花费时间)
T = 2*1024*(8+1)/9600*8 = 0.24s
虽然用库函数可以直接设置波特率,但stm32的波特率如何通过寄存器计算还是很重要的,参考公式配置USART->BRR(手册542页):
公式:
其中fck为串口所在区域外设时钟,如USART即为PCLK2时钟,USARTDIV即为要设置参数:
以波特率115200,PCLK等于系统时钟SYSTICk(48MHZ)算,USARTDIV设置为48M/(16*115200),转成二进制为0x1a0
2. 奇偶校验位
因为奇偶校验位的参与,接收到的数据帧有以下四种格式:
因此客户端接收时也要数据位要设定为实际数据长度,不然接收到数据会是乱码。
3. 硬件流控制
用于数据流控制,通讯的双方由此交换是否停止后继续接收信息,避免因处理数据速度问题而出现的缓存溢出,导致数据的丢失。
CTS:只有CTS输入信号有效(低电平)时才能发送数据。如果在数据传输的过程中,CTS信号变成无效,那么发完这个数据后,传输就停止下来。如果当CTS为无效时,向数据寄 存器里写数据,则要等到CTS有效时才会发送这个数据。
RTS:只有接收缓冲区内有空余的空间时才请求下一个数据。当前数据发送完成后,发送操作就需要暂停下来。如果可以接收数据了,将RTS输出置为有效(拉至低电平)。
ps:本例中没有使用,具体详情参考手册537页
串口输入输出实现
串口的输出是通过printf函数的,其包含在stdio.h头文件内部。此外printf还需要retarget处理,具体代码如下:
retarget.h头文件添加:
#ifdef __GNUC__ /* With GCC/RAISONANCE, small printf ( option LD Linker->Libraries->Small printf set to ‘Yes‘) calls __io_putchar() */ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif /* __GNUC__ */
retarget.c文件添加:
PUTCHAR_PROTOTYPE { /*将1字节数据发往串口寄存器 */ USART_SendData(USART1, (uint8_t) ch); /*等待传输结束*/ while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {} return ch; }
之后就可以用printf函数发送数据了,至于接收则采用缓存模式,将接受数据存储到数组中,接收到\n时结束并发送:
u8 i = 0; do { while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET) { } USART_STORE[i] = USART1->DR; if(USART_STORE[i] == ‘\n‘) { break; } i++; if(i >= USART_ISL) { i = 0; printf("you input is overlength"); break; } }while(1); if(i < USART_ISL) { //printf("you input is "); printf("\n%s",USART_STORE); }
如此就完成了USART外设的配置和输入输出轮询实现。
具体代码参考:http://files.cnblogs.com/files/zc110747/2.1-USART%28%E8%BD%AE%E8%AF%A2%E6%A8%A1%E5%BC%8F%29.7z