Lập trình Nuvoton GPIO điều khiển led và nút nhấn

Lâp trình Nuvoton GPIO dieu khien led va nut nhan

Trong bài này chúng ta cùng nhau khảo sát GPIO của N76E885.

Giới thiệu về GPIO của N76E885 

GPIO là một ngoại vi không thể thiếu trên bất kì một vi điều khiển nào. Ngoại vi này cho phép vi điều khiển có thể xuất tín hiệu ra ngoài (output) hoặc đọc tín hiệu bên ngoài (input) vào vi điều khiển thông qua các port pin (I/O pin) có kết nối với module GPIO.

N76E885 có tối đa 26 I/O pin nằm trên 4 port P0, P1, P2, và P3. Các port này cho phép truy cập đến từng bit (bit-addressable). Tuy vậy, trên kit của chúng ta đã sử dụng:

  • 2 pin (P1.0 và P1.1) cho kết nối thạch anh ngoài.
  • 1 pin (P1.2) cho chức năng reset (có thể cấu hình thành pin input only chứ cũng không thể output).

Do đó, mà trên kit của chúng ta chỉ còn lại tối đa 23 I/O pin thuộc 3 port P0, P2 và P3.

Các I/O pin của GPIO trên N76E885 hầu hết đều có 4 mode (trừ các pin liên quan đến Reset, OSC):

  • Quasi-bidirectional mode.
  • Push-pull mode.
  • Input-only mode hay High-impedance mode.
  • Open-drain mode.

Mỗi pin trên cùng port có thể hoạt động ở từng mode riêng. Việc thiết lập mode của các pin được thực hiện thông qua cặp thanh ghi PxM1 và PxM2 (x là tên port).

Ví dụ, muốn thiết lập mode cho port 3, bit thứ 6, ta cần can thiệp vào bit thứ 6 trong hai thanh ghi P3M1 và P3M2.

Giá trị của cặp bit PxM1.n và PxM2.n (dành cho bit thứ n trên port x) quyết định mode hoạt động và được định nghĩa như sau:

word image 8531 1

Ví dụ, muốn P3.6 ở mode Push-Pull, ta sẽ cho P3M1.6 = 0 và P3M2.6 = 1.

Mặc định các pin sau khi reset xong sẽ ở mode Input-only.

Trước khi khảo sát GPIO, ta sẽ tạo 1 project. Nếu chưa biết cách tạo project và chuẩn bị các header file, các bạn hãy xem lại bài trước.

Đặt tên project là gpio (thật ra tên gì cũng được), nhớ include các header file như hình nhé.

word image 8531 2

Giờ thì bắt đầu thôi!

Lập trình Nuvoton GPIO Mode Quasi-bidirectional

Cấu tạo và nguyên lý hoạt động.

Đây là mode hoạt động trên các dòng 8051 truyền thống. Mode này cho phép I/O có thể là input lẫn output. Cấu tạo bên trong GPIO khi ở mode Quasi-bidirectional:

word image 8531 3

Những lệnh liên quan đến ghi ra (output) sẽ xuất ra Port Latch, những lệnh liên quan đến đọc vào (input) sẽ đọc từ Port Pin -> Input.

Theo như hình, ta có 4 MOSFET:

  • NMOS có khả năng nhận dòng sink (dòng đi vào) cao.
  • Strong PMOS có khả năng cho dòng source (dòng đi ra) cao.
  • Weak PMOS cho dòng source kém hơn Strong PMOS, có chức năng tăng cường dòng output khi Port Pin ở mức 1.
  • Very Weak PMOS cho dòng source kém nhất, có chức năng như một điện trở pull-up.

Khi Port Latch ở mức 1, NMOS tắt, Very Weak PMOS và Strong PMOS mở. Sau đó 2 chu kỳ, Strong PMOS tắt. Việc Strong PMOS mở trong 2 chu kì nhằm mục đích kéo Port Pin từ 0 lên 1 (nếu trước đó Port Pin ở mức 0) một cách nhanh chống, tức là làm cho cạnh xung output trở nên thẳng hơn. Ngay sau đó, nếu Port Pin ở mức 1, Weak PMOS sẽ tự động mở để tăng cường dòng source cho logic 1. Nếu có tác động làm Port Pin xuống mức 0, Weak PMOS cũng tự động tắt. Điều này cho phép kết nối bên ngoài có thể làm chủ mức logic trên Port Pin. Nghĩa là khi Port Latch ở mức 1, ta có thể đọc mức logic ở Port Pin.

Khi Port Latch ở mức 0, NMOS mở và kéo Port Pin xuống 0. Lúc này, các kết nối bên ngoài không thể kéo Port Pin lên 1, nên nếu đọc Port Pin sẽ chỉ nhận được logic 0.

Tóm lại:

  • Khi muốn dùng chức năng output, ta có thể chốt mức logic trên Px.n (port x pin n) tuỳ ý.
  • Khi muốn dùng chức năng input, ta cần chốt mức 1 trên Px.n trước, sau đó mới đọc logic trên Px.n.

Trên kit đã có sẵn 2 led đơn: LED1 đỏ và LED2 vàng được kết nối tương ứng với P0.4 và P0.3:

word image 8531 4

Cả hai LED này đều có cực kathod kết nối với VDD (thông qua R9/R13). Do đó, để bật sáng LED, cần set P0.4/P0.3 ở mức 0 và ngược lại, tắt LED bằng mức 1.

Mình sẽ sử dụng LED1 cho các ví dụ bên dưới.

Ví dụ 1 – Chức năng output

Ở mode Quasi-bidirectional, chức năng output cho phép pin xuất ra mức điện áp VDD (ở logic 1) và GND (ở logic 0). Tuy nhiên, vì khả năng cho dòng source (dòng ở logic 1) khá bé nên chức năng output ở mode này chủ yếu được sử dụng để giao tiếp tín hiệu (không cần gánh dòng tải lớn) hoặc điều khiển trực tiếp tải nhỏ (như LED đơn) bằng dòng sink (dòng ở logic 0).

Mình sẽ dùng chức năng output của mode Quasi-bidirectional bằng ví dụ chớp tắt LED1 (P0.4).

Các bạn mở project và viết đoạn code sau vào file main.c:

#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

void delay1ms(uint16_t duration) {
  for (; duration > 0; duration--) {
    uint16_t i = 1700;
    while (--i);
  }
}

void main(void) {

  // Insert code

  // P0.4 (LED1) quasi-bidiretional mode
  P0M1 &= CLR_BIT4;
  P0M2 &= CLR_BIT4;

  // khởi tạo giá trị mặc định
  LED1 = OFF_LED;

  while (1) {
    LED1 = ON_LED;
    delay1ms(500);
    LED1 = OFF_LED;
    delay1ms(500);
  }
}

 

Giải thích một chút về chương trình

Mình cho sẵn một hàm delay1ms. Đại khái, hàm này trì hoãn chương trình trong khoảng thời gian = (1ms * duration) rồi mới thực hiện các câu lệnh kế tiếp. Mình sẽ nói rõ về hàm delay1ms này trong video ở cuối bài, còn hiện tại các bạn chỉ cần biết cách sử dụng là được.

Trong hàm main, trước tiên chúng ta cần cấu hình cho P0.4 ở mode Quasi-bidirectional bằng cách ghi vào bit 4 của cặp thanh ghi P0M1 và P0M2 lần lượt là 0 và 0.

Mình sẽ chèn hàm delay1ms ở phía sau mỗi câu lệnh cho LED1 sáng/tắt để kéo dài thời gian sáng/tắt. Đồng thời, lồng tất cả chúng trong vòng lặp while (1) để lặp lại vô tận.

Sau đó, chúng ta biên dịch chương trình bằng tổ hợp phím Ctrl + F9 (hoặc trên thanh menu vào mục Build/Build). Giờ thì tiến hành nạp chương trình (hex file) xuống kit thôi.

Vì đây là bài đầu tiên sử dụng đến mạch nạp nên mình sẽ hướng dẫn chi tiết, đối với những bài sau này các bạn làm tương tự.

Sau khi biên dịch thành công các bạn cắm kit vào máy tính và mở phần mềm nạp Nuvoton NuMicro ICP Programming Tool, sau đó nhấn connect để kết nối với mạch nạp:

word image 8531 5

Tiếp tục vào “APROM” để chọn file hex. Tính từ vị trí của thư mục project, file hex nằm ở bin\Release hoặc bin\Debug (tuỳ theo lúc tạo project):

word image 8531 6

Tiếp tục vào mục Config Bits, nếu các bạn không sử dụng gì đến P1.2 thì hãy bỏ qua bước này. Nhưng nếu các bạn muốn sử dụng P1.2 để đọc input thì phải disable chức năng reset nhé.

word image 8531 7

Xuống phần programming, vào mục Options, các bạn chọn Erase, Program, Verify:

word image 8531 8

Trước khi nạp các bạn nhớ chọn “APROM”, “Config” nhé. Sau đó các bạn nhấn Start, có thể sẽ xuất hiện một thông báo như hình, các bạn chọn “OK”:

word image 8531 9

Ở những lần nạp sau, các bạn chỉ cần click “Start”.

Nạp thành công:

word image 8531 10

Chú ý: Sau khi nạp, phần mềm nạp sẽ hiện lên một pop-up “Programming flash, OK!”, thì đừng vội nhấn “OK” nhé, cứ để nguyên như vậy hoặc nếu lỡ nhấn “OK” rồi thì hãy nhấn “Disconnect”, lúc đó chương trình của bạn mới chạy đúng.

(Nói một chút về mạch nạp, nếu nạp xong mà nhấn “OK” ngay thì sẽ có hiện tượng N76E885 bị reset liên tục mỗi giây. Lý do là khi mạch nạp ở trạng thái rỗi, nó sẽ tìm xem có chip mục tiêu (target chip) nào đang kết nối với nó không (trong trường hợp này chính là N76E885 trên kit) để tiến hành reset chip mục tiêu đó, quá trình này được thực hiện mỗi giây)

Trở lại ví dụ 1, sau khi nạp chương trình thành công, LED1 sẽ nhấp nháy 1 Hz:

Vậy là ta đã điều khiển được output của mode Quasi-bidirectional., không khó đúng không nào? Thay vì chỉ bật-tắt LED, các bạn có thể mở rộng bài toán để điều khiển bật-tắt relay, động cơ,…

Ví dụ 2 – Chức năng input

Chức năng input cho phép đọc tín hiệu logic (0-1) từ bên ngoài vi điều khiển, các tín hiệu này có thể đến từ bàn phím, nút nhấn, các loại cảm biến số, pin tín hiệu từ IC,…

Trong ví dụ thứ 2 này, chúng ta sẽ đọc tín hiệu từ một nút nhấn đơn BTN: một đầu kết nối với P0.0, đầu còn lại nối với GND:

word image 8531 11

Yêu cầu của ví dụ này: Mỗi lần nút BTN được nhấn thì chờ đến khi thả nút, sau 500ms kể từ lúc thả nút thì sáng-tắt LED1 ba lần, thời gian sáng bằng thời gian tắt và bằng 100ms.

Tương tự như ví dụ 1, các cấu hình liên quan đến LED1 được giữ nguyên. Chúng ta chỉ cấu hình thêm cho P0.0 mode Quasi => Cài đặt cả P0M1 và P0M2 ở bit thứ 0 lần lượt là 0 và 0.

Trước khi sử dụng chức năng input, đừng quên cho P0.0 = 1.

Có điểm cần lưu ý:

  • Khi nút được nhấn, P0.0 được nối đến GND tức logic 0.
  • Khi thả nút, P0.0 được bỏ trống, vậy làm sao biết mức logic trên P0.0?

May mắn thay, mode Quasi-bidirectional cung cấp một điện trở pull-up bên trong – chính là Very Weak PMOS mà mình đã đề cập trong phần cấu tạo và nguyên lý hoạt động. Như vậy, khi nút được thả, P0.0 sẽ ở mức 1.

Chương trình được triển khai 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 BTN P00
#define PRESSED 0

void delay1ms(uint16_t duration) {
  for (; duration > 0; duration--) {
    uint16_t i = 1700;
    while (--i);
  }
}

void main(void) {

  // Insert code

  // P0.4 (LED1) quasi-bidiretional mode
  P0M1 &= CLR_BIT4;
  P0M2 &= CLR_BIT4;

  // khởi tạo giá trị mặc định
  LED1 = OFF_LED;

  // P0.0 (BTN) quasi-bidiretional mode
  P0M1 &= CLR_BIT0;
  P0M2 &= CLR_BIT0;

  // dùng chức năng input
  BTN = 1;

  while (1) {
    if (BTN == PRESSED) {
      while (BTN == PRESSED); // chờ đến khi thả nút

      delay1ms(1000);

      LED1 = ON_LED;
      delay1ms(200);
      LED1 = OFF_LED;
      delay1ms(200);

      LED1 = ON_LED;
      delay1ms(200);
      LED1 = OFF_LED;
      delay1ms(200);

      LED1 = ON_LED;
      delay1ms(200);
      LED1 = OFF_LED;
      delay1ms(200);
    }
  }
}

 

Sau khi biên dịch và nạp chương trình thành công, ta thử nhấn nút để test nhé:

Ví dụ 3 – Chức năng input và output trên cùng một pin.

Ở ví dụ 2 vừa rồi, chúng ta cho dùng P0.4 để điều khiển LED1 và P0.0 để đọc nút nhấn BTN. Vậy thì ở ví dụ thứ 3 này, chúng ta chỉ sử dụng một pin để làm cả 2 công việc trên.

Do LED1 được nối sẵn với P0.4 nên mình dùng luôn P0.4 cho ví dụ này nhé.

Ta cần kết nối P0.4 với nút nhấn BTN:

word image 8531 12

Yêu cầu của ví dụ 3 tương tự như ví dụ 2 vừa rồi.

Vì chúng ta đang sử dụng cả hai chức năng input và output trên cùng pin, nên sau mỗi lần output, các bạn nhớ đặt pin = 1 để trở về chức năng input nhé.

Thêm một lưu ý nữa, khi nút được nhấn thì P0.4 được nối với GND, điều này vô tình làm cho LED1 sáng ngay khi vừa nhấn nút. Các bạn lưu ý điểm này để tránh hiểu lầm rằng chương trình chạy sai nhé. Sau khi thả nút thì LED1 sẽ tắt và chương trình diễn ra như ở ví dụ 2 thôi.

Chỉ cần sửa lại chương trình một chút:

#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     BTN         P04
#define     PRESSED     0



void delay1ms(uint16_t duration) {
    for (; duration > 0; duration--) {
        uint16_t i = 1700;
        while (--i);
    }
}



void main(void) {

    // Insert code

    // P0.4 (LED1/BTN) quasi-bidiretional mode
    P0M1 &= CLR_BIT4;
    P0M2 &= CLR_BIT4;

    // // khởi tạo giá trị mặc định
    // LED1 = OFF_LED;

    // dùng chức năng input
    BTN = 1;

    while (1) {
        if (BTN == PRESSED) {
            while (BTN == PRESSED); // chờ đến khi thả nút

            delay1ms(1000);

            LED1 = ON_LED;
            delay1ms(200);
            LED1 = OFF_LED;
            delay1ms(200);

            LED1 = ON_LED;
            delay1ms(200);
            LED1 = OFF_LED;
            delay1ms(200);

            LED1 = ON_LED;
            delay1ms(200);
            LED1 = OFF_LED;
            delay1ms(200);

            // trở về chức năng input
            BTN = 1;
        }
    }
}

 

Sau khi biên dịch và nạp, chương trình sẽ chạy như thế này:

Trong thực tế, hiếm khi chúng ta phải kết nối LED và nút nhấn trên cùng một pin như ở ví dụ 3. Tuy nhiên, mình muốn đưa ra ví dụ 3 để các bạn biết cách đọc/ghi (2 chiều) dữ liệu trên cùng một pin theo một cách dễ tiếp cận nhất. Một số giao thức yêu cầu đọc/ghi dữ liệu trên cùng một pin như: I2C, 1-wire,… nên mode Quasi-bidirectional sẽ cực kì hữu ích trong những trường hợp như thế.

Lập trình Nuvoton GPIO Mode Push-pull

Cấu tạo và nguyên lý hoạt động.

Mode này có cấu trúc đơn giản hơn mode Quasi-bidirectional:

word image 8531 13

Khi hoạt động, luôn có 1 trong 2 MOSFET mở. Bất kì kết nối nào bên ngoài đều không thể thay đổi mức logic trên Port Pin, do đó mà mode này không thích hợp để đọc tín hiệu. Nói cách khác, mode Push-pull chỉ có chức năng output.

Nhờ khả năng dẫn dòng cao của cả 2 MOSFET nên mode này thường được dùng để lái tải nặng với dòng source/sink cho phép đến 20mA. Nếu một pin chỉ cần chức năng output hoặc cần dòng source cao, ta có thể xem xét và ưu tiên dùng mode Push-pull thay cho mode Quasi-bidirectional.

Ví dụ 4 – Chức năng output

Tương tự ví dụ 1, nhưng lần này sử dụng P0.4 ở mode Push-pull.

Ta chỉ việc thay đổi một chút câu lệnh cấu hình mode của P0.4: cài đặt P0M1 và P0M2 ở bit thứ 4 lần lượt là 0 và 1.

#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



void delay1ms(uint16_t duration) {
    for (; duration > 0; duration--) {
        uint16_t i = 1700;
        while (--i);
    }
}



void main(void) {

    // Insert code

    // P0.4 (LED1) quasi-bidiretional mode
    P0M1 &= CLR_BIT4;
    P0M2 |= SET_BIT4;

    // khởi tạo giá trị mặc định
    LED1 = OFF_LED;

    while (1) {
        LED1 = ON_LED;
        delay1ms(500);
        LED1 = OFF_LED;
        delay1ms(500);
    }
}

 

Biên dịch và nạp, ta có kết quả tương tự như ví dụ 1:

Tóm lại thì mode Push-Pull hoạt động tương tự chức năng output của mode Quasi-Bidirectional, điểm khác biệt duy nhất nằm ở chỗ dòng source của mode Push-Pull lớn hơn.

Lập trình Nuvoton GPIO Mode Input-only

word image 8531 14

Mode này có cấu trúc khá đơn giản nhất trong 4 mode.

Ngay từ cái tên cũng đã mô tả rất rõ ràng chức năng duy nhất của mode này chỉ là đọc tín hiệu.

Port Pin nếu không có kết nối bên ngoài sẽ được thả nổi và không thể xác định mức logic nên mode này còn được gọi là “mode high impedance”.

Nhờ cấu tạo đơn giản, không có hệ thống các MOSFET như các mode còn lại nên điện áp input không bị ảnh hưởng bởi các MOSFET. Vì thế, mode này được dùng khi đọc tín hiệu analog. Chúng ta sẽ gặp lại mode này trong bài ADC.

Lập trình Nuvoton GPIO Mode Open-drain

word image 8531 15

Mode Open-drain có cấu trúc như mode Quasi-Bidirectional nhưng đã loại bỏ đi các PMOS. Do đó, mode Open-drain có cả chức năng output lẫn input.

Điểm khác biệt khi dùng chức năng output:

  • Mode Quasi-bidirectional khi xuất mức 1: điện áp trên port pin = VDD.
  • Mode Open-drain khi xuất mức 1: điện áp trên port pin không xác định (thả nổi).

Nhờ đặc tính thả nổi khi ghi (output) mức 1 nên mode Open-drain sẽ thay thế mode Quasi-bidirectional trong những trường hợp giao tiếp với ngoại vi sử dụng điện áp thấp hơn điện áp hoạt động của N76E885: module WiFi, module RF, module SIM,…

Điểm khác biệt khi dùng chức năng input:

  • Mode Quasi-bidirectional khi port pin bị bỏ trống: mức logic vẫn được xác định nhờ Very Weak PMOS như một điện trở pull-up bên trong.
  • Mode Open-drain khi port pin bị bỏ trống: không xác định được mức logic.

Đặc tính này hữu ích khi sử dụng một số chuẩn giao tiếp có nhiều node trên cùng bus như I2C.

Có một điều thú vị là nếu mắc thêm một điện trở pull-up bên ngoài port pin, thì cả 2 mode Open-drain và Quasi-bidirectional không có sự khác biệt quá lớn. Tuy nhiên sẽ chẳng ai làm thế vì bản thân mỗi mode được dùng cho từng ứng dụng đặc thù mà mình đã đề cập.

Các bạn có thể sử dụng lại các ví dụ 1, 2, 3 và áp dụng với mode Open-drain trong 2 trường hợp có và không có điện trở pull-up gắn ngoài (khoảng 10k) port pin để nhận ra các điểm khác biệt giữa 2 mode mà mình đã đề cập phía trên.

Nhìn chung, cả 2 mode chỉ khác nhau ở phạm vi ứng dụng, còn cách sử dụng là hoàn toàn như nhau. Do đó, mình sẽ không đưa ra thêm ví dụ.

Kết luận

Chúng ta đã khảo sát xong phần GPIO cơ bản của N76E885 nói riêng và 8051 Nuvoton nói chung. Mặc dù còn một số chức năng nữa liên quan đến GPIO nhưng mình không đề cập đến vì chỉ bấy nhiêu đã giúp chúng ta làm chủ GPIO cho rất nhiều ứng dụng.

Mình có làm một video về hàm delay1ms được sử dụng trong bài này, hi vọng nó có ích:

Hẹn các bạn ở bài tiếp theo!

 

5/5 - (1 bình chọn)

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *