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,各位描述如下:
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来清除,只有在多缓存通讯中才推荐这种清除程序。
User/main.c
#include <stm32f10x_lib.h>
#include “system.h”
#include “usart.h”
#include “stdio.h”
#define PRINTF_ON 0 //设置printf() 输出
#define RECEIVE_SEND_BACK 0 //设置接收信息后原文发送
#define SEND_WORDS 1 //设置发送字符串
void Gpio_Init(void);
vu8 RxBuffer[]=”\r\n i will success finally..\r\n”;
int main(void)
{
u32 i=0;
Rcc_Init(9); //系统时钟设置
Usart1_Init(72,9600); //设置系统时钟和波特率
#if RECEIVE_SEND_BACK
Nvic_Init(3,3,USART1_IRQChannel,2);
#endif
Gpio_Init(); //配置串口寄存器时可能导致IO口输出,最后配置最好
#if PRINTF_ON
printf(“\r\nThanks god ,i am success now..\r\n”);
#elif SEND_WORDS
while(sizeof(RxBuffer)>=i)
{
USART1->DR = RxBuffer[i];
while((USART1->SR&0x40) == 0);
//USART1->SR &= 0x1F; //清除TC中断
i++;
}
#endif
while(1);
}
void Gpio_Init(void)
{
RCC->APB2ENR|=1<<2; //使能PORTA时钟
//GPIOA->CRL&=0x0000FFFF; // PA0~3设置为浮空输入,PA4~7设置为推挽输出
//GPIOA->CRL|=0x33334444;
//USART1 串口I/O设置
GPIOA -> CRH&=0xFFFFF00F; //设置USART1 的Tx(PA.9)为第二功能推挽,50MHz;Rx(PA.10)为浮空输入
GPIOA -> CRH|=0x000008B0;
}
User/stm32f10x_it.c
#include “stm32f10x_it.h”
void USART1_IRQHandler(void)
{
vu8 data;
if(USART1->SR &(1<<5)) //接收到数据
{
data = USART1->DR;
USART1->DR = data;
while((USART1->SR&0x40) == 0);
}
}
Library/src/usart.c
#include <stm32f10x_lib.h>
#include “usart.h”
/
初始化 USART1的控制寄存器、波特比率寄存器、开启时钟 注意:未配置I/O口功能,需设置USART1 的Tx(PA.9)为第二功能推挽,50MHz;Rx(PA.10)为浮空输入
/
void Usart1_Init(u32 clk,u32 baudRate) //参数说明: clk 单位为Mhz
{
//USART1->BBR 波特比率设置
float temp;
u16 BRR_Value;
u16 BRR_Mantissa;
u16 BRR_Fraction;
temp = (float)(clk1000000)/(baudRate16); //得到USART 分频除法因子(每个字符16位,乘16得到每秒通过的字符数)
BRR_Mantissa = temp; //得到BRR[15:4]整数部分
BRR_Fraction = (temp - BRR_Mantissa)16; //得到BRR[3:0]小数部分
BRR_Mantissa<<=4;
BRR_Value = BRR_Mantissa + BRR_Fraction; //拼接整数和小数部分
RCC->APB2ENR|=1<<14; //使能串口时钟
RCC->APB2RSTR |= 1<<14; //复位串口1
RCC->APB2RSTR &= ~(1<<14); //初始化串口复位寄存器位
USART1->BRR = BRR_Value; //设置波特比率
USART1->CR1 |= 1<<8; //PEIE中断使能
USART1->CR1 |= 1<<5; //RXNEIE,接收完成中断使能
//USART1->CR1 |= 1<<6; //TC,发送完成中断使能
USART1->CR1 |= 0x200C; //1个停止位,无检验位
}
#if PRINTF_OUT
#pragma import(use_no_semihosting)
//标准库需要的支持函数
struct FILE
{
int handle;
/ Whatever you require here. If the only file you are using is /
/ standard output using printf() for debugging, no file handling /
/ is required. /
};
/ FILE is typedef’ d in stdio.h. /
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
USART1->DR = (u8) ch;
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
return ch;
}
#endif
Library/inc/usart.h
#include <stm32f10x_lib.h>
#define PRINTF_OUT 1 //使用printf()方式输出,使用这种方式会增大hex文件大小,所以不适用这种方式输出就置0
#if PRINTF_OUT
#include “stdio.h”
#endif
void Usart1_Init(u32 clk,u32 baudRate);
在MDK下 将上述文件添加到工程对应目录下即可:
库函数操作*
在使用printf()串口输出 MDK 必须配置为User Micro LIB 。即为动态编译,可以减小 hex文件的带下 减小flash使用空间。
代码如下:
#include “stm32f10x.h”
#include “stdio.h”
#define PRINTF_ON 0 //设置printf() 输出
#define RECEIVE_SEND_BACK 0 //设置接收信息后原文发送
#define SEND_WORDS 1 //设置发送字符串
vu8 RxBuffer[]=”\r\n i will success finally..\r\n”; //存储串口传入的数据,自定义字符串
vu32 count=0;
void RCC_Configuration(void);
void GPIO_Configuration(void);
void USART_Configuration(void);
void delay(vu32 x);
int main(void)
{
RCC_Configuration();
GPIO_Configuration();
USART_Configuration();
while(1)
{
#if PRINTF_ON
printf(“\r\nThanks god ,i am success now..\r\n”);
#elif RECEIVE_SEND_BACK
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
{
RxBuffer[count] = USART_ReceiveData(USART1);
USART_SendData(USART1,RxBuffer[count]);
delay(5);
}
#elif SEND_WORDS
USART_SendData(USART1,RxBuffer[count]);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
//while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET); //检查是否发送完成的另一种方法
count++;
if(sizeof(RxBuffer)<count)
{
count=0;
break; //只发送一次
}
#endif
}
}
void delay(vu32 x) //vu32 1us一次
{
vu32 a=100x/7;
while(–x);
}
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA , &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA , &GPIO_InitStructure);
}
void RCC_Configuration(void)
{
/ 定义枚举类型变量 HSEStartUpStatus /
ErrorStatus HSEStartUpStatus;
/ 复位系统时钟设置/
RCC_DeInit();
/ 开启HSE/
RCC_HSEConfig(RCC_HSE_ON);
/ 等待HSE起振并稳定/
HSEStartUpStatus = RCC_WaitForHSEStartUp();
/ 判断HSE起是否振成功,是则进入if()内部 /
if(HSEStartUpStatus == SUCCESS)
{
/ 选择HCLK(AHB)时钟源为SYSCLK 1分频 /
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/ 选择PCLK2时钟源为 HCLK(AHB) 1分频 /
RCC_PCLK2Config(RCC_HCLK_Div1);
/ 选择PCLK1时钟源为 HCLK(AHB) 2分频 /
RCC_PCLK1Config(RCC_HCLK_Div2);
/ 设置FLASH延时周期数为2 /
FLASH_SetLatency(FLASH_Latency_2);
/ 使能FLASH预取缓存 /
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
/ 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz 9 = 72MHz /
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
/ 使能PLL /
RCC_PLLCmd(ENABLE);
/ 等待PLL输出稳定 /
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
/ 选择SYSCLK时钟源为PLL /
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/ 等待PLL成为SYSCLK时钟源 /
while(RCC_GetSYSCLKSource() != 0x08);
}
/ 打开APB2总线上的GPIOA时钟/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1, ENABLE);
//RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE);
}
void USART_Configuration(void)
{
USART_InitTypeDef USART_InitStructure;
USART_ClockInitTypeDef USART_ClockInitStructure;
USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;
USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;
USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;
USART_ClockInit(USART1 , &USART_ClockInitStructure);
USART_InitStructure.USART_BaudRate = 9600;
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_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
USART_Init(USART1,&USART_InitStructure);
USART_Cmd(USART1,ENABLE);
}
#if PRINTF_ON
int fputc(int ch,FILE f)
{
USART_SendData(USART1,(u8) ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);
return ch;
}
#endif