你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【福利三:逢7发帖赢大礼】2、基于STM32G070板子的OLED移植U8G2库

[复制链接]
STMWoodData 发布时间:2026-7-10 00:35

本次分享下板子上OLED显示屏移植U8G2库。U8G2是一个专为嵌入式设备设计的开源单色图形库。用于驱动单色OLED和LCD显示屏,因其功能强大、支持广泛而被众多开发者采用。

核心特点: 广泛的兼容性:支持超过128种显示控制器,几乎涵盖了所有常见的小型单色屏幕。包括但不限于:OLED驱动:SSD1306, SH1106, SSD1309等。LCD驱动:ST7565, ST7920, PCD8544(Nokia 5110)等。

多接口支持:兼容I²C、SPI和并行接口,可灵活适配Arduino、ESP32、STM32等多种平台。

丰富的图形与字体:提供点、线、圆、矩形、三角形等基本图形的绘制API。内置超过700种字体,并支持UTF-8编码,方便显示中文等多语言字符。

灵活的内存管理模式:针对资源受限的嵌入式环境,提供了三种缓冲模式。全缓冲模式:在内存中维护完整的显示缓冲区,适合流畅动画,但内存消耗大。页面缓冲模式:将屏幕分成多个“页”进行渲染,内存需求低,是官方推荐的模式。直接模式:无缓冲区,直接将数据写入屏幕,内存占用极低。

板上OLED使用I2C2接口SSD1306驱动。I2C2引脚为PA11和PA12。

image.png

在STM32CubeMX中选择I2C初始化参数。然后生成KEIL代码工程。

image.png

接着下载U8G2库代码,添加到工程中。

image.png

添加接口代码,发送数据使用HAL_I2C_Master_Transmit(&hi2c2, 0x3C<<1, buffer, buf_idx, HAL_MAX_DELAY);


#define U8G2_SSD1306_I2C_NUM         0
#define U8G2_SSD1306_I2C_ADDR        (0x3C)

uint8_t u8x8_byte_hc32_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    static uint8_t buffer[32];                /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */
    static uint8_t buf_idx;
    uint8_t *data;

    switch(msg)
    {
        case U8X8_MSG_BYTE_SEND:
        data = (uint8_t *)arg_ptr;
        while( arg_int > 0 )
        {
        buffer[buf_idx++] = *data;
        data++;
        arg_int--;
        }    
        break;
        case U8X8_MSG_BYTE_INIT:
        /* add your custom code to init i2c subsystem */

        break;
        case U8X8_MSG_BYTE_SET_DC:
        /* ignored for i2c */

        break;
        case U8X8_MSG_BYTE_START_TRANSFER:
        buf_idx = 0;
        break;
        case U8X8_MSG_BYTE_END_TRANSFER:
        HAL_I2C_Master_Transmit(&hi2c2, 0x3C<<1, buffer, buf_idx, HAL_MAX_DELAY);
        //I2C_WriteOnly(U8G2_SSD1306_I2C_NUM, U8G2_SSD1306_I2C_ADDR, buffer, buf_idx);
        break;
        default:
        return 0;
    }
    return 1;
}

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:
        /* Insert codes for initialization */
        break;
    case U8X8_MSG_DELAY_MILLI:
        /* ms Delay */
        delay_ms(arg_int);
        break;
    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;
}

然后就是U8G2 gui初始化代码和绘图字符显示测试代码:

void u8g2Init(u8g2_t *u8g2)
{
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hc32_hw_i2c, u8x8_gpio_and_delay);
    u8g2_InitDisplay(u8g2);
    u8g2_SetPowerSave(u8g2, 0);
    u8g2_ClearBuffer(u8g2);
    u8g2_SendBuffer(u8g2);
}

