STM32硬體腳位接線:
Keil程式燒入操作:
前置設定
開啟 Keil 工程檔案後,進入 Options for Target 設定頁面 。
在 Debug 分頁選取 ST-Link Debugger 作為燒錄器 。
點擊工具列的 Build (F7) 進行程式編譯,確認無錯誤產生
確認開發板已上電且 ST-LINK 已連接
點擊 Download (F8) 將韌體燒錄至 STM32 晶片中
~燒入成功~
STM32硬體腳位接線:
Keil程式燒入操作:
前置設定
開啟 Keil 工程檔案後,進入 Options for Target 設定頁面 。
在 Debug 分頁選取 ST-Link Debugger 作為燒錄器 。
點擊工具列的 Build (F7) 進行程式編譯,確認無錯誤產生
確認開發板已上電且 ST-LINK 已連接
點擊 Download (F8) 將韌體燒錄至 STM32 晶片中
~燒入成功~
1.先將 PostgreSQL的pg_hba.conf 做設定
notepad
"C:\Program
Files\PostgreSQL\15\data\pg_hba.conf"
在檔案中的最後一行加上
host
all all 192.168.0.0/24 trust
如下
#
TYPE DATABASE USER ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all all scram-sha-256
# IPv4 local connections:
host all all 127.0.0.1/32 scram-sha-256
# IPv6 local connections:
host all all ::1/128 scram-sha-256
# Allow replication connections from localhost, by a user with the
# replication privilege.
local replication all scram-sha-256
host replication all 127.0.0.1/32 scram-sha-256
host replication all ::1/128 scram-sha-256
host all all 192.168.0.0/24 trust
存檔關閉,重啟 PostgreSQL 服務 。
SELECT type, address, auth_method FROM pg_hba_file_rules WHERE type = 'host';
有192.168.0.0 trust 代表有生效
2.建立 table
|
-- 啟用 TimescaleDB extension CREATE EXTENSION IF NOT EXISTS timescaledb;
-- 建立 table CREATE TABLE IF NOT EXISTS sensor_data ( time TIMESTAMPTZ NOT NULL DEFAULT NOW(), device_id TEXT NOT NULL, temperature FLOAT );
-- 轉成 hypertable SELECT create_hypertable('sensor_data', 'time', if_not_exists => TRUE); |
3.esp32程式碼:
|
#include <SPI.h> #include <Ethernet2.h> #include <ModbusMaster.h>
// ===== W5500 ===== #define W5500_CS 6 #define W5500_RST 14 byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; IPAddress dbServer(192, 168, 0, 220); // 你電腦的 IP EthernetClient client;
// ===== RS485 ===== #define RS_TX_PIN 5 #define RS_RX_PIN 4 #define RS_ENAMBLE_232_PIN 21 #define RS_ENAMBLE_422_PIN 16 #define RS_ENAMBLE_485_PIN 15 #define RS_BAUDRATE 9600 ModbusMaster node;
// ===== DB 設定 ===== const char* DB_NAME = "postgres"; const char* DB_USER = "postgres";
// ───────────────────────────────────────────── // PostgreSQL wire protocol helpers // ─────────────────────────────────────────────
// 讀掉一個完整的 server message,回傳 type byte char pgReadMessage() { unsigned long t = millis(); while (!client.available()) { if (millis() - t > 5000) { Serial.println("PG timeout"); return 0; } delay(10); } char msgType = (char)client.read();
// 讀 4 bytes 長度 uint8_t lb[4] = {0}; int got = 0; t = millis(); while (got < 4) { if (client.available()) lb[got++] = client.read(); if (millis() - t > 2000) break; } int bodyLen = (((int)lb[0]<<24)|((int)lb[1]<<16)|((int)lb[2]<<8)|(int)lb[3]) - 4;
// 如果是錯誤訊息,印出來 if (msgType == 'E' && bodyLen > 0 && bodyLen < 400) { uint8_t body[400]; got = 0; t = millis(); while (got < bodyLen) { if (client.available()) body[got++] = client.read(); if (millis() - t > 2000) break; } // PG 錯誤格式:一堆 "field_type + string\0" 組合 // 印出所有可讀字元 Serial.print("PG Error: "); for (int i = 0; i < got; i++) { if (body[i] >= 32 && body[i] < 127) Serial.print((char)body[i]); else if (body[i] == 0) Serial.print(" | "); } Serial.println(); return msgType; }
// 其他訊息:讀掉 body got = 0; t = millis(); while (got < bodyLen) { if (client.available()) { client.read(); got++; } if (millis() - t > 2000) break; } return msgType; }
// 送出有 type byte 的 message(一般訊息用) void pgSendMessage(char type, const uint8_t* body, int bodyLen) { int totalLen = bodyLen + 4; uint8_t hdr[5]; hdr[0] = (uint8_t)type; hdr[1] = (totalLen >> 24) & 0xFF; hdr[2] = (totalLen >> 16) & 0xFF; hdr[3] = (totalLen >> 8) & 0xFF; hdr[4] = (totalLen ) & 0xFF; client.write(hdr, 5); if (bodyLen > 0) client.write(body, bodyLen); }
// 等到收到 ReadyForQuery ('Z') void pgWaitReady() { char r = 0; for (int i = 0; i < 15 && r != 'Z'; i++) r = pgReadMessage(); }
// ───────────────────────────────────────────── // 連線到 PostgreSQL(Startup → Auth → Ready) // ───────────────────────────────────────────── bool pgConnect() { Serial.print("TCP -> DB..."); if (!client.connect(dbServer, 5432)) { Serial.println("FAIL"); return false; } Serial.println("OK");
// ── Startup message(沒有 type byte,格式特殊)── uint8_t buf[128]; int pos = 0; // Protocol 3.0 buf[pos++]=0x00; buf[pos++]=0x03; buf[pos++]=0x00; buf[pos++]=0x00; // "user\0<user>\0" memcpy(buf+pos,"user",4); pos+=4; buf[pos++]=0; int ul=strlen(DB_USER); memcpy(buf+pos,DB_USER,ul); pos+=ul; buf[pos++]=0; // "database\0<db>\0" memcpy(buf+pos,"database",8); pos+=8; buf[pos++]=0; int dl=strlen(DB_NAME); memcpy(buf+pos,DB_NAME,dl); pos+=dl; buf[pos++]=0; buf[pos++]=0; // terminator
int totalLen = pos + 4; uint8_t lenBytes[4] = { (uint8_t)(totalLen>>24),(uint8_t)(totalLen>>16), (uint8_t)(totalLen>>8), (uint8_t)(totalLen) }; client.write(lenBytes, 4); client.write(buf, pos);
// 伺服器回 'R'(auth request)或直接 'Z'(trust mode) char r = pgReadMessage(); Serial.printf("Startup resp: '%c'\n", r);
if (r == 'R') { // trust mode 下伺服器送 AuthenticationOk 然後一堆 parameter status // 直接等 ReadyForQuery pgWaitReady(); } else if (r != 'Z') { Serial.println("Unexpected startup response"); client.stop(); return false; }
Serial.println("DB ready!"); return true; }
// ───────────────────────────────────────────── // 執行 INSERT SQL // ───────────────────────────────────────────── bool pgInsert(float temperature) { char sql[200]; snprintf(sql, sizeof(sql), "INSERT INTO sensor_data (time, device_id, temperature) " "VALUES (NOW(), 'CH4_SENSOR', %.2f);", temperature );
int sLen = strlen(sql); uint8_t body[256]; memcpy(body, sql, sLen); body[sLen] = 0; pgSendMessage('Q', body, sLen + 1);
char resp = pgReadMessage(); Serial.printf("Insert resp: '%c'\n", resp);
if (resp == 'E') { Serial.println("SQL Error!"); pgWaitReady(); return false; } pgWaitReady(); return true; }
// ───────────────────────────────────────────── // Modbus callbacks // ───────────────────────────────────────────── void preTransmission() { while (Serial2.available()) Serial2.read(); } void postTransmission() { Serial2.flush(); delayMicroseconds(100); while (Serial2.available()) Serial2.read(); }
// ───────────────────────────────────────────── void setup() { Serial.begin(115200);
// W5500 reset pinMode(W5500_RST, OUTPUT); digitalWrite(W5500_RST, LOW); delay(20); digitalWrite(W5500_RST, HIGH); delay(100);
SPI.begin(12, 13, 11, W5500_CS); Ethernet.init(W5500_CS); IPAddress ip(192, 168, 0, 50); IPAddress gateway(192, 168, 0, 1); IPAddress subnet(255, 255, 255, 0); Ethernet.begin(mac, ip, gateway, gateway, subnet); Serial.print("IP: "); Serial.println(Ethernet.localIP());
// RS485 pinMode(RS_ENAMBLE_232_PIN, OUTPUT); pinMode(RS_ENAMBLE_422_PIN, OUTPUT); pinMode(RS_ENAMBLE_485_PIN, OUTPUT); digitalWrite(RS_ENAMBLE_232_PIN, LOW); digitalWrite(RS_ENAMBLE_422_PIN, LOW); digitalWrite(RS_ENAMBLE_485_PIN, HIGH); Serial2.begin(RS_BAUDRATE, SERIAL_8N1, RS_RX_PIN, RS_TX_PIN); node.begin(1, Serial2); node.preTransmission(preTransmission); node.postTransmission(postTransmission);
Serial.println("System Ready"); pgConnect(); }
void loop() { uint8_t result = node.readHoldingRegisters(43, 1);
if (result == node.ku8MBSuccess) { uint16_t raw = node.getResponseBuffer(0); float temperature = raw / 10.0; Serial.print("Temp: "); Serial.println(temperature);
// DB 連線斷掉就重連 if (!client.connected()) { Serial.println("Reconnecting..."); pgConnect(); }
if (pgInsert(temperature)) { Serial.println("Saved to DB!"); } } else { Serial.print("Modbus error: 0x"); Serial.println(result, HEX); }
delay(2000); } |
2-1.符合格式及建stored
procedure
{"id":10,"add":400082,"dt":1773408584755,"v":17754}
建立 table :
|
CREATE TABLE IF NOT EXISTS public.raw_sensor_data ( ts TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, machine_id INTEGER NOT NULL, tag_addr INTEGER NOT NULL, val DOUBLE PRECISION );
SELECT create_hypertable('public.raw_sensor_data', 'ts', if_not_exists => TRUE); |
建立 stored procedure :
|
CREATE OR REPLACE FUNCTION insert_machine_data_json(IN p_json_data jsonb) RETURNS void AS $$ BEGIN INSERT INTO public.raw_sensor_data (ts, machine_id, tag_addr, val) VALUES ( CURRENT_TIMESTAMP, (p_json_data->>'id')::INTEGER, (p_json_data->>'add')::INTEGER, (p_json_data->>'v')::DOUBLE PRECISION ); END; $$ LANGUAGE plpgsql; |
測試 procedure 可否寫入 :
|
SELECT insert_machine_data_json('{"id":10,"add":400082,"v":177.54}');
-- 確認有寫入 SELECT * FROM public.raw_sensor_data ORDER BY ts DESC LIMIT 5; |
2-2.調整esp32程式:
|
#include <SPI.h> #include <Ethernet2.h> #include <ModbusMaster.h>
// ===== W5500 ===== #define W5500_CS 6 #define W5500_RST 14 byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; IPAddress dbServer(192, 168, 0, 220); // 你電腦的 IP EthernetClient client;
// ===== RS485 ===== #define RS_TX_PIN 5 #define RS_RX_PIN 4 #define RS_ENAMBLE_232_PIN 21 #define RS_ENAMBLE_422_PIN 16 #define RS_ENAMBLE_485_PIN 15 #define RS_BAUDRATE 9600 ModbusMaster node;
// ===== DB 設定 ===== const char* DB_NAME = "postgres"; const char* DB_USER = "postgres";
// ───────────────────────────────────────────── // PostgreSQL wire protocol helpers // ─────────────────────────────────────────────
// 讀掉一個完整的 server message,回傳 type byte char pgReadMessage() { unsigned long t = millis(); while (!client.available()) { if (millis() - t > 5000) { Serial.println("PG timeout"); return 0; } delay(10); } char msgType = (char)client.read();
// 讀 4 bytes 長度 uint8_t lb[4] = {0}; int got = 0; t = millis(); while (got < 4) { if (client.available()) lb[got++] = client.read(); if (millis() - t > 2000) break; } int bodyLen = (((int)lb[0]<<24)|((int)lb[1]<<16)|((int)lb[2]<<8)|(int)lb[3]) - 4;
// 如果是錯誤訊息,印出來 if (msgType == 'E' && bodyLen > 0 && bodyLen < 400) { uint8_t body[400]; got = 0; t = millis(); while (got < bodyLen) { if (client.available()) body[got++] = client.read(); if (millis() - t > 2000) break; } // PG 錯誤格式:一堆 "field_type + string\0" 組合 // 印出所有可讀字元 Serial.print("PG Error: "); for (int i = 0; i < got; i++) { if (body[i] >= 32 && body[i] < 127) Serial.print((char)body[i]); else if (body[i] == 0) Serial.print(" | "); } Serial.println(); return msgType; }
// 其他訊息:讀掉 body got = 0; t = millis(); while (got < bodyLen) { if (client.available()) { client.read(); got++; } if (millis() - t > 2000) break; } return msgType; }
// 送出有 type byte 的 message(一般訊息用) void pgSendMessage(char type, const uint8_t* body, int bodyLen) { int totalLen = bodyLen + 4; uint8_t hdr[5]; hdr[0] = (uint8_t)type; hdr[1] = (totalLen >> 24) & 0xFF; hdr[2] = (totalLen >> 16) & 0xFF; hdr[3] = (totalLen >> 8) & 0xFF; hdr[4] = (totalLen ) & 0xFF; client.write(hdr, 5); if (bodyLen > 0) client.write(body, bodyLen); }
// 等到收到 ReadyForQuery ('Z') void pgWaitReady() { char r = 0; for (int i = 0; i < 15 && r != 'Z'; i++) r = pgReadMessage(); }
// ───────────────────────────────────────────── // 連線到 PostgreSQL(Startup → Auth → Ready) // ───────────────────────────────────────────── bool pgConnect() { Serial.print("TCP -> DB..."); if (!client.connect(dbServer, 5432)) { Serial.println("FAIL"); return false; } Serial.println("OK");
// ── Startup message(沒有 type byte,格式特殊)── uint8_t buf[128]; int pos = 0; // Protocol 3.0 buf[pos++]=0x00; buf[pos++]=0x03; buf[pos++]=0x00; buf[pos++]=0x00; // "user\0<user>\0" memcpy(buf+pos,"user",4); pos+=4; buf[pos++]=0; int ul=strlen(DB_USER); memcpy(buf+pos,DB_USER,ul); pos+=ul; buf[pos++]=0; // "database\0<db>\0" memcpy(buf+pos,"database",8); pos+=8; buf[pos++]=0; int dl=strlen(DB_NAME); memcpy(buf+pos,DB_NAME,dl); pos+=dl; buf[pos++]=0; buf[pos++]=0; // terminator
int totalLen = pos + 4; uint8_t lenBytes[4] = { (uint8_t)(totalLen>>24),(uint8_t)(totalLen>>16), (uint8_t)(totalLen>>8), (uint8_t)(totalLen) }; client.write(lenBytes, 4); client.write(buf, pos);
// 伺服器回 'R'(auth request)或直接 'Z'(trust mode) char r = pgReadMessage(); Serial.printf("Startup resp: '%c'\n", r);
if (r == 'R') { // trust mode 下伺服器送 AuthenticationOk 然後一堆 parameter status // 直接等 ReadyForQuery pgWaitReady(); } else if (r != 'Z') { Serial.println("Unexpected startup response"); client.stop(); return false; }
Serial.println("DB ready!"); return true; }
// ───────────────────────────────────────────── // 執行 INSERT SQL // ───────────────────────────────────────────── bool pgInsert(float val, int machine_id, int tag_addr) { char sql[256]; snprintf(sql, sizeof(sql), "SELECT insert_machine_data_json('{\"id\":%d,\"add\":%d,\"v\":%.4f}');", machine_id, tag_addr, val );
int sLen = strlen(sql); uint8_t body[256]; memcpy(body, sql, sLen); body[sLen] = 0; pgSendMessage('Q', body, sLen + 1);
char resp = pgReadMessage(); Serial.printf("Insert resp: '%c'\n", resp);
if (resp == 'E') { Serial.println("SQL Error!"); pgWaitReady(); return false; } pgWaitReady(); return true; }
// ───────────────────────────────────────────── // Modbus callbacks // ───────────────────────────────────────────── void preTransmission() { while (Serial2.available()) Serial2.read(); } void postTransmission() { Serial2.flush(); delayMicroseconds(100); while (Serial2.available()) Serial2.read(); }
// ───────────────────────────────────────────── void setup() { Serial.begin(115200);
// W5500 reset pinMode(W5500_RST, OUTPUT); digitalWrite(W5500_RST, LOW); delay(20); digitalWrite(W5500_RST, HIGH); delay(100);
SPI.begin(12, 13, 11, W5500_CS); Ethernet.init(W5500_CS); IPAddress ip(192, 168, 0, 50); IPAddress gateway(192, 168, 0, 1); IPAddress subnet(255, 255, 255, 0); Ethernet.begin(mac, ip, gateway, gateway, subnet); Serial.print("IP: "); Serial.println(Ethernet.localIP());
// RS485 pinMode(RS_ENAMBLE_232_PIN, OUTPUT); pinMode(RS_ENAMBLE_422_PIN, OUTPUT); pinMode(RS_ENAMBLE_485_PIN, OUTPUT); digitalWrite(RS_ENAMBLE_232_PIN, LOW); digitalWrite(RS_ENAMBLE_422_PIN, LOW); digitalWrite(RS_ENAMBLE_485_PIN, HIGH); Serial2.begin(RS_BAUDRATE, SERIAL_8N1, RS_RX_PIN, RS_TX_PIN); node.begin(1, Serial2); node.preTransmission(preTransmission); node.postTransmission(postTransmission);
Serial.println("System Ready"); pgConnect(); }
void loop() { uint8_t result = node.readHoldingRegisters(43, 1); if (result == node.ku8MBSuccess) { uint16_t raw = node.getResponseBuffer(0); float val = raw / 10.0; Serial.print("Temp: "); Serial.println(val);
// DB 連線斷掉就重連 if (!client.connected()) { Serial.println("Reconnecting..."); pgConnect(); }
// machine_id=10, tag_addr=43 ( Modbus register) if (pgInsert(val, 10, 43)) { Serial.println("Saved to DB!"); } } else { Serial.print("Modbus error: 0x"); Serial.println(result, HEX); } delay(2000); } |
成功寫入:
安裝PostgreSQL 15(不選擇太新的版本是因為怕不支援timescaledb)
先確認是否有timescaledb [SELECT * FROM pg_extension;]
沒有的話 先安裝 TimescaleDB:
一開始遇到問題(TimescaleDB
installer 找不到 PostgreSQL
的 pg_config
)
所以我把載檔移到了 這個路徑下
C:\Users\Hon\Downloads\timescaledb-postgresql-15-windows-amd64\timescaledb
有問題都按y
在PostgreSQL執行:
|
CREATE EXTENSION timescaledb; SELECT * FROM pg_extension; |
python api_server.py
|
from flask import Flask, request, jsonify, render_template import psycopg2
app = Flask(__name__)
conn = psycopg2.connect( host="localhost", database="postgres", user="postgres", password="1234" )
@app.route("/") def index(): return render_template("index.html")
@app.route("/data", methods=["GET"]) def get_data():
cur = conn.cursor()
cur.execute(""" SELECT time, temperature FROM sensor_data ORDER BY time DESC LIMIT 50 """)
rows = cur.fetchall()
data = [] for row in rows: data.append({ "time": row[0].strftime("%H:%M:%S"), "temp": row[1] })
cur.close()
response = jsonify(data[::-1]) response.headers["Cache-Control"] = "no-cache" return response
@app.route("/sensor", methods=["POST"]) def receive_data():
data = request.json
device = data.get("device", "unknown") temp = data.get("temp", 0)
cur = conn.cursor()
cur.execute( """ INSERT INTO sensor_data(time, device_id, temperature) VALUES (NOW(), %s, %s) """, (device, temp) )
conn.commit() cur.close()
print("Inserted:", device, temp)
return jsonify({"status": "ok"})
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
|
arduino程式:
|
#include <SPI.h> #include <Ethernet2.h> #include <ModbusMaster.h>
// ===== W5500 ===== #define W5500_CS 6 #define W5500_RST 14
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; IPAddress server(192,168,0,220); // 你的電腦IP
EthernetClient client;
// ===== RS485 ===== #define RS_TX_PIN 5 #define RS_RX_PIN 4 #define RS_ENAMBLE_232_PIN 21 #define RS_ENAMBLE_422_PIN 16 #define RS_ENAMBLE_485_PIN 15
#define RS_BAUDRATE 9600
ModbusMaster node;
// ===== Modbus callback ===== void preTransmission() { while (Serial2.available()) Serial2.read(); }
void postTransmission() { Serial2.flush(); delayMicroseconds(100); while (Serial2.available()) Serial2.read(); }
// ===== setup ===== void setup() { Serial.begin(115200);
// ===== W5500 reset ===== pinMode(W5500_RST, OUTPUT); digitalWrite(W5500_RST, LOW); delay(20); digitalWrite(W5500_RST, HIGH); delay(100);
// ⚠️ 用你成功的 SPI 腳位 SPI.begin(12,13,11,W5500_CS); Ethernet.init(W5500_CS);
IPAddress ip(192,168,0,50); IPAddress gateway(192,168,0,1); IPAddress subnet(255,255,255,0);
Ethernet.begin(mac, ip, gateway, gateway, subnet);
Serial.print("IP: "); Serial.println(Ethernet.localIP());
// ===== RS485 ===== pinMode(RS_ENAMBLE_232_PIN, OUTPUT); pinMode(RS_ENAMBLE_422_PIN, OUTPUT); pinMode(RS_ENAMBLE_485_PIN, OUTPUT);
digitalWrite(RS_ENAMBLE_232_PIN, LOW); digitalWrite(RS_ENAMBLE_422_PIN, LOW); digitalWrite(RS_ENAMBLE_485_PIN, HIGH);
Serial2.begin(RS_BAUDRATE, SERIAL_8N1, RS_RX_PIN, RS_TX_PIN);
node.begin(1, Serial2); node.preTransmission(preTransmission); node.postTransmission(postTransmission);
Serial.println("System Ready"); }
// ===== loop ===== void loop() {
uint8_t result; uint16_t raw;
// ✅ 用你成功的地址 result = node.readHoldingRegisters(43, 1);
if (result == node.ku8MBSuccess) {
raw = node.getResponseBuffer(0);
float temperature = raw / 10.0;
Serial.print("Temp: "); Serial.println(temperature);
// ===== HTTP 傳送 ===== if (client.connect(server, 5000)) {
String json = "{\"device\":\"CH4_SENSOR\",\"temp\":" + String(temperature) + "}";
client.println("POST /sensor HTTP/1.1"); client.println("Host: 192.168.0.220"); client.println("Content-Type: application/json"); client.print("Content-Length: "); client.println(json.length()); client.println(); client.println(json);
Serial.println("Data sent!");
client.stop(); } else { Serial.println("HTTP failed"); }
} else { Serial.print("Modbus error: "); Serial.println(result); }
delay(2000); } |
建立檔案: templates/index.html
你的專案/
├── api_server.py
└── templates/
└── index.html
打開: http://localhost:5000 (同個網域的都可以連線網站)
index.html:
|
<!DOCTYPE html> <html> <head> <title>Factory Dashboard</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> </head>
<body style="background:#111;color:white;font-family:sans-serif">
<h1>? 工廠監控畫面</h1>
<h2>溫度: <span id="temp">--</span> °C</h2>
<canvas id="chart" width="600" height="300"></canvas>
<script>
const ctx = document.getElementById('chart').getContext('2d');
let chart = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [{ label: 'Temperature', data: [], borderColor: 'lime', fill: false }] }, options: { scales: { y: { ticks: { color: "white" } }, x: { ticks: { color: "white" } } }, plugins: { legend: { labels: { color: "white" } } } } });
function updateData() { fetch('/data') .then(res => res.json()) .then(data => {
let labels = []; let temps = [];
data.forEach(d => { labels.push(d.time); temps.push(d.temp); });
chart.data.labels = labels; chart.data.datasets[0].data = temps; chart.update();
// 顯示最新溫度 let latest = temps[temps.length - 1]; document.getElementById("temp").innerText = latest;
// 警報(> 50°C) if (latest > 50) { document.body.style.background = "red"; } else { document.body.style.background = "#111"; } }); }
// 每2秒更新 setInterval(updateData, 2000);
</script>
</body> </html>
|
網頁畫面:
STM32 硬體腳位接線 : Keil 程式燒入操作 : 前置設定 開啟 Keil 工程檔案後,進入 Options for Target 設定頁面 。 在 Debug 分頁選取 ST-Link Debugger 作為燒錄器 。 點擊工具列的...