[开源]STC8A8K64S可调/固定电压源
时间:2022-08-24 14:30:01
主图
正视图
STC8A8K可调/固定电压源
(原型机只提供灵感和思路,不易复制)
一、功能简介
该电源由STC8A8K64SMCU有:
- 两种输出模式可调固定;
- 0.96寸OLED电压显示和Led工作状态指示灯;
- 短路保护、输出过压、低压警告、输入过压保护;
- 硬件保护电路的程序调试过程;
- AC 220V市电输入和DC 4.5~8V输入宽电压。
- 硬件的基本原理
1.控制层(顶层)
顶层主要负责将四位二进制控制信号传输到中间层,分别连接四个电源模块的使能端,上旋钮控制待机和3.3V”、 “5.0V”、“12V”、“ADJ切换三种状态,然后控制模块的工作状态,改变输出电压。
- MCU控制
MCU的ADC模数转换口与电位器相连,为转换结果设置不同的范围,实现旋钮(电位器)控制工作模式。在不同的工作模式下,MCU输出不同的位二进制信号输出到下一级的3-8译码器(74HC238)输入端,选择8个输出端口中的4个,作为上述位二进制控制信号,不仅传递给下一层,还作为led的控制信号,驱动led指示当前工作状态OLED或是MCU离线时,指示当前工作状态或错误原因。
为什么使用数字?IC作为MCU电源模块之间的缓冲?为什么不直接连接?MCU简化电源模块设计?首先,由于电源是原型机,在硬件编程过程中,如果两个电源模块同时错误,可能会损坏电源,因此使用类似于互锁的3-8翻译原理,以确保只有一个电源模块同时使用。这一步可以在程序和硬件可行的情况下删除。二是可以作为装饰品,所以大块空也是空的。
- ADC电压采样
使用了STC8A8K的ADC采样功能,采用原电阻分压采样,带SMBJ5.0保护ADC采样口。设置两个采样通道,先烧录试验程序(已上传附件),在可调模式下使用更准确的电压表记录不同电压对应的通道ADC转换结果(理论上是线性变化)计算回归方程,写入正式程序,通过两个通道计算平均值,最终获得当前电压值。最后通过结果OLED屏幕上显示了驱动程序。
采样通道(下)
上层PCB
(上层电路图有误,但是PCB准确)
- (被)驱动层(中层)
中层主要负责根据顶层传输的控制信号启用相应的电源模块,输出正确的电平。
控制信号 输出电压
000 0V(闲置)
001 3.3V
010 5V
011 12V
100nbsp; 1.8~13V(可调)
111 0V(保护状态)
- 固定输出电源模块
固定输出模式包括3.3V、5V、12V固定电压的输出模式,采用了对应型号的LM2596作为电源模块,按照正确的接线方式连接使能端和信号线即可实现功能。
- 输出可调电源模块
选择LM2596-ADJ开关电源模块,使用一大一小两个阻值的旋钮电位器串联作为反馈电阻,旋转旋钮改变反馈电压大小,实现输出电压的调节功能,一大一小的阻值则可以实现电压粗调和微调。
注:这种简单地设计并不属于数字电源的范畴,若想要实现纯数字控制的数字可调电源,可以用分立元件重建中间层,搭建一个大型降压开关电源,将MCU的IO口连接到开关管的栅极,输出不同占空比的高频方波信号(PWM调制),并设计闭环反馈系统,来输出并维持不同的Vout。
中层PCB
中层电路图
- 电源层(底层)
底层主要负责上面两层的供能,将220V的AC市电转化为15V的DC直流电,作为各电源模块降压前的VDD。再通过LDO将DC 15V转化为DC 5V提供给MCU、OLED等器件。此外,还设置了直流输入的备用路径,将DC 4.5~8V的输入电压提升到15V,再作为上述的VDD。
- AC 220V to DC 15V
220V市电通过220:12的变压器和整流桥转化为16V左右(≤12*√2 V),再通过1个LM2596-ADJ电源模块降为更稳定的15V电压,提供给中层作为VDD。同时VDD通过LDO(ASM1117-5.0)转化为5V的控制信号电源,提供给中上两层。
- DC 4.5~8V to 15V
为了防止突然断电或者没有市电接口,但又急需一个电压源的情况出现,该电源设计了针对USB、DC 2.1和DC 1.3等传统5V电源接口的输入路径。5V电压通过升压电源模块XL6019,升压到15V,直接作为VDD提供给旁边的5V LDO和中层电源模块。并且设有肖特基二极管作为PN结隔离,阻断备用路径,防止同时接入AC 220V和5V DC时可能产生的冲突或意料之外的逆流。值得注意的是,碍于功率(能量)的守恒,并且没有配备任何快充协议,该路径输出功率极其小。
- 短路保护
除每层的GND都带有自恢复保险外,出于对220V市电基本的尊重,底层市电的变压器输入脚还带有一个玻璃管保险丝座,可根据需求选择不同参数的保险丝。
- 过压保护(硬件)
在顶层关键器件(MCU和OLED)的5V和GND之间加入TVS瞬态抑制管的齐纳二极管过压保护电路。
过压保护电路
(5)继电器开关
由LDO产生的5V电源供电,控制5V电源和中上两层的开关,继电器关闭时导通,继电器启动时关断,因此不参与输出分流(这种设计纯粹是为了安全,其实完全没有必要)。
底层PCB
底层电路图
- 程序基本原理
(代码风格和性能不具有任何参考性,故下文仅介绍控制原理)
- ADC和模式切换
(上文已介绍,不再赘述)
- 过压、低压保护
保护机制的设想是:当Vout偏离预计值太远时,输出电源应该被掐断,进入预设的保护状态,当Vout归零后,电源应该尝试自动重启。理论上,通过ADC输出结果,保护功能非常容易实现,但实际上存在许多个特例,让保护机制出现很多间断点。
特例:
(1)档位切换时的充放电过程;
(2)接入负载时的瞬时降压和恢复过程;
(3)脱离保护状态(VEM,V_Emergency)并尝试重启时的充电过程。
因此,需要设置多个定时器,应用多个延时规则为充放电留足时间(百毫秒级),防止保护机制误报。在程序中的中断表现为:
T0:动态保护规则,针对容性元件的充电过程编写规则,由于电容充电V-T曲线是微分方程,因此模拟了近似的三段线性规则来逼近(其实多几段更好,但没有专业设备只能止步于此);
T1:静态保护规则,当档位切换停止一段时间后,恢复到正常的保护规则——即设置Vout的上下限,一旦越线直接进入T2的规则。
T2:瞬态保护规则,当出现Vout越线时,开始计时,当计时结束时若电压仍未恢复到正确区间,触发统一的保护机制:进入保护模式(VEM),OLED显示屏提示警告原因,并在电容放完电后(Vout = 0 V时)尝试重启。计时的时间长短决定保护机制的敏感程度,一般设置为数百毫秒。
低压(负载太小)保护
- OLED驱动
参考OLED商家提供的代码。
- 注意事项
- 由于选择的变压器最大输出400mA电流,并且两级开关电源的转换效率最多80%,导致该电源输出能力很弱,输出功率仅2.2W,只是第一版的原型机。下层中许多反馈电阻的取值比较精确,由于设计上的忽视,错误的取值可能导致5V LDO的报废,因此该方案仅提供灵感,不宜复刻;
- 疯狂旋转档位旋钮可能出现误报,重启电源恢复的过程中请顺便思考一下,为什么要这么疯狂地转它;
- 在运行过程中,千万不要直接接触与市电直连的部分,如:变压器引脚、保险丝引脚等,建议用绝缘胶或橡胶包裹。
4.顶层电路图有误,但PCB正确(改着改着不想改了,也变不回去了)
立创开源STC8A8K可调固定电压源 - 嘉立创EDA开源硬件平台
(不含oled.c、oled.h等oled驱动程序)
#include "STC8.h"
#include "oled.h"
#include "bmp.h"
#include
#define unchar unsigned char
#define unint unsigned int
//138code
sbit A1 = P4^0;
sbit A2 = P4^1;
sbit A3 = P4^2;
unchar VSB = 0;
unchar V33 = 33;
unchar V50 = 50;
unchar V12 = 120;
unchar VADJ = 122;
unchar VEM = 9;
unchar VBK = 1;
unchar state = 0;
int voltage = 0; //100 * Voltage value
unchar V1 = 0;
unchar V2 = 0;
unchar a1 = 0; //timer0 count
unint a2 = 0; //timer1 count
unint a3 = 0; //timer2 count
int limitation_t = 50; //max
int limitation_b = -10; //min
unchar cap = 0; //capture charging state 0:Vooc charging 1:fast charging 2:slow charging 3:finish charging
bit fuse = 1; //1:work 0:break
//string
char Warning[] ={'W','a','r','n','i','n','g','!','\0'}; //loading
char Low[] = {' ','L','o','w',' ','V','o','l','t','!','\0'}; //low voltage
char Over[] = {'O','v','e','r',' ','V','o','l','t','!','\0'}; //over voltage
char Volt[] = {'V','O','L','T',':','\0'};
char space[]={' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','\0'};
//0:standby
//1:3.3v
//2:5.0v
//3:12.0V
//4:adj
//5:emergency
unchar C[8]=0xff; //mode knob
unchar Q[8]=0xff; //Vout ADC channel 1
unchar G[8]=0xff; //Vout ADC channel 2
//delay 1ms 24MHz
void delay()
{
unsigned char i, j;
_nop_();
i = 32;
j = 8;
do
{
while (--j);
} while (--i);
}
void Timer0Init() //10ms@24.000MHz
{
AUXR &= 0x7F;
TMOD &= 0xF0;
TL0 = 0xE0;
TH0 = 0xB1;
TF0 = 0;
TR0 = 1;
EA = 1;
ET0 = 1;
}
void Timer1Init(void) //10ms@24.000MHz
{
AUXR &= 0xBF; //?????12T??
TMOD &= 0x0F; //???????
TL1 = 0xE0; //???????
TH1 = 0xB1; //???????
TF1 = 0; //??TF1??
TR1 = 1; //???1????
ET1 = 1;
}
void Timer2Init(void) //10ms@24.000MHz
{
AUXR &= 0xFB; //?????12T??
T2L = 0xE0; //???????
T2H = 0xB1; //???????
AUXR |= 0x10; //???2????
IE2 &=0xfb;
}
//simplify assignment process
void code138(unchar VX)
{
switch(VX)
{
case(0):A1=0;A2=0;A3=0;break;
case(9):A1=1;A2=1;A3=1;break; //emergency protection
case(33):A1=1;A2=0;A3=0;break;
case(50):A1=0;A2=1;A3=0;break;
case(120):A1=1;A2=1;A3=0;break;
case(122):A1=0;A2=0;A3=1;break;
default:A1=0;A2=0;A3=0;break;
}
}
//standby function
void standby()
{
code138(VSB);
limitation_b = -10;
limitation_t = 90;
OLED_ShowString(28,0,space);
OLED_ShowString(18,3,Volt);
OLED_DrawBMP(1,0,23,2,BAT);
OLED_ShowChar(110,3,'V');
OLED_ShowCHinese(100,6,2);
}
//3.3V function
void VV33()
{
code138(V33);
limitation_b = 300;
limitation_t = 355;
OLED_ShowString(18,3,Volt);
OLED_DrawBMP(1,0,23,2,BAT);
OLED_ShowChar(110,3,'V');
OLED_ShowCHinese(100,6,0);
}
//5.0V function
void VV50()
{
code138(V50);
limitation_b = 455;
limitation_t = 545;
OLED_ShowString(18,3,Volt);
OLED_DrawBMP(1,0,23,2,BAT);
OLED_ShowChar(110,3,'V');
OLED_ShowCHinese(100,6,0);
}
//12.0V function
void VV120()
{
code138(V12);
limitation_b = 1145;
limitation_t = 1255;
OLED_ShowString(18,3,Volt);
OLED_DrawBMP(1,0,23,2,BAT);
OLED_ShowChar(110,3,'V');
OLED_ShowCHinese(100,6,0);
}
//adjst function
void VVadj()
{
code138(VADJ);
limitation_b = 90;
limitation_t = 1350;
OLED_ShowString(18,3,Volt);
OLED_DrawBMP(1,0,23,2,BAT);
OLED_ShowChar(110,3,'V');
OLED_ShowCHinese(100,6,1);
}
//emergency function
void VVEM()
{
fuse = 0;
code138(VEM);
OLED_DrawBMP(0,5,25,8,Warn); //Warning logo
OLED_ShowString(28,6,Warning);
OLED_ShowCHinese(100,6,3);
cap = 0;
ET0 = 0;
if(voltage <= 20) //recover rules
{
fuse = 1;
state = VSB;
VBK = VEM;
standby();
}
}
//ADC for varistor controler
void ADC_1()
{
ADC_CONTR =0xc2;
//??AD??
delay();
while (!(ADC_CONTR & 0x20)); //??ADC????
ADC_CONTR &= ~0x20; //?????
*C= ADC_RES;
if(*C <= 20)
state = VADJ;
else if(*C <= 85)
state = V12;
else if(*C <= 160)
state = V50;
else if(*C <= 245)
state = V33;
else
state = VSB;
}
//evaluate power source state, leading to each function
void state_evaluate(unchar state)
{
if(fuse == 1)
{
switch(state)
{
case(0):standby();break;
case(9):VVEM();break;
case(33):VV33();break;
case(50):VV50();break;
case(120):VV120();break;
case(122):VVadj();break;
default:state = VSB;break;
}
VBK = state;
}
else //emergency protection
VVEM();
}
//initialization function
void Initialization()
{
//initialize OLED
state = VSB;
state_evaluate(state);
//initialze ADC
ADCCFG = 0x0f;
ADC_CONTR = 0x80;
OLED_Init();
delay();
OLED_Clear();
OLED_ShowString(18,3,Volt);
OLED_DrawBMP(1,0,23,2,BAT);
OLED_ShowChar(110,3,'V');
OLED_ShowCHinese(100,6,2);
}
void prot()
{
switch(cap)
{
case(0): //Vooc charging protection
{
if(voltage >= 1350)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Over);
}
break;
}
case(1): //fast charging protection
{
if(voltage < limitation_b - 200)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Low);
}
else if(voltage >= limitation_t + 300 || voltage >= 1350)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Over);
}
break;
}
case(2): //slow charging protection
{
if(voltage < limitation_b - 100)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Low);
}
else if(voltage >= limitation_t + 200 || voltage >= 1350)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Over);
}
break;
}
case(3): //finish charging protection
{
if((voltage < limitation_b) || (voltage >= limitation_t))
{
IE2 |= 0x04;
}
else
{
IE2 &= 0xfb;
a3=0;
}
break;
}
}
}
void prot_VSB() //special protection rule for rapidly switching 12V to standby(very slow discharge)
{
{
switch(cap)
{
case(0): //Vooc charging protection
{
if(voltage >= 1350)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Over);
}
break;
}
case(1): //fast charging protection
{
if(voltage < limitation_b)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Low);
}
else if(voltage >= 800)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Over);
}
break;
}
case(2): //slow charging protection
{
if(voltage < limitation_b )
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Low);
}
else if(voltage >= 700)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Over);
}
break;
}
case(3): //finish charging protection
{
if(voltage < limitation_b)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Low);
}
else if(voltage >= 580)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Over);
}
break;
}
}
}
}
//ADC for Vout
void calculate()
{
static unchar round = 0; //ADCtimes : 4 times / refresh
static unint c1=0;
static unint c2=0;
ADC_CONTR =0xc0;
//??AD??
delay();
while (!(ADC_CONTR & 0x20)); //??ADC????
ADC_CONTR &= ~0x20; //?????
*Q= ADC_RES;
c1 += *Q;
ADC_CONTR =0xc1;
//??AD??
delay();
while (!(ADC_CONTR & 0x20)); //??ADC????
ADC_CONTR &= ~0x20; //?????
*G= ADC_RES;
c2 += *G;
round++;
if(round >= 4) //calculate pre 4 times
{
voltage = (9.5430*(c1/round) - 54.2867 + 9.5514 *(c2/round) - 55.1111) / 2;
//voltage = (9.5430*(c1/round) - 54.2867 + 9.5514 *(c2/round) - 55.1111) / 2;
V1 = voltage / 100;
V2 = voltage % 100 /10;
round = 0;
c1=0;
c2=0;
OLED_ShowNum(70,3,V1,2,16); //10+1
OLED_ShowNum(100,3,V2,1,16); //0.1
OLED_ShowChar(90,3,'.');
}
}
void main()
{
Initialization();
Timer0Init();
Timer1Init();
Timer2Init();
//OLED_ShowString(18,3,Volt);
//OLED_ShowNum(70,3,V1,2,16); //10+1
//OLED_ShowChar(90,3,'.');
//OLED_ShowNum(100,3,V2,1,16); //0.1
//OLED_DrawBMP(1,0,23,2,BAT);
//OLED_DrawBMP(0,5,25,8,Warn);
//OLED_ShowChar(110,3,'V');
//OLED_ShowString(28,6,Warning);
//OLED_ShowString(28,0,Over);
//OLED_ShowCHinese(100,6,0);
while(1)
{
ADC_1();
calculate();
if(VBK != state) //run judgment when backup != state
{
OLED_Clear();
state_evaluate(state);
a1 = 0;
a2 = 0;
cap = 0;
ET0 = 1;
ET1 = 1;
}
if(state != VSB)
prot();
else
prot_VSB();
}
}
void Dynamic0() interrupt 1 //Dynamic protection
{
a1++;
if(a1 == 30)
cap = 1; //slow charge state
else if(a1 == 60)
cap = 2; //finish charging
else if(a1 == 125)
{
cap = 3;
a1 = 0;
ET0 = 0;
}
}
void Static1() interrupt 3 //Static protection (after 1.5~2.5 sencond)
{
a2++;
if(state != VSB)
{
if(a2 == 150)
{
a2 = 0;
ET1 = 0;
if(voltage < limitation_b)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Low);
}
else if(voltage >= limitation_t)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Over);
}
}
}
else
if(a2 == 250)
{
a2 = 0;
ET1 = 0;
if(voltage < limitation_b)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Low);
}
else if(voltage >= limitation_t)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Over);
}
}
}
void Tran2() interrupt 12 //transient protection
{
a3++;
if(a3==50) //500ms for transient charging, after 500ms consider as over/below normal Volt
{
IE2 &=0xfb;
a3=0;
if(voltage < limitation_b)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Low);
}
else if(voltage >= limitation_t)
{
fuse=0;
state=VEM;
VVEM();
OLED_ShowString(28,0,Over);
}
}
}
上层PCB 3D效果图
顶层PCB
中层PCB
下层PCB