Các Module FPGA nâng Cao
1. module lcd
Phần I : Cơ sở lý thuyết
Trong phần này mình chỉ nhắc đến những thứ quan trọng để hiểu rõ hơn thì các bạn đọc trong phần LCD của giáo trình thực hành kit DE2
a, các tín hiệu quan trọng.
LCD_DATA data 8 bit, LCD_RW read/wirte, LCD_EN tín hiệu enable, LCD_ON tín hiệu nguồn vào, LCD_BLON đèn màn hình, LCD_RS chọn đọc dữ liệu hay đọc Command ( Command dùng để chứa 1 số các thiết lập lcd cơ bản )
chú ý các tín hiệu LCD_ON và LCD_BLON luôn là 1
chúng ta quan tâm đến 2 tín hiệu LCD_RW và LCD_RS nếu chỉ hiển thị lên LCD thì tín hiệu LCD_RW = 0, khi cài đặt vị trí hiển thị kí tự thì ta reset LCD_RS xuống mức 0, ghi dữ liệu thì ta set lên 1.
Trong phần này mình chỉ nhắc đến những thứ quan trọng để hiểu rõ hơn thì các bạn đọc trong phần LCD của giáo trình thực hành kit DE2
a, các tín hiệu quan trọng.
LCD_DATA data 8 bit, LCD_RW read/wirte, LCD_EN tín hiệu enable, LCD_ON tín hiệu nguồn vào, LCD_BLON đèn màn hình, LCD_RS chọn đọc dữ liệu hay đọc Command ( Command dùng để chứa 1 số các thiết lập lcd cơ bản )
chú ý các tín hiệu LCD_ON và LCD_BLON luôn là 1
chúng ta quan tâm đến 2 tín hiệu LCD_RW và LCD_RS nếu chỉ hiển thị lên LCD thì tín hiệu LCD_RW = 0, khi cài đặt vị trí hiển thị kí tự thì ta reset LCD_RS xuống mức 0, ghi dữ liệu thì ta set lên 1.
Trong phần hiển thị thì tín hiệu LCD_R/W các bạn luôn đặt là 0, còn LCD_RS khi muốn cấu hình LCD thì là 0, khi hiển thị dữ liệu thì được set lên 1
b, bảng mã kí tự trong ROM, và thanh ghi AC
ví dụ trong LCD 1602 bạn muốn ghi kí tự A lên ô thứ 15 hàng 2 thì bạn phải set thanh ghi AC lên tương ứng với bảng vị trí các ô trong LCD như hình dưới
ví dụ trong LCD 1602 bạn muốn ghi kí tự A lên ô thứ 15 hàng 2 thì bạn phải set thanh ghi AC lên tương ứng với bảng vị trí các ô trong LCD như hình dưới
ô thứ 15 hàng 2 là 4E thanh ghi AC ( thanh ghi 7 bit ) là 1 0 0 1 1 1 0 còn kí tự A ta sẽ tìm mã ( 8 bit ) trong bảng kí tự trong ROM như hình dưới.
Bảng kí tự gồm chiều ngang là 4 bit cao chiều dọc là 4 bit thấp, bảng mã cung cấp các kí tự đơn giản đã được định nghĩa sẵn muốn hiển thị kí tự A thì dữ liệu vào sẽ là LHLL LLLH tức là 0100 0001 ok quá đơn giản.
Nói qua 1 chút nếu các bạn muốn có thêm những kí tự mới chúng ta cũng có thể định nghĩa 1 kí tự và lưu lại trong bộ tạo kí tự RAM ( đọc thêm ở tài liệu nhé )
c, Tập lệnh điều khiển LCD
LCD cung cấp cho chúng ta 1 số tập lệnh cơ bản để điều khiển LCD ví dụ : clear màn hình, tự động tăng con trỏ ( tự động tăng vị trí ), set chế độ giao tiếp là 4 bit hay 8 bit....
Muốn đọc 8 bit điều khiển LCD thì LCD_RS = 0 và LCD_R/W = 0, phần dưới mình sẽ đến những lệnh cơ bản nhất để tìm hiểu đầy đủ hãy đọc tài liệu
1, Clear Display
Mã lệnh: LCD_DATA= 0 0 0 0 0 0 0 1
lệnh này tắt hiển thị kí tự tất cả các ô sẽ có khoảng trắng, con trỏ về vị trí đầu tiên của LCD
2, Return home
Mã lệnh: LCD_DATA= 0 0 0 0 0 0 1 x
lệnh này trả thanh ghi AC về 0
3, Write DATA
lúc này tín hiệu LCD_RS = 1 và LCD_RW = 0, LCD_DATA = địa chỉ của kí tự cần ghi trong ROM
.............
Phần II: LCD Controller
Trong phần này chúng ta sẽ lập trình quá trình điều khiển đẩy dữ liệu từ input vào output LCD
Cài đặt chỉ hiển thị ( Write ) bằng lệnh
assign LCD_RW = 1'b0;
đoạn mã điều khiển quá trình Write Data
Nói qua 1 chút nếu các bạn muốn có thêm những kí tự mới chúng ta cũng có thể định nghĩa 1 kí tự và lưu lại trong bộ tạo kí tự RAM ( đọc thêm ở tài liệu nhé )
c, Tập lệnh điều khiển LCD
LCD cung cấp cho chúng ta 1 số tập lệnh cơ bản để điều khiển LCD ví dụ : clear màn hình, tự động tăng con trỏ ( tự động tăng vị trí ), set chế độ giao tiếp là 4 bit hay 8 bit....
Muốn đọc 8 bit điều khiển LCD thì LCD_RS = 0 và LCD_R/W = 0, phần dưới mình sẽ đến những lệnh cơ bản nhất để tìm hiểu đầy đủ hãy đọc tài liệu
1, Clear Display
Mã lệnh: LCD_DATA= 0 0 0 0 0 0 0 1
lệnh này tắt hiển thị kí tự tất cả các ô sẽ có khoảng trắng, con trỏ về vị trí đầu tiên của LCD
2, Return home
Mã lệnh: LCD_DATA= 0 0 0 0 0 0 1 x
lệnh này trả thanh ghi AC về 0
3, Write DATA
lúc này tín hiệu LCD_RS = 1 và LCD_RW = 0, LCD_DATA = địa chỉ của kí tự cần ghi trong ROM
.............
Phần II: LCD Controller
Trong phần này chúng ta sẽ lập trình quá trình điều khiển đẩy dữ liệu từ input vào output LCD
Cài đặt chỉ hiển thị ( Write ) bằng lệnh
assign LCD_RW = 1'b0;
đoạn mã điều khiển quá trình Write Data
mình sẽ giải thích đoạn code trên như sau. tín hiệu bắt đầu hiển thị mStart được điều khiển bởi tín hiệu input iStart và preStart
giả sử khi chư có dữ liệu được vào thì preStart và iStart đều bằng 0, khi bắt đầu có tín hiệu thì iStart được set lên 1 và preStart <=0 iStart ( có nghĩa là sau khi chu trình sau thì preStart mới được lên 1) lúc này preStart,iStart = 01 thì mStart sẽ được set lên 1 bắt đầu vào quá trình điều khiển.
thực chất vòng lặp case là 1 máy trạng thái ( đã giới thiệu ở phần FGPA cơ bản )
Code LCD Controll dowload tại đây
Phần III: Hiển thị chuỗi kí tự.
Giả sử chúng ta cần hiển thị từ HELLO WORLD lên dòng 1 LCD, từ chúng ta muốn hiển thị bao gồm 11 kí tự, 1 hàng gồm 16 ô mình sẽ dùng 2 ô đầu tiên và 3 ô cuối cùng của dòng 1 là ký tự trống và 16 ô hàng 2 cũng là kí tự trống.
Muốn hiển thị lên LCD 11 kí tự trên ta phải có 1 bộ chia tần ( chính là module div_frequency trong module chính ) cái này mình nói rồi trong phần FPGA cơ bản.
Tiếp theo 11 kí tự sẽ được điều khiển để trở thành input của module lcd_controller, để làm được điều này ta dùng 1 biến LUT_DATA 9 bit ( gồm 1 bit cao là bit LCD_RS và 8 bit còn lại là 8 bit dữ liệu )
Biến LUT_INDEX chạy từ 0 đến 38:
Cũng giống như LCD_controller cũng cần máy trạng thái để điều khiển quá trình từ input ra output, thì LCD_test ( chính là chuong trình hiển thị HELLO WORLD) cũng cần máy trạng thái để điều khiển quá trình lựa chọn các giá trị LCD_DATA và LCD_RS.
Các bạn tự đọc để hiểu rèn luyện thêm nhé
Module hiển thị LCD chạy trên KIT DE2 - 70 dowload tại đây
giả sử khi chư có dữ liệu được vào thì preStart và iStart đều bằng 0, khi bắt đầu có tín hiệu thì iStart được set lên 1 và preStart <=0 iStart ( có nghĩa là sau khi chu trình sau thì preStart mới được lên 1) lúc này preStart,iStart = 01 thì mStart sẽ được set lên 1 bắt đầu vào quá trình điều khiển.
thực chất vòng lặp case là 1 máy trạng thái ( đã giới thiệu ở phần FGPA cơ bản )
- Đầu tiên ST ( state ) là 0 và được gán là 1 không quan tâm đến tín hiệu đầu vào thực chất là đây chỉ là 1 trạng thái đợi để tín hiệu phải trễ đi 1 khoảng thời gian của 1 xung clock
- Tiếp theo ST là 1 tín hiệu LCD_EN được set lên 1 ST được gán là 2
- Dùng 1 biến đếm từ 0 - 16 thực chất là đợi khoảng 16 x 1/ tần số của xung clock đầu vào dữ liệu lúc này đang được đẩy vào để hiển thị, chắc các bạn thắc mắc tại sao k thấy tín hiệu LCD_DATA trong máy trạng thái này vì ở bên trên tín hiệu LCD_DATA (output) đã được gán với tín hiệu iDATA (input) và chỉ được hiển thị khi LCD_EN = 1 sau đó ST gán là 3 ( quá hay phải k :D )
- Trong khi ST = 3 tín hiệu LCD_EN= 0 , mStart = 0, oDone = 1 (tín hiệu báo hiệu quá trình hiển thị 1 kí tự kết thúc LCD lại đang nhàn rỗi đợi kí tự tiếp theo) Cont = 0, ST = 0, thực chất ở đây các tín hiệu được reset về trạng thái ban đầu.OK
Code LCD Controll dowload tại đây
Phần III: Hiển thị chuỗi kí tự.
Giả sử chúng ta cần hiển thị từ HELLO WORLD lên dòng 1 LCD, từ chúng ta muốn hiển thị bao gồm 11 kí tự, 1 hàng gồm 16 ô mình sẽ dùng 2 ô đầu tiên và 3 ô cuối cùng của dòng 1 là ký tự trống và 16 ô hàng 2 cũng là kí tự trống.
Muốn hiển thị lên LCD 11 kí tự trên ta phải có 1 bộ chia tần ( chính là module div_frequency trong module chính ) cái này mình nói rồi trong phần FPGA cơ bản.
Tiếp theo 11 kí tự sẽ được điều khiển để trở thành input của module lcd_controller, để làm được điều này ta dùng 1 biến LUT_DATA 9 bit ( gồm 1 bit cao là bit LCD_RS và 8 bit còn lại là 8 bit dữ liệu )
Biến LUT_INDEX chạy từ 0 đến 38:
- Từ 0 - 4 chính là cấu hình cho LCD hoạt động ở chế độ 8 bit và clear màn hình ban đầu ( LCD_INTIAL+0 -> LCD_INTIAL+4) tín hiệu LCD_RS = 0
- Từ 16 chỉ số tiếp theo là dữ liệu hiển thị lên dòng 1 LCD ( LCD_LINE1+0 -> LCD_LINE1+15) tín hiệu LCD_RS = 1
- Chỉ số 17 để cấu hình cho LCD xuống dòng (LCD_CH_LINE) tín hiệu LCD_RS = 0
- 16 chỉ số cuối cùng để hiển thị dữ liệu lên dòng 2 LCD (LCD_LINE2+0 -> LCD_LINE2+15) tín hiệu LCD_RS = 1
Cũng giống như LCD_controller cũng cần máy trạng thái để điều khiển quá trình từ input ra output, thì LCD_test ( chính là chuong trình hiển thị HELLO WORLD) cũng cần máy trạng thái để điều khiển quá trình lựa chọn các giá trị LCD_DATA và LCD_RS.
Các bạn tự đọc để hiểu rèn luyện thêm nhé
Module hiển thị LCD chạy trên KIT DE2 - 70 dowload tại đây
MOdule PS2 Keyboard
Phần I. Cơ Sở Lý Thuyết
Bài Code này mình lấy từ quyển PFGA prototyping by Verilog Examples các bạn có thể dowload tại đây
Bài Code này mình lấy từ quyển PFGA prototyping by Verilog Examples các bạn có thể dowload tại đây
Chuẩn giao tiếp PS2 cũng tương tự như UART, nó truyền 1 khung gồm 11 bit, bao gồm 8 bit dữ liệu. 1 bit start ( báo hiệu bắt đầu khung truyền), 1 stop bit và 1 parity bit.
Cách truyền của nó như sau khi ta bấm 1 nút bất kì trên KEY BROAD thì tín hiệu xung clock (PS2C) từ 1 xuống 0 báo hiệu bắt đầu có dữ liệu (PS2D) vào từ đây tín hiệu clock sẽ tạo ra 11 sườn âm để truyền 1 bit data như trên hình.
Data truyền vào gồm 8 bit tương ứng với mã hexa của các nút như hình bên dưới.
Cách truyền của nó như sau khi ta bấm 1 nút bất kì trên KEY BROAD thì tín hiệu xung clock (PS2C) từ 1 xuống 0 báo hiệu bắt đầu có dữ liệu (PS2D) vào từ đây tín hiệu clock sẽ tạo ra 11 sườn âm để truyền 1 bit data như trên hình.
Data truyền vào gồm 8 bit tương ứng với mã hexa của các nút như hình bên dưới.
Phần II. Lập trình Module PS2 Keyboard
Chúng ta sẽ lập trình 3 khối chính bao gồm : chia tần, PS2_receive, khối hiển thị mã code tương ứng với các phím trên KEYBOARD.
Mình sẽ đi vào khối PS2_receiver đầu tiên để từ đó giải thích tại sao phải chia tần như vậy.
Trong khối Receiver ta phải giải quyết 2 công việc chính là làm sao để biết dữ liệu được truyền khi nào và làm sao để biết dữ liệu truyền đã kết thúc.
Chúng ta sẽ lập trình 3 khối chính bao gồm : chia tần, PS2_receive, khối hiển thị mã code tương ứng với các phím trên KEYBOARD.
Mình sẽ đi vào khối PS2_receiver đầu tiên để từ đó giải thích tại sao phải chia tần như vậy.
Trong khối Receiver ta phải giải quyết 2 công việc chính là làm sao để biết dữ liệu được truyền khi nào và làm sao để biết dữ liệu truyền đã kết thúc.
Đoạn code này gồm 2 thanh ghi chính :
filter_reg thanh ghi 7bit lưu trữ mức logic của xung PS2 CLK
f_ps2c_reg thanh ghi 1 bit báo hiệu mức 1 hay mức 0 của xung clock.
filter_reg thanh ghi 7bit lưu trữ mức logic của xung PS2 CLK
f_ps2c_reg thanh ghi 1 bit báo hiệu mức 1 hay mức 0 của xung clock.
Đầu tiên ps2c đang ở mức 1 (mũi tên đỏ) -> filter_next = 8’b11111111 -> ps2c_next =1
Khi ps2c ở mức 0 mũi tên xanh -> filter_next = 8’b00000000 -> ps2c_next= 0
Qua trình nó chuyển từ 1 về 0 làm cho bit fall_edge đc lên 1 báo hiệu ps2c có 1 sườn xuống, và cứ thế nó báo hiệu 11 lần. ( OK :D)
Vấn đề đặt ra là cái tín hiệu clk (cái dòng đầu tiên posedge clk) phải có độ lớn lớn hơn rất nhiệu xung ps2c vì quá trình ps2c đang ở mức 0 và mức 1 thực ra cũng rất bé.
Mình lấy ví dụ thế này nhé ps2c là xung 1Mhz đi thì mức 0 và mức 1 có thời gian là 1/2(10^6) đại loại là khá bé. Thế nên mình lấy xung clk của mình là 50Mhz chẳng hạn có mỗi lần sườn dương của xung 50Mhz (1/(50x 10^6)) thì nó sẽ đọc giá trị của ps2c thế nên phải chia tần làm sao để trong cái đoạn mức 0 và mức 1 nó đọc đủ 8 bit ( nghe cao siêu quá nhể ).
Về chia tần thì 1 nguyên tắc là chia cho càng lớn càng tốt cũng chả sao nói chung k hiểu thì cứ cho nó to lên, hiểu thì tìm khoảng giá trị tối ưu nhất.
Lập trình nhận 11 bit:
Khi ps2c ở mức 0 mũi tên xanh -> filter_next = 8’b00000000 -> ps2c_next= 0
Qua trình nó chuyển từ 1 về 0 làm cho bit fall_edge đc lên 1 báo hiệu ps2c có 1 sườn xuống, và cứ thế nó báo hiệu 11 lần. ( OK :D)
Vấn đề đặt ra là cái tín hiệu clk (cái dòng đầu tiên posedge clk) phải có độ lớn lớn hơn rất nhiệu xung ps2c vì quá trình ps2c đang ở mức 0 và mức 1 thực ra cũng rất bé.
Mình lấy ví dụ thế này nhé ps2c là xung 1Mhz đi thì mức 0 và mức 1 có thời gian là 1/2(10^6) đại loại là khá bé. Thế nên mình lấy xung clk của mình là 50Mhz chẳng hạn có mỗi lần sườn dương của xung 50Mhz (1/(50x 10^6)) thì nó sẽ đọc giá trị của ps2c thế nên phải chia tần làm sao để trong cái đoạn mức 0 và mức 1 nó đọc đủ 8 bit ( nghe cao siêu quá nhể ).
Về chia tần thì 1 nguyên tắc là chia cho càng lớn càng tốt cũng chả sao nói chung k hiểu thì cứ cho nó to lên, hiểu thì tìm khoảng giá trị tối ưu nhất.
Lập trình nhận 11 bit:
- n_reg lưu trữ số bit đã được truyền ( bao giờ đủ 11 bit thì thôi).
- b_reg lưu trữ giá trị data cần truyền.
Máy trạng thái điều khiển quá trình nhận 11 bit.
- State idle : khi fall_edege được lên 1 báo hiệu có start bit b_next được dịch sang phải 1 lần để nhận start bit này. (tại sao dịch phải mà k dịch trái cái này các bạn tự suy nghĩ xem sao ).n_next = 9, tại sao bằng 9 nhỉ vì bên trên nó đã nhận 1 start bit rồi nên chỉ còn 10 bit thôi, thì nó đếm từ 9 – 0 là đủ 10 bit
- State dps: truyền tiếp các bit tiếp theo khi tín hiệu n_reg = 0 thì nhảy sang trạng thái cuối.
- State load: trạng thái này chủ yếu để đệm vào giữa dps và idle.