1

提示

  没有建立空白工程的读者建议先阅读这个:链接

  同时,本文对于工程模板的完善将针对科协的Birch开发板进行(版本V2.1)。

步骤

  上一篇文章中我们建了一个空白工程,不过这个工程还不太好用,增添一些能够提供一些现成函数供调用的代码进去的话,可以省去许多造轮子的功夫,今天我们就来做这件事。

一、添加BSP和硬件驱动

  BSP是板级支持包的意思,这里呢,因为相关编写工作还没有全部结束,所以提供的BSP暂时只实现了延时函数、串口接收和板载LED的驱动,然后,硬件驱动上提供独立按键、矩阵键盘的读取程序和一个IIC驱动程序。

  步骤如下:

  • 此处获取BSP文件夹和Hardware_Driver文件夹下的内容,然后,src下的内容添加到工程对应分组下(记得新建BSP分组)
  • BSP、Hardware_Driver下的inc文件夹的路径,按照上一篇文章中的方法进行添加。

二、添加说明文档

  步骤如下:

  • 从此处获取Doc文件夹下的内容。
  • 在工程模板目录下新建Doc文件夹(即Doc与User同级),将获取的内容复制到此。
  • 打开工程,将Doc文件夹下的内容添加到Doc分组下(如果没有的话请新建此分组)。

三、修改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支持。

  步骤如下:

  • 首先去这里把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上的示例工程进行查对,地址

ngHackerX86
22 声望24 粉丝

000000