采用串口DMA双缓冲方法,快速更新外部FLASH中文字库。更新原子哥例程的4个字库仅需一分钟左右!

2019-07-21 02:01发布

    最近在玩一个320*480的液晶,想显示中文字体,后来看了原子哥的例程是用FAT文件系统更新的,觉得工程比较大、较麻烦。所以我又开始想借助强大的串口哥实现字库更新了。哈哈.....
  说做就做,但是串口历来速度比较慢,还要考虑到FLASH的写入速度问题,发现网上很多实现方案大都是采用串口发一个字节存储一个字节的方案,那差不多就只能是115200的波特率来存储了。后来为了提高速度,我决定利用STM32 DMA双缓冲的方法,定义两个4096字节的串口DMA缓冲BUF,存满一个换一个的方法。那么可以利用程序稍微计算一下4096字节写入FLASH的时间是多少?
 定义一个4096字节大小的缓冲区:  u8 TEXT_Buffer[4096]={"Holle ..."};
 设置定时器以1MHZ的频率计数:TIM3_CH3_Cap_Init(0XFFFFFFFF,84-1); 
 先擦除FLASH块在写入,计算写入时间:
  W25QXX_Erase_Sector(0);
  TIM_Cmd(TIM3,ENABLE ); //使能定时器3
   W25QXX_Write(TEXT_Buffer,0,4096);
  TIM_Cmd(TIM3,DISABLE ); //关闭定时器3
  TIM3CH3_Count=TIM_GetCounter(TIM3);//获取当前的捕获值.
  printf("Used time:%d us ",TIM3CH3_Count);
   结果:多次复位看的时间很稳定,都是14ms的样子


 那再来试试先不擦除FLASH块,直接写入看看时间:
 // W25QXX_Erase_Sector(0);
  TIM_Cmd(TIM3,ENABLE ); //使能定时器3
  W25QXX_Write(TEXT_Buffer,0,4096);
  TIM_Cmd(TIM3,DISABLE ); //关闭定时器3
  TIM3CH3_Count=TIM_GetCounter(TIM3);//获取当前的捕获值.
  printf("Used time:%d us ",TIM3CH3_Count);
 结果:多次复位看时间,哎呀,貌似有些慢了。。。63ms的样子
 

行。那再来算一下在先擦除块再写入的情况下(13ms),能够允许的最高波特率是多少;
串口发送一个字节的数据是10个比特(在8N1的情况下),那么发送4096个字节需要发送 4096*10 个比特,那么时间不短于13ms的最高比特率就大概是:   4096*10/0.013 = 3150769 ;貌似很大了,没有概念,估计STM32都没法设置这么高了。

 好吧,那就直接从XCOM里面选个最高的波特率来算好了。XCOM可选的最高波特率是 1382400,发送4096个字节时间是:4096*10*1000/1382400=29.6ms;哈哈,看来按照DMA双缓冲4096个字节的方案貌似扛得住高波特率呀。但是把STM32设置成1382400之后发现通信不正常了,好吧,那就降一格。选个921600试试,发现通信正常。那就暂且把波特率设置为921600了。发送4096个字节的时间为:4096*10*1000/921600 = 44.4ms;
 综合考虑:波特率设置为921600;



程序设计思路:设置串口波特率为921600,定义两个4096字节大小的缓冲区BUF_0,BUF_1,初始化DMA为双缓冲接收模式。上电自检FLASH有无字库,没有则先擦除3.1M空间的FLASH以备更新字库。XCOM显示提示字库更新顺序,以及液晶显示字库更新进度。更新完毕后显示 12*12,16*16,24*24 三行不同字体大小的汉字。

注意:程序中根据DMA中断间隔来判断某一字库是否已经发送完毕,方法是判断两个DMA中断的时间间隔大于正常间隔10ms则认为已发送完毕。

