本文目录
文章对应视频教程:
1、引言
这是一个的 LTC2662-16 驱动代码,专门适配 STM32的 HAL 库。
2、配置
cube配置界面

这里spi的速度可以往上调 最高50Mhz,我用不到 所以给了一个低值

其他IO的默认电平

实用的spi驱动配置如下
c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file spi.c
* @brief This file provides code for the configuration
* of the SPI instances.
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "spi.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
SPI_HandleTypeDef hspi2;
/* SPI2 init function */
void MX_SPI2_Init(void)
{
/* USER CODE BEGIN SPI2_Init 0 */
/* USER CODE END SPI2_Init 0 */
/* USER CODE BEGIN SPI2_Init 1 */
/* USER CODE END SPI2_Init 1 */
hspi2.Instance = SPI2;
hspi2.Init.Mode = SPI_MODE_MASTER;
hspi2.Init.Direction = SPI_DIRECTION_2LINES;
hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;
hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi2.Init.NSS = SPI_NSS_SOFT;
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi2.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SPI2_Init 2 */
/* USER CODE END SPI2_Init 2 */
}
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(spiHandle->Instance==SPI2)
{
/* USER CODE BEGIN SPI2_MspInit 0 */
/* USER CODE END SPI2_MspInit 0 */
/* SPI2 clock enable */
__HAL_RCC_SPI2_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**SPI2 GPIO Configuration
PB13 ------> SPI2_SCK
PB14 ------> SPI2_MISO
PB15 ------> SPI2_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN SPI2_MspInit 1 */
/* USER CODE END SPI2_MspInit 1 */
}
}
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{
if(spiHandle->Instance==SPI2)
{
/* USER CODE BEGIN SPI2_MspDeInit 0 */
/* USER CODE END SPI2_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_SPI2_CLK_DISABLE();
/**SPI2 GPIO Configuration
PB13 ------> SPI2_SCK
PB14 ------> SPI2_MISO
PB15 ------> SPI2_MOSI
*/
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15);
/* USER CODE BEGIN SPI2_MspDeInit 1 */
/* USER CODE END SPI2_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
3、代码
ltc2662.h
c
#ifndef LTC2662_H
#define LTC2662_H
#include "main.h"
/* ------------------- 命令定义 (Table 1) ------------------- */
#define LTC2662_CMD_WRITE_N 0x0 // 写入输入寄存器 n
#define LTC2662_CMD_UPDATE_N 0x1 // 更新 DAC 寄存器 n
#define LTC2662_CMD_WRITE_UPDATE_N 0x3 // 写入输入寄存器 n 并更新 DAC n (常用)
#define LTC2662_CMD_POWER_DOWN_N 0x4 // 关闭通道 n
#define LTC2662_CMD_POWER_DOWN_CHIP 0x5 // 关闭整个芯片
#define LTC2662_CMD_WRITE_SPAN_N 0x6 // 设置通道 n 的量程 (必须配置)
#define LTC2662_CMD_CONFIG 0x7 // 配置命令
#define LTC2662_CMD_WRITE_ALL_UPDATE_ALL 0xA // 写入所有并更新所有
#define LTC2662_CMD_NOOP 0xF // 空操作 (用于回读故障位)
#define LTC2662_CMD_WRITE_ALL 0x8 // 写入所有通道的输入寄存器 (不更新输出)
#define LTC2662_CMD_UPDATE_ALL 0x9 // 更新所有通道 DAC 寄存器 (所有通道同时变化)
#define LTC2662_CMD_WRITE_ALL_UPDATE_ALL 0xA // 写入并更新所有通道 (所有通道变成同一个值)
#define LTC2662_CMD_WRITE_SPAN_ALL 0xE // 设置所有通道的量程
/* ------------------- 地址定义 (Table 2) ------------------- */
#define LTC2662_ADDR_DAC0 0x0
#define LTC2662_ADDR_DAC1 0x1
#define LTC2662_ADDR_DAC2 0x2
#define LTC2662_ADDR_DAC3 0x3
#define LTC2662_ADDR_DAC4 0x4
#define LTC2662_ADDR_ALL 0x0 // 配合 All 命令使用时地址通常忽略,但在某些命令中用于占位
/* ------------------- 量程代码 (Table 3) ------------------- */
// 注意:上电默认为 Hi-Z,必须设置 Span 才有输出
#define LTC2662_SPAN_HI_Z 0x0 // 高阻态 (默认)
#define LTC2662_SPAN_3_125mA 0x1 // 0 to 3.125mA
#define LTC2662_SPAN_6_25mA 0x2 // 0 to 6.25mA
#define LTC2662_SPAN_12_5mA 0x3 // 0 to 12.5mA
#define LTC2662_SPAN_25mA 0x4 // 0 to 25mA
#define LTC2662_SPAN_50mA 0x5 // 0 to 50mA
#define LTC2662_SPAN_100mA 0x6 // 0 to 100mA
#define LTC2662_SPAN_200mA 0x7 // 0 to 200mA
#define LTC2662_SPAN_300mA 0xF // 0 to 300mA (注意是 0xF)
#define LTC2662_SPAN_V_NEG 0x8 // 切换到负电源 (Switch to V-)
/* ------------------- 结构体定义 ------------------- */
typedef struct {
SPI_HandleTypeDef *hspi; // SPI 句柄
// CS 引脚
GPIO_TypeDef *CsPort;
uint16_t CsPin;
// LDAC 引脚 (异步更新,如果不使用则保持高电平)
GPIO_TypeDef *LdacPort;
uint16_t LdacPin;
// CLR 引脚 (异步清除,低电平有效)
GPIO_TypeDef *ClrPort;
uint16_t ClrPin;
} LTC2662_HandleTypeDef;
/* ------------------- 函数声明 ------------------- */
void LTC2662_Init(LTC2662_HandleTypeDef *dev);
void LTC2662_Reset(LTC2662_HandleTypeDef *dev);
HAL_StatusTypeDef LTC2662_Write(LTC2662_HandleTypeDef *dev, uint8_t cmd, uint8_t addr, uint16_t data);
HAL_StatusTypeDef LTC2662_SetChannelSpan(LTC2662_HandleTypeDef *dev, uint8_t channel, uint8_t spanCode);
HAL_StatusTypeDef LTC2662_SetChannelValue(LTC2662_HandleTypeDef *dev, uint8_t channel, uint16_t value);
uint8_t LTC2662_ReadFault(LTC2662_HandleTypeDef *dev);
/* 函数声明新增 */
HAL_StatusTypeDef LTC2662_SetAllSpans(LTC2662_HandleTypeDef *dev, uint8_t spanCode);
HAL_StatusTypeDef LTC2662_SetAllValues(LTC2662_HandleTypeDef *dev, uint16_t value);
HAL_StatusTypeDef LTC2662_UpdateAll(LTC2662_HandleTypeDef *dev);
#endif
ltc2662.c
c
#include "ltc2662.h"
/**
* @brief 初始化 LTC2662 控制引脚状态
* @param dev: 设备结构体指针
*/
void LTC2662_Init(LTC2662_HandleTypeDef *dev) {
// 1. 设置 CS 为高 (空闲状态)
HAL_GPIO_WritePin(dev->CsPort, dev->CsPin, GPIO_PIN_SET);
// 2. 设置 LDAC 为高 (禁止异步更新,改用软件命令更新)
if (dev->LdacPort != NULL) {
HAL_GPIO_WritePin(dev->LdacPort, dev->LdacPin, GPIO_PIN_SET);
}
// 3. 设置 CLR 为高 (正常工作模式,低电平会复位芯片)
if (dev->ClrPort != NULL) {
HAL_GPIO_WritePin(dev->ClrPort, dev->ClrPin, GPIO_PIN_SET);
}
// TGP 引脚在主函数 MX_GPIO_Init 中应配置为低电平(如果不使用 Toggle 功能)
}
/**
* @brief 硬件复位芯片 (拉低 CLR 引脚)
*/
void LTC2662_Reset(LTC2662_HandleTypeDef *dev) {
if (dev->ClrPort != NULL) {
HAL_GPIO_WritePin(dev->ClrPort, dev->ClrPin, GPIO_PIN_RESET);
HAL_Delay(1); // 脉宽至少 20ns,1ms 绰绰有余
HAL_GPIO_WritePin(dev->ClrPort, dev->ClrPin, GPIO_PIN_SET);
}
}
/**
* @brief 底层 SPI 发送函数 (24-bit)
* @param cmd: 4-bit 命令
* @param addr: 4-bit 地址
* @param data: 16-bit 数据 (对于 Span 命令,数据位于低位)
*/
HAL_StatusTypeDef LTC2662_Write(LTC2662_HandleTypeDef *dev, uint8_t cmd, uint8_t addr, uint16_t data) {
uint8_t txData[3];
HAL_StatusTypeDef status;
// 组装 24-bit 数据帧
// Byte 1: [C3 C2 C1 C0 A3 A2 A1 A0]
txData[0] = ((cmd & 0x0F) << 4) | (addr & 0x0F);
// Byte 2: Data MSB [D15 ... D8]
txData[1] = (data >> 8) & 0xFF;
// Byte 3: Data LSB [D7 ... D0]
txData[2] = data & 0xFF;
// 拉低 CS 开始传输
HAL_GPIO_WritePin(dev->CsPort, dev->CsPin, GPIO_PIN_RESET);
// 发送 3 字节
status = HAL_SPI_Transmit(dev->hspi, txData, 3, 100);
// 拉高 CS 结束传输,上升沿触发 DAC 执行命令
HAL_GPIO_WritePin(dev->CsPort, dev->CsPin, GPIO_PIN_SET);
return status;
}
/**
* @brief 设置通道量程 (必须在设置电流值之前调用)
* @param channel: 通道地址 (LTC2662_ADDR_DACx)
* @param spanCode: 量程代码 (LTC2662_SPAN_xxx)
*/
HAL_StatusTypeDef LTC2662_SetChannelSpan(LTC2662_HandleTypeDef *dev, uint8_t channel, uint8_t spanCode) {
// 使用 "Write Span to n" 命令 (0110)
// Span Code 位于数据字的低 4 位 (S3 S2 S1 S0)
// 根据数据手册 Table 3 和 Figure 4,数据字的高 12 位是 Don't Care
return LTC2662_Write(dev, LTC2662_CMD_WRITE_SPAN_N, channel, (uint16_t)spanCode);
}
/**
* @brief 设置单通道电流输出值
* @param channel: 通道地址 (LTC2662_ADDR_DACx)
* @param value: 16-bit DAC 数值 (0 - 65535)
*/
HAL_StatusTypeDef LTC2662_SetChannelValue(LTC2662_HandleTypeDef *dev, uint8_t channel, uint16_t value) {
// 使用 "Write Code to n, Update n" 命令 (0011)
// 这将直接改变输出电流
return LTC2662_Write(dev, LTC2662_CMD_WRITE_UPDATE_N, channel, value);
}
/**
* @brief 读取故障寄存器 (Fault Register)
* @return 返回 8-bit 故障状态字节 (FR0-FR7)
* Bit 0-4: 通道 0-4 开路故障
* Bit 5: 过温
* Bit 6: 功率限制
* Bit 7: 无效 SPI 长度
*/
uint8_t LTC2662_ReadFault(LTC2662_HandleTypeDef *dev) {
uint8_t txData[3] = {LTC2662_CMD_NOOP << 4, 0x00, 0x00}; // 发送 NOOP
uint8_t rxData[3] = {0};
// SPI 机制:SDO 会在这一帧传输时,移出上一帧产生的故障状态
// Fault Register 位于 24-bit 回读数据的第一个字节
HAL_GPIO_WritePin(dev->CsPort, dev->CsPin, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(dev->hspi, txData, rxData, 3, 100);
HAL_GPIO_WritePin(dev->CsPort, dev->CsPin, GPIO_PIN_SET);
// 返回第一个字节 (Fault Register)
return rxData[0];
}
/**
* @brief 同时设置所有 5 个通道的量程 (Span)
* @param spanCode: 统一的量程代码 (例如 LTC2662_SPAN_200mA)
*/
HAL_StatusTypeDef LTC2662_SetAllSpans(LTC2662_HandleTypeDef *dev, uint8_t spanCode) {
// 使用命令 0xE: Write Span to All
// 地址位 (A3-A0) 在此命令下会被忽略,填 0 即可
return LTC2662_Write(dev, LTC2662_CMD_WRITE_SPAN_ALL, 0, (uint16_t)spanCode);
}
/**
* @brief 同时将所有 5 个通道设置为同一个数值,并立即输出
* @param value: 16-bit DAC 数值
*/
HAL_StatusTypeDef LTC2662_SetAllValues(LTC2662_HandleTypeDef *dev, uint16_t value) {
// 使用命令 0xA: Write Code to All, Update All
// 所有通道会立即变成这个值
return LTC2662_Write(dev, LTC2662_CMD_WRITE_ALL_UPDATE_ALL, 0, value);
}
/**
* @brief 同步更新所有通道 (触发之前写入的数据)
* @note 配合 "Write Code to n" (cmd 0x0) 使用
*/
HAL_StatusTypeDef LTC2662_UpdateAll(LTC2662_HandleTypeDef *dev) {
// 使用命令 0x9: Update All
// 数据位被忽略,填 0
return LTC2662_Write(dev, LTC2662_CMD_UPDATE_ALL, 0, 0);
}
4、使用
main.c
c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ltc2662.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void Ltc_test(void)
{
LTC2662_HandleTypeDef ltc2662;
// 1. 初始化结构体
ltc2662.hspi = &hspi2;
ltc2662.CsPort = LD_CS_GPIO_Port;
ltc2662.CsPin = LD_CS_Pin;
ltc2662.LdacPort = LD_LDAC_GPIO_Port;
ltc2662.LdacPin = LD_LDAC_Pin;
ltc2662.ClrPort = LD_CLR_GPIO_Port;
ltc2662.ClrPin = LD_CLR_Pin;
// 2. 初始化引脚电平
LTC2662_Init(<c2662);
// 3. 复位芯片 (可选)
LTC2662_Reset(<c2662);
// 4. 配置通道量程 (关键步骤,否则没有输出)
// // 例如:将 DAC0 配置为 0-200mA 范围
// LTC2662_SetChannelSpan(<c2662, LTC2662_ADDR_DAC0, LTC2662_SPAN_100mA);
// // 例如:将 DAC1 配置为 0-300mA 范围
// LTC2662_SetChannelSpan(<c2662, LTC2662_ADDR_DAC1, LTC2662_SPAN_100mA);
// 将所有 5 个通道一次性都配置为 0-200mA 范围
LTC2662_SetAllSpans(<c2662, LTC2662_SPAN_200mA);
// 5. 输出电流
// // DAC0 输出半量程 (100mA, 因为量程是 200mA) -> 32768
// LTC2662_SetChannelValue(<c2662, LTC2662_ADDR_DAC0, 65535);
// // DAC1 输出满量程 (300mA) -> 65535
// LTC2662_SetChannelValue(<c2662, LTC2662_ADDR_DAC1, 65535);
// 所有通道立即输出满量程 (65535)
LTC2662_SetAllValues(<c2662, 65535);
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI2_Init();
/* USER CODE BEGIN 2 */
Ltc_test();
// LTC1622_Initialize();
// Ld_Current_Out(200);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
5、手册指令
P15页

P17页

附件有ltc2662的手册
时间流逝、年龄增长,是自己的磨炼、对知识技术的应用,还有那不变的一颗对嵌入式热爱的心!

到这里就结束了!希望大家给我的文章和B站视频
点赞o( ̄▽ ̄)d、关注(o)/~、评论(▽)!
