2026年2月8日 星期日

ESP32 NODEMCU-32S 發送遙控器紅外線訊號

目的:

將之前從電風扇紅外線遙控器讀取的訊號:
ESP32 NODEMCU-32S 讀取遙控器紅外線訊號
在 Arduino IDE 的 Serial Monitor 用鍵盤輸入1~7,讓紅外線發射器發送遙控器上 7 個按鈕的訊號,控制電風扇。

環境:

Win10、Arduino IDE 2.3.7、ESP32 NODEMCU-32S、紅外線發射器(電壓:1.4V~1.6V,電流:20mA)、電阻(90 歐姆)


[關於紅外線接收]

紅外線發射器是一種 LED,發出的光是紅外線。
我看到有兩種紅外線發射器,一種是長短兩隻腳的紅外線 LED,一種是做成做成插上就可使用的電路板模組。
我想自己接電組,所以使用兩隻腳的紅外線 LED。
遙控器發出的紅外線,常用波長有 850nm、940nm 兩種,我不確定原本遙控器是哪種,所以都買來測試。
我買時看到有外觀有 3mm、5mm 兩種大小,也都買來測試。

先講測試結果

  • 850nm、940nm,兩種都可以正常發送紅外線,遙控電風扇。
    個人感覺 940nm 在我的電風扇似乎稍微好一點。
  • 3mm、5mm 兩種大小,兩種都可以正常發送紅外線,遙控電風扇。
    比較大顆的 5mm 感覺有效角度大些,比較不用對準電風扇。




引腳線路接法:

  • GPIO27 (ESP32)  ---------- 電阻(90歐姆) ---------- 正極(+)  (紅外線 LED 長腳)
  • GND (ESP32)  ----------   負極(-) (紅外線 LED 短腳)


安裝 IRremote library:

使用 IRremote 這個 library 處理紅外線訊號,
先到「Tools」->「Manage Libraries...」確認已安裝 IRremote 。


紅外線訊號:

之前讀取的紅外線訊號如後,可知是用 NEC 通訊協議。
用 IrSender.sendNEC(<aAddress>,  <aCommand>, <numberOfRepeats>) 發射訊號。

電源開關
14:32:44.974 -> EF1FEFE
14:32:44.974 -> Protocol=NEC Address=0xFEFE Command=0xF1 Raw-Data=0xEF1FEFE 32 bits LSB first Gap=3276750us Duration=75150us
14:32:45.041 -> Send with: IrSender.sendNEC(0xFEFE, 0xF1, <numberOfRepeats>);

減風量
14:23:42.992 -> FB04FEFE
14:23:42.992 -> Protocol=NEC Address=0xFEFE Command=0x4 Raw-Data=0xFB04FEFE 32 bits LSB first Gap=3276750us Duration=75100us
14:23:43.100 -> Send with: IrSender.sendNEC(0xFEFE, 0x4, <numberOfRepeats>);

加風量
14:25:02.445 -> F906FEFE
14:25:02.445 -> Protocol=NEC Address=0xFEFE Command=0x6 Raw-Data=0xF906FEFE 32 bits LSB first Gap=3276750us Duration=75150us
14:25:02.576 -> Send with: IrSender.sendNEC(0xFEFE, 0x6, <numberOfRepeats>);

指示燈開關
14:25:50.912 -> CF3FEFE
14:25:50.912 -> Protocol=NEC Address=0xFEFE Command=0xF3 Raw-Data=0xCF3FEFE 32 bits LSB first Gap=3276750us Duration=75150us
14:25:50.997 -> Send with: IrSender.sendNEC(0xFEFE, 0xF3, <numberOfRepeats>);

轉頭開關
14:27:12.477 -> DF2FEFE
14:27:12.477 -> Protocol=NEC Address=0xFEFE Command=0xF2 Raw-Data=0xDF2FEFE 32 bits LSB first Gap=3276750us Duration=75150us
14:27:12.584 -> Send with: IrSender.sendNEC(0xFEFE, 0xF2, <numberOfRepeats>);

