Giới thiệu về Module Input Capture
Input capture là một module cho phép ghi lại các móc thời gian bằng cách lưu giá trị của timer tại các thời điểm ấy một cách tự động bằng phần cứng. Mặc dù ta có thể làm công việc tương tự module input capture bằng phần mềm, nhưng sẽ không thể đáp ứng yêu cầu độ trễ thấp hoặc sẽ làm gián đoạn chương trình chính.
N76E885 có ba bộ input capture là IC0, IC1 và IC2. Cả ba bộ đều được kết nối đến timer 2 và có cấu tạo tương tự nhau:
Mỗi bộ input capture đều có một I/O riêng để kích hoạt sự kiện “capture”:
- IC0: kết nối với P2.0.
- IC1: kết nối với P2.1.
- IC2: kết nối với P2.2.
Khi sử dụng input capture, ta cũng cần cấu hình chức năng input tại gpio tương ứng.
Tín hiệu vào I/O được đưa tới một bộ lọc nhiễu trước khi đi đến mạch phát hiện cạnh xung. Chúng ta có thể sử dụng bộ lọc này hoặc không bằng cách thiết lập trên thanh ghi CAPCON2:
Sự kiện “capture” xảy ra khi trên I/O (của bộ input capture tương ứng) có một cạnh xung giống với loại cạnh xung đã được cài đặt trên CAPCONx[1:0] (x là số thứ tự bộ input capture):
- CAPCONx[1:0] = 00: kích hoạt capture bằng cạnh xuống.
- CAPCONx[1:0] = 01: kích hoạt capture bằng cạnh lên trên.
- CAPCONx[1:0] = 10: kích hoạt capture bằng cạnh lên hoặc xuống đều được.
Trước khi dùng chức năng input capture, cần enable module mà chúng ta muốn sử dụng thông qua thanh ghi CAPCON0:
Một khi xuất hiện sự kiện “capture” ở kênh x:
- Cờ CAPFx được set.
- Giá trị trong TH2 & TL2 sẽ được sao chép vào cặp thanh ghi CxH & CxL.
- Ngoài ra, nếu cấu hình CM_RL2 = 0 và CAPCR = 1, timer 2 sẽ tự động xoá tại sự kiện capture.
Dựa vào đặc tính của module input capture, ta có thể dùng nó để đo các khoảng thời gian mức cao/thấp trên I/O của bộ input capture tương ứng. Từ đó, ta áp dụng trong đo chu kỳ xung, tần số xung, giải mã xung, phát hiện mất xung,…
Giải mã hồng ngoại giao thức NEC
Giao thức NEC
Giao thức NEC là một giao thức truyền không dây, sử dụng ánh sáng hồng ngoại.
Hình bên dưới là mô tả tóm tắt về giao thức NEC:
(Tóm tắt giao thức NEC)
Mã hoá NEC
Tại phía phát, dữ liệu gốc được mã hoá thành tín hiệu hồng ngoại.
Có 2 khái niệm cần làm rõ là “pulse burst” và “space”:
- “Pulse burst”: là khoảng thời gian mà phía phát băm xung (bật – tắt) LED hồng ngoại theo một tần số quy định gọi là tần số sóng mang, từ 36 kHz đến 38 kHz (thường là 38 kHz).
- “Space”: là khoảng thời gian mà phía phát tắt LED hồng ngoại.
(LED phát hồng ngoại)
Một khung truyền của giao thức NEC bao gồm:
- 1 bit start.
- 32 bit logic:
- 8 bit logic của Address.
- 8 bit logic của Address bị đảo bit.
- 8 bit logic của Command.
- 8 bit logic của Command bị đảo bit.
- 1 bit stop.
Mô tả về các bit như sau:
- Bit start: (562.5us x 16) pulse burst và theo sau là (562.5us x 8) space.
- Bit logic có hai loại và “0” và “1”:
- Bit logic “1”: 562.5us pulse burst và theo sau là (562.5us x 3) space.
- Bit logic “0”: 562.5us pulse burst và theo sau là 562.5us space.
- Bit stop: 562.5us pulse burst.
(Một frame có Address = 0xF0 và Command = 0x78)
Giao thức NEC cho phép phát hiện lỗi nhờ vào phần đảo bit của phần Address và Command.
(Có một biến thể khác của NEC mở rộng phần Address lên đến 16-bit, trong biến thể này chúng ta không thể kiểm lỗi phần Address vì không có phần đảo bit của nó, tuy nhiên phần Command vẫn có thể kiểm tra lỗi như bình thường).
Ngoài ra, khi cần truyền liên tiếp các frame có Address và Command không đổi, phía phát thay vì truyền lặp lại frame trước đó, thì nó truyền “repeat code”. Các bạn có thể tham khảo chi tiết về định nghĩa “repeat code” trong khung truyền đầy đủ của giao thức NEC tại đây: https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol.
Giải mã NEC
Tại phía thu, tín hồng ngoại sẽ được giải mã thành dữ liệu gốc.
Tín hiệu hồng ngoại được đọc vào bởi module thu hồng ngoại.
(Module thu hồng ngoại)
Module thu hồng ngoại hoạt động như sau:
- Lúc bình thường không có sóng mang (tương ứng khoảng “space”), module xuất mức 1.
- Lúc có sóng mang hồng ngoại (tương ứng khoảng “pulse burst”), module xuất mức 0.
Theo nguyên lý đó, ta có được dạng sóng từ module thu như hình sau:
(Dạng sóng từ module thu hồng ngoại)
Để giải mã dạng sóng, ta tiến hành đo các khoảng “pulse burst” (mức 0) và “space” (mức 1) xem có khớp với mô tả của giao thức hay không.
Lập trình Nuvoton Input Capture giải mã điều khiển hồng ngoại NEC
Yêu cầu:
Sử dụng 2 nút trên điều khiển hồng ngoại loại mã hoá NEC: một nút để bật và một nút để tắt LED1.
Phần cứng:
Một số điều khiển mã hoá NEC có thể dễ dàng tìm mua ở cửa hàng điện tử:
Hoặc điều khiển của đầu DVD và đầu thu anten mặt đất cũng thường là mã hoá NEC:
Chúng ta sẽ sử dụng module input capture kênh 2 (IC2) để giải mã tín hiệu từ module thu. Chân DAT của module thu sẽ kết nối với pin IC2 (tức pin P2.2):
Phân tích:
Các nút trên điều khiển đều có chung Address nhưng Command thì khác nhau. Khi một nút trên điều khiển được nhấn, một frame sẽ được truyền đi thông qua LED hồng ngoại.
Mình sẽ chọn ra 2 nút trên điều khiển mình đang có:
Mình sẽ gọi nút khoanh vàng là ON và nút khoanh xanh lục là OFF.
Đồng thời, dùng máy đo logic để cho tín hiệu trên chân DAT (module thu) để xác định Address và Command của nút đang nhấn.
Dạng sóng thu được khi nhấn nút ON (vàng):
Dạng sóng thu được khi nhấn nút OFF (xanh):
Như vậy, ta đã biết trước mã của 2 nút nhấn:
- Nút ON: Address = 0x00 và Command = 0x98.
- Nút OFF: Address = 0x00 và Command = 0xD8.
Vấn đề còn lại chỉ là giải mã và kiểm tra kết quả sau giải mã.
Trong ví dụ này, timer 2 cần được cấu hình như sau:
Và cấu hình prescaler = 1/4, tương ứng chu kỳ clock là:
Gọi “duration” là khoảng thời gian đo bằng giây và “capture” là giá trị tương ứng của “duration” được thể hiện trong timer 2. Mối quan hệ giữa “capture” và “duration”:
Ta có các giá trị đặc biệt, liên quan đến giao thức NEC:
Theo như mô tả của giao thức NEC đã đề cập, mình sẽ tóm tắt quá trình giải mã dưới dạng máy trạng thái như sau:
Tại một thời điểm, chương trình sẽ ở một trong các trạng thái sau:
- STATE_INIT (Đây cũng là trạng thái sau khi khởi động):
- Run timer.
- Cho phép capture bằng bất kỳ loại cạnh xung.
- Chuyển đến trạng thái STATE_BEG_START_BIT.
- STATE_BEG_START_BIT (đầu bit start):
- Nếu “duration” = 9000us:
- Chuyển đến trạng thái STATE_MID_START_BIT.
- Ngược lại => Error => Reset.
- Nếu “duration” = 9000us:
- STATE_MID_START_BIT (giữa bit start):
- Nếu “duration”= 4500us:
- success = 0.
- logic_bit = 0.
- Chuyển đến trạng thái STATE_BEG_LOGIC_BIT.
- Ngược lại => Error => Reset.
- Nếu “duration”= 4500us:
- STATE_BEG_LOGIC_BIT:
- Nếu “duration” = 562.5us:
- Chuyển đến trạng thái STATE_MID_LOGIC_BIT.
- Ngược lại => Error => Reset.
- Nếu “duration” = 562.5us:
- STATE_MID_LOGIC_BIT:
- Nếu “duration” = 562.5us:
- Thêm vào bit logic 0.
- Tăng logic_bit thêm 1.
- Nếu logic_bit = 32 => Chuyển đến trạng thái STATE_BEG_STOP_BIT.
- Nếu logic_bit < 32 => Chuyển về trạng thái STATE_BEG_LOGIC_BIT.
- Nếu “duration” = 3 x 562.5us:
- Thêm vào bit logic 1.
- Tăng logic_bit thêm 1.
- Nếu logic_bit = 32 => Chuyển đến trạng thái STATE_BEG_STOP_BIT.
- Nếu logic_bit < 32 => Chuyển về trạng thái STATE_BEG_LOGIC_BIT.
- Ngược lại => Error => Reset.
- Nếu “duration” = 562.5us:
- STATE_BEG_STOP_BIT:
- Nếu “duration” = 562.5us:
- Giải mã thành công: success = 1.
- Reset.
- Ngược lại => Error => Reset.
- Nếu “duration” = 562.5us:
Trong đó:
- Biến “success” = 1 khi giải mã thành công một frame.
- Biến “logic_bit” cho biết số bit logic đã được giải mã thành công.
- Error: khoảng “duration” đo được không khớp với định nghĩa trong giao thức.
- Reset: khởi tạo lại quá trình giải mã mới:
- Dừng timer.
- Cấu hình cho phép capture tại cạnh xuống.
- Chuyển về trạng thái STATE_INIT.
Ngoài ra, nếu sau một khoảng thời gian không có cạnh xung trên DAT, timer 2 sẽ tràn (bởi vì timer 2 chỉ xoá khi có capture). Khi đó, ta xem như giải mã thất bại và Reset.
Chương trình được viết như sau:
#include <stdint.h> #include <mcs51/N76E885.h> #include <mcs51/Define.h> #define LED1 P04 // #define LED2 P03 #define ON_LED 0 #define OFF_LED 1 #define STATE_INIT 0 #define STATE_BEG_START_BIT 1 #define STATE_MID_START_BIT 2 #define STATE_BEG_LOGIC_BIT 3 #define STATE_MID_LOGIC_BIT 4 #define STATE_BEG_STOP_BIT 5 typedef struct { uint8_t logic_bit : 7; uint8_t success : 1; uint8_t state; union { struct { uint8_t inverse_of_command; uint8_t command; uint8_t inverse_of_address; uint8_t address; }; uint32_t full; } code; } nec_t; // biến lưu kết quả giải mã nec_t decode; void necReset() { // // dừng timer TR2 = 0; // // capture kênh 2 tại cạnh xuống CAPCON1 &= CLR_BIT5 & CLR_BIT4; // decode.success = 0; decode.state = STATE_INIT; } void necHandler() { uint16_t capture = MAKEWORD(C2H, C2L); switch (decode.state) { case STATE_INIT: // // run timer TR2 = 1; // // capture kênh 2 tại bất kỳ cạnh xung CAPCON1 |= SET_BIT5; CAPCON1 &= CLR_BIT4; // decode.state = STATE_BEG_START_BIT; break; case STATE_BEG_START_BIT: if ((capture >= 47000) && (capture <= 60825)) { decode.state = STATE_MID_START_BIT; } else { decode.state = STATE_INIT; } break; case STATE_MID_START_BIT: if ((capture >= 22118) && (capture <= 27648)) { decode.logic_bit = 0; decode.success = 0; decode.state = STATE_BEG_LOGIC_BIT; } else { decode.state = STATE_INIT; } break; case STATE_BEG_LOGIC_BIT: if ((capture >= 2212) && (capture <= 4424)) { decode.state = STATE_MID_LOGIC_BIT; } else { decode.state = STATE_INIT; } break; case STATE_MID_LOGIC_BIT: if ((capture >= 2212) && (capture <= 4424)) { ++decode.logic_bit; decode.code.full <<= 1; } else if ((capture >= 8294) && (capture <= 9953)) { ++decode.logic_bit; decode.code.full <<= 1; decode.code.inverse_of_command |= SET_BIT0; } else { decode.state = STATE_INIT; break; } //---------------- if (decode.logic_bit == 32) { decode.state = STATE_BEG_STOP_BIT; } else { decode.state = STATE_BEG_LOGIC_BIT; } break; case STATE_BEG_STOP_BIT: if ((capture >= 2212) && (capture <= 4424)) { decode.success = 1; } decode.state = STATE_INIT; break; } if (decode.state == STATE_INIT) { // // dừng timer TR2 = 0; // // capture kênh 2 tại cạnh xuống CAPCON1 &= CLR_BIT5 & CLR_BIT4; } } void main(void) { // Insert code // // cấu hình ouput cho pin LED1 (P0.4) P0M1 &= CLR_BIT4; P0M2 &= CLR_BIT4; LED1 = OFF_LED; // // khởi tạo input capture // // cấu hình input cho pin của input capture kênh 2 (P2.2) P2M1 &= CLR_BIT2; P2M2 &= CLR_BIT2; P22 = 1; // // cho phép kênh 2 CAPCON0 |= SET_BIT6; // // cho phép bộ lọc nhiễu kênh 2 CAPCON2 |= SET_BIT6; // // capture kênh 2 tại cạnh xuống CAPCON1 &= CLR_BIT5 & CLR_BIT4; // // khởi tạo timer 2 // // prescaler = 1/4 T2MOD &= CLR_BIT6 & CLR_BIT5; T2MOD |= SET_BIT4; // // mode không nạp lại CM_RL2 = 0; T2MOD &= CLR_BIT7; // // tự xoá timer khi capture T2MOD |= SET_BIT3; // // khởi tạo tiến trình giải mã mới // necReset(); while (1) { // // sự kiện capture // if (CAPCON0 & SET_BIT2) { // // xoá cờ capture kênh 2 CAPCON0 &= CLR_BIT2; // necHandler(); } // // sự kiện timer 2 tràn // if (TF2) { // // xoá cờ timer 2 TF2 = 0; // necReset(); } // // chương trình chính // if (decode.success) { decode.success = 0; if (decode.code.address == 0x00) { switch (decode.code.command) { case 0x98: LED1 = ON_LED; break; case 0xD8: LED1 = OFF_LED; break; } } } } }
Sau khi biên dịch project và nạp code xuống kit:
Kết luận
Ngoài NEC, chúng ta còn nhiều giao thức hồng ngoại khác như RC5, Sony,… Hy vọng bài viết này sẽ là nền tảng để các bạn áp dụng input capture cho các ứng dụng sau này.
Hẹn gặp lại các bạn ở những bài tiếp theo.
Bài viết của Bạn rất hữu ích, mong tiếp tục xem kênh của Bạn !