简介:这份资源面向使用8051系列单片机的嵌入式开发者,提供CY8CMBR3116电容触摸传感器在C51平台上的完整驱动实现,并配套CT1642液晶驱动,解决触摸按键检测与键值显示的一体化控制问题。压缩包共30个文件,约54KB,以c源码、h头文件、obj与lst编译中间文件为主,另含hex烧录文件、uvproj工程配置及A51启动代码,可直接在Keil环境下打开编译与调试。资源已有823人学习下载,具备一定参考基础。代码按模块拆分,触摸驱动涵盖初始化配置、采样阈值设定、触摸事件判断与中断响应,CT1642部分则包含LCD初始化、显示位置设置及数据命令写入,主程序将两者串联,演示触摸键值实时上屏的完整流程。读者可据此理解C51与触摸、显示外设的通信方式,快速搭建智能家居面板、工业控制按键等交互原型,并在此基础上调整灵敏度与显示逻辑。
1. 从一块调不通的触摸板说起:CY8CMBR3116 驱动到底解决什么问题
去年帮一个做小家电控制面板的朋友收拾烂摊子,板子上焊着一颗 CY8CMBR3116,I2C 能扫到地址,寄存器读出来却全是 0xFF,按键死活不响应。翻遍手头资料才发现,这颗芯片不是「上电就能用」的普通触摸 IC,它内部有一整套 CapSense 状态机,必须按固定时序走完配置、校准、使能,才会把按键状态吐到寄存器里。这份 CY8CMBR3116 触摸驱动就是把这套时序固化成 C51 代码的工程包,配套 CT1642 做数码管/指示灯显示,适合正在用 8051 系 MCU 做触摸面板、又不想啃原厂几十页寄存器手册的开发者。它不追求通用,只解决一件事:让主控稳定读到 16 个触摸通道的按下/释放状态,并把结果送到显示侧。
2. 驱动架构拆解:I2C 时序、寄存器映射与 CT1642 显示链路
2.1 整体分层:三层结构各管什么
这份工程没有用 RTOS,也没有抽象出复杂的 HAL,走的是 8051 项目里最常见的三层切分:最底下是软件模拟 I2C 的位操作层,中间是 CY8CMBR3116 的寄存器访问与状态机层,最上面是业务层,负责把按键事件翻译成 CT1642 的显示数据。三层之间靠函数调用直连,没有回调注册那套东西,好处是编译出来代码小、时序可控,坏处是换 MCU 平台要动底层。
我一般会把目录理解成下面这样,实际工程里文件名可能略有出入,但职责划分基本一致:
project/ ├── main.c // 初始化、主循环、按键扫描调度 ├── i2c_soft.c/.h // 软件 I2C:起始、停止、读写字节、ACK ├── mbr3116.c/.h // 芯片配置、校准、按键状态读取 ├── ct1642.c/.h // 显示驱动:段码移位、亮度控制 └── config.h // 引脚定义、I2C 延时、通道映射底层 I2C 用 GPIO 模拟而不是硬件外设,原因很实际:很多低成本 8051 的硬件 I2C 只支持主机模式且时序固定,遇到 CY8CMBR3116 这种对建立/保持时间有要求的从机,反而容易在重复起始条件上翻车。软件模拟虽然占 CPU,但延时可以按主频精调,调试期改一个宏就能试不同速率,血泪经验是先把 I2C 跑通再谈触摸逻辑。
2.2 软件 I2C 的位操作实现
CY8CMBR3116 的 I2C 从机地址常见配置是 0x37(7 位),写寄存器时先发寄存器地址再发数据,读的时候要先写地址再重复起始。下面这段是典型的位操作写法,延时用空循环实现,I2C_DELAY在 config.h 里按主频调:
// i2c_soft.c —— 软件 I2C 基础时序 #include "config.h" static void i2c_delay(void) { volatile uint8_t i = I2C_DELAY; // 按主频调整,典型 5~20 while (i--); } void i2c_start(void) { SDA_HIGH(); SCL_HIGH(); i2c_delay(); SDA_LOW(); i2c_delay(); // SCL 高时 SDA 下降 = 起始 SCL_LOW(); i2c_delay(); } void i2c_stop(void) { SDA_LOW(); SCL_HIGH(); i2c_delay(); SDA_HIGH(); i2c_delay(); // SCL 高时 SDA 上升 = 停止 } uint8_t i2c_write_byte(uint8_t dat) { uint8_t i, ack; for (i = 0; i < 8; i++) { if (dat & 0x80) SDA_HIGH(); else SDA_LOW(); dat <<= 1; SCL_HIGH(); i2c_delay(); SCL_LOW(); i2c_delay(); } SDA_HIGH(); // 释放 SDA 等从机拉低 SCL_HIGH(); i2c_delay(); ack = SDA_READ(); // 0 = ACK,1 = NACK SCL_LOW(); i2c_delay(); return ack; }逻辑说明:起始条件必须在 SCL 为高时让 SDA 由高变低,停止条件相反,这两个边沿是 I2C 协议里唯一允许 SDA 在 SCL 高电平期间变化的时刻,其余时间 SDA 只能在 SCL 低电平期间切换。i2c_write_byte返回 0 表示从机应答,返回 1 表示无应答,调用方必须检查这个返回值,否则芯片没焊好或地址配错时程序会静默跑飞。参数I2C_DELAY是空循环次数,主频 12MHz 下取 10 左右对应约 100kHz,实际要用示波器或逻辑分析仪看一眼 SCL 波形再定,别凭感觉。
读字节的函数对称写一遍即可,关键是第 9 个时钟由主机决定是否给 ACK:读最后一个字节前要给 NACK,告诉从机别再吐数据。很多新手在这里翻车,连续读多个寄存器时忘了最后一个字节发 NACK,结果总线卡死。
2.3 CY8CMBR3116 的寄存器访问与配置流程
这颗芯片的寄存器分两类:配置区和状态区。配置区决定灵敏度、扫描周期、通道使能,状态区里 0xAA 附近是按键状态位图。上电后不能直接读状态,要先确认芯片处于「配置模式」还是「运行模式」,常见做法是读一个固定的器件 ID 寄存器做握手,再写配置块,最后发使能命令。
// mbr3116.c —— 配置与状态读取 #define MBR_ADDR 0x37 #define REG_CTRL 0x00 #define REG_STATUS 0xAA #define CMD_ENABLE 0x01 #define CMD_CALIBRATE 0x02 uint8_t mbr_write_reg(uint8_t reg, uint8_t val) { i2c_start(); if (i2c_write_byte(MBR_ADDR << 1)) { i2c_stop(); return 1; } if (i2c_write_byte(reg)) { i2c_stop(); return 1; } if (i2c_write_byte(val)) { i2c_stop(); return 1; } i2c_stop(); return 0; } uint8_t mbr_read_reg(uint8_t reg, uint8_t *out) { i2c_start(); if (i2c_write_byte(MBR_ADDR << 1)) { i2c_stop(); return 1; } if (i2c_write_byte(reg)) { i2c_stop(); return 1; } i2c_start(); // 重复起始,切读方向 if (i2c_write_byte((MBR_ADDR << 1) | 1)) { i2c_stop(); return 1; } *out = i2c_read_byte(0); // 0 = 最后一个字节给 NACK i2c_stop(); return 0; } uint8_t mbr_init(void) { if (mbr_write_reg(REG_CTRL, CMD_CALIBRATE)) return 1; // 先校准 delay_ms(50); // 等内部基线稳定 if (mbr_write_reg(REG_CTRL, CMD_ENABLE)) return 1; // 再使能扫描 delay_ms(10); return 0; }逻辑说明:mbr_write_reg和mbr_read_reg都返回错误码而不是直接返回数据,这样调用方能把「总线故障」和「数据为 0」区分开,调试期这个区分非常关键。校准命令之后必须留足等待时间,芯片内部要采集环境基线,这个时间短了会导致上电后按键误触发,长了只是启动慢一点,我一般给 50ms 起步。使能之后状态寄存器才会周期性刷新,读太早读到的还是旧值。
参数上,MBR_ADDR要按实际硬件跳线确认,7 位地址左移一位再或读写位是标准写法,别把 8 位地址直接填进去。REG_STATUS的具体偏移和位定义要以手头芯片的配置为准,不同固件版本可能不同,工程里通常用宏把每个通道映射到位,改通道时只动宏不动逻辑。
2.4 CT1642 显示驱动:把按键状态送到数码管
CT1642 是常见的数码管/ LED 驱动移位寄存器,两线控制(CLK + DATA),级联输出。驱动侧要做的事很朴素:把 16 个触摸通道的状态压缩成一个 16 位字,按位映射到段码,再逐位移出。下面是一个通道到段码的转换片段:
// ct1642.c —— 移位输出与段码映射 static const uint8_t seg_map[10] = { 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F }; void ct1642_send(uint16_t data) { uint8_t i; for (i = 0; i < 16; i++) { if (data & 0x8000) DATA_HIGH(); else DATA_LOW(); data <<= 1; CLK_LOW(); delay_us(2); CLK_HIGH(); delay_us(2); // 上升沿锁存 } } void ct1642_show_keys(uint16_t key_bits) { uint8_t ch, digit = 0; for (ch = 0; ch < 16; ch++) { if (key_bits & (1 << ch)) digit++; // 简单示例:按下计数 } ct1642_send((uint16_t)seg_map[digit % 10] << 8); }逻辑说明:ct1642_send是纯移位,MSB 先出,每个 bit 在 CLK 上升沿被锁存,delay_us(2)是给移位寄存器留的建立时间,主频高时可以缩短但不能省。ct1642_show_keys这里只做了按下计数显示,实际项目里更常见的是把每个通道映射到独立 LED 或段位,映射表放在 config.h 里,业务层只传位图。注意 CT1642 的输出是恒流灌电流还是拉电流取决于具体型号接法,段码表要按共阴/共阳调整,接反了显示会全亮或全灭,这是调试期最容易怀疑「驱动写错了」的假故障。
3. 从零跑通:编译环境、接线与上电验证步骤
3.1 编译环境与工程导入
这份代码面向 8051 内核,常见工具链是 Keil C51 或者 SDCC。Keil 下直接新建工程、把 .c 文件加进去、选对应芯片型号即可;SDCC 下用命令行编译,注意--model-small和头文件路径。工程里没有用到浮点和动态内存,RAM 占用很小,普通 1K RAM 的片子够用。
导入后第一件事不是编译,是打开 config.h 核对三组宏:I2C 的 SCL/SDA 引脚、CT1642 的 CLK/DATA 引脚、以及I2C_DELAY的值。引脚定义通常写成sbit SCL = P1^0;这种形式,换板子只改这里。核对完再编译,第一次编译建议把优化等级调到最低,方便单步调试时变量不被优化掉。
3.2 接线检查清单
上电前用万用表把下面几项过一遍,比上电后瞎猜省时间:
| 检查项 | 正常表现 | 异常处理 |
|---|---|---|
| VCC 与 GND | 3.3V 或 5V 按芯片规格 | 电压不符先查稳压 |
| SCL/SDA 上拉 | 各 4.7k 到 VCC | 缺上拉会导致 ACK 失败 |
| 从机地址跳线 | 与MBR_ADDR一致 | 不一致改宏或改跳线 |
| CT1642 供电 | 与段码电平匹配 | 电平不匹配加驱动 |
I2C 上拉电阻是最容易被忽略的一项,很多模块板自带,自己搭的板子忘了加,现象就是i2c_write_byte永远返回 1。上拉阻值 4.7k 是常见起点,总线长了或速率高了要减小,但别小于 1k,否则灌电流超标。
3.3 上电验证:先看总线,再看状态
烧录后不要急着看按键,先确认 I2C 能通。最省事的办法是在mbr_init里加一句读器件 ID 并点灯,或者用串口把读回的字节打出来。如果读回 0xFF,八成是从机没应答或地址错;如果读回 0x00,可能是上拉缺失或 SDA 被拉死。总线通了之后再看状态寄存器是否随触摸变化,手指按住某个通道,对应位应该翻转,不翻转就回去查通道使能配置和校准等待时间。
CT1642 侧单独验证:先发一个固定图案(比如全 0xAA),看数码管是否按预期亮灭,确认移位方向和锁存边沿没错,再接按键逻辑。显示和触摸分开调,出问题时才能定位到是哪一侧。
4. 避坑与排查:四个真实踩过的坑
4.1 现象:读状态寄存器恒为 0xFF,换芯片依旧
原因通常不是芯片坏,而是 I2C 从机地址写错或上拉缺失导致 ACK 阶段 SDA 浮空被读成高。解决:先用逻辑分析仪抓一次完整写时序,看第 9 个时钟 SDA 是否被拉低;没有分析仪就用万用表测 SDA 静态电平,正常应被上拉为高。地址按 7 位左移核对,别把数据手册里的 8 位写地址直接当参数。
4.2 现象:上电后按键误触发,过几秒才正常
原因是校准命令后等待时间不足,芯片内部基线还没稳定就使能了扫描,环境电容被当成触摸。解决:把delay_ms(50)加大到 100~200ms 试,稳定后再往下压;同时确认校准期间没有手指放在面板上,产线测试工装如果压着面板校准,批量出货必然误触发。
4.3 现象:连续读多个寄存器时总线卡死,后续所有 I2C 失败
原因是读最后一个字节没发 NACK,从机继续驱动 SDA,主机发停止条件时总线冲突。解决:检查i2c_read_byte的最后一个参数,确保最后一次调用传 0;卡死后给 SCL 补 9 个时钟再发停止条件可以软恢复,工程里可以加一个i2c_bus_recover()兜底。
4.4 现象:触摸正常但 CT1642 显示乱码或全亮
原因是段码表共阴/共阳接反,或者 CLK 上升沿与 DATA 建立时间不满足。解决:先发 0x00 和 0xFF 两个极端图案看全灭/全亮是否符合预期,符合说明移位没问题、只是段码映射错;不符合就加大delay_us并确认 CLK 空闲电平。显示侧的问题不要和触摸逻辑混在一起查,分开验证最快。
5. 二次开发:换通道、换显示、换主控的改造路径
拿到这份驱动后最常见的改造有三个方向,我按改动量从小到大说。第一是改通道数和映射,config.h 里把通道位图宏改掉,业务层ct1642_show_keys里的映射表跟着调,注意 CY8CMBR3116 的通道使能是配置寄存器里的位,不是所有通道都能随便关,关之前确认硬件上对应引脚没接感应焊盘。第二是换显示器件,CT1642 换成 74HC595 或专用 LED 驱动时,只要保留「位图进、移位出」这个接口,改ct1642_send的时序和段码表即可,上层逻辑不用动。第三是换主控,把i2c_soft.c里的 GPIO 宏换成新平台的寄存器操作,delay_us用定时器或空循环重写,其余文件基本可以原样搬。
验证改造是否成功,我习惯用三步:先跑 I2C 回环读器件 ID,再单通道触摸看状态位翻转,最后整板跑 10 分钟看有没有基线漂移导致的误触发。改造中最容易忽略的是校准等待时间和上拉阻值,换了板子这两项都要重新确认,别直接沿用旧参数。
有个教训我记了很久:早期调这类触摸驱动时,我总想先把显示调出来找成就感,结果触摸和显示两个变量搅在一起,一个乱码能查一下午。从那以后我每次接手带显示的触摸项目,都强制先把 I2C 和状态寄存器单独跑通、用串口打点确认,再碰显示侧一个引脚。希望帮到你。
本文还有配套的精品资源,点击获取