基于stm32f103rct6的USART串口通信
硬件说明
芯片stm32f103rct6
使用串口USART1,对应引脚 TX---PA9 RX---PA10
代码部分
需要延时函数便于观察运行情况,因此导入dalay模块
“delay.h”
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
“delay.c”
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
“Serial.h”
#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>
void Serial_Init(void);
void Serial_SendByte(u8 Byte);
void Serial_SendArray(u8 *Array,u16 length);
void Serial_SendString(u8 *String);
void Serial_SendNumber(u32 num,u8 length);
void Serial_Printf(char *format,...);
u8 Serial_GetRxData(void);
u8 Serial_GetRxFlag(void);
#endif
“Serial.c”
#include "stm32f10x.h" // Device header
#include "Serial.h"
#include <stdio.h>
#include <stdarg.h>
//设置两个变量,方便写get函数给外部访问
u8 Serial_Rx_Data,Serial_Rx_Flag;
void Serial_Init(void)
{
//打开 USART1 和 GPIOA 时钟; USART1由APB2总线控制; USART2,USART3是由APB1控制
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置PA9和PA10为 复用推挽输出 和 上拉输入,后者上拉输入保证默认高电平,这样和 USART 起始位低电平相配合
GPIO_InitTypeDef GPIO_InitStructure;
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);
//这里直接调用不会对PA9的端口配置产生覆盖,不难发现GPIO的初始化处在局部
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//使用结构体,配置USART的波特率,流量控制,需要开启的端口,奇偶校验,数据长度
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate=9600;
USART_InitStruct.USART_HardwareFlowControl= USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;
USART_InitStruct.USART_Parity=USART_Parity_No;
USART_InitStruct.USART_StopBits=USART_StopBits_1;
USART_InitStruct.USART_WordLength=USART_WordLength_8b;
USART_Init(USART1, &USART_InitStruct);
//如果需要接收数据,一般采取中断策略,需要配置中断
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStruct);
//USART 启动!!!
USART_Cmd(USART1,ENABLE);
}
u8 Serial_GetRxData(void)
{
return Serial_Rx_Data;
}
u8 Serial_GetRxFlag(void)
{
if(Serial_Rx_Flag==1){
Serial_Rx_Flag=0;
return 1;
}
return Serial_Rx_Flag;
}
void Serial_SendByte(u8 Byte)
{
USART_SendData(USART1, Byte);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET);
}
void Serial_SendArray(u8 *Array,u16 length)
{
u16 i=0;
for(i=0;i<length;i++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(u8 *String)
{
u8 i=0;
for(i=0;String[i]!='\0';i++){
Serial_SendByte(String[i]);
}
}
void Serial_SendNumber(u32 num,u8 length)
{
u8 str[length+1];
for(int i=length-1;i>=0;i--){
str[i]='0'+num%10;
num/=10;
}
Serial_SendString(str);
}
int fputc(int ch,FILE *f)
{
Serial_SendByte(ch);
return ch;
}
void Serial_Printf(char *format,...)
{
char string[100];
va_list arg;
va_start(arg,format);
vsprintf(string,format,arg);
va_end(arg);
Serial_SendString(string);
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE)==SET){
Serial_Rx_Data=USART_ReceiveData(USART1);
Serial_Rx_Flag=1;
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
“main.c”
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Serial.h"
int main(void)
{
Serial_Init();
while(1){
u8 RxFlag=Serial_GetRxFlag();
if(RxFlag){
u8 RxData[1];
//接受数据
RxData[0]=Serial_GetRxData();
//收到并回复给发送方
Serial_SendString(RxData);
}
//等待1s方便观察
Delay_ms(1000);
}
}
将printf()函数映射到串口
如果想在开发中直接使用c语言std库中的printf()函数,可以进行如下设置:
首先需要在 Keil5 点击“魔术棒”,勾选 Use MicroLIB 选项。
方法一:
//在 Serial.c 和 Serial.h 的头文件中都加入
#include<stdio.h>
//然后在 Serial.c 文件中添加上
int fputc(int ch,FILE *f)
{
Serial_SendByte(ch);
return ch;
}
//这样就可以直接调用printf()
方法二:
//在 Serial.c 和 Serial.h 的头文件中都加入
#include<stdio.h>
int main()
{
char string[100];
int num;
//将格式化后的内容保存到字符串string中
sprintf(string,"你好,我的编号是 %d \r\d",num);
//调用封装好的串口发送函数
Serial.SendString(string);
}
方法三
//在 Serial.c 和 Serial.h 的头文件中都加入
#include<stdio.h>
#include<stdarg.h>
//直接在Serial.c中封装一个函数,用法与printf()一致
void Serial_Printf(char *format,...)
{
char string[100];
va_list arg;
va_start(arg,format);
vsprintf(string,format,arg);
va_end(arg);
Serial_SendString(string);
}
基于状态机模型,接收USART串口数据包
以一种协议为例,要求数据以 Oxa5
作为起始字节,下一个字节为 0x01
,表示数据长度为1,下一个字节为 数据内容0x01
,最后一个字节为校验码,即数据长度与数据内容累加得到 0x02
,即所发的数据包为
0xa5,0x01,0x01,0x02
定义两个状态:
状态0------等待起始字节,对每一个收到的字节判断是否为 0xa5
,如果是则变为状态1 ;
状态1-----收到数据并存入GetData[pRx++],然后按照 数据长度 + 1位校验位 往后接收数据,接收完成后则变成状态0;
//定义好缓冲区用于接受数据
u8 GetData[256];
void USART1_IRQHandler(void)
{
/* 利用状态机解决接受hex数据包的问题*/
//static变量,只初始化一次,但是可以反复使用
static u8 RxState=0; //状态机状态标志
static u8 pRx=0; //类似于缓冲区指针,初始为0
if(USART_GetITStatus(USART1, USART_IT_RXNE)==SET){
//先用一个变量接收数据
u8 Serial_Rx_Data=USART_ReceiveData(USART1);
//判断状态机状态,并进行相应操作
if(RxState==0){
RxFlag=0; //用于与外界进行信息交互的变量,按需保留
if(Serial_Rx_Data==0xa5){
GetData[pRx]=Serial_Rx_Data;
pRx++; //更新指针,提前指向下一个空闲位置
RxState=1; //改变状态
}
}else if(X8_RxState==1){
GetData[pRx]=Serial_Rx_Data;
pRx++; //更新指针,提前指向下一个空闲位置
GetData_length=pRx; //更新数据规模
if(pRx==GetData[1]+3){
RxState=0; //改变状态
pRx=0; //更新指针
//RxFlag=1; //用于与外界进行信息交互的变量,按需保留
//this_task=1; //用于与外界进行信息交互的变量,按需保留
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}