
开源协议
:GPL 3.0
(未经作者授权,禁止转载)描述
演示视频
项目说明
UV灯在我们日常生活中并不陌生,他应用的领域非常广泛,比如:验钞,美甲,洗衣机杀菌,空气净化,UV胶水,3D打印机等等,然而作为一个电子DIY爱好者来说,我为什么要设计这么一个UV灯能?事情是这样的,我在一次维修电路板的时候,由于PCB的阻焊绿油被刮去了一部分,很容易造成短路,于是我买了阻焊绿油进行修补,但我发现卖家提供的UV灯功率特别小,烘烤时间也非常的长,主要绿油也干的很慢,对于像我这样用的电烙铁都是必须3秒钟升温到300度的急性子来说,这无疑是一个漫长的等待。于是就有了想自己DIY一个UV灯的想法。
那么既然有了想法,那就马上步入实践,我找变了某宝,目前PCB维修用的UV灯单个灯珠都在10W以下,但我觉得只是维修PCB使用的话,未免应用场景太小了,所以我开始
寻找适合我的UV灯珠,在经历了一天的海淘,我找到了合适的如图1所示(单个灯珠功率在15W)这就非常够用了。

那么新的问题来了,这个灯珠是6~8V供电,也就是说如果想要发挥这个UV灯的最大亮度,必须是8V供电,我最早想的是用一节18650锂电池搞定,但是现在看来单节锂电池只有3.7V满电才4.2V也就是说,至少需要两节电池串联,但是如果串联的话,体积就非常庞大了,而且携带和使用都不方便,那有没有非常符合要求的电池呢?答案是有的那就是航模电池(如图2:航模动力电池所示)

首先给大家普及一个知识,航模电池和普通电池的最大区别在于输出的电流大小,“C”用来表示电池充放电电流大小的比率,即倍率。充放电倍率=充放电电流/额定容量,如1200mAh的电池,0.2C表示240mA(1200mAh的0.2倍率),1C表示1200mA(1200mAh的1倍率)。(普通锂电池一般在0.5~1C,比如1000MAH容量的电池,1000mAX0.5=0.5A的放电电流)。
因此可以看到图2我框起来的地方写着20~70C,也就是说同样1000mAh容量的电池,放电电流等于:1000mAh X 20 = 20A,也就是说他的输出电流可以达到20A,那么对于15W的UV灯珠完全够用了。这里我选用了,(如图3锂电池型号所示)

PCB主要采用了立创EDA进行设计和打样,总的来说分为控制板(如图4)和铝基板(如图5)

这个控制板主要集成功能有:
- WIFI---用于配网和后续的APP控制
- 串口下载接口---用于升级更新程序
- OLED---用于显示相关信息
- 锂电池保护电路---用于防止锂电池的过充过放
- 锂电池充电电路---用于给串联锂电池充电
- UV灯驱动电路---用于控制UV灯的亮度

这个铝基板主要是由于UV灯在工作过程中,发热非常严重,直接焊接在PCB上,散热效果比较差,所以采用铝基板,至于为什么要做成这个形状,这和我买的如图6(散热风扇)的接触面积有关。

项目相关功能

设计原理

考虑成本和整体体积问题,这次选用ESP8266作为主控(外围电路简单,价格便宜),其实这个主控IO口有点不够用,这次的项目已经把所有IO口都用上了。简单介绍一下,这个主控如果需要正常工作需要将IO15下拉,IO2,IO0上拉,这里我将IO16也进行了上拉,因为这个引脚我接了按键(必须上拉)否则按键不起作用,这里通过使用一个五向按键来代替5个独立按键,极大节省PCB布局的摆放空间,这个flash芯片采用(W25Q32JVSSIQ)尽量靠近主控,减少干扰。至于主控的外围电路,可以参考ESP8266硬件数据手册,(直接照抄,靠谱!!!)。
PCB布局注意:
1.(RST,EN)电路要靠近芯片摆放,这是复位驱动电路,容易受到干扰。
2.(天线电路)尽可能的不要拐弯,(天线周围多打过孔)防止干扰。

这个只是使用了一个简单的MOS管驱动的,在那之前我尝试了四五种驱动芯片,因为UV灯是8V供电,15W,也就是说电流约等于2A,所有效果都不理想。最后选用MOS管作为驱动,同时风扇已经没有多余的IO口了,也是使用的这个MOS管。LDO就不详细介绍了。

