串口通信虽然在如今的电脑上使用的越来越少,因为其在通信速率,距离已经不适应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,各位描述如下:

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读取的,各位描述如下:

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下 将上述文件添加到工程对应目录下即可:

库函数操作
在使用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 | RCC_DeInit(); |
083 | /* 开启HSE*/ |
084 | RCC_HSEConfig(RCC_HSE_ON); |
085 | /* 等待HSE起振并稳定*/ |
086 | HSEStartUpStatus = RCC_WaitForHSEStartUp(); |
087 | /* 判断HSE起是否振成功,是则进入if()内部 */ |
088 | if (HSEStartUpStatus == SUCCESS) |
089 | { |
090 | /* 选择HCLK(AHB)时钟源为SYSCLK 1分频 */ |
091 | RCC_HCLKConfig(RCC_SYSCLK_Div1); |
092 | /* 选择PCLK2时钟源为 HCLK(AHB) 1分频 */ |
093 | RCC_PCLK2Config(RCC_HCLK_Div1); |
094 | /* 选择PCLK1时钟源为 HCLK(AHB) 2分频 */ |
095 | RCC_PCLK1Config(RCC_HCLK_Div2); |
096 | /* 设置FLASH延时周期数为2 */ |
097 | FLASH_SetLatency(FLASH_Latency_2); |
098 | /* 使能FLASH预取缓存 */ |
099 | FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); |
100 | /* 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz * 9 = 72MHz */ |
101 | RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); |
102 | /* 使能PLL */ |
103 | RCC_PLLCmd(ENABLE); |
104 | /* 等待PLL输出稳定 */ |
105 | while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); |
106 | /* 选择SYSCLK时钟源为PLL */ |
107 | RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); |
108 | /* 等待PLL成为SYSCLK时钟源 */ |
109 | while (RCC_GetSYSCLKSource() != 0x08); |
110 | } |
111 | /* 打开APB2总线上的GPIOA时钟*/ |
112 | RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1, ENABLE); |
113 |
114 | //RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE); |
115 | |
116 | } |
117 |
118 | |
119 | void USART_Configuration( void ) |
120 | { |
121 | USART_InitTypeDef USART_InitStructure; |
122 | USART_ClockInitTypeDef USART_ClockInitStructure; |
123 |
124 | USART_ClockInitStructure.USART_Clock = USART_Clock_Disable; |
125 | USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low; |
126 | USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge; |
127 | USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable; |
128 | USART_ClockInit(USART1 , &USART_ClockInitStructure); |
129 |
130 | USART_InitStructure.USART_BaudRate = 9600; |
131 | USART_InitStructure.USART_WordLength = USART_WordLength_8b; |
132 | USART_InitStructure.USART_StopBits = USART_StopBits_1; |
133 | USART_InitStructure.USART_Parity = USART_Parity_No; |
134 | USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; |
135 | USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; |
136 | USART_Init(USART1,&USART_InitStructure); |
137 |
138 | USART_Cmd(USART1,ENABLE); |
139 | } |
140 |
141 |
142 |
143 | #if PRINTF_ON |
144 |
145 | int fputc ( int ch, FILE *f) |
146 | { |
147 | USART_SendData(USART1,(u8) ch); |
148 | while (USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET); |
149 | return ch; |
150 | } |
151 |
152 | #endif |