ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

QMK 固件 OLED 驱动完全指南:SSD1306/SH1106/SH1107 驱动、配置项与 API 深度解析

QMK 固件 OLED 驱动完全指南:SSD1306/SH1106/SH1107 驱动、配置项与 API 深度解析 QMK 固件 OLED 驱动完全指南SSD1306/SH1106/SH1107 驱动、配置项与 API 深度解析【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware在 QMK 键盘固件中OLED 小屏已成为状态显示的标配显示当前 Layer、锁灯状态、Logo 甚至自定义图案。本文基于仓库中的 OLED 驱动文档 并结合 驱动源码 与 构建系统完整讲解如何启用 OLED 功能、选择驱动芯片与 I2C/SPI 传输方式、配置屏幕尺寸预设、编写oled_task_user渲染逻辑以及 90 度旋转、脏块刷新、缓冲读取等底层机制的实现原理。支持的硬件QMK OLED 驱动支持使用SSD1306、SH1106 或 SH1107驱动芯片、通过I2C 或 SPI通信的 OLED 模组。文档中列出的已测试组合如下IC尺寸平台备注SSD1306128x32AVR主要支持对象SSD1306128x64AVR已验证可用SSD1306128x32ArmSSD1306128x64Arm已验证可用SH1106128x64AVR不支持滚动SH110764x128AVR不支持滚动SH110764x128Arm不支持滚动SH1107128x128Arm不支持滚动使用 Arm 主控或其他尺寸模组的硬件配置也可能兼容但未经官方测试启用前请自行验证。启用 OLED 功能启用 OLED 需要两步编译配置 在keymap.c中实现渲染任务。第一步rules.mk 配置在键盘的rules.mk中加入OLED_ENABLE yes驱动类型选择OLED Driver支持的器件ssd1306默认SSD1306、SH1106、SH1107 均适用例如OLED_DRIVER ssd1306从源码 builddefs/common_features.mk 可以看到构建系统实际接受的取值范围OLED_DRIVER合法值为custom ssd1306默认ssd1306OLED_TRANSPORT合法值为i2c spi custom默认i2c填写非法值会触发CATASTROPHIC_ERROR直接终止编译。启用后构建系统会定义OLED_ENABLE、OLED_DRIVER、OLED_TRANSPORT_TRANSPORT三个宏并把 drivers/oled 目录加入源码路径当OLED_DRIVER为custom时则不编入官方oled_driver.c由用户自行提供驱动实现。传输方式选择OLED Transport说明i2c默认使用 I2C 与 OLED 面板通信spi使用 SPI 与 OLED 面板通信OLED_TRANSPORT i2c同样从 common_features.mk 可以确认选择i2c时构建系统会设置I2C_DRIVER_REQUIRED yes选择spi时设置SPI_DRIVER_REQUIRED yes即相应的 I2C/SPI 总线驱动会随 OLED 一起被强制拉入编译。第二步实现 oled_task_user在keymap.c中实现oled_task_user回调。下面示例假设键盘有三个层_QWERTY、_FN、_ADJ#ifdef OLED_ENABLE bool oled_task_user(void) { // Host Keyboard Layer Status oled_write_P(PSTR(Layer: ), false); switch (get_highest_layer(layer_state)) { case _QWERTY: oled_write_P(PSTR(Default\n), false); break; case _FN: oled_write_P(PSTR(FN\n), false); break; case _ADJ: oled_write_P(PSTR(ADJ\n), false); break; default: // Or use the write_ln shortcut over adding \n to the end of your string oled_write_ln_P(PSTR(Undefined), false); } // Host Keyboard LED Status led_t led_state host_keyboard_led_state(); oled_write_P(led_state.num_lock ? PSTR(NUM ) : PSTR( ), false); oled_write_P(led_state.caps_lock ? PSTR(CAP ) : PSTR( ), false); oled_write_P(led_state.scroll_lock ? PSTR(SCR ) : PSTR( ), false); return false; } #endif注意 AVR 上使用oled_write_P配合PSTR()将字符串放入 PROGMEMflash以节省 RAM在 ARM 上这三个_P宏会被 头文件 直接映射为oled_write/oled_write_ln/oled_write_raw代码无需改动即可跨平台。Logo 示例默认字体文件中保留了特定字符区间用于渲染 QMK Logo。利用以下代码即可在屏幕上输出该 Logostatic void render_logo(void) { static const char PROGMEM qmk_logo[] { 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0x00 }; oled_write_P(qmk_logo, false); } bool oled_task_user(void) { render_logo(); return false; }默认字体文件位于 drivers/oled/glcdfont.c可通过OLED_FONT_H配置项覆盖。字体内容可用 Helix Font Editor、QMK Logo Editor 等外部工具编辑。对应地oled_driver.h 中定义了一整套字体相关宏OLED_FONT_H默认glcdfont.c、OLED_FONT_START默认0、OLED_FONT_END默认223、OLED_FONT_WIDTH默认6、OLED_FONT_HEIGHT默认8自定义点阵字体时需同时保证这些取值与字体文件匹配。读取显示缓冲渐隐示例某些场景需要读取 OLED 显示缓冲的当前内容。oled_read_raw可以安全地按字节读取缓冲。以下示例在oled_task_user中调用fade_display通过随机关闭像素让屏幕内容逐渐“淡出”//Setup some mask which can be ord with bytes to turn off pixels const uint8_t single_bit_masks[8] {127, 191, 223, 239, 247, 251, 253, 254}; static void fade_display(void) { //Define the reader structure oled_buffer_reader_t reader; uint8_t buff_char; if (random() % 30 0) { srand(timer_read()); // Fetch a pointer for the buffer byte at index 0. The return structure // will have the pointer and the number of bytes remaining from this // index position if we want to perform a sequential read by // incrementing the buffer pointer reader oled_read_raw(0); //Loop over the remaining buffer and erase pixels as we go for (uint16_t i 0; i reader.remaining_element_count; i) { //Get the actual byte in the buffer by dereferencing the pointer buff_char *reader.current_element; if (buff_char ! 0) { oled_write_raw_byte(buff_char single_bit_masks[rand() % 8], i); } //increment the pointer to fetch a new byte during the next loop reader.current_element; } } }其中oled_buffer_reader_t的定义可参见 oled_driver.h是一个打包结构体包含current_element当前字节指针与remaining_element_count剩余字节数两个成员支持从任意索引开始顺序读取。进阶示例分体键盘与开机画面双屏分体键盘分体键盘常配两块方向/朝向不同的 OLED。可以用 split_util.h 提供的is_keyboard_master()/is_keyboard_left()的返回值决定各自渲染的内容#ifdef OLED_ENABLE oled_rotation_t oled_init_user(oled_rotation_t rotation) { if (!is_keyboard_master()) { return OLED_ROTATION_180; // flips the display 180 degrees if offhand } return rotation; } bool oled_task_user(void) { if (is_keyboard_master()) { render_status(); // Renders the current keyboard state (layer, lock, caps, scroll, etc) } else { render_logo(); // Renders a static logo oled_scroll_left(); // Turns on scrolling } return false; } #endif进入 Bootloader 前显示提示信息利用shutdown_user弱函数在跳转 bootloader 前向屏幕输出等待画面void oled_render_boot(bool bootloader) { oled_clear(); for (int i 0; i 16; i) { oled_set_cursor(0, i); if (bootloader) { oled_write_P(PSTR(Awaiting New Firmware ), false); } else { oled_write_P(PSTR(Rebooting ), false); } } oled_render_dirty(true); } bool shutdown_user(bool jump_to_bootloader) { oled_render_boot(jump_to_bootloader); }基础配置项以下配置项应放在config.h中例如#define OLED_BRIGHTNESS 128宏默认值说明OLED_BRIGHTNESS255OLED 默认亮度0~255OLED_COLUMN_OFFSET0输出右移像素数。对 132x64 的 SH1106 芯片上居中安装的 128x64 屏很有用OLED_DISPLAY_CLOCK0x80设置显示时钟分频比/振荡器频率OLED_FONT_Hglcdfont.c自定义字体代码文件OLED_FONT_START0自定义字体起始字符索引OLED_FONT_END223自定义字体结束字符索引OLED_FONT_WIDTH6字体宽度OLED_FONT_HEIGHT8字体高度未经测试OLED_ICOLED_IC_SSD1306若使用对应控制芯片设为OLED_IC_SH1106或OLED_IC_SH1107OLED_FADE_OUT未定义启用淡出动画需配合OLED_TIMEOUT使用OLED_FADE_OUT_INTERVAL0淡出动画速度0~15值越大越慢OLED_SCROLL_TIMEOUT0OLED 空闲 N ms 后开始滚动有助于减少烧屏。设为 0 禁用OLED_SCROLL_TIMEOUT_RIGHT未定义定义后滚动方向为向右未定义为向左OLED_TIMEOUT60000屏幕内容 N ms 未更新后关闭 OLED减少烧屏。设为 0 禁用OLED_UPDATE_INTERVAL0分体键盘为50以 ms 为单位设置 OLED 更新间隔可提高矩阵扫描率OLED_UPDATE_PROCESS_LIMIT1每轮循环渲染的脏块数量调高可能拖慢整体性能从 oled_driver.h 的源码可确认这些默认值的落地细节OLED_TIMEOUT支持旧宏OLED_DISABLE_TIMEOUT若已定义该宏则超时被强制设为0OLED_FADE_OUT_INTERVAL超出0x00~0x0F范围会在编译期直接#error报错OLED_UPDATE_INTERVAL仅在分体键盘SPLIT_KEYBOARD未显式定义时自动取50此外还有一个文档表格未列出的宏OLED_I2C_TIMEOUT默认100控制 I2C 传输超时。I2C 配置宏默认值说明OLED_DISPLAY_ADDRESS0x3COLED 的 I2C 地址SPI 配置宏默认值说明OLED_DC_PIN必填OLED 的 DC 引脚OLED_CS_PIN必填OLED 的 CS 引脚OLED_RST_PIN未定义OLED 的 RST 引脚未接 RST 可不定义OLED_SPI_MODE3OLED 的 SPI 模式通常不修改OLED_SPI_DIVISOR2OLED 的 SPI 时钟倍率128x64 与自定义尺寸屏幕该功能的默认屏幕尺寸为 128x32所有默认值都围绕它设定。仓库为常见尺寸提供了一系列预设宏定义其一即可切换宏默认值说明OLED_DISPLAY_128X64未定义切换为 128x64 显示参数OLED_DISPLAY_64X32未定义切换为 64x32 显示参数OLED_DISPLAY_64X48未定义切换为 64x48 显示参数OLED_DISPLAY_64X128未定义切换为 64x128 显示参数OLED_DISPLAY_128X128未定义切换为 128x128 显示参数OLED_DISPLAY_CUSTOM未定义自定义显示需自行实现下方各宏注意64x128 与 128x128 屏幕默认使用 SH1107 芯片类型因为其他芯片类型不支持这些高度——这一点在 oled_driver.h 中可以直接看到OLED_DISPLAY_64X128与OLED_DISPLAY_128X128分支在OLED_IC未定义时都会#define OLED_IC OLED_IC_SH1107同时 64x128 还会自动设置OLED_COM_PIN_OFFSET 32。各预设还会自动派生出缓冲与脏块参数以默认 128x32 为例源码 oled_driver.h宏默认值说明OLED_DISPLAY_WIDTH128屏幕宽度OLED_DISPLAY_HEIGHT32屏幕高度OLED_MATRIX_SIZE512本地缓冲大小(OLED_DISPLAY_HEIGHT / 8 * OLED_DISPLAY_WIDTH)OLED_BLOCK_TYPEuint16_t脏块渲染使用的无符号整型OLED_BLOCK_COUNT16屏幕被划分的脏块数量sizeof(OLED_BLOCK_TYPE) * 8OLED_BLOCK_SIZE32每个脏块的大小OLED_MATRIX_SIZE / OLED_BLOCK_COUNTOLED_COM_PINSCOM_PINS_SEQSSD1306 芯片内存到显示的映射方式可选COM_PINS_SEQ、COM_PINS_ALT、COM_PINS_SEQ_LR、COM_PINS_ALT_LROLED_COM_PIN_COUNT未定义控制芯片支持的 COM 引脚数量未定义时按OLED_IC取合适值OLED_COM_PIN_OFFSET0OLED 矩阵使用的第一个 COM 引脚编号OLED_SOURCE_MAP{ 0, ... N }90 度渲染时用于 source 缓冲到 OLED 内存映射的预计算数组OLED_TARGET_MAP{ 24, ... N }90 度渲染时用于 source 缓冲到 OLED 内存映射的预计算数组90 度旋转的实现原理// OLED Rotation enum values are flags typedef enum { OLED_ROTATION_0 0, OLED_ROTATION_90 1, OLED_ROTATION_180 2, OLED_ROTATION_270 3, // OLED_ROTATION_90 | OLED_ROTATION_180 } oled_rotation_t;该枚举定义见 oled_driver.h。SSD1306/SH1106/SH1107 硬件原生只支持 0 度和 180 度渲染90/270 度是纯软件实现是有代价的旋转会拉长计算待发送 I2C 数据的时间。在 ATmega32U4 上的实测中渲染时间从 2ms 增加到 5ms直到约 15ms 才出现按键码丢失。因此从源码结构看在算力紧张的板上启用 90 度旋转需谨慎。内存重映射机制90 度旋转通过对每个 8 字节块做位运算旋转实现并使用两张预计算数组将本地缓冲内存重映射到 OLED 内存。以 128x32 uint8_t块类型为例块大小为 64 字节即有 8 个 8 字节块需要旋转。OLED 是按“横向写满两个 8 字节块后换页”的方式排布的01234567而本地缓冲却按“高度 x 宽度”而非“宽度 x 高度”存储37261504因此OLED_SOURCE_MAP/OLED_TARGET_MAP这两张预计算数组本质上是按各自遍历顺序索引内存偏移。128x32 预设下默认值为OLED_SOURCE_MAP {0, 8, 16, 24}与OLED_TARGET_MAP {24, 16, 8, 0}源码注释中还给出了uint8_t/uint16_t/uint32_t块类型下各表格的对应形态方便自定义尺寸时参照。SH1106/SH1107 上的效率差异这两款控制器不支持“水平寻址模式”horizontal addressing mode无法一次传输整个旋转块的数据必须对块内每一页单独下发地址设置命令。因此在 STM32 上启用 90 度旋转时SH1107 的刷新耗时比同尺寸 SSD1306 高约 45%在 AVR 上由于位图旋转本身耗时更多差距缩小到约 20%。OLED API 速查以下 API 声明以 drivers/oled/oled_driver.h 为准与文档一致// 初始化显示按传入的 rotation 旋转渲染输出成功返回 true bool oled_init(oled_rotation_t rotation); // 初始化入口的 weak 钩子可被 keymap 覆盖以改写旋转 oled_rotation_t oled_init_kb(oled_rotation_t rotation); oled_rotation_t oled_init_user(oled_rotation_t rotation); // 发送命令/数据 bool oled_send_cmd(const uint8_t *data, uint16_t size); bool oled_send_cmd_P(const uint8_t *data, uint16_t size); bool oled_send_data(const uint8_t *data, uint16_t size); // 清空显示缓冲重置光标并标记为脏 void oled_clear(void); // oled_render 是 oled_render_dirty 的兼容别名 #define oled_render() oled_render_dirty(false) // all 为 true 时一次渲染全部脏块否则只渲染一部分 void oled_render_dirty(bool all); // 光标控制 void oled_set_cursor(uint8_t col, uint8_t line); // 定位光标越界回绕 void oled_advance_page(bool clearPageRemainder); // 换页可选清空本页剩余 void oled_advance_char(void); // 前进一个字符 // 文本写入 void oled_write_char(const char data, bool invert); void oled_write(const char *data, bool invert); void oled_write_ln(const char *data, bool invert); // 缓冲操作 void oled_pan(bool left); // 整体平移缓冲 oled_buffer_reader_t oled_read_raw(uint16_t start_index); // 获取缓冲读取器 void oled_write_raw(const char *data, uint16_t size); // 原样写入缓冲 void oled_write_raw_byte(const char data, uint16_t index); // 写入单个字节 void oled_write_pixel(uint8_t x, uint8_t y, bool on); // 开关单个像素 // AVR 专用 PROGMEM 变体ARM 上映射到非 _P 版本 void oled_write_P(const char *data, bool invert); void oled_write_ln_P(const char *data, bool invert); void oled_write_raw_P(const char *data, uint16_t size); // 电源与亮度 bool oled_on(void); bool oled_off(void); bool is_oled_on(void); uint8_t oled_set_brightness(uint8_t level); uint8_t oled_get_brightness(void); // 任务循环 void oled_task(void); // 带超时管理与 oled_task_user 调用的渲染入口 bool oled_task_kb(void); bool oled_task_user(void); // 硬件滚动 void oled_scroll_set_area(uint8_t start_line, uint8_t end_line); // 0 起始、7 结束为全高 void oled_scroll_set_speed(uint8_t speed); // 0-7帧间隔 2/3/4/5/25/64/128/256 bool oled_scroll_right(void); // 滚动期间不能修改屏幕内容 bool oled_scroll_left(void); bool oled_scroll_off(void); bool is_oled_scrolling(void); // 其他 bool oled_invert(bool invert); uint8_t oled_max_chars(void); uint8_t oled_max_lines(void);两点重要限制SH1106 和 SH1107 不支持滚动SSD1306 上若显示宽度小于 128滚动不能正常工作。从旧版 SSD1306.h 驱动迁移如果你的 keymap 还停留在旧 API按以下对照表迁移旧 API推荐的新 APIstruct CharacterMatrix已移除 —— 删除所有引用iota_gfx_initoled_initiota_gfx_onoled_oniota_gfx_offoled_offiota_gfx_flusholed_renderiota_gfx_write_charoled_write_chariota_gfx_writeoled_writeiota_gfx_write_Poled_write_Piota_gfx_clear_screenoled_clearmatrix_clear已移除 —— 删除所有引用matrix_write_char_inneroled_write_charmatrix_write_charoled_write_charmatrix_writeoled_writematrix_write_lnoled_write_lnmatrix_write_Poled_write_Pmatrix_write_ln_Poled_write_ln_Pmatrix_renderoled_renderiota_gfx_taskoled_taskiota_gfx_task_useroled_task_user小结QMK 的 OLED 功能以 drivers/oled/oled_driver.c 为核心实现配合 oled_driver.h 中的尺寸预设与默认值体系、common_features.mk 中的构建开关覆盖了从 64x32 到 128x128 的主流 OLED 模组。掌握本文内容后你可以正确启用 OLED 并选择 I2C/SPI 通道按屏幕实际尺寸选择预设宏或自定义全部OLED_*参数编写 Layer/锁灯/Logo 等oled_task_user渲染逻辑利用oled_read_raw做像素级缓冲操作并为分体键盘配置双屏差异化显示与 180 度翻转。对于自定义尺寸屏幕直接参照OLED_DISPLAY_CUSTOM分支的派生公式OLED_MATRIX_SIZE、OLED_BLOCK_*、OLED_SOURCE_MAP/OLED_TARGET_MAP逐一定义即可复用整套脏块渲染与旋转机制。【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表