void draw(u8g2_t *u8g2)
{
    u8g2_ClearDisplay(u8g2);
    u8g2_SetFontMode(u8g2, 1); /*字体模式选择*/
    u8g2_SetFontDirection(u8g2, 0); /*字体方向选择*/
    u8g2_SetFont(u8g2, u8g2_font_inb24_mf); /*字库选择*/
    u8g2_DrawStr(u8g2, 0, 20, "U");

    u8g2_SetFontDirection(u8g2, 1);
    u8g2_SetFont(u8g2, u8g2_font_inb30_mn);
    u8g2_DrawStr(u8g2, 21,8,"8");

    u8g2_SetFontDirection(u8g2, 0);
    u8g2_SetFont(u8g2, u8g2_font_inb24_mf);
    u8g2_DrawStr(u8g2, 51,30,"g");
    u8g2_DrawStr(u8g2, 67,30,"\xb2");

    u8g2_DrawHLine(u8g2, 2, 35, 47);
    u8g2_DrawHLine(u8g2, 3, 36, 47);
    u8g2_DrawVLine(u8g2, 45, 32, 12);
    u8g2_DrawVLine(u8g2, 46, 33, 12);

    u8g2_SetFont(u8g2, u8g2_font_5x8_tr);
    u8g2_DrawStr(u8g2, 1,54,"github.com/olikraus/u8g2");

    u8g2_SendBuffer(u8g2);
}

再在MAIN中调用初始化和测试代码,定义变量u8g2_t u8g2;

image.png

编译下载之后可以看到OLED屏上显示字符串。

image.png

下面利用OLED和按键基于U8G2实现一个简单的打方块游戏玩玩。游戏控制逻辑和显示代码使用AI生成的,我简单调整下就可以用了。

代码:

#include <stdlib.h>     // 用于 rand() 和 srand()
#include <string.h>     // 用于 sprintf()
#include <stdio.h>     // 用于 sprintf()

#include "main.h"     // 用于 sprintf()
#include "u8g2.h"       // U8G2库核心头文件

// --- 硬件配置 (需要根据你的平台修改) ---
// 这些函数需要底层实现: 读取按键状态, 初始化硬件等
extern void delay_ms(uint32_t ms);    // 毫秒级延时函数

u8g2_t u8g2;

// --- 游戏常量 ---
#define SCREEN_WIDTH    128
#define SCREEN_HEIGHT   64

#define PADDLE_WIDTH    20
#define PADDLE_HEIGHT   4
#define PADDLE_Y        (SCREEN_HEIGHT - 10)

#define BALL_RADIUS     2
#define BALL_SPEED      1.5f

#define BRICK_ROWS      5       // 砖块行数
#define BRICK_COLS      16      // 砖块列数
#define BRICK_WIDTH     8
#define BRICK_HEIGHT    6
#define BRICK_TOP       10      // 砖块区域顶部Y坐标

// --- 游戏状态变量 ---
typedef struct {
    float ballX, ballY;         // 球心坐标
    float ballSpeedX, ballSpeedY;
    int paddleX;                // 挡板左上角X坐标
    bool brickMap[BRICK_COLS][BRICK_ROWS]; // 砖块地图: true表示存在
    int score;
    bool gameOver;
    bool levelComplete;
} GameState;

GameState game;

// --- 函数声明 ---
static void game_update(void);
static void game_draw(void);
static bool check_collision(float ax, float ay, float aw, float ah,
                            float bx, float by, float bw, float bh);

// ============================================================
// 初始化游戏
// ============================================================
void game_init(void) 
{
    // 初始化随机数种子 (如需随机生成砖块)
    // srand(42); // 或者使用硬件RNG

    game.paddleX = (SCREEN_WIDTH - PADDLE_WIDTH) / 2;
    game.ballX = SCREEN_WIDTH / 2;
    game.ballY = SCREEN_HEIGHT / 2;
    game.ballSpeedX = BALL_SPEED;
    game.ballSpeedY = -BALL_SPEED;
    game.score = 0;
    game.gameOver = false;
    game.levelComplete = false;

    // 初始化砖块地图: 全部置为true (表示有砖块)
    for (int x = 0; x < BRICK_COLS; x++) {
        for (int y = 0; y < BRICK_ROWS; y++) {
            // 可选: 隔列留空, 增加趣味性
            game.brickMap[x][y] = ( (x % 2 == 0) || (y % 2 == 0) );
        }
    }
}

// ============================================================
// 矩形碰撞检测工具函数
// ============================================================
static bool check_collision(float ax, float ay, float aw, float ah,
                            float bx, float by, float bw, float bh) {
    return (ax < bx + bw && ax + aw > bx &&
            ay < by + bh && ay + ah > by);
}

// ============================================================
// 更新游戏逻辑 (物理、碰撞等)
// ============================================================
static void game_update(void) 
{
    if (game.gameOver || game.levelComplete) {
        return;
    }

    // 1. 读取按键并更新挡板位置
    if (GetKey_KEY2()) {
        game.paddleX -= 4;
        if (game.paddleX < 0) game.paddleX = 0;
    }
    if (GetKey_KEY3()) {
        game.paddleX += 4;
        if (game.paddleX + PADDLE_WIDTH > SCREEN_WIDTH) {
            game.paddleX = SCREEN_WIDTH - PADDLE_WIDTH;
        }
    }

    // 2. 更新小球位置
    game.ballX += game.ballSpeedX;
    game.ballY += game.ballSpeedY;

    // 3. 边界碰撞 (左右墙)
    if (game.ballX - BALL_RADIUS < 0 || game.ballX + BALL_RADIUS > SCREEN_WIDTH) {
        game.ballSpeedX = -game.ballSpeedX;
    }
    // 边界碰撞 (上墙)
    if (game.ballY - BALL_RADIUS < 0) {
        game.ballSpeedY = -game.ballSpeedY;
    }
    // 边界碰撞 (下墙 -> 游戏结束)
    if (game.ballY + BALL_RADIUS > SCREEN_HEIGHT) {
        game.gameOver = true;
        return;
    }

    // 4. 挡板碰撞检测
    if (check_collision(
            game.ballX - BALL_RADIUS, game.ballY - BALL_RADIUS, BALL_RADIUS*2, BALL_RADIUS*2,
            game.paddleX, PADDLE_Y, PADDLE_WIDTH, PADDLE_HEIGHT)) 
    {
        // 根据撞击位置改变球的水平速度, 增加可玩性
        float hitPos = (game.ballX - game.paddleX) / PADDLE_WIDTH; // 0~1
        game.ballSpeedX = (hitPos - 0.5f) * 3.0f; // -1.5 ~ 1.5
        game.ballSpeedY = -BALL_SPEED; // 确保向上反弹
        // 将球移到挡板上方, 防止卡碰撞
        game.ballY = PADDLE_Y - BALL_RADIUS;
    }

    // 5. 砖块碰撞检测
    for (int x = 0; x < BRICK_COLS; x++) {
        for (int y = 0; y < BRICK_ROWS; y++) {
            if (game.brickMap[x][y]) {
                int brickX = x * BRICK_WIDTH;
                int brickY = y * BRICK_HEIGHT + BRICK_TOP;

                if (check_collision(
                        game.ballX - BALL_RADIUS, game.ballY - BALL_RADIUS, BALL_RADIUS*2, BALL_RADIUS*2,
                        brickX, brickY, BRICK_WIDTH, BRICK_HEIGHT)) {
                    // 消除砖块
                    game.brickMap[x][y] = false;
                    game.score++;

                    // 反转Y轴速度
                    game.ballSpeedY = -game.ballSpeedY;

                    // 检查是否所有砖块都被消除
                    bool allCleared = true;
                    for (int x2 = 0; x2 < BRICK_COLS; x2++) {
                        for (int y2 = 0; y2 < BRICK_ROWS; y2++) {
                            if (game.brickMap[x2][y2]) {
                                allCleared = false;
                                break;
                            }
                        }
                        if (!allCleared) break;
                    }
                    if (allCleared) {
                        game.levelComplete = true;
                    }
                    // 跳出双重循环 (一个球一次只撞一块砖)
                    goto collision_done;
                }
            }
        }
    }
collision_done:
    // 空语句, 用于跳出嵌套循环
    ;
}

// ============================================================
// 绘制游戏画面 (使用U8G2 API)
// ============================================================
static void game_draw(void) {
    // --- 开始绘制 (U8G2双缓冲标准流程) ---
    u8g2_FirstPage(&u8g2);
    do {
        // 1. 绘制砖块
        for (int x = 0; x < BRICK_COLS; x++) {
            for (int y = 0; y < BRICK_ROWS; y++) {
                if (game.brickMap[x][y]) {
                    int brickX = x * BRICK_WIDTH;
                    int brickY = y * BRICK_HEIGHT + BRICK_TOP;
                    // 绘制空心砖块 (更美观)
                    u8g2_DrawFrame(&u8g2, brickX, brickY, BRICK_WIDTH, BRICK_HEIGHT);
                    // 若需要实心砖块, 可使用: u8g2_DrawBox(&u8g2, brickX, brickY, BRICK_WIDTH, BRICK_HEIGHT);
                }
            }
        }

        // 2. 绘制挡板
        u8g2_DrawBox(&u8g2, game.paddleX, PADDLE_Y, PADDLE_WIDTH, PADDLE_HEIGHT);

        // 3. 绘制小球
        u8g2_DrawDisc(&u8g2, (uint16_t)game.ballX, (uint16_t)game.ballY, BALL_RADIUS,U8G2_DRAW_ALL);

        // 4. 绘制分数
        char scoreStr[16];
        sprintf(scoreStr, "Score:%d", game.score);
        u8g2_SetFont(&u8g2, u8g2_font_6x10_tf);
        u8g2_DrawStr(&u8g2, 0, 8, scoreStr);

        // 5. 绘制游戏结束/通关信息
        if (game.gameOver) {
            u8g2_SetFont(&u8g2, u8g2_font_6x10_tf);
            u8g2_DrawStr(&u8g2, 30, 32, "GAME OVER");
            u8g2_DrawStr(&u8g2, 20, 45, "Press RESET");
        } else if (game.levelComplete) {
            u8g2_SetFont(&u8g2, u8g2_font_6x10_tf);
            u8g2_DrawStr(&u8g2, 20, 32, "LEVEL COMPLETE!");
            u8g2_DrawStr(&u8g2, 20, 45, "Press RESET");
        }
    } while (u8g2_NextPage(&u8g2));
}

// ============================================================
// 主循环 (在你的 main() 中调用)
// ============================================================
void game_main_loop(void)
{
    // 检测复位按键
    if (GetKey_KEY1()) {
        game_init();
        // 等待按键释放, 防止连续复位
        while (GetKey_KEY1()) {
            delay_ms(1);
        }
    }

    // 更新游戏逻辑
    game_update();

    // 绘制画面
    game_draw();
}

4个按键读取:

uint32_t GetKey_KEY1(void)
{
    if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET)     return 1;
    else        return 0;
}

uint32_t GetKey_KEY2(void)
{
    if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET)     return 1;
    else        return 0;
}

uint32_t GetKey_KEY3(void)
{
    if(HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET)     return 1;
    else        return 0;
}

uint32_t GetKey_KEY4(void)
{
    if(HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET)     return 1;
    else        return 0;
}

main函数中调用游戏初始化和运行循环:

image.png

编译下载之后就可以看到游戏正常运行了。S1按键重新开始游戏,S2和S3按键左右移动挡板弹球。

image.png

image.png

image.png
收藏 评论0 发布时间:2026-7-10 00:35

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版