顯示具有 ESP32 CAM 標籤的文章。 顯示所有文章
顯示具有 ESP32 CAM 標籤的文章。 顯示所有文章

2026年2月4日 星期三

[遙控甲蟲] 用Web控制甲蟲

 


Board:ESP32 Wrover Module
Arduino程式:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
/*
  ESP32-CAM Web 控制影像 + 搖桿 + 夾爪
  優化重點
  1) 控制與串流分成兩個 HTTP Server
     - 控制頁 + /set 在 port 80
     - MJPEG /stream 在 port 81避免串流阻塞控制
  2) 影像改用較順設定QVGA + 較高壓縮 + grab latest
  3) 伺服 PWM 固定用 LEDC channel 1~3避開相機用 channel 0
  4) 控制端傳送頻率調穩避免爆量 fetch 造成卡頓

  AP 模式
    http://192.168.4.1/        控制頁
    http://192.168.4.1:81/stream  影像串流

  /set?fb=1500&rl=1500&t=15001000~2000, 1500 中立
*/

#include <WiFi.h>
#include "esp_camera.h"
#include "esp_http_server.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include <esp_wifi.h>

// ========= 伺服/馬達腳位 =========
static const int TongsPin  = 14;  // 夾爪
static const int WheelRPin = 12;  // 右輪
static const int WheelLPin = 13;  // 左輪

// ========= LEDC channels避開 0相機用 channel 0 =========
static const int CH_TONGS  = 1;
static const int CH_WHEELR = 2;
static const int CH_WHEELL = 3;

// ========= WiFi AP 設定 =========
static const char *ssid = "2AC0";
static const char *password = "12345678";

// ========= 控制狀態1000~2000, 1500中立 =========
volatile int web_fb = 1500; // 前後
volatile int web_rl = 1500; // 左右
volatile int web_t  = 1500; // 夾爪
volatile unsigned long lastCmdMs = 0;

// ========= failsafe / deadband =========
static const uint32_t FAILSAFE_MS = 2000; // 2 秒無指令回中
static const int DEADBAND = 5;            // FB/RL 小於 5 當作 0

// ========= 相機翻轉設定 =========
int vFlip = 0;    // 1=上下翻轉
int hMirror = 0;  // 1=左右鏡像

// ========= 串流設定 =========
#define PART_BOUNDARY "123456789000000000000987654321"
static const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char *_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

// 兩個 server控制(80) / 串流(81)
httpd_handle_t control_httpd = NULL;
httpd_handle_t stream_httpd  = NULL;

sensor_t *s = nullptr;

// ========= 伺服 PWM微秒duty50Hz =========
static inline void servo_us_ch(int ch, int us) {
  us = constrain(us, 400, 2600);
  const int RES = 16;
  const uint32_t maxDuty = (1UL << RES) - 1; // 65535
  uint32_t duty = (uint32_t)((us / 20000.0) * maxDuty); // 50Hz => 20000us
  ledcWriteChannel(ch, duty);
}

static inline void servo_angle_ch(int ch, int angle) {
  angle = constrain(angle, 0, 180);
  int us = map(angle, 0, 180, 500, 2500); // 行程緊可改 600~2400
  servo_us_ch(ch, us);
}

// ========= 初始化伺服 =========
void initServo() {
  ledcAttachChannel(TongsPin,  50, 16, CH_TONGS);
  ledcAttachChannel(WheelRPin, 50, 16, CH_WHEELR);
  ledcAttachChannel(WheelLPin, 50, 16, CH_WHEELL);

  servo_angle_ch(CH_TONGS,  90);
  servo_angle_ch(CH_WHEELR, 90);
  servo_angle_ch(CH_WHEELL, 90);
}