USART.c代码:
[mw_shl_code=c,true]//初始化IO 串口1 //bound:波特率 void uart_init(u32 bound){ //GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE); //使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART6,ENABLE);//使能USART1时钟 //串口1对应引脚复用映射 GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_USART6); //GPIOA9复用为USART1 GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_USART6); //GPIOA10复用为USART1 //USART1端口配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //GPIOA9与GPIOA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOC,&GPIO_InitStructure); //初始化PA9,PA10 //USART1 初始化设置 USART_InitStructure.USART_BaudRate = bound;//波特率设置 USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 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; //收发模式 USART_Init(USART6, &USART_InitStructure); //初始化串口1 USART_Cmd(USART6, ENABLE); //使能串口1 USART_ClearFlag(USART6, USART_FLAG_TC); USART_ITConfig(USART6, USART_IT_RXNE, ENABLE);//开启相关中断 USART_DMACmd(USART6,USART_DMAReq_Rx,ENABLE); //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART6_IRQn;//串口1中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、 //DMA配置 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2 时钟使能 DMA_DeInit(DMA2_Stream1); //恢复默认值 串口1接收是DMA2数据流2通道4 while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待 DMA 可配置 /* 配置 DMA Stream */ DMA_InitStructure.DMA_Channel = DMA_Channel_5; //通道选择 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART6->DR;//DMA 外设地址 DMA_InitStructure.DMA_Memory0BaseAddr = (u32)Usart6_Rece_Buf0;//DMA 存储器 0 地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设到存储器模式 DMA_InitStructure.DMA_BufferSize = Usart6_DMA_Len;//数据传输量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8 位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8 位 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//注意:这里设置为循环模式,不然不能启动第二次传输 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//FIFO 模式禁止 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//FIFO 阈值 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输 DMA_DoubleBufferModeConfig(DMA2_Stream1, (uint32_t)Usart6_Rece_Buf1, DMA_Memory_0); //Usart6_Rece_Buf0 先缓冲 DMA_DoubleBufferModeCmd(DMA2_Stream1, ENABLE); DMA_Init(DMA2_Stream1, &DMA_InitStructure);//初始化 DMA Stream DMA_Cmd(DMA2_Stream1, ENABLE); //开启 DMA 传输 DMA_ITConfig(DMA2_Stream1,DMA_IT_TC,ENABLE); //使能DMA传输完成中断 //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream1_IRQn;//DMA2_Stream1_IRQn中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority =2; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、 } //开启一次 DMA 传输 //DMA_StreamxMA 数据流,DMA1_Stream0~7/DMA2_Stream0~7 //ndtr:数据传输量 void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr) { DMA_Cmd(DMA_Streamx, DISABLE); //关闭 DMA 传输 while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //确保 DMA 可以被设置 DMA_SetCurrDataCounter(DMA_Streamx,ndtr); //数据传输量 DMA_Cmd(DMA_Streamx, ENABLE); //开启 DMA 传输 } void USART6_IRQHandler(void) //串口1中断服务程序 { if(USART_GetITStatus(USART6, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { USART_ReceiveData(USART6);//(USART1->DR); //读取接收到的数据 } } void DMA2_Stream1_IRQHandler(void) //串口1中断服务程序 { if(DMA_GetFlagStatus(DMA2_Stream1,DMA_FLAG_TCIF1)==SET) { DMA_ClearFlag(DMA2_Stream1,DMA_FLAG_TCIF1); //**********************数据帧处理******************// if(1==DMA_GetCurrentMemoryTarget(DMA2_Stream1)) GBK_BUF_Flag=0; else GBK_BUF_Flag=1; //**************************************************// } } [/mw_shl_code]
字库更新部分代码: [mw_shl_code=c,true]//字库区域占用的总扇区数大小(3个字库+unigbk表+字库信息=3238700字节,约占791个W25QXX扇区) #define FONTSECSIZE 791 //字库存放起始地址 #define FONTINFOADDR 1024*1024*0 //字库存放首地址 //定义各个字库的大小 #define UNIGBK 171*1024 //171KB #define GBK12_FONSIZE 562*1024 //562KB #define GBK16_FONSIZE 749*1024 //749KB #define GBK24_FONSIZE 1684*1024 //1684KB //显示当前字体更新进度 //x,y:坐标 //size:字体大小 //fsize:整个文件大小 //pos:当前文件指针位置 u32 fupd_prog(u16 x,u16 y,u8 size,u32 fsize,u32 pos) { float prog; u8 t=0XFF; prog=(float)pos/fsize; prog*=100; if(t!=prog) { LCD_ShowString(x+3*size/2,y,240,320,size,"%"); t=prog; if(t>100)t=100; LCD_ShowNum(x,y,t,3,size);//显示数值 } return 0; } //更新某一个 //x,y:坐标 //size:字体大小 //fx:更新的内容 0,ungbk;1,gbk12;2,gbk16;3,gbk24; //返回值:0,成功;其他,失败. u8 updata_fontx(u16 x,u16 y,u8 size,u8 fx) { u32 flashaddr=0; u8 res; u32 offx=0; u32 fsize=0; switch(fx) { case 0: //更新UNIGBK.BIN ftinfo.ugbkaddr=FONTINFOADDR+sizeof(ftinfo); //信息头之后,紧跟UNIGBK转换码表 fsize=ftinfo.ugbksize=UNIGBK; //UNIGBK大小 flashaddr=ftinfo.ugbkaddr; printf("Please send UNIGBK.bin "); break; case 1: ftinfo.f12addr=ftinfo.ugbkaddr+ftinfo.ugbksize; //UNIGBK之后,紧跟GBK12字库 fsize=ftinfo.gbk12size=GBK12_FONSIZE; //GBK12字库大小 flashaddr=ftinfo.f12addr; //GBK12的起始地址 printf("Please send GBK12.FON "); break; case 2: ftinfo.f16addr=ftinfo.f12addr+ftinfo.gbk12size; //GBK12之后,紧跟GBK16字库 fsize=ftinfo.gbk16size=GBK16_FONSIZE; //GBK16字库大小 flashaddr=ftinfo.f16addr; //GBK16的起始地址 printf("Please send GBK16.FON "); break; case 3: ftinfo.f24addr=ftinfo.f16addr+ftinfo.gbk16size; //GBK16之后,紧跟GBK24字库 fsize=ftinfo.gkb24size=GBK24_FONSIZE; //GBK24字库大小 flashaddr=ftinfo.f24addr; //GBK24的起始地址 printf("Please send GBK24.FON "); break; } fupd_prog(x,y,size,fsize,offx); //进度显示 while(1)//死循环执行 { if(GBK_OVER_Flag) GBK_OVER_Flag++; if(GBK_BUF_Flag!=2) { GBK_OVER_Flag=1; if(GBK_BUF_Flag==0) W25QXX_Write(Usart6_Rece_Buf0,offx+flashaddr,Usart6_DMA_Len); //开始写入Usart6_DMA_Len个数据 else if(GBK_BUF_Flag==1) W25QXX_Write(Usart6_Rece_Buf1,offx+flashaddr,Usart6_DMA_Len); //开始写入Usart6_DMA_Len个数据 offx+=Usart6_DMA_Len; GBK_BUF_Flag=2; fupd_prog(x,y,size,fsize,offx); //进度显示 } delay_us(100); if(GBK_OVER_Flag>(WATE_TIME+10)*10) //超过正常时间10ms则说明此字库发送完毕 break; } if(DMA_GetCurrentMemoryTarget(DMA2_Stream1)==1) W25QXX_Write(Usart6_Rece_Buf1,offx+flashaddr,Usart6_DMA_Len-DMA_GetCurrDataCounter(DMA2_Stream1));//将DMA最后的一帧数据写入FLASH else W25QXX_Write(Usart6_Rece_Buf0,offx+flashaddr,Usart6_DMA_Len-DMA_GetCurrDataCounter(DMA2_Stream1));//将DMA最后的一帧数据写入FLASH printf("This Font updated successfull! "); uart_init(BAUD_RATE); //重新初始化串口及DMA GBK_OVER_Flag=0; return res; } //更新字体文件,UNIGBK,GBK12,GBK16,GBK24一起更新 //x,y:提示信息的显示地址 //size:字体大小 //提示信息字体大小 //返回值:0,更新成功; // 其他,错误代码. u8 update_font(u16 x,u16 y,u8 size) { u16 i,j; LCD_ShowString(x,y,240,320,size,(u8*)"Erasing sectors... ");//提示正在擦除扇区 for(i=0;i<FONTSECSIZE;i++) //先擦除字库区域,提高写入速度 { fupd_prog(x+20*size/2,y,size,FONTSECSIZE,i);//进度显示 W25QXX_Read((u8*)Usart6_Rece_Buf1,((FONTINFOADDR/4096)+i)*4096,4096);//读出整个扇区的内容(借用一下DMA缓冲区) for(j=0;j<4096;j++)//校验数据 { if(Usart6_Rece_Buf1[j]!=0XFF)break;//需要擦除 } if(j!=4096)W25QXX_Erase_Sector((FONTINFOADDR/4096)+i); //需要擦除的扇区 } delay_ms(100); LCD_ShowString(x,y,240,320,size,(u8*)"Updating UNIGBK.BIN "); updata_fontx(x+20*size/2,y,size,0); //更新GBK12.FON LCD_ShowString(x,y,240,320,size,(u8*)"Updating GBK12.FON "); updata_fontx(x+20*size/2,y,size,1); //更新GBK12.FON LCD_ShowString(x,y,240,320,size,(u8*)"Updating GBK16.FON "); updata_fontx(x+20*size/2,y,size,2); //更新GBK16.FON LCD_ShowString(x,y,240,320,size,(u8*)"Updating GBK24.FON "); updata_fontx(x+20*size/2,y,size,3); //更新GBK16.FON //全部更新好了 ftinfo.fontok=0XAA; W25QXX_Write((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo)); //保存字库信息 printf("All Font file updated successfull!!! "); LCD_Clear(WHITE); return 0; }[/mw_shl_code] 图片:



