在Keil MDK中实现OLED屏显示控制(SSD1306+STM32)

你想了解的是在Keil MDK开发环境下,通过代码完成OLED屏(以最常用的SSD1306驱动芯片、I2C接口为例)的显示控制,核心是先搭建Keil工程环境,再实现I2C底层时序、SSD1306初始化和显示功能函数,最终完成字符/数字/字符串的显示。下面从工程准备到代码实战,一步步教你实现。

一、前置准备

1.1 硬件与环境要求

  • 硬件:STM32单片机(以STM32F103C8T6为例)、0.96英寸I2C接口OLED屏(SSD1306驱动)、杜邦线;
  • 软件:Keil MDK-ARM V5.x(需安装对应STM32的Device Pack);
  • 基础工程:已搭建好STM32F103的Keil工程(包含系统初始化、延时函数)。

1.2 硬件接线(I2C接口)

OLED屏4个核心引脚与STM32的接线如下(可自定义GPIO,后续代码对应修改即可):

OLED引脚 STM32引脚 功能说明
VCC 3.3V 供电(禁止接5V)
GND GND 共地
SCL PB6 I2C时钟线
SDA PB7 I2C数据线

二、Keil工程中添加OLED驱动代码

2.1 新建驱动文件

在Keil工程中新建两个文件:oled.h(头文件)和oled.c(源文件),添加到工程目录并纳入编译。

2.2 实现I2C底层驱动(软件模拟,新手友好)

软件I2C无需依赖STM32硬件I2C外设,仅通过GPIO模拟时序,兼容性更强,先在oled.c中实现:

#include "oled.h"
#include "delay.h"  // 需提前实现us/ms级延时函数

// ************************* 引脚定义(可根据实际修改)*************************
#define OLED_SCL_PIN     GPIO_Pin_6
#define OLED_SCL_PORT    GPIOB
#define OLED_SDA_PIN     GPIO_Pin_7
#define OLED_SDA_PORT    GPIOB

// 引脚电平控制宏
#define OLED_SCL_H()  GPIO_SetBits(OLED_SCL_PORT, OLED_SCL_PIN)
#define OLED_SCL_L()  GPIO_ResetBits(OLED_SCL_PORT, OLED_SCL_PIN)
#define OLED_SDA_H()  GPIO_SetBits(OLED_SDA_PORT, OLED_SDA_PIN)
#define OLED_SDA_L()  GPIO_ResetBits(OLED_SDA_PORT, OLED_SDA_PIN)

/**
 * @brief  初始化OLED的I2C引脚
 * @param  无
 * @retval 无
 */
void OLED_I2C_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    
    // 使能GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    // 配置SCL/SDA为推挽输出
    GPIO_InitStruct.GPIO_Pin = OLED_SCL_PIN | OLED_SDA_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;  // 推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(OLED_SCL_PORT, &GPIO_InitStruct);
    
    // 初始电平置高
    OLED_SCL_H();
    OLED_SDA_H();
}

/**
 * @brief  软件I2C起始信号(时序核心)
 * @param  无
 * @retval 无
 */
void OLED_I2C_Start(void)
{
    OLED_SDA_H();
    OLED_SCL_H();
    delay_us(2);  // 延时保证时序稳定,需确保delay_us函数精准
    OLED_SDA_L(); // SDA在SCL高电平时拉低,产生起始信号
    delay_us(2);
    OLED_SCL_L();
}

/**
 * @brief  软件I2C停止信号
 * @param  无
 * @retval 无
 */
void OLED_I2C_Stop(void)
{
    OLED_SDA_L();
    OLED_SCL_H();
    delay_us(2);
    OLED_SDA_H(); // SDA在SCL高电平时拉高,产生停止信号
    delay_us(2);
    OLED_SCL_L();
}

/**
 * @brief  软件I2C发送一个字节
 * @param  dat:待发送的8位数据
 * @retval 无
 */
void OLED_I2C_SendByte(uint8_t dat)
{
    uint8_t i;
    for(i=0; i<8; i++)
    {
        OLED_SCL_L();
        delay_us(2);
        // 高位先行,逐位发送
        if(dat & 0x80) OLED_SDA_H();
        else OLED_SDA_L();
        dat <<= 1;
        delay_us(2);
        OLED_SCL_H();
        delay_us(2);
    }
    // 省略应答检测(新手简化版)
    OLED_SCL_L();
    delay_us(2);
    OLED_SDA_H();
    delay_us(2);
    OLED_SCL_H();
    delay_us(2);
    OLED_SCL_L();
}

