stm32 USART 串口通信[操作寄存器+库函数]

串口通信虽然在如今的电脑上使用的越来越少,因为其在通信速率,距离已经不适应pc的要求,取而代之的是USB口。但是在嵌入式领域,USART仍然广泛运用着。 
 
stm32的最多可以提供5路串口,有分数波特率发生器、支持同步单线通信和半双工单线通信、具有DMA等。使用USART时,stm32的I/O口经RS232电平转换电路 和电脑的串口连接。 
 
串口使用只需要开始串口时钟,设置相应的I/O口模式,配置波特率、数据位长度、奇偶校验位等信息就可以使用了。
 
我使用了三种方式使用串口通信,只可以开启一项: 
  • USART通过使用printf()函数发送信息; 
  • USART和上位机通信,接收到数据后原数据输出; 
  • USART主动发送数据。 
 
操作寄存器
    串口的复位是通过配置APB2RSTR 寄存器的第14位,当外设出现故障时,可以通过复位寄存器复位,在系统初始化时,都会执行复位操作。
 
    串口的波特率设置是在USART_BRR寄存器上, 实际上这个寄存器配置的是波特比率的分频触发因子的值,波特率是一秒钟通过的字符,而波特比率是一秒钟通过的二进制位数,所以设置了波特率需要经过一段算法处理 ,得出特定时钟下,实现这个波特率的,时钟分频值。
 
    串口控制寄存器有3个 USART_CR1~3,常用到的就是USART_CR1,各位描述如下:
usart-cr.png
   UE:USART使能 (USART enable)
   M:字长 (Word length) 该位定义了数据字的长度, 0:一个起始位,8个数据位,n个停止位;
1:一个起始位,9个数据位,n个停止位。 n由USART_CR2中设置。
   WAKE:唤醒的方法 (Wakeup method)  0:被空闲总线唤醒;  1:被地址标记唤醒。
   PCE:检验控制使能 (Parity control enable)
   PS:校验选择 (Parity selection)  0:偶校验;1:奇校验。
   PEIE:PE中断使能 (PE interrupt enable)
  TXEIE:发送缓冲区空中断使能 (TXE interrupt enable)
  TCIE:发送完成中断使能 (Transmission complete interrupt enable)
  RXNEIE:接收缓冲区非空中断使能 (RXNE interrupt enable)
  IDLEIE:IDLE中断使能 (IDLE interrupt enable)  0:禁止产生中断; 1:当USART_SR中的IDLE为’1’时,产生USART中断。
 TE:发送使能 (Transmitter enable)
 RE:接收使能 (Receiver enable)
 RWU:接收唤醒 (Receiver wakeup)  0:接收器处于正常工作模式; 1:接收器处于静默模式。
注意:1.在把USART置于静默模式(设置RWU位)之前,USART要已经先接收了一个数据字节。否则在静默模式下,不能被空闲总线检测唤醒。
2.当配置成地址标记检测唤醒(WAKE位=1),在RXNE位被置位时,不能用软件修改RWU位。
 SBK:发送断开帧 (Send break)
 
  数据的发送和接收是在USART_DR来实现的,这是一个双寄存器,包含了TDR和RDR,当向该寄存器写入数据时,串口就会自动发送数据;当收到数据时,也是存在该寄存器内中,可以直接读出。该寄存器只有低9位有效(8:0),其他位都是保留的。

 
 串口状态是通过状态寄存器USART_SR读取的,各位描述如下:
usart_sr.png
    TXE:发送数据寄存器空 (Transmit data register empty)
        当TDR寄存器中的数据被硬件转移到移位寄存器的时候,该位被硬件置位。如果USART_CR1寄存器中的TXEIE为1,则产生中断。对USART_DR的写操作,将该位清零。
        0:数据还没有被转移到移位寄存器;
        1:数据已经被转移到移位寄存器。
    TC:发送完成 (Transmission complete)
    当包含有数据的一帧发送完成后,并且TXE=1时,由硬件将该位置’1’。如果USART_CR1中的TCIE为’1’,则产生中断。由软件序列清除该位(先读USART_SR,然后写入USART_DR)。TC位也可以通过写入’0’来清除,只有在多缓存通讯中才推荐这种清除程序。
    RXNE:读数据寄存器非空 (Read data register not empty)
    当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位,表示已经接收到了数据。如果USART_CR1寄存器中的RXNEIE为1,则产生中断。对USART_DR的读操作可以将该位清零。RXNE位也可以通过写入0来清除,只有在多缓存通讯中才推荐这种清除程序。
 
直接操作寄存器代码如下:(system.h 和 stm32f10x_it.h 等相关代码参照  stm32 直接操作寄存器开发环境配置
 
User/main.c
01 #include <stm32f10x_lib.h>    
02 #include "system.h"
03 #include "usart.h"
04 #include "stdio.h"
05  
06 #define  PRINTF_ON 0            //设置printf() 输出
07 #define  RECEIVE_SEND_BACK 0   //设置接收信息后原文发送
08 #define  SEND_WORDS        1    //设置发送字符串
09  
10 void Gpio_Init(void);
11  
12 vu8 RxBuffer[]="\r\n i will success finally..\r\n";
13  
14 int main(void)
15 {          
16     u32 i=0;     
17  
18     Rcc_Init(9);             //系统时钟设置
19  
20     Usart1_Init(72,9600);   //设置系统时钟和波特率
21  
22     #if RECEIVE_SEND_BACK
23         Nvic_Init(3,3,USART1_IRQChannel,2);         
24     #endif
25  
26     Gpio_Init();   //配置串口寄存器时可能导致IO口输出,最后配置最好
27  
28     #if PRINTF_ON
29         printf("\r\nThanks god ,i am success now..\r\n");
30     #elif SEND_WORDS
31         while(sizeof(RxBuffer)>=i)
32         {
33             USART1->DR = RxBuffer[i];
34             while((USART1->SR&0x40) == 0);
35             //USART1->SR &= 0x1F;     //清除TC中断
36             i++;
37         }
38      #endif
39       
40       
41      while(1);
42 }
43  
44  
45 void Gpio_Init(void)
46 {
47     RCC->APB2ENR|=1<<2;    //使能PORTA时钟     
48  
49     //GPIOA->CRL&=0x0000FFFF; // PA0~3设置为浮空输入,PA4~7设置为推挽输出
50     //GPIOA->CRL|=0x33334444;
51  
52     //USART1 串口I/O设置
53  
54     GPIOA -> CRH&=0xFFFFF00F;   //设置USART1 的Tx(PA.9)为第二功能推挽,50MHz;Rx(PA.10)为浮空输入
55     GPIOA -> CRH|=0x000008B0;     
56 }
User/stm32f10x_it.c
01 #include "stm32f10x_it.h"
02  
03 void USART1_IRQHandler(void)
04 {
05     vu8 data;
06  
07     if(USART1->SR &(1<<5))  //接收到数据
08     {
09         data = USART1->DR;
10         USART1->DR = data;
11         while((USART1->SR&0x40) == 0);
12     }
13 }
Library/src/usart.c
01 #include <stm32f10x_lib.h>    
02 #include "usart.h"
03  
04 /**
05  *  初始化 USART1的控制寄存器、波特比率寄存器、开启时钟
06  *  注意:未配置I/O口功能,需设置USART1 的Tx(PA.9)为第二功能推挽,50MHz;Rx(PA.10)为浮空输入
07  */
08  
09 void Usart1_Init(u32 clk,u32 baudRate)   //参数说明: clk 单位为Mhz
10 {
11     //USART1->BBR 波特比率设置
12     float temp;
13  
14     u16 BRR_Value;
15     u16 BRR_Mantissa;
16     u16 BRR_Fraction;
17  
18     temp = (float)(clk*1000000)/(baudRate*16);   //得到USART 分频除法因子(每个字符16位,乘16得到每秒通过的字符数)
19  
20     BRR_Mantissa = temp;    //得到BRR[15:4]整数部分
21  
22     BRR_Fraction = (temp - BRR_Mantissa)*16;  //得到BRR[3:0]小数部分
23  
24     BRR_Mantissa<<=4;
25  
26     BRR_Value = BRR_Mantissa + BRR_Fraction;  //拼接整数和小数部分
27      
28     RCC->APB2ENR|=1<<14;    //使能串口时钟
29  
30     RCC->APB2RSTR |= 1<<14; //复位串口1
31     RCC->APB2RSTR &= ~(1<<14); //初始化串口复位寄存器位
32  
33     USART1->BRR = BRR_Value;  //设置波特比率
34  
35     USART1->CR1 |= 1<<8;       //PEIE中断使能
36     USART1->CR1 |= 1<<5;       //RXNEIE,接收完成中断使能
37     //USART1->CR1 |= 1<<6;     //TC,发送完成中断使能
38  
39     USART1->CR1 |= 0x200C;  //1个停止位,无检验位
40  
41              
42 }
43  
44  
45 #if  PRINTF_OUT
46 #pragma import(__use_no_semihosting)            
47 //标准库需要的支持函数                
48 struct __FILE
49 {
50     int handle;
51     /* Whatever you require here. If the only file you are using is */
52     /* standard output using printf() for debugging, no file handling */
53     /* is required. */  
54 };
55 /* FILE is typedef’ d in stdio.h. */
56 FILE __stdout;      
57 //定义_sys_exit()以避免使用半主机模式   
58 _sys_exit(int x)
59 {
60     x = x;
61 }
62 //重定义fputc函数
63 int fputc(int ch, FILE *f)
64 {     
65     USART1->DR = (u8) ch;     
66     while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
67     return ch;
68 }
69 #endif
Library/inc/usart.h
 
01 #include <stm32f10x_lib.h>    
02  
03 #define PRINTF_OUT 1   //使用printf()方式输出,使用这种方式会增大hex文件大小,所以不适用这种方式输出就置0
04  
05 #if PRINTF_OUT
06     #include "stdio.h"
07 #endif
08  
09  
10 void Usart1_Init(u32 clk,u32 baudRate);
在MDK下 将上述文件添加到工程对应目录下即可:
QQ截图20120702120304.png
 
库函数操作
 
在使用printf()串口输出 MDK 必须配置为User Micro LIB 。即为动态编译,可以减小 hex文件的带下  减小flash使用空间。
 
代码如下:
001 #include "stm32f10x.h"
002 #include "stdio.h"
003  
004 #define  PRINTF_ON 0            //设置printf() 输出
005 #define  RECEIVE_SEND_BACK 0    //设置接收信息后原文发送
006 #define  SEND_WORDS        1    //设置发送字符串
007  
008 vu8 RxBuffer[]="\r\n i will success finally..\r\n";      //存储串口传入的数据,自定义字符串
009 vu32 count=0;
010  
011 void RCC_Configuration(void);
012 void GPIO_Configuration(void);
013 void USART_Configuration(void);
014 void delay(vu32 x);
015  
016  
017 int main(void)
018 {
019     RCC_Configuration();
020     GPIO_Configuration();
021     USART_Configuration();
022     while(1)
023     {  
024             #if PRINTF_ON
025                 printf("\r\nThanks god ,i am success now..\r\n");
026             #elif RECEIVE_SEND_BACK
027                 if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
028                 {
029                     RxBuffer[count] = USART_ReceiveData(USART1);
030                     USART_SendData(USART1,RxBuffer[count]);
031                     delay(5);  
032                 }
033             #elif SEND_WORDS
034  
035                 USART_SendData(USART1,RxBuffer[count]);
036      
037                 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);      
038                 //while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);    //检查是否发送完成的另一种方法
039  
040                 count++;
041                  
042                 if(sizeof(RxBuffer)<count)
043                 {
044                     count=0;
045                     break;    //只发送一次 
046                 }
047                          
048             #endif
049     }
050  
051 }
052  
053  
054 void delay(vu32 x)       //vu32 1us一次
055 {
056     vu32 a=100*x/7;     
057     while(--x);         
058 }
059  
060    
061 void GPIO_Configuration(void)
062 {
063     GPIO_InitTypeDef GPIO_InitStructure;
064      
065     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
066     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
067     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        
068     GPIO_Init(GPIOA , &GPIO_InitStructure);
069  
070     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
071     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;          
072     GPIO_Init(GPIOA , &GPIO_InitStructure);
073 }
074  
075  
076 void RCC_Configuration(void)
077 {
078     /* 定义枚举类型变量 HSEStartUpStatus */
079     ErrorStatus HSEStartUpStatus;
080  
081     /* 复位系统时钟设置*/
082