CH32V307 u8g2移植

发布时间 2023-04-12 16:11:03作者: ZaiLi

前言:该篇文章以CH32V307硬件IIC驱动OLED为基础,介绍u8g2库的移植

1、关于u8g2
u8g2是一个用于嵌入式设备设备的单色图形库,支持单色OLED和LCD,包含多种控制器(具体见库下载链接页面介绍)。
u8g2图形库支持多种字体,支持各种简单和复杂图形的绘制,具有完整的驱动函数库,使用时可直接调用,便于移植,但需要占用一定的内存空间。此外,u8g2图形库同时包含了u8x8库,该库仅支持简单文本显示以及使用8*8像素的字体,可直接绘制图形,不需要占用内存空间作为缓冲区。
u8g2库的下载链接如下:
https://github.com/olikraus/u8g2
u8g2官方移植参考示例链接如下:
https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform
里面包含对CH32V307的移植,可以参考一下。

 

2、关于硬件IIC驱动OLED
CH32V307具有两个硬件IIC,本次测试例程使用IIC2,所用OLED屏为1.3寸OLED屏,分辨率为128*64,供电电压范围为3V到5V,MCU与OLED屏的引脚连接如下:
PB8 —— SCK
PB9 —— SDA
GND —— GND
VCC —— VCC

 

3、IIC驱动配置
在CH32V307工程文件User文件夹下新建两个文件:IIC_OLED.c文件和IIC_OLED.h文件。主要为IIC驱动配置。
IIC_OLED.h文件

//////////////////////////////////////////////////////////////////////////////////
//              GND   电源地
//              VCC   接5V或3.3v电源
//              SCL   接PB8(SCL)
//              SDA   接PB9(SDA)
//////////////////////////////////////////////////////////////////////////////////
#ifndef __IIC_OLED_H
#define __IIC_OLED_H

#include "debug.h"

void IIC_Init( u32 bound, u16 address );
void IIC_SendData(uint8_t *data,uint8_t len);

#endif

IIC_OLED.c文件

#include "IIC_OLED.h"
#include "debug.h"

/*******************************************************************************
* Function Name  : IIC_Init
* Description    : Initializes the IIC peripheral.
* Input          : None
* Return         : None
*******************************************************************************/
void IIC_Init( u32 bound, u16 address )
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    I2C_InitTypeDef  I2C_InitTSturcture = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
    GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;  //SCK
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;   //SDA
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    I2C_InitTSturcture.I2C_ClockSpeed = bound;
    I2C_InitTSturcture.I2C_Mode = I2C_Mode_I2C;
    I2C_InitTSturcture.I2C_DutyCycle = I2C_DutyCycle_16_9;
    I2C_InitTSturcture.I2C_OwnAddress1 = address;
    I2C_InitTSturcture.I2C_Ack = I2C_Ack_Enable;
    I2C_InitTSturcture.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_Init(I2C1, &I2C_InitTSturcture);

    I2C_Cmd(I2C1, ENABLE);

#if(I2C_MODE == HOST_MODE)
    I2C_AcknowledgeConfig(I2C1, ENABLE);
#endif
}

void IIC_SendData(uint8_t *data,uint8_t len)
{
    while( I2C_GetFlagStatus( I2C1, I2C_FLAG_BUSY ) != RESET );
    I2C_GenerateSTART( I2C1, ENABLE );

    while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_MODE_SELECT ) );
    I2C_Send7bitAddress( I2C1, 0x78, I2C_Direction_Transmitter );

    while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ) );
    for (uint8_t i = 0;  i < len; ++ i)
    {

        if(I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE) != RESET)
        {
            I2C_SendData(I2C1, data[i]);
        }
        while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE) == RESET);
    }
    while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED ) );
    I2C_GenerateSTOP( I2C1, ENABLE );
}

 

4、u8g2库的移植
u8g2库下载解压完成后,打开如下:

 此处主要用到csrc文件夹,csrc文件夹打开如下:

 

 本次移植主要用到csrc文件夹文件,将该文件夹按类型排序,在文件夹最后可以看到有4个头文件(.h文件),此处主要用到u8g2.h和u8x8.h两个文件,需要在接下的源文件中调用这两个文件。在该文件夹中,u8x8_d_xxx文件均为屏幕的驱动文件,此处例程所用屏幕为1.3寸OLED屏,屏幕分辨率为128*64,因此此处驱动文件选择u8x8_d_ssd1306_128x64_noname.c文件,其余驱动文件删除即可。最后将csrc文件夹整个文件夹复制到CH32V307工程的User文件夹下,如下图:

 注意在工程中添加文件夹路径:

 此外,若需要减小代码体积以及RAM用量,可删除u8g2_d_setup.c和u8g2_d_memory.c文件中与 ssd1306 无关的代码。u8g2_d_setup.c文件中只保留 u8g2_Setup_ssd1306_128x64_noname_f函数即可,其他全部删掉。u8g2_d_memory.c文件中只保留*u8g2_m_16_8_f函数即可,其他全部删掉。该文章测试例程对此未做处理。

 

5、创建OLED.c和OLED.h文件
本次例程使用硬件IIC,需要编写硬件驱动函数,向OLED写入字节。此外,还需要编写一个回调函数,因为此处使用硬件IIC,无需像软件模拟IIC那样配置GPIO和时序操作,只需在回调函数中添加一个延时函数即可。
具体程序如下:
OLED.h文件

#ifndef __OLED_H
#define __OLED_H

#include "IIC_OLED.h"
#include "u8g2.h"
#include "u8x8.h"

uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);

#endif

OLED.c文件

#include "OLED.h"
#include "u8g2.h"
#include "u8x8.h"

uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */
    uint8_t *data = (uint8_t*) arg_ptr;

    switch (msg)
    {
        case U8X8_MSG_BYTE_INIT:
            {
                /* add your custom code to init i2c subsystem */
                IIC_Init(400000,0x78); //I2C初始化
            }
            break;

        case U8X8_MSG_BYTE_START_TRANSFER:
            while( I2C_GetFlagStatus( I2C1, I2C_FLAG_BUSY ) != RESET );
            I2C_GenerateSTART( I2C1, ENABLE );
            while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_MODE_SELECT ) );
            I2C_Send7bitAddress( I2C1, 0x78, I2C_Direction_Transmitter );
            while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ) );
            break;

        case U8X8_MSG_BYTE_SEND:
            while( arg_int-- > 0 )
            {
                I2C_SendData(I2C1, *data++);
                while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
            }
            break;

        case U8X8_MSG_BYTE_END_TRANSFER:
            I2C_GenerateSTOP( I2C1, ENABLE );
            break;

        case U8X8_MSG_BYTE_SET_DC:
            break;

        default:
            return 0;
    }

    return 1;
}

//提供给软件模拟 I2C 的 GPIO 输出和延时,使用之前编写的配置函数
uint8_t u8x8_gpio_and_delay(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
            Delay_Init();
            break;                            // can be used to setup pins

        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
            Delay_Ms(1);
            break;

        case U8X8_MSG_DELAY_I2C:      // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
            Delay_Us(1);
            break;                    // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us

        case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin
//            arg_int ? GPIO_SetBits(GPIOB, GPIO_Pin_6) : GPIO_ResetBits(GPIOB, GPIO_Pin_6);
            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
//            arg_int ? GPIO_SetBits(GPIOB, GPIO_Pin_7) : GPIO_ResetBits(GPIOB, GPIO_Pin_7);
            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;
}

从u8x8_byte_hw_i2c函数了解IIC的驱动过程:
U8X8_MSG_BYTE_INIT :初始化 I2C 外设;
U8X8_MSG_BYTE_START_TRANSFER:产生起始信号并发送地址;
U8X8_MSG_BYTE_SEND :开始发送字节,并且发送的字节存储在 *arg_ptr 参数中,arg_int 是字节的总长度;
U8X8_MSG_BYTE_END_TRANSFER :产生停止信号。

 

6、main函数配置

/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2021/06/06
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for 
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
 *@Note
 USART Print debugging routine:
 USART1_Tx(PA9).
 This example demonstrates using USART1(PA9) as a print debug port output.
*/

#include "debug.h"

#include "OLED.h"
#include "u8g2.h"
#include "u8x8.h"

/* Global typedef */

/* Global define */

/* Global Variable */

/*********************************************************************
 * @fn      main
 *
 * @brief   Main program.
 *
 * @return  none
 */
int main(void)
{
    u8g2_t u8g2;
    int nTemp = 0;

    /* USER CODE BEGIN 1 */
    char year[6];  //
    char month[6]; //
    char Tem[6];
    /* USER CODE END 1 */

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);    

    printf("SystemClk:%d\r\n",SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
    printf("This is printf example\r\n");

    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_hw_i2c,u8x8_gpio_and_delay);  //初始化u8g2结构体
    u8g2_InitDisplay(&u8g2);                                                                       // 根据所选的芯片进行初始化工作,初始化完成后,显示器处于关闭状态
    u8g2_SetPowerSave(&u8g2, 0);                                                                   // 打开显示器
    u8g2_ClearBuffer(&u8g2);
    u8g2_SetFont(&u8g2,u8g2_font_6x12_mr);//设置英文字体

    u8g2_ClearBuffer(&u8g2);//清空缓冲区的内容
    u8g2_DrawStr(&u8g2,10,10,"CH32V307 u8g2 Test");
    u8g2_DrawStr(&u8g2,5,20,"Year:");   //输出固定不变的字符串Year,显示年份
    u8g2_DrawStr(&u8g2,75,20,"Month:"); //输出固定不变的字符串Month,显示月份
    u8g2_DrawStr(&u8g2,40,30,"Tem:");   //输出固定不变的字符串Tem ,显示温度

    sprintf(year,"%4d",2023);                    //将年份数据格式化输出到字符串
    u8g2_DrawStr(&u8g2,35,20,year);              //输出实时变化的年份
    sprintf(month,"%2d",04);                     //将月份数据格式化输出到字符串
    u8g2_DrawStr(&u8g2,110,20,month);            //输出实时变化的月份数据
    sprintf(Tem,"%2.1f",22.0);                   //将温度数据格式化输出到字符串
    u8g2_DrawStr(&u8g2,70,30,Tem);               //输出实时变化的温度数据

    u8g2_SendBuffer(&u8g2);//绘制缓冲区的内容

    for(u8 i=0;i<10;i++)
    {
        Delay_Ms(1000);
    }
    while(1)
    {
        u8g2_ClearBuffer(&u8g2);//清空缓冲区的内容
        u8g2_DrawStr(&u8g2,10,10,"CH32V307 u8g2 Test");
        u8g2_DrawStr(&u8g2,5,20,"Year:");   //输出固定不变的字符串Year,显示年份
        u8g2_DrawStr(&u8g2,75,20,"Month:"); //输出固定不变的字符串Month,显示月份
        u8g2_DrawStr(&u8g2,40,30,"Tem:");   //输出固定不变的字符串Tem ,显示温度

        sprintf(year,"%4d",2023);                    //将年份数据格式化输出到字符串
        u8g2_DrawStr(&u8g2,35,20,year);              //输出实时变化的年份
        sprintf(month,"%2d",04);                     //将月份数据格式化输出到字符串
        u8g2_DrawStr(&u8g2,110,20,month);            //输出实时变化的月份数据
        sprintf(Tem,"%2.1f",22.0);                   //将温度数据格式化输出到字符串
        u8g2_DrawStr(&u8g2,70,30,Tem);               //输出实时变化的温度数据

        if(++nTemp>=15) nTemp=1;
        u8g2_DrawCircle(&u8g2, 30, 50, nTemp, U8G2_DRAW_ALL);//画圆
        u8g2_DrawCircle(&u8g2, 50, 50, nTemp, U8G2_DRAW_ALL);//画圆
        u8g2_DrawCircle(&u8g2, 70, 50, nTemp, U8G2_DRAW_ALL);//画圆
        u8g2_DrawCircle(&u8g2, 90, 50, nTemp, U8G2_DRAW_ALL);//画圆

        u8g2_SendBuffer(&u8g2);//绘制缓冲区的内容
    }
}

在main函数中创建 u8g2的句柄,并创建一个临时变量 nTemp,然后对u8g2结构体进行初始化。在while循环中,显示年月、温度信息以及圆的变化,在显示数据信息时,要先使用标准库函数 sprintf() 对数据(原来的格式为浮点型、INT型)格式化输出为字符串,再使用函数 u8g2_DrawStr() 显示到 OLED 显示器上。调用u8g2_DrawCircle函数画圆,由此看到OLED显示4个圆在不断变化。注意在显示时,不会立即显示,会先送入buffer再依次显示。
注意,要显示浮点时,要对MounRiver进行配置,如下图,要注意勾选浮点打印。