
Làm thế nào để thiết kế một module phần cứng có thể nhận data theo chuẩn UART ?
Tìm hiểu đối tượng thiết kế:
Trước hết, thử tìm hiểu chuẩn UART là gì?
Từ google, ta có thể tìm thấy được các tài liệu về chuẩn bus này. Có bạn có thể chon bất kỳ nguồn nào cũng được vì chuẩn này rất thông dụng.
Ví dụ, Wikipedia có đoạn mô tả về chuẩn UART như sau:
Tóm tắt các thông tin yêu cầu của thiết kế và các ý tưởng thiết kế sơ khai.
Để thiết kế được phần cứng nhận theo chuẩn UART, các bạn cần 1 số lưu ý sau:
1.Baudrate: Là tốc độ bit được truyền, tính bằng số bit/giây. Giá trị này chỉ đúng khi xét ở một lần truyền, với 1 byte data tương ứng.
2.Tín hiệu clock: Để xử lý nhận dữ liệu thông qua chân RX, chúng ta phải sử dụng clock nội ( internal clock ). Clock nội này có tần số phải đủ lớn để có thể lấy mẫu được RX đồng thời có thể phát hiện được sự thay đổi mức của RX. Thông thường, các board FPGA đều có nguồn clock trên chip từ 50Mhz - 100Mhz. Chúng ta có thể sử dụng trực tiếp nguồn clock này.
3.Số lượng bit trong 1 lần truyền: Thông thường, UART có các 3 thông tin cần phân biệt: Baudrate, Stop bit, Parity bit. Tùy theo nhu cầu, chuẩn UART có dạng như sau:
- START BIT - 8BIT DATA - PARITY BIT - STOP BIT: Tổng cộng sẽ có 11bits trong 1 lần truyền.
- START BIT - 8BIT DATA - STOP BIT: Tổng cộng sẽ có 10bits trong 1 lần truyền
- START BIT - 8BIT DATA: Tổng cộng sẽ có 9bits trong 1 lần truyền.
4. FIFO lưu dữ liệu: Vì UART truyền đi 1 byte với các bit là nối tiếp nhau, do đó, để lưu lại và sử dụng data 8bits sau khi kết thúc, chúng ta cần có 1 bộ đệm để lưu các bits dữ liệu. Người ta gọi đó là Buffer, hoặc FIFO, hoặc đơn giản là Registers.
5. State Machine: Đối với những khối logic có chức năng và hoạt động mang tính chu trình, lặp đi lặp lại và đi qua các trạng thái cố định, chúng ta thường sử dụng State Machine. Logic của State Machine này sẽ được nói đến chi tiết ở phần sau.
Sau đây, chúng ta tìm hiểu thiết kế 1 module nhận - UART Receiver có đặc điểm:
- 1 bit Start, 1 bit Stop, 8 Bit data, 0 Parity
- Baudrate 9600
- Internal clock 100Mhz
- Bộ đệm nhận giá trị ( Receiver FIFO) 8Bit.
// Code:
// counter of baudrate
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) begin
counter <= 14'd0;
end
else begin
if ( state != `IDLE) begin
if ( counter_end == 1'b1 ) counter <= 14'd0;
else counter <= counter + 14'd1;
end
end
end
// rx data counter
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) data_cnt <= 3'b000;
else if ((state == `DATA) && (counter_end == 1'b1)) data_cnt <= data_cnt + 3'd1;
end
Triển khai mô tả đầy đủ của phần cứng dạng Block Diagram
Sơ đồ State Machine:
Sử dụng state machine với 4 State. IDLE, START, DATA, STOP. Do đó, cần 1 register 2 bits để có thể mã hóa được 4 states trên. Trong đó, START và STOP kéo dài trong 1 thời gian bit theo Baudrate. DATA kéo dài trong 8 thời gian bit theo Baudrate. IDLE thì ko có thời hằng, đó là trạng thái tồn tại khi không có quá trình nhận được yêu cầu từ tín hiệu RX.
State Machine UART Receiver
Timing Counter:
_timing counter: dùng để đếm số lượng xung internal clock để cho ra thời gian bit theo Baudrate. Ví dụ, đối với Baudrate 9600, số lượng bit trong 1 giây là 9600, tương ứng với tần số 9600Hz, hay 1 chu kỳ bit là 1/9600 = 1.04167e-4 (s) = 0.104167 (ms) = 1041.67(us) = 1041670 (ns).
Tần số của Internal clock là 100Mhz, tương ứng với chu kỳ là 10(ns). Vậy để tạo ra chu kỳ bits theo Baudrate 9600 thì timing counter theo internal clock phải đếm đến giá trị 1041670/10 = 104167.
Giá trị 104167 trong thập phân tương ứng với 13bit nhị phân: 13'b1_1001_0110_1110_0111. Do đó, timing counter phải có bitwidth là 13.
Data Bit counter: Data Bit counter có 2 tác dụng:
- Để tạo điều kiện dừng cho State Machine trong state DATA
- Tạo chỉ số để lưu giá trị nhận vào FIFO.
Giá trị của Data Bit Counter tối đa là 7, vì số lượng bit data là 8 và chỉ số bắt đầu là từ "0".
Do đó, Data Bit Counter chỉ cần dùng 3 bits là đủ.
Received Data FIFO:
Dạng sóng của UART
Như hình vẽ về waveform của UART, data có thể được chốt tại mỗi khoảng giữa của thời gian bits. Có nghĩa là khoảng thời gian khi timing counter được 1/2 giá trị. Tại thời điểm đó, tín hiệu DATA đã ổn định, sẽ được lưu vào FIFO. Theo Baudrate 9600, giá trị counter để lưu dữ liệu là 104167/2. Để có thể lấy được số nguyên, ta chấp nhận 52083. Thực chất, đây là một thanh ghi ( FlipFlop) 8bits.
Đồng bộ tín hiệu RX:
Để có thể chuyển từ IDLE sang START cần có 1 tín hiệu báo hiệu sự sườn xuống của tín hiệu RX. Để làm điều này, trước tiên, chúng ta phải đồng bộ tín hiệu RX về internal clock. Việc đồng bộ này nhằm tránh hiện tượng không ổn định của tín hiệu do sự sai lệch về pha của RX và Internal clock. Mạch đồng bộ thường gồm 2 FlipFlop nối nối tiếp nhau, được cấp clock là internal.
Mạch đồng bộ RX
Phát hiện sự kiện START
Để bắt được sườn xuống của RX làm sự kiện Start, chúng ta dùng mạch bắt cạnh xuống. Tín hiêu ra sẽ báo hiệu thời điểm bắt đầu của Bit START.
Mạch phát hiện sườn xuống của RX
Tín hiệu start sẽ được nối vào Logic của state. Tuy nhiên, để chuyển state đầy đủ thì chúng ta cần các tín hiệu khác nữa, bao gồm Timing Counter và Data Bit counter.
// Code:
// synchronous ff for rx
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) begin
syncff0 <= 1'b1;
syncff1 <= 1'b1;
end
else begin
syncff0 <= rx;
syncff1 <= syncff0;
end
end
// start detected
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) rx_ <= 1'b1;
else rx_ <= syncff1;
end
// start
assign start = ~syncff1 & rx_;
Phần điều khiển của State Machine
// Code:
wire counter_end, start, start_end, data_end, stop_end, get_dat;
assign get_dat = (counter == `CNT_HALF); // timing of getting serial data
assign counter_end = (counter == `CNT); // end timing of baud period
assign start_end = (state == `START) && (counter_end == 1'b1); //
assign data_end = (state == `DATA) && (counter_end == 1'b1) && (data_cnt == 3'd7);
assign stop_end = (state == `STOP) && (counter_end == 1'b1);
// state
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) state <= `IDLE;
else state <= next;
end
// next
always @ (*) begin
case ( state )
`IDLE: if ( start == 1'b1 ) next = `START;
else next = `IDLE;
`START: if ( start_end == 1'b1 ) next = `DATA;
else next = `START;
`DATA: if ( data_end == 1'b1 ) next = `STOP;
else next = `DATA;
`STOP: if ( stop_end == 1'b1 ) next = `IDLE;
else next = `STOP;
default: next = `IDLE;
endcase
end
Điều khiển thông qua giá trị nhận của Reveiver:
Nếu chỉ nhận giá trị của RX mà không sử dụng thì cũng không có ý nghĩa gì. Để áp dụng một số hoạt động điều khiển vào mạch nhận, chúng ta cần thêm một vài logic để điều khiển. Về cơ bản, có thể dùng chính giá trị của RX data nhận được và mã hóa nó thành giá trị điều khiển. Giá trị data này là 8 bits, do đó, về lý thuyết thì có thể mã hóa được 2^8 = 256 lệnh. Sau đây là sơ đồ khối cho chức năng này:
Các ứng dụng điều khiển thông qua giá trị dữ liệu nhận được.
Ở ví dụ của code này, sẽ thực hiện 3 hành động. Bật tắt LED FIX và LED TOUCH.
// rx data
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) begin
rdat <= 8'h00;
end
else begin
if ((state == `DATA) && (get_dat == 1'b1)) rdat <= {syncff1, rdat[7:1]};
else rdat <= rdat;
end
end
// output decode
always @ (posedge clock) begin
if ( resetn == 1'b0 ) begin
led_fix <= 1'b0;
led_touch <= 1'b0;
end
else begin
if ( stop_end == 1'b1 ) begin
case (rdat)
8'h61: begin // button1 push
led_fix <= ~led_fix;
end
8'h62: begin // button2 down
led_touch <= 1'b1;
end
8'h63: begin // button2 up
led_touch <= 1'b0;
end
default: begin
led_fix <= 1'b0;
led_touch <= 1'b0;
end
endcase
end
end
end
Tổng hơp:
Từ tất cả các phần bên trên, ta có một phần cứng nhận UART như sau:
Các bạn có thể dùng ý tưởng này để coding và thiết kế cho hệ thống có hỗ trợ chuẩn truyền UART. Các module thông dụng như GPS, Camera, BlueTooth cũng sử dụng chuẩn UART để giao tiếp.
//Full code:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
`define CNT 10416
`define CNT_HALF 5028
`define IDLE 2'b00
`define START 2'b01
`define DATA 2'b10
`define STOP 2'b11
module uart_rx_top(
input wire clock,
input wire resetn,
input wire rx,
//output wire tx
output reg led_fix,
output reg led_touch
);
// baudrate 9600 counter.
reg [13:0] counter;
reg syncff0, syncff1;
reg rx_;
reg [1:0] state, next;
reg [7:0] rdat;
reg [2:0] data_cnt;
wire counter_end, start, start_end, data_end, stop_end, get_dat;
assign get_dat = (counter == `CNT_HALF); // timing of getting serial data
assign counter_end = (counter == `CNT); // end timing of baud period
assign start_end = (state == `START) && (counter_end == 1'b1); //
assign data_end = (state == `DATA) && (counter_end == 1'b1) && (data_cnt == 3'd7);
assign stop_end = (state == `STOP) && (counter_end == 1'b1);
// synchronous ff for rx
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) begin
syncff0 <= 1'b1;
syncff1 <= 1'b1;
end
else begin
syncff0 <= rx;
syncff1 <= syncff0;
end
end
// Company: DegicLab
// Engineer: Admin
// Website: degiclab.blogspot.com
// start detected
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) rx_ <= 1'b1;
else rx_ <= syncff1;
end
// start
assign start = ~syncff1 & rx_;
// state
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) state <= `IDLE;
else state <= next;
end
// next
always @ (*) begin
case ( state )
`IDLE: if ( start == 1'b1 ) next = `START;
else next = `IDLE;
`START: if ( start_end == 1'b1 ) next = `DATA;
else next = `START;
`DATA: if ( data_end == 1'b1 ) next = `STOP;
else next = `DATA;
`STOP: if ( stop_end == 1'b1 ) next = `IDLE;
else next = `STOP;
default: next = `IDLE;
endcase
end
// Company: DegicLab
// Engineer: Admin
// Website: degiclab.blogspot.com
// counter of baudrate
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) begin
counter <= 14'd0;
end
else begin
if ( state != `IDLE) begin
if ( counter_end == 1'b1 ) counter <= 14'd0;
else counter <= counter + 14'd1;
end
end
end
// rx data counter
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) data_cnt <= 3'b000;
else if ((state == `DATA) && (counter_end == 1'b1)) data_cnt <= data_cnt + 3'd1;
end
// rx data
always @ ( posedge clock ) begin
if ( resetn == 1'b0 ) begin
rdat <= 8'h00;
end
else begin
if ((state == `DATA) && (get_dat == 1'b1)) rdat <= {syncff1, rdat[7:1]};
else rdat <= rdat;
end
end
// output decode
always @ (posedge clock) begin
if ( resetn == 1'b0 ) begin
led_fix <= 1'b0;
led_touch <= 1'b0;
end
else begin
if ( stop_end == 1'b1 ) begin
case (rdat)
8'h55: begin // button1 push
led_fix <= ~led_fix;
end
8'h62: begin // button2 down
led_touch <= 1'b1;
end
8'h63: begin // button2 up
led_touch <= 1'b0;
end
default: begin
led_fix <= 1'b0;
led_touch <= 1'b0;
end
endcase
end
end
end
endmodule
Testbench:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
module testbench();
reg clock, resetn, rx;
wire led_fix, led_touch;
integer i;
uart_rx_top dut (
.clock ( clock ),
.resetn ( resetn ),
.rx ( rx ),
.led_fix ( led_fix ),
.led_touch ( led_touch )
);
initial begin
clock = 1'b0;
resetn = 1'b1;
rx = 1'b1;
end
always begin
#5; clock = ~ clock;
end
// DegicLab Group
// Engineer: Admin
// Website: https://degiclab.blogspot.com/
// Facebook: https://www.facebook.com/DegicLab/
initial begin
#100; resetn = 1'b0;
#100; resetn = 1'b1;
#10000; rx = 1'b0; // Start
// baudrate at 9600.
// time unit is in (ns)
for ( i = 0; i <= 7; i = i + 1) begin
#104170; rx = ~rx;
end
#104170; rx = 1'b1; // stop
#1000;
$finish;
end
endmodule
Waveform:
Nguồn: degiclab.blogspot.com
Mong Muốn Có Thêm Cơ Hội Trong Công Việc
Và Trở Thành Một Người Có Giá Trị Hơn
Bạn Chưa Biết Phương Thức Nào Nhanh Chóng Để Đạt Được Chúng
Hãy Để Chúng Tôi Hỗ Trợ Cho Bạn. SEMICON
Hotline: 0972.800.931 - 0938.838.404 (Mr Long)