发作品签到
专业版

ESP32-S3数字电源

工程标签

8.1k
0
0
11

简介

一款基于SC8701 Buck-Boost拓扑、ESP32-S3控制的小型数字电源。

简介:一款基于SC8701 Buck-Boost拓扑、ESP32-S3控制的小型数字电源。
仪器仪表训练营

开源协议

CC BY-NC-SA 4.0

(未经作者授权,禁止转载)
创建时间:2023-10-16 13:35:50更新时间:2023-11-20 06:08:34

描述

零、写在前面

作为一名机械大类学生,在项目过程中调试机电设备往往需要使用到电源。但线性电源、开关电源等在不同方面具有限制,难以适应多样的要求。因此,设计一款小巧的数字电源是计划已久,但从大二到大四,因为种种原因迟迟没能抽出时间实现。本次训练营属实是push了自己一下,把挖了好久的史前大坑给填了。
本项目是第一次独立完成所有相关工作,之前在团队和项目当中主要负责机械与硬件部分工作。可以预见的,项目仍存在很多可改进的地方,尤其是在软件部分。欢迎各位提出相关建议,本项目会作为长期项目持续修改更新(若有大迭代会新建项目)。

0.1 实物图片

TOQQkaHyPqeZc9ct39Dn6ZQjNcH5BVNBzAevBh4i.jpeg 图1 电源正面 1OHEGXJElvFMUrS3arOoX6FS7MOl90apSkH0eBzy.jpeg 图2 电源背面 * 小面板 · 大师造 - 纯手工匠心磨制(有傻子计算忘记算板厚偏移了1.6mm) * 6hvPFE7H5oCSq8rMBQCcCyg4HtvC3Se8nqSjXtig.jpeg 图3 电源爆炸图(怪诶)

摘要

对不起发病了,没有摘要。

一、基本特性

1.1 电气特性

  • 输入电压: 10V ≤ Uin ≤ 24V (10V - SC8701 PG要求)
  • 输出电压: 3V ≤ Uout ≤ 25V (软件限制范围,25V - INA219 VBUS要求)
  • 输出电流: Iout ≤ 5A (工程PCB设计限制)
  • 采样LSB:1LSB - 10mV(对于电压采样)

1.2 使用特性

  • TFT信息显示与按键编码器交互
  • JTAG-串口上位机交互
  • 预留蓝牙与WIFI等功能 待开发

1.3 硬件特性

  • DC-DC拓扑结构:Buck-Boost
  • DC-DC控制器:SC8701QDER
  • MCU:ESP32-S3-WROOM-1U-N16R8(前期调试调得高血压,脑子一热直接上满配S3)
  • 采样&输出放大器:LM358、LMV321、LM321(稍微有点拉但问题不大)
  • 电流采样:INA219(有点问题,下文详说)

二、系统框架

2.1 系统主要框架

hfarpfEl7DcT6CkCdsE3kTMqxaNiOlZCohe945ju.png 图4 系统主要框架示意图

2.2 电源网络框架

GVfJ6a1NPthjGwPLSYulQpsfRXcaF9OuSdQVZvlI.png 图5 电源网络框架示意图

2.3 面板交互逻辑(对于恒压输出)

mqHDZLZclTqGlPBdnwncSNbyr4kXDPuAskip7ycX.png 图6 面板交互逻辑示意图(对于恒压输出)

2.4 串口交互逻辑(对于恒压输出)

7IFPiWmoFllzMUQBLjYBzWd2Y7ONUkxKpfMFUeon.png 图7 串口交互逻辑示意图(对于恒压输出)

三、设计思路

  • 满足常用电压数值的可控输出(3.3V、5V、9V、12V、19V、24V - 因此需满足 3.3V ≤ Uout ≤ 24V )
  • 较为可观的输出能力(设计100W)
  • 能够使用USB Type-C供电(考虑便携性)
    ……(考虑挺多的暂时想到这么多)

3.1 DC-DC拓扑结构设计思路

市场上常见的便携数字电源常用Buck拓扑结构,这限制了输出电压不能大于输入电压(比如某原子)。因此,需要使用具有升降压功能的拓扑结构。
我想到的有两种拓扑结构:SEPIC、Buck-Boost。
对比两种拓扑结构优劣我就不写了,网上能找到很多。选择Buck-Boost而不是SEPIC的主要原因是对SEPIC有心理阴影,之前验证过但没有验证成功。
在本次项目设计开始前,我参考了一些开源工程设计了一款基于SC8701的Buck-Boost方案验证版(后续开源)。在验证通过后给我带来一定的信心,开始投入本项目的硬件设计当中。

关于控制器选型,主要考虑的是成本以及资料丰富程度。
成本:额。。。穷
资料:可参考的资料、开源方案、验证版等能够提供参考借鉴,从而降低设计开发遇到的阻力。
在比较考虑后选择了SC8701。

