如果你看过《STM32的中文手册》,你会发现STM32的串口是非常强大的,不仅支持最基本的通用串口同步,异步通讯,还具有LAN总线的功能(局域互联网),IRDA功能(红外通讯),SmartCard功能
异步串口通讯协议:
这里介绍的是串口最基本,最常用的方式,全双工,异步通讯方式.
通过串口的通讯协议,我们知道要配置串口通讯,至少要配置几个参数:
字长(一次传送的数据长度);
波特率(每秒传输的数据位数);
奇偶校验位;
还有停止位;
当然,在我们的ST库里面,肯定有一个串口初始化结构体啦,这个结构体肯定有几个成员是来存储这些控制参数的!!!!!
*串口线:*
要实现基本的全双工异步通讯,只要3条线,分别为Rx、Tx、和GND.
串口线主要分两种,直通线(平行线)和交叉线.
交叉线:
假如PC与板子之间要实现全双工串口通讯,必然是PC的Tx针脚要连接到板子的Rx针脚,而PC的Rx针脚则要连接至板子的Tx针脚了.由于板子和pc的串口接法是相同的,就要使用交叉线来连接了.
直通线(平行线):
如果有的开发板是 Tx 连接至
DB9的第2针脚,而Rx连接至第3针脚,其实就是与PC接法是相反的,这样的板子与 PC 通讯就需要使用直通线了.
推荐使用PC接法(交叉线):
假如使用非 PC 接法,由于板子与 PC 的接法相反,通讯就要使用直通线,但两个板子之间想要进行串口通讯时,由于接法相同,就要使用交叉线.如果使用PC接法,板子与PC之间接法相同,通讯使用交叉线,两个相同板子之间接法也相同,通讯也是使用交叉线.
串口工作工程:
这个架构图图看起来好像很复杂,但其实我们做软件的只要大概了解串口发送过程就行!!!
从下到上,串口外设主要是由三部分组成,分别是波特率控制部分,收发控制部分,数据存储转移部分.
波特率控制:
波特率,即每秒传输的二进制位数, 用 b/s (bps)表示,通过对时钟的控制可以改变波特率.在配置波特率时,我们向波特比率寄存器 USART_BRR 写入参数,修改了串口时钟的分频值 USARTDIV. USART_BRR 寄存器包括两部分,分别是 DIV_Mantissa(USARTDIV 的整数部分)和 DIVFraction(USARTDIV的小数)部分,
最终,计算公式为USARTDIV=DIV_Mantissa+(DIVFraction/16).
USARTDIV 是对串口外设的时钟源进行分频的,对于 USART1,由于它是挂载在 APB2 总线上的,所以它的时钟源为 fPCLK2;而 USART2、 3 挂载在APB1 上,时钟源则为 fPCLK1,串口的时钟源经过 USARTDIV 分频后分别输出作为发送器时钟及接收器时钟,控制发送和接收的时序.
收发控制:
围绕着发送器和接收器控制部分,有好多个寄存器: CR1、 CR2、 CR3、SR,即 USART 的三个控制寄存器(Control Register)及一个状态寄存器(StatusRegister).通过向寄存器写入各种控制参数,来控制发送和接收,如奇偶校验位,停止位等,还包括对 USART 中断的控制;串口的状态在任何时候都可以从状态寄存器中查询得到.
(具体的控制和状态检查,都是使用库函数来实现的,所以就不具体分析这些寄存器位啦.)
数据存储转移部分:
收发控制器根据我们的寄存器配置,对数据存储转移部分的移位寄存器进行控制.
当我们需要发送数据时,内核或 DMA 外设把数据从内存(变量)写入到发送数据寄存器 TDR 后, 发送控制器将适时地自动把数据从 TDR 加载到发送移位寄存器,然后通过串口线 Tx,把数据一位一位地发送出去,
在数据从 TDR 转移到移位寄存器时,会产生发送寄存器TDR 已空事件 TXE,当数据从移位寄存器全部发送出去时,会产生数据发送完成事件 TC,这些事件可以在状态寄存器中查询到.
(而接收数据则是一个逆过程,数据从串口线 Rx 一位一位地输入到接收移位寄存器,然后自动地转移到接收数据寄存器 RDR,最后用内核指令或 DMA读取到内存(变量)中.)
例程解析:
main函数:
int main(void)
{
/* USART1 config 115200 8-N-1 */
USART1_Config();
printf(USART1, “\r\n This is a USART1 test \r\n”);
printf(USART1, “\r\n(“DATE ” -” TIME “) \r\n”);
while(1)
{}
}
这里main函数首先调用了USART1_Config(),接着就打印了几条信息.
USART1_Config()函数:
void USART1_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIO //注意这里是GPIO复用
A, ENABLE);
//TX脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//RX脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置UART
USART_InitStructure.USART_BaudRate = 115200; //设置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字节长
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位
USART_InitStructure.USART_Parity = USART_Parity_No ; //无校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlow //不采用硬件流控制
Control_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //配置串口模式 全双工
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
这个函数做了以下的工作:
1. 使能了串口 1 的时钟.
2. 配置好了 usart1 的 I/O.
3. 配置好了 usart1 的工作模式,具体为波特率为 115200 、 8 个数据位、 1个停止位、无硬件流控制.
(即 115200 8-N-1)
RCC_APB2PeriphClockCmd()
打开了GPIOA和USART1的时钟
这是因为使用了 GPIOA 的 PA9 和 PA10 的默认复用USART1 的功能,在使用复用功能的时候,要开启相应的功能时钟USART1.
GPIO_InitStructure配置
GPIO的初始化
通过《 STM32F103CDE 增强型系列数据手册》的引脚功能定义中查询到为什么是PA9和PA10用作串口的Tx和Rx,而不是其它GPIO引脚
选定了这两个引脚,PA9作为TX,PA10作为RX.
Tx 为发送端, 输出引脚,而且现在 GPIO 是使用复用功能,所以要把它配置为复用推挽输出(GPIO_Mode_AF_PP)
Rx 引脚为接收端, 输入引脚,所以配置为浮空输入模式 GPIO_Mode_IN_FLOATING.
(在使用复用功能的时候如果对GPIO的模式不太确定的话,可以从《STM32参考手册》的GPIO章节查询)
USART的初始化:(根据通讯协议)
USART_InitStructure.USART_BaudRate = 115200;
波特率设置,利用库函数,我们可以直接这样配置波特率,而不需要自行计算 USARTDIV 的分频因子.在这里把串口的波特率设置为 115200,也可以设置为 9600 等常用的波特率,在《 STM32 参考手册》中列举了一些常用的
波特率设置及其误差.如果配置成 9600, 那么在和 PC 通讯的时候,也应把 PC 的串口传输波特率设置为相同的 9600.通讯协议要求两个通讯器件之间的波特率、字长、停止位奇偶校验位都相同.
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
配置串口传输的子长,也可以设置为9位.
USART_InitStructure.USART_StopBits = USART_StopBits_1;
配置停止位.把通讯协议中的停止位设置为 1 位.
USART_InitStructure.USART_Parity = USART_Parity_No ;
由于是个例程,所以就不弄校验位了.
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlow Control_None;
配置硬件控制流,不采用硬件流
硬件流:
在 STM32 的很多外设都具有硬件流的功能,其功能表现为:当外设硬件处于准备好的状态时,硬件启动自动控制,而不需要软件再进行干预.
*在串口这个外设的硬件流具体表现为:
使用串口的 RTS (Request toSend) 和 CTS(Clear to Send) 针脚,当串口已经准备好接收新数据时,由硬件流自动把 RTS 针拉低(向外表示可接收数据);在发送数据前,由硬件流自动检查 CTS 针是否为低(表示是否可以发送数据),再进行发送.
(例程中没有使用到 CTS 和 RTS,所以不采用硬件流控制.)
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
配置串口的模式.为了配置双线全双工通讯,需要把Rx和Tx模式都开启.
printf()函数重定向:
在main函数中,配置好USART1后,就往终端打印信息.
要使printf()函数工作的话,我们需要把printf()重新定向到串口中.
重定向:是指用户可以自己重写C的库函数,当连接器检查到用户编写了与C库函数相同名字的函数时,优先采用用户编写的函数,这样用户就可以实现对库的修改了.
(因为printf()在C标准库函数中实质是一个宏,最终是调用了fputc()这个函数的.)
int fputc(int ch,FILE *f)
{
USART_SendData(USART1,(unsigned char)ch);
while( USART_GetFlagStatus(USART1,USART_FLAF_TC)!=RESET);
return(ch);
}
这里是调用了两个库函数USART_SendData()和USART_GetFlagStatus()
(具体可以在keil环境下跟踪查看函数实现)
重定向时,把fputc()的形参ch作为串口要发送的数据,也就是说,当使用当使用printf(),它调用这个 fputc()函数时,然后使用 ST 库的串口发送函数 USART_SendData(),把数据转移到发送数据寄存器 TDR,触发串口向 PC 发送一个相应的数据.
调用完USART_SendData()后,要使用while( USART_GetFlagStatus(USART1,USART_FLAG_TC)!= SET)语句不停地检查串口发送是否完成的标志位TC,一直检测到标志为完成,才进入一下步的操作,避免出错.
(在这段 while 的循环检测的延时中,串口外设已经由发送控制器根据我们的配置把数据从移位寄存器一位一位地通过串口线 Tx 发送出去了)
fgetc()用于接收的重定向(scanf())
int fgetc(FILE *f)
{
/* 等待串口1输入数据 */
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
这里就是在循环检测状态,一旦有数据就调用USART_ReceiveData()库函数
PS:在使用C标准输出库函数时,切记要把stdio.h这个头文件添加进来,如果是在keil环境下,还需要设置中选择Target选项,勾选Use MicroLIB(微库)
(这个微库是keil MDK为嵌入式应用定做的C库)
PS:关于printf()
受缓冲区大小的影响,有时候在用它打印的时候程序会发生莫名奇妙的错误,而实际上就是由于使用printf()这个函数引起的,其优点就是这种情况很少见且支持的格式较多.