// ========= 相機設定AI Thinker ESP32-CAM 常見腳位 =========
void setupCam() {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer   = LEDC_TIMER_0;

  config.pin_d0 = 5;
  config.pin_d1 = 18;
  config.pin_d2 = 19;
  config.pin_d3 = 21;
  config.pin_d4 = 36;
  config.pin_d5 = 39;
  config.pin_d6 = 34;
  config.pin_d7 = 35;

  config.pin_xclk = 0;
  config.pin_pclk = 22;
  config.pin_vsync = 25;
  config.pin_href  = 23;
  config.pin_sscb_sda = 26;
  config.pin_sscb_scl = 27;

  config.pin_pwdn  = 32;
  config.pin_reset = -1;

  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  // ===== 影像速度優化建議值=====
  // QQVGA(160x120) 幀率會明顯比 VGA 好很多
  config.frame_size   = FRAMESIZE_QQVGA; // 160x120;
  // 數字越大壓縮越高越省 CPU/頻寬畫質會下降一些
  config.jpeg_quality = 18;
  // 沒 PSRAM 的情況用 1有 PSRAM 可用 2
  config.fb_count     = psramFound() ? 2 : 1;

#if defined(CAMERA_GRAB_LATEST)
  config.grab_mode = CAMERA_GRAB_LATEST; // 優先送最新畫面降低延遲
#endif
#if defined(CAMERA_FB_IN_PSRAM)
  if (psramFound()) config.fb_location = CAMERA_FB_IN_PSRAM;
#endif

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed: 0x%x\n", err);
    return;
  }

  s = esp_camera_sensor_get();
  s->set_brightness(s, 1);
  s->set_contrast(s,   1);
  s->set_saturation(s, 1);
  s->set_wb_mode(s,    0);

  s->set_vflip(s, vFlip);
  s->set_hmirror(s, hMirror);
  s->set_framesize(s, FRAMESIZE_QQVGA);

  Serial.println("Camera Setup OK");
}

// ========= MJPEG 串流 handler跑在 port 81 的 server =========
static esp_err_t stream_handler(httpd_req_t *req) {
  camera_fb_t *fb = NULL;
  esp_err_t res = ESP_OK;
  size_t jpg_len = 0;
  uint8_t *jpg_buf = NULL;
  char part_buf[64];

  httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");

  while (true) {
    fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("Camera capture failed");
      res = ESP_FAIL;
    } else {
      if (fb->format != PIXFORMAT_JPEG) {
        bool ok = frame2jpg(fb, 80, &jpg_buf, &jpg_len);
        esp_camera_fb_return(fb);
        fb = NULL;
        if (!ok) {
          Serial.println("JPEG compression failed");
          res = ESP_FAIL;
        }
      } else {
        jpg_len = fb->len;
        jpg_buf = fb->buf;
      }
    }

    if (res == ESP_OK) {
      size_t hlen = snprintf(part_buf, sizeof(part_buf), _STREAM_PART, jpg_len);
      res = httpd_resp_send_chunk(req, part_buf, hlen);
    }
    if (res == ESP_OK) res = httpd_resp_send_chunk(req, (const char *)jpg_buf, jpg_len);
    if (res == ESP_OK) res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));

    if (fb) {
      esp_camera_fb_return(fb);
      fb = NULL;
      jpg_buf = NULL;
    } else if (jpg_buf) {
      free(jpg_buf);
      jpg_buf = NULL;
    }

    if (res != ESP_OK) break;

    // 讓出 CPU避免把控制端擠爆
    vTaskDelay(1);
  }
  return res;
}

// ========= 控制頁 HTML =========
static const char INDEX_HTML[] PROGMEM = R"HTML(
<!doctype html>
<html lang="zh-TW">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32-CAM Web 控制</title>
<style>
  body{font-family:Arial,Helvetica,sans-serif;background:#f5f5f5;margin:0;padding:14px}
  .wrap{max-width:920px;margin:0 auto}
  .card{background:#fff;border-radius:12px;padding:12px;margin-bottom:12px;box-shadow:0 2px 10px rgba(0,0,0,.06)}
  .row{display:flex;gap:12px;flex-wrap:wrap}
  .col{flex:1;min-width:280px}
  img{width:100%;border-radius:10px;background:#000}
  .joy{width:260px;height:260px;border-radius:16px;background:#eee;position:relative;touch-action:none;user-select:none}
  .dot{width:28px;height:28px;border-radius:50%;background:#333;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%)}
  button{padding:10px 12px;border:0;border-radius:10px;background:#2d7ef7;color:#fff;font-size:16px}
  button.gray{background:#6c757d}
  button.red{background:#d9534f}
  .btns{display:flex;gap:10px;flex-wrap:wrap;margin-top:10px}
  .hint{color:#555;font-size:14px;line-height:1.5}
  .val{font-family:ui-monospace,Consolas,monospace}
</style>
</head>
<body>
<div class="wrap">
  <div class="card">
    <div class="row">
      <div class="col">
        <div class="hint">影像port 81 /stream</div>
        <img id="cam" src="" alt="stream">
      </div>
      <div class="col">
        <div class="hint">搖桿控制差速混控):<span class="val" id="vv">fb=1500 rl=1500</span></div>
        <div class="joy" id="joy"><div class="dot" id="dot"></div></div>
        <div class="btns">
          <button id="stop" class="red">停止</button>
          <button id="center" class="gray">歸中</button>
        </div>

        <div class="hint" style="margin-top:10px">
          夾爪<span class="val" id="tv">t=1500</span>
        </div>
        <div class="btns">
          <button id="open">上舉</button>
          <button id="close" class="gray">下壓</button>
        </div>
      </div>
    </div>
  </div>

  <div class="card hint">
    操作拖曳搖桿控制前後/左右放開回中若 WiFi 抖動2 秒無指令會自動停止
  </div>
</div>

<script>
let fb=1500, rl=1500, t=1500;

// 控制送出頻率建議 40~60ms16~25Hz
const SEND_INTERVAL_MS = 50;
let lastSend=0;

function clamp(x,a,b){return Math.max(a,Math.min(b,x));}

function send(force=false){
  const now=Date.now();
  if(!force && (now-lastSend < SEND_INTERVAL_MS)) return;
  lastSend=now;

  document.getElementById('vv').textContent = `fb=${fb} rl=${rl}`;
  document.getElementById('tv').textContent = `t=${t}`;

  fetch(`http://${location.host}/set?fb=${fb}&rl=${rl}&t=${t}`, {cache:"no-store"})
    .catch(()=>{});
}

function moveDot(nx, ny){
  const joy = document.getElementById('joy');
  const dot = document.getElementById('dot');
  const w = joy.clientWidth, h = joy.clientHeight;
  const cx = w/2, cy = h/2;
  const r  = Math.min(w,h)*0.42;
  dot.style.left = (cx + nx*r) + 'px';
  dot.style.top  = (cy + ny*r) + 'px';
}

function setCenter(){
  fb=1500; rl=1500;
  moveDot(0,0);
  send(true);
}
function setStop(){
  fb=1500; rl=1500; t=1500;
  moveDot(0,0);
  send(true);
}

const joy = document.getElementById('joy');

function calcFromEvent(e){
  const rect = joy.getBoundingClientRect();
  const x = (e.clientX - rect.left) - rect.width/2;
  const y = (e.clientY - rect.top)  - rect.height/2;
  const r = Math.min(rect.width, rect.height)*0.42;

  let nx = clamp(x / r, -1, 1);
  let ny = clamp(y / r, -1, 1);

  // 建議先600太敏感再降太鈍再升
  fb = Math.round(1500 + (-ny)*600);
  rl = Math.round(1500 + ( nx)*600);

  fb = clamp(fb,1000,2000);
  rl = clamp(rl,1000,2000);

  moveDot(nx, ny);
  send(false);
}

let active=false;

joy.addEventListener('pointerdown', (e)=>{
  active=true;
  joy.setPointerCapture(e.pointerId);
  calcFromEvent(e);
});
joy.addEventListener('pointermove', (e)=>{
  if(!active) return;
  calcFromEvent(e);
});
joy.addEventListener('pointerup', ()=>{
  active=false;
  setCenter();
});
joy.addEventListener('pointercancel', ()=>{
  active=false;
  setCenter();
});

document.getElementById('stop').onclick   = setStop;
document.getElementById('center').onclick = setCenter;

// 上舉 / 下壓仍用 t=2000 / 1000角度在 ESP32 端映射
document.getElementById('open').onclick = ()=>{
  t=2000; send(true);
};
document.getElementById('close').onclick = ()=>{
  t=1000; send(true);
};

// 影像改連 port 81
document.getElementById('cam').src = `http://${location.hostname}:81/stream`;

moveDot(0,0);
send(true);
</script>
</body>
</html>
)HTML";

// ========= handler/ =========
static esp_err_t index_handler(httpd_req_t *req) {
  httpd_resp_set_type(req, "text/html; charset=utf-8");
  return httpd_resp_send(req, INDEX_HTML, HTTPD_RESP_USE_STRLEN);
}

// ========= handler/set =========
static esp_err_t set_handler(httpd_req_t *req) {
  char qs[128];
  char v[16];

  int fb = web_fb, rl = web_rl, tt = web_t;

  if (httpd_req_get_url_query_str(req, qs, sizeof(qs)) == ESP_OK) {
    if (httpd_query_key_value(qs, "fb", v, sizeof(v)) == ESP_OK) fb = atoi(v);
    if (httpd_query_key_value(qs, "rl", v, sizeof(v)) == ESP_OK) rl = atoi(v);
    if (httpd_query_key_value(qs, "t",  v, sizeof(v)) == ESP_OK) tt = atoi(v);
  }

  fb = constrain(fb, 1000, 2000);
  rl = constrain(rl, 1000, 2000);
  tt = constrain(tt, 1000, 2000);

  web_fb = fb;
  web_rl = rl;
  web_t  = tt;
  lastCmdMs = millis();

  // Debug
  // Serial.printf("SET fb=%d rl=%d t=%d\n", web_fb, web_rl, web_t);

  httpd_resp_set_type(req, "text/plain");
  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  return httpd_resp_sendstr(req, "OK");
}

// ========= 啟動控制 Serverport 80 =========
void startControlServer() {
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
  config.ctrl_port   = 32768;   // 第二個 server 需要不同 ctrl_port
  config.max_open_sockets = 6;

  httpd_uri_t uri_index = { .uri="/", .method=HTTP_GET, .handler=index_handler, .user_ctx=NULL };
  httpd_uri_t uri_set   = { .uri="/set", .method=HTTP_GET, .handler=set_handler, .user_ctx=NULL };

  if (httpd_start(&control_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(control_httpd, &uri_index);
    httpd_register_uri_handler(control_httpd, &uri_set);
  }
  Serial.println("Control server: port 80 ( / , /set )");
}

// ========= 啟動串流 Serverport 81 =========
void startStreamServer() {
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 81;
  config.ctrl_port   = 32769;
  config.max_open_sockets = 3;

  httpd_uri_t uri_stream = { .uri="/stream", .method=HTTP_GET, .handler=stream_handler, .user_ctx=NULL };

  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &uri_stream);
  }
  Serial.println("Stream server: port 81 ( /stream )");
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
  Serial.begin(115200);

  // WiFi 穩定度/延遲優化
  WiFi.mode(WIFI_AP);
  WiFi.setSleep(false);
  esp_wifi_set_ps(WIFI_PS_NONE);
  WiFi.softAP(ssid, password, 1, false, 4); // channel=1, max conn=4
  WiFi.setTxPower(WIFI_POWER_19_5dBm);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP: http://");
  Serial.println(IP);

  setupCam();
  initServo();

  startControlServer();
  startStreamServer();

  // 關掉板上閃光通常 GPIO4
  pinMode(4, OUTPUT);
  digitalWrite(4, LOW);

  lastCmdMs = millis();
}

void loop() {
  // failsafe
  if (millis() - lastCmdMs > FAILSAFE_MS) {
    web_fb = 1500;
    web_rl = 1500;
    web_t  = 1500;
  }

  // FB/RL 映射到 -90~90
  int FB = map(web_fb, 1000, 2000, -90, 90);
  int RL = map(web_rl, 1000, 2000, -90, 90);

  if (abs(FB) < DEADBAND) FB = 0;
  if (abs(RL) < DEADBAND) RL = 0;

  int RWheel = 90 + FB + RL;
  int LWheel = 90 - FB + RL;

  RWheel = constrain(RWheel, 0, 180);
  LWheel = constrain(LWheel, 0, 180);

  // 夾爪上舉=120t=2000)、下壓=75t=1000
  int tongAngle = map(web_t, 1000, 2000, 75, 120);

  servo_angle_ch(CH_TONGS,  tongAngle);
  servo_angle_ch(CH_WHEELR, RWheel);
  servo_angle_ch(CH_WHEELL, LWheel);

  // 控制迴圈不需要太慢縮短 delay 讓操控更跟手
  delay(5);
}


2024年10月13日 星期日

[8051 and ESP32 CAM] 實作雙晶片的連線

參考教材1:單晶片微處理機實習,黃嘉輝,台科大出版

參考教材2:使用ESP32開發版與Arduino C程式語言,尤濬哲 ,台科大出版

前一篇文章:[8051] 實作檢查碼,若有錯則顯示錯誤

前幾篇文章都是設定在通訊鮑率為9600bps,但在這一篇我們選擇1200bps,其原因是因為實驗室的實習板是採用12M Hz石英振盪器,您可以參考:8051 Baud Rate Calculator

8051程式碼(新增恢復連線的功能):

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#include <regx51.h>

#define STX 0x02
#define ETX 0x03
unsigned char SendBuf[]="51015100peanut10";
unsigned char ReceiveBuf[16];
int receive_cnt=0;
unsigned char buf;
int count=1000;									//1秒=1000*1ms
int on_line_sec=0;							//連線秒數
int on_line_status=0;						//onlne or offline
int is_receive=0;								//是否有接收
int is_check=0; 								//檢查碼
unsigned char calculateLRC(unsigned char *d, int length);

// 使用函數的設定區
extern void LCD_Init(void);
extern void LCD_Out(unsigned char x, unsigned char y, unsigned char *text);
extern void LCD_Out_Cp(unsigned char *text);
extern void LCD_Chr(unsigned char x, unsigned char y, unsigned char c);
extern void LCD_Chr_Cp(unsigned char c);
extern void LCD_Cmd(unsigned char cmd);
extern void LCD_GotoXY(unsigned char x, unsigned char y);

// 使用命令的設定區
extern unsigned char LCD_CURSOR_ON;
extern unsigned char LCD_CURSOR_OFF;
extern unsigned char LCD_CLEAR;

void init_UART(unsigned int baudrate)
{
		SCON=0x52;
		TMOD=0x20;
#	TH1=256-((11059200/384)/baudrate);
	  TH1=0xE6;
		TL1=TH1;
		TR1=1;
}

void T0_int(void) interrupt 1
{
	TL0=(8192-1000)%32;						//重設設定值
	TH0=(8192-1000)/32;
	if(count==0)					//連線已經有1秒了嗎?
	{
		if(on_line_status)
			{
				on_line_sec++;
				on_line_sec%=100;
				LCD_Chr(2,16, on_line_sec%10+0x30);    //將接收到的資料送往顯示
				LCD_Chr(2,15, on_line_sec/10+0x30);    //將接收到的資料送往顯示
			}
		if(!is_receive)
			{
				LCD_Cmd(LCD_CLEAR);
				LCD_Out(2,1,"offline  ");
				on_line_status=0;
				on_line_sec=0;
			}
		else
			LCD_Out(2,1,"online   ");
		is_receive=0;	 
		count=1000;									//1秒count
	}
	else
		count--;										//count每1ms減1
}

void UART_int(void) interrupt 4		//串列中斷函式	
{
	if(RI==1)												//是不為接收中斷?
	{
		RI=0;													//完成後清除RI
		buf=SBUF;											//將接到的資料給buf
		if(is_check){
			is_check=0;
			if(buf != calculateLRC(ReceiveBuf, 16))
				LCD_Out(2,1,"check err");
			else
				LCD_Out(2,1,"online   ");
		}
		else if(buf == STX)
			receive_cnt=0;
		else if (buf == ETX)
		{
			LCD_Out(1,1,ReceiveBuf);
			is_check=1;
		}
		else
		{			
			ReceiveBuf[receive_cnt++]=buf;
			receive_cnt%=16;
		}
		is_receive=1;
		if(!on_line_status){
			on_line_sec=0;
			on_line_status=1;
			LCD_Out(2,1,"online   ");
			count=1000;	
		}
	}
	else
		TI=0;													//如果是傳送中斷,則清除TI,下次才方再傳送
}

void Delay_ms(unsigned int count) 						//延遲count*1ms函式
{
				unsigned int i,j;
				for(i=0;i<count;i++)
								for(j=0;j<123;j++);
}

unsigned char calculateLRC(unsigned char *d, int length)
{
	  unsigned char LRC = 0;  // LRC 的初始值
		int i;
    for (i = 0; i < length; i++) {
        LRC ^= d[i];  // 進行 XOR 運算
    }
    LRC ^= ETX;  // XOR 運算 ETX
    return LRC;
}

void main(void)
{
			unsigned int i;
			init_UART(1200);						//設定串列傳輸模式-9600bps
			ES=1;												//開啟串列中斷
			ET0=1;											//啟動計時器1
			EA=1;	                      //開啟總中斷
			TR0=1; 											//啟動計時器
	
			LCD_Init();									// LCD 初始化
			LCD_Cmd(LCD_CURSOR_ON);			// 將游標打開
			LCD_Chr(1,7,'8');						// LCD 在(1,7)位置顯示8
			LCD_Chr_Cp('0');						// LCD 在(1,8)位置顯示0
			LCD_Out_Cp("51");						// LCD 在(1,9)位置顯示51
			LCD_Cmd(LCD_CURSOR_OFF);		// 將游標關閉
			LCD_Out(1,1," ");	
			LCD_Out(2,1,"offline");			// LCD 在(2,1)位置顯示offline
			LCD_Out(2,15,"00");

			while(1)
			{
				SBUF=STX;
				Delay_ms(10);
				for(i=0;i<16;i++)
				{
					SBUF=SendBuf[i];					//傳送0-9
					Delay_ms(10);
				}
				SBUF=ETX;
				Delay_ms(10);
				SBUF=calculateLRC(SendBuf, 16);
				Delay_ms(1000);
			}
}

ESP32 CAM程式(感謝小霸王科技 施經理提供):

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
 *   按下IO0傳給8051
 * 
 */

// 定義引腳
const int buttonPin = 0;  // IO0 引腳
const int ledPin = 4;     // LED 引腳(通常是 GPIO 4)
#define RXD2 12
#define TXD2 13

// 定義字節常量
byte STX = 0x02;  // 開始標記
byte ETX = 0x03;  // 結束標記
byte ACK = 0x06;  // 確認標記

// 數據陣列
byte dataBytes[16] = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48};
byte receivedData[18];  // 接收的數據
int receivedIndex = 0; // 接收數據的索引


// 定義變量

int dataIndex = 0;      // 接收數據的索引
bool receiving = false;  // 是否正在接收數據

unsigned long previousMillis = 0;  // 儲存上次更新的時間
const long interval = 1000;          // 設定間隔時間

// 計算 LRC 的函數
byte calculateLRC(byte *data, size_t length) {
    byte LRC = 0;  // LRC 的初始值
    for (size_t i = 0; i < length; i++) {
        LRC ^= data[i];  // 進行 XOR 運算
    }
    LRC ^= ETX;  // XOR 運算 ETX
    return LRC;
}

// 發送數據的函數
void sendData(byte *data, size_t length) {
    // 計算 LRC
    byte LRC = calculateLRC(data, length);

    // 構建完整數據包
    Serial2.write(STX);  // 發送 STX
    Serial2.write(data, length);  // 發送 DATA
    Serial2.write(ETX);  // 發送 ETX
    Serial2.write(LRC);  // 發送 LRC

    // 發送 ACK
//    Serial2.write(ACK);
}
// 處理接收的數據
void handleIncomingData() {
    while (Serial2.available()) {
        byte incomingByte = Serial2.read(); // 讀取字節
//        Serial.print("incomm:");
//        Serial.println(incomingByte,HEX);

        // 檢查是否是 STX
        if (incomingByte == STX) {
            receivedIndex = 0; // 重置索引,不存儲 STX
        } else if (receivedIndex < sizeof(receivedData)) {
            // 在 STX 和 ETX 之間接收數據
            receivedData[receivedIndex++] = incomingByte;
        }

        // 檢查是否接收到完整數據包
        if (receivedIndex >= 3 && receivedData[receivedIndex - 2] == ETX) {
            byte LRC = calculateLRC(receivedData, receivedIndex - 2); // 計算 LRC,不包括 ETX 和 LRC
            byte receivedLRC = receivedData[receivedIndex - 1]; // 最後一個字節是 LRC

            // 打印接收到的數據和 LRC
            Serial.print("Received Data: ");
            for (int i = 0; i < receivedIndex; i++) {
                Serial.print(receivedData[i], HEX); // 以十六進制格式顯示
                Serial.print(" ");
            }
            Serial.print("LRC: ");
            Serial.print(LRC, HEX);
            Serial.print("    receivedLRC: ");
            Serial.println(receivedLRC, HEX);

            // 檢查 LRC 是否匹配
            if (LRC == receivedLRC) {
              processReceivedData(receivedData, receivedIndex - 2);
            }
            else{
                Serial.println("LRC check failed!");
            }
            receivedIndex = 0; // 重置索引以接收下一個數據包
        }
    }
}

// 處理接收到的數據
void processReceivedData(byte *data, int length) {
    Serial.print("Received Data: ");
    for (int i = 0; i < length; i++) {
        dataBytes[i] = data[i];
        Serial.print(data[i], HEX); // 以十六進制格式顯示
        Serial.print(" ");
    }
    Serial.println();
}
void setup() {
    Serial.begin(115200);  // 初始化串口,波特率為 115200

    Serial2.begin(1200, SERIAL_8N1, RXD2, TXD2);
    pinMode(buttonPin, INPUT_PULLUP);  // 設定 IO0 為輸入,並啟用內部上拉電阻
    pinMode(ledPin, OUTPUT);             // 設定 LED 引腳為輸出
    digitalWrite(ledPin, LOW);           // 確保 LED 初始狀態為熄滅
}
void loop() {
    // 讀取按鈕狀態
    int buttonState = digitalRead(buttonPin);
    
    // 檢查按鈕是否被按下
    if (buttonState == LOW) { // 按鈕按下時,IO0 為 LOW
        dataBytes[0] = 0x48;  // 設置數據陣列的第一個字節
        //Serial.println("Button State: Pressed"); // 傳送狀態到 SERIAL
    } else {
        dataBytes[0] = 0x4C;  // 設置數據陣列的第一個字節
        //Serial.println("Button State: Not Pressed"); // 傳送狀態到 SERIAL
    }
    
 
    handleIncomingData();  // 處理接收到的數據
    // 檢查 dataBytes[1] 是否等於 0x48
    if (dataBytes[1] == 0x48) {
        digitalWrite(ledPin, HIGH);  // 點亮 LED
    } else {
        digitalWrite(ledPin, LOW);   // 熄滅 LED
    }

    yield(); // 讓 ESP32 檢查看門狗定時器
    //delay(500); // 延遲 500 毫秒
    // 非阻塞延遲
    unsigned long currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        // 在這裡執行需要延遲的代碼
        // 發送數據到 Serial2
        Serial.println("millis");
        sendData(dataBytes, sizeof(dataBytes) / sizeof(dataBytes[0]));
    } 
}