一、协议介绍
上篇文章【STM32】基于TPS61165芯片的LED驱动电路-CSDN博客介绍了TPS61165芯片的传统操作(PWM调光和高低电平点亮熄灭)。看到网友留言芯片还可以用单总线控制,便有了这篇文章。

芯片有2种调光模式,一种就是之前使用的PWM调光,还有一种叫做EasyScale 单总线数字调光。
芯片默认是PWM调光,要想进入单总线模式,需要在芯片从shutdown起来后执行以下3个步骤

对应的时序图如下

其中关键的时序参数如下表格所示

可以看出,逻辑0是长低+短高,逻辑1是短低+长高。下降沿开始,下降沿结束。

笔者设置逻辑0是300us的低电平+100us的高电平;逻辑1是100us的低电平+300us的高电平。
us延时函数
cpp
#define CPU_FREQUENCY_MHZ 72 // STM32时钟主频
void delay_us(__IO uint32_t delay) {
int last, curr, val;
int temp;
while (delay != 0) {
temp = delay > 900 ? 900 : delay;
last = SysTick->VAL;
curr = last - CPU_FREQUENCY_MHZ * temp;
if (curr >= 0) {
do {
val = SysTick->VAL;
} while ((val < last) && (val >= curr));
} else {
curr += CPU_FREQUENCY_MHZ * 1000;
do {
val = SysTick->VAL;
} while ((val <= last) || (val > curr));
}
delay -= temp;
}
}
逻辑生成函数
cpp
/* short:100us long:300us */
static void genLogic(uint8_t logic)
{
if(logic == 0)
{
CTRL_LOW();
delay_us(300);
CTRL_HIGH();
delay_us(100);
}
else if(logic == 1)
{
CTRL_LOW();
delay_us(100);
CTRL_HIGH();
delay_us(300);
}
else
{
}
}

先拉低CTRL引脚至少2.5ms让芯片进入shutdown

cpp
void LED_Shutdown()
{
CTRL_LOW();
HAL_Delay(3);
}

拉高CTRL,保持至少100us,再拉低至少260us,拉高CTRL。至此,芯片就成功进入单总线数字调光模式了。从CTRL被拉高开始,至少1ms后(检测延时>100us,检测时间>260us)再发送编程波形。笔者简化处理为CTRL拉高600us,CTRL拉低600us,再拉高
cpp
void LED_Enter()
{
CTRL_HIGH();
delay_us(600);
CTRL_LOW();
delay_us(600);
CTRL_HIGH();
}

一旦设置好亮度,就不能在线修改,需要将芯片先shutdown,唤醒后再发送数据调节亮度。


这里阐述了这个功能的应用场景:有时其实我们并不需要一直给芯片发PWM,可以让芯片通过单总线配置好亮度就进入空闲状态(低功耗/休眠)以节约能源消耗。
单总线数字调光是在总线上发送不同的数据来改变芯片反馈引脚的电压实现调光,有32个挡位,如下表格所示

二、协议实现
数据链路

有点类似I2C总线。地址固定为0x72。一帧信息为起始+设备地址+结束+起始+数据+结束。

发送字节前需要发送Start
cpp
static void writeStart()
{
CTRL_HIGH();
delay_us(50);
}
发送数据
cpp
static void writeData(uint8_t dat)
{
for (uint8_t i = 0; i < 8; i++)
{
genLogic(((dat << i) & 0x80) == 0x80);
}
}
发送完一个字节需要发送EOS
cpp
static void writeEOS()
{
CTRL_LOW();
delay_us(50);
CTRL_HIGH();
}
如果需要响应,切换IO模式
cpp
static void changePinMode()
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pin : CTRL_Pin */
GPIO_InitStruct.Pin = CTRL_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(CTRL_GPIO_Port, &GPIO_InitStruct);
}
static void resumePinMode()
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pin : CTRL_Pin */
GPIO_InitStruct.Pin = CTRL_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(CTRL_GPIO_Port, &GPIO_InitStruct);
}
设置亮度函数
数据字节的最高位用于配置是否要芯片做出应答。
cpp
HAL_StatusTypeDef LED_SetLight(uint8_t step)
{
writeStart();
writeData(0x72);
writeEOS();
writeStart();
#ifdef LED_ACK
writeData(step | 0x80);
writeEOS();
changePinMode();
if (HAL_GPIO_ReadPin(CTRL_GPIO_Port, CTRL_Pin) == GPIO_PIN_RESET)
{
resumePinMode();
return HAL_OK;
}
else
{
resumePinMode();
return HAL_ERROR;
}
#else
writeData(step);
writeEOS();
return HAL_OK;
#endif
}
无需响应的情形

发送完地址的最后一个bit,等待一段时间EOS(小于360us),拉高CTRL(Tstart),开始发送数据字节,发送完毕后等待EOS,总线常高。连接CTRL的单片机引脚配置推挽输出。

使用逻辑分析仪采集波形如下

需响应的情形

数据字节最高位是1,发送完数据字节,等待EOS时间,如果芯片应答,CTRL引脚会被芯片拉低一段时间tACK(<512us),需要在总线配置上拉电阻用于检测CTRL电平判断是否正确应答(拉低)。连接CTRL的单片机引脚配置开漏输出,并在CTRL引脚上拉电阻到3.3V。

发送完数据后,改成输入模式,此时如果芯片正确响应,会把CTRL拉低,让MCU检测到就可以。检测完毕,将GPIO再切换回开漏输出。使用逻辑分析仪采集波形如下

设置好亮度后MCU就可以进低功耗模式。主函数如下
cpp
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int32_t light = 0;
uint8_t str[20];
/* 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_DMA_Init();
MX_SPI2_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
KeyDrv_Config();
ST7789_Init();
ST7789_Clear(BLACK);
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
uint8_t re = 0;
LED_Shutdown();
LED_Enter();
re = LED_SetLight(light % 32);
sprintf(str, "light:%d %d ", light % 32, re);
ST7789_ShowString(0, 140, str, Font_16x26, ST7789_RGB565(0xFFFF), BLACK);
light+=3;
HAL_Delay(1000);
if (light == 30)
{
sprintf(str, "in stop mode");
ST7789_ShowString(0, 140, str, Font_16x26, ST7789_RGB565(0xFFFF), BLACK);
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFE);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
三、驱动附录
LED.c
cpp
#include "LED.h"
#define CPU_FREQUENCY_MHZ 72 // STM32时钟主频
void delay_us(__IO uint32_t delay) {
int last, curr, val;
int temp;
while (delay != 0) {
temp = delay > 900 ? 900 : delay;
last = SysTick->VAL;
curr = last - CPU_FREQUENCY_MHZ * temp;
if (curr >= 0) {
do {
val = SysTick->VAL;
} while ((val < last) && (val >= curr));
} else {
curr += CPU_FREQUENCY_MHZ * 1000;
do {
val = SysTick->VAL;
} while ((val <= last) || (val > curr));
}
delay -= temp;
}
}
/* short:100us long:300us */
static void genLogic(uint8_t logic)
{
if(logic == 0)
{
CTRL_LOW();
delay_us(300);
CTRL_HIGH();
delay_us(100);
}
else if(logic == 1)
{
CTRL_LOW();
delay_us(100);
CTRL_HIGH();
delay_us(300);
}
else
{
}
}
static void writeData(uint8_t dat)
{
for (uint8_t i = 0; i < 8; i++)
{
genLogic(((dat << i) & 0x80) == 0x80);
}
}
static void writeStart()
{
CTRL_HIGH();
delay_us(50);
}
static void changePinMode()
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pin : CTRL_Pin */
GPIO_InitStruct.Pin = CTRL_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(CTRL_GPIO_Port, &GPIO_InitStruct);
}
static void resumePinMode()
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pin : CTRL_Pin */
GPIO_InitStruct.Pin = CTRL_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(CTRL_GPIO_Port, &GPIO_InitStruct);
}
static void writeEOS()
{
CTRL_LOW();
delay_us(50);
CTRL_HIGH();
}
HAL_StatusTypeDef LED_SetLight(uint8_t step)
{
writeStart();
writeData(0x72);
writeEOS();
writeStart();
#ifdef LED_ACK
writeData(step | 0x80);
writeEOS();
changePinMode();
if (HAL_GPIO_ReadPin(CTRL_GPIO_Port, CTRL_Pin) == GPIO_PIN_RESET)
{
resumePinMode();
return HAL_OK;
}
else
{
resumePinMode();
return HAL_ERROR;
}
#else
writeData(step);
writeEOS();
return HAL_OK;
#endif
}
void LED_Shutdown()
{
CTRL_LOW();
HAL_Delay(3);
}
void LED_Enter()
{
CTRL_HIGH();
delay_us(600);
CTRL_LOW();
delay_us(600);
CTRL_HIGH();
}
LED.h
cpp
#ifndef LED_H
#define LED_H
#include "GPIO.h"
#include "main.h"
// #define LED_ACK
#define CTRL_LOW() HAL_GPIO_WritePin(CTRL_GPIO_Port, CTRL_Pin, GPIO_PIN_RESET)
#define CTRL_HIGH() HAL_GPIO_WritePin(CTRL_GPIO_Port, CTRL_Pin, GPIO_PIN_SET)
HAL_StatusTypeDef LED_SetLight(uint8_t step);
void LED_Shutdown();
void LED_Enter();
#endif