技术标签: MCU学习 stm32 USART IDLE DMA 嵌入式
作者博客主页
作者 : Eterlove
一笔一画,记录我的学习生活!站在巨人的肩上Standing on Shoulders of Giants!
该文章为原创,转载请注明出处和作者
声明:这段时间较忙,相关知识点分析讲解后面抽时间补上。
嵌入式软件面试的那点事,重点难点一网打尽
你是怎么接收、发送串口数据的?
这个问题其实比较宽泛,一般经验少的会说使用查询方式,但是查询方式效率是非常低下的,所以如果你只能回答这个,100分的题你只能得个30分。如果你说用中断方式,那么请问你具体是如何处理的?如果你回答说一个字节接收完之后再接收下一个字节,那么可以得个50分。
紧接着又问你,你是怎么接收一帧数据的(这个其实不应该由面试官问,而是由你自己补充全面),如果你说采用帧头、帧尾判断的方式接收的,那么这道题还是给你50分,但是你说用空闲中断,那么70分以上,如果你说用DMA+空闲中断的方式接收的,那么90分以上(这是我认为最好的方式了,可能会有其他更好的方式也说不定)。
那么现在说说空闲中断,为什么你说了空闲中断之后,一下子从不及格到及格了?
空闲中断,顾名思义,就是串口空闲后产生的中断。我们都知道,数据一般是按照数据帧来发送的,即一个数据帧一个数据帧的发送,如果两帧发送之间能间隔一段时间,那么在接收端就可以产生空闲中断(关于这个空闲中断,以后可能会专门写一篇笔记介绍),有了空闲中断有什么好处?可以接收不定长数据(这是最明显的好处) 不需要复杂的帧格式(比如帧头、帧尾可以不要) 一个数据帧接收错误,不会影响到下一帧数据的接收 有了空闲中断,可谓好处多多(有的单片机没有空闲中断,那就没办法,当然也可以舍弃一个定时器资源来获得空闲中断的效果),所以当初了解到这个之后,就一直使用这种方式接收了。
但是空闲中断虽好,如果你每接收一个字节都要CPU干预,还是效率太低,那么这时候就得配合DMA了。
怎么配合?比如说你一个数据帧的最大长度是10个字节,设置串口接收缓存区为20个字节,那么你可以设置DMA传输长度为20,这样DMA每从串口传输一个字节,传输长度就会自减,当产生空闲中断时,只要你知道开始设置的传输长度和剩余的传输长度,那么就可以得到你已经接收的数据长度,之后你再重新设置新的接收长度即可进行下一次数据帧的接收。
如此一来,接收一个数据帧只要CPU干预一次就够了,就是在接收完数据帧的时候由空闲中断通知CPU进行后续处理即可(注意不是DMA中断),极大的减少了CPU工作时间。
有的时候,数据量很大,CPU来不及处理,那么你可以通过以下方式解决:
- 增加消息队列(非常好的解决方式)
- 增加两帧之间的发送时间(对于实时性要求很高的可能不合适) 前面两种方式叠加
读了颇有心得体会,亲自动手调试实践才有了自己一些浅谈技巧,故写下《STM32单片机+USART2+DMA+IDLE空闲中断来接受数据》这篇博客,拙作一篇,敬请斧正,本文章期望起到一个抛砖引玉的作用。
1.STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\USART\DMA_Interrupt
2.STM32中文参考手册_V10
/* PA2--->TX2 PA3--->RX2
初始化注意时钟USART2是挂在APB1-->RCC_APB1Periph_USART2!!!
USART2_TX ---->DMA1 通道7
USART2_RX ---->DMA1 通道6 <参见STM32F10xxx参考手册P148>
*/
#include "stm32f10x.h"
#include "USART_IDLE_DMA.h"
#include "stdio.h"
uint32_t RxBuffer[20];
/* Private define ------------------------------------------------------------*/
#define RxBufferSize (countof(RxBuffer) - 1)
/* Private macro -------------------------------------------------------------*/
#define countof(a) (sizeof(a) / sizeof(*(a)))
void RCC_Configuration()
{
/* DMA clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* Enable GPIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
}
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the USART2 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure USART2 Rx as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART2 Tx as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* USART2_Rx_DMA_Channel 6 Config */
DMA_DeInit(DMA1_Channel6);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR; //搬运数据的开始地
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer; //搬运数据的目的地
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //指定外围设备是源设备还是目标设备,这里指USART2
DMA_InitStructure.DMA_BufferSize = RxBufferSize; //以字节单位指定指定通道的缓冲区大小。
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel6, &DMA_InitStructure);
}
/* USART2 configuration -------------------------------------------*/
/* - BaudRate = 115200 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled
*/
void USART2_IDLE_DMA_Init(void)
{
USART_InitTypeDef USART_InitStructure;
/* System Clocks Configuration */
RCC_Configuration();
/* NVIC configuration */
NVIC_Configuration();
/* Configure the GPIO ports */
GPIO_Configuration();
DMA_Configuration();
USART_InitStructure.USART_BaudRate = 115200;
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;
/* Configure USART2 */
USART_Init(USART2, &USART_InitStructure);
/* Enable USART2 DMA RX request */
USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);
/* Enable the USART2 Receive Interrupt */
// USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
/* Enable the USART2 IDLE Interrupt */
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);
/* Enable USART2 */
USART_Cmd(USART2, ENABLE);
/* Enable USART2 DMA RX Channel */
DMA_Cmd(DMA1_Channel6, ENABLE);
}
//printf重定向
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
USART_SendData(USART2 , (uint8_t) ch);
/* Loop until the end of transmission */
while (USART_GetFlagStatus(USART2 , USART_FLAG_TC) == RESET)
{
}
return ch;
}
/*当检测到总线空闲时,该位被硬件置位。如果USART_CR1中的IDLEIE为’1’,则产生中断。由
软件序列清除该位(先读USART_SR,然后读USART_DR)。*
void USART2_IRQHandler(void)
{
uint8_t USART2_ReceSize;
if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)
{
USART2->SR;
USART2->DR;
/*计算出接受这一帧数据的长度*/
USART2_ReceSize = RxBufferSize-DMA_GetCurrDataCounter(DMA1_Channel6);
/*以下是数据接受以后,对数据处理进行一个简单的模拟,在真实项目中,中断服务函数的
代码应该短小精悍!所以我们一般这样处理:在中断服务函数(前台)中,设置一个接受完成标志位
Rece_Flag , 在Main主函数(我们称为后台)while()大循环中判断标志位进行处理数据*/
printf ("The Data length is:%d\r\n",USART2_ReceSize);
printf ("Eterlove! Sending data completed\r\n");
printf ("The data:%s\r\n",RxBuffer);
memset(RxBuffer,0,sizeof(RxBuffer));
/*************************************/
DMA_Cmd(DMA1_Channel6, DISABLE ); //关闭USART2 RX DMA1所指示的通道
DMA_SetCurrDataCounter(DMA1_Channel6,RxBufferSize); //现在我们得知这帧数据的真实长度,重新设置DMA通道的缓冲区大小
DMA_Cmd(DMA1_Channel6, ENABLE); //打开USART2 RX DMA1所指示的通道
}
}
(1)对宏定义不懂请看这------>(sizeof(a) / sizeof(*(a)))解析
/* Private define ------------------------------------------------------------*/
#define RxBufferSize (countof(RxBuffer) - 1)
/* Private macro -------------------------------------------------------------*/
#define countof(a) (sizeof(a) / sizeof(*(a)))
(2)接受数据思路是: 数据到来时,DMA直接从USART2的DR处运输数据到我定义的数组RxBuffer[ ]内,中间无需CPU的参与。 只需要CPU开始时告诉DMA从哪里搬数据(源地址),搬到哪里去(目标地址)即可,数据传输结束后,DMA只需要告诉CPU一声 ”数据我搬完了“
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR; //搬运数据的开始地
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer; //搬运数据的目的地
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //指定外围设备是源设备还是目标设备,这里指USART2
DMA_InitStructure.DMA_BufferSize = RxBufferSize; //以字节单位指定指定通道的缓冲区大小。
(3)讲解以下如何算出收到的数据长度(字节数) ,首先最开始我并不知道数据长度有多少,所以我定义一个比较长的数组RxBuffer[20] ;通过我定义的宏运算《参见讲解(1)》来算出RxBufferSize。所以我设置DMA通道缓冲区为RxBufferSize。
DMA_InitStructure.DMA_BufferSize = RxBufferSize; //以字节单位指定指定通道的缓冲区大小。
那我们调用这个函数干什么呢?DMA_GetCurrDataCounter(),请看函数原型:
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)
{
/* Check the parameters */
assert_param(IS_DMA_ALL_PERIPH(DMAy_Channelx));
/* Return the number of remaining data units for DMAy Channelx */
return ((uint16_t)(DMAy_Channelx->CNDTR));
}
这个函数去读CNDTR寄存器的值,参见STM32参考手册,这个寄存器中的值是DMA通道设置的传输字节数,注意:每接受数据一字节,此值将被递减一字节。
例如设置RxBufferSize为20 , 此时接受到5个字节数的数据 ,CNDTR寄存器的值递减5个字节(RxBufferSize减去5个字节),此时我们调用DMA_GetCurrDataCounter()函数去读CNDTR寄存器的值为20-5=15.所以我们调用DMA_GetCurrDataCounter()函数返回的是DMA通道缓冲区的剩余字节长度。
接收的字符串长度=设置的接收长度 - 剩余DMA缓存大小
所以计算出接受这一帧数据的长度如下图所示。
/*计算出接受这一帧数据的长度*/
USART2_ReceSize = RxBufferSize-DMA_GetCurrDataCounter(DMA1_Channel6);
DMA_SetCurrDataCounter(DMA1_Channel6,RxBufferSize);
//现在我们得知这帧数据的真实长度,重新设置DMA通道的缓冲区大小
(4)如何清除IDLE空闲中断的标志位 : 先读USART_SR,然后读USART_DR ,为什么?,参考STM32手册P541 , 当检测到总线空闲时,该位被硬件置位。如果USART_CR1中的IDLEIE为’1’,则产生中断。由软件序列清除该位(先读USART_SR,然后读USART_DR)。
(5)中断服务函数的处理。
以下是数据接受以后,对数据处理进行一个简单的模拟。
printf ("The Data length is:%d\r\n",USART2_ReceSize);
printf ("Eterlove! Sending data completed\r\n");
printf ("The data:%s\r\n",RxBuffer);
memset(RxBuffer,0,sizeof(RxBuffer));
在真实项目中,中断服务函数的代码应该短小精悍!所以我们一般这样处理:在中断服务函数(前台)中,设置一个接受完成标志位Rece_Flag , 在Main主函数(我们称为后台)while()大循环中判断标志位进行处理数据。
改进如下:
_Bool Rece_Flag = 0; //接受完成标志
int main(void)
{
SysTick_Config(SystemCoreClock/1000);
Delay_Ms(200);
STM3210B_LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
Led_Init(); //LED初始化
Led_Control(LED_ALL , OFF); //LED全关
Key_Init(); //按键初始化
USART2_IDLE_DMA_Init();
while(1)
{
if(Rece_Flag==1)
{
Rece_Flag = 0;
printf ("The Data length is:%d\r\n",USART2_ReceSize);
printf ("Eterlove! Sending data completed\r\n");
printf ("The data:%s\r\n",RxBuffer);
memset(RxBuffer,0,sizeof(RxBuffer));
}
}
}
//中断服务函数处理
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)
{
USART2->SR;
USART2->DR;
USART2_ReceSize =RxBufferSize-DMA_GetCurrDataCounter(DMA1_Channel6); //计算出接受这一帧数据的长度
Rece_Flag = 1;
DMA_Cmd(DMA1_Channel6, DISABLE ); //关闭USART2 RX DMA1所指示的通道
DMA_SetCurrDataCounter(DMA1_Channel6,RxBufferSize); //现在我们得知这帧数据的真实长度,重新设置DMA通道的缓冲区大小
DMA_Cmd(DMA1_Channel6, ENABLE); //打开USART2 RX DMA1所指示的通道
}
}
Qt 创建项目完成后上方出现这是由于构件套件不能使用所导致的。你需要将这个东西点开,选择其他的构建套件然后Qt就可以正常使用了。_this file is not part of any project
姓名:吴兴隆出生年月:1979年8月学历(学位):博士毕业院校:浙江大学(本科)、美国University ofMiami(硕士/博士)专业:应用物理现任职务:武汉工程大学计算机学院讲师,硕士生导师主要社会兼职:湖北省青年科协常务理事,湖北省计算机学会理事,多家高新科技企业高级技术顾问个人经历:2008.01-2011.04年,美国University of Miami,博士后/Assistant...
1.编辑/etc/vsftpd/user_list和/etc/vsftpd/ftpusers两个设置文件脚本,将root账户前加上#号变为注释。(即让root账户从禁止登录的用户列表中排除) 2.重新开启vsftpd service vsftpd reload 允许root账户访问ftp,可以远程访问centos系统中的任一文件,对于远程维护centos系统或下载文件十分方便。..._ftp root登录
Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个Java虚拟机。Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识进行详细介绍。一、Java JVM内存介绍JVM管理两种类型的内存,堆和非堆。按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。简单来说堆就是_jvm 不指定堆大小
转载来自:https://blog.csdn.net/zhanyuanlin/article/details/72667293概述想要对Yarn的FairScheduler队列资源管理、以及抢占规则有正确的理解,必须知道Yarn的Fair Share的含义。我们在yarn的管理页面里面,经常可以看到队列的Instantaneous Fair Share以及Steady Fair Share..._instantaneous fair share
序其实,simditor真的是很好用的,只需要简单的配置就可以用的,只是初步接触到它的时候,不知道原理,然后内心又存在着对未知事物的“恐惧”,所以在使用的过程中还是纠结了一段时间的。编辑文章simditor的官方网址为http://simditor.tower.im/ 上面有它的一些使用帮助。但是,我在使用的过程中,发现很多问题写的不是很清晰,需要自己去摸索和实验。下载simditor 并使用首先..._simditor使用
摘 要:本论文简要介绍了计算机的发展现状,然后对比了两种不同的体系结构,比较了这两种体系结构中存在的问题,进而提出计算机体系结构的发展趋势。[1]关键词:计算机体系结构;冯诺依曼;RISC;CISC中图分类号:TP303计算机的发展大体上可以分为两个阶段,这两个阶段分别是:串行阶段以及并行阶段。所谓并行计算机,是在串行计算的基础上,使得许多组处理单元相互协调、相互调度来完成数据以及计算等处理的方式...
头文件 #includestdio.h> #include//注意 不可以掉了这个头文件,perror是包含在这个文件里的//定义函数 void perror(const char *s); perror ("open_port");函数说明 perror ( )用 来 将 上 一 个 函 数 发 生 错 误 的 原 因 输 出 到 标 准 设_qt perror
SiamMask: Qiang Wang, Li Zhang, Luca Bertinetto, Weiming Hu, Philip H.S. Torr.“Fast Online Object Tracking and Segmentation: A Unifying Approach.” CVPR (2019). [paper] [project] [code]SiamRPN++: Bo ..._object tracking by reconstruction with view-specific discriminative correlat
PostgreSQL安装完成后是不能从本机之外的机器连接的按以下步骤修改配置1,安装目录/data/pg_hba.confhost all all 192.168.1.1/32 md5 --/32代表只允许192.168.1.1访问host all all 192.168.1.0/24 md5..._postgre 远程连接
源代码demo已上传到百度网盘:永久生效 ,代码封装了创建线程、挂起线程、恢复线程、等待线程退出、设置优先级、停止线程等功能直接上代码看封装类的头文件#include <windows.h>//回调函数指针typedef void (*THREAD_ROUTINE)(void *);class CWorkThread {public: CWorkThread(); virtual ~CWorkThread();public: // ..._vc++类内封装多线程
strdim声明字符串数组变量。strdim <array> <size>用法说明定义具有<size>条目的整数类型数组。 <size>范围是1到65536,数组索引是以0为起始点。数组的默认值是空字符串。例子strdim timeary 10for i 9 0 gettime timeary[9-i...