查看: 406|回复: 0

[教程] 17..STM32F469I--- LCD硬件加速应用

[复制链接]

主题

好友

1万

积分

翰林

  • TA的每日心情
    郁闷
    8 小时前
  • 签到天数: 740 天

    连续签到: 35 天

    [LV.9]以坛为家II

    发表于 2018-1-12 14:48:59 |显示全部楼层
    【STM32F469I试用】+ LCD硬件加速应用【转】

    关于STM32F469I-DISCO,这篇帖子只介绍了使用液晶的BSP库,但是这个不是重点,重点是后面的福利——自己之前写的两个有用的模块:简单CUI(命令用户接口)模块,基于超级终端的外置键盘。本帖就分3个部分来讲
    1. 使用液晶的BSP库
    STM32F469I-DISCO板的一大特点就是使用了一块高分屏,这篇帖子就来研究一下使用HAL库和BSP库来使用液晶屏的方法。
    1.1 DSI模式简介
           STM32F469I-DISCO的液晶控制模块LTDC是和DSI模块一起使用的,DSI有两种模式CommandMode和Video mode
    DSI(Video Mode)视频模式.
    这种工作模式与传统RGB接口相似,主机需要持续刷新显示器。由于不使用专用的数据信号传输同步信息,控制信号和RGB数据是以报文的形式通过MIPI总线传输的。因为主机需要定期刷新显示器,显示器就不需要帧缓冲器。
    DCS(Command mode)命令模式
    MIPI总线控制器使用显示命令报文来向显示器发送像素数据流。显示器应该有一个全帧长的帧缓冲器来存储所有的像素数据。一旦数据被放在显示器的帧缓冲器中,定时控制器就从帧缓冲器中取出数据,并自动把它们显示在屏幕上。MIPI总线控制器不需要定期刷新显示器。
    STM32F469使用命令模式时可以手动刷新显示器,也可以自动刷新(消除TearingEffect模式,TE)
    如果想了解更多关于这两种模式的内容,可以看这个博客
    见附件文档
    如果想了解更多关于TE的内容,看下面这个博客
    见附件文档
           在写这篇帖子的过程中对两种方式都进行了测试,发现Command Mode下在刷屏时屏幕上会雪花出现(刷完屏后显示一切正常),理论上来讲,在刷新显存之前,显存RAM内容的改变并不会表现在屏幕上,暂时没有精力去找原因了,帖子的工程中使用了Video Mode

    1.2 工程建立
    接下来开始建立工程,这个工程是从LCD_DSI_VideoMode_DoubleBuffering工程修改而来的
    工程需要的库文件:

    1.jpg


    BSP的LCD库函数需要板载SDRAM,因为显存存放在从地址LCD_FB_START_ADDRESS(0xC0000000)开始的内存中,而这个地址就是SDRAM的起始地址
    Main函数:
           Main函数开头的初始化都是直接调用库函数完成,初始化完成之后,就可以使用LCD的库函数了,包括了画点,画线,画图填充,写字符等函数。

    2.jpg


    2. 简易CUI模块
           PC上的cmd窗口就是CUI的一个例子,相对于GUI,它的实现简单,占用资源少,非常适合嵌入式,本例是一个简单的CUI演示,它有两个特点:软件光标和屏幕卷动。但是屏幕缓冲区只有一屏的内容,因此不能回看。

    2.1 CUI界面配置
           CUI界面只支持字符显示,可以显示固定行数和列数的字符,并且字符只能顺序写入,不能设定光标的位置。
    通过配置文件可以设置界面的行数,列数,字体样式,背景色和前景色。

    3.jpg


    LCD_WIDTH,LCD_HEIGHT:只用来限制显示CUI界面的大小,不必等于真实液晶宽和高
    CUI_FONT:font.c文件有几种字体可选,CUI_WIDTH和CUI_HEIGHT要和所选的字体一致。也可以添加其他字体,字体取模的格式为:纵向取模,从上到下(MSb在上),从左到右,不足8bit的LSb补0
    CURSOR_HEIGHT:光标使用横向的格式,高度从1到FONT_HEIGHT都可以
    CURSOR_BLINK_RATE:光标闪烁频率,这里设置的是1Hz,这个要求定时器中断周期为1ms
    CUI_LINE,CUI_ROW:CUI显示的行数和列数,这两个值设定之后最好保证整个CUI界面小于LCD_WIDTH和LCD_HEIGHT所限定的区域,否则一部分字符会显示不出来

    2.2 移植准备
    移植CUI模块只要2个外部函数,Lcd_SetPixel()和Lcd_GetPixel(),在程序中是要用这两个函数实现画字符的功能,因此可以结合LCD控制器的特点进行优化。
    另外,还需要将Cui_TimerProc()函数在1ms定时器中断程序中调用,用来实现软件光标。
    2.3应用接口:

    4.jpg


    接口函数只有4个,Cui_Putc和Cui_Puts可以像C标准库里的那样使用,重定位之后可以直接printf了
    2.4 基于STM32F469I-DISCO的优化
    使用上面未优化的方法,移植到STM32F469I-DISCO,全屏幕时,屏幕卷动的速度很慢,因为屏幕卷动一次需要将当前屏幕整体往上移动一行字符的高度,未优化时,是直接重新给整个屏幕填充字符,一个字符一个字符画出来,当然消耗时间,如果有方法将整个屏幕整体拷贝,这个过程将提速N倍。MCU接口的LCD的驱动IC一般支持窗口函数,这种使用软件去描点就类似于这种拷贝了,但是RGB接口的显存都是线性的,无法实现这种方法,而STM32F469的硬件加速器正是为了这种拷贝而设计的,假如有一组图片数据,颜色格式为ARGB8888,但是图片大小是100x100的,现在要将图片拷贝到屏幕的(320,240)的位置,如果使用软件拷贝就要对每一行数据计算起始RAM地址,拷贝一行,接着计算下一行的起始RAM地址,拷贝下一行…,并且图片数据的格式和显存中的格式还不一样,拷贝之前还要先转换一下,但是使用DMA2D硬件加速器,这个过程变得非常简单迅速,设置一下图片数据的起始地址(源),在显存中存放的起始地址(目的),图片的颜色格式,图片的宽和高,启动DMA2D,然后等着硬件拷贝完成就行了。
           经过上面的分析,就知道了该怎样优化了,可以有两种方法优化:
    优化1:优化画字符的函数,这种方法对CUI的软件改变最小,但是要求取字模时,一个点要用4个字节表示,而不再是1bit,字模体积最大为原来的32倍,虽说STM32F469的Flash有2M,还有16M的QSPI,也不能这么任性,关键是如果屏幕颜色数据改变了,字模岂不是要随着改变,所以这个优化先排除掉
    优化2:在终端中输入字符时,只要当前屏幕未满,显示速度都是飞快的,毕竟只显示一个字符,但是如果是上面这种情况,那么,整个屏幕都要上移一行,未优化的方法都是重新写满屏数据,这个过程比较慢,但是如果能将当前屏幕从第二行开始拷贝到从第一行开始,再将最后一行清除,那么就相当于把整屏往上移动了一行,这就是这种优化的思想;
        遗憾的是,并没有发现可以将屏幕显存的一部分拷贝到另外一个位置的方法,例程中使用DMA2D的拷贝只是将一个图片数据拷贝到屏幕的某个位置,如果只拷贝图片的一部分,就不知道该怎么处理了,所以只能绕个路,每次拷贝一行像素,如果CUI界面占用了整个屏幕倒是可以整个拷贝。这种方法其实使用普通的DMA也可实现。
    注1:后来实际测试了一下普通的DMA,发现速度非常慢,显示效果还不如不用优化,也许是我没配置好,对这个结果不满意的坛友可以研究一下,看是不是可以更快。另外实测DMA从SDRAM拷贝到SDRAM所用时间大约是Flash到RAM时间的4~5倍。
           附件工程中提供了未优化和用DMA2D优化这两种方法,可以通过命令行启动或关闭加速,对比一下效果吧。

    注2:调试程序时发现BSP库中关于BSP_LCD_ReadPixel()函数有一处错误:

    5.jpg


    红圈中的2应该改为4,ARGB格式一个点占4个字节,不是两个

    附件3_STM32F469-DISCO_CUI 超级终端外置键盘.pdf (615.2 KB, 下载次数: 0, 售价: 2 铜板)

    附件4_windows 7 超级终端 v1.01 绿色版.rar (239.97 KB, 下载次数: 0, 售价: 2 铜板)

    附件2_STLink_win7_32位精简版USB转串口解决方案.rar (566.26 KB, 下载次数: 0, 售价: 2 铜板)

    附件1_STM32F469I-DISCO_cui_demo.rar (73.21 KB, 下载次数: 0, 售价: 2 铜板)


    3 基于超级终端的外置键盘
           一般开发板只有有限的几个按键,有的甚至没有,想扩展键盘比较麻烦,使用也不方便,但是串口大多开发板都是有的,这部分就介绍如何使用串口配合PC的超级终端来外扩一个外置的键盘,有了这个键盘,几乎能输入电脑标准键盘上的所有按键,并且扩展非常方便,“即插即用”。在介绍这个模块之前,先做些铺垫。

    3.1 MCU串行通信处理方法
    先介绍一下自己经常用的一个串口通信处理方法和一个自定义的printf函数,这些都是从日本人chaN(FatFs的作者)那里得来的,使用非常方便。
    要介绍的这种串口通信方法使用了软件FIFO
    发送时:应用程序写要发送的数据到发送FIFO,TX中断函数查询发送FIFO,如果不为空就从里面读数据发送出去
    接收时: RX中断函数将接收到的数据写入接收FIFO,应用程序查询接收FIFO,如果不为空,就可以将数据读出来。
    使用这种处理,应用程序编程变得很方便;但是移植时,发送中断的处理需要结合MCU的中断触发机制。后面会结合STM32和51单片机讲一下怎样移植这个模块。

    3.1.1 软件FIFO操作
           软件FIFO只需要提供MCU的开关中断的方法即可
    关中断:FIFO_ENTER_CRITICAL()
    开中断:FIFO_EXIT_CRITICAL()

    3.1.2移植方法
    串口初始化
    串口的初始化不同的平台都不一样,但是初始化之后要开接收中断,发送中断要不要开根据平台决定,下面例子有说。
    中断触发条件的设置
    对于接收中断,一般设为只要有数据接收到就触发中断,大部分MCU都支持这个功能。要注意的是有些MCU的串口接收具有硬件FIFO功能,接收中断的触发条件可以设为FIFO满或者FIFO不空,这时要设为不空,否则会有一些接收数据存留在FIFO中。
    对于发送中断,有的MCU是发送寄存器空触发,有的是发送完成触发,这两个本质是不一样的,下面分别根据51和STM32来介绍。
    STM32
           初始化:初始化之后开接收中断,发送中断先不要开,接收中断触发条件是RXNE。
           Uart_Putc()函数:(阻塞)
    void Uart_Putc(unsigned char c)
    {
    Fifo_Write(&TxFifo, c);
    __HAL_UART_ENABLE_IT(&UartHandle, UART_IT_TXE);
    }
    将要发送的字节写入FIFO,开发送中断,发送中断的触发条件为TXE,即发送寄存器空触发中断,这时如果没有数据在发送,会立刻进入中断

    Uart_Getc()函数(阻塞)
    unsigned char Uart_Getc(void)
    {
      returnFifo_Read(&RxFifo);
    }
    不用移植,注意这个函数是阻塞函数,如果需要非阻塞函数,可以先查询一下FIFO是否为空,如果为空,读取失败返回。

    中断处理函数:
    void USART3_IRQHandler(void)
    {
      unsigned charc;

    if(__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_RXNE)==1) {
        c =(unsigned char)((UartHandle.Instance)->DR);
       if(!Fifo_Full(&RxFifo))
         Fifo_Write(&RxFifo, c);
      }

    if(__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_TXE)==1) {
       if(!Fifo_Empty(&TxFifo)) {
          c =Fifo_Read(&TxFifo);
         (UartHandle.Instance)->DR = (unsigned short)c;
        }
        else {
         __HAL_UART_DISABLE_IT(&UartHandle, UART_IT_TXE);
        }
      }
    }
    中断处理函数,对于接收中断,将接收到的数据写入接收FIFO即可,但是如果FIFO已满,接收到的数据将被丢弃;对于发送中断,如果FIFO不为空,读出数据发送,如果FIFO为空,注意要禁用发送中断,否则会一直进入中断。

    51单片机
           初始化:
    初始化之后,开中断,51的接收和发送是一个中断,只能同时使能或禁用
           Uart_Putc()函数:
           void Uart_Putc(unsigned char c)
    {
    Fifo_Write(&TxFifo, c);
      EA= 0;
    if(!TxRun) {
         TxRun= 1;
         TI= 1;
    }
      EA= 1;
    }
    先写数据到FIFO,51的发送中断是发送完成触发,并且可以软件置位标识位触发,这里就是将TI置1来触发软件中断;这里有一个TxRun变量,如果有数据要发送,要将这个变量置1,中断程序会在FIFO中所有数据接收完成之后将其清零,注意修改TxRun时要先关中断。
    void UartISR(void) interrupt 4
    {
      if(RI){
         Fifo_Write(&RxFifo,SBUF);
         RI= 0;      
      }
      else {
         TI= 0; //清除发送中断标志
         if(!Fifo_Empty(&TxFifo){
            SBUF = Fifo_Read(&TxFifo);
    }
         else  {
            TxRun= 0;
         }
      }
    }

    3.1.3 非阻塞函数
          前面的getc和putc函数都是阻塞函数,但有时需要非阻塞函数,这时可以使用FIFO_Full()和FIFO_Empty()函数先判断一下,如果可以写或者读,就执行,然后返回成功,否则返回失败,这个功能配合定时器就可以实现带超时的非阻塞函数。
    3.2 xprintf模块
           在串口输出时,如果将putc重定位,可以使用C标准库的printf函数,这对于调试意义重大,但重定向到标准的printf函数好像比较麻烦,编译出来的代码也会增加,RAM小的情况下也比较容易出问题(我自己没试过),所以一直以来都没用过,一直在用的是chaN的xprintf模块,这个模块基本上具备了printf的所有功能(不支持浮点数打印),还有sprintf,等等,代码编译之后大概2K,实现只需要stdarg.h头文件,这个头文件中都是一些宏操作,没什么消耗,它有几个好处:
           1. 使用方便:不需要移植,通过一个函数调用完成重定向,也可以将输出函数作为参数来实现临时的重定向
           2. 适合调试用:模块提供了xgetc, xgets,xatoi等函数,具有回显功能,可以方便从串口输入数据,完成字符串到整数的转换等,后面Demo程序会看到相关的用法。
    模块使用
    这个模块不用移植,可以直接使用,只需要调用两个函数:
    xdev_out(Uart_Putc);
    xdev_in(Uart_Getc);
    这样就完成了重定向,虽说一般用于串口,其实这个模块可以用于任何字符输出设备,比如液晶。
    chaN的网站上有很多值得学习的地方,电子爱好者不要错过
    见附件文档
    3.3 基于超级终端的外置键盘3.3.1设计思路:
           在PC的超级终端下,按下一个按键,会(不是所有按键都会)通过串口发送一个字符,有些功能键按下之后会发送3个字符,可以利用这个特性来用MCU的串口扩展一个外接键盘。
    键盘扫描:
    根据前面的介绍,串口接收到的字符都存放在接收FIFO中,这样隔一段时间去查询一下FIFO,如果有数据,将数据读出,就知道那个按键按下,然后执行相应的处理,而这个FIFO相当于键盘的缓冲区(本模块使用了单独的按键缓冲FIFO),有些功能键按下会一下发送3个字符,这三个字符如果分别读出会被误判为其他按键按下,如:UP键对应的键盘码为1B 5B 41,如果顺序读出来,会认为ESC(1B),“[”和“A”按下了,对于这种情况,可以用一个状态机来处理,如果接收到的字符是1B,那么延时5ms,再扫描,如果5ms之内又接收到了数据,认为新接收到的数据和将要接收到的数据是同一个按键的码值,如果5ms之内没接收到,说明按下的就是ESC键。
    本模块在定时器中断中执行案件扫描,占用CPU时间很少,并且这个外接键盘有一个好处,就是免去了判断按键键值的麻烦,接收到的字符既是按键键值。
    超级终端的高级用途:
    超级终端除了可以按顺序输出打印的字符外,还可以在指定位置显示字符,改变背景颜色,字符颜色和显示模式等,完全可以当做一个简单的监控界面来使用,在要介绍的模块中,实现了几个简单的函数,想了解超级终端高级用途的可以参考这里:
    见附件文档

    3.3.2 模块移植
           这个模块的移植需要fifo.c,其他只需要提供一个非阻塞的串口接收函数
    #define ht_getc_unblock(c)  Uart_Getc_Unblock(c)
    如果接收FIFO为空,这个函数返回0,如果接收FIFO不为空,从接收FIFO中读出一个字节,将这个字节赋值给*c
    3.3.3 接口函数


    11.jpg



    KeyBuf_Proc():这个函数要放在1ms定时器中断服务函数中执行
    HT_GetKey():从键盘缓冲中读取一个按键,如果读到的值为0,说明没有按键按下。
    HT_KeyScan_Enable(),HT_KeyScan_Disable():键盘的扫描可以使能或禁用,使能键盘扫描时,串口接收FIFO中的数据会在KeyBuf_Proc()函数中读入键盘缓冲;禁用扫描时,键盘缓冲会被清除,KeyBuf_Proc()函数也不会执行按键扫描,超级终端可以当做普通的串口终端来使用。
           下面这几个函数属于超级终端的高级用法了
    HT_ClearScreen():清除当前显示
    HT_SetStyle():设置显示风格,可以是高亮,闪烁,带下划线等
    HT_SetBackground():设置字符背景色
    HT_SetForeground():设置字符前景色
    HT_SetXY():设置光标位置(注意光标的原点位置坐标是(1,1)不是(0,0)
           在Demo程序中会看到相应的用法。

    4. 综合DEMO程序
           前面讲了这么多,看得都没劲了,还是来个Demo程序来演示一下吧
    硬件准备:
           将STM32F469I-DISCO通过STlink的USB接口连接到电脑,保证可以下载程序,并且虚拟串口可以使用(这个虚拟串口的驱动相当难装,我自己折腾了一个下午才装成功,后面会附上这个方法),下载程序。
    超级终端演示:

    12.jpg


    程序默认将输出函数定位到了串口上,超级终端会打印Hello World,并有“>”来提示输入命令,这个Demo程序支持几个命令,用于超级终端演示的有
    “dir2lcd”:定位输出函数到液晶,如果输入这个命令,显示将会从超级终端切换到液晶。
    “ht_test”:进入外置键盘的测试程序,进入之后的界面是这样的:

    13.jpg


    按下一个按键,将会在屏幕固定位置打印出按下的按键及其键值,按ESC键会退出测试模式。
    液晶和CUI演示
           在超级终端中输入“dir2lcd”命令将输出重定向到LCD,这时要看LCD屏幕了,在LCD屏幕上可以有以下命令:
    “dir2ht”:切换输出到超级终端
    “opt_enable”:使能CUI的DMA2D硬件加速,上电默认是使能的
    “opt_disable”:禁用CUI的DMA2D硬件加速
    “cui_test”:输入这个命令会打印出来几行字符,多输入几次写满整个屏幕,可以看滚屏的效果,并且可以对比开优化和不开优化的效果。

    14.jpg


    附录:关于附件中的DEMO工程
           cui_demo工程文件和STM32CUBEF4软件包的位置关系如下:

    15.jpg


    工程所需要的文件都在cui_demo/src文件夹下,串口驱动的HAL库太乱了,自己写了一个,比较简洁。


    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    关闭

    站长推荐上一条 /4 下一条

    手机版|爱板网

    GMT+8, 2018-10-21 08:14 , Processed in 0.088012 second(s), 15 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-5   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001056号

    Powered by Discuz!

    返回顶部