ESP32电压显示

一 、使用说明

  1. 硬件
  • 电压输入:GPIO34
  • 分压电阻默认:10k + 4.7k (可在read_voltage中修改)
  • PWM 输出:GPIO14
  • LED:板载GPIO2
  1. 网页功能
  • LED 开关
  • PWM 滑块控制(500~1000)
  • 实时电压显示(每秒自动刷新)
  1. API 接口
  • /light/on
  • /light/off
  • /pwm/xxx
  • /voltage(返回:如3.72

直接烧录、打开网页即可使用

二、实物怎么接?(一步一步)

【待测电压 VIN】

R1 10k

├──────────→ GPIO34 (ADC)

R2 4.7k

GND

二、修改后 b.html(完整代码)

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

<title>ESP32 控制器</title>

<style>

* {

margin: 0;

padding: 0;

box-sizing: border-box;

-webkit-tap-highlight-color: transparent;

}

body {

font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

background: #fff;

padding: 60px 20px;

}

h1 {

text-align: center;

font-size: 24px;

color: #1f2d3d;

margin-bottom: 40px;

font-weight: 600;

}

.control-btn {

display: block;

width: 220px;

height: 70px;

margin: 0 auto 25px;

border: none;

border-radius: 35px;

font-size: 24px;

font-weight: 600;

color: #fff;

background: linear-gradient(135deg, #ff6b6b, #e74c3c);

box-shadow: 0 8px 20px rgba(231,76,60,0.3);

transition: all 0.15s ease;

}

.control-btn:active {

transform: scale(0.96);

}

.control-btn.on {

background: linear-gradient(135deg, #2ecc71, #27ae60);

box-shadow: 0 8px 20px rgba(46,204,110,0.3);

}

/* 关闭按钮样式 - 仅视觉区分,无清理逻辑 */

.close-btn {

background: linear-gradient(135deg, #6c757d, #343a40);

box-shadow: 0 8px 20px rgba(108,117,125,0.3);

}

#status {

text-align: center;

font-size: 16px;

color: #333;

margin-bottom: 20px;

}

/* 电压显示样式 */

.voltage-box {

text-align: center;

font-size: 22px;

font-weight: bold;

color: #ff5722;

margin-bottom: 40px;

display: flex;

align-items: center;

justify-content: center;

gap: 10px;

}

/* 竖直iPhone风格电池图标(带充电闪电) */

.battery-icon {

display: inline-block;

width: 30px;

height: 50px;

border: 3px solid #27ae60;

border-radius: 4px;

border-bottom-width: 3px;

position: relative;

background: linear-gradient(to bottom, rgba(39, 174, 96, 0.1) 0%, rgba(39, 174, 96, 0.2) 100%);

}

/* 电池顶部凸起 */

.battery-icon::before {

content: '';

position: absolute;

top: -8px;

left: 50%;

transform: translateX(-50%);

width: 15px;

height: 6px;

background: #27ae60;

border-radius: 3px 3px 0 0;

}

/* 电池内部电量指示 */

.battery-icon::after {

content: '';

position: absolute;

bottom: 3px;

left: 3px;

right: 3px;

height: 85%;

background: linear-gradient(to top, #2ecc71, #27ae60);

border-radius: 1px;

}

/* 充电闪电图标 */

.battery-icon .flash {

position: absolute;

top: 50%;

left: 50%;

transform: translate(-50%, -50%) rotate(30deg);

width: 18px;

height: 18px;

color: #ffffff;

font-size: 18px;

font-weight: bold;

text-shadow: 0 0 2px rgba(0,0,0,0.2);

z-index: 1;

}

.pwm-box {

padding: 0 10px;

}

.pwm-title {

display: flex;

justify-content: space-between;

align-items: center;

font-size: 18px;

font-weight: 500;

color: #333;

margin-bottom: 20px;

}

#pwm-value {

font-size: 22px;

font-weight: bold;

color: #007aff;

}

.volume-container {

position: relative;

width: 100%;

height: 70px;

display: flex;

align-items: flex-end;

justify-content: space-between;

padding: 0 1px;

}

.bar {

width: 3.5px;

background: #e5e7eb;

border-radius: 2px;

height: 10%;

transition: height 0.05s linear;

}

#pwmSlider {

-webkit-appearance: none;

position: absolute;

width: 100%;

height: 70px;

top: 0;

left: 0;

background: transparent;

outline: none;

z-index: 10;

margin: 0;

}

#pwmSlider::-webkit-slider-thumb {

-webkit-appearance: none;

width: 26px;

height: 26px;

border-radius: 50%;

background: #007aff;

border: 3px solid #fff;

box-shadow: 0 0 12px rgba(0,122,255,0.5);

}

</style>

</head>

<body>

<h1>✨ ESP32 控制器 ✨</h1>

<button id="ledBtn" class="control-btn">点亮LED</button>

<!-- 退出按钮 - 增加清理+关闭逻辑 -->

<button id="closeBtn" class="control-btn close-btn">退出</button>

<div id="status">等待操作...</div>

<!-- 实时电压显示(带竖直iPhone风格充电电池图标) -->

<div class="voltage-box">

<div class="battery-icon">

<span class="flash">⚡</span> <!-- 充电闪电符号 -->

</div>

<div>

实时电压:<span id="volt">0.00</span> V

</div>

</div>

<div class="pwm-box">

<div class="pwm-title">

<span>电机 强度</span>

<span id="pwm-value">750</span>

</div>

<div class="volume-container">

<div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>

<div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>

<div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>

<input type="range" id="pwmSlider" min="0" max="1000" value="100"> <!-- 修正min值为0,匹配退出逻辑 -->

</div>

</div>

<script>

const esp32_ip = "192.168.4.1";

let ledOn = false;

const btn = document.getElementById("ledBtn");

const closeBtn = document.getElementById("closeBtn");

const status = document.getElementById("status");

const slider = document.getElementById("pwmSlider");

const valueText = document.getElementById("pwm-value");

const bars = document.querySelectorAll(".bar");

const voltText = document.getElementById("volt");

// 电压刷新定时器(保留原有逻辑)

let voltageTimer = setInterval(getVoltage, 1000);

// UI更新

function updateUI() {

const val = slider.value;

valueText.textContent = val;

const percentage = (val - 0) / 10; // 修正百分比计算(min改为0)

const totalBars = bars.length;

bars.forEach((bar, index) => {

const heightRatio = index / totalBars;

bar.style.height = 10 + heightRatio * 55 + "%";

const hue = 200 + heightRatio * 40;

const saturation = 100;

const lightness = percentage > heightRatio * 100 ? 60 : 90;

bar.style.backgroundColor = `hsl({hue}, {saturation}%, ${lightness}%)`;

});

}

// 设置PWM

async function setPWM(v) {

try {

await fetch(`http://${esp32_ip}/pwm/${v}`);

status.textContent = `PWM: ${v}`;

} catch (e) {

status.textContent = "连接失败";

}

}

// 获取电压

async function getVoltage() {

try {

let res = await fetch(`http://${esp32_ip}/voltage`);

let volt = await res.text();

voltText.textContent = volt;

} catch (e) {

voltText.textContent = "--";

}

}

// LED控制

btn.addEventListener("click", async () => {

const cmd = ledOn ? "off" : "on";

try {

await fetch(`http://${esp32_ip}/light/${cmd}`);

ledOn = !ledOn;

btn.textContent = ledOn ? "熄灭LED" : "点亮LED";

btn.classList.toggle("on", ledOn);

status.textContent = ledOn ? "LED 已点亮" : "LED 已熄灭";

} catch (e) {

status.textContent = "连接失败";

}

});

// 退出按钮 - 完整逻辑:设置PWM=0 → 清理资源 → 申请权限关闭页面

closeBtn.addEventListener("click", async () => {

try {

// 1. 第一步:设置PWM值为0(发送到py文件)

await setPWM(0);

status.textContent = "正在退出,已设置PWM为0...";

// 2. 第二步:清理页面资源

clearInterval(voltageTimer); // 停止电压刷新定时器

slider.removeEventListener("input", updateUI); // 移除滑块监听

btn.removeEventListener("click", btn.onclick); // 移除LED按钮监听

bars.forEach(bar => bar.style.backgroundColor = "#e5e7eb"); // 重置UI

voltText.textContent = "0.00"; // 重置电压显示

valueText.textContent = "0"; // 重置PWM显示

slider.value = 0; // 重置滑块值

updateUI(); // 更新UI到PWM=0状态

// 3. 第三步:申请系统权限并尝试关闭页面

// 注:浏览器限制,仅在用户主动触发+特定场景下可关闭窗口

const userConfirm = confirm("是否确认关闭页面?\n(需授权才能关闭窗口权限)");

if (userConfirm) {

// 尝试关闭当前窗口(仅对通过window.open打开的窗口/部分浏览器生效)

if (window.close) {

window.close();

} else {

alert("浏览器限制,无法自动关闭页面,请手动关闭!");

}

} else {

status.textContent = "取消退出,已重置PWM为0";

}

} catch (e) {

status.textContent = "退出失败:" + e.message;

}

});

// PWM滑动

slider.addEventListener("input", () => {

updateUI();

setPWM(slider.value);

});

// 初始化

updateUI();

setPWM(slider.value);

getVoltage();

</script>

</body>

</html>

修改后 a.py(完整代码)

import socket

import network

import time

from machine import Pin, PWM, ADC

------------------ 全局配置 ------------------

LED配置 (板载GPIO2)

led = Pin(2, Pin.OUT)

led.value(0)

PWM配置 (引脚14,频率500Hz)

pwm_pin = 14

pwm_freq = 500

pwm = PWM(Pin(pwm_pin), freq=pwm_freq, duty=0)

PWM_MIN = 0

PWM_MAX = 1000

电压检测ADC配置 (GPIO34)

adc = ADC(Pin(34))

adc.atten(ADC.ATTN_11DB)

adc.width(ADC.WIDTH_12BIT)

------------------ 新增:电压读取函数 ------------------

def read_voltage(adc, R1=10000, R2=4700, samples=100):

"""

读取ADC平均值并计算分压后的电压值

  • 参数:

  • adc: ADC对象

  • R1: 分压电阻1 (默认10kΩ)

  • R2: 分压电阻2 (默认4.7kΩ)

  • samples: 采样数量 (默认100)

  • 返回值: 测量电压(单位: 伏)

"""

try:

total = 0

for _ in range(samples):

total += adc.read()

adc_value = total / samples

divider_ratio = (R1 + R2) / R2

voltage = (adc_value * 3.3 / 4095) * divider_ratio + 0.75 # 补偿+0.55V

return round(voltage, 2)

except Exception as e:

print("读取电压时出错:", e)

return 0.0

------------------ AP热点启动 ------------------

def start_ap():

ap = network.WLAN(network.AP_IF)

ap.active(False)

time.sleep(0.5)

ap.active(True)

ssid = 'ESP32_APTest'

password = '12345678'

ap.config(

essid=ssid,

password=password,

authmode=3,

max_clients=10

)

while not ap.active():

time.sleep(0.1)

print('='*30)

print('AP模式已启动')

print('SSID:', ssid)

print('密码:', password)

ip = ap.ifconfig()[0]

print('IP地址:', ip)

print('PWM控制: http://' + ip + '/pwm/500~1000')

print('电压获取: http://' + ip + '/voltage')

print('='*30)

return ip

------------------ Web服务器(API接口) ------------------

def web_server(ip):

web_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

web_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

web_socket.bind(("0.0.0.0", 80))

web_socket.listen(5)

print("服务器启动完成!")

print("访问示例:")

print(" LED开: http://%s/light/on" % ip)

print(" LED关: http://%s/light/off" % ip)

print(" PWM设值: http://%s/pwm/666 (数值500-1000)" % ip)

print(" 获取电压: http://%s/voltage" % ip)

print("="*40)

while True:

新增:每次循环都检测电压,低于7.2V则停止PWM输出

volt = read_voltage(adc)

if volt < 7.2:

pwm.duty(0) # 强制设为0,停止PWM输出

try:

conn, addr = web_socket.accept()

request = conn.recv(1024).decode("utf-8")

response = ""

if "/light/on" in request:

led.value(1)

response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nLED已点亮"

elif "/light/off" in request:

led.value(0)

response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nLED已熄灭"

elif "/pwm/" in request:

try:

pwm_str = request.split('/pwm/')[1].split(' ')[0]

target_val = int(pwm_str)

if target_val < PWM_MIN:

target_val = PWM_MIN

if target_val > PWM_MAX:

target_val = PWM_MAX

电压低于7.2V时,忽略设置并强制设0

if volt < 7.2:

pwm.duty(0)

response = f"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\n当前电压{volt}V < 7.2V,PWM已强制停止(duty=0),忽略设置值{target_val}"

else:

pwm.duty(target_val)

response = f"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\nPWM设置成功: {target_val} (当前电压: {volt}V)"

except:

response = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\n\r\n参数错误!请输入 500-1000 数字"

------------------ 新增:电压API ------------------

elif "/voltage" in request:

volt = read_voltage(adc)

response = f"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\n{volt}"

else:

response = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n\r\nAPI不存在"

conn.send(response.encode("utf-8"))

conn.close()

except:

try:

conn.close()

except:

pass

continue

------------------ 入口 ------------------

def main():

pwm.duty(1023)

time.sleep(2)

pwm.duty(400)

time.sleep(2)

ip = start_ap()

web_server(ip)

if name == "main":

main()

相关推荐
阿贵---2 小时前
使用Python进行PDF文件的处理与操作
jvm·数据库·python
winfredzhang2 小时前
FolderMover Pro用 Python + wxPython 构建安全高速的文件移动工具
python·wxpython·多线程·md5校验·容量筛选·失败回滚·实时进度
551只玄猫2 小时前
【基于python的金融分析和风险管理 学习笔记】中阶篇 第6章 分析利率和汇率
笔记·python·学习·金融·学习笔记·汇率·利率
一拳不是超人2 小时前
2026年最值得关注的JavaScript新特性:Signals,响应式编程的下一个十年
前端·javascript·响应式编程
探序基因2 小时前
R语言读取单细胞转录组基因表达矩阵loom文件
开发语言·r语言
大尚来也2 小时前
高并发架构下的缓存“三座大山”:穿透、雪崩与击穿的深度突围
开发语言
暮冬-  Gentle°2 小时前
移动设备上的C++优化
开发语言·c++·算法
2401_874732532 小时前
C++中的装饰器模式高级应用
开发语言·c++·算法
lars_lhuan2 小时前
Go atomic
开发语言·后端·golang