STM32串口
0赞
发表于 2013/7/18 22:50:52
阅读(26161)
STM32
的串口是相当丰富的,功能也很强劲。最多可提供5
路串口(MiniSTM32
使用的是STM32F103RBT6
,具有3
个串口),有分数波特率发生器、支持单线光通信和半双工单线通讯、支持LIN
、智能卡协议和IrDASIR ENDEC
规范(仅串口3
支持)、具有DMA
等。
串口最基本的设置,就是波特率的设置。STM32 的串口使用起来还是蛮简单的,只要你开启了串口时钟,并设置相应IO 口的模式,然后配置一下波特率,数据位长度,奇偶校验位等信息,就可以使用了。下面,我们就简单介绍下这几个与串口基本配置直接相关的寄存器。
1 ,串口时钟使能。串口作为STM32 的一个外设,其时钟由外设时钟使能寄存器控制,这里我们使用的串口1 是在APB2ENR 寄存器的第14 位。APB2ENR 寄存器在之前已经介绍过了,这里不再介绍。只是说明一点,就是除了串口1 的时钟使能在APB2ENR 寄存器,其他串口的时钟使能位都在APB1ENR 。
2 ,串口复位。当外设出现异常的时候可以通过复位寄存器里面的对应位设置,实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。一般在系统刚开始配置外设的时候,都会先执行复位该外设的操作。串口1 的复位是通过配置APB2RSTR 寄存器的第14 位来实现的。APB2RSTR 寄存器的各位描述如下:
串口最基本的设置,就是波特率的设置。STM32 的串口使用起来还是蛮简单的,只要你开启了串口时钟,并设置相应IO 口的模式,然后配置一下波特率,数据位长度,奇偶校验位等信息,就可以使用了。下面,我们就简单介绍下这几个与串口基本配置直接相关的寄存器。
1 ,串口时钟使能。串口作为STM32 的一个外设,其时钟由外设时钟使能寄存器控制,这里我们使用的串口1 是在APB2ENR 寄存器的第14 位。APB2ENR 寄存器在之前已经介绍过了,这里不再介绍。只是说明一点,就是除了串口1 的时钟使能在APB2ENR 寄存器,其他串口的时钟使能位都在APB1ENR 。
2 ,串口复位。当外设出现异常的时候可以通过复位寄存器里面的对应位设置,实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。一般在系统刚开始配置外设的时候,都会先执行复位该外设的操作。串口1 的复位是通过配置APB2RSTR 寄存器的第14 位来实现的。APB2RSTR 寄存器的各位描述如下:
图3.3.1.1寄存器APB2RSTR各位描述
从上图可知串口1
的复位设置位在APB2RSTR
的第14
位。通过向该位写1
复位串口1
,写0
结束复位。其他串口的复位位在APB1RSTR
里面。
3 ,串口波特率设置。每个串口都有一个自己独立的波特率寄存器USART_BRR ,通过设置该寄存器达到配置不同波特率的目的。该寄存器的各位描述如下:
3 ,串口波特率设置。每个串口都有一个自己独立的波特率寄存器USART_BRR ,通过设置该寄存器达到配置不同波特率的目的。该寄存器的各位描述如下:
图3.3.1.2寄存器USART_BRR各位描述
前面提到STM32
的分数波特率概念,其实就是在这个寄存器里面体现的。最低4
位用来存放小数部分DIV_Fraction
,[15:4]
这12
位用来存放整数部分DIV_Mantissa
。高16
位未使用。这里波特率的计算通过如下公式计算:
这里的fpclkx (x=1 、2 )是给外设的时钟(PCLK1 用于串口2 、3 、4 、5 ,PCLK2 用于串口1 ),USARTDIV 是一个无符号的定点数,它的值可以有串口的BRR 寄存器值得到。而我们更关心的是如何从USARTDIV 的值得到USART_BRR 的值,因为一般我们知道的是波特率,和PCLKx 的时钟,要求的就是USART_BRR 的值。
下面我们来介绍如何通过USARTDIV 得到串口USART_BRR 寄存器的值,假设我们的串口1 要设置为9600 的波特率,而PCLK2 的时钟为72M 。这样,我们根据上面的公式有:USARTDIV=72000000/9600*16=468.75
那么得到:DIV_Fraction=16*0.75=12=0X0C; DIV_Mantissa= 468=0X1D4;
这样,我们就得到了USART1->BRR 的值为0X1D4C 。只要设置串口1 的BRR 寄存器值为0X1D4C 就可以得到9600 的波特率。
4 ,串口控制。STM32 的每个串口都有3 个控制寄存器USART_CR1~3 ,串口的很多配置都是通过这3 个寄存器来设置的。这里我们只要用到USART_CR1 就可以实现我们的功能了 。
这里的fpclkx (x=1 、2 )是给外设的时钟(PCLK1 用于串口2 、3 、4 、5 ,PCLK2 用于串口1 ),USARTDIV 是一个无符号的定点数,它的值可以有串口的BRR 寄存器值得到。而我们更关心的是如何从USARTDIV 的值得到USART_BRR 的值,因为一般我们知道的是波特率,和PCLKx 的时钟,要求的就是USART_BRR 的值。
下面我们来介绍如何通过USARTDIV 得到串口USART_BRR 寄存器的值,假设我们的串口1 要设置为9600 的波特率,而PCLK2 的时钟为72M 。这样,我们根据上面的公式有:USARTDIV=72000000/9600*16=468.75
那么得到:DIV_Fraction=16*0.75=12=0X0C; DIV_Mantissa= 468=0X1D4;
这样,我们就得到了USART1->BRR 的值为0X1D4C 。只要设置串口1 的BRR 寄存器值为0X1D4C 就可以得到9600 的波特率。
4 ,串口控制。STM32 的每个串口都有3 个控制寄存器USART_CR1~3 ,串口的很多配置都是通过这3 个寄存器来设置的。这里我们只要用到USART_CR1 就可以实现我们的功能了 。
BIT 13: 串口功能;
BIT 12: MODE,字长。0: 1个开始位,8个数据位,1位停止位(默认);1: 1个开始位,9位数据位,1位停止位(默认);
*注意:停止位的长度可在USART_CR2寄存器中设置。
BIT 11: WAKE 唤醒功能
BIT 10: 校检使能位,当激活奇偶校验功能时,置位该位将自动往要传输数据的高位字节处插入就校验位。
BIT 09: Parity Selection,0:偶校验;1:奇校验。
BIT 08: PE Interrupt Enable
BIT 07: 发送缓冲区空中断使能位
BIT 06: 发送完成中断使能位
BIT 05: 接收缓冲区非空中断使能位
BIT 04: Idle Interrupt Enable
BIT 03: Transfer Enable
BIT 02: Receive Enable
BIT 01: Receiver Wakeup
BIT 00: Send Break
5 ,数据发送与接收。STM32 的发送与接收是通过数据寄存器USART_DR 来实现的,这是一个双寄存器,包含了TDR 和RDR 。当向该寄存器写数据的时候,串口就会自动发送,当收到收据的时候,也是存在该寄存器内。该寄存器的各位描述如下:
图3.3.1.3寄存器USART_DR各位描述
可以看出,虽然是一个32
位寄存器,但是只用了低9
位(DR[8:0]
),其他都是保留。
DR[8:0] 为串口数据,包含了发送或接收的数据。由于它是由两个寄存器组成的,一个给发送用(TDR) ,一个给接收用(RDR) ,该寄存器兼具读和写的功能。TDR 寄存器提供了内部总线和输出移位寄存器之间的并行接口。RDR 寄存器提供了输入移位寄存器和内部总线之间的并行接口。
当使能校验位(USART_CR1 种PCE 位被置位) 进行发送时,写到MSB 的值( 根据数据的长度不同,MSB 是第7 位或者第8 位) 会被后来的校验位该取代。
当使能校验位进行接收时,读到的MSB 位是接收到的校验位。
6 ,串口状态。串口的状态可以通过状态寄存器USART_SR 读取。USART_SR 的各位描述如下:
DR[8:0] 为串口数据,包含了发送或接收的数据。由于它是由两个寄存器组成的,一个给发送用(TDR) ,一个给接收用(RDR) ,该寄存器兼具读和写的功能。TDR 寄存器提供了内部总线和输出移位寄存器之间的并行接口。RDR 寄存器提供了输入移位寄存器和内部总线之间的并行接口。
当使能校验位(USART_CR1 种PCE 位被置位) 进行发送时,写到MSB 的值( 根据数据的长度不同,MSB 是第7 位或者第8 位) 会被后来的校验位该取代。
当使能校验位进行接收时,读到的MSB 位是接收到的校验位。
6 ,串口状态。串口的状态可以通过状态寄存器USART_SR 读取。USART_SR 的各位描述如下:
图3.3.1.4寄存器USART_SR各位描述
这里我们关注一下两个位,第5
、6
位RXNE
和TC
。
RXNE (读数据寄存器非空),当该位被置1 的时候,就是提示已经有数据被接收到了,并且可以读出来了。这时候我们要做的就是尽快去读取USART_DR ,通过读USART_DR 可以将该位清零,也可以向该位写0 ,直接清除。
TC (发送完成),当该位被职位的时候,表示USART_DR 内的数据已经被发送完成了。如果设置了这个位的中断,则会产生中断。该位也有两种清零方式:1 )读USART_SR ,写USART_DR 。2 )直接向该位写0 。
// 初始化IO 串口1
//pclk2 CLK2 时钟频率(Mhz)
//bound: 波特率
void uart_init(u32 pclk2 ,u32bound)
{
floattemp;
u16mantissa;
u16fraction;
temp=(float)(pclk2*1000000)/(bound*16);// 得到USARTDIV
mantissa=temp;
// 得到整数部分
fraction=(temp-mantissa)*16;// 得到小数部分
mantissa<<=4;
mantissa+=fraction;
RCC->APB2ENR|=1<<2;
// 使能PORTA 口时钟
RCC->APB2ENR|=1<<14;
// 使能串口时钟
GPIOA->CRH=0X444444B4;//IO 状态设置
RCC->APB2RSTR|=1<<14;
// 复位串口1
RCC->APB2RSTR&=~(1<<14);// 停止复位
// 波特率设置
USART1->BRR=mantissa; // 波特率设置
USART1->CR1|=0X200C;
//1 位停止,无校验位.
#ifdef EN_USART1_RX
// 如果使能了接收
// 使能接收中断
USART1->CR1|=1<<8;
//PE 中断使能
USART1->CR1|=1<<5;
// 接收缓冲区非空中断使能
MY_NVIC_Init(3 ,3 ,USART1_IRQChannel ,2);// 组2 ,最低优先级
#endif
}
从该代码可以看出,其初始化串口的过程,和我们前面介绍的一致先计算得到USART1->BRR 的内容。然后开始初始化串口引脚,接着把USART1 复位,然之后设置波特率和奇偶校验等。
这里需要注意一点,因为我们使用到了串口的中断接收,必须在usart.h 里面定义EN_USART1_RX 。该函数才会配置中断使能,以及开启串口1 的NVIC 中断。这里我们把串口1 中断放在组2 ,优先级设置为组2 里面的最低。
再介绍一下串口1 的中断服务函数USART1_IRQHandler ,该函数的名字不能自己定义了,MDK 已经给每个中断都分配了一个固定的函数名,我们直接用就可以了。具体这些函数的名字是什么,我们可以在MDK 提供的例子里面,找到stm32f10x_it.c ,该文件里面包含了STM32 所有的中断服务函数。USART1_IRQHandler 的代码如下:
void USART1_IRQHandler(void)
{
u8res;
if(USART1->SR&(1<<5))// 接收到数据
{
res=USART1->DR;
if((USART_RX_STA&0x80)==0)// 接收未完成
{
if(USART_RX_STA&0x40)// 接收到了0x0d
{
if(res!=0x0a)USART_RX_STA=0;// 接收错误,重新开始
elseUSART_RX_STA|=0x80;
// 接收完成了
}else // 还没收到0X0D
{
if(res==0x0d)USART_RX_STA|=0x40;
else
{
USART_RX_BUF[USART_RX_STA&0X3F]=res;
USART_RX_STA++;
if(USART_RX_STA>63)USART_RX_STA=0;// 接收数据错误,重新开始接收
}}}}}
该函数的重点就是判断接收是否完成,通过检测是否收到0X0D 、0X0A 的连续2 个字节(回车键)来检测是否结束。当检测到这个结束序列之后,就会置位USART_RX_STA 的最高为来标记已经收到了一次数据。之后等待外部函数清空该位之后才开始第二次接收。所接收的数据全部存放在USART_RX_BUF 里面,一次接收数据不能超过64 个字节,否则被丢弃。
介绍完了这两个函数,我们回到test.c ,在test.c 里面编写如下代码:
#include
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
//Mini STM32 开发板范例代码3
// 串口实验
// 正点原子@ALIENTEK
//2010.5.28
int main(void)
{
u8t;
u8len;
u16times=0;
Stm32_Clock_Init(9);// 系统时钟设置
delay_init(72);
// 延时初始化
uart_init(72 ,9600);
// 串口初始化为9600
LED_Init();
// 初始化与LED 连接的硬件接口
while(1)
{
if(USART_RX_STA&0x80)
{len=USART_RX_STA&0x3f;// 得到此次接收到的数据长度
printf("\n 您发送的消息为:\n");
for(t=0;t{USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);// 等待发送结束}
printf("\n\n");// 插入换行
USART_RX_STA=0;
}else
{times++;
if(times%5000==0)
{printf("\nMiniSTM32 开发板
串口实验\n");
printf(" 正点原子@ALIENTEK\n\n\n");
}
if(times%200==0)printf(" 请输入数据,以回车键结束\n");
if(times%30==0)LED0=!LED0;// 闪烁LED ,提示系统正在运行.
delay_ms(10);}}}
这段代码比较简单,重点看下以下两句:
USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);// 等待发送结束
第一句,其实就是发送一个字节到串口,通过直接操作寄存器来实现的。第二句呢,就是我们在写了一个字节在USART1->DR 之后,要检测这个数据是否已经被发送完成了,通过检测USART1->SR 的第6 位,是否为1 来决定是否可以开始第二个字节的发送。
RXNE (读数据寄存器非空),当该位被置1 的时候,就是提示已经有数据被接收到了,并且可以读出来了。这时候我们要做的就是尽快去读取USART_DR ,通过读USART_DR 可以将该位清零,也可以向该位写0 ,直接清除。
TC (发送完成),当该位被职位的时候,表示USART_DR 内的数据已经被发送完成了。如果设置了这个位的中断,则会产生中断。该位也有两种清零方式:1 )读USART_SR ,写USART_DR 。2 )直接向该位写0 。
// 初始化IO 串口1
//pclk2 CLK2 时钟频率(Mhz)
//bound: 波特率
void uart_init(u32 pclk2 ,u32bound)
{
floattemp;
u16mantissa;
u16fraction;
temp=(float)(pclk2*1000000)/(bound*16);// 得到USARTDIV
mantissa=temp;
// 得到整数部分
fraction=(temp-mantissa)*16;// 得到小数部分
mantissa<<=4;
mantissa+=fraction;
RCC->APB2ENR|=1<<2;
// 使能PORTA 口时钟
RCC->APB2ENR|=1<<14;
// 使能串口时钟
GPIOA->CRH=0X444444B4;//IO 状态设置
RCC->APB2RSTR|=1<<14;
// 复位串口1
RCC->APB2RSTR&=~(1<<14);// 停止复位
// 波特率设置
USART1->BRR=mantissa; // 波特率设置
USART1->CR1|=0X200C;
//1 位停止,无校验位.
#ifdef EN_USART1_RX
// 如果使能了接收
// 使能接收中断
USART1->CR1|=1<<8;
//PE 中断使能
USART1->CR1|=1<<5;
// 接收缓冲区非空中断使能
MY_NVIC_Init(3 ,3 ,USART1_IRQChannel ,2);// 组2 ,最低优先级
#endif
}
从该代码可以看出,其初始化串口的过程,和我们前面介绍的一致先计算得到USART1->BRR 的内容。然后开始初始化串口引脚,接着把USART1 复位,然之后设置波特率和奇偶校验等。
这里需要注意一点,因为我们使用到了串口的中断接收,必须在usart.h 里面定义EN_USART1_RX 。该函数才会配置中断使能,以及开启串口1 的NVIC 中断。这里我们把串口1 中断放在组2 ,优先级设置为组2 里面的最低。
再介绍一下串口1 的中断服务函数USART1_IRQHandler ,该函数的名字不能自己定义了,MDK 已经给每个中断都分配了一个固定的函数名,我们直接用就可以了。具体这些函数的名字是什么,我们可以在MDK 提供的例子里面,找到stm32f10x_it.c ,该文件里面包含了STM32 所有的中断服务函数。USART1_IRQHandler 的代码如下:
void USART1_IRQHandler(void)
{
u8res;
if(USART1->SR&(1<<5))// 接收到数据
{
res=USART1->DR;
if((USART_RX_STA&0x80)==0)// 接收未完成
{
if(USART_RX_STA&0x40)// 接收到了0x0d
{
if(res!=0x0a)USART_RX_STA=0;// 接收错误,重新开始
elseUSART_RX_STA|=0x80;
// 接收完成了
}else // 还没收到0X0D
{
if(res==0x0d)USART_RX_STA|=0x40;
else
{
USART_RX_BUF[USART_RX_STA&0X3F]=res;
USART_RX_STA++;
if(USART_RX_STA>63)USART_RX_STA=0;// 接收数据错误,重新开始接收
}}}}}
该函数的重点就是判断接收是否完成,通过检测是否收到0X0D 、0X0A 的连续2 个字节(回车键)来检测是否结束。当检测到这个结束序列之后,就会置位USART_RX_STA 的最高为来标记已经收到了一次数据。之后等待外部函数清空该位之后才开始第二次接收。所接收的数据全部存放在USART_RX_BUF 里面,一次接收数据不能超过64 个字节,否则被丢弃。
介绍完了这两个函数,我们回到test.c ,在test.c 里面编写如下代码:
#include
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
//Mini STM32 开发板范例代码3
// 串口实验
// 正点原子@ALIENTEK
//2010.5.28
int main(void)
{
u8t;
u8len;
u16times=0;
Stm32_Clock_Init(9);// 系统时钟设置
delay_init(72);
// 延时初始化
uart_init(72 ,9600);
// 串口初始化为9600
LED_Init();
// 初始化与LED 连接的硬件接口
while(1)
{
if(USART_RX_STA&0x80)
{len=USART_RX_STA&0x3f;// 得到此次接收到的数据长度
printf("\n 您发送的消息为:\n");
for(t=0;t
while((USART1->SR&0X40)==0);// 等待发送结束}
printf("\n\n");// 插入换行
USART_RX_STA=0;
}else
{times++;
if(times%5000==0)
{printf("\nMiniSTM32 开发板
串口实验\n");
printf(" 正点原子@ALIENTEK\n\n\n");
}
if(times%200==0)printf(" 请输入数据,以回车键结束\n");
if(times%30==0)LED0=!LED0;// 闪烁LED ,提示系统正在运行.
delay_ms(10);}}}
这段代码比较简单,重点看下以下两句:
USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);// 等待发送结束
第一句,其实就是发送一个字节到串口,通过直接操作寄存器来实现的。第二句呢,就是我们在写了一个字节在USART1->DR 之后,要检测这个数据是否已经被发送完成了,通过检测USART1->SR 的第6 位,是否为1 来决定是否可以开始第二个字节的发送。
寄存器:
1.
串口1的时钟使能在APB2ENR寄存器第14位(其他串口的时钟使能位都在APB1ENR)。
2.
串口1的复位是通过配置APB2RSTR寄存器的第14位(其他串口的复位位在APB1RSTR里面)
3.
波特率寄存器USART_BRR(低16位有效)
4.
控制寄存器USART_CR1~3:低14位有效;位13(UE)是串口使能位,位3(TE)是发送使能位,位2(RE)是接受使能位
5.
数据寄存器USART_DR(低9位有效,兼具读写功能)
6.
状态寄存器USART_SR:5位
RXNE(读数据寄存器非空),6位TC(发送完成)