zoukankan      html  css  js  c++  java
  • 基于STM32H743设计UI界面心得(还没写完)

    原料

    硬件:STM32H743最小系统板,显示屏(7寸,型号7016),SW下载器,PC,

    软件:CUBEMX4.26.0 (软件包1.3.2), MDK5 (软件包版本2.3.1)

     ①环境配置

      1-时钟配置

      时钟来源是外部25MHZ的晶振,系统配置后,CPU运行主频400MHZ,其余各个外设的时钟如配置图所示

    2-外设配置

    根据我们需要用到的硬件设备,配置相应的外设。我们工程中需要用到的硬件设备有:超声波探针-输出模拟信号;7inch RGB显示屏(1024*600)-LTDC接口;wifi模块-UART;开发板自拓展的W9825G6KH(SDRAM)。因此需要用到的外设有:一个ADC,一个串口,FMC(扩展SDRAM),LTDC,其他一些IO口-用于指示灯之类的。OK,了解目标以后,我们的配置就明确多了:

        (1)配置SDRAM:

    首先在Pinout界面进行如下的配置

        再进入configuration界面后,对FMC进行一定的配置(具体为什么这么配置参考FMC使用手册),IO映射没有问题,无需改动。点击Connectivity-FMC

     这是CUBEMX帮我配置的驱动,但是对于具体的硬件使用,这是不够的,我们还要对工程添加自己的驱动,再工程中添加以下一些文件,SDRAM部分主要是发送初始化序列。

    之后我们打开工程验证一下。我们定义一个1024*1024的u8类型数组(大小为1MB),地址分配在0XC00000000(SDRAM地址起始位置),并在main中对其使用(赋值即可),可以发现,如果不使用外扩的内存,仅仅依靠芯片本身的1056MB(其中包含仅CPU和DMA访问的内存,并且地址不连续)的SRAM,系统是无法运行的,因为运行内存不够,但是经过我们外扩SDRAM后,内存扩展了32MB,此时可以满足运行条件,系统正常工作。

     至此,我们的SDRAM已经配置完成,可以将其用于液晶显示屏的显存运行内存。

        (2)配置LTDC:

    用CUBEMX帮我们配置的外设IO引脚和我们实际接口有出入(重映射),需要我们手动更改引脚配置以满足实际需求。我们对比正点原子写的驱动配置和CUBEMX的驱动配置:

            发现主要需要改动的引脚有:

        • 引脚转换    功能
        • PA8 -> PG11     LTDC_B3
        • PA5 -> PH10     LTDC_R4
        • PA6 -> PH13     LTDC_G2
        • PC9 -> PH14    LTDC_G3
        • PB10 -> PH15  LTDC_G4
        • PH4 -> PI0       LTDC_G5
        • PI11->PI1         LTDC_G6  
        • PA10 -> PI4      LTDC_B4
        • PA3 -> PI5        LTDC_B5
        • PB8 -> PI6       LTDC_B6
        • PB9 -> PI7       LTDC_B7

      把所有的IO速度都配置为GPIO_SPEED_FREQ_VERY_HIGH,最后再添加PB5用于控制显示屏的背光,直接配置为OUTPUT即可

      

    其实到这一步,我们的配置是不完整的,我们再添加正点原子官方写的驱动,稍作修改,即完成配置(这里解释一下既然用的是别人的驱动,为什么上面还要这么复杂的配置:是因为CUBEMX生成工程文件后,我们的初始化中用到HAL_LTDC_MspInit函数存在重复定义,因此我们舍弃原子哥写的函数,不然每次都要再fmc.c中添加__weak,太麻烦了,所以我们对上面的IO配置后,相当于让软件配置的HAL_LTDC_MspInit代替原子哥的函数)。

    至此,我们完成了LTDC接口的全部配置,在atk_ltdc.c中,我们声明了一个长度为1024*600的u16类型的二维数组(2*600*1024B=1200KB=1.2MB),用于RGB显示屏的运行显存,这也就体现了为什么使用RGB显示屏一定要先拓展内存的理由。

    (3)QSPI

    QSPI是一种高速SPI,一共有6个线(4根信号线,1根时钟线,1根片选线),我们用该方式实现与FLASH的通讯。先在CUBEMX面板进行配置

    同样,需要更改一下默认的配置,包括参数配置和IO配置,具体如下:

      IO修改如下:

    1:QUADSPI_BK1_NCS  PB10  ->  PB6

    2:QUADSPI_BK1_IO2  PE2  ->  PF7

     (4)USB_OTG_FS

    这个外设服务于上层软件USB_DEVICE使用的,这里只需简单配置即可,在CUBEMX的界面打开他的配置:

     一切都按系统给我们默认配置即可,无需更改。改外设对应的IO为:

    PA12: USB_OTG_FS_DP

    PA11: USB_OTG_FS_DM

    (5)Timer, RNG (太简单,不介绍)

          3-软件

    (1)嵌入式操作系统

    在一个复杂的嵌入式应用中,操作系统的使用可以极大提高系统运行的鲁棒性和效率,整个系统各任务的执行可以不用再拘泥于中断,事件等。本次实验是为了以后研发产品做基础,因此,操作系统的嵌入式是非常有必要的。小型嵌入式操作系统种类有很多,比如FREERTOS,UCOSII,UCOSIII,RTThread等,CUBEMX软件可以帮我们搭建FREERTOS的框架,只需要在图形配置界面简单操作,即可省区繁琐的系统驱动移植过程。下面就介绍如何在CUBEMX中配置我们的嵌入式操作系统。

    我们在MiddleWares中Enable一下FREERTOS

    将一个定时器配置为操作系统的滴答时钟,比如下面的配置中我们用的是TIM6,因为这个定时器功能最少,少一个也没什么影响

      进入configuration界面,点开freertos,进入配置界面,配置如下:

      红色框中的参数建议修改一下,这些分别是:单个任务配置的stack空间大小,任务名最大长度,是否使能计数信号量,操作系统总内存。其他参数按照默认的就行了,然后我们就可以开心的添加任务了,再配合信号量,计时器,互斥量等工具,就可以顺利地让操作系统运行起来了。

    比如在我地另一个嵌入式应用中,我一共创建了十几个任务,通过共享一些信号量,可以保证整个进程有条不紊地运行,关于操作系统的工作原理,建议百度简单了解一下。

    另外,我们还可以看到FreeRTOS中内存的分配情况,提醒一下,有些任务可能需要的运行内存较大,比如你在里面定义了一个长数组,如果分配的内存不足的话,系统运行会出现问题。

    好了,现在我们添加两个任务,简单控制一下两个LED的Blink:

    最后再生成工程文件,打开进入freertos.c中,再任务函数中简单加入控制LED的代码

    void LED0Blink(void const * argument)
    {
    
      /* USER CODE BEGIN LED0Blink */
      /* Infinite loop */
      for(;;)
      {
        osDelay(500);
        LED1_Toggle;
      }
      /* USER CODE END LED0Blink */
    }
    
    /* LED1Blink function */
    void LED1Blink(void const * argument)
    {
      /* USER CODE BEGIN LED1Blink */
      /* Infinite loop */
      for(;;)
      {
        osDelay(200);
        LED0_Toggle;
      }
      /* USER CODE END LED1Blink */
    }

    可以看到整个框架已经帮我们搭好了,加深的代码就是我们添加的控制代码。

    至此,我们的嵌入式操作系统已经配置完了。

    (2)FATFS文件系统

    文件系统可以让我们数据规范化的保存和传递,对于一般的小型嵌入式应用,如果需要实现数据可视化,一个好的文件系统可以提供极大的帮助。举个例子,比如我们做了一个记录空气温度,湿度的设备,数据记录后上传给服务器,再在后台处理,传递的方式一种是直接通过一些通讯方式,比如USART,SPI,IIC等,但是这些都会涉及到通讯协议,还有一种方式是直接将存有数据的文件,比如CSV,TXT文件,传递给后台。显然,传递文件的形式肯定更受欢迎,相应的API也方便调用,这就是使用文件系统的必要性。

    说起文件系统,就必须要谈到内存的问题了,这个内存不是我们之前说的运行内存(RAM),因为这些内存掉电以后数据就消失了,而是硬盘内存。然而,STM32H743自带的FLASH只有2M,还要考虑到程序和常量的存放问题,所以我们一般是需要外部扩展内存的。这里需要用到QSPI接口拓展一个32MB的FLASH(NAND FLASH和SD卡也是不错的选择,这里选择SPI FLASH的原因是我们的最小系统板已经帮我们扩展好了这样一块内存,就直接拿来用了,当然你再买一块SD卡插在SD卡槽里也是没有问题的,外设需要再相应的配置一下)

    在CUBEMX的帮助下,我们配置FATFS文件管理系统的效率将会大大提升:

    首先,在主界面,将FTAFS配置为User-define,这样一来,我们就可以自定义接口操作了。

    然后进入configuration界面,对FATFS进行详细配置:

    这当然还没结束,我们现在仅仅是把这个框架搭了起来,打开工程文件后,我们还要在user-diskio.c里面配置我们的读写API接口,显然,这里我们要将文件管理系统和SPI FLASH联系起来,因此,具体操作如下:

      1 ... ...
      2 /* USER CODE BEGIN DECL */
      3 
      4 /* Includes ------------------------------------------------------------------*/
      5 #include <string.h>
      6 #include "ff_gen_drv.h"
      7 
      8 #include "atk_w25qxx.h"
      9 #define SPIFLASH_SECTOR_SIZE     512    
     10 #define FLASH_SECTOR_COUNT     1024*25*2    
     11 #define FLASH_BLOCK_SIZE       8             
     12 /* Private typedef -----------------------------------------------------------*/
     13 /* Private define ------------------------------------------------------------*/
     14 
     15 /* Private variables ---------------------------------------------------------*/
     16 /* Disk status */
     17 static volatile DSTATUS Stat = STA_NOINIT;
     18 
     19 /* USER CODE END DECL */
     20 ... ...
     21 DSTATUS USER_initialize (
     22     BYTE pdrv           /* Physical drive nmuber to identify the drive */
     23 )
     24 {
     25   /* USER CODE BEGIN INIT */
     26     Stat = STA_NOINIT;
     27     Stat = RES_OK;
     28     return Stat;
     29   /* USER CODE END INIT */
     30 }
     31 ... ...
     32 DSTATUS USER_status (
     33     BYTE pdrv       /* Physical drive number to identify the drive */
     34 )
     35 {
     36   /* USER CODE BEGIN STATUS */
     37     Stat = STA_NOINIT;
     38     Stat = RES_OK;
     39     return Stat;
     40   /* USER CODE END STATUS */
     41 }
     42 ... ...
     43 DRESULT USER_read (
     44     BYTE pdrv,      /* Physical drive nmuber to identify the drive */
     45     BYTE *buff,     /* Data buffer to store read data */
     46     DWORD sector,   /* Sector address in LBA */
     47     UINT count      /* Number of sectors to read */
     48 )
     49 {
     50   /* USER CODE BEGIN READ */
     51     for(;count>0;count--){
     52         W25QXX_Read(buff,sector*SPIFLASH_SECTOR_SIZE,SPIFLASH_SECTOR_SIZE);
     53         sector++;
     54         buff+=SPIFLASH_SECTOR_SIZE;
     55     }
     56     return RES_OK;
     57   /* USER CODE END READ */
     58 }
     59 ... ...
     60 DRESULT USER_write (
     61     BYTE pdrv,          /* Physical drive nmuber to identify the drive */
     62     const BYTE *buff,   /* Data to be written */
     63     DWORD sector,       /* Sector address in LBA */
     64     UINT count          /* Number of sectors to write */
     65 )
     66 { 
     67   /* USER CODE BEGIN WRITE */
     68   /* USER CODE HERE */
     69     for(;count>0;count--){                                            
     70         W25QXX_Write((u8*)buff,sector*SPIFLASH_SECTOR_SIZE,SPIFLASH_SECTOR_SIZE);
     71         sector++;
     72         buff+=SPIFLASH_SECTOR_SIZE;
     73     }
     74     return RES_OK;
     75   /* USER CODE END WRITE */
     76 }
     77 ... ...
     78 DRESULT USER_ioctl (
     79     BYTE pdrv,      /* Physical drive nmuber (0..) */
     80     BYTE cmd,       /* Control code */
     81     void *buff      /* Buffer to send/receive control data */
     82 )
     83 {
     84   /* USER CODE BEGIN IOCTL */
     85     DRESULT res = RES_ERROR;
     86     switch(cmd){
     87         case CTRL_SYNC:
     88         res = RES_OK; 
     89                 break;     
     90         case GET_SECTOR_SIZE:
     91                 *(WORD*)buff = SPIFLASH_SECTOR_SIZE;
     92                 res = RES_OK;
     93                 break;     
     94         case GET_BLOCK_SIZE:
     95                 *(WORD*)buff = (WORD)FLASH_BLOCK_SIZE;
     96                 res = RES_OK;
     97                 break;     
     98         case GET_SECTOR_COUNT:
     99                 *(DWORD*)buff = FLASH_SECTOR_COUNT;
    100                 res = RES_OK;
    101                 break;
    102         default:
    103                 res = RES_PARERR;
    104                 break;
    105     }
    106     return res;
    107   /* USER CODE END IOCTL */
    108 }
    109 ... ...

    这时我们的文件管理系统才算配置完成,但是如果想要运行,还需要对这个磁盘格式化,也就是说,让我们操作的最小扇区大小和磁盘格式化的单元大小一致(512B),我们通过将SPI FALSH以USB的方式连接到PC上,再在PC上对其格式化。

    (3)USB DEVICE

    USB_DEVICE属于中间层的软件,基于底层的外设USB_OTG_FS或者USB_OTG_HS,由于后者需要额外配置高速的物理层,在本应用中我们使用前者,该软件可以配置为多种应用,本应用中我们配置为大容量存储设备MSC(最后一个),这样配合文件管理系统可以方便的管理我们设备中的数据和文件。

     在configuration界面也无需对其进行更改,按照默认的参数即可。但是运行USB_DEVICE软件必须用较大的堆内存(heap)支持,否则会运行失败,因此在生成文件的设置中,我们将heap的内存由512B 增大为8KB.

    生成软件后,我们还需要配置对应的接口,将USB与SPI FLASH联系起来,具体接口在usbd_storage_if.c文件里,主要是读写接口的配置,另外最好在使用之前使能USB电压检测。

    ... ...
    int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
    {
      /* USER CODE BEGIN 6 */
      W25QXX_Read(buf,blk_addr*512,blk_len*512);
      return (USBD_OK);
      /* USER CODE END 6 */
    }
    ... ...
    int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
    {
      /* USER CODE BEGIN 7 */
      W25QXX_Write(buf,blk_addr*512,blk_len*512);
      return (USBD_OK);
      /* USER CODE END 7 */
    }
    ... ...    

    在usb_device.c中使能电压检测:

    void MX_USB_DEVICE_Init(void)
    {
      /* USER CODE BEGIN USB_DEVICE_Init_PreTreatment */
      
      /* USER CODE END USB_DEVICE_Init_PreTreatment */
      
      /* Init Device Library, add supported class and start the library. */
      USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
    
      USBD_RegisterClass(&hUsbDeviceFS, &USBD_MSC);
    
      USBD_MSC_RegisterStorage(&hUsbDeviceFS, &USBD_Storage_Interface_fops_FS);
    
      USBD_Start(&hUsbDeviceFS);
    
      /* USER CODE BEGIN USB_DEVICE_Init_PostTreatment */
      HAL_PWREx_EnableUSBVoltageDetector();
      /* USER CODE END USB_DEVICE_Init_PostTreatment */
    }

    最后测试后我们获得:

      可以看到,我们的上层应用USB DEVICE和FATFS都运行正常,windows系统下查看其目录:

    这两个文件就是我们刚才在STM32中创建的。

    (4)触摸屏驱动

    我们的显示屏与MCU是通过40-pin软排线连接起来的,这40根线中,除了控制显示屏必须的LTDC接口线外,还有IIC线,用于完成触摸屏的位置读取功能。显示屏的型号是7016,这种显示屏的触摸驱动芯片是GT911,对此,我们需要编写专门的驱动软件,好在已经有人写过相关的控制例程,我们只需要把里面的驱动软件拷贝过来即可。

    我们将这几个文件复制到工程目录下,atk是指开发作者为正点原子团队,ctiic是IO驱动,里面包含了IIC控制总线的驱动程序,gt911是触摸驱动芯片GT911的驱动软件,主要是配置芯片的初始化程序以及信号IO配置等,touch是较上层的驱动软件了,也是我们开发时主要需要参考的文件。

    文件加入工程后,我们在程序中运行触摸屏的初始化代码(先要初始化显示屏),再运行触摸屏的测试程序:

     ②硬件

    我们再复习一下上面提到的各种硬件设备,并附图:

    1-显示屏

    我们用的显示屏类型为RGB显示屏,色彩配置为RGB565(如果是ARGB8888,那需要的运行内存就要大一倍,小的嵌入式应用没有这个必要,565色彩已经很丰富了),大小为7inch,型号为7016(像素1024*600),通过LTDC接口与CPU交流,接线中通过40-pin的软排线连接

     2-内存

    (1)SDRAM

    该内存主要用于申请显示屏的运行内存,也是我们的最小系统板帮我们拓展好的(贴心),大小32MB(实际只用到1.2MB左右),内存地址0XC0000000,通过FMC接口与CPU交流

    下图我们程序的运行内存,可以看到,运行内存差不多也就1.2MB多一点,和我们的理论值差不多

    (2)SPI FLASH

    这部分的内存是给我们的文件系统的,或者存放中文字库也可以,可以用SD Card或者其他内存盘代替。所用硬件为W25Q256,通讯方式为SPI,大小32MB

    (3)SD Card

    等我什么时候买了SD卡再更新吧

    3-USB

     

    由于该最小系统板只集成了最基础的USB硬件,没有集成高速物理芯片,因此我们只能使用USB_OTG_FS功能。

     4-传感器

    ③数据处理

    本设计中用到了DSP,先简单介绍一下DSP:DSP全称Digital Signal Process,数字信号方法,我们在获取一段数字信号后,经常会对这段信号进行一些处理,比如滤波,提取特征,时频域转换等,这些操作当然也可以通过自己编写代码的方式实现,但是DSP芯片的出现就大大加速了中间的计算速度,这都要归功于DSP芯片对于数据处理方式,比如一般我们处理两个32位数的相乘可能需要几个机器周期,但是,在DSP指令集的帮助下,只需一个周期即可完成该运算。

    DSP芯片指的是包含了DSP指令集的微处理器,STM32F1,F3系列的芯片是Cortex-M1和Cortex-M3架构,不包含DSP指令集,我们用的芯片是STM32H743,是Cortex-M7架构的,包含了FPU运算单元(一种加快浮点数运算的物理单元)和DSP指令集,另外,已有现成的DSP库可供我们使用,这些库中包含了很多方便的信号处理算法:

    我们重点需要用到最后一个库,TransformFunctions。包括复数 FFT(CFFT)/复数 FFT逆运算( CIFFT)、实数 )、实数 FFT(RFFT)/实数 FFT逆运算( RIFFT)、和 )、和 DCT(离散余弦变换)和配套的初始化函数。 

    使用相关的库需要我们手动将库文件添加进工程中,并且添加全局宏定义ARM_MATH_CM7,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING:

    配置完后,我们就可以调用其中的函数对我们的数据处理了。这里我们自己利用三角函数创建一个波形

    即:A(t) = 10sin(w1*t)+30*sin(w2*t)+5*cos(w3*t),然后利用STM32的硬件随机数添加噪声,产生的时域波形在显示屏上表现为:

    图中,上面的是波形图,下面的是频谱图,可以看到,在频率等于2,5,10三个位置,频谱的能量远大于其他频率的能量值。通过仿真器我们可以进一步读出频率段的能量值:

    在仿真器中,我们可以看到傅里叶变换结果中,频率为0,2,5,10的能量值尤为突出,但0频率值我们一般不予考虑(多由于环境因素造成),而且这三个能量的比值为5108:15392:2607,和我们在时域上定义的值10:30:5刚好对应,证明了DSP库函数的科学性。在这里,我们还可以用定时器统计进行一次傅里叶转换所需要的时间:

    配置一个通用定时器(工作频率200MHZ),分频系数设置为199(每1us计数一次),通过读取CNT寄存器的数值,可以计算代码运行时间:

        __HAL_TIM_SET_COUNTER(&htim7,0);
        HAL_TIM_Base_Start(&htim7);
        FFT_Transform_test();
        HAL_TIM_Base_Stop(&htim7);
        sprintf(ForPrint,"time(us):%5d",__HAL_TIM_GET_COUNTER(&htim7));
        LCD_ShowString(300,500,200,24,24,ForPrint);

    我们可以得到,计算一次傅里叶转换+波形显示所需要的时间大约为8ms,因而在我们的嵌入式实时操作系统中,可以设置较低的刷新频率(10HZ左右),以实现实时计算频谱。

    GUI界面设计

    相信到这一步,基本工作已经做完了,后面的工作就比较有趣了,主要是在FreeRTOS框架下编写GUI库,设计界面。

      

  • 相关阅读:
    第一篇博文,纪念下
    HDU 1026 Ignatius and the Princess I (bfs+存储路径)
    acer Empowering Technology下载(转)
    设定sql server定期自动备份数据库
    web.config加密解密
    WCF安全性资料
    SharePoint中CAML日期格式
    Map Reduce the Free Lunch is not over?(转)
    asp.net与javascript
    获取当前build的版本信息
  • 原文地址:https://www.cnblogs.com/showtime20190824/p/12702793.html
Copyright © 2011-2022 走看看