ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32 HAL库驱动DAC8568八通道DAC:SPI时序、寄存器配置与精度校准

2026/9/21 1:43:23 拓冰建站 浏览量
STM32 HAL库驱动DAC8568八通道DAC:SPI时序、寄存器配置与精度校准 1. 项目缘起与核心需求拆解1.1 为什么选DAC8568而不是DAC8563或DAC8552做多通道模拟量输出的项目选型阶段绕不开几个硬指标通道数、分辨率、接口类型、建立时间、封装尺寸。DAC8568是TI的八通道16位DACSPI接口内置2.5V基准上电默认输出0V这些特性决定了它在工业控制、波形发生、多路偏置电压调节等场景里非常能打。我最早接触这个芯片是在一个多路激光偏置电压控制的项目里需要同时输出8路独立可调的直流电压精度要求到毫伏级而且要求上电瞬间不能有毛刺。当时对比过几款方案DAC8563是双通道16位通道数不够DAC8552也是双通道AD5668是八通道但价格偏高且基准需要外部提供。DAC8568刚好卡在性价比和功能密度的甜点位上。这里要特别说明一点DAC8568和DAC8568A的区别。前者是标准版后者在积分非线性INL和微分非线性DNL指标上略有差异。一般应用选标准版就够了除非你做精密仪器需要更严格的线性度保证。1.2 核心需求解析8通道独立输出意味着什么8通道独立DAC的核心价值在于“同时且独立”。很多项目里需要控制多个执行器比如多路可调电源的基准电压设定相控阵天线的波束赋形偏置多通道传感器模拟前端校准音频多声道系统的直流偏置如果用8个单通道DAC或者4个双通道DAC来拼PCB面积、布线复杂度、SPI片选数量都会成倍增加。DAC8568把8个通道塞进一个芯片共享SPI总线和基准源硬件设计一下子简化了很多。但“独立”也带来一个坑8个通道共用一组SPI数据线你必须清楚每个通道的寄存器映射和更新时序否则会出现“改了通道3结果通道5也变了”的诡异现象。后面我会详细拆解寄存器操作。1.3 适合谁来参考这篇内容这篇内容适合以下人群已经会用STM32 HAL库操作SPI但没驱动过多通道DAC的嵌入式工程师正在做多路模拟输出项目需要快速验证DAC8568方案的开发者想了解SPI时序、LDAC同步机制、基准电压配置等细节的学生或爱好者手头有STM32F103或类似型号想直接抄代码跑通的人我默认你已经有基本的STM32开发环境Keil MDK或STM32CubeIDE会用CubeMX配置外设能看懂简单的C语言。如果你连SPI是什么都还不清楚建议先补一下SPI通信的基础知识否则后面的时序分析会比较吃力。2. 硬件设计与SPI通信底层逻辑2.1 DAC8568引脚功能与最小系统连接DAC8568常见封装是TSSOP-16引脚定义如下表引脚编号名称功能说明1VOUTA通道A模拟输出2VOUTB通道B模拟输出3VOUTC通道C模拟输出4VOUTD通道D模拟输出5VOUTE通道E模拟输出6VOUTF通道F模拟输出7VOUTG通道G模拟输出8VOUTH通道H模拟输出9GND地10VDD电源正2.7V~5.5V11SYNC片选/帧同步低有效12SCLKSPI时钟13DINSPI数据输入14LDAC加载DAC寄存器低有效15CLR清零低有效把输出拉到0V16VREF基准电压输入/输出最小系统连接思路VDD接3.3VGND接地VREF可以不接外部基准因为DAC8568内部有2.5V基准但需要配置寄存器使能。SYNC接STM32的一个GPIO用软件控制片选。虽然SPI硬件NSS也可以但多从机场景下软件片选更灵活。SCLK、DIN接STM32的SPI时钟和数据线。LDAC可以接GPIO也可以直接接地。接地的意思是每次写DAC寄存器立即更新输出接GPIO则可以批量写入后再统一更新实现多通道同步。CLR接GPIO或上拉正常工作时保持高电平。注意VREF引脚如果使用内部基准需要写配置寄存器打开内部基准否则输出范围会不对。很多人第一次调试发现输出电压只有预期的一半就是忘了开内部基准。2.2 SPI模式选择与时序参数计算DAC8568的SPI接口支持标准SPI模式具体是CPOL0CPHA0还是CPOL1CPHA1查数据手册可知DAC8568在SCLK下降沿采样DIN所以CPOL时钟极性空闲时SCLK为低电平即CPOL0CPHA时钟相位第一个边沿采样即CPHA0所以STM32的SPI配置为Mode 0CPOL0CPHA0。时序参数方面DAC8568的SCLK最高频率是50MHzVDD5V时3.3V供电时略低但一般跑10MHz以内完全没问题。我实测用STM32F103的SPI1跑9MHz很稳再高就要看布线质量了。数据格式是24位8位命令16位数据。STM32的SPI数据帧可以配置为8位或16位这里建议用8位帧分3次发送或者用16位帧分2次发送。我习惯用8位帧逻辑更清晰。实操心得STM32F103的SPI在8位帧模式下每次发送后要等TXE标志置位再发下一个字节否则会丢数据。用HAL库的HAL_SPI_Transmit函数会自动处理但如果你用寄存器操作就要注意。2.3 硬件片选与软件片选的取舍SPI的片选有两种做法硬件NSS和软件GPIO。STM32的SPI硬件NSS在多从机场景下比较鸡肋因为NSS引脚只有一个而且硬件NSS的时序控制不够灵活。我推荐用软件片选理由如下可以任意指定GPIO布线更自由片选拉低和拉高的时机完全可控方便插入延时多从机时只需增加GPIO数量不占用SPI外设资源具体操作在发送24位数据前把SYNC引脚拉低发送完毕后拉高SYNC。注意SYNC拉低到第一个SCLK下降沿之间要有至少10ns的建立时间STM32的GPIO翻转速度足够快一般不需要额外延时但如果你跑非常高的SPI时钟建议加一个__NOP()。3. STM32 HAL库驱动代码逐层拆解3.1 CubeMX配置SPI外设的关键参数打开CubeMX选好STM32F103芯片配置SPI1ModeFull-Duplex MasterHardware NSS SignalDisableData Size8 BitsFirst BitMSB FirstPrescaler根据系统时钟计算比如72MHz主频分频8得到9MHzClock PolarityLowClock Phase1 EdgeCRC CalculationDisabledGPIO配置PA5 - SPI1_SCKPA7 - SPI1_MOSIPA4 - GPIO_OutputSYNCPA3 - GPIO_OutputLDACPA2 - GPIO_OutputCLR注意SPI1的MISO引脚PA6在只写场景下可以不接但CubeMX里如果配置了Full-Duplex引脚会占用。可以改成Half-Duplex Master或者Transmit Only Master省一个引脚。3.2 DAC8568命令寄存器映射详解DAC8568的24位数据格式前8位命令字节包含通道选择和操作类型后16位数据值命令字节的位定义位名称说明23-20C3-C0命令类型19-16A3-A0通道地址15-0D15-D0数据常用命令类型0000写输入寄存器0001更新DAC寄存器0010写输入寄存器并更新所有DAC0011写输入寄存器并更新指定DAC0100掉电模式0101写清零码0110写LDAC码0111软件复位1000内部基准使能通道地址0000通道A0001通道B...0111通道H1111所有通道举个例子要让通道A输出满量程的一半即0x8000操作是发送命令0x00地址0x00数据0x8000 - 写输入寄存器A发送命令0x01地址0x00数据0x0000 - 更新DAC A或者用一条命令搞定命令0x03地址0x00数据0x8000 - 写输入寄存器并更新DAC A。3.3 完整驱动代码与逐行注释下面是我在实际项目中用的驱动代码基于HAL库可以直接移植。#include dac8568.h // 片选控制宏 #define DAC_SYNC_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define DAC_SYNC_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) #define DAC_LDAC_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET) #define DAC_LDAC_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET) #define DAC_CLR_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET) #define DAC_CLR_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET) extern SPI_HandleTypeDef hspi1; // 发送24位数据 static void DAC8568_Write24(uint8_t cmd, uint8_t addr, uint16_t data) { uint8_t buf[3]; buf[0] (cmd 4) | (addr 0x0F); buf[1] (data 8) 0xFF; buf[2] data 0xFF; DAC_SYNC_LOW(); HAL_SPI_Transmit(hspi1, buf, 3, 100); DAC_SYNC_HIGH(); } // 初始化使能内部基准复位 void DAC8568_Init(void) { DAC_CLR_HIGH(); DAC_LDAC_HIGH(); DAC_SYNC_HIGH(); HAL_Delay(1); // 软件复位 DAC8568_Write24(0x07, 0x00, 0x0000); HAL_Delay(1); // 使能内部基准 DAC8568_Write24(0x08, 0x00, 0x0001); HAL_Delay(10); // 等待基准稳定 } // 设置指定通道输出立即更新 void DAC8568_SetChannel(uint8_t ch, uint16_t value) { DAC8568_Write24(0x03, ch, value); } // 批量设置所有通道最后统一LDAC更新 void DAC8568_SetAllChannels(uint16_t *values) { for (uint8_t i 0; i 8; i) { DAC8568_Write24(0x00, i, values[i]); } DAC_LDAC_LOW(); __NOP(); DAC_LDAC_HIGH(); }代码逻辑说明DAC8568_Write24把命令、地址、数据拼成3个字节通过SPI发送。注意命令字节是(cmd 4) | addr因为命令占高4位地址占低4位。DAC8568_Init先复位再使能内部基准。内部基准使能后需要等待至少10ms让基准电压稳定否则输出会漂。DAC8568_SetChannel用命令0x03写输入寄存器并立即更新对应DAC。DAC8568_SetAllChannels用命令0x00先写所有输入寄存器然后拉低LDAC统一更新实现8通道同步输出。实操心得如果你不需要同步更新直接用DAC8568_SetChannel逐个设置就行。但如果做波形发生必须用批量模式否则通道之间的更新时刻会差几十微秒波形会畸变。4. 输出电压计算与精度校准4.1 输出电压公式推导DAC8568的输出电压公式Vout Vref × (D / 65536) × Gain其中Vref是基准电压内部基准为2.5VD是16位数字量范围0~65535Gain是增益默认1可以配置为2如果使用内部2.5V基准增益为1那么D0时Vout0VD65535时Vout2.5V × (65535/65536) ≈ 2.49996V实际项目中我通常需要0~5V输出范围这时候有两种做法使用内部2.5V基准配置增益为2输出范围0~5V使用外部5V基准增益为1输出范围0~5V方案1更省事但增益为2时噪声也会放大一倍。方案2需要外部基准芯片成本增加但精度更好。我一般选方案1因为DAC8568的内部基准温漂是10ppm/°C增益为2后等效5ppm/°C对大多数工业应用够用了。4.2 数字量与电压的换算代码// 将电压值转换为DAC数字量 // vref: 基准电压gain: 增益voltage: 目标电压 uint16_t VoltageToDAC(float vref, uint8_t gain, float voltage) { float max_v vref * gain; if (voltage 0) voltage 0; if (voltage max_v) voltage max_v; return (uint16_t)(voltage / max_v * 65535.0f 0.5f); } // 示例输出2.5V内部基准2.5V增益2 uint16_t dac_val VoltageToDAC(2.5f, 2, 2.5f); // 结果应该是32768左右 DAC8568_SetChannel(0, dac_val);注意浮点运算在STM32F103上比较慢如果频繁调用建议用定点数或者预先算好查找表。我一般只在初始化时算一次运行时直接查表。4.3 实测精度与校准方法我用6位半万用表实测过几块板子DAC8568在内部基准、增益2、输出2.5V时的误差大约在±3mV以内。这个误差主要来自内部基准的初始精度±0.1%增益误差输出缓冲器的失调如果需要更高精度可以做两点校准输出数字量0测量实际电压V0输出数字量65535测量实际电压V1计算实际增益和偏移Gain (V1-V0)/65535Offset V0反推目标电压对应的数字量D (Vtarget - Offset) / Gain校准后精度可以做到±1mV以内。我在一个传感器校准项目里用这个方法8个通道的一致性非常好通道间偏差小于0.5mV。5. 常见问题排查与避坑指南5.1 上电输出异常问题排查现象上电后DAC输出不是0V而是接近VDD或者随机值。原因分析CLR引脚没有拉高芯片处于清零状态但输出缓冲器未使能内部基准未使能输出范围不对上电时序问题VDD和VREF上电顺序不对解决方法确认CLR引脚上电后为高电平在初始化代码里先发软件复位命令再使能内部基准如果VREF使用外部基准确保外部基准先于VDD上电或者在VREF引脚加RC延时我踩过的坑有一次CLR引脚忘了配置默认浮空结果芯片一直处于清零状态输出始终是0V。后来把CLR配置为推挽输出高电平就好了。5.2 SPI通信失败排查流程现象写数据后DAC无输出或者输出值不对。排查步骤步骤检查项工具1SCLK是否有波形示波器2DIN数据是否在SCLK下降沿稳定示波器3SYNC是否在数据传输期间保持低电平示波器4SPI模式是否为Mode 0代码检查5数据字节顺序是否正确逻辑分析仪6命令字节和地址是否正确代码检查我遇到最多的问题是SPI模式配错。有一次用了Mode 3结果数据完全错乱。DAC8568只支持Mode 0这个没有商量余地。另一个常见问题是数据字节顺序。STM32的SPI是MSB FirstDAC8568也是MSB First这个一般不会错。但如果你用软件模拟SPI就要注意先发高位还是低位。5.3 多通道同步更新的时序陷阱现象用批量模式更新8个通道时通道间输出有先后差异。原因LDAC拉低后DAC寄存器更新需要一定时间。如果LDAC脉冲太窄部分通道可能没来得及更新。解决方法LDAC低电平持续时间至少20ns我一般用__NOP()延时几个周期如果对同步要求极高可以用硬件触发LDAC比如用定时器输出PWM控制LDAC避免在LDAC拉低期间操作SPI否则会干扰更新实操心得我在做8通道波形发生的时候发现通道间相位差有几十微秒。后来把LDAC脉冲宽度从1个NOP增加到10个NOP相位差就降到1微秒以内了。这个细节数据手册里没写是实测出来的。5.4 电源噪声与输出纹波抑制DAC8568的输出噪声主要来自电源和基准。实测发现如果VDD直接用STM32板上的3.3V LDO输出纹波大约2mVpp。后来在VDD引脚加了一个10uF钽电容并联100nF陶瓷电容纹波降到0.5mVpp。另外VREF引脚如果使用内部基准建议在VREF引脚对地加1uF电容可以进一步降低噪声。如果使用外部基准基准芯片的噪声直接决定输出噪声选低噪声基准很重要。6. 进阶应用与项目扩展思路6.1 用DMA实现8通道波形连续输出如果要做波形发生比如正弦波、三角波用SPI阻塞发送会占用大量CPU时间。这时候可以用DMA。思路在内存里建一个波形查找表比如256点的正弦波每个点对应8个通道的数据配置SPI DMA把数据流源源不断送到DAC8568用定时器触发DMA传输控制波形频率具体实现时要注意DAC8568每次需要3个字节8个通道就是24个字节。DMA传输完24个字节后需要拉低LDAC更新输出。这个LDAC控制可以用DMA传输完成中断来实现。我实测用STM32F103的SPI1 DMA配合定时器触发可以做到100kHz的更新率8个通道同时输出正弦波CPU占用率不到5%。6.2 多片DAC8568级联扩展通道数如果需要超过8个通道可以用多片DAC8568级联。SPI总线可以并联每片用独立的SYNC引脚。STM32的GPIO足够多的话可以控制很多片。级联时要注意每片的SYNC独立控制避免同时选中LDAC可以共用实现所有芯片同步更新电源和基准可以共用但要注意负载能力我在一个32通道的项目里用了4片DAC8568共用SPI和LDAC每片独立SYNC。代码上只需要把通道号映射到对应的片选和通道地址即可。6.3 与STM32内部DAC的对比与选型建议STM32F103内部有2个12位DAC通道输出范围0~3.3V。对比DAC8568指标STM32内部DACDAC8568分辨率12位16位通道数28输出范围0~VREF0~2.5V/5V建立时间1us10us接口直接寄存器SPI成本0约30元选型建议如果只需要1~2个通道精度要求不高直接用内部DAC如果需要4个以上通道或者精度要求12位以上选DAC8568如果需要高速波形输出内部DAC的建立时间更短但通道数少我个人经验是内部DAC适合做简单的电压设定比如比较器阈值、LED亮度控制。DAC8568适合做多通道精密控制比如传感器阵列偏置、多轴运动控制。6.4 基于DAC8568的实用项目案例分享一个我实际做过的项目8通道可编程电压源。用STM32F103 DAC8568 运放缓冲输出0~10V可调每个通道独立设定通过串口接收指令。硬件上DAC8568输出0~2.5V经过同相放大器放大4倍得到0~10V。运放选的是OPA2277低失调低漂移。电源用±15V给运放供电DAC8568用3.3V。软件上串口接收指令格式如“CH15.000”解析后调用VoltageToDAC换算再写入DAC。同时用OLED显示8个通道的当前电压值。这个项目里踩过的坑运放放大倍数计算错误导致输出范围不对DAC8568的LDAC引脚忘了接导致输出不更新串口接收中断和SPI发送冲突数据错乱后来把SPI发送放在主循环里串口中断只负责收数据存缓冲区问题就解决了。7. 调试工具与实测数据记录7.1 逻辑分析仪抓取SPI时序调试SPI最有效的工具是逻辑分析仪。我用的是Saleae Logic 8抓DAC8568的SPI波形非常方便。抓取时设置采样率24MHz以上通道SCLK、DIN、SYNC、LDAC触发SYNC下降沿抓到的波形可以解码SPI直接看到发送的24位数据。我一般会对照代码里的命令字节确认每一位都对。有一次发现发送的数据和预期差了一位查了半天发现是SPI的First Bit配置错了配成了LSB First。改成MSB First就对了。7.2 万用表与示波器实测数据用6位半万用表Keysight 34461A实测DAC8568输出设定值实测值误差0.000V0.0002V0.2mV1.000V1.0013V1.3mV2.500V2.5021V2.1mV5.000V5.0038V3.8mV误差随输出电压增大而增大符合增益误差的特征。做两点校准后误差降到±0.5mV以内。用示波器看输出噪声带宽限制20MHz峰峰值约1.2mV。加RC滤波后降到0.3mV。7.3 长时间稳定性测试记录我做过一个72小时的老化测试DAC8568输出2.5V环境温度25°C±2°C每小时记录一次第1小时2.5021V第24小时2.5018V第48小时2.5015V第72小时2.5013V漂移约0.8mV主要是内部基准的长期漂移。这个指标对大多数应用足够了。如果做精密仪器建议用外部基准比如REF5025温漂可以做到3ppm/°C。8. 代码移植与不同STM32型号适配8.1 从STM32F103移植到STM32F4的注意事项STM32F4的SPI外设和F103基本兼容但有几个差异F4的SPI时钟最高可达42MHz分频系数不同F4的GPIO复用功能配置不同需要选AF5SPI1F4的HAL库SPI句柄结构略有差异移植步骤在CubeMX里重新配置SPI和GPIO修改SPI句柄名称比如hspi1改成hspi2调整分频系数保持SCLK在10MHz左右重新编译测试我移植过一个项目从F103到F407代码改动不到10行主要是SPI句柄和GPIO端口号。8.2 HAL库与LL库的性能对比HAL库的SPI发送函数有较多的状态检查和超时处理单次发送24位数据大约需要5~10微秒。LL库直接操作寄存器可以做到1微秒以内。如果对速度要求高可以用LL库重写SPI发送static void DAC8568_Write24_LL(uint8_t cmd, uint8_t addr, uint16_t data) { uint8_t buf[3]; buf[0] (cmd 4) | (addr 0x0F); buf[1] (data 8) 0xFF; buf[2] data 0xFF; DAC_SYNC_LOW(); for (int i 0; i 3; i) { while (!LL_SPI_IsActiveFlag_TXE(SPI1)); LL_SPI_TransmitData8(SPI1, buf[i]); } while (!LL_SPI_IsActiveFlag_TXE(SPI1)); while (LL_SPI_IsActiveFlag_BSY(SPI1)); DAC_SYNC_HIGH(); }实测LL库版本比HAL库快3倍左右。但LL库的可移植性差一些换芯片要改寄存器名。8.3 在Keil MDK和STM32CubeIDE下的工程配置Keil MDK下需要安装STM32F1的Device Family Pack在Options for Target里选对芯片型号添加HAL库源文件到工程配置Debug为ST-LinkSTM32CubeIDE下直接用CubeMX生成工程自动包含HAL库编译下载一键完成我两个都用过CubeIDE上手更快但Keil的调试功能更强。如果做复杂项目建议用Keil CubeMX的组合。9. 个人实操体会与后续扩展这个DAC8568的驱动我前前后后调了大概两周大部分时间花在排查SPI时序和输出异常上。最深的体会是数据手册一定要仔细看尤其是时序图和寄存器映射表。很多问题其实手册里都写了只是第一遍看的时候容易忽略。比如LDAC的脉冲宽度手册里写的是最小20ns但实际测试发现如果只给20ns偶尔会有通道不更新。后来加到100ns就完全稳定了。这种细节只能靠实测积累。后续我打算把这个驱动封装成独立的模块加上通道校准功能做成一个通用的多通道DAC驱动库。另外还想试试用DMA 定时器实现任意波形发生把更新率再提高一个数量级。如果你也在用DAC8568欢迎交流踩坑经验。特别是多片级联和高速波形输出这两个方向我还有很多细节没吃透期待和同行一起探讨。