考虑到UV灯需要8V电压,因此采用两节3.7V锂电池串联的方式,充电芯片采用TP4256,可同时重两组串联的电池,电池保护电路,就是最普通的方案,这里就不过多介绍了,不懂的可以去看(HY2120-CB)的数据手册十分详细。
软件说明
为了方便大家复刻,采用了ArduinoIDE进行编程,我个人认为,这个程序的主要特点有两个:
1.图标的取模
这里采用的是iconfont-阿里巴巴矢量图标库
里面的图标特别全,完全可以满足日常需要,将需要的图标以PNG格式下载下来,并通过PS软件调整到适合OLED的分辨率,这里我调整到了32*32像素,后通过(PCtoLCD2002.exe )软件进行取模,取模完成后,就可以得到需要的数组了,大家可以根据需要设计适合自己的图标。通过修改(Text.h)文件进行图标的替换。


2.菜单的程序
本人才疏学浅,所以这个程序是和chatGPT共同完成的,为了方便理解,我会将程序进行中文注释:
首先:
Serial.begin(115200);
pinMode(UVdeng, OUTPUT); // 设置UV灯引脚为输出模式
pinMode(shang, INPUT_PULLUP); // 设置按键引脚为输入模式,并启用上拉电阻
pinMode(xia, INPUT_PULLUP);
pinMode(zuo, INPUT_PULLUP);
pinMode(you, INPUT_PULLUP);
pinMode(zhong, INPUT_PULLUP);
analogWrite(UVdeng, dutyCycle); //// 设置初始亮度
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // 初始化OLED显示屏
Serial.println(F("无法连接到SSD1306显示器"));
for(;;);// 如果显示屏初始化失败,进入无限循环
}
timeClient.begin();
strip.begin(); // 初始化WS2812
strip.setBrightness(ambientBrightness); // 设置初始亮度
strip.show(); // 初始化所有LED为关闭状态
display.clearDisplay(); // 清除显示内容
display.display(); // 刷新显示
hans_display_1(); //开机LOGO
delay(500);
updateDisplay(); // 强制刷新显示内容
}
定义了按键与UV灯MOS管的控制引脚
pinMode(UVdeng, OUTPUT); // 设置UV灯引脚为输出模式
pinMode(shang, INPUT_PULLUP); // 设置按键引脚为输入模式,并启用上拉电阻
pinMode(xia, INPUT_PULLUP);
pinMode(zuo, INPUT_PULLUP);
pinMode(you, INPUT_PULLUP);
pinMode(zhong, INPUT_PULLUP)
初始化了OLED并设置了开机LOGO,这个函数updateDisplay(); 是开机LOGO的横向移动效果。
接下来是LOOP函数:
void loop() {
if (digitalRead(zuo) == LOW) {
currentMenu = (currentMenu == 0) ? 4 : currentMenu - 1;
delay(200);
}
if (digitalRead(you) == LOW) {
currentMenu = (currentMenu == 4) ? 0 : currentMenu + 1;
delay(200);
}
// 确定当前菜单下"zhong"键的功能
if (digitalRead(zhong) == LOW) {
if (currentMenu == MENU_BRIGHTNESS) {
UVLightOn = !UVLightOn; // 切换UV灯的状态
analogWrite(UVdeng, UVLightOn ? 255 : 0); // 设置UV灯亮度
} else if (currentMenu == MENU_TIMER && timerControlEnabled) {
timerRunning = !timerRunning; // 切换定时器的状态
if (timerRunning) {
timerEndTime = millis() + ((timerHours * 60 + timerMinutes) * 60000);
//timerRunning = true;
digitalWrite(UVdeng, HIGH);
} else {
//timerRunning = false;
digitalWrite(UVdeng, LOW);
}
} else if (currentMenu == MENU_AMBIENT) {
ambientLightOn = !ambientLightOn; // 切换氛围灯的状态
if (!ambientLightOn) {
strip.clear();
strip.show();
}
}
delay(200);
}
// 处理亮度菜单
if (currentMenu == MENU_BRIGHTNESS) {
if(digitalRead(shang) == LOW) {
dutyCycle += step;
if(dutyCycle > 255) dutyCycle = 255;
analogWrite(UVdeng, dutyCycle);
delay(200);
}
if(digitalRead(xia) == LOW) {
dutyCycle -= step;
if(dutyCycle < 0) dutyCycle = 0;
analogWrite(UVdeng, dutyCycle);
delay(200);
}
}
//// 处理定时菜单
if (currentMenu == MENU_TIMER && !timerRunning && timerControlEnabled) {
if(digitalRead(shang) == LOW) {
timerMinutes += 1;
if (timerMinutes >= 60) {
timerMinutes = 0;
timerHours += 1;
}
/* if (timerHours >= 1) {
timerHours = 0;
}*/
delay(200);
}
if(digitalRead(xia) == LOW) {
timerMinutes -= 1;
if (timerMinutes < 0) {
timerMinutes = 59;
timerHours -= 1;
}
if (timerHours < 0) {
timerHours = 0;
timerMinutes = 0;
}
delay(200);
}
}
//检查定时器是否到期
if (timerRunning && millis() > timerEndTime) {
timerRunning = false;
digitalWrite(UVdeng, LOW);
}
// WIFI菜单
if (currentMenu == MENU_WIFI) {
if (digitalRead(shang) == LOW) {
WIFIMode();
if (wifiConnected) {
timeClient.update();
}
delay(200);
}
if (digitalRead(xia) == LOW) {
WiFi.disconnect();
wifiConnected = false;
delay(200);
}
}
// 处理氛围灯菜单
if(currentMenu == MENU_AMBIENT){
if (digitalRead(shang) == LOW) {
ambientMode = (ambientMode + 1) % 8;
setAmbientMode(ambientMode);
delay(200);
}
if (digitalRead(xia) == LOW) {
ambientBrightness += 32;
if (ambientBrightness > 255) ambientBrightness = 32;
strip.setBrightness(ambientBrightness);
strip.show();
delay(200);
}
}
updateDisplay();
}
我将这五个按键分别命名为“上,下,左,右,中”目前程序:首先我设计了5个菜单分别为:亮度菜单,时间菜单,定时菜单,WIFI菜单,氛围灯菜单。





左,右按键负责切换菜单:
if (digitalRead(zuo) == LOW) {
currentMenu = (currentMenu == 0) ? 4 : currentMenu - 1;
delay(200);
}
if (digitalRead(you) == LOW) {
currentMenu = (currentMenu == 4) ? 0 : currentMenu + 1;
delay(200);
}
中键在不同的菜单,代表不同功能的确认按键
// 确定当前菜单下"zhong"键的功能
if (digitalRead(zhong) == LOW) {
if (currentMenu == MENU_BRIGHTNESS) {
UVLightOn = !UVLightOn; // 切换UV灯的状态
analogWrite(UVdeng, UVLightOn ? 255 : 0); // 设置UV灯亮度
} else if (currentMenu == MENU_TIMER && timerControlEnabled) {
timerRunning = !timerRunning; // 切换定时器的状态
if (timerRunning) {
timerEndTime = millis() + ((timerHours * 60 + timerMinutes) * 60000);
//timerRunning = true;
digitalWrite(UVdeng, HIGH);
} else {
//timerRunning = false;
digitalWrite(UVdeng, LOW);
}
} else if (currentMenu == MENU_AMBIENT) {
ambientLightOn = !ambientLightOn; // 切换氛围灯的状态
if (!ambientLightOn) {
strip.clear();
strip.show();
}
}
delay(200);
}
当切换到亮度菜单上,下按键负责亮度的调整,当切换到定时菜单,上,下键负责定时时长的调整。
if (currentMenu == MENU_BRIGHTNESS) {
if(digitalRead(shang) == LOW) {
dutyCycle += step;
if(dutyCycle > 255) dutyCycle = 255;
analogWrite(UVdeng, dutyCycle);
delay(200);
}
if(digitalRead(xia) == LOW) {
dutyCycle -= step;
if(dutyCycle < 0) dutyCycle = 0;
analogWrite(UVdeng, dutyCycle);
delay(200);
}
}
if (currentMenu == MENU_TIMER && !timerRunning && timerControlEnabled) {
if(digitalRead(shang) == LOW) {
timerMinutes += 1;
if (timerMinutes >= 60) {
timerMinutes = 0;
timerHours += 1;
}
/* if (timerHours >= 1) {
timerHours = 0;
}*/
delay(200);
}
if(digitalRead(xia) == LOW) {
timerMinutes -= 1;
if (timerMinutes < 0) {
timerMinutes = 59;
timerHours -= 1;
}
if (timerHours < 0) {
timerHours = 0;
timerMinutes = 0;
}
delay(200);
}
其他菜单也是同理,这里就不一一列举了。
后期优化:
1.充电界面---当充电时显示充电进度;
2.电量显示界面---电池使用时显示电池消耗百分比;
3.APP控制界面---当切换为远程控制,则可以通过手机实现远程控制;
实物图






设计图

BOM


评论