基于stm32的双缓冲的实现

合集下载

如何实现双缓冲

如何实现双缓冲

如何实现双缓冲双缓冲即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。

双缓冲实现过程如下:1、在内存中创建与画布一致的缓冲区2、在缓冲区画图3、将缓冲区位图拷贝到当前画布上4、释放内存缓冲区(1)在内存中创建与画布一致的缓冲区CDC dc;//这是窗口的DC,假设已加载好CDC MemDC; //创建内存中的一个临时dc- MemDC, MemDC用来向窗口绘图的“草稿”//随后建立与屏幕显示兼容的内存显示设备MemDC.CreateCompatibleDC(&dc); //这时还不能绘图,因为没有地方画 ^_^//创建的临时空白bitmap作为“画布”,至于位图的大小,可以用窗口的大小CBitmap MemBitmap;MemBitmap.CreateCompatibleBitmap(&dc,nWidth,nHeight);//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap); //将上面创建的临时“画布”MemBitmap与MemDC连接,注意此处的MemBitmap为一个空白临时画布,可以在这个空白画布上自绘图,也可以在这个画布上加载图片//先用背景色将位图清除干净,这里我用的是白色作为背景//你也可以用自己应该用的颜色MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));(2)在缓冲区画图MemDC.MoveTo(……);MemDC.LineTo(……);(2)'在第(2)步中,如果不是自绘图,而是加载一个位图,则需要再定义一个临时dc- MemDC2,用来将位图加载到上面建立的空白画布MemDC中CBitmap p1;//这是要画的位图,假设已加载好CDC MemDC2;MemDC2.CreateCompatibleDC(&dc);MemDC2.SelectObject(&p1);// MemDC2与图片链接//在这里,p1保存的是要加载到临时空白画布上的图片,MemDC2是与p1链接的dc(3)将缓冲区位图拷贝到当前画布(屏幕)上dc.BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);(3)’如果是位图的话首先,将与MemDC2链接的位图p1拷贝到临时空白画布MemDC中MemDC.BitBlt(x,y,width,height,& MemDC2,0,0,SRCCOPY); //向草稿绘制第一张图片,x,y,width,height请自行设置其次,将草稿绘制到屏幕上dc.BitBlt(0,0,width,height,&MemDC,0,0,SRCCOPY);(4)释放内存缓冲区//绘图完成后的清理MemBitmap.DeleteObject();MemDC.DeleteDC();MemDC2.DeleteDC();下面是一个不使用和使用双缓存的例子使用双缓存//CPoint ptCenter;//CRect rect, ellipseRect;//GetClientRect(&rect); //获得窗口客户区的大小//ptCenter = rect.CenterPoint(); //获得矩形的中心点,目的是为了确定后面同心圆图像的圆心//CDC dcMem; // 创建用于缓冲作图的内存DC对象dcMem//CBitmap bmp; // 创建内存中存放临时图像的位图对象bmp//dcMem.CreateCompatibleDC(pDC); // 依附窗口DC(窗口对象为pDC),创建兼容内存DC(就是创建一个内存DC,所有图形先画在这上面)//bmp.CreateCompatibleBitmap(&dcMem, rect.Width(), rect.Height());// 在兼容内存DC上,创建兼容位图//dcMem.SelectObject(&bmp); // 将位图选入内存DC//dcMem.FillSolidRect(rect, pDC->GetBkColor());// 按照原有背景色填充客户区,否则会成为黑色,同时也使内存DC的背景色保持一致//// 绘图操作//for (int i = 60; i > 0; --i)//{// ellipseRect.SetRect(ptCenter, ptCenter);// ellipseRect.InflateRect(i * 5, i * 5);// dcMem.Ellipse(ellipseRect); // 在内存DC上绘图,做同心圆图像//}//pDC->BitBlt(0, 0, rect.Width(), rect.Height(),// &dcMem, 0, 0, SRCCOPY); // 将内存DC上的图像复制到前台pDC,即实际屏幕对象pDC//dcMem.DeleteDC(); // 删除内存DC//bmp.DeleteObject(); // 删除内存位图不使用双缓存CPoint ptCenter;CRect rect,ellipseRect;GetClientRect(&rect);ptCenter = rect.CenterPoint();for(int i=60;i>0;i--){ellipseRect.SetRect(ptCenter,ptCenter);ellipseRect.InflateRect(i*5,i*5);pDC->Ellipse(ellipseRect);}下面的例子是加载两幅图片CBitmap p1,p2;//这是要画的位图,假设已加载好CDC dc;//这是窗口的DC,假设已加载好//创建两个临时dc,dc1为向窗口绘图的“草稿”,dc2为与源位图连接的dc(实际上dc2也可以用别的方法代替,这只是我的癖好)CDC dc1,dc2;dc1.CreateCompatibleDC(&DC);dc2.CreateCompatibleDC(&DC);//创建一个临时bitmap作为“画布”,与dc1连接CBitmap bm;CBitmap *Oldbm1,Oldbm2bm.CreateCompatibleBitmap(pDC,width,height); //长度宽度设置成与绘图面积一样大dc1.SelectObject(&bm);dc2.SelectObject(&p1);//dc2与第一张图片链接dc1.BitBlt(x,y, width,height,&dc2,0,0,SRCCOPY); //向草稿绘制第一张图片,x,y,width,height请自行设置dc2.SelectObject(&p2);//dc2与第一张图片链接dc1.BitBlt(x,y, width,height,&dc2,0,0,SRCCOPY); //向草稿绘制第二张图片//将草稿转移至窗口dc.BitBlt(0,0, width,height,&dc1,0,0,SRCCOPY);//清理工作...。