/**
 * @brief  向OLED发送指令
 * @param  cmd:指令字节
 * @retval 无
 */
void OLED_SendCmd(uint8_t cmd)
{
    OLED_I2C_Start();
    OLED_I2C_SendByte(0x78);  // SSD1306 I2C设备地址(0x3C<<1,部分屏为0x38)
    OLED_I2C_SendByte(0x00);  // 0x00表示后续是指令
    OLED_I2C_SendByte(cmd);
    OLED_I2C_Stop();
}

/**
 * @brief  向OLED发送显示数据
 * @param  dat:像素数据字节
 * @retval 无
 */
void OLED_SendData(uint8_t dat)
{
    OLED_I2C_Start();
    OLED_I2C_SendByte(0x78);  // 设备地址
    OLED_I2C_SendByte(0x40);  // 0x40表示后续是数据
    OLED_I2C_SendByte(dat);
    OLED_I2C_Stop();
}

2.3 实现OLED初始化与坐标设置

oled.c中继续添加SSD1306初始化函数(配置显示参数)和坐标设置函数:

/**
 * @brief  OLED屏初始化(SSD1306核心配置)
 * @param  无
 * @retval 无
 */
void OLED_Init(void)
{
    delay_ms(100);  // 上电延时,确保芯片稳定
    
    OLED_I2C_Init();  // 初始化I2C引脚
    
    // SSD1306初始化指令集
    OLED_SendCmd(0xAE);  // 关闭显示
    OLED_SendCmd(0x00);  // 设置列起始地址低4位
    OLED_SendCmd(0x10);  // 设置列起始地址高4位
    OLED_SendCmd(0x40);  // 设置显示起始行
    OLED_SendCmd(0xB0);  // 设置页地址(Y轴,每页8行)
    OLED_SendCmd(0x81);  // 开启对比度调节
    OLED_SendCmd(0xFF);  // 对比度最大值(0~255)
    OLED_SendCmd(0xA1);  // 段重映射(正常显示,0xA0为左右翻转)
    OLED_SendCmd(0xA6);  // 正常显示(0xA7为反显)
    OLED_SendCmd(0xA8);  // 设置多路复用率
    OLED_SendCmd(0x3F);  // 64行显示
    OLED_SendCmd(0xC8);  // 扫描方向(正常显示,0xC0为上下翻转)
    OLED_SendCmd(0xD3);  // 设置显示偏移
    OLED_SendCmd(0x00);  // 偏移量0
    OLED_SendCmd(0xD5);  // 设置时钟分频因子
    OLED_SendCmd(0x80);  // 默认值
    OLED_SendCmd(0xD9);  // 设置预充电周期
    OLED_SendCmd(0xF1);
    OLED_SendCmd(0xDA);  // 设置COM引脚配置
    OLED_SendCmd(0x12);
    OLED_SendCmd(0xDB);  // 设置VCOMH电压
    OLED_SendCmd(0x40);
    OLED_SendCmd(0x8D);  // 启用电荷泵(必须开启,否则无显示)
    OLED_SendCmd(0x14);
    OLED_SendCmd(0xAF);  // 开启显示
}

/**
 * @brief  设置OLED显示坐标
 * @param  x:列坐标(0~127)
 * @param  y:页坐标(0~7,每页对应8行)
 * @retval 无
 */
void OLED_SetPos(uint8_t x, uint8_t y)
{
    OLED_SendCmd(0xB0 + y);                // 设置页地址
    OLED_SendCmd(((x & 0xF0) >> 4) | 0x10); // 列地址高4位
    OLED_SendCmd(x & 0x0F);                // 列地址低4位
}

/**
 * @brief  OLED清屏
 * @param  无
 * @retval 无
 */
void OLED_Clear(void)
{
    uint8_t x, y;
    for(y=0; y<8; y++)  // 遍历8个页
    {
        OLED_SetPos(0, y);
        for(x=0; x<128; x++)  // 遍历128列
        {
            OLED_SendData(0x00);  // 写入0,关闭所有像素
        }
    }
}

2.4 实现字符/字符串显示函数

添加8×16点阵ASCII字模(截取数字/字母部分)和显示函数,在oled.c中补充:

