简介ET6238驱动是一份基于I2C总线协议的LED显示控制源码采用C语言编写目标芯片为ET6238适用于驱动十六行八列的LED矩阵。资源面向嵌入式开发者、单片机学习者及需要实现信息显示功能的相关从业者可帮助快速构建数字仪表盘、广告屏或交互灯光等应用。压缩包共2个文件包括一个头文件与一个源文件体积约两KB结构非常紧凑便于直接放入现有工程使用。头文件对外提供了芯片初始化、命令写入、数据显示与亮度调节等接口源文件则实现了I2C底层通信、从机地址设置、点阵位操作及PWM灰度控制并包含基本的超时重试与错误恢复处理同时提供同步和异步两种调用方式。目前已有四百零四人学习下载这份精简驱动既适合在Linux下基于i2c-dev进行开发也便于迁移到Arduino等平台是掌握LED驱动与I2C协议的有效样例。1. ET6238驱动能解决什么问题把散落的GPIO收口到两根线上很多人一提到ET6238驱动的C源码第一反应是“又要对着数据手册写寄存器了”。实际上手后会发现这颗I2C接口的LED驱动芯片思路是显示和按键一把抓一路I2C总线同时驱动8位数码管和几十颗按键把单片机的引脚占用从二十几个压缩到两根信号线再加一个中断脚。这份源码就是可以直接拷进工程的ET6238 C源码驱动覆盖I2C写帧、显存刷新、按键消抖、多片级联地址分配。适合正在做家电面板、仪表显示、键盘矩阵或者手里刚好有一块ET6238模块却不知道怎么下手的人。读完你不仅能跑通点灯还能按自己的PCB走线改段码映射不用再为引脚打架重新画板。2. 硬件与通信协议I2C帧、显示RAM与按键寄存器2.1 ET6238是什么一颗芯片同时管显示和按键先看硬件。ET6238内部集成了显示驱动和按键扫描两块逻辑外部只需要SDA、SCK两根信号线加上电源和地就能驱动8位数字的数码管或者64颗独立LED同时扫描矩阵按键。对做控制面板来说这相当于把原来要占用的几十个GPIO压缩到两线。芯片工作电压常见3.3V和5V两种I2C速率按数据手册走100k到400k都可以实际工程里我一般稳定跑100k抗干扰更好也更容易排查问题。参数常见值说明显示位数8位每个字节对应一位显示8段共64点段输出8段a~g, dp段码表需要和硬件走线匹配按键扫描最多32键实际行/列数受封装引脚限制通信接口I2C从机速率建议100k~400k级联支持可配地址通过地址引脚区分多片芯片拿到驱动源码后第一件事是查你手上这颗芯片的数据手册确认I2C设备地址。不同批次丝印、不同地址引脚配置默认设备地址可能有差异驱动源码里ET6238_I2C_ADDR这个宏就是专门留给你的改口。经验之谈只要焊好之后I2C扫描找不到设备八成是地址引脚没拉对不是芯片烧了。这个“玄学”每次画板都会遇到不用慌。2.2 寄存器读写模型驱动源码背后的状态空间驱动代码看起来分层很多底层寄存器模型其实很精简。显示部分芯片内部有一块显示RAM8个地址对应8个显示位往某个地址写一个字节这个字节的8个bit就对应8个段的亮灭。按键部分是一个只读的键值寄存器驱动去读它再按bit位判断哪颗按键被按下。控制寄存器负责显示开关、消隐、闪烁模式这些杂项。寄存器方向作用0x00~0x07写8位显示RAM0x08写控制寄存器显示开关/模式键值寄存器读当前按键状态I2C写帧的格式是固定套路起始位、设备地址加写位、寄存器地址、数据字节、停止位。显示连续位时可以把多个数据字节跟在寄存器地址后面芯片内部地址指针会自动递增一次突发写8个字节比逐位写高效得多。驱动源码里ET6238_Display_Update用的就是这种突发写。3. 源码结构拆解从目录文件到四条数据流3.1 目录结构拿到源码后先认路一个合格的驱动包不会把所有代码堆在一个文件里。这份ET6238驱动的目录结构按“平台抽象、芯片驱动、应用层”三层拆开拿到手先看文件树ET6238_driver/ ├── core/ │ ├── et6238.h # 对外头文件寄存器地址、API声明 │ ├── et6238.c # 芯片驱动显示、按键、控制寄存器操作 │ ├── i2c_soft.h # 软件I2C接口头文件 │ ├── i2c_soft.c # GPIO模拟I2C时序 │ └── platform_config.h # 引脚、设备地址、速率配置 ├── app/ │ ├── seg_tab.c # 0~9、A~F 段码表 │ ├── app_display.c # 显示缓冲和刷新任务 │ └── app_key.c # 按键消抖和状态处理 └── demo/ └── main.c # 演示主循环这个分法最大的好处是换平台时只改core里的底层文件app层完全不用动。et6238.c只认“写寄存器、读寄存器”两个动作不管底下是硬件I2C还是GPIO模拟app_display.c只维护自己的缓冲数组不关心芯片寄存器的偏移量。3.2 四条数据流显示、按键、控制、级联读源码建议顺着数据流走而不是从头到尾读函数。这个驱动里有四条主要数据通路。显示链路应用层往shadow_buf里写段码app_display.c定时把整份缓冲突发写到ET6238的显示RAM。按键链路主循环轮询或INT引脚触发et6238.c读键值寄存器app_key.c做消抖后交给回调函数。控制链路初始化时写控制寄存器设置显示开关、消隐模式。级联链路多片芯片时按设备地址区分分别把显存数据发到对应的chip。这四条链路在代码里是分开的互不耦合。调试的时候可以只验证第一条链路按键部分先屏蔽等显示稳定了再开按键排错会快很多。3.3 头文件里的可裁剪配置改一处就换一块板子platform_config.h是整个驱动的“总闸”换板子基本只动这个文件#define ET6238_I2C_ADDR 0x70 /* 设备地址按数据手册和地址脚配置 */ #define ET6238_USE_SOFT_I2C 1 /* 1: GPIO模拟I2C, 0: 硬件I2C */ #define ET6238_DISPLAY_NUM 8 /* 显示位数最多8 */ #define ET6238_KEY_MAX 32 /* 按键数量按实际矩阵裁剪 */ #define ET6238_CHAIN_NUM 1 /* 级联的芯片数量 */ET6238_USE_SOFT_I2C是一个很实用的开关。开发阶段建议置1用GPIO模拟I2C任何单片机都能跑方便定位问题产品稳定后想省CPU时间再切到硬件I2C。级联数量改成2时et6238.c内部会用chip_index区分基址不需要改应用层代码。4. 核心代码精读I2C写帧、显存刷新和按键状态机4.1 I2C写帧函数所有显示动作的地基不管点亮哪一段最终都要落到一次I2C写操作。驱动的底层写寄存器函数是这样的/* 写一个字节到指定寄存器返回0成功返回1表示NACK */ static uint8_t et6238_write_reg(uint8_t reg, uint8_t dat) { i2c_start(); if (i2c_write_byte(ET6238_I2C_ADDR | 0x00) I2C_NACK) { i2c_stop(); return 1; /* 设备没应答直接退出 */ } i2c_write_byte(reg); /* 目标寄存器地址 */ i2c_write_byte(dat); /* 要写入的数据 */ i2c_stop(); return 0; }这里ET6238_I2C_ADDR | 0x00表示最低位是写方向I2C协议规定设备地址最后一bit为0时是写、为1时是读。函数返回1时别急着查芯片先用I2C扫描工具确认地址对不对、上拉电阻有没有焊。i2c_write_byte返回I2C_NACK的检测放在第一步比发了字节之后才判断要可靠。4.2 显存批量更新一次突发写刷新8位显示逐位写寄存器在静态显示时够用但显示动态刷新或要做闪烁时效率太低驱动里实现了一次把整块显存刷过去的函数void ET6238_Display_Update(const uint8_t *buf, uint8_t len) { i2c_start(); i2c_write_byte(ET6238_I2C_ADDR | 0x00); i2c_write_byte(ET6238_DISP_RAM_START); /* 从第0位显示RAM开始 */ while (len--) { i2c_write_byte(*buf); /* 芯片地址指针自动递增 */ } i2c_stop(); }关键在ET6238_DISP_RAM_START这个起始地址。芯片收到起始地址后每收一个字节地址自动加1所以连续发8个段码字节就能把整屏刷完。len最大取8超过8会写进控制寄存器造成显示模式被意外改动——这是很多人踩过的坑。调用前做一次len ET6238_DISPLAY_NUM的检查能省下后面的排查时间。4.3 按键读取I2C读方向怎么拼帧按键部分比显示多一个难点要从芯片读数据I2C要发起一次读操作。驱动里的读键值寄存器实现uint8_t ET6238_Read_KeyReg(void) { uint8_t dat 0; i2c_start(); i2c_write_byte(ET6238_I2C_ADDR | 0x01); /* 最低位为1读方向 */ i2c_write_byte(ET6238_KEY_REG); /* 先写要读的寄存器地址 */ i2c_start(); /* 重复起始位切换读模式 */ dat i2c_read_byte(I2C_NACK); /* 读最后一个字节主机回NACK结束 */ i2c_stop(); return dat; }注意这里用了“重复起始位”也就是在写寄存器地址之后、读数据之前再发一次START。这是I2C协议里标准的“写寄存器地址切读数据”套路少发这一次芯片会一直停在写模式读回来的全是0xFF。i2c_read_byte(I2C_NACK)最后一个参数表示读完这一字节后主机不回ACK告诉从机传输结束。4.4 按键消抖状态机连按两次问题的标准解机械按键最烦人的是抖动按下一次被识别成七八次。驱动里的状态机思路是“状态变了先等稳定连续两次读取一致才上报”uint8_t ET6238_Key_Scan(void) { static uint8_t stable_key 0; static uint8_t last_raw 0; static uint8_t cnt 0; uint8_t raw ET6238_Read_KeyReg(); if (raw ! last_raw) { /* 电平刚变化大概率在抖动 */ cnt 0; last_raw raw; return 0xFF; /* 返回无效值让上层忽略 */ } if (cnt 2) { /* 连续两次读取一致才认为稳定 */ cnt; return 0xFF; } stable_key raw; return stable_key; /* 返回稳定的键值 */ }这段逻辑不需要延时函数靠“连续读取一致”来过滤抖动不会阻塞主循环。0xFF当作无效键值上层回调收到它直接丢弃。如果你发现按键偶尔丢失可以把cnt的判断阈值从2提到3代价是响应慢一个扫描周期对于人按键来说根本感知不到。4.5 初始化顺序先开显示再清屏上电时显示RAM的内容是不确定的可能全亮也可能花屏。驱动初始化的顺序有讲究void ET6238_Init(void) { i2c_init(); et6238_write_reg(ET6238_CTRL_REG, 0x01); /* 开显示、关消隐 */ ET6238_Display_Update(blank_buf, 8); /* 上电清屏 */ }先写控制寄存器打开显示立刻清屏避免花屏闪烁。如果先清屏再开显示中间有一个短暂的全亮状态在人眼看来就是上电闪一下测试时容易被误判为硬件问题。blank_buf是一张8字节的全0段码表对应所有段熄灭。5. 移植到STM32实战与避坑六步点亮常见问题排查5.1 移植六步从拿到源码到按出按键我拿到这份ET6238驱动源码在STM32上跑通整套显示和按键只用了不到半天。移植步骤可以固化成一套流程按顺序做基本不会出问题。第一步看原理图确认ET6238的SDA、SCK接在MCU哪两个引脚INT接在哪个中断口记录引脚对应的GPIO Port和Pin。第二步改platform_config.h里的引脚宏和设备地址。第三步把i2c_soft.c里的SDA_OUT、SDA_IN、SCK_OUT宏改成你的引脚实现如果你的MCU有硬件I2C也可以直接把et6238.c底层的i2c_start、i2c_write_byte换成HAL库接口。第四步写一个I2C地址扫描确认总线能ACK到设备。第五步调显示先用单字节写寄存器的方式点亮一颗LED。第六步接按键用第4章的ET6238_Key_Scan状态机在中断或轮询里跑。这六步里最容易卡住的是第四步。I2C扫描代码很短可以在串口上打印结果for (uint8_t addr 0x01; addr 0x7F; addr) { i2c_start(); if (i2c_write_byte(addr) I2C_ACK) { printf(found: 0x%02X\r\n, addr); } i2c_stop(); }如果什么都扫不到不要怀疑芯片坏了先量SDA和SCK的电平再确认上拉电阻有没有焊。STM32的GPIO开漏模式需要外部上拉很多板子在I2C上省了这两个电阻总线一直拉不高自然搜不到设备。5.2 从51到STM32时序差异处理很多人的第一版工程是从51的例程改过来的直接把51上的延时函数搬到STM32结果I2C时序完全对不上。51通常11.0592MHz晶振单片机一个_nop_()延时约1微秒而STM32主频72MHz同样的延时函数被编译优化后可能只剩几十纳秒I2C时序超出了芯片的时序上限表现为偶尔通信成功、连续刷几次就丢一帧显示。我一般会在i2c_soft.c里用DWT_Delay或者for循环做一个最小延时并且把延时值调成可配置的宏。调试时用逻辑分析仪抓SDA和SCK确认时钟高电平和低电平都满足数据手册的最小时间要求。另一个更隐蔽的问题是STM32的GPIO速度等级I2C引脚建议把GPIO Speed配到GPIO_SPEED_FREQ_HIGH否则边沿太缓从机在高速通信时误判电平。5.3 避坑清单高频踩坑的排查记录现象一I2C扫描找不到设备。原因多半是设备地址脚没拉对或者SDA/SCK缺少上拉电阻。解决把地址引脚按数据手册配好SDA和SCK各加4.7k上拉到VCC速率降到100k再扫。地址脚悬空有时候能工作有时候不能这是最典型的“玄学”问题。现象二共阳数码管显示全反0x3F显示成0xC0。原因段码表是按共阴写的共阳数码管需要按位取反。解决在段码表入口处加一个取反宏或者直接改seg_tab.c不要在上层每次显示时都做一次取反运算。现象三按键按一次触发两次事件。原因没有消抖或者把键值寄存器的bit位误当成电平翻转。解决用第4章的状态机连续读取一致才上报同时确认按键扫描的是稳定键值而不是每次扫描都上报原始电平。现象四STM32硬件I2C用HAL库卡死在超时。原因HAL库的HAL_I2C_Master_Transmit在总线状态异常时会一直等待。解决先用GPIO模拟I2C验证芯片本身没问题再排查硬件I2C的总线状态恢复必要时调用__HAL_I2C_CLEAR_FLAG清总线错误标志。这个坑在ST的HAL库里很常见多半不是芯片的问题。现象五级联第二片芯片不刷显示。原因两个芯片的设备地址不同驱动把地址写成了同一份。解决确认第二片的地址引脚配置后在ET6238_Display_Update加chip_index参数按索引分别发数据。级联调试时要先把两片拆开单独测都通了再合到一起。6. 二次开发改造显示闪烁、段码映射和级联扩展6.1 显示缓冲改造给驱动加闪烁效果原生驱动只做静态显示实际产品里经常要用到“某个位闪烁”来提示用户。我在app_display.c里加了一层影子缓冲应用层只写影子缓冲刷新任务负责合成闪烁效果static uint8_t shadow_buf[8]; static uint8_t blink_mask 0; void App_Display_Task(uint32_t tick) { uint8_t out[8]; for (uint8_t i 0; i 8; i) { if (blink_mask (1 i)) { out[i] (tick / 500) % 2 ? shadow_buf[i] : 0x00; } else { out[i] shadow_buf[i]; } } ET6238_Display_Update(out, 8); }闪烁频率由tick / 500控制500毫秒翻转一次也就是1Hz的闪烁节奏。blink_mask的bit位对应该位是否启用闪烁运行时改这一位就能随时开启或关闭闪烁不需要动显示逻辑。如果你想让某一位在闪烁时显示别的图案而不是熄灭把0x00换成对应的段码即可。6.2 段码重映射解决PCB走线顺序不一致实际项目中经常遇到这种问题买来的数码管模块a段没接在芯片的SEG1而是接在了SEG5改硬件来不及了只能在驱动里做重映射。驱动源码里加一张表就行/* 按PCB实际走线调整seg_remap[i]表示i段实际接到芯片的哪个段 */ const uint8_t seg_remap[8] {0, 1, 2, 3, 4, 5, 6, 7}; uint8_t MapSegment(uint8_t raw) { uint8_t out 0; for (uint8_t i 0; i 8; i) { if (raw (1 i)) { out | (1 seg_remap[i]); } } return out; }所有段码进入驱动前先过一次MapSegment就能把PCB走线顺序“校正”回来。第一次画板用默认的{0,1,2,3,4,5,6,7}焊完发现显示不对逐个测试段位然后改这张表比重新改板快得多。6.3 级联扩展多片芯片共享一根总线当前PCB上如果有两块ET6238SDA和SCK可以共用每个芯片通过地址引脚区分。驱动里把设备地址拆成参数uint8_t ET6238_Display_Update_Chip(uint8_t chip_index, const uint8_t *buf, uint8_t len) { uint8_t addr (chip_index 0) ? ET6238_I2C_ADDR : ET6238_I2C_ADDR2; /* 后续写帧逻辑和单芯片版一致只是设备地址换成 addr */ }注意级联时每个芯片的显示RAM基址是独立的不能把两块芯片的8个字节当成连续的16个字节一次突发写完。正确做法是分别发两次先选地址0的芯片发8字节再选地址1的芯片发8字节。这样应用层看到的还是两份独立的8字节缓冲不会出现第二片显示错位。从那以后我每次拿到一块新板子都会先扫I2C总线、翻一遍原理图确认设备地址最后才写显示驱动这个顺序一次都没乱过。段码映射表一定在打样前确认好否则焊完再跳线很痛苦。这份ET6238 C源码驱动已经把脏活累活都封装好了剩下的就是你在自己板子上点亮第一颗LED。希望帮到你。本文还有配套的精品资源点击获取