基于STM32芯片环形缓冲区的构建(IAREWARM环境)

基于STM32芯片环形缓冲区的构建(IAREWARM环境)

基于STM32芯片环形缓冲区的构建(IAREWARM环境)除了通信协议以外,设置数据缓冲区是完善不同速率,或不同系统之间通信的解决方案之一。

本文设置了一个环形缓冲区,来接收PC 上位机通过串口(COM口)发往STM32下位机的数据,具体代码如下://接受数据处理相关定义#define RxSize 16 //接收缓冲区大小u8 RxBuffer[RxSize]; //缓冲区定义u8 RxPut = 0; //接收缓冲区(环形)的当前放人位置u8 RxGet = 0; //接收缓冲区()的当前取出位置u8 RxEff = 0; //接收缓冲区(环形)中的元素总数量//利用去除存入缓冲区数据的冗余信息后,将有用数据存入命令缓存u8 command[7]; //命令缓存,用于存放收到的命令(最长命令为五个字符,其中加上起始符和结尾符,共7个字符)u8 comm_length; //命令长度,命令解码时用//接受缓冲区读取函数u8 Address(u8);void put(u8 z);u8 get(void);void decode(void);//解码程序void get_command(u8 c); //识别命令格式,并存入命令缓冲区//函数实现/************************************************************** *****************环形缓冲区的地址编号计算函数,,如果到达唤醒缓冲区的尾部,将绕回到头部。

环形缓冲区的有效地址编号为:0到 RxSize*************************************************************** ****************///接受缓冲区地址运算u8 Address (u8 i){return (i+1)==RxSize ? 0 : i+1;}//从环形缓冲区中取一个元素u8 get(void){u8 pos;if (RxEff>0){pos = RxGet;RxGet = Address(RxGet); RxEff--;return RxBuffer[pos];}elsereturn '~'; //标志缓冲区中的数据被读完}//向环形缓冲区中放人一个元素void put(u8 z){if (RxEff<RxSize){RxBuffer[RxPut]=z;RxPut = Address(RxPut);RxEff++;}//else//; //若缓冲区为满,则放弃当前数据}//缓冲区的应用/************************************************************** ****************** Function Name : USART1_IRQHandler* Description : This function handles USART1 global interrupt request.* Input : None* Output : None* Return : None*************************************************************** ****************/void USART1_IRQHandler(void) //利用串口中断,来填充缓冲区{if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)//检查EXTI_Line3线路触发请求发生与否,若发生中断目前将不被执行(即保持原先各种操作){u8 z;z=(u8)( USART_ReceiveData(USART1)& (u8)0xFF);if(z == '%') //单字符命令处理函数 //联机状态软件控制部分{para_send((u8)('%'),0); //同意联机(此处SBUF为发送缓冲器:发送确认信号)while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);//等待发送完成}else // C为除'%',其他字符(多字符命令处理函数)---先将命令存入缓冲区{put(z); //字符送入缓冲区}/* Clear the USART_IT_RXNE pending bit */// USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除EXTI线路挂起位,标志当前中断已经发生}}在main函数中的while循环中,读取缓冲区数据,并做相关响应main(void){#ifdef DEBUGdebug();#endif/* 调用各外设配置函数,使外设处于工作状态 -----------------------------------*//* 写解调体统核心程序 -----------------------------------*/while(1){c = get(); //读缓冲区get_command(c); //命令解析(数码管显示相应命令,并置stopflag以便于下属语句驱动电机)//根据解析代码实现电机运行相关参数设置if(stopflag==1) //根据解析参数正常运行{curPosition=GoSteps1(steps,direction,curPosition);} //按指定方向,连续运行steps步else if(stopflag==2) //复位代码(向右复位){stepfm=curPosition - Osteps;curPosition=GoSteps1(stepfm,RIGHT,curPosition); }else if(stopflag==3) //复位代码(向左复位){stepfm= Osteps-curPosition ;curPosition=GoSteps1(stepfm,LEFT,curPosition);}else if(stopflag==4) //复位代码(不动){curPosition=GoSteps1(0,RIGHT,curPosition); }else if(stopflag==5) //单步运行{curPosition=GoSteps1(1,direction,curPosition); }}}附录:void get_command(u8 c) //识别命令格式,并存入命令缓冲区command[comm_length]{if(c == '@')//命令字开始{comm_length = 0;}else if(c == '$')//命令字结束{decode();//执行解命令,对stopflag赋值}else if((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')) {command[comm_length] = c; //指令字符放入命令缓存comm_length++;}else{//空语句} }。

实验指导书基于STM32的嵌入式系统原理与设计.docx

实验指导书基于STM32的嵌入式系统原理与设计.docx

实验指导书(实验)课程名称:基于STM32的嵌入式系统设计实验实验一电路板焊接与调试-•实验简介完成实验板上部分兀件的焊接,焊接完成后进行基本测试。

实验目的及原理掌握STM32F103实验板的基本原理,掌握焊接电路板的基本技能,掌握下载测试程序的基本方法。

原理:详细内容参考教材《基于STM32的嵌入式系统原理与设计》MCU和周边电路如图为MCU及其周边电路。

图1 MCU及其周边电路1. 唤醒电路,高有效,不按时接220K 电阻下拉。

2. 复位电路,低有效。

带RC 启动复位。

3. 配置启动,用跳线选择B00T1和BOOTO 接高电平或低电平。

4. 高速晶振电路,采用8M 晶振,在STM32内部倍频为72M 。

5. AD 参考电路,采用LC 滤波,可跳线选择直接接VCC 或通过TL431稳压电路产生的参考电压。

6. 后备电池。

可通过跳线选择直接接VCC 或电池。

7. AD 输入,可选择使用RC 滤波,共8路。

&低速晶振电路,选用32. 768kHz 晶振,为产生准确的串口波特率。

USB 转串口电路USB 转串口电路可以方便没有串口的笔记本电脑用户通过USB 接口下载代码到FLASH 中,及进行RS232串行通信。

USB 转串口芯片是CP2102,该芯片稳定性较好。

当其正常工作的时候,灯LED6亮。

该 芯片DP/D+引脚连MINI USB 接口的脚3, DM/D-引脚连MINI USB 接口的脚2,为一对USB 输入输出线。

TXD 与 RXD 引脚接 MCU 的 PA10 (USART1_RX)和 PA9 (USART1_TX)。

I2C 接口电路Jusbm USB图2 USB 转串口接口电路14NCNCNCNCNCNCNCONS.LO(一XE- (一ON 二 N (INHdsfls 二N 二一二乂ON本书选择的EEPROM 是AT24C02是256字节的电可擦出PROM,通过I2C 协议与STM32 进行通信,连接十分简单。

实现一个双缓冲队列

实现一个双缓冲队列

实现⼀个双缓冲队列在⽣产者-消费者模式中,我们常常会使⽤到队列,这个队列在多个线程共享访问时存在互斥和竞争操作, 意味着每次访问都要加锁。

如何更好的如何减少锁竞争次数呢 ?今天要介绍的双缓冲队列就是个不错的选择。

双缓冲队列就是冲着同步/互斥的开销来的。

我们知道,在多个线程并发访问同⼀个资源的时候,需要特别注意线程的同步问题。

稍稍不注意,噢货,程序结果不正确了。

直接上图:在双缓冲队列中,锁除了起到保护数据安全的作⽤来,还要承担线程调度的任务。

双队列交换位置和任务⼊队列都需要对当前队列进⾏操作,因此,他们是互斥的操作。

消费操作放在单独的线程中,在没有任务进来时,需要将线程置为等待状态。

使⽤两个信号量,来调度⼊列队和交换队列的操作。

同时,我们还需要⼀个信号量,在没有任务⼊队列时,阻塞整个消费线程。

主要使⽤ AutoResetEvent ,ManualResetEvent ,它们的具体使⽤可以看⼀下园⼦⾥的⽂章:/springyangwc/archive/2011/10/12/2208991.html1. AutoResetEvent.WaitOne()每次只允许⼀个线程进⼊,当某个线程得到信号后,AutoResetEvent 会⾃动⼜将信号置为不发送状态,则其他调⽤WaitOne 的线程只有继续等待,也就是说AutoResetEvent ⼀次只唤醒⼀个线程;2. ManualResetEvent 则可以唤醒多个线程,因为当某个线程调⽤了ManualResetEvent.Set()⽅法后,其他调⽤WaitOne 的线程获得信号得以继续执⾏,⽽ManualResetEvent 不会⾃动将信号置为不发送;3. 也就是说,除⾮⼿⼯调⽤了ManualResetEvent.Reset()⽅法,则ManualResetEvent 将⼀直保持有信号状态,ManualResetEvent 也就可以同时唤醒多个线程继续执⾏。

stm32串口环形缓冲区开发实例代码

stm32串口环形缓冲区开发实例代码

1. 引言在嵌入式开发中,stm32系列是非常常用的微控制器芯片之一。

在实际的项目开发中,串口通信是非常常见的需求,而串口环形缓冲区的开发在串口通信中也扮演着非常重要的角色。

本文将从实际应用的角度出发,介绍如何在stm32中开发串口环形缓冲区,并提供相应的实例代码,帮助读者更好理解和应用该技术。

2. 串口环形缓冲区概述在串口通信中,特别是在高速串口通信中,往往需要处理大量的数据。

而传统的串口接收方式往往会遇到数据丢失、溢出等问题。

为了解决这些问题,通常会采用串口环形缓冲区来缓存数据。

串口环形缓冲区可以很好解决数据处理不及时导致的数据丢失问题,并能够提高数据的处理效率。

3. stm32串口环形缓冲区的开发在stm32中,开发串口环形缓冲区的关键是要理解串口接收中断的机制,并结合环形缓冲区的原理来实现对接收数据的缓存和处理。

以下是一个简单的示例代码,用于说明如何在stm32中实现串口环形缓冲区。

```c#include "stm32f4xx.h"#define BUFFER_SIZE 100uint8_t buffer[BUFFER_SIZE];volatile uint8_t head = 0;volatile uint8_t tail = 0;void USART1_IRQHandler() {if(USART1->SR & USART_SR_RXNE) { buffer[head] = USART1->DR;head = (head + 1) % BUFFER_SIZE; }}int main() {// 初始化串口// ...// 使能串口接收中断USART1->CR1 |= USART_CR1_RXNEIE; NVIC_EnableIRQ(USART1_IRQn);// ...while(1) {if(head != tail) {// 从缓冲区中读取数据并进行处理// ...tail = (tail + 1) % BUFFER_SIZE;}}}```4. 实例代码解析以上示例代码中,我们定义了一个长度为100的缓冲区buffer,并使用head和tail两个指针来分别指向缓冲区的头部和尾部。

stm32串口环形缓冲区开发实例代码

stm32串口环形缓冲区开发实例代码

stm32串口环形缓冲区开发实例代码【STM32串口环形缓冲区开发实例代码】近年来,随着物联网技术的快速发展,嵌入式系统的需求日益增加。

而在嵌入式系统中,串口通信一直都是一项非常重要的功能。

而在使用串口通信时,我们经常会遇到一个问题,即数据接收和发送的速度不匹配导致数据丢失的情况。

为了解决这个问题,我们可以使用环形缓冲区来进行数据的存储和管理。

本文将以STM32单片机为例,介绍如何开发串口环形缓冲区,并给出相应的实例代码。

一、环形缓冲区的原理环形缓冲区是一种循环队列,它具有固定的大小,并且在填满数据后会自动循环覆盖之前的数据。

这种数据结构可以很好地解决数据接收和发送速度不匹配的问题。

在串口通信中,我们可以将接收到的数据存储到环形缓冲区中,在发送数据时,则可以从环形缓冲区中取出数据进行发送。

二、环形缓冲区的实现在STM32单片机中,我们可以通过使用指针和数组来实现环形缓冲区。

我们需要定义缓冲区的大小,然后创建两个指针,分别指向缓冲区的头部和尾部。

当接收到新的数据时,我们将数据存储到尾部指针所指向的位置,并将尾部指针向后移动一个位置。

当需要取出数据进行发送时,我们则从头部指针所指向的位置取出数据,并将头部指针向后移动一个位置。

需要注意的是,当头部指针和尾部指针相遇时,表示缓冲区已满,此时需要进行循环覆盖操作。

下面是一个基于STM32的串口环形缓冲区的实例代码:```c#include "stm32f4xx.h"#define BUFFER_SIZE 256volatile uint8_t rx_buffer[BUFFER_SIZE];volatile uint8_t tx_buffer[BUFFER_SIZE];volatile uint16_t rx_head = 0, rx_tail = 0;volatile uint16_t tx_head = 0, tx_tail = 0;void USART2_IRQHandler(void){if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) {rx_buffer[rx_head] = USART_ReceiveData(USART2);rx_head = (rx_head + 1) % BUFFER_SIZE;}if(USART_GetITStatus(USART2, USART_IT_TXE) != RESET) {if(tx_head != tx_tail){USART_SendData(USART2, tx_buffer[tx_tail]);tx_tail = (tx_tail + 1) % BUFFER_SIZE;}else{USART_ITConfig(USART2, USART_IT_TXE, DISABLE); }}}void send_data(uint8_t data){tx_buffer[tx_head] = data;tx_head = (tx_head + 1) % BUFFER_SIZE;USART_ITConfig(USART2, USART_IT_TXE, ENABLE);}```在上面的代码中,我们定义了两个缓冲区rx_buffer和tx_buffer,并分别设置了头部指针和尾部指针rx_head、rx_tail和tx_head、tx_tail。

STM32F4 ADC-DMA双缓冲模式配置

STM32F4 ADC-DMA双缓冲模式配置

STM32F4 ADC-DMA双缓冲模式配置(亲测有效)/*****************************************************//* 主函数*//*****************************************************/#include "led.h"#include "adc.h"#include "usart1.h"/*ADC1-DMA方式进行采样,在主函数中用串口把数组中的数据打印输出*/extern u16 ADC1_ConvertedValueBuffer0[DMABufferSize];extern u16 ADC1_ConvertedValueBuffer1[DMABufferSize];extern u16 ADC1_ConvertedValue;extern u8 ADC1_DMA_FLAG;int main(){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);delay_init();LED_Init();USART1_Init(9600);ADC1_Init();LED0=0;while(1){if( ADC1_DMA_FLAG == 1 ){ //打印ADC1_ConvertedValueBuffer1数据printf("\r\n***B1***%d\t%d\t%d\t%d\t%d\t***B1***\r\n",ADC1_ConvertedValueBuffer1[9995],ADC1_ConvertedValueBuffer1[9996],ADC1_ConvertedValueBuffer1[9997],ADC1_ConvertedValueBuffer1[9998],ADC1_ConvertedValueBuffer1[9999]);}else{//打印ADC1_ConvertedValueBuffer2数据printf("\r\n***B0***%d\t%d\t%d\t%d\t%d\t***B0***\r\n",ADC1_ConvertedValueBuffer0[9995],ADC1_ConvertedValueBuffer0[9996],ADC1_ConvertedValueBuffer0[9997],ADC1_ConvertedValueBuffer0[9998],ADC1_ConvertedValueBuffer0[9999]);}}}/*****************************************************//* ADC1-DMA.h *//*****************************************************/#include "common.h"#define DMABufferSize 10000void ADC1_Init(void);void DMA2_Stream4_Init(void);/*****************************************************//* ADC1-DMA.c *//*****************************************************/#include "adc.h"/*用DMA双缓冲模式做,根据数据手册258页,在最后一次DMA 传输,将DDS 位置1,则可继续生成请求。

【STM32H7教程】第43章STM32H7的DMA应用之双缓冲控制任意IO和脉冲数控制

【STM32H7教程】第43章STM32H7的DMA应用之双缓冲控制任意IO和脉冲数控制

【STM32H7教程】第43章STM32H7的DMA应⽤之双缓冲控制任意IO和脉冲数控制完整教程下载地址:第43章 STM32H7的DMA应⽤之双缓冲控制任意IO和脉冲数控制本章教程为⼤家讲解定时器触发DMAMUX,控制DMA让GPIO输出PWM以及脉冲数的控制,实际项⽬中有⼀定的使⽤价值。

43.1 初学者重要提⽰43.2 定时器触发DMA驱动设计43.3 DMA板级⽀持包(bsp_tim_dma.c)43.4 DMA驱动移植和使⽤43.5 实验例程设计框架43.6 实验例程说明(MDK)43.7 实验例程说明(IAR)43.8 总结43.1 初学者重要提⽰1. 学习本章节前,务必优先学习第39章和42章,需要对DMAMUX,DMA的基础知识和HAL库的⼏个常⽤API有个认识。

2. 相⽐定时器本⾝⽀持的PWM,这种⽅式更加灵活,可以让任意IO都可以输出PWM,⽽且⽅便运⾏中动态修改输出状态。

43.2 定时器触发DMA驱动设计定时器触发DMAMUX,控制DMA让GPIO输出PWM的实现思路框图如下:下⾯将程序设计中的相关问题逐⼀为⼤家做个说明。

43.2.1 定时器选择使⽤DMA的话,请求信号都是来⾃DMAMUX2,⽽控制DMA做周期性传输的话,可以使⽤定时器触发,这样的话就可以使⽤DMAMUX的请求发⽣器功能,⽀持如下⼏种触发:#define HAL_DMAMUX1_REQ_GEN_DMAMUX1_CH0_EVT 0U#define HAL_DMAMUX1_REQ_GEN_DMAMUX1_CH1_EVT 1U#define HAL_DMAMUX1_REQ_GEN_DMAMUX1_CH2_EVT 2U#define HAL_DMAMUX1_REQ_GEN_LPTIM1_OUT 3U#define HAL_DMAMUX1_REQ_GEN_LPTIM2_OUT 4U#define HAL_DMAMUX1_REQ_GEN_LPTIM3_OUT 5U#define HAL_DMAMUX1_REQ_GEN_EXTI0 6U#define HAL_DMAMUX1_REQ_GEN_TIM12_TRGO 7U我们这⾥使⽤的是TIM12_TRGO。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

终于开荤了~~~DMA 先抄一小段DMA的说明。

对于没玩过DMA 的朋友,这里简单说一下DMA,用自己的语言说吧,那就是,从某个位置传输数据到某个位置,如果不用DMA,那要CPU参与操作,一个字节一个字节地搬,效率高点的,就一个字一个字地搬.但当你用了DMA 后,那就是只需要设置:A.从哪里开始搬; B,搬到哪里去;C以字节方式搬还是半字还是字;D:一共搬多少个.之后,启动DMA.CPU内部就会开始搬数据了,整个搬数据的过程都不需要指令的参与,唯一要做的,就是检测什么时候搬完.你可以扫描寄存器,也可以用中断.这里,我使用了中断.具体设置功能看注释就可以明白了.注意一点就是,有一个设置:DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;这个是外设的地址不递增.也就是说,每次搬动,都是从源头,也就是USART1的DR寄存器搬,但内存地址却是递增的:DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;这个历程实现了接受串口的数据写到FLASH 之中工作,而DMA的作用在于将串口收寄存器USART1->D u8 USART1_DMA_Buf1[512]; 写满512个字节之后将进入DMA中断(通道5)在这里修改DMA 的内存写入u8 USART1_DMA_Buf2[512]; ,同时标记下次的入口Free_Buf_No=BUF_NO1; 与Buf_Ok=TRUE; 证明已USART1_DMA_Buf1中的数据写入FLASH .又抄了一点这次使用的是双缓冲,也有人叫乒乓缓冲.因为一般情况下,串口的数据DMA 传输进BUF1 的过程中,是不建议对BUF1 进行操作的.但由于串口数据是不会等待的直传,所以你总不能等BUF1 满了,才往FLASH 上写,因为这时候串口数据依旧是源源不断.于是,使用双缓冲就变的理所当然了.当BUF1 满了的时候,就马上设置DMA的目标为BUF2,并且BUF1的数据往25F080上灌.当串口DMA写满了BUF2的时候,再设置DMA的目标为BUF1,此时再操作BUF2写进25F080.如此一直循环,就好像打乒乓球那样吧,所以就叫乒乓缓冲.用这个方法的速度极限就是,你必须确保两点a.DMA 灌满了BUF1 的时候,会发生中断,此时切换DMA 的目标缓冲为BUF2,而且切换的过程必须在新的串口数据溢出之前完成.b.在DMA的BUF1满之前,另外一个有数据的BUF2必须能全部写进25F080,其中包括了遇到新的扇区边界而要刷除扇区的操作时间!!可以看出,BUF的增大,并不能够很大程度的提升速度极限.假设USART 与FLASH 的底层驱动已经写好了。

点击查看。

/************DMA方式传输***************************/#define SRC_USART1_DR (&(USART1->DR)) //串口接收寄存器作为源头//DMA目标缓冲,这里使用双缓冲u8 USART1_DMA_Buf1[512];u8 USART1_DMA_Buf2[512];bool Buf_Ok; //BUF是否已经可用BUF_NO Free_Buf_No; //空闲的BUF号typedef enum {BUF_NO1=0,BUF_NO2=1}BUF_NO;DMA_InitTypeDef DMA_InitStructure;void USART_DMAToBuf1(void){RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开DMA时钟DMA_DeInit(DMA1_Channel5); //将DMA的通道1寄存器DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)SRC_USART1_DR; //源头BUF既是(&(U DMA_InitStructure.DMA_MemoryBaseAddr = (u32)USART1_DMA_Buf1; //目标BUF 既是要写在DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设作源头//外设是DMA_InitStructure.DMA_BufferSize = 512; //DMA缓存的大小单位在下边设定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_PeripheralDataSize_Byte; //内存字节为单位DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在循环缓存模式DMA_InitStructure.DMA_Priority = DMA_Priority_High; //4优先级之一的(高优先)VeryH DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非内存到内存DMA_Init(DMA1_Channel5, &DMA_InitStructure); //根据DMA_InitStruct中指定的参DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE); //DMA5传输完成中断USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能USART1的接 /************************************************************************************************************************* //初始化BUF标志Free_Buf_No=BUF_NO2; //因为DMA_InitStructure.DMA_MemoryBaseAddr = (u32)USART1_DMA_Buf1 Buf_Ok=FALSE; //此时没有数据准备完成当然FALSEDMA_Cmd(DMA1_Channel5, ENABLE); //正式允许DMA}再来看看DMA中断://u16 DataCounter;extern DMA_InitTypeDef DMA_InitStructure;void DMA1_Channel5_IRQHandler(void){if(DMA_GetITStatus(DMA1_IT_TC5)) //通道5传输完成中断TC还有传输过半中断HT 错误中断TE 全局{//DataCounter = DMA_GetCurrDataCounter(DMA1_Channel5);//获取剩余长度,一般都为0,调试用DMA_ClearITPendingBit(DMA1_IT_GL5); //清除全部中断标志//转换可操作BUFif(Free_Buf_No==BUF_NO1){DMA_InitStructure.DMA_MemoryBaseAddr = (u32)USART1_DMA_Buf1;DMA_Init(DMA1_Channel5, &DMA_InitStructure);Free_Buf_No=BUF_NO2;}else{DMA_InitStructure.DMA_MemoryBaseAddr = (u32)USART1_DMA_Buf2;DMA_Init(DMA1_Channel5, &DMA_InitStructure);Free_Buf_No=BUF_NO1;}Buf_Ok=TRUE; //有准备好的数据了}}写FLASH的操作while(1){if(Buf_Ok==TRUE){LED1_ON; //一个标记Buf_Ok=FALSE; //操作了准备好的数据if((addr%4096)==0) //跨越一个扇区,则需要先刷除{SST25SectorErase(addr);sector_count++;}if(Free_Buf_No==BUF_NO1)SST25Write(addr,USART1_DMA_Buf1,512);elseSST25Write(addr,USART1_DMA_Buf2,512);addr+=512;Timer1=5000; //时间重置LED1_OFF;}//检测超时开了定时器if(Timer1==0) //五秒内没准备好的数据{//获取长度len=512-DMA_GetCurrDataCounter(DMA1_Channel5);//写入最后数据if(Free_Buf_No==BUF_NO1)SST25Write(addr,USART1_DMA_Buf2,len);elseSST25Write(addr,USART1_DMA_Buf1,len);addr+=len;break;}}还是很简单的。

有一点比较困扰就是FlagStatus标志位与ITStatus中断标志位的区别。

其实就DMA 来说DMA_IT值甚至2者值的获取都是读DMA ISR register 的值清除也是设置DMA_IFCR 寄存器来清除的所以貌似没有在但我还不可保证IT 与FLAG 的值总是相同的这个存在也许是为了兼容但一定有其意义务必不可混用即使有。

相关文档
最新文档