网站开发 名片,dw设计试图做网站,wordpress 高并发崩溃,小白学做网站教程目录标题 前言1、串口中断接收固定帧头帧尾数据1.1、任务需求1.2、实现思路1.3、程序源码#xff1a; 2、串口中断接收用定时器来判断帧结束3、串口中断接收数据空闲中断3.1、串口的空闲中断3.2、实现思路3.3、程序源码 4、串口的空闲中断DMA转运4.1、DMA简介4.2、DMA模式4.3、… 目录标题 前言1、串口中断接收固定帧头帧尾数据1.1、任务需求1.2、实现思路1.3、程序源码 2、串口中断接收用定时器来判断帧结束3、串口中断接收数据空闲中断3.1、串口的空闲中断3.2、实现思路3.3、程序源码 4、串口的空闲中断DMA转运4.1、DMA简介4.2、DMA模式4.3、DMA资源4.4、DMA主要特征4.5、实现思路4.6程序源码 报错及解决 前言
使用串口传输数据时因为串口是异步通信协议所以我们需要去判断哪是一帧完整的数据并进行数据的处理。 接收不定长的数据以下几个主要有以下方法
加固定的帧头和帧尾串口中断接收用定时器来判断帧结束串口中断接收利用串口空闲中断来判断帧结束
1、串口中断接收固定帧头帧尾数据
1.1、任务需求
1、PC端通过串口1发送给单片机一个有固定帧头帧尾的数据包。 2、单片机利用串口中断接收数据并判断帧头帧尾。 3、当识别到对应的帧头帧尾时将接收到的数据再发送给PC端。 4、清除接收缓存。
1.2、实现思路 1、使能相关时钟 使能相关GPIO所在APB2总线的时钟 使能串口所在APB2总线的时钟 2、初始化串口 配置数据位个数、停止位个数、校验位、波特率等 3、初始化GPIO 配置RX、TX对应GPIO口。 4、初始化串口中断 配置中断通道中断优先级等 4、使能串口中断 5、使能串口 6、编写中断服务程序 ①判断接收到的数据是否是帧头②若是帧头则将数据写入到缓冲区若不是则无视等待下一个数据。③当有帧头后不断在后面接收到的数据中找帧尾。找到了则说明这一帧数据接收完成了。④将这一帧数据再通过printf发送回PC端。⑤清楚缓冲区数据将索引清零。
1.3、程序源码
usart.h
#ifndef __SERIAL_H__
#define __SERIAL_H__#include stm32f10x.h // Device header
#include stdarg.h
#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);//串口初始化
#endifusart.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_ModeGPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStructure.GPIO_PinGPIO_Pin_9;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);GPIO_InitStructure.GPIO_ModeGPIO_Mode_IPU; //读。上拉输入GPIO_InitStructure.GPIO_PinGPIO_Pin_10;GPIO_InitStructure.GPIO_SpeedGPIO_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_IRQChannelUSART1_IRQn;NVIC_Structure.NVIC_IRQChannelCmdENABLE;NVIC_Structure.NVIC_IRQChannelPreemptionPriority1;//抢占优先级NVIC_Structure.NVIC_IRQChannelSubPriority3;//响应优先级NVIC_Init(NVIC_Structure);
#endifUSART_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);//清中断标志位}
}2、串口中断接收用定时器来判断帧结束
暂时不写
3、串口中断接收数据空闲中断
3.1、串口的空闲中断
空闲中断USART_IT_IDLE俗称帧中断即第一帧数据接收完毕到第二帧数据开始接收期间存在一个空闲状态(就是没数据接收的状态)检测到此空闲状态后即执行中断程序。进入中断程序即意味着已经接收到一组完整帧数据仅需及时对数据处理或将数据转移出缓冲区即可。 相较于上面的利用固定帧头和帧尾来判断完整数据帧这种方法更为使用通过利用空闲中断对于没有固定帧头和帧尾的数据我们也能准确接收了。
3.2、实现思路
1、使能相关时钟 使能相关GPIO所在APB2总线的时钟 使能串口所在APB2总线的时钟2、初始化串口 配置数据位个数、停止位个数、校验位、波特率等3、初始化GPIO 配置RX、TX对应GPIO口。4、初始化串口中断 配置中断通道中断优先级等4、使能串口中断5、使能串口的空闲中断5、使能串口6、编写中断服务程序 ①判断中断类型串口中断、串口的空闲中断②若是串口中断就将接收的数据存入缓冲区。然后清除中断标志③若是串口的空闲中断则说明这一帧数据接收完了后面就是数据处理了。④数据处理完后就将数据缓冲区清除将索引号清零。⑤清除串口空闲中断标志位通过读串口的SR和DR寄存器
3.3、程序源码
usart.h
#ifndef __SERIAL_H__
#define __SERIAL_H__#include stm32f10x.h // Device header
#include stdarg.h
#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);//串口初始化
#endifusart.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_ModeGPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStructure.GPIO_PinGPIO_Pin_9;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);GPIO_InitStructure.GPIO_ModeGPIO_Mode_IPU; //读。上拉输入GPIO_InitStructure.GPIO_PinGPIO_Pin_10;GPIO_InitStructure.GPIO_SpeedGPIO_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_IRQChannelUSART1_IRQn;NVIC_Structure.NVIC_IRQChannelCmdENABLE;NVIC_Structure.NVIC_IRQChannelPreemptionPriority1;//抢占优先级NVIC_Structure.NVIC_IRQChannelSubPriority3;//响应优先级NVIC_Init(NVIC_Structure);#if USART1_IDLE_INTERRUPT_ENABLE USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);//使能串口1的空闲中断
#endif#endifUSART_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.LenthRECEIVE_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;}}4、串口的空闲中断DMA转运
4.1、DMA简介
直接存储器访问(Direct Memory Access)简称DMA。DMA是CPU一个用于数据从一个地址空间到另一地址空间“搬运”(拷贝)的组件数据拷贝过程不需CPU干预数据拷贝结束则通知CPU处理。因此大量数据拷贝时使用DMA可以释放CPU资源。
4.2、DMA模式
DMA数据拷贝过程典型的有 (1)内存—内存内存间拷贝; (2)外设—内存如uart、spi、i2c等总线接收数据过程; (3)内存—外设如uart、spi、i2c等总线发送数据过程。
4.3、DMA资源
STM32F1系列的MCU有两个DMA控制器(DMA2只存在于大容量产品中)DMA1有7个通道DMA2有5个通道每个通道专门用来管理来自于一个或者多个外设对存储器的访问请求。还有一个仲裁器来协调各个DMA请求的优先权。
4.4、DMA主要特征
每个通道都直接连接专用的硬件 DMA 请求每个通道都同样支持软件触发。这些功能通过软件来配置。在同一个 DMA 模块上多个请求间的优先权可以通过软件编程设置(共有四级很高、高、中等和低)优先权设置相等时由硬件决定(请求 0 优先于请求 1 依此类推可以参考STM32数据手册。独立的源和目标数据区的传输宽度(字节、半字、全字)模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。支持循环的缓冲器管理会把原来的数据覆盖。每个通道都有 3 个事件标志(DMA 半传输 DMA 传输完成和 DMA 传输出错)这 3 个事件标志逻辑或成为一个单独的中断请求。存储器和存储器间的传输仅 DMA2 可以。外设和存储器、存储器和外设之间的传输。闪存、SRAM 、外设的 SRAM 、APB1 、APB2 和 AHB 外设均可作为访问的源和目标。可编程的数据传输数目最大为65535。
高波特率传输场景下串口非常有必要使用DMA。
4.5、实现思路
1、使能相关时钟 使能相关GPIO所在APB2总线的时钟 使能串口所在APB2总线的时钟 使能DMA1时钟2、初始化串口 配置数据位个数、停止位个数、校验位、波特率等3、初始化GPIO 配置RX、TX对应GPIO口。4、初始化串口空闲中断 配置中断通道中断优先级等5、使能串口的空闲中断6、配置初始化DMA1。7、使能串口1的DMA接收使能DMA1的TX通道DMA1_Channel48、使能串口9、编写中断服务程序 ①判断中断类型串口的空闲中断②清除串口空闲中断标志位通过读串口的SR和DR寄存器③关闭DMA通道④进行数据处理置标志位等⑤重新设置 DMA 传输数据长度⑥使能DMA通道
4.6程序源码
usart.h
#ifndef __SERIAL_H__
#define __SERIAL_H__#include stm32f10x.h // Device header
#include stdarg.h
#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);//串口初始化
#endifusart.c
#include Serial.husart_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_ModeGPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStructure.GPIO_PinGPIO_Pin_9;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);GPIO_InitStructure.GPIO_ModeGPIO_Mode_IPU; //读。上拉输入GPIO_InitStructure.GPIO_PinGPIO_Pin_10;GPIO_InitStructure.GPIO_SpeedGPIO_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_ENABLENVIC_Structure.NVIC_IRQChannelUSART1_IRQn;NVIC_Structure.NVIC_IRQChannelCmdENABLE;NVIC_Structure.NVIC_IRQChannelPreemptionPriority1;//抢占优先级NVIC_Structure.NVIC_IRQChannelSubPriority3;//响应优先级NVIC_Init(NVIC_Structure);
#if USART1_INTERRUPT_ENABLEUSART_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#endifUSART_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” ”。