3.2 框架及MCU选型思路

本项目基于Arduino框架进行ESP32-S3的开发。
为什么选择Arduino和ESP32-S3呢?
主要是菜。之前主要做硬件,软件经验相对薄弱,因此挑软柿子上手了。

3.3 器件选型

由于数年的硬件设计经历(白嫖)攒了不少器件,因此选型主要根据我手里有的器件进行设计。
如果有复刻的朋友可以根据情况酌情替换。

3.4 硬件设计思路

我主要将硬件设计划分为控制、电源、接口、交互四个部分。

  • 控制:以ESP32-S3-WROOM-1U-N16R8为核心的相关硬件设计,分为MCU模块、MCU外围电路、电压&电流采样、输出控制等。
  • 电源:主要分为SC8701为核心的Buck-Boost拓扑结构设计,实现主输入输出的DC-DC变换;供电保障 - 10V、5V、3.3V,采用Buck DC-DC与LDO共同实现。
  • 接口:电源输入(DC、Type-C);调试(JTAG Type-C、Uart 排针);主板(main)与交互板(interaction)的连接(采用PCIE x1,以锡手指连接)。
  • 交互:显示(TFT ST7735 0.85);按键(轻触开关);编码器(EC11)

并根据以上划分进行模块化设计。
相对比较简单,在这里着重提一下电压采样电路和输出控制电路的设计。

3.4.1 电压采样电路

YWTJyqhfMUNPFDByaWQekcKzu4YKd82wr2NTZwVn.png 图8 电压采样电路原理图

这里我采用的是片上ADC与运放电路的组合,其中采样运放电路着重做量程标度变换的处理。
ESP32-S3的片上共有2个12bit ADC合计10各通道,参考电压为3.3V,采样值为0~4095。
为了使采样值更为直观且在采样值与实际电压建立更为明确关系,这里需要做标度变换。如下式:
WprOQQa7rOj8ZEV3lAZtvBwMk3G9qCT8hlqNGBKT.png

在考虑DC-DC电源输出、控制精度及可行性等,我将标度变换为 04095 - 040.95V,即每LSB对应10mV。
为降低成本,我采用E24系列电阻配置运放放大系数,并考虑运放输入两端阻抗匹配因素进行求解。
我采用遗传算法进行多目标优化求解,以放大系数误差绝对值为主要优化目标、阻抗偏差绝对值为次要优化目标构造适应度函数。得到结果如下:
PUy0g096VEWYEnov7ddJ1GcDkOG0Zc7TFJbcO0Ct.jpeg
图9 电压采样电路配置电阻优化求解

  • R1 = 18K; R2 = 1.3K; R3 = 16K; R4 = 1.3K
  • 放大系数偏差:7.0199e-05
  • 输入匹配偏差:0.0195

至此完成电压采样电路的参数设计。(程序见附件)

3.4.2 输出控制电路

SSLg1o1kJpQWyXWfYXeHFdjrGSmmuxjRFVIHnq95.png 图10 输出控制电路原理图

我采用电阻网络控制输出电压,控制端输入电压将会与输出电压成一阶线性反比。
mQbk6loTU5jyaGLA1BAIi6F8nh9cyLVZqHJvBrhU.png
图11 电阻网络控制(@BV1oC4y1V7k1)

本来是想使用片上DAC进行输出控制。但是!ESP32-S3并没有DAC(ESP32 → ESP32-S3 反向升级)。
因此改用PWM输出配合二阶低通滤波器模拟DAC进行控制。

反馈电阻网络各组成电阻的阻值共同确定输出电压范围。
如3供电范围分析及器件限制推得:2V ≤ Uout ≤ 26V
同样,以输出电压偏差量绝对值构造适应度函数,并采用遗传算法进行优化求解。得到结果如下:
ZzcI4fy79D3slvXazYhG0RtK1skoGSjSSjsLdkMo.jpeg
图12 输出电阻网络配置优化求解

  • R1 = 13K; R2 = 1K; R3 = 1.8K
  • Uout_max = 25.3911V
  • Uout_min = 2.1667V

至此完成输出控制电路的参数设计。(程序见附件)

四、ESP32-S3程序编写

ESP32-S3主要需要实现以下功能:

  • 电压电流采样
  • 输出电压控制
  • 输出控制
  • 本机交互
  • 上位机交互

4.0 开发环境

本项目程序基于VSCode PlatformIO,Arduino框架开发。
a9T5z0oR6ps6iUp8pklphZI0IMCfhkiGnP8eaKkq.png
图13 开发环境

4.1 电压电流采样程序

4.1.1 电压采样

对于电压采样较为简单,只需要使用analogread()函数读取对应ADC引脚的值。

4.1.2 电流采样

电流采样方面需要使用I2C总线与INA219进行通信,在Arduino当中需要调用Wire.h库或其他第三方库(封装好)。
我共尝试了Adafruit_INA219.h库、INA226.h库、Wire.h库直接读取三种方法。但很可惜,读取的数值不太正常,疑似INA219损坏暂时没有实现电流采样功能。

4.2 输出电压控制

输出电压控制也相对简单,只需要使用analogwrite()函数操控指定IO发出PWM波,范围为0 - 255(够用了,实际操控分度值约为10mV,与检测LSB对应电压相似)。
将设定的目标输出电压UoutGoal按照 图11 中关系换算为PWM控制值,并控制PWM波占空比,即可实现输出电压控制。

4.3 本机交互

本机交互包含了按键交互、编码器交互与屏幕显示。

4.3.1 按键交互

我使用了Onebutton.h库对按键功能进行调用。该库可以设置按键单击、双击、长按等事件。

4.3.2 编码器交互

我使用了Encoder.h库读取编码器数值,通过比较当前数值与上一时刻数值可以判断编码器的左旋与右旋,并对相应数值做出调整。
注意,我尝试将Encoder读取编码器位置的函数放置于定时器事件中执行,但读取信息非常诡异,几乎只增不减。当我将函数放置于void loop()主循环内执行时,功能与读取信息正常(原因未知)。

4.3.3 屏幕显示

使用TFT_eSPI.h库驱动ST7735屏幕进行显示。

4.4 上位机交互

目前完成了串口通信上位机交互。分为上传数据和接受控制两个部分。
通过Serial.println()将输出电压信息上传给上位机;通过Serial.read()读取上位机发出的指令,并作出对应的控制或调整。

4.5 定时器事件

其中,大部分的任务需要定时完成。例如:数据获取、输出控制(PID控制)、串口上传、TFT刷新等。
这里我使用了两个定时器(1,2),其中定时器1用于数据获取、输出控制(PID控制)、串口上传;定时器2用于TFT刷新。

4.6 其他

在编程过程中,不可避免会使用到小数进行运算,这时候需要使用float等对变量进行定义。
但当我使用float定义变量时,程序一运行到使用float变量环境就会重启。改用double对变量进行定义时则功能正常(原因未知)。

五、调试

来了来了,最高血压的环节他来了。
调试到极其烦躁,本次项目差点让我对ESP系列粉转黑。

5.1 Flash/PSRAM

说实话,我对这玩意挺无感的,到底能带来多少优化和提升我也不知道。
但是!它额外占用IO!
og3ACniSDGrwAsFgRhuKRDZXCHqQqglUFHnquBif.png
图14 PSRAM管脚对应表[4]

如图13,八线SPI驱动的Flash/PSRAM会额外占用GPIO33-37。
在设计时我使用了其中几根引脚用于驱动ST7735,并且启用了PSRAM功能。
这时候高血压来了,每次启动时就是ST7735 SPI和PSRAM在抢GPIO,能不能正常启动完全看脸,复位一次可能就寄。
后面重读了一遍datasheet发现了该问题,关闭PSRAM功能后解决。(所以说xdm,datasheet全文背诵!)
以上问题主要还是我对于ESP32-S3相关认识还不够清晰,所以希望各位引以为戒,注意datasheet中提到的重要细节。

5.2 ADC校准

当完成主要功能程序的编写后,开始对输出进行测试。这时,我发现输出/输入的电压与万用表、电流表的值不一致。
因此我对测量值与输出值进行比较,得到结果如下:
fyEAYPIDmt1wfpfo323DUtSEA6LfV6VkhwejTWRY.png
图15 输出值-测量值比较(校准前)

如上图所示,可以看出实际电压测量值与输出值间两条线斜率不同、高度不同,存在斜率与截距的偏差。
可以很容易看出,误差值主要呈一阶线性分布。
因此,我定义CorrectUout(斜率校准)、UoutOffest(截距校准)对ADC进行校准。
我将测量得到的数值导入到Matlab当中,并将实际控制量、实际测量值做一阶线性拟合。得到:
qzvqJLNOCf7MAatZ4ern4vlNZnydpYPX9cSWNnGF.jpeg
图16 线性拟合结果

  • CorrectUout = 1.0384
  • UoutOffest = 50.8339

并将以上结果带入程序中对ADC进行校准。
rYyhtZHoHU2vtKnjqRmI6q8ddLFyw01NgYnDpJSs.png
图17 输出值-测量值比较(校准后)

如上图,可以看出校准后实际控制量与实际测量值一致性良好,偏差量较小,可能由测量误差或控制误差带来。

推测斜率差值来自采样运放电路的放大误差,可能由电阻误差等带来;
截距误差来自片上ADC自身基准漂移(猜测)。

以上完成ADC校准,校准后效果良好。(程序及文档见附件)

5.3 PID参数整定

输出电压准确性、电源纹波等在很大程度上会收到MCU控制的影响,因此PID的参数是否合适会显著影响电源输出质量。
PbAwIdbQnTK5CMtbtYRmIsFUufk2yx3KP9l53FwG.png
图18 PID参数整定调试

目前PID参数设置还存在一定问题,例如:存在静态误差、超调量较大、振荡等。
因此仍需进一步整定参数。

六、性能测试

6.1 转换率测试

为测试电源性能,我使用电子负载进行测试。
anGFpmuo9lE3Pj2gmE1xkHbMMqgnZIpBEXtNukJE.jpeg
图19 性能测试环境

  • 村 · 好 · 仪 - 刷脸从老师那借来的负载 *

输入条件:努比亚GaN氮化镓单口65W充电器 @20V 3.25A max
输入信息:炬为安全充电多功能检测仪(USB电流电压表)
输出信息:电子负载

测试结果如下所示:
表1 测试结果
5RH563NdRtp3FzbEGUs5tufPz8MlkxGk3vUM6F2u.png

如上表所示,电源在大电流负载条件下转换率较低,推测MOS/电感内阻较大。

6.2 纹波测试

使用示波器交流耦合档测试,测得以下结果:
346AvF8nUZnhxhfFvtwqssMnDC2WhmAt8a4QykhR.jpeg
图20 纹波测试结果

emmm还行?但总感觉怪怪的。

*以上结果不完全准确,仅供参考

七、电源装配

IMG_20231119_082501.jpg
图21 电源装配图1(使用3mm导热垫将MOS、芯片热量导到外壳)

IMG_20231119_082908.jpg
图22 电源装配图2(增加14x14x5散热片辅助电感、MCU模块散热)

IMG_20231119_075419.jpg
图23 电源装配图3(使用M2.5x6螺丝固定前后面板)

IMG_20231119_193838.jpg
图24 电源装配图4

八、电源效果图

前面板功能介绍.png
图25 电源效果图1(前面板)

IMG_20231119_091134.jpg
图26 电源效果图2(后面板 - 工程中面板已修正孔位偏差)

显示及输出状态介绍.png
图27 电源效果图2(输出状态:等待 | 输出 | 输入错误)

IMG_20231119_105933.jpg
图28 电源效果图4(使用4mm香蕉头连接输出端)

参考文献

附录

I.源代码(工程见附件压缩包)

#include 
#include 
#include 
#include 
#include 
#include 
#define ENCODER_OPTIMIZE_INTERRUPTS 
#include 

// 引脚定义
#define CeCtrl 17                 // SC8701使能控制引脚 1 无效 - 0 有效
#define FbCtrl 18                 // 电压反馈控制网络引脚
#define OutCtrl 13                // 输出MOS控制引脚  1 有效 - 0 无效
#define UinADC 5                  // 输入电压采样ADC引脚
#define UoutADC 6                 // 输出电压采样ADC引脚
#define UoutpADC 9                // 终端输出电压采样ADC引脚
#define I2C_SDA 11                // I2C SDA
#define I2C_SCL 12                // I2C SCL
#define Key1 48                   // 编码器按键
#define KeyA 47                   // 编码器A端
#define KeyB 21                   // 编码器B端
#define Key2 14                   // 输出控制按键

// 变量定义
double UinOffset=137.3825;                  // 输入采样偏置量
double correctUin=1.0248;            // 输入电压采样修正系数
double UoutOffset=50.8339;                 // 输出采样偏置量
double correctUout=1.0384;         // 输出电压采样修正系数
double UoutpOffset=0;                 // 终端输出采样偏置量
double correctUoutp=0;         // 终端输出电压采样修正系数
uint16_t Uin=0;                   // 当前输入电压采样值0-4096 ~ 0-40.96V
uint16_t Uout=0;                  // 当前输出电压采样值0-4096 ~ 0-40.96V
uint16_t Uoutp=0;                 // 当前终端输出电压采样值0-4096 ~ 0-40.96V
uint16_t Iout=0;                  // 当前输出电流采样值
uint16_t UoutINA219=0;            // INA219采样电压值
uint16_t IoutINA219=0;            // INA219采样电流值
uint16_t PoutINA219=0;            // INA219功率计算值
const unsigned char sampleTimes=4;       // 均值采样次数
uint16_t UoutTemp[sampleTimes];   // 输出电压采样队列
uint16_t IoutTemp[sampleTimes];   // 输出电压采样队列
int UoutError0=0;                   // 当前时刻输出电压误差
int IoutError0=0;                   // 当前时刻输出电流误差
int UoutError1=0;                   // 前一时刻输出电压误差
int IoutError1=0;                   // 前一时刻输出电流误差
int UoutError2=0;                   // 前一时刻输出电压误差
int IoutError2=0;                   // 前一时刻输出电流误差
uint16_t UoutGoal=500;            // 输出电压目标 - 默认5V
uint16_t IoutLim=5000;            // 输出电流限制 - 默认5A
unsigned char UoutCtrlValue;      // 输出电压反馈控制量
boolean SelectStatues=0;          // 选中状态
boolean OutputStaus=0;            // 输出状态标志
boolean InError=0;                // 电源输入状态
uint16_t Ts=200;                  // 采样、上传、控制周期设置
unsigned char FPS=16;             // TFT帧率

// TFT
TFT_eSPI tft = TFT_eSPI();

// Encoder
Encoder Enc(KeyA, KeyB);
long oldPosition=-999;
long newPosition=0;

void EncoderRead(){
  newPosition = Enc.read();
  if(newPosition != oldPosition){
    if(SelectStatues){
      if(newPosition > oldPosition){
        UoutGoal+=50;
      }
      else{
        UoutGoal-=50;
      }
    }
    oldPosition=newPosition;
  }

  if(UoutGoal>=2500){
    UoutGoal=2500;
  }
  else if(UoutGoal<=300){
    UoutGoal=300;
  }
}

// INA219
uint8_t addressINA219=0x80;          // INA219地址
Adafruit_INA219 INA219(addressINA219);

void dataGetINA219(){
  UoutINA219=INA219.getBusVoltage_V()*100;
  Iout=INA219.getCurrent_mA()/10;
}

// uint16_t readRegister(uint8_t reg)
// {
//   Wire.beginTransmission(addressINA219);
//   Wire.write(reg);
//   Wire.endTransmission();

//   Wire.requestFrom(addressINA219, (uint8_t)2);
//   uint16_t value = Wire.read();
//   value <<= 8;
//   value |= Wire.read();
//   return value;
// }

// uint16_t writeRegister(uint8_t reg, uint16_t value)
// {
//   Wire.beginTransmission(addressINA219);
//   Wire.write(reg);
//   Wire.write(value >> 8);
//   Wire.write(value & 0xFF);
//   return Wire.endTransmission();
// }

// void initINA219(){
//   // 寄存器00设置 - 0x2C47 - 0010 1100 0100 0111
//   uint16_t Reg00=0x2C47;
//   writeRegister(00,Reg00);
//   // 寄存器05设置 - 0x4000 - MAX 8A & I_LSB 0.00025A & P_LSB 0.005W
//   uint16_t Reg05=0x4000;

//   /*
//   00寄存器bit13:设置检测最大检测电压 0=16V,1=32V
//   00寄存器bit11-12:设置总线分流电阻最大的电压
//   00寄存器bit0-2:设置工作模式
//   05寄存器:设置基准值
//   */

// }

// void dataGetINA219(){
//   UoutINA219=readRegister(0x02)*4/10;
//   //PoutINA219=readRegister(0x03);
//   //IoutINA219=readRegister(0x04);
// }

// Onebutton
OneButton button1(Key1, true);
OneButton button2(Key2, true);
  // 编码器按键单击 - 选中UoutGoal编辑
void button1click1(){
  SelectStatues=!SelectStatues;
}
  // 编码器按键双击 - UoutGoal自增50mV,超过2500变为300
void button1click2(){
  UoutGoal+=50;
  if(UoutGoal>=2550){
    UoutGoal=300;
  }
}
  // 编码器长按 - UoutGoal自增10mV,超过2500变为300
void button1LongPress(){
  UoutGoal+=10;
  if(UoutGoal>=2510){
    UoutGoal=300;
  }
}
  // 按键单机 - 输出开关
void button2click1(){
  if(Uin>=950){
    InError=0;
    OutputStaus=!OutputStaus;
    digitalWrite(OutCtrl,OutputStaus);
  }
  else{
    InError=1;
  }
}

// 数据刷新
uint16_t UoutTemp_t;
uint16_t IoutTemp_t;

unsigned char times=0;              // 采样次数计数器
void dataGet(){
  UoutTemp_t=0;
  IoutTemp_t=0;
  
  Uin=analogRead(UinADC)*correctUin+UinOffset;
  if(Uin<= (UinOffset+1) ){
    Uin=0;
  }
  //Uoutp=analogRead(UoutpADC)*correctUoutp+UoutpOffset;

  //dataGetINA219();
  //UoutINA219=INA.getBusVoltage();

  if(times<=sampleTimes-1){
    UoutTemp[times]=uint16_t( analogRead(UoutADC)*correctUout+UoutOffset );
    //UoutTemp[times]=INA219.getBusVoltage_V();
    //IoutTemp[times]=INA219.getCurrent_mA();
    times++;
  }
  else{
    for(unsigned char i=0;i<=sampleTimes-2;i++){
      UoutTemp[i]=UoutTemp[i+1];
      IoutTemp[i]=IoutTemp[i+1];
    }
    UoutTemp[sampleTimes-1]=uint16_t( analogRead(UoutADC)*correctUout+UoutOffset );
    //IoutTemp[sampleTimes-1]=INA219.getCurrent_mA();
  }
  for(unsigned i=0;i<=times;i++){
  UoutTemp_t+=UoutTemp[i];
  IoutTemp_t+=IoutTemp[i];
  }

  // 记录当前数据
  Uout=UoutTemp_t/times;
  if(Uout<= (UoutOffset+1) ){
    Uout=0;
  }
  Iout=IoutTemp_t/times;
}

// 数据上传
String message;
void dataUpload(){
  message = String(Uin) + "#" + String(Uout) + "#" + String(Iout);     
  // 格式:0000#0000#0000;输入电压10mV#输出电压10mV#输出电流10mA
  Serial.println(message);
}

// 数据接收
String inputString = "";
boolean stringComplete;
void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag so the main loop can
    // do something about it:
    if (inChar == '\n') {
      stringComplete = true;
    }
    if (stringComplete) {
      if(inputString=="CTRL\n"){
        button2click1();
      }
      else{
        if(inputString.toInt() >= 300 | inputString.toInt() <= 2500){
          UoutGoal=inputString.toInt();     // 串口电压设置
        }
      }
      // clear the string:
      inputString = "";
      stringComplete = false;
    }
  }
}

// PID控制
boolean outputMode=0;         // 输出模式 0 - 恒压控制;1 - 恒流控制
uint16_t UoutCtrlValueTemp=UoutGoal;

double Kp=0.1, Ti=125, Td=5;                 // 恒压PID参数设置
double KIp=1, TIi=1, TId=1;              // 恒流PID参数设置
int Pterm=0, Itrem=0, Dterm=0;
void PidCtrl(){
  // 增量式PID
  Pterm=(UoutError0 - UoutError1);
  Itrem=UoutError0;
  Dterm=( UoutError0 - 2*UoutError1 +UoutError2);
  UoutCtrlValueTemp=UoutCtrlValueTemp + Kp*( Pterm + Ts/Ti*Itrem + Td/Ts*Dterm );

  if(UoutCtrlValueTemp>2540){
    UoutCtrlValueTemp=2540;
  }
  else if(UoutCtrlValueTemp<220){
    UoutCtrlValueTemp=220;
  }
  // Serial.println(UoutCtrlValueTemp);
  UoutCtrlValue=255-(UoutCtrlValueTemp-220)*0.11;
  //Serial.println(UoutCtrlValue);
  analogWrite(FbCtrl,UoutCtrlValue);

  // 记录误差
  UoutError2=UoutError1;
  IoutError2=IoutError1;
  UoutError1=UoutError0;
  IoutError1=IoutError0;
  UoutError0=UoutGoal-Uout;
  IoutError0=IoutLim-Iout;
}

// 定时器设置
hw_timer_t * timer1 = NULL;       // 定时器1 采样、上传、控制刷新
hw_timer_t * timer2 = NULL;       // 定时器2 屏幕刷新

  // 定时器1 采样、上传、控制刷新
void IRAM_ATTR onTimer1(){
  dataGet();                      // 数据采样
  serialEvent();                  // 串口接收
  if(OutputStaus){
    PidCtrl();                    // PID控制
  }
  
  if(Serial){
    //dataUpload();                 // 数据上传
    Serial.println(Uout);
    //Serial.println(Uoutp);
    //Serial.println(UoutINA219);
    //Serial.println(newPosition);
  }
}

  // TFT刷新
const unsigned char FirstLinePos=10;  // 首行文字行坐标
const unsigned char LineSpace=25;     // 文字行间距
void IRAM_ATTR onTimer2(){
  //tft.fillScreen(TFT_BLACK);                             //屏幕全黑
  //tft.setTextColor(TFT_WHITE,TFT_BLACK);                 //将字体颜色设置为白色,背景为黑色,将文本大小倍增设置为1
  //tft.setTextSize(2);                                    //字体大小
  // tft.setCursor(10, 10, 2);                              //将“光标”设置在显示器的左上角(0,0),并选择2号字体
  // tft.println(times);

  tft.fillRect(56, 5, 120, 100, TFT_BLACK);               // 数值清除
  tft.setTextColor(TFT_WHITE,TFT_BLACK);                  // 设置文字颜色
  // Uin
  tft.setCursor(60, FirstLinePos);
  if(Uin>=1000){
    tft.println(Uin/100);
    tft.setTextSize(1);
    tft.setCursor(82, FirstLinePos+5);
    tft.println(".");
    tft.setCursor(87, FirstLinePos+5);
    if( (Uin%100) >=10){
      tft.println(Uin%100);
    }
    else{
      tft.println("0");
      tft.setCursor(93, FirstLinePos+5);
      tft.println(Uin%10);
    }
    tft.setTextSize(2);
    tft.setCursor(102, FirstLinePos);
    tft.println("V");
  }
  else{
    if(Uin>=100){
      tft.println(Uin/100);
    }
    else{
      tft.println("0");
    }
    tft.setTextSize(1);
    tft.setCursor(70, FirstLinePos+5);
    tft.println(".");
    tft.setCursor(75, FirstLinePos+5);
    if( (Uin%100) >=10){
      tft.println(Uin%100);
    }
    else{
      tft.println("0");
      tft.setCursor(81, FirstLinePos+5);
      tft.println(Uin%10);
    }
    tft.setTextSize(2);
    tft.setCursor(90, FirstLinePos);
    tft.println("V");
  }
  // Uout
  tft.setCursor(60, FirstLinePos+LineSpace);
  if(Uout>=1000){
    tft.println(Uout/100);
    tft.setTextSize(1);
    tft.setCursor(82, FirstLinePos+LineSpace+5);
    tft.println(".");
    tft.setCursor(87, FirstLinePos+LineSpace+5);
    if( (Uout%100) >=10){
      tft.println(Uout%100);
    }
    else{
      tft.println("0");
      tft.setCursor(93, FirstLinePos+LineSpace+5);
      tft.println(Uout%10);
    }
    tft.setTextSize(2);
    tft.setCursor(102, FirstLinePos+LineSpace);
    tft.println("V");
  }
  else{
    if(Uout>=100){
      tft.println(Uout/100);
    }
    else{
      tft.println("0");
    }
    tft.setTextSize(1);
    tft.setCursor(70, FirstLinePos+LineSpace+5);
    tft.println(".");
    tft.setCursor(75, FirstLinePos+LineSpace+5);
    if( (Uout%100) >=10){
      tft.println(Uout%100);
    }
    else{
      tft.println("0");
      tft.setCursor(81, FirstLinePos+LineSpace+5);
      tft.println(Uout%10);
    }
    tft.setTextSize(2);
    tft.setCursor(90, FirstLinePos+LineSpace);
    tft.println("V");
  }
  // Iout
  tft.setCursor(60, FirstLinePos+2*LineSpace);
  if(Iout>=1000){
    tft.println(Iout/100);
    tft.setTextSize(1);
    tft.setCursor(82, FirstLinePos+2*LineSpace+5);
    tft.println(".");
    tft.setCursor(87, FirstLinePos+2*LineSpace+5);
    if( (Iout%100) >=10){
      tft.println(Iout%100);
    }
    else{
      tft.println("0");
      tft.setCursor(93, FirstLinePos+2*LineSpace+5);
      tft.println(Iout%10);
    }
    tft.setTextSize(2);
    tft.setCursor(102, FirstLinePos+2*LineSpace);
    tft.println("A");
  }
  else{
    if(Iout>=100){
      tft.println(Iout/100);
    }
    else{
      tft.println("0");
    }
    tft.setTextSize(1);
    tft.setCursor(70, FirstLinePos+2*LineSpace+5);
    tft.println(".");
    tft.setCursor(75, FirstLinePos+2*LineSpace+5);
    if( (Iout%100) >=10){
      tft.println(Iout%100);
    }
    else{
      tft.println("0");
      tft.setCursor(81, FirstLinePos+2*LineSpace+5);
      tft.println(Iout%10);
    }
    tft.setTextSize(2);
    tft.setCursor(90, FirstLinePos+2*LineSpace);
    tft.println("A");
  }
  // UoutGoal
  tft.setCursor(60, FirstLinePos+3*LineSpace);
  tft.setTextColor(TFT_ORANGE,TFT_BLACK);
  if(UoutGoal>=1000){
    tft.println(UoutGoal/100);
    tft.setTextSize(1);
    tft.setCursor(82, FirstLinePos+3*LineSpace+5);
    tft.println(".");
    tft.setCursor(87, FirstLinePos+3*LineSpace+5);
    if( (UoutGoal%100) >=10){
      tft.println(UoutGoal%100);
    }
    else{
      tft.println("0");
      tft.setCursor(93, FirstLinePos+3*LineSpace+5);
      tft.println(UoutGoal%10);
    }
    tft.setTextSize(2);
    tft.setCursor(102, FirstLinePos+3*LineSpace);
    tft.println("V");
  }
  else{
    if(UoutGoal>=100){
      tft.println(UoutGoal/100);
    }
    else{
      tft.println("0");
    }
    tft.setTextSize(1);
    tft.setCursor(70, FirstLinePos+3*LineSpace+5);
    tft.println(".");
    tft.setCursor(75, FirstLinePos+3*LineSpace+5);
    if( (UoutGoal%100) >=10){
      tft.println(UoutGoal%100);
    }
    else{
      tft.println("0");
      tft.setCursor(81, FirstLinePos+3*LineSpace+5);
      tft.println(UoutGoal%10);
    }
    tft.setTextSize(2);
    tft.setCursor(90, FirstLinePos+3*LineSpace);
    tft.println("V");
  }
  // OutStatus
  tft.fillRect(77, 105, 128, 128, TFT_BLACK);                   // 输出状态清除
  if(InError){
    tft.setCursor(78, FirstLinePos+4*LineSpace);
    tft.setTextColor(TFT_YELLOW,TFT_BLACK);
    tft.println("InEr");
  }
  else{
    if(OutputStaus){
      tft.setCursor(83, FirstLinePos+4*LineSpace);
      tft.setTextColor(TFT_YELLOW,TFT_BLACK);
      tft.println("OUT");
    }
    else{
      tft.setCursor(78, FirstLinePos+4*LineSpace);
      tft.setTextColor(TFT_BLUE,TFT_BLACK);
      tft.println("WAIT");
    }
  }
  // SelectStatus
  if(SelectStatues){
    if(UoutGoal/1000){
      tft.fillRect(58, FirstLinePos+3*LineSpace+15, 55, 3, TFT_ORANGE);
    }
    else{
      tft.fillRect(58, FirstLinePos+3*LineSpace+15, 45, 3, TFT_ORANGE);
    }
  }
}

void setup() {
  // put your setup code here, to run once:

  // 串口初始化
  Serial.begin(115200);
  delay(500);

  // TFT初始化
  tft.init(); //初始化
  delay(500);
  tft.setRotation(1);                                    // 屏幕方向 - 旋转270°
  tft.fillScreen(TFT_BLACK);                             // 屏幕全黑
  tft.setTextColor(TFT_WHITE,TFT_BLACK);                 // 将字体颜色设置为白色,背景为黑色,将文本大小倍增设置为1
  tft.setTextSize(2);                                    // 字体大小
  tft.setCursor(5, FirstLinePos);
  tft.println("Uin");
  tft.setCursor(5, FirstLinePos+LineSpace);
  tft.println("Uout");
  tft.setCursor(5, FirstLinePos+2*LineSpace);
  tft.println("Iout");
  tft.setCursor(5, FirstLinePos+3*LineSpace);
  tft.println("Uset");
  tft.setCursor(5, FirstLinePos+4*LineSpace);
  tft.println("Status");

  // 引脚初始化
  pinMode(CeCtrl,OUTPUT);                       // SC8701使能引脚模式设置 - 输出
  digitalWrite(CeCtrl,0);                       // SC8701使能设置 - 有效
  pinMode(OutCtrl,OUTPUT);                      // 输出控制引脚模式设置 - 输出
  digitalWrite(OutCtrl,0);                      // 默认输出状态 - 不输出
  pinMode(FbCtrl,OutCtrl);                      // 电压反馈控制网络引脚模式设置 - 输出
  UoutCtrlValue=255-(UoutGoal-250)*0.1;

  // INA219初始化
  Wire.begin(I2C_SDA, I2C_SCL);
  delay(500);
  //initINA219();
  Wire.begin();
  if (! INA219.begin()){
    // while (1){
    //   Serial.println("could not connect. Fix and Reboot");
    //   delay(1000);
    // }
    Serial.println("could not connect. Fix and Reboot");
  }

  // Onebutton初始化
  button1.attachClick(button1click1);
  button1.attachDoubleClick(button1click2);
  button1.attachLongPressStart(button1LongPress);
  button2.attachClick(button2click1);
  
  // 定时器初始化
    // 定时器1
  timer1 = timerBegin(1,80,true);               // 初始化定时器-使用定时器1
  timerAttachInterrupt(timer1,onTimer1,true);   // 绑定定时器中断服务函数
  timerAlarmWrite(timer1,Ts*1000,true);         // 设置中断 间隔为采样周期
  timerAlarmEnable(timer1);                     // 启动定时器
    // 定时器2
  timer2 = timerBegin(2,80,true);               // 初始化定时器-使用定时器2
  timerAttachInterrupt(timer2,onTimer2,true);   // 绑定定时器中断服务函数
  timerAlarmWrite(timer2,1000000/FPS,true);     // 设置中断
  timerAlarmEnable(timer2);                     // 启动定时器
}

void loop() {
  // put your main code here, to run repeatedly:
  button1.tick();
  button2.tick();
  dataGetINA219();
  EncoderRead();                  // 编码器读取
}

设计图

未生成预览图,请在编辑器重新保存一次

BOM

暂无BOM

附件

序号文件名称下载次数
1
ADCcorrect.m
67
2
ADCresistance.m
57
3
LPFresistance.m
58
4
性能测试.xlsx
64
5
ADC测量偏置.xlsx
60
6
ESP32-S3-DigitalPowerSupply.zip
457
克隆工程
添加到专辑
0
0
分享
侵权投诉

工程成员

评论

全部评论(1)
按时间排序|按热度排序
粉丝0|获赞0
相关工程
暂无相关工程

底部导航