關機定時
14:28:15.624 -> AF5FEFE
14:28:15.624 -> Protocol=NEC Address=0xFEFE Command=0xF5 Raw-Data=0xAF5FEFE 32 bits LSB first Gap=3276750us Duration=75100us
14:28:15.701 -> Send with: IrSender.sendNEC(0xFEFE, 0xF5, <numberOfRepeats>);

開機定時
14:29:05.835 -> 9F6FEFE
14:29:05.835 -> Protocol=NEC Address=0xFEFE Command=0xF6 Raw-Data=0x9F6FEFE 32 bits LSB first Gap=3276750us Duration=75150us
14:29:05.913 -> Send with: IrSender.sendNEC(0xFEFE, 0xF6, <numberOfRepeats>);


程式碼:

後面是 IRremote 4.x 版本的寫法,若版本不同,可到官網 https://github.com/Arduino-IRremote/Arduino-IRremote 查對應的寫法。
若要關閉發送紅外訊號時,開發板內建LED燈閃爍的效果,可在 #include <IRremote.hpp> 之前 #define NO_LED_SEND_FEEDBACK_CODE 或 #define NO_LED_FEEDBACK_CODE
//設定發送訊號時, LED_BUILTIN 這顆 LED 不閃爍
//#define NO_LED_SEND_FEEDBACK_CODE
//#define NO_LED_FEEDBACK_CODE
#include <IRremote.hpp>  // include the library

// 發送紅外線的 GPIO 引腳
const int IR_SEND_PIN = 27;

//要發送的紅外線訊號
int sCmdArrCnt = 7;
uint8_t sCmdArr[7] = {
  0xF1,  //1.電源開關
  0xF2,  //2.轉頭開關
  0xF3,  //3.指示燈開關
  0x4,   //4.減風量
  0xF5,  //5.關機定時
  0xF6,  //6.開機定時
  0x6    //7.加風量
};
String sCmdStrArr[7] = {
  "電源開關",
  "轉頭開關",
  "指示燈開關",
  "減風量",
  "關機定時",
  "開機定時",
  "加風量"
};


void setup() {
  Serial.begin(115200);

  pinMode(LED_BUILTIN, OUTPUT);

  //version 4.5: LED feedback is always enabled for sending.
  //It can only be disabled by using #define NO_LED_SEND_FEEDBACK_CODE or #define NO_LED_FEEDBACK_CODE.
  IrSender.begin(IR_SEND_PIN);
}

/*
 * Set up the data to be sent.
 * For most protocols, the data is build up with a constant 8 (or 16 byte) address
 * and a variable 8 bit command.
 * There are exceptions like Sony and Denon, which have 5 bit address.
 */
uint16_t sAddress = 0xFEFE;
uint8_t sCommand = 0xF1;
int_fast8_t sRepeats = 0;

void loop() {

  if (Serial.available()) {

    String readString = Serial.readStringUntil('\n');  // Read until newline
    Serial.print("[readString]:");
    Serial.println(readString);

    //轉成整數
    //若用 Serial.parseInt() 讀取整數,讀到換行,會轉成0,導致多執行一次
    int readInt = atoi(readString.c_str());
    Serial.print("[readInt]:");
    Serial.println(readInt);
    if (readInt >= 1 && readInt <= sCmdArrCnt) {
      //7個功能,分別輸入1~7代表,轉成 array index
      int idx = readInt - 1;
      //印出16進位紅外線指令
      Serial.print("0x");
      Serial.println(sCmdArr[idx], HEX);
      Serial.println(sCmdStrArr[idx]);  //文字說明
      IrSender.sendNEC(sAddress, sCmdArr[idx], sRepeats);
    }
    Serial.println("===================");
  }
}
結果



參考:





2026年2月4日 星期三

ESP32 NODEMCU-32S 讀取遙控器紅外線訊號

目的: 

電風扇遙控器的開關按鍵,越來越不靈敏,趁還未完全壞掉前,將遙控器紅外線訊號複製起來保存。


環境:

Win10、Arduino IDE 2.3.7、ESP32 NODEMCU-32S、1838 紅外線接收模組、NWT 威技電風扇(WPF-14P7)遙控器


[關於紅外線接收]
一般有三種:
  • 單純的紅外線接收,是一種光電二極體(photodiode、PD),可將接收到的光,轉換成電。
    雖然長得很像 LED,但 LED 是發光二極體(light-emitting diode),作用是將電轉換成光。
    兩者引腳雖然都用長短區分正負,但內部的PN接面剛好相反,所以從內部PN接面來看,
    發光二極體工作時,是施加順向偏壓。
    光電二極體工作時,是施加逆向偏壓。
    這形式的光電二極體,最直接的應用,是感測紅外線強弱變化,來偵測物體。

  • 一般紅外線遙控器,使用的載波頻率(Carrier Frequency、可想成背載電磁波進行傳送)是 38KHz,所以下面這種三支引腳的封裝,是將原本兩支腳的光電二極體,加上濾波取得載波頻率為 38KHz 的訊號,對訊號解調(demodulation)後,再轉成以 HIGH 或 LOW 訊號輸出。
    三支引腳分別是接正極(+)、接地(-)、輸出訊號(S)。

  • 最後一種,是做成插上就可使用的電路板模組,我使用的是這種,我用的模組上面除了有三支引腳的紅外線接收器,還接好了電阻,以及一個 LED,紅外線接收器收到訊號時,LED 會閃爍。



引腳線路接法:

  • 3.3V (ESP32)  ----------  正極(+)  (1838 IR module)
  • GND (ESP32)  ----------   負極(-) (1838 IR module)
  • GPIO33 (ESP32)  ----------  訊號(S)  (1838 IR module)


安裝 IRremote library:
  • 這邊使用 IRremote 這個 library 處理紅外線訊號
    「Tools」->「Manage Libraries...」


  • 找到 IRremote 進行安裝
    1. 安裝好的 library 會在「File」->「Preferences...」->「sketchbook location」設定路徑裡的 libraries 資料夾。
    2. sketchbook location 預設路徑
      C:\Users\%UserName%\Documents\Arduino\
    3. 安裝好 library 後,如果在設定裡改了 sketchbook location 路徑,Arduino IDE 會認為沒安裝,若之後要在「Manage Libraries...」裡移除已安裝的 library,也是 sketchbook location 設定是原本安裝的路徑下才能辨別。



程式碼:

後面是 IRremote 4.x 版本的寫法,若版本不同,可到官網 https://github.com/Arduino-IRremote/Arduino-IRremote 查對應的寫法。
#include <IRremote.h>
// 連接紅外線接收模組訊號引腳(S引腳)的 GPIO
const int IR_RECEIVE_PIN = 33;

void setup() {

  //LED_BUILTIN 是開發板內建的 LED,GPIO 2
  pinMode(LED_BUILTIN, OUTPUT);

  Serial.begin(115200);
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);  // Start the receiver

  delay(2000);
  Serial.println("IR Receiver Ready. Point your remote at the sensor and press buttons");
}

void loop() {
  // 檢查是否收到紅外線訊號
  if (IrReceiver.decode()) {
    Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX);  // Print "old" raw data
    IrReceiver.printIRResultShort(&Serial);                        // Print complete received data in one line
    IrReceiver.printIRSendUsage(&Serial);                          // Print the statement required to send this data
    IrReceiver.resume();                                           // Enable receiving of the next value
  }
}


遙控器須對準紅外線接收器,如果沒對準,可能每次都出現不一樣的結果。
若不確定遙控器有無送出訊號,可透過相機(手機)鏡頭,看遙控器發送端,透過鏡頭可看到紅外線。

最終輸出的結果如下:

電源開關
14:32:44.974 -> EF1FEFE
14:32:44.974 -> Protocol=NEC Address=0xFEFE Command=0xF1 Raw-Data=0xEF1FEFE 32 bits LSB first Gap=3276750us Duration=75150us
14:32:45.041 -> Send with: IrSender.sendNEC(0xFEFE, 0xF1, <numberOfRepeats>);

減風量
14:23:42.992 -> FB04FEFE
14:23:42.992 -> Protocol=NEC Address=0xFEFE Command=0x4 Raw-Data=0xFB04FEFE 32 bits LSB first Gap=3276750us Duration=75100us
14:23:43.100 -> Send with: IrSender.sendNEC(0xFEFE, 0x4, <numberOfRepeats>);

加風量
14:25:02.445 -> F906FEFE
14:25:02.445 -> Protocol=NEC Address=0xFEFE Command=0x6 Raw-Data=0xF906FEFE 32 bits LSB first Gap=3276750us Duration=75150us
14:25:02.576 -> Send with: IrSender.sendNEC(0xFEFE, 0x6, <numberOfRepeats>);

指示燈開關
14:25:50.912 -> CF3FEFE
14:25:50.912 -> Protocol=NEC Address=0xFEFE Command=0xF3 Raw-Data=0xCF3FEFE 32 bits LSB first Gap=3276750us Duration=75150us
14:25:50.997 -> Send with: IrSender.sendNEC(0xFEFE, 0xF3, <numberOfRepeats>);

轉頭開關
14:27:12.477 -> DF2FEFE
14:27:12.477 -> Protocol=NEC Address=0xFEFE Command=0xF2 Raw-Data=0xDF2FEFE 32 bits LSB first Gap=3276750us Duration=75150us
14:27:12.584 -> Send with: IrSender.sendNEC(0xFEFE, 0xF2, <numberOfRepeats>);

關機定時
14:28:15.624 -> AF5FEFE
14:28:15.624 -> Protocol=NEC Address=0xFEFE Command=0xF5 Raw-Data=0xAF5FEFE 32 bits LSB first Gap=3276750us Duration=75100us
14:28:15.701 -> Send with: IrSender.sendNEC(0xFEFE, 0xF5, <numberOfRepeats>);

開機定時
14:29:05.835 -> 9F6FEFE
14:29:05.835 -> Protocol=NEC Address=0xFEFE Command=0xF6 Raw-Data=0x9F6FEFE 32 bits LSB first Gap=3276750us Duration=75150us
14:29:05.913 -> Send with: IrSender.sendNEC(0xFEFE, 0xF6, <numberOfRepeats>);





參考:


2026年1月25日 星期日

讀取 Arduino IDE 序列埠監控窗輸入,ESP32 使用程式碼 reset (software reset)

Demo 影片:




效果:

在 Arduino IDE 序列埠監控窗(Serial Monitor)輸入字串,ESP32 開發板上的程式讀取字串,當輸入字串為 RST,則執行 Reset,重啟開發板。

  • 使用 Serial.readStringUntil() 讀取字串,並設定讀取到換行字元(\n)時停止,所以序列埠監控窗(Serial Monitor)須設定送出訊息時,加上 New Line。
    等待 serial data,預設 Timeout 為 1000ms (1秒),所以若送出訊息時,選擇 No Line Ending,過了1秒 Timeout,仍會結束讀取,繼續執行後面程式。
  • Serial Monitor 和程式的 baud rate 需相同,這邊設為 115200
  • 使用 ESP.restart() 進行 Reset
    • software reset of the chip (對晶片進行軟體重設)
    • execution of the program stops (程式執行停止)
    • both CPUs are reset (兩個 CPU 都將被重設)
    • the application is loaded by the bootloader and starts execution again (應用程式由啟動引導程式載入並重新開始執行)
  • 使用 esp_reset_reason() 取得最近一次 Reset  的原因
  • 執行結果


程式碼:

