CH32V307 u8g2移植
前言:该篇文章以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进行配置,如下图,要注意勾选浮点打印。