ARTICLE DETAIL

资讯详情

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

STM32F103驱动DAC7311:SPI接口12位DAC例程详解

STM32F103驱动DAC7311:SPI接口12位DAC例程详解 简介这是一份基于STM32F10X微控制器驱动DAC7311芯片的完整工程例程适合嵌入式开发入门与进阶者学习I2C/SPI通信、DAC输出以及压控增益控制。资源共163个文件代码以C语言与头文件为主并包含编译生成的axf、hex、map等输出文件以及工程配置、备份与烧录辅助工具压缩包大小为3.26MB可直接在KEIL MDK下打开工程进行编译调试。内容覆盖STM32F10X的底层驱动、DAC7311寄存器读写与OPA830运放增益调节逻辑配套README提供了项目说明与编译注意事项能帮助使用者快速搭建数字模拟转换与压控增益实验环境。已有1430人学习下载适合需要掌握微控制器与模拟器件协同设计、提升信号链调试能力的工程师参考。通过本例程可理解从数字量到模拟电压的完整链路并了解实际工程中如何配置时钟、通信接口及输出精度控制。 拿到这颗 DAC7311 的时候我其实有点意外——从封装到引脚怎么看都像一颗普通的小型串行 DAC但数据手册翻到参考电压那一页我才发现它的参考电压就是 VDD输出可以直接顶到电源轨。这意味着只要给 STM32F103 写一套 SPI 驱动就能用一个 6 脚小芯片做可调电压源、波形发生器甚至给传感器提供偏置。最近我把 DAC7311 接到 STM32F103C8T6 上用标准外设库把底层驱动、电压换算、波形输出全部调通整理成一份可直接落地的 STM32F10X 例程。这篇帖子就把这颗芯片的选型逻辑、驱动代码、调试验证和踩过的坑完整拆开讲清楚适合正在用 STM32F1 系列做模拟输出、信号源或者电源控制的朋友参考。1. DAC7311 是什么先看参数再写代码网上关于 DAC7311 的中文资料很零散很多帖子直接用 MSP430 或者 Arduino 的库来驱动到了 STM32 上就得自己重写。在动手之前建议先把芯片的几项关键参数吃透不然写出来的代码很可能“发了数据却看不到输出”。DAC7311 是 TI 推出的一款 12 位单通道电压输出 DAC采用 SOT-23-6 小封装工作电压范围是 2.7V 到 5.5V。和很多 DAC 不同的是它没有独立的参考电压引脚满量程输出值直接取决于 VDD。也就是说VDD 是 3.3V 时满量程约 3.3VVDD 是 5V 时满量程约 5V输出是典型的轨到轨结构负载能力不算强但驱动一般的高阻抗输入端完全没问题。SPI 接口只需要三根线DIN、SCLK、SYNC。SYNC 相当于片选信号拉低后开始传输数据传输完一帧后拉高DAC 输出更新。它支持 12 位和 16 位两种数据帧12 位帧只发数据16 位帧则带着控制位一起发。例程里推荐使用 16 位帧因为这可以同时设置芯片的工作模式和掉电状态驱动逻辑也更统一。我选择这颗芯片的核心原因有三个第一是封装小适合做便携仪表和传感器模块第二是 SPI 接口几乎不占 GPIO一个 SPI 总线可以挂多片实现多通道输出第三是有掉电模式在低功耗设备里可以随手关掉 DAC静态电流降得非常明显。同系列还有 DAC8311 等变体如果对分辨率要求更高可以直接换用引脚和驱动时序基本兼容。和常见的 MCP4921 相比DAC7311 的写入时序更简单MCP4921 还要处理 A/B 通道选择位和增益位DAC7311 则没有通道选择问题每帧直接就写一个通道的电压值。和 TLV5618 这类双通道 DAC 相比DAC7311 虽然只有单通道但封装小、功耗低很多时候“单通道但体积小”反而是真正的需求。2. 硬件连接与电平匹配三根线也有讲究DAC7311 的接线不复杂但我在调板子时发现电平匹配问题被很多人忽略了。例程里默认使用 SPI1引脚分配如下DAC7311 引脚STM32F103 引脚说明VDD3.3V 或 5VDAC 的参考电压必须加去耦电容GNDGND电源地靠近引脚接地DINPA7 (SPI1_MOSI)数据输入SCLKPA5 (SPI1_SCK)串行时钟SYNCPA4 (任意 GPIO)帧同步/片选低有效VOUT负载模拟电压输出这里有个极易踩的坑如果 DAC7311 用 5V 供电而 STM32 是 3.3V 供电那么 STM32 输出的 SPI 信号高电平大概只有 3.3V但 DAC7311 在 VDD5V 时要求输入高电平最低为 0.7VDD也就是 3.5V3.3V 的电平根本达不到 VIH 阈值。表现就是“数据发过去了芯片偶尔响应一下更多时候输出不动”。所以要么让 DAC 也用 3.3V 供电要么在 SPI 信号线上加电平转换芯片例程里为了图省事直接把 VDD 接到了 3.3V这样逻辑电平完全匹配。电源端的去耦也很重要VDD 既是供电又是参考电压电源纹波会直接反映到模拟输出上。实测下来如果只在 VDD 附近放一颗 0.1µF 陶瓷电容用示波器看 VOUT 还是能观察到和开关电源同频的毛刺。我在例程的原理图建议中同时放了 10µF 钽电容和 0.1µF 陶瓷电容并且靠近芯片引脚放置输出噪声明显下降。SYNC 引脚我特意选了一个普通 GPIOPA4而不是直接使用 SPI 的硬件 NSS。硬件 NSS 在主模式下需要额外配置 SSOE 位否则在双线全双工模式下会自动拉低 NSS干扰帧同步逻辑。用软件控制电平是最稳妥的方案读写过程也更透明方便加延时和调试。此外VOUT 输出端最好串一个 100Ω 左右的电阻主要为了防插拔瞬间的静电和过流损坏同时不影响精度因为后续负载通常是高阻抗输入。如果你要驱动长线或者容性负载建议在输出端加一个单位增益运放缓冲。DAC7311 直接驱动大电容负载时可能会出现波形过冲串电阻只能缓解治本的办法还是运放。3. SPI 初始化与命令字例程里最核心的一段STM32F103 的 SPI1 挂在 APB2 总线上初始化代码不复杂但有几处细节直接决定通信成败建议直接照抄例程里的配置。默认时钟配置下APB2 是 72MHz。SPI1 时钟通过预分频得到例程里选择 16 分频也就是 4.5MHz。这个速度对 DAC7311 来说非常安全它最高支持 20MHz 以上的 SCLK所以留足余量。如果后续要用 DMA 刷波形可以尝试把预分频降到 8 或 4但要注意布线长度和信号质量长杜邦线下高频 SPI 容易出问题。void DAC7311_SPI_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_16b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_16; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }关键点有三个。第一SPI_DataSize_16b必须在初始化时设为 16 位因为 DAC7311 的 16 位帧里包含了控制位和数据位如果以 8 位帧发送数据会错位输出值完全乱掉。第二时钟极性和相位要匹配 DAC7311 的采样时序DAC7311 在 SCLK 的下降沿锁存数据CPOLLow、CPHA2Edge 的组合是实践中最常用的配置。如果换了其他 SPI 从设备输出不对时优先检查这两位。第三NSS 必须设置为软件模式否则硬件自动生成片选信号会和 PA4 的软件控制冲突。写一个 16 位命令字到 DAC7311 的流程是SYNC 拉低等 SPI 发送寄存器空写入数据等 SPI 忙标志清除SYNC 拉高。SYNC 拉高这个动作很关键数据实际上是在 SYNC 上升沿被锁存到 DAC 输出寄存器里的忘了拉高则输出永远是上一次的值。void DAC7311_WriteData(uint16_t data) { uint16_t cmd (data 0x0FFF) | 0x1000; GPIO_ResetBits(GPIOA, GPIO_Pin_4); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, cmd); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET); GPIO_SetBits(GPIOA, GPIO_Pin_4); }为什么要| 0x1000因为 DAC7311 的 16 位帧里低 12 位是 DAC 数据高 4 位是控制区。把 bit13 位置 1 是让芯片保持正常输出缓冲模式bit15 和 bit14 保持 0 表示正常工作状态。如果直接发送 raw data 且高位全 0有些版本芯片可能进入特殊模式导致输出异常。我在第一次调试时只发了data 0x0FFF结果 VOUT 始终是 0V后来对照手册重新组帧才定位到问题。这里再补充一个容易疏忽的细节SPI_I2S_SendData的参数是 uint16_t在 16 位数据帧模式下硬件会自动按 MSB 先发把 16 位数据挪出去不需要手动移位。如果你用的是 12 位帧模式才需要手动把数据左移 4 位再发。例程统一使用 16 位帧代码可读性更高后续扩展控制位也方便。4. 电压输出与三种波形这套例程能干什么驱动打通之后第一件事就是验证电压输出是否准确。根据 DAC7311 的特性输出电压与数字码值之间的关系是VOUT (DATA / 4095) × VREF其中 VREF 就是 VDD。如果 VDD3.3V那么 1 LSB 大约是 3.3V / 4095 ≈ 0.806mV。注意12 位 DAC 的最大码值是 4095 而不是 4096这和很多人习惯的“0~4095”计数区间一致换算时不要误用 4096否则满量程会有大约 0.024% 的偏差。虽然这个误差很小但在精确调压场景下还是值得抠一下。例程里封装了一个按电压值直接设置输出的函数调用起来非常直观void DAC7311_SetVoltage(float voltage, float vref) { uint16_t data; if (voltage 0) voltage 0; if (voltage vref) voltage vref; data (uint16_t)(voltage / vref * 4095.0f 0.5f); DAC7311_WriteData(data); }0.5f是为了四舍五入避免浮点转整型时直接截断导致最大 1 LSB 的误差。用万用表实测VDD 为 3.300V 时设置输出 1.650V实测 1.649V误差在 1mV 以内符合 12 位 DAC 的正常精度水平说明电压换算和 SPI 时序都正确。接下来是波形输出。例程里封装了方波、三角波/锯齿波和正弦波三种常见波形足以覆盖信号发生器、灯光线性调光、马达速度曲线模拟等场景。方波最直接循环设置高电平和低电平电压中间加延时即可。真正需要注意的是延时精度如果只用for循环空转波形频率会受编译器优化影响建议用SysTick或定时器来产生延时。例程里用了一个简单的毫秒级延时函数在 72MHz 主频下误差很小。三角波和锯齿波的实现是同一个思路从最小值开始逐码递增到最大值然后递减回最小值。每步之间的延时决定了频率。如果每步发送时间不稳定输出波形看起来就会一顿一顿的。这里我建议用查表法而不是每次都做浮点运算。因为浮点三角函数在 STM32F103 上开销不小会让波形的码值更新率不稳定输出出现抖动。正弦波查表法如下#define SINE_TABLE_SIZE 128 static uint16_t sineTable[SINE_TABLE_SIZE]; void DAC7311_GenSineTable(float vref, float vpp) { uint16_t i; float mid vref / 2.0f; float amp vpp / 2.0f; for (i 0; i SINE_TABLE_SIZE; i) { float vout mid amp * sinf(2.0f * 3.14159265f * i / SINE_TABLE_SIZE); sineTable[i] (uint16_t)(vout / vref * 4095.0f 0.5f); } }表长 128 是一个比较均衡的选择。表太短比如 32 点波形台阶感明显谐波分量大表太长比如 512 点虽然波形平滑但一帧一帧发下来单周期时间太长输出频率上不去。128 点配合 4.5MHz SPI 时钟实测单点发送加软件循环约 6µs 左右一个完整正弦周期约 768µs对应频率约 1.3kHz。如果想输出更高频率的正弦波可以减小表长、提高 SPI 时钟或者直接改用定时器触发 SPI 并通过 DMA 搬运数据这是后续优化的方向。实际运行代码时在 while 循环里依次发送表中数据while (1) { for (uint16_t i 0; i SINE_TABLE_SIZE; i) { DAC7311_WriteData(sineTable[i]); } }用示波器看 VOUT可以看到平滑的正弦波台阶噪声不大。DAC7311 的建立时间在微秒级别完全跟得上这种更新频率。5. 调试过程中踩过的坑从输出不动到波形毛刺驱动代码本身不长真正花时间的是排查各种“看似合理但不工作”的问题。我把这次调试中遇到的四个典型坑整理出来基本按照由静到动的排查顺序遇到同类问题可以直接对照。第一个坑是 SPI 帧长配置错误。项目初期为了兼容另一颗 8 位 DAC我把SPI_DataSize设成了 8 位想着自己手动拼字节发送 16 位数据。结果发现 DAC7311 输出始终是乱的比如写入 0x001 结果输出 0x800逐位对不上。原因在于 DAC7311 在 16 位帧模式下要求一帧内数据完整如果以 8 位帧发送片上移位寄存器会把两次 8 位数据拼接成 16 位但 SYNC 在第一次 8 位传输后就被我拉高了导致数据被提前锁存后 8 位数据完全没有参与转换。解决办法就是老老实实配置 16 位数据帧一次写完一整帧。第二个坑是 SYNC 拉高时机不对。刚开始我参考某些源码写完SPI_I2S_SendData后立刻拉高 SYNC没有等待BSY标志清除。输出偶尔正确、偶尔不变尤其是连续快速写数据时输出值和预期完全对不上。仔细分析发现SPI 发送寄存器只是把数据装进去了移位寄存器可能还在传输过程中这时候拉高 SYNC 会打断数据帧。必须在发送后等待SPI_I2S_FLAG_BSY清零确保所有位都移出再拉高 SYNC 锁存数据。第三个坑是 VDD 纹波导致输出毛刺。调试正弦波时发现 VOUT 波形上叠加了和开关电源频率一致的锯齿状噪声频率大约几十 kHz。把 DAC 的供电从板载 DCDC 切换到线性稳压器供电后噪声立刻消失。这印证了前面提到的问题——DAC7311 的参考电压就是 VDD电源纹波没有经过任何抑制就直接出现在输出端。解决方法是加强去耦并且不要在同一个电源轨上驱动电机、继电器之类的感性负载。第四个坑是上电瞬间输出拉满。DAC7311 在上电复位完成前VOUT 引脚可能短暂输出高电平。如果后端接的是敏感的模拟电路比如音频功放或测量比较器这个毛刺可能造成冲击。最稳妥的方案是在 VOUT 后加一个 RC 滤波或者用运放做输出级同时把 DAC 的电源控制和系统其他电源分开用 MOS 管控制 VDD 上电时机。除了这四个坑还有一个容易被忽略的细节是 SPI 时钟线的信号完整性。用杜邦线连接时SCLK 频率超过几 MHz 后波形会变圆甚至有振铃导致传输偶尔出错。我在例程中默认使用 4.5MHz用短杜邦线测试几百次连续读写都没有出错。如果信号线比较长建议把预分频调大降低时钟频率换取稳定性或者直接用 PCB 排针短接。调试顺序上我建议先用万用表量 VDD 电压再用逻辑分析仪看 SPI 波形确认 DIN、SCLK、SYNC 三条线的逻辑是否符合预期最后才用示波器看 VOUT。逻辑分析仪能直观看到 SYNC 拉高时 DIN 上是否刚好是完整 16 位数据这个信息对排查帧错位问题非常有帮助。没有逻辑分析仪的话可以先用固定电压值测试比如设置 1.65V用万用表观察输出逐步逼近问题点。如果你按照这套流程调完DAC7311 STM32F103 的组合基本就稳定了。后续如果想扩展多通道输出可以把多个 DAC7311 的 DIN 并联共用一个 SCLK每片单独用一根 SYNC 控制这样代码不用大改只需把写驱动里的 GPIO 口换成对应片选即可。这个方案比换双通道 DAC 芯片更灵活通道数量和 PCB 布局都可以按项目需要自由调整。本文还有配套的精品资源点击获取
返回列表