前文回顾:
书接前文,下面开始基于HAL库编写OLED显示驱动。如前文所述,先在Hardware文件夹中新建一个OLED文件夹,然后在其中新建一个oled.h和一个oled.c文件。
定义别名
为了提高程序的可读性和通用性,先在oled.h中为常量和常用指令定义别名。SSD1306的指令有几十条,在手册上都可以查到。不必求全,按需定义即可。先定义几个常量:
#define Max_Col 128U
#define Max_Row 32U
#define Max_Page 4U
这3个常量主要是为了适应不同分辨率的屏幕。这里是128×32的屏,共4页。如果换了128×64的,Max_Row和Max_Page相应调整为64U和8U(每页8行)即可。指令定义如下:
typedef enum {
SET_CONTRAST = 0x81, //#2bytes command#
SCROLL_STOP = 0x2E, //Stop scrolling
SCROLL_START = 0x2F, //Start scrolling
SET_SCROLL_AREA = 0xA3, //Set vertical scroll area,
SET_ADDR_MODE = 0x20, //Set memory addressing mode.
SET_COL_ADDR = 0x21, //Setup column start and end address
SET_PAG_ADDR = 0x22, //Setup page start and end address
ADDR_Y_START = 0xB0, //0xB0+i, set PAGE start address in GDDRAM
ADDR_X_LOWER = 0x00, //0x00, set column start address lower 4bit
ADDR_X_HIGHER = 0x10, //0x10, set column start address higher 4bit
SET_MUX = 0xA8, //Set MUX
SET_OSC_FRE = 0xD5, //Set DCLK divide ratio / oscillator frequency FOCS
SET_PRE_CHARGE = 0xD9, //Set pre-charge period A[3:0]:phase 1; A[7:4]:phase 2
SET_VCOMH_LEVE = 0xDB, //Set V_COMH deselect level
SET_CHARGE_PUMP = 0X8D, //Charge pump setting
CHARGE_PUMP_ON = 0x14, //Enable charge pump during display on. (ENABLE BEFORE ON)
CHARGE_PUMP_OFF = 0x10, //Disable charge pump. (RESET)
} OLED_CMD;
//Display mode
typedef enum {
DISPLAY_NORMAL = 0xA6, //1 in RAM ->ON
DISPLAY_INVERSE = 0xA7, //1 in RAM ->OFF
DISPLAY_SLEEP = 0xAE, //Display OFF, sleep mode. (RESET)
DISPALY_WAKEUP = 0xAF, //Display ON, normal mode.
DISPLAY_ENTIRE_RAM = 0xA4, //Entire Display ON. Output follows RAM content. (RESET)
DISPLAY_ENTIRE_ON = 0xA5, //Entire Display ON. Output ignores RAM content.
} DISPLAY_MODE;
以枚举类的形式为常用指令定义了别名。名字按自己喜好随便取,达意即可。显示模式相关的指令单独定义到了一个类中,主要是为了函数传参方便。
头文件中还要有必要的#Include和公共函数的声明。都是常识,略过不表。
指令或数据的发送
OLED是自带驱动模块的,驱动程序只需发送控制指令和把拟显示的内容写入到显存中的合适位置即可。可分为三种基本操作:指令写入、数据写入、定位数据写入位置。
由于这里所用的OLED是I2C接口,写入操作需要调用I2C API中的HAL_I2C_Mem_Write,而不是像前文控制SHT35传感器时那样用HAL_I2C_Master_Transmit。前者是向内存中写入数据,后者是向从机发送数据。HAL_I2C_Mem_Write函数可以一次写入任意数量的数据,因此我们只需把要发送的指令或数据放到一个数组中,然后调用HAL_I2C_Mem_Write一次性发送出去即可。这样操作可以避免HAL库的冗余校验造成额外开销。
先来定义两个写入函数:
// I2C Address of OLED
#define ADDR_WRITE 0x78 //0111 1000
#define ADDR_READ 0x79 //0111 1001
#define ADDR_CMD 0x00
#define ADDR_DAT 0x40
/*----------------------------------------------------------------------------*
* @brief Write an amount of bytes to a specific memory address
* @param DAT/CMD Data/command need to be write
* @param NUM number of bytes
*----------------------------------------------------------------------------*/
void OLED_Write_DAT(u8 *dat, u16 num) {
HAL_StatusTypeDef status;
status = HAL_I2C_Mem_Write( &hi2c1, ADDR_WRITE, ADDR_DAT,
I2C_MEMADD_SIZE_8BIT, dat, num, 100);
if (status!=HAL_OK)
Error_Handler();
}
void OLED_Write_CMD(u8 *cmd, u16 num) {
HAL_StatusTypeDef status;
status = HAL_I2C_Mem_Write( &hi2c1, ADDR_WRITE, ADDR_CMD,
I2C_MEMADD_SIZE_8BIT, cmd, num, 100);
if (status!=HAL_OK)
Error_Handler();
}
OLED的控制器是根据地址来区分读、写指令,以及判断收到的信息是指令还是数据的。因此,一开始先定义了这4个地址。OLED_Write_DAT函数写数据,OLED_Write_CMD写指令。注意第2个地址(第3个参数)的区别。
定位函数如下:
void OLED_Set_Pos(u8 x, u8 y) {
//Locate the SEG at (Col X, Page Y)
u8 cmdlist[] = {
(x&0x0f), //Lower 4 bits
((x&0xf0)>>4U)|0X10, //Higher 4 bits
ADDR_Y_START+y //Page
};
OLED_Write_CMD(cmdlist, sizeof(cmdlist));
}
这个函数把数据写入位置定位到(Col x,Page y)。注意看这个函数,很有代表性。把拟发送的指令:列地址的LSB、MSB和页地址放到一个数组中,然后调用OLED_Write_CMD一次性发送。
做完这些准备工作,接下来就可以按需添加功能函数了。