ESP32-S3数字电源 - 嘉立创EDA开源硬件平台

编辑器版本 ×
标准版 Standard

1、简单易用,可快速上手

2、流畅支持300个器件或1000个焊盘以下的设计规模

3、支持简单的电路仿真

4、面向学生、老师、创客

专业版 professional

1、全新的交互和界面

2、流畅支持超过3w器件或10w焊盘的设计规模,支持面板和外壳设计

3、更严谨的设计约束,更规范的流程

4、面向企业、更专业的用户

专业版 ESP32-S3数字电源

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

开源协议: CC BY-NC-SA 4.0

(未经作者授权,禁止转载)

已参加:仪器仪表训练营

创建时间: 2023-10-16 21:35:50
更新时间: 2023-12-30 12:21:21
描述
## 零、写在前面 作为一名机械大类学生,在项目过程中调试机电设备往往需要使用到电源。但线性电源、开关电源等在不同方面具有限制,难以适应多样的要求。因此,设计一款小巧的数字电源是计划已久,但从大二到大四,因为种种原因迟迟没能抽出时间实现。本次训练营属实是push了自己一下,把挖了好久的史前大坑给填了。 本项目是第一次独立完成所有相关工作,之前在团队和项目当中主要负责机械与硬件部分工作。可以预见的,项目仍存在很多可改进的地方,尤其是在软件部分。欢迎各位提出相关建议,本项目会作为长期项目持续修改更新(若有大迭代会新建项目)。 ### 0.1 实物图片 <center>TOQQkaHyPqeZc9ct39Dn6ZQjNcH5BVNBzAevBh4i.jpeg</center> <center>图1 电源正面</center> <center>1OHEGXJElvFMUrS3arOoX6FS7MOl90apSkH0eBzy.jpeg</center> <center>图2 电源背面</center> <center> * 小面板 · 大师造 - 纯手工匠心磨制(有傻子计算忘记算板厚偏移了1.6mm) * </center> <center>6hvPFE7H5oCSq8rMBQCcCyg4HtvC3Se8nqSjXtig.jpeg</center> <center>图3 电源爆炸图(怪诶)</center> ## <center> ~~摘要~~ </center> 对不起发病了,没有摘要。 ## 一、基本特性 ### 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 系统主要框架 <center>hfarpfEl7DcT6CkCdsE3kTMqxaNiOlZCohe945ju.png</center> <center>图4 系统主要框架示意图</center> ### 2.2 电源网络框架 <center>GVfJ6a1NPthjGwPLSYulQpsfRXcaF9OuSdQVZvlI.png</center> <center>图5 电源网络框架示意图</center> ### 2.3 面板交互逻辑(对于恒压输出) <center>mqHDZLZclTqGlPBdnwncSNbyr4kXDPuAskip7ycX.png</center> <center>图6 面板交互逻辑示意图(对于恒压输出)</center> ### 2.4 串口交互逻辑(对于恒压输出) <center>7IFPiWmoFllzMUQBLjYBzWd2Y7ONUkxKpfMFUeon.png</center> <center>图7 串口交互逻辑示意图(对于恒压输出)</center> ## 三、设计思路 * 满足常用电压数值的可控输出(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 电压采样电路 <center>YWTJyqhfMUNPFDByaWQekcKzu4YKd82wr2NTZwVn.png</center> <center>图8 电压采样电路原理图</center> 这里我采用的是片上ADC与运放电路的组合,其中采样运放电路着重做量程标度变换的处理。 ESP32-S3的片上共有2个12bit ADC合计10各通道,参考电压为3.3V,采样值为0~4095。 为了使采样值更为直观且在采样值与实际电压建立更为明确关系,这里需要做标度变换。如下式: <center>WprOQQa7rOj8ZEV3lAZtvBwMk3G9qCT8hlqNGBKT.png</center> 在考虑DC-DC电源输出、控制精度及可行性等,我将标度变换为 0~4095 - 0~40.95V,即每LSB对应10mV。 为降低成本,我采用E24系列电阻配置运放放大系数,并考虑运放输入两端阻抗匹配因素进行求解。 我采用遗传算法进行多目标优化求解,以放大系数误差绝对值为主要优化目标、阻抗偏差绝对值为次要优化目标构造适应度函数。得到结果如下: <center>PUy0g096VEWYEnov7ddJ1GcDkOG0Zc7TFJbcO0Ct.jpeg</center> <center>图9 电压采样电路配置电阻优化求解</center> * R1 = 18K; R2 = 1.3K; R3 = 16K; R4 = 1.3K * 放大系数偏差:7.0199e-05 * 输入匹配偏差:0.0195 至此完成电压采样电路的参数设计。(程序见附件) #### 3.4.2 输出控制电路 <center>SSLg1o1kJpQWyXWfYXeHFdjrGSmmuxjRFVIHnq95.png</center> <center>图10 输出控制电路原理图</center> 我采用电阻网络控制输出电压,控制端输入电压将会与输出电压成一阶线性反比。 <center>mQbk6loTU5jyaGLA1BAIi6F8nh9cyLVZqHJvBrhU.png</center> <center>图11 电阻网络控制(@BV1oC4y1V7k1)</center> 本来是想使用片上DAC进行输出控制。但是!ESP32-S3并没有DAC(ESP32 → ESP32-S3 反向升级)。 因此改用PWM输出配合二阶低通滤波器模拟DAC进行控制。 反馈电阻网络各组成电阻的阻值共同确定输出电压范围。 如3供电范围分析及器件限制推得:2V ≤ Uout ≤ 26V 同样,以输出电压偏差量绝对值构造适应度函数,并采用遗传算法进行优化求解。得到结果如下: <center>ZzcI4fy79D3slvXazYhG0RtK1skoGSjSSjsLdkMo.jpeg</center> <center>图12 输出电阻网络配置优化求解</center> * R1 = 13K; R2 = 1K; R3 = 1.8K * Uout_max = 25.3911V * Uout_min = 2.1667V 至此完成输出控制电路的参数设计。(程序见附件) ## 四、ESP32-S3程序编写 ESP32-S3主要需要实现以下功能: * 电压电流采样 * 输出电压控制 * 输出控制 * 本机交互 * 上位机交互 ### 4.0 开发环境 本项目程序基于VSCode PlatformIO,Arduino框架开发。 <center>a9T5z0oR6ps6iUp8pklphZI0IMCfhkiGnP8eaKkq.png</center> <center>图13 开发环境</center> ### 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! <center>og3ACniSDGrwAsFgRhuKRDZXCHqQqglUFHnquBif.png</center> <center>图14 PSRAM管脚对应表[4]</center> 如图13,八线SPI驱动的Flash/PSRAM会额外占用GPIO33-37。 在设计时我使用了其中几根引脚用于驱动ST7735,并且启用了PSRAM功能。 这时候高血压来了,每次启动时就是ST7735 SPI和PSRAM在抢GPIO,能不能正常启动完全看脸,复位一次可能就寄。 后面重读了一遍datasheet发现了该问题,关闭PSRAM功能后解决。(所以说xdm,datasheet全文背诵!) 以上问题主要还是我对于ESP32-S3相关认识还不够清晰,所以希望各位引以为戒,注意datasheet中提到的重要细节。 ### 5.2 ADC校准 当完成主要功能程序的编写后,开始对输出进行测试。这时,我发现输出/输入的电压与万用表、电流表的值不一致。 因此我对测量值与输出值进行比较,得到结果如下: <center>fyEAYPIDmt1wfpfo323DUtSEA6LfV6VkhwejTWRY.png</center> <center>图15 输出值-测量值比较(校准前)</center> 如上图所示,可以看出实际电压测量值与输出值间两条线斜率不同、高度不同,存在斜率与截距的偏差。 可以很容易看出,误差值主要呈一阶线性分布。 因此,我定义CorrectUout(斜率校准)、UoutOffest(截距校准)对ADC进行校准。 我将测量得到的数值导入到Matlab当中,并将实际控制量、实际测量值做一阶线性拟合。得到: <center>qzvqJLNOCf7MAatZ4ern4vlNZnydpYPX9cSWNnGF.jpeg</center> <center>图16 线性拟合结果</center> * CorrectUout = 1.0384 * UoutOffest = 50.8339 并将以上结果带入程序中对ADC进行校准。 <center>rYyhtZHoHU2vtKnjqRmI6q8ddLFyw01NgYnDpJSs.png</center> <center>图17 输出值-测量值比较(校准后)</center> 如上图,可以看出校准后实际控制量与实际测量值一致性良好,偏差量较小,可能由测量误差或控制误差带来。 推测斜率差值来自采样运放电路的放大误差,可能由电阻误差等带来; 截距误差来自片上ADC自身基准漂移(猜测)。 以上完成ADC校准,校准后效果良好。(程序及文档见附件) ### 5.3 PID参数整定 输出电压准确性、电源纹波等在很大程度上会收到MCU控制的影响,因此PID的参数是否合适会显著影响电源输出质量。 <center>PbAwIdbQnTK5CMtbtYRmIsFUufk2yx3KP9l53FwG.png</center> <center>图18 PID参数整定调试</center> 目前PID参数设置还存在一定问题,例如:存在静态误差、超调量较大、振荡等。 因此仍需进一步整定参数。 ## 六、性能测试 ### 6.1 转换率测试 为测试电源性能,我使用电子负载进行测试。 <center>anGFpmuo9lE3Pj2gmE1xkHbMMqgnZIpBEXtNukJE.jpeg</center> <center>图19 性能测试环境</center> <center> * 村 · 好 · 仪 - 刷脸从老师那借来的负载 * </center> 输入条件:努比亚GaN氮化镓单口65W充电器 @20V 3.25A max 输入信息:炬为安全充电多功能检测仪(USB电流电压表) 输出信息:电子负载 测试结果如下所示: <center>表1 测试结果</center> <center>5RH563NdRtp3FzbEGUs5tufPz8MlkxGk3vUM6F2u.png</center> 如上表所示,电源在大电流负载条件下转换率较低,推测MOS/电感内阻较大。 ### 6.2 纹波测试 使用示波器交流耦合档测试,测得以下结果: <center>346AvF8nUZnhxhfFvtwqssMnDC2WhmAt8a4QykhR.jpeg</center> <center>图20 纹波测试结果</center> emmm还行?但总感觉怪怪的。 *以上结果不完全准确,仅供参考 ## 七、电源装配 ![IMG_20231119_082501.jpg](//image.lceda.cn/pullimage/ZWUp1WhAzg8dN2jRJLykpBalIL9ySu7rhNwjNkow.jpeg) <center>图21 电源装配图1(使用3mm导热垫将MOS、芯片热量导到外壳)</center> ![IMG_20231119_082908.jpg](//image.lceda.cn/pullimage/VDHiHNupMQpwamrCZRvdCHIMVaBra6iBIs5VKusR.jpeg) <center>图22 电源装配图2(增加14x14x5散热片辅助电感、MCU模块散热)</center> ![IMG_20231119_075419.jpg](//image.lceda.cn/pullimage/eEF9WOkYXzyX7towI3ht9J73HEJKcipryPv6Jom2.jpeg) <center>图23 电源装配图3(使用M2.5x6螺丝固定前后面板)</center> ![IMG_20231119_193838.jpg](//image.lceda.cn/pullimage/TdaB6Kab8nf7RmXc2FsRTG3Scj9QvS6stjMTbnHR.jpeg) <center>图24 电源装配图4</center> ## 八、电源效果图 ![前面板功能介绍.png](//image.lceda.cn/pullimage/wDhQ4AoQ5SyCNd6NERgXd5icRvZem52wXxMw2cZ9.png) <center>图25 电源效果图1(前面板)</center> ![IMG_20231119_091134.jpg](//image.lceda.cn/pullimage/2yfTOEOXa7MW1NmlxLZJOhyqsguwN3efvuI6ssTv.jpeg) <center>图26 电源效果图2(后面板 - 工程中面板已修正孔位偏差)</center> ![显示及输出状态介绍.png](//image.lceda.cn/pullimage/qHynosMZa5XSwYlSKynKESVlzKlS4VbDXHqwm5HA.png) <center>图27 电源效果图2(输出状态:等待 | 输出 | 输入错误)</center> ![IMG_20231119_105933.jpg](//image.lceda.cn/pullimage/ZQM1q9P3JO6xryAF7sWWToT0jyr6SqFKjMLIz2lH.jpeg) <center>图28 电源效果图4(使用4mm香蕉头连接输出端)</center> ## 参考文献 * SC8701 [1] 手册:SC8701 [https://www.semiee.com/file/SouthChip/SouthChip-SC8701.pdf](https://www.semiee.com/file/SouthChip/SouthChip-SC8701.pdf) [2] 开源工程:SC8701 buck-boost可调DC-DC验证 [https://oshwhub.com/8bit_in_1byte/sc8701-ke-diaodc-dc](https://oshwhub.com/8bit_in_1byte/sc8701-ke-diaodc-dc) [3] 开源工程:636 - SC8701自动升降压车充 [https://oshwhub.com/LoveTombSeries/LoveTomb636](https://oshwhub.com/LoveTombSeries/LoveTomb636) * ESP32-S3-WROOM-1U-N16R8 [4] 手册:ESP32-S3技术规格书 [https://www.espressif.com.cn/sites/default/files/documentation/esp32-s3_datasheet_cn.pdf](https://www.espressif.com.cn/sites/default/files/documentation/esp32-s3_datasheet_cn.pdf) [5] 手册:ESP32-S3硬件设计指南 [https://www.espressif.com.cn/sites/default/files/documentation/esp32-s3_hardware_design_guidelines_cn.pdf](https://www.espressif.com.cn/sites/default/files/documentation/esp32-s3_hardware_design_guidelines_cn.pdf) [6] 手册:ESP32-S3技术参考手册 [https://www.espressif.com.cn/sites/default/files/documentation/esp32-s3_technical_reference_manual_cn.pdf](https://www.espressif.com.cn/sites/default/files/documentation/esp32-s3_technical_reference_manual_cn.pdf) [7] 手册:ESP32-S3-WROOM-1 & ESP32-S3-WROOM-1U 技术规格书 [https://www.espressif.com.cn/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_cn.pdf](https://www.espressif.com.cn/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_cn.pdf) [8] 开源工程:ESP32-S3 UNO(我的工程!) [https://oshwhub.com/carele/ESP32-S3hu-xin-ban](https://oshwhub.com/carele/ESP32-S3hu-xin-ban) * 数字电源设计 [9] 视频:全开源DIY|手搓1台Mini数控电源(上&下) [https://www.bilibili.com/video/BV1oC4y1V7k1](https://www.bilibili.com/video/BV1oC4y1V7k1) [10] 视频:分析一位网友设计的电源板,ESP32主控,BUCK-BOOST,使用PD快充供电,具备升压和降压功能 [https://www.bilibili.com/video/BV1GV411K7bN](https://www.bilibili.com/video/BV1GV411K7bN) * TFT屏幕/TFT_eSPI库 [11] 博客:ESP32 Arduino 学习篇(五)TFT_eSPI库 [https://blog.csdn.net/DOF526570/article/details/128859819](https://blog.csdn.net/DOF526570/article/details/128859819) [12] 博客:TFT_eSPI使用 [https://blog.csdn.net/qq_44633275/article/details/129149580](https://blog.csdn.net/qq_44633275/article/details/129149580) [13] 开源工程:st7789_1.3inch_240x240 [https://oshwhub.com/sync.sh/extendboard_st7789_1-3inch_240x240](https://oshwhub.com/sync.sh/extendboard_st7789_1-3inch_240x240) * INA219(虽然没能驱动) [14] 博客:INA219电量监测芯片的使用经验和资料及使用步骤详细说明 [https://www.elecfans.com/d/1067986.html](https://www.elecfans.com/d/1067986.html) [15] 博客:INA219例程,可校准电流值误差(基于stm32) [https://blog.csdn.net/m0_46175164/article/details/127013014](https://blog.csdn.net/m0_46175164/article/details/127013014) [16] 博客:INA219例程 [https://blog.csdn.net/jgagdwp/article/details/79470158](https://blog.csdn.net/jgagdwp/article/details/79470158) [17] 开源项目:Adafruit INA219 Current Sensor Breakout [https://learn.adafruit.com/adafruit-ina219-current-sensor-breakout](https://learn.adafruit.com/adafruit-ina219-current-sensor-breakout) * PWM模拟DAC [18] 视频:2022年电赛,数模转换器DAC,PWM做DAC DAC8562 DAC8563 TLV5616,TLV5618,TLV5638,2022年大学生电子设计竞赛 [https://www.bilibili.com/video/BV1VT411J7ru](https://www.bilibili.com/video/BV1VT411J7ru) * EC11/Encoder库 [19] 博客:旋转编码器的工作原理及其与 Arduino 的接口 [https://blog.csdn.net/sxstj/article/details/132244808](https://blog.csdn.net/sxstj/article/details/132244808) [20] 博客:玩转电机驱动——电机编码器 [https://blog.csdn.net/weixin_43002939/article/details/124751083](https://blog.csdn.net/weixin_43002939/article/details/124751083) * Onebutton库 [21] 博客:ESP32 Arduino(十一) 按键控制库 OneButton [https://blog.csdn.net/DOF526570/article/details/128943669](https://blog.csdn.net/DOF526570/article/details/128943669) (暂时想到这么多,后续继续更新) ## 附录 ### I.源代码(工程见附件压缩包) ``` #include <Arduino.h> #include <TFT_eSPI.h> #include <SPI.h> #include <OneButton.h> #include <Wire.h> #include <Adafruit_INA219.h> #define ENCODER_OPTIMIZE_INTERRUPTS #include <Encoder.h> // 引脚定义 #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(); // 编码器读取 } ```
设计图
原理图
1 /
PCB
1 /
未生成预览图,请在编辑器重新保存一次
工程成员
侵权投诉
相关工程
换一批
加载中...
添加到专辑 ×

加载中...

温馨提示 ×

是否需要添加此工程到专辑?

温馨提示
动态内容涉嫌违规
内容:
  • 153 6159 2675

服务时间

周一至周五 9:00~18:00
  • 技术支持

support
  • 开源平台公众号

MP