工程见附件。

测试视屏:http://v.youku.com/v_show/id_XMTQwNDk3NDU1Ng==.html





 









友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
49条回答
八度空间
1楼-- · 2019-07-22 23:27
很好,很牛逼,谢谢分享!!!
513393302@qq.co
2楼-- · 2019-07-23 02:10
谢谢楼主分享,刚好准备搞下双缓存,谢谢,呵呵
1201yuge
3楼-- · 2019-07-23 05:47
回复【14楼】八度空间:
---------------------------------
大牛也来了,多多交流。。
hello_galaxy
4楼-- · 2019-07-23 11:07
【串口+双缓存+DMA】相比【串口+双缓存】的优势在哪?
ricefat
5楼-- · 2019-07-23 12:21
回复【17楼】hello_galaxy:
---------------------------------
DMA的优势是什么
1201yuge
6楼-- · 2019-07-23 12:35
回复【17楼】hello_galaxy:
---------------------------------
回复【18楼】ricefat:
---------------------------------
DMA的优势就是不需要CPU干预了,像我上面的程序,电脑可以通过921600的波特率一次性发送一个2M字节的文件给单片机,单片机通过DMA自动的把数据循环缓冲到两个4096字节的BUF里面,每次缓冲满一个BUF会自动调到另一个BUF缓冲区,同时产生一个中断告诉CPU已收到一次4096字节数据,然后处理。  那么单片机就是间隔 4096*10*1000/921600=44.4ms 处理一下数据就可以实现串口发过来的大文件了。如果不这么用,单片机每次接收到一个字节都要中断一次,那情况就不同了,这就是DMA和双缓冲的好处了。

一周热门 更多>