void setup() {

  Serial.begin(115200);  //設定 baud rate

  // 獲取最近一次重置的原因
  esp_reset_reason_t reason = esp_reset_reason();

  Serial.println("========================================");
  Serial.print("系統啟動... 重置原因代碼: ");
  Serial.println(reason);

  switch (reason) {
    case ESP_RST_UNKNOWN:
      Serial.println("原因: Reset reason can not be determined. (無法確定重置原因)");
      break;
    case ESP_RST_POWERON:
      Serial.println("原因: Reset due to power-on event. (因開機事件而重設)");
      break;
    case ESP_RST_EXT:
      Serial.println("原因: Reset by external pin. (透過外部引腳,不適用於 ESP32)");
      break;
    case ESP_RST_SW:
      Serial.println("原因: Software reset via esp_restart. (軟體重置:透過調用 esp_restart 觸發)");
      break;
    case ESP_RST_PANIC:
      Serial.println("原因: Software reset due to exception/panic. (異常重置:系統崩潰或程式碼運行錯誤)");
      break;
    case ESP_RST_INT_WDT:
      Serial.println("原因: Reset due to interrupt watchdog. (中斷看門狗重置:中斷服務例程 ISR 受到長時間阻斷,即 IWDT 逾時)。IWDT:Independent Watchdog Timer");
      break;
    case ESP_RST_TASK_WDT:
      Serial.println("原因: Reset due to task watchdog. (任務看門狗重置:某個 FreeRTOS 任務未及時餵狗)");
      break;
    case ESP_RST_WDT:
      Serial.println("原因: Reset due to other watchdogs. (其他看門狗重置:包括 RTC 看門狗等硬體計時器)");
      break;
    case ESP_RST_DEEPSLEEP:
      Serial.println("原因: Reset after exiting deep sleep mode. (深睡喚醒重置:系統從深度睡眠模式中恢復)");
      break;
    case ESP_RST_BROWNOUT:
      Serial.println("原因: Brownout reset (software or hardware). (欠壓重置:供電電壓不穩定導致)");
      break;
    case ESP_RST_SDIO:
      Serial.println("原因: Reset over SDIO. (SDIO 重置:透過 SDIO 接口發送的復位指令)");
      break;
    case ESP_RST_USB:
      Serial.println("原因: Reset by USB peripheral. (USB 重置:由 USB 設備接口觸發的重置)");
      break;
    case ESP_RST_JTAG:
      Serial.println("原因: Reset by JTAG. (JTAG 重置:調試器通過 JTAG 觸發)");
      break;
    case ESP_RST_EFUSE:
      Serial.println("原因: Reset due to efuse error. (eFuse 錯誤重置:檢測到硬件 eFuse 數據損壞)");
      break;
    case ESP_RST_PWR_GLITCH:
      Serial.println("原因: Reset due to power glitch detected. (電源毛刺重置:檢測到極短時間的電壓異常)");
      break;
    case ESP_RST_CPU_LOCKUP:
      Serial.println("原因: Reset due to CPU lock up (double exception). (CPU 死鎖重置:發生了嚴重的雙重異常)");
      break;
    default:
      Serial.println("原因: Undefined reset reason. (未定義的重置原因)");
      break;
  }
  Serial.println("========================================");
}

void loop() {
  //讀取 Serial Monitor 輸入
  if (Serial.available()) {

    String readString = Serial.readStringUntil('\n');  // Read until newline
    readString.toUpperCase();
    Serial.println("[readString]:" + readString);

    if (readString == "RST") {
      Serial.println("[ESP.restart]");
      ESP.restart();  //software reset
    }
  }
}




參考:


電路圖上的英文縮寫

  •  控制介面
    • EN:Enable
      要用的時候,開啟 EN 腳,不用的時候就關閉。
      有些是高電平Enable,有些是低電平Enable。
    • CS:Chip Select
      晶片選擇,通常用於發送資料的時候,選擇哪個晶片接收。
      例如,SPI 匯流排可以連接多個設備,DDR 匯流排也可以連接多個 DDR 記憶體晶片。此時,需要使用 CS 來控制資料傳送到哪個裝置。
    • RST:Reset
      若標記為 RST_N,表示低電位時生效。(N:Negative)
    • INT:Interrupt,中斷
      可在接收到中斷訊號時,放下目前工作,優先執行中斷服務程序(ISR),當中斷服務程序完成以後,再繼續原本工作。
    • PD:Power Down,斷電
      不是 USB Type-C 介面中的 PD(Power Delivery)
    • CLK:Clock
      一般標為 xxx_xCLK,如 SPI_CLK、SDIO_CLK、I2S_MCLK(Main Clock)等。
      對於系統時鐘,往往會標註頻率。如SYS_26M、32K等。
      也有標了數字而不標 CLK 三個字,因為只有時鐘才會這麼標。
    • CTRL:control
      或 CMD(Command)。
    • SW:Switch,開關
    • PWM:Pulse-width modulation、脈波寬度調變、脈寬調變
      一條輸出訊號線上輸出不同佔空比的脈衝訊號達到傳遞能量/訊息的目的,
      例如:控制馬達的轉速、加上一個RC構成DAC電路、開關穩壓控制器透過PWM達到穩壓。
    • REF:Reference
      I_REF(參考電流)、V_REF(參考電壓)
      常用在穩壓電路、ADC、DAC。
    • FB:Feedback,回饋
      升壓、降壓電路上都會有回授訊號,和 Reference 類似,晶片根據外部採集來的電壓高低,動態調整輸出。外部電壓偏低了,就加大輸出,外部電壓偏高了,就減少輸出。
    • A/D:Analog/Digital,類比/數位
      DBB(數位基頻)、AGNG(類比地線)、DGND(數位地線)
      其他,PGND(功率地線),CGND(交流地線)、EGND(大地地線)
    • D/DATA:資料
      I2C 上稱為 SDA(Serial DATA)
      SPI 上稱為 SPI_DI、SPI_DO(Data In,Data Out)
      DDR 上稱為 D0,D1,D32
    • A/Address:位址線
      用法同數據線。
      主要用在 DDR 等位址和資料分開的傳輸介面。
      其他接口,慢的像I2C、SPI,快的像MIPI、RJ45等,都是地址和資料放在一組線上傳輸的,就沒有位址線。

  • 方向標識
    • TX/RX:Transmit/Receive、發送/接收
      用在串口(UART)上是最多的,一條線負責發送,一根線負責接收。
    • P/N:Positive/Negative,正/負
    • L/R:Left/Right。
      通常用於音頻線,區分左右。
  • 常用設備
    • BB:Baseband,基頻處理​​器
    • P(GPIO):General-purpose input/output,通用型輸入輸出
      例如,P1、P2、P1_3(第1組的第3個)
    • BAT:Battery
      所有的電池電壓都可以叫做 VBAT
    • CHG:Charge,充電
    • CAM:Camera
    • LCD:顯示器
    • TP:Touch Panel,觸控螢幕
    • DC:Direct Current,直流電
      設備上通常用作外部直流輸入接口,而不是指供電方式或供電電壓。
      例如 VCC_DC_IN,表示外部 DC 介面供電
  • 元件符號
    • C:Capacitor,電容
    • L:Inductor,電感
    • D:Diode,二極體
    • Q:Transistor,晶體管
    • U:Integrated Circuit,積體電路
    • GND:Ground,接地
    • VCC:Power Supply Positive,電源正極
    • VDD:電源電壓,通常用於MOSFET
    • VSS:接地電壓,負極,通常用於MOSFET
    • LED:Light Emitting Diode,發光二極體
    • Z:Zener Diode,齊納二極體
    • VR:Variable Resistor,可變電阻
    • X:Crystal Oscillator,晶體振盪器
    • JP:Jumper,跳線
    • TP:Test Point,測試點


參考:


2026年1月24日 星期六

Arduino 安裝 ESP32 開發板平台出現 DEADLINE_EXCEEDED 錯誤

 環境:

Windows 10、Arduino IDE 2.3.7


問題:

要將 ESP32 開發板從 3.5.2 更到 3.5.5 時,
出現「Error: 4 DEADLINE_EXCEEDED: context deadline exceeded (Client.Timeout or context cancellation while reading body)」錯誤。



解決方式:

因為 Arduino IDE 網路連接逾時時間(connection_timeout)預設為60秒,下載 ESP32 安裝檔案時間超過60秒,便會發生逾時錯誤。

所以將逾時時間(connection_timeout)設定適當延長即可。
設定檔在
C:\Users\<使用者名稱>\.arduinoIDE\arduino-cli.yaml

在檔案總管路徑輸入
C:\Users\%UserName%\.arduinoIDE\
到達該目錄後,編輯 arduino-cli.yaml

在 arduino-cli.yaml 設定檔,新增逾時時間設定如下(若已有該設定,則將逾時時間改大)
我這邊設為600秒,若設為0表示無限期等待。
【 connection_timeout - network inactivity timeout, the value format must be a valid input for time.ParseDuration(), defaults to 60s (60 seconds). 0 means it will wait indefinitely.】

network:
  connection_timeout: 600s

修改後,重啟 Arduino IDE,再安裝 ESP32 即可。







參考: