STM32F103驱动TPC116S8 DAC芯片:一个SPI接口搞定8通道电压输出(附完整工程) STM32F103驱动TPC116S8 DAC芯片从硬件设计到软件实现的完整指南在工业控制、测试测量和音频处理等领域多通道高精度电压输出是常见需求。TPC116S8作为一款8通道16位DAC芯片通过SPI接口与微控制器通信为嵌入式系统提供了灵活的模拟输出解决方案。本文将基于STM32F103平台深入讲解如何从零开始构建完整的驱动方案。1. 硬件设计与接口分析TPC116S8采用标准3线SPI接口SYNC、SCLK、DIN支持最高30MHz时钟频率。其核心特性包括8通道独立输出每通道16位分辨率宽电压范围支持±10V输出需外部放大器低功耗设计典型工作电流仅2.5mA菊花链连接通过LDAC引脚实现多片同步更新1.1 引脚功能详解引脚名称类型功能描述SYNC输入片选信号低电平有效SCLK输入串行时钟输入DIN输入串行数据输入LDAC输入加载DAC寄存器低电平有效VREF输入参考电压输入(2.5V-5.5V)VOUTx输出模拟电压输出通道(A-H)1.2 STM32硬件连接方案推荐使用STM32F103的硬件SPI接口SPI1或SPI2连接TPC116S8。若需模拟SPI可任意选择GPIO// 硬件连接示例使用SPI1 #define TPC116S8_SPI SPI1 #define TPC116S8_SPI_CLK RCC_APB2Periph_SPI1 #define TPC116S8_GPIO GPIOA #define TPC116S8_GPIO_CLK RCC_APB2Periph_GPIOA #define TPC116S8_PIN_MOSI GPIO_Pin_7 #define TPC116S8_PIN_MISO GPIO_Pin_6 // 未使用但需配置 #define TPC116S8_PIN_SCK GPIO_Pin_5 #define TPC116S8_PIN_SYNC GPIO_Pin_4 // 片选 #define TPC116S8_PIN_LDAC GPIO_Pin_3 // 加载控制2. 底层驱动开发2.1 SPI接口初始化无论使用硬件SPI还是GPIO模拟都需要正确配置时序参数。TPC116S8要求时钟空闲状态为高电平CPOL1数据在时钟下降沿采样CPHA1void TPC116S8_SPI_Init(void) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(TPC116S8_SPI_CLK | TPC116S8_GPIO_CLK, ENABLE); // 配置SPI引脚 GPIO_InitStructure.GPIO_Pin TPC116S8_PIN_SCK | TPC116S8_PIN_MOSI | TPC116S8_PIN_MISO; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(TPC116S8_GPIO, GPIO_InitStructure); // 配置控制引脚 GPIO_InitStructure.GPIO_Pin TPC116S8_PIN_SYNC | TPC116S8_PIN_LDAC; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(TPC116S8_GPIO, GPIO_InitStructure); // SPI参数配置 SPI_InitStructure.SPI_Direction SPI_Direction_1Line_Tx; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_High; SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; // 9MHz 72MHz SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(TPC116S8_SPI, SPI_InitStructure); SPI_Cmd(TPC116S8_SPI, ENABLE); }2.2 数据传输协议解析TPC116S8采用24位数据帧格式[23:20] 无关位可任意 [19:16] 通道选择D19-D16 [15:0] 16位输出数据通道选择编码规则二进制十六进制对应通道00000x0CH000100x2CH101000x4CH2.........11100xECH73. 驱动函数实现3.1 单通道电压设置void TPC116S8_SetChannel(uint8_t channel, uint16_t value) { uint8_t data[3]; // 构造24位数据帧 data[0] 0x00; // 高4位无关 通道选择高4位 data[1] (channel 4); // 通道选择低4位 数据高4位 data[2] (uint8_t)(value 8); // 数据中间8位 data[3] (uint8_t)(value); // 数据低8位 // 拉低SYNC开始传输 GPIO_ResetBits(TPC116S8_GPIO, TPC116S8_PIN_SYNC); // 发送数据 for(uint8_t i0; i4; i) { SPI_I2S_SendData(TPC116S8_SPI, data[i]); while(SPI_I2S_GetFlagStatus(TPC116S8_SPI, SPI_I2S_FLAG_TXE) RESET); } // 拉高SYNC结束传输 GPIO_SetBits(TPC116S8_GPIO, TPC116S8_PIN_SYNC); // 触发LDAC更新输出 GPIO_ResetBits(TPC116S8_GPIO, TPC116S8_PIN_LDAC); DelayUs(1); GPIO_SetBits(TPC116S8_GPIO, TPC116S8_PIN_LDAC); }3.2 多片级联控制当系统需要超过8通道时可通过LDAC引脚实现多片同步更新typedef struct { GPIO_TypeDef* SYNC_Port; uint16_t SYNC_Pin; GPIO_TypeDef* LDAC_Port; uint16_t LDAC_Pin; } TPC116S8_Device; TPC116S8_Device dac_devices[3] { {GPIOA, GPIO_Pin_4, GPIOA, GPIO_Pin_3}, // 设备1 {GPIOB, GPIO_Pin_0, GPIOB, GPIO_Pin_1}, // 设备2 {GPIOC, GPIO_Pin_2, GPIOC, GPIO_Pin_3} // 设备3 }; void TPC116S8_MultiUpdate(uint8_t device_num, uint8_t channel, uint16_t value) { // 选择目标设备 GPIO_TypeDef* sync_port dac_devices[device_num].SYNC_Port; uint16_t sync_pin dac_devices[device_num].SYNC_Pin; // 发送数据同单设备操作 // ... // 仅拉低目标设备的LDAC GPIO_TypeDef* ldac_port dac_devices[device_num].LDAC_Port; uint16_t ldac_pin dac_devices[device_num].LDAC_Pin; GPIO_ResetBits(ldac_port, ldac_pin); DelayUs(1); GPIO_SetBits(ldac_port, ldac_pin); }4. 应用层封装与优化4.1 电压换算函数根据参考电压Vref将目标电压转换为DAC码值uint16_t VoltageToCode(float voltage, float vref) { // 确保电压在0-Vref范围内 voltage (voltage 0) ? 0 : (voltage vref) ? vref : voltage; // 计算16位码值 uint32_t code (uint32_t)((voltage / vref) * 65535.0f); return (uint16_t)(code 0xFFFF); }4.2 批量更新函数void TPC116S8_UpdateAllChannels(uint16_t values[8]) { // 先设置所有通道值 for(uint8_t ch0; ch8; ch) { TPC116S8_SetChannel(ch, values[ch]); } // 最后统一更新输出 GPIO_ResetBits(TPC116S8_GPIO, TPC116S8_PIN_LDAC); DelayUs(1); GPIO_SetBits(TPC116S8_GPIO, TPC116S8_PIN_LDAC); }4.3 输出校准技术为提高输出精度可实施以下校准措施零点校准设置输出码值为0测量实际输出电压Vzero存储偏移量用于补偿满量程校准设置输出码值为0xFFFF测量实际输出电压Vfull计算增益误差typedef struct { float offset; float gain; } DAC_Calibration; DAC_Calibration calib[8]; // 每通道独立校准 void ApplyCalibration(uint8_t channel, uint16_t* code) { float corrected (*code) * calib[channel].gain calib[channel].offset; *code (uint16_t)((corrected 0) ? 0 : (corrected 65535) ? 65535 : corrected); }5. 调试技巧与常见问题5.1 典型问题排查表现象可能原因解决方案无输出电源异常检查VDD和VREF电压输出值不正确SPI时序不匹配调整CPOL/CPHA配置通道间串扰LDAC信号不同步确保所有更新使用同一LDAC脉冲输出噪声大参考电压不稳定增加参考电压滤波电容SPI通信失败片选信号异常检查SYNC信号波形5.2 示波器调试要点SPI信号质量检查确保SCLK频率不超过芯片规格验证数据在时钟下降沿稳定时序参数测量SYNC下降沿到第一个SCLK上升沿的延迟t1SCLK高/低电平时间t2/t3SYNC上升沿后LDAC脉冲宽度t4// 调试用延时调整根据实际硬件调整 #define T1_DELAY() DelayUs(1) // SYNC到SCLK的延迟 #define T4_DELAY() DelayUs(1) // LDAC脉冲宽度6. 工程架构设计建议6.1 驱动模块划分tpc116s8_driver/ ├── inc/ │ ├── tpc116s8.h // 公共接口定义 │ └── tpc116s8_conf.h // 硬件配置 └── src/ ├── tpc116s8.c // 核心驱动实现 └── tpc116s8_io.c // 硬件抽象层6.2 硬件抽象层示例// tpc116s8_io.h typedef struct { void (*SetSYNC)(uint8_t state); void (*SetLDAC)(uint8_t state); uint8_t (*SPI_Transmit)(uint8_t data); } TPC116S8_IO_t; // 用户需实现这些接口 extern TPC116S8_IO_t TPC116S8_IO;这种设计使得驱动代码不依赖于特定硬件平台便于移植到其他MCU。