// 8×16 ASCII字模表(数字0-9,可扩展字母/符号)
const unsigned char OLED_F8x16[] = {
    0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,0x00,0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,0x00, // 0
    0x00,0x00,0x00,0x70,0x08,0x08,0x08,0x70,0x00,0x00,0x00,0x70,0x08,0x08,0x08,0x70, // 1
    0x00,0x00,0x7C,0x02,0x01,0x02,0x7C,0x00,0x00,0x00,0x7C,0x02,0x01,0x02,0x7C,0x00, // 2
    // 其余数字/字母字模可自行补充
};

/**
 * @brief  显示单个字符(8×16点阵)
 * @param  x:起始列(0~127)
 * @param  y:起始页(0~7,单个字符占2页)
 * @param  c:待显示字符(ASCII码)
 * @retval 无
 */
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t c)
{
    uint8_t i;
    c -= '0';  // 以数字0为基准偏移(显示字母需调整偏移量)
    // 显示上半部分(第1页)
    OLED_SetPos(x, y);
    for(i=0; i<8; i++)
    {
        OLED_SendData(OLED_F8x16[c*16 + i]);
    }
    // 显示下半部分(第2页)
    OLED_SetPos(x, y+1);
    for(i=8; i<16; i++)
    {
        OLED_SendData(OLED_F8x16[c*16 + i]);
    }
}

/**
 * @brief  显示字符串
 * @param  x:起始列
 * @param  y:起始页
 * @param  str:字符串指针
 * @retval 无
 */
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str)
{
    while(*str != '\0')
    {
        OLED_ShowChar(x, y, *str);
        x += 8;  // 每个字符占8列
        if(x > 120)  // 超出屏幕宽度则换行
        {
            x = 0;
            y += 2;
        }
        str++;
    }
}

2.5 头文件oled.h声明

oled.h中添加函数声明和必要的头文件,方便主函数调用:

#ifndef __OLED_H
#define __OLED_H

#include "stm32f10x.h"
#include "stdint.h"

// 函数声明
void OLED_I2C_Init(void);
void OLED_SendCmd(uint8_t cmd);
void OLED_SendData(uint8_t dat);
void OLED_Init(void);
void OLED_SetPos(uint8_t x, uint8_t y);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t c);
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str);

#endif

三、Keil中编写主函数测试

main.c中调用OLED驱动函数,实现显示测试:

#include "stm32f10x.h"
#include "oled.h"
#include "delay.h"  // 需确保delay_init、delay_ms、delay_us已实现

int main(void)
{
    // 系统初始化
    SystemInit();          // 配置STM32主频(72MHz)
    delay_init();          // 初始化延时函数
    OLED_Init();           // 初始化OLED屏
    OLED_Clear();          // 清屏
    
    while(1)
    {
        // 显示固定字符串
        OLED_ShowString(0, 0, "Keil OLED Test");
        // 显示数字(示例:计数递增)
        static uint32_t cnt = 0;
        OLED_ShowString(0, 2, "Count:");
        // 拼接数字为字符串(或扩展OLED_ShowNum函数)
        uint8_t num_str[6];
        sprintf((char*)num_str, "%05d", cnt);
        OLED_ShowString(40, 2, num_str);
        
        cnt++;
        if(cnt > 99999) cnt = 0;
        delay_ms(100);  // 100ms刷新一次
    }
}

四、Keil工程编译与下载

  1. 检查工程:确保oled.c已添加到Keil工程的Source Group中,头文件路径正确;
  2. 编译工程:点击Keil工具栏的BuildRebuild,解决语法错误(如缺少延时函数、GPIO定义错误);
  3. 下载程序:连接调试器(J-Link/ST-Link),点击Download将程序下载到STM32;
  4. 验证效果:上电后OLED屏显示“Keil OLED Test”和递增的数字,说明控制成功。

五、常见问题排查(Keil环境下)

  1. 编译报错“undefined reference to delay_us”
    • 原因:未实现延时函数,或未添加到工程;
    • 解决:补充delay.c/delay.h,实现基于SysTick的delay_usdelay_ms
  2. 下载后OLED无显示
    • 检查Keil调试配置:确认Debug标签页中调试器、Core Clock配置正确;
    • 检查I2C地址:将OLED_I2C_SendByte(0x78)改为0x38测试(部分屏地址为0x1C<<1);
    • 检查代码烧录:Keil中点击View->Watch & Call Stack Window,确认OLED_Init执行完成。
  3. 显示乱码
    • 原因:字模点阵与显示函数不匹配,或Keil编译器优化等级过高;
    • 解决:将Keil优化等级改为-O0(Options for Target->Optimization),核对字模点阵格式。
posted @ 2026-01-14 22:35  wo是个狠人  阅读(0)  评论(0)    收藏  举报