推送所有的文件到GIT
@ -2,20 +2,20 @@
|
|||||||
Rebuild target 'caiic_boot'
|
Rebuild target 'caiic_boot'
|
||||||
assembling startup_n32wb03x.s...
|
assembling startup_n32wb03x.s...
|
||||||
compiling boot_crc32.c...
|
compiling boot_crc32.c...
|
||||||
compiling n32wb03x_gpio.c...
|
compiling app_bootset.c...
|
||||||
compiling boot_cli.c...
|
|
||||||
compiling n32wb03x_rcc.c...
|
|
||||||
compiling app_ota.c...
|
compiling app_ota.c...
|
||||||
compiling main.c...
|
compiling main.c...
|
||||||
|
compiling n32wb03x_rcc.c...
|
||||||
compiling boot_usart.c...
|
compiling boot_usart.c...
|
||||||
compiling n32wb03x_usart.c...
|
compiling n32wb03x_usart.c...
|
||||||
|
compiling boot_cli.c...
|
||||||
compiling n32wb03x_dma.c...
|
compiling n32wb03x_dma.c...
|
||||||
|
compiling n32wb03x_gpio.c...
|
||||||
compiling system_n32wb03x.c...
|
compiling system_n32wb03x.c...
|
||||||
compiling app_bootset.c...
|
|
||||||
compiling n32wb03x_qflash.c...
|
compiling n32wb03x_qflash.c...
|
||||||
linking...
|
linking...
|
||||||
Program Size: Code=11910 RO-data=2650 RW-data=96 ZI-data=7288
|
Program Size: Code=11910 RO-data=2650 RW-data=96 ZI-data=7288
|
||||||
FromELF: creating hex file...
|
FromELF: creating hex file...
|
||||||
After Build - User command #1: fromelf --bin --output=.\bin\caiic_boot.bin .\Objects\caiic_boot.axf
|
After Build - User command #1: fromelf --bin --output=.\bin\caiic_boot.bin .\Objects\caiic_boot.axf
|
||||||
".\Objects\caiic_boot.axf" - 0 Error(s), 0 Warning(s).
|
".\Objects\caiic_boot.axf" - 0 Error(s), 0 Warning(s).
|
||||||
Build Time Elapsed: 00:00:01
|
Build Time Elapsed: 00:00:02
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"configurations" : [
|
"configurations" : [
|
||||||
{
|
{
|
||||||
"customPlaygroundType" : "local",
|
"customPlaygroundType" : "local",
|
||||||
"playground" : "standard",
|
"playground" : "custom",
|
||||||
"type" : "uni-app:app-android"
|
"type" : "uni-app:app-android"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
BIN
SmartAssiter/.icon_build/reg_backup/AeDebug_32_backup.reg
Normal file
BIN
SmartAssiter/.icon_build/reg_backup/AeDebug_64_backup.reg
Normal file
|
Before Width: | Height: | Size: 604 KiB |
|
Before Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 604 KiB |
|
Before Width: | Height: | Size: 38 KiB |
59
hardware_design/_analyze.mjs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
const { compData, wireData, pinData } = JSON.parse(readFileSync('H:/caiic_workspace/mothercup/hardware_design/_dump.json', 'utf8'));
|
||||||
|
|
||||||
|
function connPoint(p) {
|
||||||
|
const len = p.len || 20;
|
||||||
|
switch (p.rot) {
|
||||||
|
case 0: return { x: p.x - len, y: p.y }; // pin points left
|
||||||
|
case 90: return { x: p.x, y: p.y - len }; // pin points up
|
||||||
|
case 180: return { x: p.x + len, y: p.y }; // pin points right
|
||||||
|
case 270: return { x: p.x, y: p.y + len }; // pin points down
|
||||||
|
default: return { x: p.x, y: p.y };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSeg(px, py, ax, ay, bx, by, tol = 4) {
|
||||||
|
const cr = (bx - ax) * (py - ay) - (by - ay) * (px - ax);
|
||||||
|
if (Math.abs(cr) > tol * 20) return false;
|
||||||
|
return px >= Math.min(ax, bx) - tol && px <= Math.max(ax, bx) + tol &&
|
||||||
|
py >= Math.min(ay, by) - tol && py <= Math.max(ay, by) + tol;
|
||||||
|
}
|
||||||
|
function wpts(line) {
|
||||||
|
if (!Array.isArray(line)) return [];
|
||||||
|
if (Array.isArray(line[0])) return line.flat();
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// net flags (components with no designator but a name)
|
||||||
|
const netflags = compData.filter(c => (!c.d || c.d.trim()==='') && c.n);
|
||||||
|
|
||||||
|
function netsAt(pt) {
|
||||||
|
const s = new Set();
|
||||||
|
for (const w of wireData) {
|
||||||
|
const p = wpts(w.line);
|
||||||
|
for (let i = 0; i + 3 < p.length; i += 2) {
|
||||||
|
if (onSeg(pt.x, pt.y, p[i], p[i+1], p[i+2], p[i+3])) if (w.net) s.add(w.net);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const f of netflags) {
|
||||||
|
if (Math.abs(f.x - pt.x) <= 8 && Math.abs(f.y - pt.y) <= 8 && f.n) s.add(f.n);
|
||||||
|
}
|
||||||
|
return [...s];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('=== key component pin -> net ===');
|
||||||
|
for (const k of Object.keys(pinData)) {
|
||||||
|
const pd = pinData[k];
|
||||||
|
if (!pd) { console.log(`${k}: (not found)`); continue; }
|
||||||
|
console.log(`${k} (${pd.name}) @ (${pd.pos.x},${pd.pos.y})`);
|
||||||
|
for (const p of pd.pins) {
|
||||||
|
const pt = connPoint(p);
|
||||||
|
const nets = netsAt(pt);
|
||||||
|
console.log(` pin ${p.num} ${p.name||''} rot=${p.rot} len=${p.len} -> ${JSON.stringify(nets)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all components with positions (sorted by x then y) to see layout
|
||||||
|
console.log('\n=== all components (d:name @x,y) ===');
|
||||||
|
const sorted = [...compData].filter(c=>c.d && c.d.trim()).sort((a,b)=>a.x-b.x);
|
||||||
|
for (const c of sorted) console.log(` ${c.d}:${c.n||''} @ (${c.x},${c.y})`);
|
||||||
25
hardware_design/_diag.mjs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
const { compData, wireData, pinData } = JSON.parse(readFileSync('H:/caiic_workspace/mothercup/hardware_design/_dump.json', 'utf8'));
|
||||||
|
|
||||||
|
console.log('=== SW1/Q1/Q7/Q4/Q2/Q3/D1 pins (x,y,rot,len) ===');
|
||||||
|
for (const k of ['SW1','Q1','Q7','Q4','Q2','Q3','D1']) {
|
||||||
|
const pd = pinData[k];
|
||||||
|
if (!pd) continue;
|
||||||
|
console.log(`${k} @ (${pd.pos.x},${pd.pos.y})`);
|
||||||
|
for (const p of pd.pins) console.log(` pin${p.num} ${p.name} (${p.x},${p.y}) rot=${p.rot} len=${p.len}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n=== wires near power-switch region (x -2000..-1500, y -1100..-400) ===');
|
||||||
|
let cnt = 0;
|
||||||
|
for (const w of wireData) {
|
||||||
|
const p = Array.isArray(w.line) ? (Array.isArray(w.line[0]) ? w.line.flat() : w.line) : [];
|
||||||
|
let near = false, pts = [];
|
||||||
|
for (let i=0;i+1<p.length;i+=2) {
|
||||||
|
if (p[i]>=-2000 && p[i]<=-1500 && p[i+1]>=-1100 && p[i+1]<=-400) { near=true; pts.push([p[i],p[i+1]]); }
|
||||||
|
}
|
||||||
|
if (near && cnt < 60) {
|
||||||
|
cnt++;
|
||||||
|
console.log(` net=${w.net} pts=${JSON.stringify(pts.slice(0,10))}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('total near wires:', cnt);
|
||||||
33
hardware_design/_dump.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
const comps = await eda.sch_PrimitiveComponent.getAll();
|
||||||
|
const wires = await eda.sch_PrimitiveWire.getAll();
|
||||||
|
|
||||||
|
const compData = comps.map(c => {
|
||||||
|
let d=null,n=null,x=null,y=null;
|
||||||
|
try{d=c.getState_Designator();}catch(e){}
|
||||||
|
try{n=c.getState_Name();}catch(e){}
|
||||||
|
try{x=c.getState_X();y=c.getState_Y();}catch(e){}
|
||||||
|
return {d,n,x,y};
|
||||||
|
});
|
||||||
|
|
||||||
|
const wireData = wires.map(w => {
|
||||||
|
let net=null,line=null;
|
||||||
|
try{net=w.getState_Net();}catch(e){}
|
||||||
|
try{line=w.getState_Line();}catch(e){}
|
||||||
|
return {net,line};
|
||||||
|
});
|
||||||
|
|
||||||
|
// pins of key power-switch components
|
||||||
|
const key = ['SW1','Q1','Q7','Q4','Q2','Q3','D1','U1','U3','U5','BAT'];
|
||||||
|
const pinData = {};
|
||||||
|
for (const k of key) {
|
||||||
|
const c = comps.find(x => { try{return x.getState_Designator()===k;}catch(e){return false;} });
|
||||||
|
if (!c) { pinData[k]=null; continue; }
|
||||||
|
const pins = await eda.sch_PrimitiveComponent.getAllPinsByPrimitiveId(c.getState_PrimitiveId());
|
||||||
|
pinData[k] = {
|
||||||
|
pos: { x: c.getState_X(), y: c.getState_Y() },
|
||||||
|
name: c.getState_Name(),
|
||||||
|
pins: pins.map(p => ({ num:p.pinNumber, name:p.pinName, x:p.x, y:p.y, rot:p.rotation, len:p.pinLength }))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { compData, wireData, pinData };
|
||||||
1
hardware_design/_dump.json
Normal file
14
hardware_design/_dump_run.mjs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { readFileSync, writeFileSync } from 'node:fs';
|
||||||
|
import { request } from 'node:http';
|
||||||
|
const code = readFileSync('H:/caiic_workspace/mothercup/hardware_design/_dump.js', 'utf8');
|
||||||
|
const body = JSON.stringify({ code });
|
||||||
|
const req = request({ host: '127.0.0.1', port: 49620, path: '/execute', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, res => {
|
||||||
|
let d = '';
|
||||||
|
res.on('data', c => d += c);
|
||||||
|
res.on('end', () => {
|
||||||
|
const j = JSON.parse(d);
|
||||||
|
if (j.success) { writeFileSync('H:/caiic_workspace/mothercup/hardware_design/_dump.json', JSON.stringify(j.result)); console.log('saved', JSON.stringify(j.result).length); }
|
||||||
|
else console.log('FAIL', d);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.write(body); req.end();
|
||||||
63
hardware_design/_netlist.mjs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
const { compData, wireData, pinData } = JSON.parse(readFileSync('H:/caiic_workspace/mothercup/hardware_design/_dump.json', 'utf8'));
|
||||||
|
|
||||||
|
// Build a connectivity graph from wire segments. Snap to ~1 unit grid.
|
||||||
|
const key = (x,y) => `${Math.round(x)},${Math.round(y)}`;
|
||||||
|
const graph = new Map(); // node -> {neighbors:Set, label:null}
|
||||||
|
|
||||||
|
function node(k) { if(!graph.has(k)) graph.set(k,{n:new Set(),label:null}); return graph.get(k); }
|
||||||
|
function link(a,b){ const A=node(a),B=node(b); A.n.add(b); B.n.add(a); }
|
||||||
|
|
||||||
|
for (const w of wireData) {
|
||||||
|
const p = Array.isArray(w.line) ? (Array.isArray(w.line[0]) ? w.line.flat() : w.line) : [];
|
||||||
|
const pts = [];
|
||||||
|
for (let i=0;i+1<p.length;i+=2) pts.push(key(p[i],p[i+1]));
|
||||||
|
// connect consecutive points
|
||||||
|
for (let i=0;i+1<pts.length;i++) link(pts[i],pts[i+1]);
|
||||||
|
// if a wire has a net label, mark ALL its points with the label
|
||||||
|
if (w.net) for (const k of pts) node(k).label = w.net;
|
||||||
|
}
|
||||||
|
|
||||||
|
// net flags: components with no designator but a name -> mark nearest node
|
||||||
|
for (const f of compData.filter(c=>(!c.d||c.d.trim()==='')&&c.n)) {
|
||||||
|
const k = key(f.x,f.y);
|
||||||
|
const nd = node(k);
|
||||||
|
if (!nd.label) nd.label = f.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find connected components and collect labels
|
||||||
|
const visited = new Set();
|
||||||
|
function component(start) {
|
||||||
|
const comp = new Set(); const labels = new Set();
|
||||||
|
const stack=[start]; visited.add(start);
|
||||||
|
while(stack.length){
|
||||||
|
const k=stack.pop(); comp.add(k);
|
||||||
|
const nd=graph.get(k); if(nd.label) labels.add(nd.label);
|
||||||
|
for(const nb of nd.n) if(!visited.has(nb)){visited.add(nb);stack.push(nb);}
|
||||||
|
}
|
||||||
|
return {comp,labels:[...labels]};
|
||||||
|
}
|
||||||
|
|
||||||
|
const components=[];
|
||||||
|
for(const k of graph.keys()){ if(!visited.has(k)) components.push(component(k)); }
|
||||||
|
|
||||||
|
function compOf(x,y){
|
||||||
|
const k=key(x,y);
|
||||||
|
for(const c of components) if(c.comp.has(k)) return c;
|
||||||
|
// nearest node within 2 units
|
||||||
|
for(const c of components) for(const kk of c.comp){ const [a,b]=kk.split(',').map(Number); if(Math.abs(a-x)<=2&&Math.abs(b-y)<=2) return c; }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('=== key pin -> net (connectivity) ===');
|
||||||
|
for (const k of ['SW1','Q1','Q7','Q4','Q2','Q3','D1','U5','U1']) {
|
||||||
|
const pd=pinData[k]; if(!pd){console.log(k+': n/a');continue;}
|
||||||
|
console.log(`${k} (${pd.name})`);
|
||||||
|
for(const p of pd.pins){
|
||||||
|
const c=compOf(p.x,p.y);
|
||||||
|
console.log(` pin${p.num} ${p.name||''} @(${p.x.toFixed(0)},${p.y.toFixed(0)}) -> ${c?JSON.stringify(c.labels):'??'} (${c?c.comp.size:0} nodes)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n=== labeled nets (component size >= 2) ===');
|
||||||
|
for(const c of components) if(c.labels.length && c.comp.size>=2) console.log(` ${JSON.stringify(c.labels)} : ${c.comp.size} nodes`);
|
||||||
43
hardware_design/_trace.mjs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
const { compData, wireData, pinData } = JSON.parse(readFileSync('H:/caiic_workspace/mothercup/hardware_design/_dump.json', 'utf8'));
|
||||||
|
|
||||||
|
function onSeg(px, py, ax, ay, bx, by, tol = 4) {
|
||||||
|
const cr = (bx - ax) * (py - ay) - (by - ay) * (px - ax);
|
||||||
|
if (Math.abs(cr) > tol * 20) return false;
|
||||||
|
return px >= Math.min(ax, bx) - tol && px <= Math.max(ax, bx) + tol &&
|
||||||
|
py >= Math.min(ay, by) - tol && py <= Math.max(ay, by) + tol;
|
||||||
|
}
|
||||||
|
function wpts(line) { if (!Array.isArray(line)) return []; if (Array.isArray(line[0])) return line.flat(); return line; }
|
||||||
|
|
||||||
|
// net flags
|
||||||
|
const netflags = compData.filter(c => (!c.d || c.d.trim()==='') && c.n);
|
||||||
|
|
||||||
|
// For a pin at (px,py), find nets of wires touching it (pin connects at its own x,y)
|
||||||
|
function netsAt(px, py) {
|
||||||
|
const s = new Set();
|
||||||
|
for (const w of wireData) {
|
||||||
|
const p = wpts(w.line);
|
||||||
|
for (let i=0;i+3<p.length;i+=2) {
|
||||||
|
if (onSeg(px,py,p[i],p[i+1],p[i+2],p[i+3])) if (w.net) s.add(w.net);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const f of netflags) if (Math.abs(f.x-px)<=6 && Math.abs(f.y-py)<=6 && f.n) s.add(f.n);
|
||||||
|
return [...s];
|
||||||
|
}
|
||||||
|
|
||||||
|
// map each key pin to nets (pin connects at its own x,y)
|
||||||
|
console.log('=== key pin -> net (pin at own x,y) ===');
|
||||||
|
for (const k of ['SW1','Q1','Q7','Q4','Q2','Q3','D1','Q5','U1','U3','U5']) {
|
||||||
|
const pd = pinData[k];
|
||||||
|
if (!pd) { console.log(`${k}: not found`); continue; }
|
||||||
|
console.log(`${k} (${pd.name}) @(${pd.pos.x.toFixed(0)},${pd.pos.y.toFixed(0)})`);
|
||||||
|
for (const p of pd.pins) {
|
||||||
|
console.log(` pin${p.num} ${p.name||''} @(${p.x.toFixed(0)},${p.y.toFixed(0)}) -> ${JSON.stringify(netsAt(p.x,p.y))}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// also dump components near the power-switch region with positions
|
||||||
|
console.log('\n=== components x -2000..-1500, y -1100..-400 ===');
|
||||||
|
for (const c of compData.filter(c=>c.d&&c.d.trim()).sort((a,b)=>a.x-b.x)) {
|
||||||
|
if (c.x>=-2000 && c.x<=-1500 && c.y>=-1100 && c.y<=-400) console.log(` ${c.d}:${c.n||''} @(${c.x.toFixed(0)},${c.y.toFixed(0)})`);
|
||||||
|
}
|
||||||
144
hardware_design/开关机电路设计方案.md
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# 软开关机电路设计方案(长按 2 秒开机 + 自动关机)
|
||||||
|
|
||||||
|
> 适用于 MotherCup 整机:电池供电,按键软开关,防误触开机,无操作自动断电。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 需求
|
||||||
|
|
||||||
|
| 项 | 要求 |
|
||||||
|
|---|---|
|
||||||
|
| 开机 | **长按按键 2 秒**才开机(防误触,短按不开机) |
|
||||||
|
| 关机 | **自动关机**:无操作超时后自动断电(固件定时,超时值可配) |
|
||||||
|
| 掉电后 | 整机彻底断电(VSYS=0),待机电流仅 P-MOS 漏电流(<1µA 级) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 电路设计
|
||||||
|
|
||||||
|
```
|
||||||
|
VBAT(电池 3.0~4.2V)
|
||||||
|
│
|
||||||
|
│ S
|
||||||
|
Q1 ─────┤ P-MOS (AO3401)
|
||||||
|
│ D
|
||||||
|
├─────────────────────── VSYS(整机电源,去 LDO/升压/制冷片)
|
||||||
|
│ G(栅极节点 G)
|
||||||
|
│
|
||||||
|
R2 100K ─────────┼────┬──────────────────┬─────────────────────┐
|
||||||
|
(接 VBAT) │ │ │ │
|
||||||
|
│ │D │D │
|
||||||
|
│ Q2(N-MOS 2N7002) Q3(N-MOS 2N7002) │
|
||||||
|
│ │G │G │
|
||||||
|
│ │ │ │
|
||||||
|
│ ├─ R3 10K ─┐ ├── C1 3.3µF ── GND │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ├─ R5 100K ┴ GND ├── R4 10M ── GND │
|
||||||
|
│ │ (PWR_HOLD 下拉,MCU 掉电时确保 Q2 关) │
|
||||||
|
│ │ │ │
|
||||||
|
│ └──── PWR_HOLD (MCU 输出) │
|
||||||
|
│ R1 1M │
|
||||||
|
│ │ │
|
||||||
|
│ SW1 ──── VBAT │
|
||||||
|
│ │ │
|
||||||
|
│ (可选) KEY_DET (MCU 输入) │
|
||||||
|
│ Q2/Q3 源极 ── GND Q2/Q3 源极 ── GND
|
||||||
|
```
|
||||||
|
|
||||||
|
> 说明:Q2 的漏极与 Q3 的漏极都并到栅极节点 G,任一路导通都能把 Q1 栅极拉低。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 工作原理
|
||||||
|
|
||||||
|
### 3.1 开机(长按 2 秒)
|
||||||
|
|
||||||
|
1. 初始:`PWR_HOLD` 悬空(MCU 未上电),R5 把 Q2 栅极拉低 → Q2 关断;
|
||||||
|
R2 把 Q1 栅极拉高到 VBAT → Q1 关断 → VSYS=0,整机断电。
|
||||||
|
2. 按住 SW1:VBAT 经 SW1 → R1 给 C1 充电(τ = R1×C1 ≈ 3.3s)。
|
||||||
|
3. 约 **2.1 秒**后 C1 电压达到 Q3(2N7002) 阈值(~2V) → Q3 导通 → 拉低 Q1 栅极 → Q1 导通 → VSYS 得电。
|
||||||
|
4. MCU 上电启动,**第一时间置 `PWR_HOLD=高`** → Q2 导通 → 持续拉低 Q1 栅极(锁存开机)。
|
||||||
|
5. 松开 SW1:C1 经 R4 缓慢泄放,Q3 逐渐关断,但 Q2 已锁存,不影响供电。
|
||||||
|
6. 若按的时间不足 2 秒就松手:C1 没充到阈值,Q3 不导通,Q1 不开机(防误触)。
|
||||||
|
|
||||||
|
### 3.2 自动关机
|
||||||
|
|
||||||
|
1. MCU 运行中检测「无操作」超时(BLE 无指令 / 按键无动作,超时值可配,见 §6)。
|
||||||
|
2. 超时到 → 置 `PWR_HOLD=低` → Q2 关断 → R2 把 Q1 栅极拉高 → Q1 关断 → VSYS=0 → 整机断电。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 器件清单(BOM)
|
||||||
|
|
||||||
|
| 位号 | 器件 | 参数/型号 | 封装 | 数量 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| Q1 | P-MOSFET | AO3401(-30V/-4A,低压开启) | SOT23 | 1 | 高边主开关 |
|
||||||
|
| Q2 | N-MOSFET | 2N7002(Vth≈2V) | SOT23 | 1 | PWR_HOLD 锁存 |
|
||||||
|
| Q3 | N-MOSFET | 2N7002(Vth≈2V) | SOT23 | 1 | 长按开机触发 |
|
||||||
|
| R1 | 电阻 | 1MΩ ±5% | 0402 | 1 | C1 充电电阻(定 2s 延时) |
|
||||||
|
| R2 | 电阻 | 100KΩ | 0402 | 1 | Q1 栅极上拉(默认关机) |
|
||||||
|
| R3 | 电阻 | 10KΩ | 0402 | 1 | PWR_HOLD → Q2 栅极 |
|
||||||
|
| R4 | 电阻 | 10MΩ | 0402 | 1 | C1 泄放 |
|
||||||
|
| R5 | 电阻 | 100KΩ | 0402 | 1 | Q2 栅极下拉(MCU 掉电时确保 Q2 关) |
|
||||||
|
| C1 | 电容 | 3.3µF / 10V X7R | 0603 | 1 | 延时电容 |
|
||||||
|
| SW1 | 轻触按键 | 6×6 侧按/顶按 | — | 1 | 电源键 |
|
||||||
|
| (可选) D1 | 二极管 | BAT54 | SOT23 | 1 | 如 KEY_DET 需隔离时用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. RC 延时计算
|
||||||
|
|
||||||
|
- 充电:`Vc(t) = VBAT × (1 − e^(−t/RC))`,RC = R1×C1 = 1M×3.3µF = 3.3s。
|
||||||
|
- 2N7002 阈值约 2V,取 VBAT=4.2V:到达 2V 的时间
|
||||||
|
`t = −RC·ln(1 − 2/4.2) = −3.3·ln(0.524) ≈ 2.13s` ✓(约 2 秒)。
|
||||||
|
- 若需更精确 2.0s,可调 R1=910K 或 C1=3.0µF;**最终以实测为准微调 R1/C1**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 固件配合
|
||||||
|
|
||||||
|
### 6.1 GPIO
|
||||||
|
|
||||||
|
| 引脚 | 方向 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `PWR_HOLD` | 输出 | **`main()` 最开头就置高**(先于 banner/初始化),锁存开机;自动关机时置低 |
|
||||||
|
| `KEY_DET`(可选) | 输入 | 接 C1 节点/按键节点,运行中检测按键,用于复位自动关机定时器 |
|
||||||
|
|
||||||
|
> 注意:`PWR_HOLD` 必须在 MCU 复位后**立即**拉高,否则用户一松键就掉电。
|
||||||
|
|
||||||
|
### 6.2 自动关机定时(伪代码)
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 空闲超时值(可做成 APP_DATA 参数,appset 配置)
|
||||||
|
#define AUTO_OFF_TIMEOUT_MS 120000 /* 默认 2 分钟,可配 */
|
||||||
|
|
||||||
|
void activity_heartbeat(void) { // 任何活动(BLE 指令/按键)都调
|
||||||
|
s_last_activity = xTaskGetTickCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在某个常驻任务里(如 BLE 调度任务)周期性检查:
|
||||||
|
if ((xTaskGetTickCount() - s_last_activity) >= pdMS_TO_TICKS(AUTO_OFF_TIMEOUT_MS)) {
|
||||||
|
display_set(0, DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); // 可先清屏/提示
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
PWR_HOLD_LOW(); // 断电
|
||||||
|
for(;;) {} // 等电源跌落
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 注意事项
|
||||||
|
|
||||||
|
1. **开机时序**:`PWR_HOLD` 必须在 MCU 启动后立即置高,且 R5 保证 MCU 掉电时 Q2 可靠关断。
|
||||||
|
2. **C1 泄放**:R4 取 10M 时泄放 τ≈33s;若担心「短按后快速再长按」会把上次残余电荷算进去,可把 R4 调小(如 4.7M)或加受控泄放。
|
||||||
|
3. **待机功耗**:关机时只有 R2(100K→VBAT) 与 P-MOS 漏电流,约 0.04µA + 漏电流,可满足低待机要求。
|
||||||
|
4. **VBAT 下限**:Q3(2N7002) 阈值 2V,电池低到 ~2.5V 时 C1 充不到阈值会导致开不了机;若需支持更低电压,换低压开启的 N-MOS(如 SI2302,Vth<1V)并重算 RC。
|
||||||
|
5. **栅极保护**:Q1 栅极如需加 TVS/钳位(防静电/浪涌),可在 G 节点对 VBAT 加 4.7~6.8V 稳压管(可选)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 待确认
|
||||||
|
|
||||||
|
1. **自动关机超时值**:本文默认 2 分钟(`AUTO_OFF_TIMEOUT_MS`),你之前写的是「2 秒」——请确认用多少。
|
||||||
|
2. 是否需要 **KEY_DET**(按键复位自动关机定时 / 手动关机)。
|
||||||
|
3. 电池最低电压(决定 Q3 选型与 RC 是否需重算)。
|
||||||
@ -73,7 +73,7 @@
|
|||||||
<LExpSel>0</LExpSel>
|
<LExpSel>0</LExpSel>
|
||||||
</OPTXL>
|
</OPTXL>
|
||||||
<OPTFL>
|
<OPTFL>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>1</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<IsCurrentTarget>1</IsCurrentTarget>
|
<IsCurrentTarget>1</IsCurrentTarget>
|
||||||
</OPTFL>
|
</OPTFL>
|
||||||
@ -250,7 +250,7 @@
|
|||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<IsCurrentTarget>0</IsCurrentTarget>
|
<IsCurrentTarget>0</IsCurrentTarget>
|
||||||
</OPTFL>
|
</OPTFL>
|
||||||
<CpuCode>0</CpuCode>
|
<CpuCode>255</CpuCode>
|
||||||
<DebugOpt>
|
<DebugOpt>
|
||||||
<uSim>1</uSim>
|
<uSim>1</uSim>
|
||||||
<uTrg>0</uTrg>
|
<uTrg>0</uTrg>
|
||||||
@ -391,7 +391,7 @@
|
|||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<IsCurrentTarget>0</IsCurrentTarget>
|
<IsCurrentTarget>0</IsCurrentTarget>
|
||||||
</OPTFL>
|
</OPTFL>
|
||||||
<CpuCode>0</CpuCode>
|
<CpuCode>255</CpuCode>
|
||||||
<DebugOpt>
|
<DebugOpt>
|
||||||
<uSim>1</uSim>
|
<uSim>1</uSim>
|
||||||
<uTrg>0</uTrg>
|
<uTrg>0</uTrg>
|
||||||
@ -532,7 +532,7 @@
|
|||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<IsCurrentTarget>0</IsCurrentTarget>
|
<IsCurrentTarget>0</IsCurrentTarget>
|
||||||
</OPTFL>
|
</OPTFL>
|
||||||
<CpuCode>0</CpuCode>
|
<CpuCode>255</CpuCode>
|
||||||
<DebugOpt>
|
<DebugOpt>
|
||||||
<uSim>1</uSim>
|
<uSim>1</uSim>
|
||||||
<uTrg>0</uTrg>
|
<uTrg>0</uTrg>
|
||||||
@ -1410,6 +1410,18 @@
|
|||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<bDave2>0</bDave2>
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>..\src\app_display.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_display.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>10</GroupNumber>
|
||||||
|
<FileNumber>61</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
<PathWithFileName>..\src\app_params.c</PathWithFileName>
|
<PathWithFileName>..\src\app_params.c</PathWithFileName>
|
||||||
<FilenameWithoutPath>app_params.c</FilenameWithoutPath>
|
<FilenameWithoutPath>app_params.c</FilenameWithoutPath>
|
||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
@ -1417,7 +1429,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>10</GroupNumber>
|
<GroupNumber>10</GroupNumber>
|
||||||
<FileNumber>61</FileNumber>
|
<FileNumber>62</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@ -1437,7 +1449,7 @@
|
|||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>11</GroupNumber>
|
<GroupNumber>11</GroupNumber>
|
||||||
<FileNumber>62</FileNumber>
|
<FileNumber>63</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@ -1449,7 +1461,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>11</GroupNumber>
|
<GroupNumber>11</GroupNumber>
|
||||||
<FileNumber>63</FileNumber>
|
<FileNumber>64</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@ -1469,7 +1481,7 @@
|
|||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>12</GroupNumber>
|
<GroupNumber>12</GroupNumber>
|
||||||
<FileNumber>64</FileNumber>
|
<FileNumber>65</FileNumber>
|
||||||
<FileType>5</FileType>
|
<FileType>5</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@ -1489,7 +1501,7 @@
|
|||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>13</GroupNumber>
|
<GroupNumber>13</GroupNumber>
|
||||||
<FileNumber>65</FileNumber>
|
<FileNumber>66</FileNumber>
|
||||||
<FileType>5</FileType>
|
<FileType>5</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@ -1509,7 +1521,7 @@
|
|||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>14</GroupNumber>
|
<GroupNumber>14</GroupNumber>
|
||||||
<FileNumber>66</FileNumber>
|
<FileNumber>67</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@ -1521,7 +1533,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>14</GroupNumber>
|
<GroupNumber>14</GroupNumber>
|
||||||
<FileNumber>67</FileNumber>
|
<FileNumber>68</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@ -1533,7 +1545,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>14</GroupNumber>
|
<GroupNumber>14</GroupNumber>
|
||||||
<FileNumber>68</FileNumber>
|
<FileNumber>69</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@ -1545,7 +1557,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>14</GroupNumber>
|
<GroupNumber>14</GroupNumber>
|
||||||
<FileNumber>69</FileNumber>
|
<FileNumber>70</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@ -1557,7 +1569,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>14</GroupNumber>
|
<GroupNumber>14</GroupNumber>
|
||||||
<FileNumber>70</FileNumber>
|
<FileNumber>71</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@ -1569,7 +1581,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>14</GroupNumber>
|
<GroupNumber>14</GroupNumber>
|
||||||
<FileNumber>71</FileNumber>
|
<FileNumber>72</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
|||||||