Điều khiển màn hình LCD bằng vi điều khiển AVR qua giao tiếp SPI

MỤC LỤC

Bạn sẽ đi đến đâu

Trong bài ứng dụng này chúng ta không khảo sát nhiều cấu trúc AVR mà chủ yếu là tìm hiểu Text LCD cách điều khiển bằng AVR.

Text LCD

Sơ đồ chân

Các chân điều khiển RS, R/W, EN và các đường dữ liệu được nối trực tiếp với vi điều khiển. Tùy theo chế độ hoạt động 4 bit hay 8 bit mà các chân từ D0 đến D3 có thể bỏ qua hoặc nối với vi điều khiển, chúng ta sẽ khảo sát kỹ càng hơn trong các phần sau.

Bảng 1. Sơ đồ chân.
Bảng 1. Sơ đồ chân.

Thanh ghi và tổ chức bộ nhớ

    LCD chúng ta không cần quan tâm đến cách thức hoạt động của 2 thanh ghi này, vì thế cũng không cần khảo sát chi tiết chúng. HD44780U có 3 loại bộ nhớ, đó là bộ nhớ RAM dữ liệu cần hiển thị DDRAM (Didplay Data RAM), bộ nhớ chứa ROM chứa bộ font tạo ra ký tự CGROM (Character Generator ROM) và bộ nhớ RAM chứa bộ font tạo ra các symbol tùy chọn CGRAM (Character Generator RAM). Các ký tự nằm ngoài 32 ô nhớ trên sẽ không được hiển thị, tuy nhiên vẫn không bị mất đi, chúng có thể được dùng cho các mục đích khác nếu cần thiết.

    Tiếp theo, chip HD44780U đọc giá trị 97 này và coi như là địa chỉ của vùng nhớ CGROM, nó sẽ tìm đến vùng nhớ CGROM có địa chỉ 97 và đọc bảng font đã được định nghĩa sẵn ở đây, sau đó xuất bản font này ra các “chấm” trên màn hình LCD tại vị trí đầu tiên của dòng 2 trên LCD. Như mô tả, công việc của người lập trình điều khiển LCD tương đối đơn giản, đó là viết mã ASCII vào bộ nhớ DDRAM tại đúng vị trí được yêu cầu, bước tiếp theo sẽ do HD44780U đảm nhiệm. CGRAM là vùng nhớ chứa các symbol do người dùng tự định nghĩa, mỗi symbol được có kích thước 5x8 và được dành cho 8 ô nhớ 8 bit.

    Tài liệu này không đề cập đến sử dụng bộ nhớ CGRAM nên tôi sẽ không đi chi tiết phần này, bạn có thể tham khảo datasheet của HD44780U để biết thêm.

    Điều khiển hiển thị Text LCD

    Để đọc và ghi data từ LCD chúng ta cần tạo một “xung cạnh xuống” trên chân EN, nói theo cách khác, muốn ghi dữ liệu vào LCD trước hết cần đảm bảo rằng chân EN=0, tiếp đến xuất dữ liệu đến các chân D0:7, sau đó set chân EN lên 1 và cuối cùng là xóa EN về 0 để tạo 1 xung cạnh xuống. Danh sách lệnh trên được tôi tô 2 màu khác nhau, các lệnh màu đỏ sẽ được dùng thường xuyên trong lúc hiển thị LCD và các lệnh màu xanh thường chỉ được dùng 1 lần trong lúc khởi động LCD, riêng lệnh Read BF có thể được dùng hoặc không tùy theo cách viết chương trình wait_LCD. - Cursor home – đưa con trỏ về vị trí đầu, dòng 1 của LCD: lệnh này thực hiện việc đưa con trỏ về vị trí đầu tiên của bộ nhớ DDRAM, vì thế nếu sau lệnh này một biến được ghi vào DDRAM thì biến này sẽ nằm ở vị trí đầu tiên (1;1).

    Kế đến là nhóm lệnh màu xanh: nhóm lệnh này thường chỉ thực hiện 1 lần (ít nhất là trong bài học này) và thường được viết chung trong 1 chương trình con khởi động LCD ( chúng ta gọi là init_LCD trong bài học này). Mã lệnh cho lệnh này có dạng 00001DCB trong đó D (Display) cho phép hiển thị LCD nếu mang giá trị 1, C (Cursor) bằng 1 thì cursor sẽ được hiển thị và B là blinking cho cursor tại vị trí hiển thị (blinking là dạng 1 ô đen nhấp nháy tại vị trí ký tự đang hiển thị). Trong đó nếu DL=1 (DL: Data Length) thì mode giao tiếp 8 bit sẽ được dùng, lúc này tất cả các chân từ D0 đến D7 phải được kết nối với bộ điều khiển ngoài. Nếu DL=0 thì mode 4 bit được dùng, trong trường hợp này chỉ có 4 chân D4:7 được dùng để truyền nhận dữ liệu và kết nối với bộ điều khiển ngoài, các chân D0:3 được để trống. F là kích thước font chữ hiển thị, do LCD có 2 bộ font chữ có sẵn trong CGROM nên chúng ta cần lựa chọn thông qua bit F, nếu F=1 bộ font 5x10 được sử dụng và nếu F=0 thì font 5x8 được hiển thị. 2 bit thấp trong mã lệnh này có thể được gán giá trị tùy ý. Ví dụ trong bài này sử dụng cả 2 mã lệnh trên. Như trình bày trong lệnh function set, có 2 mode để ghi và đọc dữ liệu vào LCD đó là mode 8 bit và mode 4 bit:. dụ PORTC của ATmega32 trong ví dụ của bài này) như trong hình 3.

    Các instruction và data 8 bit sẽ được ghi và đọc bằng cách chia thành 2 phần, gọi là các Nibbles, mỗi nibble gồm 4 bit và được giao tiếp thông qua 4 chân D7:4, nibble cao được xử lí trước và nibble thấp sau.

    AVR và Text LCD

    Trình tự giao tiếp Text LCD

    Ưu điểm của phương pháp giao tiếp này là dữ liệu được ghi và đọc rất nhanh và đơn giản vì chip điều khiển chỉ cần xuất hoặc nhận dữ liệu trên 1 PORT. Tuy nhiên, phương pháp này có nhược điểm là tổng số chân dành cho giao tiếp LCD quá nhiều, nếu tính luôn cả 3 chân điều khiển thì cần đến 11 đường cho giao tiếp LCD. Tuy nhiên, việc đọc và ghi từng nibble tương đối khó khăn hơn đọc và ghi dữ liệu 8 bit.

    Trong bài học này, tôi sẽ trình bày 2 chương trình con được viết riêng để ghi và đọc các nibbles gọi là Read2Nib và Write2Nib. Để sử dụng LCD chúng ta cần khởi động LCD, sau khi được khởi động LCD đã sẵn sàng để hiển thị. Trong bài này, quá trình khởi động được viết trong 1 chương trình con tên int_LCD, khởi động LCD thường bao gồm xác lập cách giao tiếp, kích thước font, số dòng LCD (funcstion set), cho phép hiển thị LCD, sursor…(Display control), chế độ hiển thị tăng/giảm, shift (Entry mode set).

    Các thủ tục khác như xóa LCD, viết ký tự lên LCD, di chuyển con trỏ…được sử dụng liên tục trong quá trình hiển thị LCD và sẽ được trình bày trong các đoạn chương trình con riêng.

    AVR giao tiếp với Text LCD trong WinAVR

    Trong trường hợp của bài này, bạn thấy tôi định nghĩa CTRL là PORTB nghĩa là PORTB được dùng để kết nối với các chân điều khiển LCD, vì CTRL là PORTB nên DDR_CTRL phải là DDRB (thanh ghi điều khiển hướng của PORTB). Việc chọn các PORT giao tiếp và thứ tự chân phụ thuộc vào kết nối thật trong mạch điện giao tiếp, bạn phải thay đổi các định nghĩa này cho phù hợp với thiết kế mạch điện của bạn. Ví dụ, một người không muốn dùng PORTB để điều khiển LCD mà dùng PORTA thì người này chỉ cần thay đổi định nghĩa ở 2 dòng 7 và 8, không cần thay đổi nội dung các hàm vì trong các hàm này chúng ta chỉ dùng tên thay thế là CTRL và DDR_CTRL.

    Chúng ta cần đổi hướng của PORT dữ liệu trên AVR để sẵn sàng nhận dữ liệu về, do chỉ có 4 bit cao của PORT data kết nối với các đường data của LCD (vì đây là mode 4 bit) nên chỉ cần set hướng cho 4 bit này trên AVR, dòng 6 thực hiện việc set hướng. Trong chế độ 4 bit, LCD sẽ truyền và nhận nibble cao trước vì thế dòng 7 đọc dữ liệu từ LCD thông qua các chân DATA_I vào biến HNib, chú ý là chúng ta chỉ cần lấy 4 bit cao của DATA_I nên cần phải dùng giải thuật mặt nạ (mask) che các bit thấp lại (and với 0xF0). Bằng cách kiểm tra sự có mặt của biến LCD8BIT chương trình sẽ biết cách ghi và đọc LCD phù hợp, phương pháp dùng #ifdef LCD8BIT được áp dụng cho tất cả các hàm sau này.

    Trong trường hợp mode 4 bit được sử dụng (#else), quá trình kiểm tra cờ BF cũng tương tự, điểm khác nhau duy nhất là cách đọc dữ liệu về có khác, chúng ta dùng hàm Read2Nib đã được viết trước đó để nhận giá trị về (xem dòng 23). Như đã trình bày, chúng ta có thể viết hàm wait_LCD bằng cách dùng hàm delay một khoảng thời gian cố định, trong dòng 29 bạn thấy một hàm _delay_ms(1) không được sử dụng, nếu muốn bạn có thể xóa hết các dòng lệnh trước đó trong hàm wait_LCD và dùng hàm delay này để thay thế, LCD vẫn sẽ hoạt động tốt. Theo mặc định, khi vừa khởi động LCD thì mode 8 bit sẽ được chọn, vì thế nếu một hàm nào đó đươc ghi vào LCD đầu tiên, LCD sẽ cố gắng đọc hết các chân D0:7 để lấy dữ liệu, do trong mode 4 bit các chân D0:3 không được kết nối với AVR nên việc đọc lần đầu có thể dẫn đến sai số.

    Hình 7. Ví dụ Kết nối LCD với AVR trong mode 4 bit (chip mega8).
    Hình 7. Ví dụ Kết nối LCD với AVR trong mode 4 bit (chip mega8).