1. 任务目标
实现OLED屏幕显示信息
2. OLED 介绍
此款 OLED 是Seeed Studio 出品的 Grove 接口的 0.96英寸 OLED 显示屏,驱动IC为 SSD1315.
OLED Display 0.96'' SSSD1315 V1.1 分辨率 为128x64。
供电为 3.3V 或者 5V,通信接口为 I2C。
考虑到电流表 INA219 也是 I2C 通信接口,在STM32开发板上使用同一组I2C硬件,OLED和INA219配置不同的器件地址,就可以一个I2C控制器驱动两个I2C器件。
此开发板有 Arduino 接口,恰好把 Seeed Base Shield 扩展板接上。
3. STM32 I2C配置
3.1 选用 I2C1
信号 | STM32F411 管脚 | 丝印 |
SCL | PB8 | CN10.3 或者 Arduino.D15 |
SDA | PB9 | CN10.5 或者 Arduino.D14 |
Nucleo-F411RE 管脚分配
参考两个文档
• 《UM1724.pdf》,查看其中的 6.12 ST morpho connector 管脚和 6.10 Extension connections Figure19. NUCLEO-F411RE
• 《mb1136-default-c04_schematic.pdf》Page5,Extension connectors
3.2 STM32CubeMX 工程配置
• 在 Connecity 中选择 I2C1
• 在右侧使能 i2c
• 配置 I2C 参数为 Standard Mode, 速率 100KHz,地址为7比特,主要的从器件地址为 0x78
3.3 PB8 配置为SCL
3.4 PB9 配置为 SDA
4. PCtoLCD 汉字取模软件
此次任务在OLED上展示中文字符如下:
电子产品世界 得捷 你好 功率检测 系统控制 电压电流 |
所有取模的文字:
电子产品世界得捷你好功率监测系统控制电压流 |
PCtoLCD2002 软件配置如下:
• 点阵格式:阴码
• 取模方式:列行式
• 取模走向:逆向(低位在前)
• 自定义格式:C51格式,且行前缀、行后缀都为空。
在输入框输入文字后点击“生成字模”,拷贝到工程中即可。
5. 流程图
6. 关键代码
6.1 主函数
• HAL库初始化
• 时钟初始化
• 硬件初始化,如GPIO,TIM,UART,I2C 等
• OLED初始化,清屏
• 显示界面,界面切换,死循环
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART2_UART_Init(); MX_TIM1_Init(); MX_I2C1_Init(); /* USER CODE BEGIN 2 */ printf("\r\n"); printf("\t\t EEPW_2025_DIY1_Task2_OLED \r\n"); printf("\t\t Build: %s %s \r\n", __DATE__, __TIME__); printf("\r\n"); HAL_TIM_Base_Start_IT(&htim1); OLED_Init(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { screen_00_welcome(); HAL_Delay(1000 * 3); screen_01_welcome(); HAL_Delay(1000 * 3); screen_02_diy_power_monitor(); HAL_Delay(1000 * 5); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ // HAL_Delay(2000); } /* USER CODE END 3 */ }
6.2 I2C 初始化
这里的初始化代码是 STM32CubeMX 生成的,初始化参数来自界面配置的参数。
• 函数 MX_I2C1_Init() 先保存 I2C 参数到 hi2c1 结构体上,调用 HAL_I2C_Init() 初始化硬件
• 函数 HAL_I2C_MspInit() 由 HAL_I2C_Init() 函数调用,初始化 I2C1 管脚,配置为复用功能
void MX_I2C1_Init(void) { /* USER CODE BEGIN I2C1_Init 0 */ /* USER CODE END I2C1_Init 0 */ /* USER CODE BEGIN I2C1_Init 1 */ /* USER CODE END I2C1_Init 1 */ hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 240; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN I2C1_Init 2 */ /* USER CODE END I2C1_Init 2 */ } void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(i2cHandle->Instance==I2C1) { /* USER CODE BEGIN I2C1_MspInit 0 */ /* USER CODE END I2C1_MspInit 0 */ __HAL_RCC_GPIOB_CLK_ENABLE(); /**I2C1 GPIO Configuration PB8 ------> I2C1_SCL PB9 ------> I2C1_SDA */ GPIO_InitStruct.Pin = OLED_SCL_Pin|OLED_SDA_Pin; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* I2C1 clock enable */ __HAL_RCC_I2C1_CLK_ENABLE(); /* USER CODE BEGIN I2C1_MspInit 1 */ /* USER CODE END I2C1_MspInit 1 */ } }
6.3 OLED 接口函数
OLED 接口层,主要实现两个函数,其他的 OLED API都是依赖这两个函数
• Oled_Write_Data() 发送数据到 OLED IC
• Oled_Write_Cmd() 发送命令到 OLED IC
6.3.1 Oled_Write_Data() 实现
这里直接调用 HAL API 往 OLED 发送数据
void Oled_Write_Data(uint8_t data) { HAL_I2C_Mem_Write(&HI2CX, OLED_ID, OLED_WR_DATA, I2C_MEMADD_SIZE_8BIT, &data, 1, 0x100); }
6.3.2 Oled_Write_Cmd() 实现
这里直接调用 HAL API 往 OLED 发送命令
void Oled_Write_Cmd(uint8_t cmd) { HAL_I2C_Mem_Write(&HI2CX, OLED_ID, OLED_WR_CMD, I2C_MEMADD_SIZE_8BIT, &cmd, 1, 0x100); }
6.4 OLED API
这里仅列举几个常用的 API 实现。
6.4.1 OLED_Init() 初始化
OLED 上电后需要发送命令配置参数
void OLED_Init(void) { /* 设置显示打开/关闭 * AE--->显示打开 * AF--->显示关闭(休眠模式) */ Oled_Write_Cmd(0xAE); /* ================== 基本命令表 ===================*/ /* 设置对比度 * 0~255:数值越大 亮度越亮 */ Oled_Write_Cmd(0x81); Oled_Write_Cmd(0xFF); /* 使能全屏显示 * A4--->恢复到RAM内容显示 * A5--->忽略RAM内容显示 */ Oled_Write_Cmd(0xA4); /* 设置显示模式 * A6--->正常显示:0灭1亮 * A7--->逆显示:1灭0亮 */ Oled_Write_Cmd(0xA6); /* ================== 滚动命令表 ===================*/ /* 滚动使能/失能 * 2E--->失能 * 2F--->使能 */ Oled_Write_Cmd(0x2E); /* 七字节命令: 连续水平滚动设置 */ /* 左/右水平滚动设置 * 26--->右水平滚动 * 27--->左水平滚动 */ Oled_Write_Cmd(0x26); /* 虚拟字节 */ Oled_Write_Cmd(0x00); /* 设置滚动起始页地址 */ Oled_Write_Cmd(0x00); /* 设置滚动间隔 */ Oled_Write_Cmd(0x03); /* 设置滚动结束地址 */ Oled_Write_Cmd(0x07); /* 虚拟字节 */ Oled_Write_Cmd(0x00); Oled_Write_Cmd(0xFF); /* =============== 寻址设置命令表 ==================*/ /* 双字节命令:寄存器寻址模式 */ Oled_Write_Cmd(0x20); /* 10:页寻址模式 * 01:垂直寻址模式 * 00:水平寻址模式 */ Oled_Write_Cmd(0x10); /* 单字节命令:设置页寻址的起始页地址 */ Oled_Write_Cmd(0xB0); /* 单字节命令:设置页寻址的起始列地址低位 */ Oled_Write_Cmd(0x00); /* 单字节命令:设置页寻址的起始列地址高位 */ Oled_Write_Cmd(0x10); /*=============== 硬件配置命令表 ==================*/ /* 设置显示开始线 * 0x40~0x7F对应0~63 */ Oled_Write_Cmd(0x40); /* 设置列重映射 * A0:addressX--->segX * A1:addressX--->seg(127-X) */ Oled_Write_Cmd(0xA1); /* 设置多路复用比 */ Oled_Write_Cmd(0xA8); Oled_Write_Cmd(0x3F); /* 设置COM输出扫描方向 * C0:COM0--->COM63(从上往下扫描) * C8:COM63--->COM0(从下往上扫描) */ Oled_Write_Cmd(0xC8); /* 双字节命令:设置COM显示偏移量 */ Oled_Write_Cmd(0xD3); Oled_Write_Cmd(0x00); /* COM不偏移 */ /* 双字节命令:配置COM重映射 */ Oled_Write_Cmd(0xDA); Oled_Write_Cmd(0x12); /* 双字节命令:设置预充期 */ Oled_Write_Cmd(0xD9); Oled_Write_Cmd(0x22); /* 阶段一2个无效DCLK时钟/阶段二2个无效DCLK时钟 */ /* 设置VCOMH取消选择电平 * 00:0.65xVcc * 20:0.77xVcc * 30:0.83xVcc */ Oled_Write_Cmd(0xDB); Oled_Write_Cmd(0x20); /* 双字节命令:设置电荷泵 */ Oled_Write_Cmd(0x8d); Oled_Write_Cmd(0x14); Oled_Write_Cmd(0xAF); }
6.4.2 OLED_Fill() 刷屏
m 表示page 地址,数值0~7,n 表示列地址,数值 0~127。
填充的数据 fill_data,设置为0,则屏幕为黑色;设置为1,则屏幕白色;设置为其他值,屏幕显示条纹。
void OLED_Fill(uint8_t fill_data) // 全屏填充 { unsigned char m, n; for (m = 0; m < 8; m++) { Oled_Write_Cmd(0xb0 + m); // page0-page1 Oled_Write_Cmd(0x00); // low column start address Oled_Write_Cmd(0x10); // high column start address for (n = 0; n < 128; n++) { Oled_Write_Data(fill_data); } } }
6.4.3 OLED_ShowStr()
这里支持两种字体大小,分别是 6x8 和 8x16 大小。
/** * @brief 显示 ASCII 字符,有 6x8 和 8x16 字体大小可以选择 * * @param x 起始点坐标 x [0, 127] * @param y 起始点坐标 y [0, 7] * @param ch 要显示的字符串 * @param textsize 1 表示字体大小 6x8; 2 表示字体大小 8x16 */ void OLED_ShowStr(uint16_t x, uint16_t y, uint8_t ch[], ascii_font_e font) { uint8_t c = 0, i = 0, j = 0; switch (font) { case ASCII_FONT_6X8: { while (ch[j] != '\0') { c = ch[j] - 32; if (x > 126) { x = 0; y++; } OLED_SetPos(x, y); for (i = 0; i < 6; i++) Oled_Write_Data(FONT_ASCII_6x8[c][i]); x += 6; j++; } break; } case ASCII_FONT_8X16: { while (ch[j] != '\0') { c = ch[j] - 32; if (x > 120) { x = 0; y++; } OLED_SetPos(x, y); for (i = 0; i < 8; i++) Oled_Write_Data(FONT_ASCII_8x16[c * 16 + i]); OLED_SetPos(x, y + 1); for (i = 0; i < 8; i++) Oled_Write_Data(FONT_ASCII_8x16[c * 16 + i + 8]); x += 8; j++; } break; } default: break; } }
6.4.4 OLED_ShowCN()
这里只支持一种中文字体大小,16x16 。
/** * @brief 显示 oled_codetab.h 中的汉字,16x16 点阵 * * @param x 起始坐标 x [0, 127] * @param y 起始坐标 y [0, 7] * @param n 汉字在 16x16 点阵中的索引,下标从0开始 */ void OLED_ShowCN(uint16_t x, uint16_t y, uint16_t index) { unsigned char wm = 0; unsigned int adder = 32 * index; OLED_SetPos(x, y); for (wm = 0; wm < 16; wm++) { Oled_Write_Data(FONT_CN_16x16[adder]); adder += 1; } OLED_SetPos(x, y + 1); for (wm = 0; wm < 16; wm++) { Oled_Write_Data(FONT_CN_16x16[adder]); adder += 1; } }
7. 实操演示
显示中文欢迎界面
显示英文欢迎界面
显示功率检测界面,其中的电压、电流和功率数值是预设值,并非实际测量的。