基於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庫,設計界面。

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM