拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AD转换芯片与DA转换芯片实现可调光台灯闭环调光与低亮度调校

简介围绕单片机应用技术中的可调光台灯设计这份PPT面向电子、自动化及相关专业的学习者与初学者聚焦A/D、D/A转换与I2C总线通信的入门实践。内容以PCF8591为核心梳理模拟电压输出驱动LED并调节亮度的完整思路涉及A/D与D/A转换原理、PCF8591控制字与05V输出范围、I2C启动/停止及字节收发时序、1KΩ限流电阻选型、按键增减输出电压的控制程序以及主程序流程和产生正弦波、数字万用表等举一反三案例可帮助读者理解单片机与外围芯片间的通信过程并对照硬件电路与程序代码完成从原理到实现的梳理。压缩包仅含1个pptx文件约751KB以图文页面呈现任务导入、硬件电路、程序设计与扩展应用便于课堂演示或自学查阅。目前已有274人学习下载。1. 从 AD 转换芯片和 DA 转换芯片看可调光台灯的闭环调光把 AD 转换芯片和 DA 转换芯片放进一盏可调光台灯里最直接的价值不是把旋钮换成数字编码器而是让亮度设定、环境光补偿和驱动电压形成可标定的模拟链路。AD 转换芯片负责把电位器分压、光敏电阻或触摸滑条的电压变成数字量MCU 用这个数字量查表、滤波、限幅DA 转换芯片再把目标亮度还原成 0~3.3V 或 0~10V 的模拟电压送进恒流驱动或调光接口。这样做的台灯旋钮手感由软件定义低亮度不闪、不同批次灯珠亮度一致。适合做课程设计、桌面照明产品原型以及需要把 AD 转换和 DA 转换芯片写进方案里的嵌入式开发者。2. AD 转换芯片在可调光台灯里的采样链路与参数标定2.1 可调光台灯里 AD 转换芯片要采什么信号可调光台灯里最常见的被测量是电位器中间抽头的电压。电位器两端接 3.3V 和 GND中间抽头随旋钮角度在 0~3.3V 之间变化AD 转换芯片读到的码值就是亮度设定值。另一类信号是环境光用光敏电阻和固定电阻分压或者用光电二极管加跨阻放大器输出仍是 0~VREF 的电压。触摸滑条则可能直接输出模拟电压经过 AD 转换芯片采样后做位置判断。采样率不用追求高。人手转动旋钮的带宽通常低于 20Hz环境光变化更慢AD 转换芯片每秒采 50~200 次就够。分辨率要够8 位立即可见阶跃12 位在 0~3.3V 下每 LSB 约 0.8mV配合软件映射能做出细腻手感。参考电压要稳如果直接用 MCU 的 3.3V 供电当参考灯珠电流变化会通过地弹和电源纹波影响读数常见做法是外接 2.5V 或 3.0V 基准或者至少给 AD 转换芯片的 VREF 加 1uF 加 100nF 退耦。还有一个容易忽略的点电位器不是理想分压器。碳膜电位器在低亮度端接触电阻会变大读数可能跳。我一般会在电位器抽头和 AD 输入之间串 1k 电阻再并 100nF 到地截止频率约 1.6kHz不影响旋钮响应还能压住高频噪声。2.2 AD 转换芯片选型与外围电路参数选 AD 转换芯片先看接口、通道和参考方式。台灯里通常只有 1~2 路模拟量I2C 或 SPI 外置芯片比 MCU 内置 ADC 更贴近“基于 AD 转换芯片”这个标题也更容易做高分辨率。下面这张表是我常用的几类选择。型号分辨率接口通道参考适合场景ADS111516 位I2C4 单端/2 差分内部 2.048V 或外部低速高精度电位器、光敏MCP320112 位SPI1 单端外部 VREF成本低采样率可到 100kspsADS782812 位I2C8 单端内部/外部多路模拟量带光敏和温度MCU 内置 ADC10~12 位片内多路VDD 或 VREF成本最省精度要求不高外围电路按“分压、限流、滤波、退耦”四步走。电位器阻值取 10k~100k太大容易受 AD 输入漏电流影响太小则静态功耗高。分压后如果超过 VREF要用电阻分压到量程内。ADS1115 的 AIN 输入范围由 PGA 决定±2.048V 或 ±4.096V 要算清楚别让 3.3V 直接灌进去。下面是用 Python 在树莓派一类 Linux 板上读 ADS1115 的最小代码寄存器配置按单端 AIN0、±4.096V、128SPS 设置。import time import smbus2 I2C_BUS 1 ADS1115_ADDR 0x48 # 配置寄存器单端 AIN0±4.096V128SPS关比较器 CONFIG_MSB 0xC3 # 1100 0011 CONFIG_LSB 0x83 # 1000 0011 bus smbus2.SMBus(I2C_BUS) def read_ads1115(): # 写配置寄存器 bus.write_i2c_block_data(ADS1115_ADDR, 0x01, [CONFIG_MSB, CONFIG_LSB]) time.sleep(0.01) data bus.read_i2c_block_data(ADS1115_ADDR, 0x00, 2) raw (data[0] 8) | data[1] if raw 32767: raw - 65536 # ±4.096V 量程16 位有符号1LSB 4.096/32768 voltage raw * 4.096 / 32768.0 return raw, voltage while True: raw, voltage read_ads1115() print(fraw{raw:6d} voltage{voltage:.4f} V) time.sleep(0.1)逻辑说明先向配置寄存器 0x01 写入两个字节选择 AIN0 单端输入、PGA 为 ±4.096V、采样率 128SPS。再读转换寄存器 0x00把两个字节拼成 16 位有符号数负数做补码还原。最后按 1LSB4.096V/32768 换算电压。参数说明ADS1115_ADDR由 ADDR 引脚决定接地为 0x48接 VDD 为 0x49接 SDA 为 0x4A接 SCL 为 0x4BCONFIG_MSB和CONFIG_LSB要按数据手册的位定义改特别是输入多路选择位和 PGA 位。如果读数一直是 0x7FFF 或 0x8000先量 AIN0 对地电压再查 I2C 地址。提示ADS1115 的模拟输入不能超过 VDD0.3V台灯里如果电位器用 5V 供电必须先用两个电阻分压到 3.3V 以内否则会打坏输入保护二极管。2.3 用软件滤波把抖动压到 1 个 LSB 以内AD 转换芯片的原始码值不会完全安静电源纹波、LED 驱动开关噪声、人手触碰都会让最后几位跳。直接在原始值上做映射低亮度端会出现肉眼可见的闪烁。常见做法是“中值滤波去毛刺再加一阶低通或滑动平均”。中值窗口取 5 点能干掉单次尖峰滑动平均取 8~16 点牺牲一点响应换稳定。下面是一段 Python 滤波代码适合在每 10ms 采样一次的主循环里调用。from collections import deque class AdFilter: def __init__(self, median_win5, avg_win8, alpha0.15): self.median_buf deque(maxlenmedian_win) self.avg_buf deque(maxlenavg_win) self.alpha alpha self.low_pass None def update(self, raw): # 1. 中值滤波 self.median_buf.append(raw) med sorted(self.median_buf)[len(self.median_buf)//2] # 2. 滑动平均 self.avg_buf.append(med) avg sum(self.avg_buf) / len(self.avg_buf) # 3. 一阶低通 if self.low_pass is None: self.low_pass avg else: self.low_pass self.low_pass * (1 - self.alpha) avg * self.alpha return int(self.low_pass)逻辑说明每来一个原始码值先压入中值窗口取排序后的中间值再把中值压入平均窗口求均值最后用alpha做一阶低通输出平滑后的整数码值。参数说明median_win5对连续尖峰有效如果噪声是持续高频窗口可加到 7avg_win8在 100SPS 下约 80ms 延迟手感可以接受alpha0.15越小越稳但越迟钝调到 0.3 会跟手一些。注意不要在中值窗口还没填满时做映射否则上电瞬间亮度会冲一下我一般等len(median_buf) median_win再输出。2.4 AD 采样值到亮度等级的映射和校准AD 转换芯片给出的是电压码值台灯需要的是亮度等级比如 0~1000 或 0~255。直接线性映射有两个问题电位器两端有机械死区码值到不了 0 和满量程不同台灯的电位器角度和灯珠亮度曲线不一致。所以我一般做两点校准上电时记录电位器最低端和最高端的稳定码值存到 Flash 或 EEPROM再用分段线性映射。假设校准后得到ad_min和ad_max当前滤波值为ad_val先限幅def ad_to_level(ad_val, ad_min, ad_max, level_max1000): if ad_val ad_min: return 0 if ad_val ad_max: return level_max ratio (ad_val - ad_min) / (ad_max - ad_min) # 低亮度端留 2% 死区避免刚离开最低端就跳亮 if ratio 0.02: return 0 return int(ratio * level_max)逻辑说明先把码值限幅到校准区间再归一化到 0~1最后乘以最大亮度等级。参数说明ad_min和ad_max不是理论值 0 和 32767而是实测稳定值通常留 2%~5% 余量level_max取 1000 方便后面做 Gamma 映射取 255 则适合 8 位 DA 或 PWM。如果电位器是反接的交换ad_min和ad_max即可。注意校准值不要每次上电都重算否则用户转到中间位置开机会把中间位置当成最低端亮度全乱。常见做法是长按某个隐藏按键 3 秒进入校准或者只在首次上电且检测到电位器在最低端时记录。3. DA 转换芯片驱动可调光台灯的模拟调光输出3.1 DA 转换芯片选型MCP4725、DAC8551、PWMRC 的边界可调光台灯的 DA 转换芯片通常输出 0~3.3V、0~5V 或 0~10V 的模拟电压去控制恒流驱动芯片的 DIM 引脚。选型时先看驱动接口要电压还是电流再看分辨率、接口和上电默认输出。MCP4725 是 I2C 12 位 DAC带 EEPROM上电可以恢复保存值适合小台灯DAC8551 是 SPI 16 位精度高但需要外部基准和运放PWM 加 RC 滤波成本最低但纹波和建立时间难控制低亮度容易闪。下面表格把边界列清楚。方案分辨率接口上电默认低亮度表现适用MCP472512 位I2CEEPROM 可保存较好需滤波桌面台灯、原型DAC855116 位SPI0V 或中间值很好高精度调光PWMRC取决于 PWMGPIO取决于定时器易纹波需二级 RC成本极敏感内置 DAC8~12 位片内不确定一般MCU 自带 DAC如果台灯驱动要求 0~10VDA 转换芯片输出 0~3.3V 后要加同相放大器增益约 3.03 倍把 0~3.3V 拉到 0~10V。运放选轨到轨输出供电至少 12V否则输出摆不到 10V。DAC8551 这类电流输出型 DAC 还需要外部运放做 I/V 转换不能直接接 DIM 引脚。3.2 DA 转换芯片与 LED 驱动的接口电路DA 转换芯片输出到 LED 驱动之间常见接口有三类模拟电压调光、PWM 调光、电阻调光。模拟电压调光最贴合 DA 转换芯片DIM 引脚接受 0.5~2.5V 或 0~10V对应 0~100% 电流。接口电路要做三件事限幅、滤波、阻抗匹配。限幅用肖特基二极管钳位到驱动芯片的 VDD 和 GND防止上电顺序异常时打坏 DIM 引脚。滤波用 1k 加 1uF 低通截止频率约 160Hz能压住 DA 更新带来的台阶。阻抗匹配要看 DIM 引脚输入阻抗如果只有几十 kDA 输出串 100 欧姆后直接接没问题如果输入阻抗低要加运放缓冲。提示DA 转换芯片的模拟地和 LED 驱动的功率地要单点连接最好在驱动芯片的 GND 引脚附近汇合。把 DA 的地和功率地分开走再在电源入口连低亮度时不容易出现随电流变化的亮度抖动。还有一个细节很多恒流驱动芯片的 DIM 引脚在 0V 附近不是完全关断而是有 1%~3% 的残余电流。DA 输出 0V 时灯珠可能微亮。解决方法是把 DA 最低输出设在 20~50mV或者用 MCU 的 GPIO 控制驱动的 EN 引脚在亮度等级为 0 时彻底关断。3.3 用 MCP4725 输出 0~3.3V 控制恒流驱动MCP4725 的 I2C 地址通常是 0x60 或 0x61写 12 位数据加 2 位命令。下面代码把 0~1000 的亮度等级映射到 0~4095 的 DAC 码值并写入 MCP4725 的寄存器。import smbus2 MCP4725_ADDR 0x60 bus smbus2.SMBus(1) def set_dac_level(level, level_max1000, vref3.3): if level 0: level 0 if level level_max: level level_max # 12 位 DAC码值 0~4095 dac_code int(level / level_max * 4095) # 命令字节写 DAC 寄存器不保存到 EEPROM cmd 0x40 data [(dac_code 4) 0xFF, (dac_code 4) 0xF0] bus.write_i2c_block_data(MCP4725_ADDR, cmd, data) voltage dac_code / 4095.0 * vref return dac_code, voltage逻辑说明先把亮度等级限幅再按 12 位满量程 4095 换算成 DAC 码值。MCP4725 写寄存器时第一个字节高 4 位是码值高 4 位第二个字节高 4 位是码值低 8 位低 4 位补 0。命令字节 0x40 表示只写 DAC 寄存器掉电不保存0x60 表示写 DAC 并保存到 EEPROM。参数说明level_max与上一章映射输出保持一致如果上一章输出 0~1000这里也取 1000vref按 MCP4725 的 VDD 实际电压填3.3V 供电时满量程接近 3.3V但输出高端会有几十 mV 压降要求高时用外部基准。如果写完后电压不动先量 VDD再查 I2C 地址是否被 A0 引脚改变。3.4 DA 输出非线性与最低亮度死区的处理DA 转换芯片本身线性度通常很好12 位 MCP4725 的 INL 在几个 LSB 以内真正非线性来自 LED 驱动和灯珠。人眼对亮度感知近似 Gamma 2.2如果 DA 码值线性增加低亮度端变化太快高亮度端又看不出差别。常见做法是在 AD 映射和 DA 输出之间插入 Gamma 查表。假设亮度等级 0~1000Gamma 2.2 映射可以写成def gamma_correct(level, level_max1000, gamma2.2): ratio level / level_max corrected ratio ** gamma return int(corrected * level_max) for level in [0, 50, 100, 200, 500, 800, 1000]: print(level, gamma_correct(level))逻辑说明把归一化亮度做 Gamma 幂运算再恢复到等级量程。参数说明gamma2.2是常用视觉曲线如果灯珠和驱动本身已经做了 Gamma这里可以取 1.0 或 1.8输出等级再送set_dac_level。最低亮度死区要单独处理当level 1%时直接输出 0 并关 EN当level在 1%~5% 时用查表值不要把 DAC 码值从 0 直接跳到几十否则会看到“咔”一下亮起来。注意Gamma 查表不要每次控制循环都算浮点幂低端 MCU 上耗时且抖动。上电时生成 1001 个 uint16 的查表数组运行时只做查表。4. AD 与 DA 转换芯片联调可调光台灯控制逻辑与整定4.1 主循环状态机采样、滤波、映射、输出AD 转换芯片和 DA 转换芯片联调时主循环不要写成“读一次、算一次、写一次”的裸循环否则 I2C 阻塞和滤波延迟会让旋钮手感发涩。我一般用 10ms 定时器做采样节拍主循环里做状态机采样态读 AD滤波态更新滤波器映射态做校准和 Gamma输出态写 DA。亮度等级变化小于 1 个等级时不写 DA减少 I2C 流量。下面是一个简化状态机代码。import time class LampController: def __init__(self, ad_filter, ad_min, ad_max): self.ad_filter ad_filter self.ad_min ad_min self.ad_max ad_max self.last_level -1 self.current_level 0 def tick(self, raw_ad): filtered self.ad_filter.update(raw_ad) level ad_to_level(filtered, self.ad_min, self.ad_max, 1000) level gamma_correct(level, 1000, 2.2) # 迟滞变化超过 2 个等级才更新避免边界抖动 if abs(level - self.last_level) 2: self.current_level level self.last_level level set_dac_level(level, 1000, 3.3) return self.current_level ctrl LampController(AdFilter(), ad_min1200, ad_max31000) while True: raw read_ads1115()[0] ctrl.tick(raw) time.sleep(0.01)逻辑说明每个 10ms 节拍读一次 AD 原始值经过滤波器做 AD 到等级映射、Gamma 校正再和上次输出比较超过 2 个等级才写 DA。参数说明ad_min1200和ad_max31000是示例校准值实际要按实测改迟滞阈值 2 可以改到 3~5旋钮越差可以越大time.sleep(0.01)决定采样周期配合 AD 转换芯片的 128SPS 时要注意别读太快否则读到重复值。4.2 调光曲线线性、Gamma 2.2 与视觉亮度匹配调光曲线决定“手感”。线性曲线在低亮度端变化剧烈高亮度端迟钝Gamma 2.2 接近人眼但最低端会偏暗有些产品用分段曲线前 20% 等级映射到 0~5% 电流后 80% 映射到 5%~100%。下面表格对比三种曲线在典型等级下的 DA 码值假设 DA 满量程 4095。亮度等级线性 DA 码值Gamma 2.2 DA 码值分段曲线 DA 码值000050205210100410925300122810518050020473415008003276106418001000409540954095从表里能看出Gamma 2.2 在 50 级时只输出 2 个码值如果 DA 有噪声低亮度会闪。所以低亮度端要么用分段曲线把码值抬起来要么提高 DA 分辨率到 16 位。另一个做法是在 DA 输出后加 RC 滤波但 RC 不能消除码值量化噪声只能压高频。4.3 采样频率、DA 更新率与闪烁的权衡AD 采样频率、DA 更新率和滤波器长度三者互相牵制。采样太快噪声进得多采样太慢旋钮响应迟DA 更新太快I2C 流量大且可能写坏 EEPROMDA 更新太慢调光有台阶。下面这张参数表是我在 12 位 AD、12 位 DA、I2C 400kHz 下的常用起点。参数典型值可调范围影响AD 采样周期10ms5~50ms越小越跟手噪声越大中值窗口53~7越大去毛刺越强延迟越大平均窗口84~16越大越稳旋钮越钝DA 更新阈值2 等级1~5越大越省 I2C台阶越明显I2C 速率400kHz100~400kHz越高写 DA 越快抗干扰越差Gamma2.21.8~2.5越大低端越暗整定时先固定 AD 采样 10ms、中值 5、平均 8然后转旋钮看低亮度有没有闪烁。如果闪先加 DA 输出 RC 滤波再把 Gamma 降到 1.8最后才考虑换 16 位 DA。不要一上来就加长平均窗口那会把旋钮变成“橡皮筋”。4.4 常见故障排查读数跳、亮度阶跃、DA 无输出联调阶段的问题大多集中在 I2C 地址、参考电压、地线和驱动接口。下面表格按现象列检查点。现象可能原因检查点AD 读数乱跳参考电压不稳、地线长、电位器接触不良量 VREF 纹波短接地线换电位器AD 读数固定 0 或满量程输入超范围、I2C 没应答、配置寄存器错量 AIN 电压扫 I2C 地址读配置回写低亮度闪烁DA 码值太小、Gamma 太陡、驱动 DIM 有纹波看 DA 输出波形改分段曲线加 RC亮度阶跃DA 更新阈值太大、查表步进粗减小阈值增加等级数检查 Gamma 表DA 无输出地址错、VDD 没电、命令字节错量 VDD扫 I2C用万用表量 OUT高亮度上不去DA 满量程不够、运放没轨到轨、DIM 上限低量 DA 满码输出查运放供电查驱动规格排查时先分清楚是 AD 侧还是 DA 侧。把 AD 原始值打印出来如果原始值稳定而亮度不对问题在映射或 DA如果原始值就跳先治 AD。DA 侧可以用万用表直流档量 OUT 引脚写 0、2048、4095 三个码值看电压是否接近 0、VDD/2、VDD。如果 4095 时电压只有 2.8V而 VDD 是 3.3V说明输出接近轨但没到轨驱动 DIM 的上限要重新算。提示I2C 总线上挂 AD 和 DA 时上拉电阻不要重复焊。很多模块自带 4.7k 上拉两个模块并联后等效 2.35k400kHz 下可能上升沿太陡反而振铃。用示波器看 SCL 上升沿如果过冲明显拆掉一个模块的上拉。5. 可调光台灯低亮度验证AD 采样校准与 DA 输出保持技巧5.1 上电自动校准 AD 采样上下限可调光台灯批量做的时候电位器角度和 AD 转换芯片的零点会有差异。手动写死ad_min和ad_max会导致每台灯低亮度端不一样。我一般在上电时做一次快速校准检测到电位器在最低端停留 500ms记录 20 次 AD 采样的中值作为ad_min检测到最高端停留 500ms记录中值作为ad_max。校准值写入 EEPROM下次上电直接读。如果没有 EEPROM可以用 MCU 的 Flash 模拟或者用 MCP4725 自带的 EEPROM 存一个标志位。校准代码片段def auto_calibrate(read_raw, save_func): low_samples [] high_samples [] # 简化连续 20 次低于 5% 量程认为在最低端 for _ in range(20): raw read_raw() if raw 32768 * 0.05: low_samples.append(raw) time.sleep(0.02) if len(low_samples) 15: ad_min sorted(low_samples)[len(low_samples)//2] save_func(ad_min, ad_min) # 最高端同理高于 95% 量程 for _ in range(20): raw read_raw() if raw 32768 * 0.95: high_samples.append(raw) time.sleep(0.02) if len(high_samples) 15: ad_max sorted(high_samples)[len(high_samples)//2] save_func(ad_max, ad_max)逻辑说明在最低端和最高端各采 20 次用中值作为校准点避免单次噪声。参数说明5% 和 95% 是判断阈值如果电位器机械角度小可以改成 10% 和 90%20 次采样在 20ms 周期下约 400ms用户感觉不到。注意校准期间不要写 DA先把亮度关到 0否则用户会看到亮度乱跳。5.2 DA 输出保持与掉电保存亮度MCP4725 支持把 DAC 值写入 EEPROM上电自动恢复。这样即使 MCU 还没启动台灯也能保持上次亮度。写入 EEPROM 不能太频繁MCP4725 的 EEPROM 擦写寿命约 100 万次如果每次旋钮都写几天就写坏了。常见做法是“运行中只写 DAC 寄存器检测到亮度稳定 3 秒后再写 EEPROM”或者掉电检测时用储能电容供电写一次。DAC8551 没有 EEPROM可以在 MCU 的 Flash 里存亮度等级上电后先写 DA 再开 EN。5.3 低亮度验证示波器、万用表和眼睛的组合低亮度是台灯最难调的部分。验证时不要只靠眼睛眼睛会适应暗环境。我一般用示波器看 DA 输出纹波用万用表看直流平均电压再用眼睛看 1%、3%、5% 三个等级有没有闪烁和色偏。示波器探头接 DA 输出交流耦合带宽限制 20MHz如果看到几十 mV 的周期性纹波先查 I2C 写入频率和电源。万用表直流档量 DIM 引脚确认 1% 等级电压不在驱动芯片的死区内。眼睛验证要在暗室适应 5 分钟后进行如果 1% 等级看不见但 2% 突然亮说明 Gamma 低端太陡把分段曲线的前 10% 等级再压低。把示波器探头从 DA 输出移到 LED 驱动电流采样电阻两端看电流波形是否随亮度等级单调变化这是判断 AD 到 DA 整条链路是否线性的最后一步。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门