顯示具有 UART 標籤的文章。 顯示所有文章
顯示具有 UART 標籤的文章。 顯示所有文章

2026年8月14日 星期五

ESP32 × JQ6500 實作筆記 從「沒有聲音」到 UART 成功播放

智慧生活科技實作筆記

ESP32 × JQ6500 實作筆記
從「沒有聲音」到 UART 成功播放

一次實際除錯,重新認識電源、UART、GPIO 與模組測試的重要性

一、為什麼這次實作很值得記錄?

JQ6500 是一款價格低、體積小,而且能直接播放 MP3 語音的模組。 對於語音導覽、作品故事機、互動裝置、AIoT 展示與地方文化導覽而言, 它有一個很大的優點:即使沒有網路,也可以直接從內建 Flash 播放語音。

這次實作的目標,是利用 ESP32 控制 JQ6500, 讓未來的樹藝作品只要按下按鈕,就可以播放不同的作品故事; 再搭配 QR Code,延伸到手機網站與 AI 問答。

原本以為只是「接 TX、RX,送一段 UART 指令」就可以完成, 實際測試卻經歷了:

燒錄成功但沒有聲音 → K1~K5 測試 → 檢查喇叭 → 檢查 Flash → 更換 ESP32 UART 腳位 → UART 收發測試 → 收到 SPIOK → 最後成功由 ESP32 指令播放 MP3。

這次經驗最值得留下來的,不只是最後成功的程式,而是: 電源與通訊腳位,往往才是嵌入式系統除錯的第一現場。

二、使用硬體

ESP32
NodeMCU-32 / ESP32-WROOM-32
語音模組
JQ6500
喇叭
4Ω~8Ω 小型喇叭

JQ6500 使用 UART TTL 通訊,預設鮑率為 9600 bps。 此次採用 JQ6500 內建 Flash 播放 MP3, 並利用 ESP32 的硬體 UART2 控制播放。

三、這次實測最重要的三個發現

1. 電源真的很重要

一開始很容易把注意力全部放在程式、MP3 格式或 UART 指令, 但 JQ6500 本身包含 MP3 解碼與喇叭功率輸出, 播放瞬間的電源狀態會直接影響模組是否正常工作。

實作建議:
ESP32 與 JQ6500 在除錯初期可以各自使用 USB 供電, 但兩者的 GND 必須相連

「兩塊板都有 USB」不代表 UART 就能直接通訊。 數位訊號需要共同的電位參考,所以:

ESP32 GND ↔ JQ6500 GND

2. GPIO26 / GPIO27 最後成為穩定的 UART 配置

最初曾以 GPIO16、GPIO17 測試 UART, 實作過程中一直無法得到理想結果。 後來改用 NodeMCU-32 板上明確標示的:

P26 / GPIO26
ESP32 TX → JQ6500 RX
P27 / GPIO27
ESP32 RX ← JQ6500 TX

這裡特別容易誤解: GPIO26 是板子上印著「P26/26」的腳位, 不是從排針開始數第 26 隻腳。

需要特別說明的是,這並不表示所有 ESP32 都不能使用 GPIO16、17; 這次紀錄的是本次 NodeMCU-32 實機測試中,GPIO26 / 27 成功完成 JQ6500 UART 通訊。 實際專案仍應依開發板版本與腳位配置選擇。

3. TX 與 RX 一定要交叉

ESP32 TX → JQ6500 RX
ESP32 RX ← JQ6500 TX

TX 是「送出去」,RX 是「接進來」, 所以不能 TX 接 TX、RX 接 RX。

四、ESP32 × JQ6500 實測成功接線圖

ESP32 NodeMCU-32 P26 / GPIO26 TX P27 / GPIO27 RX GND JQ6500 RX TX GND SPK+ SPK− UART 9600 共地 4Ω~8Ω 喇叭 USB 供電 USB 供電

五、JQ6500 UART 指令

成功通訊後,ESP32 可以直接送 HEX 命令控制 JQ6500。 例如播放第一首 MP3:

7E 04 03 00 01 EF

其中:

7E:起始碼
04:資料長度
03:指定曲目命令
00 01:第 1 首
EF:結束碼

設定音量 20:

7E 03 06 14 EF

選擇內建 Flash:

7E 03 09 04 EF

六、最關鍵的一刻:收到 SPIOK

在 UART 除錯過程中,ESP32 最後收到:

53 50 49 4F 4B

轉成 ASCII 即為:

SPIOK

這是一個非常重要的除錯訊號。 因為它代表 JQ6500 已經有資料回到 ESP32, 也證明雙向 UART 通訊路徑已經建立。

ESP32 TX → JQ6500 RX ✓
JQ6500 TX → ESP32 RX ✓
UART 9600 ✓
共地 ✓

七、最後成功的 ESP32 最小播放程式

#include <HardwareSerial.h>

HardwareSerial JQ(2);

#define JQ_TX 26
#define JQ_RX 27

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

  JQ.begin(
    9600,
    SERIAL_8N1,
    JQ_RX,
    JQ_TX
  );

  delay(3000);

  // 選擇內建 Flash
  uint8_t flash[] = {
    0x7E, 0x03, 0x09, 0x04, 0xEF
  };

  JQ.write(flash, sizeof(flash));
  JQ.flush();

  delay(2500);

  // 音量 = 20
  uint8_t volume[] = {
    0x7E, 0x03, 0x06, 0x14, 0xEF
  };

  JQ.write(volume, sizeof(volume));
  JQ.flush();

  delay(1000);

  // 播放第一首
  uint8_t play1[] = {
    0x7E,
    0x04,
    0x03,
    0x00,
    0x01,
    0xEF
  };

  JQ.write(play1, sizeof(play1));
  JQ.flush();
}

void loop()
{
}

八、真正值得學生學習的是「怎麼除錯」

這次實作最有價值的地方,其實不是最後那幾行程式, 而是建立一個合理的除錯順序。

Step 1 先不要接 ESP32
JQ6500 燒入 MP3,用 K1~K5 測試能否直接播放。

Step 2 確認 Flash、喇叭與播放核心正常
只要 K1 能播放,就不要再一直懷疑 MP3 或喇叭。

Step 3 再加入 ESP32
先建立 TX、RX、GND 三條 UART 線。

Step 4 確認 GPIO 名稱
GPIO26 是 P26,不是「第 26 隻實體腳」。

Step 5 先送,再收
觀察 Serial Monitor 是否真的送出 HEX 指令。

Step 6 確認回傳
看到 SPIOK,代表 UART 雙向通訊已建立。

Step 7 最後才加入按鈕、QR Code、網路與 AI

九、下一步:把它變成「樹藝故事機」

JQ6500 成功之後,就可以把系統進一步做成三層式架構:

第一層|離線故事

實體按鈕

ESP32

JQ6500

MP3 語音故事
第二層|QR Code

掃描作品 QR Code

手機 Web

作品圖文、影片與導覽
第三層|AI

Django API

LLM + RAG

AI 作品問答

例如三個按鈕可以分別設定:

① 001.mp3:作品介紹
② 002.mp3:創作者故事
③ 003.mp3:地方文化與永續故事

十、結語:AIoT 教育不能只教「成功的接法」

這次 ESP32 × JQ6500 的測試再次說明, 真正的工程學習,不只是取得一份「可以跑的程式」。

接錯、沒聲音、沒有回覆,
其實都是理解系統最好的教材。

從電源、共地、Flash、喇叭,到 UART 的 TX/RX、GPIO 選擇與 HEX 指令, 每一次排除問題,都讓學生更理解: 硬體、軟體與通訊,其實是一個完整的系統。

當這個基礎完成之後, JQ6500 就不再只是一塊「MP3 播放板」, 而可以成為樹藝、工藝、地方文化與 AI 導覽之間, 一個低成本、可靠而有溫度的聲音介面。

關鍵字: ESP32、JQ6500、UART、AIoT、語音模組、樹藝故事機、QR Code、Django、RAG、智慧生活科技

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]));
    } 
}


2024年10月12日 星期六

[8051] 實作檢查碼,若有錯則顯示錯誤

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

前一篇文章:設計簡單的通訊協定以POS機串接的通訊為例

程式碼:

  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
#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);
		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;
			}
		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 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(9600);						//設定串列傳輸模式-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);
			}
}

執行結果:

若把第149行改為SBUF=calculateLRC(SendBuf, 16)+1;
執行結果:


[8051]設計簡單的通訊協定以POS機串接的通訊為例

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

前一篇文章:偵測通訊狀態,顯示連線/斷線和秒數

POS機串接的通訊格式:通訊規格

程式碼:

  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
#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;								//是否有接收

// 使用函數的設定區
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);
		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;
			}
		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(buf == 0x02)
			receive_cnt=0;
		else if (buf == 0x03)
			LCD_Out(1,1,ReceiveBuf);
		else 
			ReceiveBuf[receive_cnt++]=buf;
		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++);
}

void main(void)
{
			unsigned int i;
			init_UART(9600);						//設定串列傳輸模式-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);
				Delay_ms(1000);
			}
}

執行結果:



[8051] 偵測通訊狀態,顯示連線/斷線和秒數。

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

前一篇文章:注意檢查副程式和主程式對暫存器的設定,以TMOD為例

程式碼:

  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
#include <regx51.h>

unsigned char SendBuf[]={0,1,2,3,4,5,6,7,8,9};
// 0-9七段顯示器資料區
unsigned char code table[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
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;								//是否有接收

// 使用函數的設定區
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;

void init_UART(unsigned int baudrate)
{
		SCON=0x52;
		TMOD=0x20;
		TH1=256-((11059200/384)/baudrate);
		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_Out(2,1,"offline");
				on_line_status=0;
				on_line_sec=0;
			}
		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
		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 main(void)
{
			unsigned int i;
			init_UART(9600);						//設定串列傳輸模式-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(2,1,"offline");			// LCD 在(2,1)位置顯示offline
			LCD_Out(2,15,"00");

			while(1)
			{
				for(i=0;i<10;i++)
					SBUF=SendBuf[i];					//傳送0-9
			}
}

執行結果: