【Birch開發板系列教程】(二)完善工程模板並移植u8g2lib

提示

  沒有創建空白工程的讀者建議先閱讀這個:連接git

  同時,本文對於工程模板的完善將針對科協的Birch開發板進行(版本V2.1)。github

步驟

  上一篇文章中咱們建了一個空白工程,不過這個工程還不太好用,增添一些可以提供一些現成函數供調用的代碼進去的話,能夠省去許多造輪子的功夫,今天咱們就來作這件事。segmentfault

1、添加BSP和硬件驅動

  BSP是板級支持包的意思,這裏呢,由於相關編寫工做尚未所有結束,因此提供的BSP暫時只實現了延時函數、串口接收和板載LED的驅動,而後,硬件驅動上提供獨立按鍵、矩陣鍵盤的讀取程序和一個IIC驅動程序。數組

  步驟以下:函數

  • 此處獲取BSP文件夾和Hardware_Driver文件夾下的內容,而後,src下的內容添加到工程對應分組下(記得新建BSP分組)
  • BSP、Hardware_Driver下的inc文件夾的路徑,按照上一篇文章中的方法進行添加。

2、添加說明文檔

  步驟以下:測試

  • 今後處獲取Doc文件夾下的內容。
  • 在工程模板目錄下新建Doc文件夾(即Doc與User同級),將獲取的內容複製到此。
  • 打開工程,將Doc文件夾下的內容添加到Doc分組下(若是沒有的話請新建此分組)。

3、修改main函數

  在添加了BSP之後,延時和點亮LED等操做都有現成函數能夠調用了,故main函數能夠修改以下:字體

#include "bsp.h"

int main()
{
    Bsp_Init();
    
    while(1)
    {
        Board_LED_ON();
        
        Bsp_Delay_ms(200);
        
        Board_LED_OFF();
        
        Bsp_Delay_ms(200);
    }
    return 0;//程序不會運行到這裏
}

敲黑板

  到這裏的話,工程模板已經比較完善了,由於新增的部分主要是與Birch開發板配套的,因此工程模板能夠改相似「F103_Template_Birch」這樣的名字,過程當中有問題的話請參考示例工程:連接)優化

移植u8g2lib

  u8g2lib是一個適合單色屏使用的開源屏幕驅動庫,能提供大量的圖形繪製函數、豐富的字庫、多種屏幕控制芯片的驅動程序,並且,移植很是簡單,在一番測試以後(其實還測試了ugui、SimpleGUI、lkdGUI等等),決定爲Birch開發板配套的工程模板移植此庫提供GUI支持。ui

  步驟以下:this

  • 首先去這裏把u8g2lib的倉庫下載下來,爲保證步驟和我說的移植,建議使用2020.4.9更新的版本(方法自行搜索),以後版本的步驟可能會略有不一樣。
  • 在工程模板「F103_Template_Birch」的User文件夾下新建GUI目錄,而後,把u8g2倉庫的csrc目錄下的所有內容複製到GUI文件夾下。
  • 打開工程,新建GUI分組,準備添加文件。

  這裏須要說明一下,csrc目錄下的一些像u8x8_d_器件名.c這樣的文件用於存儲屏幕控制芯片的驅動程序,只要添加本身屏幕對應的便可,其餘相似格式的可不用添加,針對咱們使用的OLED模塊,添加u8x8_d_ssd1306_128x64_noname.c便可

  • 除不用的那部分,其餘.c文件所有添加到GUI分組下
  • GUI目錄的路徑添加到工程中(這裏沒有劃分inc、src,能夠自行劃分)

  在不少狀況下,移植GUI至少須要向GUI組件提供硬件初始化程序和畫點的程序,不過,u8g2lib在這塊基本是傻瓜化的,畢竟,驅動已經有不少前輩作好了,移植者須要作的事情能夠說至關簡單,主要就是按照模板實現一個函數和註釋掉一些不用的部分以減小空間佔用。

  • u8g2的開發者爲移植者提供了一個函數模板,移植者應參照此模板實現一個函數供u8g2lib調用以實現延時等等,模板以下:

    uint8_t u8x8_gpio_and_delay_template(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
    {
      switch(msg)
      {
        case U8X8_MSG_GPIO_AND_DELAY_INIT:    // called once during init phase of u8g2/u8x8
          break;                            // can be used to setup pins
        case U8X8_MSG_DELAY_NANO:            // delay arg_int * 1 nano second
          break;    
        case U8X8_MSG_DELAY_100NANO:        // delay arg_int * 100 nano seconds
          break;
        case U8X8_MSG_DELAY_10MICRO:        // delay arg_int * 10 micro seconds
          break;
        case U8X8_MSG_DELAY_MILLI:            // delay arg_int * 1 milli second
          break;
        case U8X8_MSG_DELAY_I2C:                // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
          break;                            // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
        case U8X8_MSG_GPIO_D0:                // D0 or SPI clock pin: Output level in arg_int
        //case U8X8_MSG_GPIO_SPI_CLOCK:
          break;
        case U8X8_MSG_GPIO_D1:                // D1 or SPI data pin: Output level in arg_int
        //case U8X8_MSG_GPIO_SPI_DATA:
          break;
        case U8X8_MSG_GPIO_D2:                // D2 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_D3:                // D3 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_D4:                // D4 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_D5:                // D5 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_D6:                // D6 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_D7:                // D7 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_E:                // E/WR pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_CS:                // CS (chip select) pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_DC:                // DC (data/cmd, A0, register select) pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_RESET:            // Reset pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_CS1:                // CS1 (chip select) pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_CS2:                // CS2 (chip select) pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_I2C_CLOCK:        // arg_int=0: Output low at I2C clock pin
          break;                            // arg_int=1: Input dir with pullup high for I2C clock pin
        case U8X8_MSG_GPIO_I2C_DATA:            // arg_int=0: Output low at I2C data pin
          break;                            // arg_int=1: Input dir with pullup high for I2C data pin
        case U8X8_MSG_GPIO_MENU_SELECT:
          u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);
          break;
        case U8X8_MSG_GPIO_MENU_NEXT:
          u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);
          break;
        case U8X8_MSG_GPIO_MENU_PREV:
          u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);
          break;
        case U8X8_MSG_GPIO_MENU_HOME:
          u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
          break;
        default:
          u8x8_SetGPIOResult(u8x8, 1);            // default return value
          break;
      }
      return 1;
    }

    諾,看看代碼就知道,傳入一個消息,而後由這個函數對消息進行判斷,並實現相應操做。

  • 這裏咱們的實現以下:

    uint8_t STM32_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
    {
        switch(msg)
        {
            case U8X8_MSG_DELAY_100NANO:        // delay arg_int * 100 nano seconds
                __NOP();
                break;
            case U8X8_MSG_DELAY_10MICRO:        // delay arg_int * 10 micro seconds
                for (uint16_t n = 0; n < 320; n++)
                {
                   __NOP();
                }
                break;
            case U8X8_MSG_DELAY_MILLI:        // delay arg_int * 1 milli second
                Bsp_Delay_ms(1);
                break;                                            
            case U8X8_MSG_DELAY_I2C:                            
                __NOP();__NOP();__NOP();
                break;                                            
            case U8X8_MSG_GPIO_I2C_CLOCK:                        // arg_int=0: Output low at I2C clock pin
                if(arg_int == 1)                                // arg_int=1: Input dir with pullup high for I2C clock pin
                    GPIO_WriteBit(GPIOB,GPIO_Pin_8,1);
                else if(arg_int == 0)
                    GPIO_WriteBit(GPIOB,GPIO_Pin_8,0);
                break;
            case U8X8_MSG_GPIO_I2C_DATA:                        // arg_int=0: Output low at I2C data pin
                if(arg_int == 1)                                // arg_int=1: Input dir with pullup high for I2C data pin
                    GPIO_WriteBit(GPIOB,GPIO_Pin_9,1);
                else if(arg_int == 0)
                    GPIO_WriteBit(GPIOB,GPIO_Pin_9,0);
                break;
            case U8X8_MSG_GPIO_MENU_SELECT:
                u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);
                break;
            case U8X8_MSG_GPIO_MENU_NEXT:
                u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);
                break;
            case U8X8_MSG_GPIO_MENU_PREV:
                u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);
                break;
            case U8X8_MSG_GPIO_MENU_HOME:
                u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
                break;
            default:
                u8x8_SetGPIOResult(u8x8, 1);                     // default return value
                break;
        }
        return 1;
    }

    在某一特定應用下,並非全部case都是必須的,上面的函數通過了必定的精簡。

    另外,注意所使用的引腳須要預先初始化。

  • 而後,初始化的套路差很少是下面這樣:

    // a structure which will contain all the data for one display
        u8g2_t u8g2;
        // init u8g2 structure
        u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_sw_i2c, STM32_gpio_and_delay);  
        // send init sequence to the display, display is in sleep mode after this
        u8g2_InitDisplay(&u8g2);                                                                         
        //wake up display
        u8g2_SetPowerSave(&u8g2, 0);

    就是,初始化一個結構體用於各項參數的存儲和幀緩衝,而後初始化函數初始化該結構體,以後經過咱們以前編寫的函數進行初始化操做,默認是關閉的,因此最後須要打開顯示,固然,到這裏屏幕上尚未內容,具體的顯示內容咱們以後來編寫。

  • 這些函數調用以及咱們以前實現的函數,若是全放main.c裏面的話,說實話容易被人打,因此呢,新建gui_port.c和gui_port.h,存到User文件夾下的src和inc裏,文件加到工程裏(路徑已添加過),兩文件的內容請參考這裏這裏
  • 接下來,將main.c的內容替換爲這個以方便以後的操做。
  • 編譯工程,你會發現報了一大堆的錯,其中第一條內容以下:

    .\Objects\F103_Template.axf: Error: L6406E: No space in execution regions with .ANY selector matching u8g2_fonts.o(.constdata).

    OK,看No Space倆字大概就能知道是空間不夠用了,這個提示是由於u8g2_fonts.c這個文件夾利用用了一大堆超大的數組來存放字體數據,雖然你大部分都沒使用,可是編譯器仍是給它們分配了空間,而後C8T6可憐的64KB的ROM就不夠用了,其實開O3優化的話能夠解決這個問題,可是另外一個文件裏定義的一堆所有變量仍是須要手動處理,因此推薦按下面的步驟操做:

  • 打開u8g2_d_setup.c,全選,所有註釋,而後,一開始的文件包含指令(#include "u8g2.h")的註釋取消掉。這個文件的話存着各類不一樣芯片、設置下的初始化函數,但咱們只須要用到的哪個,因此,Ctrl + F搜索「u8g2_Setup_ssd1306_i2c_128x64_noname_f」,將這個函數的取消註釋。
  • 打開u8g2_fonts.c和u8g2_d_memory.c,兩者都所有註釋並取消掉開頭的文件包含指令的註釋,而後,編譯工程。
  • 下一步,依據報錯提示,取消掉兩個上一步兩個文件中部份內容的註釋。

    .\Objects\F103_Template.axf: Error: L6218E: Undefined symbol u8g2_m_16_8_f (referred from u8g2_d_setup.o).

    好比看到這一條的話,在u8g2_d_memory.c裏面搜一下「u8g2_m_16_8_f」,解除相關部分的註釋,以下圖:

    P1.jpg

    .\Objects\F103_Template.axf: Error: L6218E: Undefined symbol u8g2_font_inb24_mf (referred from gui_port.o).

    而後,這樣的報錯的話,去u8g2_fonts.c裏面搜u8g2_font_inb24_mf,解除那個數組的註釋(有點長,須要耐心)。

  • 完成後再次編譯,若是順利的話,此時應該沒有Error了,下載到開發板,屏幕顯示狀況以下:(u8g2lib的官方logo)

P2.jpg

  • 若是你的程序也順利運行的話,那麼恭喜,移植完成了,如今能夠給它起個名字好比「F103_Template_Birch_WithGUI」什麼的,而後試着使用u8g2.h裏面提供的函數嘗試着繪製一些幾何圖形。
  • 不順利的同窗請參考gitee上的示例工程進行查對,地址
相關文章
相關標籤/搜索