使用串口传输数据时,因为串口是异步通信协议,所以我们需要去判断哪是一帧完整的数据,并进行数据的处理。
接收不定长的数据以下几个主要有以下方法:
1、PC端通过串口1发送给单片机一个有固定帧头帧尾的数据包。
2、单片机利用串口中断接收数据并判断帧头帧尾。
3、当识别到对应的帧头帧尾时,将接收到的数据再发送给PC端。
4、清除接收缓存。
1、使能相关时钟
使能相关GPIO所在APB2总线的时钟
使能串口所在APB2总线的时钟
2、初始化串口
配置数据位个数、停止位个数、校验位、波特率等
3、初始化GPIO
配置RX、TX对应GPIO口。
4、初始化串口中断
配置中断通道,中断优先级等
4、使能串口中断
5、使能串口
6、编写中断服务程序:
usart.h
#ifndef __SERIAL_H__
#define __SERIAL_H__
#include "stm32f10x.h" // Device header
#include
#include "stdio.h"
#include "string.h"
#define USART1_ENABLE 1 //使能串口1
#define USART1_BAUDRATE 9600 //串口1波特率
#define USART1_INTERRUPT_ENABLE 1 //使能串口1中断
#define RECEIVE_BUF_MAX_SIZE 100 //单次最大接收字节数
typedef struct{
uint8_t Buffer[RECEIVE_BUF_MAX_SIZE];
uint16_t Lenth;
}usart_data;
void Serial_Init(void);//串口初始化
#endif
usart.c
void Serial_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_Structure;
#if USART1_ENABLE
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//串口1在APB2总线上,串口2、3在APB1总线上
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //读。上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate= USART1_BAUDRATE;
USART_InitStructure.USART_HardwareFlowControl= USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode= USART_Mode_Tx|USART_Mode_Rx;
USART_InitStructure.USART_Parity= USART_Parity_No;
USART_InitStructure.USART_StopBits= USART_StopBits_1;
USART_InitStructure.USART_WordLength= USART_WordLength_8b;
USART_Init(USART1,&USART_InitStructure);
#if USART1_INTERRUPT_ENABLE
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//使能串口1接受中断
NVIC_Structure.NVIC_IRQChannel=USART1_IRQn;
NVIC_Structure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Structure.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级
NVIC_Structure.NVIC_IRQChannelSubPriority=3;//响应优先级
NVIC_Init(&NVIC_Structure);
#endif
USART_Cmd(USART1,ENABLE);
#endif
}
int fputc(int ch,FILE *f) //重构定向,printf直接打印到串口1
{
USART_SendData(USART1,ch);
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET);
return ch;
}
//串口一中断处理函数
usart_data usart1_rxdata;
uint16_t data;
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET) //若发生串口中断
{
usart1_rxdata.Buffer[usart1_rxdata.Lenth++] = USART_ReceiveData(USART1);
if(usart1_rxdata.Buffer[0]!='h') //判断帧头
usart1_rxdata.Lenth =0;
if((usart1_rxdata.Buffer[0]=='h')&&(usart1_rxdata.Buffer[usart1_rxdata.Lenth-1]=='e'))
//判断帧头帧尾
{
printf("rx_data:%s\r\n",usart1_rxdata.Buffer);//将接收到的一帧数据再发送回去,做验证
memset(usart1_rxdata.Buffer,'\0',usart1_rxdata.Lenth);
usart1_rxdata.Lenth = 0;
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清中断标志位
}
}
暂时不写
空闲中断USART_IT_IDLE,俗称帧中断,即第一帧数据接收完毕到第二帧数据开始接收期间存在一个空闲状态(就是没数据接收的状态),检测到此空闲状态后即执行中断程序。进入中断程序即意味着已经接收到一组完整帧数据,仅需及时对数据处理或将数据转移出缓冲区即可。
相较于上面的利用固定帧头和帧尾来判断完整数据帧,这种方法更为使用,通过利用空闲中断,对于没有固定帧头和帧尾的数据我们也能准确接收了。
usart.h
#ifndef __SERIAL_H__
#define __SERIAL_H__
#include "stm32f10x.h" // Device header
#include
#include "stdio.h"
#include "string.h"
#define USART1_ENABLE 1
#define USART1_BAUDRATE 9600
#define USART1_INTERRUPT_ENABLE 1
#define USART1_IDLE_INTERRUPT_ENABLE 1
#define RECEIVE_BUF_MAX_SIZE 100 //单次最大接收字节数
typedef struct{
uint8_t Buffer[RECEIVE_BUF_MAX_SIZE];
uint16_t Lenth;
}usart_data;
void Serial_Init(void);//串口初始化
#endif
usart.c
#include "Serial.h"
void Serial_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_Structure;
#if USART1_ENABLE
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//串口1在APB2总线上,串口2、3在APB1总线上
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //读。上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate= USART1_BAUDRATE;
USART_InitStructure.USART_HardwareFlowControl= USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode= USART_Mode_Tx|USART_Mode_Rx;
USART_InitStructure.USART_Parity= USART_Parity_No;
USART_InitStructure.USART_StopBits= USART_StopBits_1;
USART_InitStructure.USART_WordLength= USART_WordLength_8b;
USART_Init(USART1,&USART_InitStructure);
#if USART1_INTERRUPT_ENABLE
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//使能串口1接受中断
NVIC_Structure.NVIC_IRQChannel=USART1_IRQn;
NVIC_Structure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Structure.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级
NVIC_Structure.NVIC_IRQChannelSubPriority=3;//响应优先级
NVIC_Init(&NVIC_Structure);
#if USART1_IDLE_INTERRUPT_ENABLE
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);//使能串口1的空闲中断
#endif
#endif
USART_Cmd(USART1,ENABLE);
#endif
}
//=================================================================
int fputc(int ch,FILE *f) //重构定向,printf直接打印到串口1
{
Serial_Sendbyte(USART1,ch);
return ch;
}
//串口一中断
usart_data usart1_rxdata;
uint16_t data;
void USART1_IRQHandler(void)
{
uint16_t clear;
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
{
usart1_rxdata.Buffer[usart1_rxdata.Lenth++] = USART_ReceiveData(USART1);
if(usart1_rxdata.Lenth>RECEIVE_BUF_MAX_SIZE)
usart1_rxdata.Lenth =0;
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
else if(USART_GetITStatus(USART1,USART_IT_IDLE)==SET)//串口空闲中断
{
printf("rx_data:%s\r\n",usart1_rxdata.Buffer);
memset(usart1_rxdata.Buffer,'\0',usart1_rxdata.Lenth);
usart1_rxdata.Lenth = 0;
clear = USART1->SR;
clear = USART1->DR;
}
}
直接存储器访问(Direct Memory Access),简称DMA。DMA是CPU一个用于数据从一个地址空间到另一地址空间“搬运”(拷贝)的组件,数据拷贝过程不需CPU干预,数据拷贝结束则通知CPU处理。因此,大量数据拷贝时,使用DMA可以释放CPU资源。
DMA数据拷贝过程,典型的有:
(1)内存—>内存,内存间拷贝;
(2)外设—>内存,如uart、spi、i2c等总线接收数据过程;
(3)内存—>外设,如uart、spi、i2c等总线发送数据过程。
STM32F1系列的MCU有两个DMA控制器(DMA2只存在于大容量产品中),DMA1有7个通道,DMA2有5个通道,每个通道专门用来管理来自于一个或者多个外设对存储器的访问请求。还有一个仲裁器来协调各个DMA请求的优先权。
高波特率传输场景下,串口非常有必要使用DMA。
usart.h
#ifndef __SERIAL_H__
#define __SERIAL_H__
#include "stm32f10x.h" // Device header
#include
#include "stdio.h"
#include "string.h"
#define USART1_ENABLE 1
#define USART1_BAUDRATE 9600
#define USART1_INTERRUPT_ENABLE 1
#define USART1_IDLE_INTERRUPT_ENABLE 1
#define USART1_DMA_TX_ENABLE 0
#define USART1_DMA_RX_ENABLE 1
#define USART_BUF_MAX_SIZE 100 //单次最大接收字节数
typedef struct{
uint8_t Buffer[USART_BUF_MAX_SIZE];
uint16_t Lenth;
}usart_data;
void Serial_Init(void);//串口初始化
#endif
usart.c
#include "Serial.h"
usart_data usart1_rxdata;
usart_data usart_txdata;
void Serial_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_Structure;
DMA_InitTypeDef DMA_InitStructure;
#if USART1_ENABLE
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//串口1在APB2总线上,串口2、3在APB1总线上
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //读。上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate= USART1_BAUDRATE;
USART_InitStructure.USART_HardwareFlowControl= USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode= USART_Mode_Tx|USART_Mode_Rx;
USART_InitStructure.USART_Parity= USART_Parity_No;
USART_InitStructure.USART_StopBits= USART_StopBits_1;
USART_InitStructure.USART_WordLength= USART_WordLength_8b;
USART_Init(USART1,&USART_InitStructure);
#if USART1_INTERRUPT_ENABLE |USART1_IDLE_INTERRUPT_ENABLE
NVIC_Structure.NVIC_IRQChannel=USART1_IRQn;
NVIC_Structure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Structure.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级
NVIC_Structure.NVIC_IRQChannelSubPriority=3;//响应优先级
NVIC_Init(&NVIC_Structure);
#if USART1_INTERRUPT_ENABLE
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//使能串口1接受中断
#endif
#if USART1_IDLE_INTERRUPT_ENABLE
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);//使能串口1的空闲中断
#endif
#endif
#if USART1_DMA_TX_ENABLE
/*Usart1_TX_DMA_config,从存储区到USART1->DR*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(DMA1_Channel4);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR); //外设站点起始地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设站点地址不自增
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)usart_txdata.Buffer;//存储器站点起始地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器站点自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //存储器站点到外设站点 //传输方向
DMA_InitStructure.DMA_BufferSize = USART_BUF_MAX_SIZE;//缓冲区大小,传输计数器
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //普通模式,不循环
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //失能(不是存储器到存储器)
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //优先级
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送
DMA_Cmd(DMA1_Channel4,ENABLE); //使能DMA1的TX通道(DMA1_Channel4)
#endif
#if USART1_DMA_RX_ENABLE
/*Usart1_RX_DMA_config,从USART1->DR到存储区*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR); //外设站点起始地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设站点地址不自增
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)usart1_rxdata.Buffer;//存储器站点起始地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器站点自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设站点到存储器站点 //传输方向
DMA_InitStructure.DMA_BufferSize = USART_BUF_MAX_SIZE;//缓冲区大小,传输计数器
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //普通模式,不循环
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //失能(不是存储器到存储器)
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //优先级
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能串口1的DMA接收
DMA_Cmd(DMA1_Channel5,ENABLE); //使能DMA1的TX通道(DMA1_Channel4)
#endif
USART_Cmd(USART1,ENABLE);
#endif
}
//串口一中断
void USART1_IRQHandler(void)
{
uint16_t clear;
if(USART_GetITStatus(USART1,USART_IT_IDLE)==SET)
{
clear = USART1->SR;
clear = USART1->DR;
DMA_Cmd(DMA1_Channel5, DISABLE); // 关闭 DMA 通道
usart1_rxdata.Lenth = sizeof(usart1_rxdata.Buffer) - DMA_GetCurrDataCounter(DMA1_Channel5); // 计算接收到的数据长度
printf("rx_data:%s\r\n",usart1_rxdata.Buffer);
memset(usart1_rxdata.Buffer,'\0',usart1_rxdata.Lenth);
DMA_SetCurrDataCounter(DMA1_Channel5, sizeof(usart1_rxdata.Buffer)); // 重新设置 DMA 传输数据长度
DMA_Cmd(DMA1_Channel5, ENABLE); // 使能 DMA 通道
}
}
int fputc(int ch,FILE *f) //重构定向,printf直接打印到串口1
{
USART_SendData(USART1,ch);
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET);
return ch;
}
我们在使用上面的程序进行printf重构定向时,需要注意以下两点:
1、在“魔法棒”—>"Target"中,将”USB Micro LIB“勾选上,否则会出现不能正常打印甚至单片机直接进入硬件错误中断宕机的情况。
2、需要添加头文件 #include “stdio.h”,否则会出现报错“error: unknown type name “FILE” ”。