MCU向FIFO寫入數據後,則發送FIFO的空狀態標誌位變爲非空,串口發送模塊監測到非空狀態後,讀取1個8位並行數據,按照MCU配置的波特率向串口芯片發送數據,發送的格式爲:1位起始位,8位數據位(數據位按照從低位到高位的方式,即發送bit0,bit1……,bit7),奇偶校驗位(如MCU配置無奇偶校驗位,則該位不發送,該奇偶校驗位可設爲奇校驗或偶校驗),停止位(可設爲1位或2位),發送結束後,重新檢測FIFO的空狀態位,如果非空則繼續讀取數據向串口芯片發送串行數據,直到FIFO爲空狀態。
串口發送模塊端口信號說明雙端口
端口名稱 |
位寬 |
類型 |
功能 |
clk |
1 |
輸入 |
時鐘(上升沿) |
rst |
1 |
輸入 |
復位信號(低有效) |
fifo_empty |
1 |
輸入 |
發送FIFO的空狀態標誌位(高有效,即爲空) |
fifo_data |
8 |
輸入 |
發送FIFO的8位輸出數據信號 |
fifo_rden |
1 |
輸出 |
發送FIFO的讀使能信號 |
serial_tx |
1 |
輸出 |
串口輸出信號 |
serial_baudrate |
8 |
輸入 |
串口波特率 |
serial_stop_bit |
2 |
輸入 |
串口停止位 |
serial_parity |
2 |
輸入 |
串口奇偶校驗位 |
復位期間,狀態機處於IDLE狀態,在該狀態下,串口的輸出一直爲高電平;
當檢測到待發送FIFO爲非空狀態時,進入RD_FIFO狀態,該狀態維持5個時鐘週期,在該狀態完成讀取FIFO中的一個8位數據的功能。首先產生FIFO的讀使能有效信號,然後將FIFO的輸出數據寄存下來,在該狀態下,串口的輸出一直爲高電平;
接下來是START_BIT狀態(發送起始位),該狀態維持的時間爲一個波特率寬度,即MCU如果將波特率配置爲0x30,則維持48個時鐘週期,如果將波特率設置爲0xC0,則維持192個時鐘週期。在該狀態下,串口的輸出一直爲低電平;
接下來是SEND_DATA狀態(發送數據位),該狀態維持的時間爲8個波特率寬度,8位數據中的每bit佔用1個波特率的時間寬度。發送時,首先bit0,依次bit1……,bit6,bit7。需要說明的是:雖然RS232串口爲負邏輯,但是如果待發送bit位爲高電平,則FPGA仍然輸出高電平,如果待發送bit位爲低電平,則FPGA仍然輸出低電平。負邏輯的轉換在外部串口接口芯片完成;
SEND_DATA狀態結束後,狀態機判斷serial_parity(MCU設置的奇偶校驗位),如果爲10或者01(進行奇偶校驗),則進入PARITY_BIT狀態,否則直接進入STOP_BIT狀態。在PARITY_BIT狀態維持一個波特率時間寬度,結束後進入STOP_BIT狀態。在該狀態產生校驗位,並且將校驗位輸出給串口接口芯片;
最後一個狀態是STOP_BIT狀態。該狀態保持的時間寬度爲一個或兩個波特率寬度,取決於MCU配置的serial_stop_bit信號。在該狀態下,串口的輸出一直爲高電平。
串口發送模塊的狀態轉移圖見下圖。
串口發送模塊狀態轉移圖
串口發送模塊的邏輯代碼如下:
module serial_t(
clk,
rst,
fifo_empty,
fifo_data,
fifo_rden,
serial_tx,
serial_baudrate,
serial_stop_bit,
serial_parity
);
//system signal
input clk;
input rst;
//fifo signal
input fifo_empty;//待發送FIFO的空狀態標誌
input [7:0] fifo_data; //待發送FIFO的8位輸出數據
output reg fifo_rden; //待發送FIFO的讀使能信號
//transmit serial signal
output reg serial_tx; //串口輸出信號
//serial_configure registers
input [7:0] serial_baudrate;
//MCU配置的串口波特率,如爲0x30,則爲230400bps;如爲0x60,則爲115200bps;
//如爲0xC0,則爲57600bps,其他爲115200bps;
input [1:0] serial_stop_bit;
//MCU配置的串口停止位,如爲10,則停止位爲2位;其他則爲1位;
input [1:0] serial_parity;
//MCU配置的串口奇偶校驗位,如爲01,則爲奇校驗;如爲10,則爲偶校驗;其他
//爲無校驗
reg [7:0] data;//將從FIFO讀出的數據進行寄存的寄存器
reg [3:0] bit_cnt;//8位數據中bit位
parameter IDLE =6'h01;
parameter RD_FIFO =6'h02;
parameter START_BIT =6'h04;
parameter SEND_DATA =6'h08;
parameter PARITY_BIT =6'h10;
parameter STOP_BIT =6'h20;
reg [5:0] cstate;//當前狀態
reg [5:0] nstate;//下一狀態
reg [2:0] rfifo_cnt;//讀FIFO計數器
reg [7:0] start_counter; //發送起始位計數器
reg [8:0] stop_counter; //發送停止位計數器
reg [7:0] send_data_counter; //發送數據位計數器
reg [8:0] stop_reg; //停止位總數
reg [7:0] parity_counter; //發送奇偶校驗位計數器
wire start_done; //發送起始位結束
wire parity_done; //發送奇偶校驗位結束
wire stop_done; //發送停止位結束
always @ (posedge clk or negedge rst)
if(rst==0)
rfifo_cnt<=3'b0;
else
if(nstate==RD_FIFO || cstate==RD_FIFO)
rfifo_cnt<=rfifo_cnt+1;
else
rfifo_cnt<=3'b0;
//產生讀FIFO計數器
always @ (posedge clk or negedge rst)
if(rst==0)
start_counter<=8'b0;
else
if(nstate==START_BIT || cstate==START_BIT)
start_counter<=start_counter+1;
else
start_counter<=8'b0;
//產生髮送起始位計數器
always @ (posedge clk or negedge rst)
if(rst==0)
send_data_counter<=8'b0;
else
if(nstate==SEND_DATA || cstate==SEND_DATA)
begin
if(send_data_counter==serial_baudrate)
send_data_counter<=8'b0;
else
send_data_counter<=send_data_counter+1;
end
else
send_data_counter<=8'b0;
//產生髮送數據位計數器
always @ (posedge clk or negedge rst)
if(rst==0)
parity_counter<=8'b0;
else
if(nstate==PARITY_BIT || cstate==PARITY_BIT)
parity_counter<=parity_counter+1;
else
parity_counter<=8'b0;
//產生髮送奇偶校驗位計數器
always @ (posedge clk or negedge rst)
if(rst==0)
stop_counter<=9'b0;
else
if(nstate==STOP_BIT || cstate==STOP_BIT)
stop_counter<=stop_counter+1;
else
stop_counter<=9'b0;
//產生髮送停止位位計數器
always @ (posedge clk or negedge rst)
if(rst==0)
cstate<=IDLE;
else
cstate<=nstate;
//時鐘沿到來時當前狀態等於下一狀態
assign start_done=(start_counter==serial_baudrate)?1'b1:1'b0;
assign parity_done=(parity_counter==serial_baudrate)?1'b1:1'b0;
assign stop_done=(stop_counter==stop_reg);
always @ (cstate,fifo_empty,rfifo_cnt,bit_cnt,start_done,parity_done,stop_done,serial_parity)
begin
nstate=IDLE;
case(cstate)
IDLE:
if(fifo_empty==1'b0)
nstate=RD_FIFO;
else
nstate=IDLE;
RD_FIFO:
if(rfifo_cnt==3'd5)
nstate=START_BIT;
else
nstate=RD_FIFO;
START_BIT:
if(start_done)
nstate=SEND_DATA;
else
nstate=START_BIT;
SEND_DATA:
if(bit_cnt==4'd8)
begin
if(serial_parity==2'b01 || serial_parity==2'b10)
nstate=PARITY_BIT;
else
nstate=STOP_BIT;
end
else
nstate=SEND_DATA;
PARITY_BIT:
if(parity_done)
nstate=STOP_BIT;
else
nstate=PARITY_BIT;
STOP_BIT:
if(stop_done)
nstate=IDLE;
else
nstate=STOP_BIT;
default:nstate=IDLE;
endcase
end
//產生下一狀態
always @ (posedge clk or negedge rst)
if(rst==0)
begin
serial_tx<=1'b1;
end
else
case(nstate)
IDLE:
begin
serial_tx<=1'b1;
end
RD_FIFO:
begin
serial_tx<=1'b1;
end
START_BIT:serial_tx<=1'b0;
SEND_DATA:serial_tx<=data[bit_cnt[2:0]];
PARITY_BIT:
begin
if(serial_parity==2'b01)
serial_tx<=!(^data);
else
serial_tx<=^data;
end
STOP_BIT:serial_tx<=1'b1;
default:serial_tx<=1'b1;
endcase
//輸出串口信號
always @ (posedge clk or negedge rst)
if(rst==0)
bit_cnt<=4'b0;
else if(nstate==IDLE)
bit_cnt<=4'b0;
else if(nstate==SEND_DATA && send_data_counter==serial_baudrate)
bit_cnt<=bit_cnt+1;
else
bit_cnt<=bit_cnt;
//產生8位數據中bit位
always @ (posedge clk or negedge rst)
if(rst==0)
fifo_rden<=1'b1;
else if(rfifo_cnt==3'd1)
fifo_rden<=1'b0;
else
fifo_rden<=1'b1;
//產生FIFO的讀信號
always @ (posedge clk or negedge rst)
if(rst==0)
data<=8'b0;
else if(rfifo_cnt==3'd4)
data<=fifo_data;
else
data<=data;
//將FIFO輸出的數據進行寄存
always @ (posedge clk or negedge rst)
if(rst==0)
stop_reg<={1'b0,serial_baudrate};
else if(serial_stop_bit==2'b10)
stop_reg<={serial_baudrate,1'b0};
else
stop_reg<={1'b0,serial_baudrate};
//生成停止位總數
endmodule
串口發送模塊仿真波形圖見下圖。
串口發送模塊仿真波形圖1(數據爲33、44、52,無奇偶偶校驗,2個停止位)
串口發送模塊仿真波形圖2(數據爲AA、55,偶校驗,2個停止位)
該模塊接口信號包括mcu_rd、mcu_wr、mcu_data、mcu_addr、mcu_cs、mcu_int等。
異步時鐘域之間傳輸數據在FPGA設計中是一個非常重要的技術,處理不好很容易出現不穩定,或者某次佈局佈線後功能正常,重新佈局佈線後,功能就不正常,並且多數情況下,錯誤的現象沒有明確的規律,問題定位非常困難。解決的辦法是由本地時鐘進行同步化處理。
mcu讀模塊中首先檢測mcu_rd的下降沿,檢測到下降沿之後,再判斷片選信號和地址信號,給不同的地址分配特定的寄存器或者FIFO空間;
mcu寫模塊中首先檢測mcu_wr的下降沿,檢測到下降沿之後,再判斷片選信號和地址信號,給不同的地址分配特定的寄存器或者FIFO空間。
具體的FPGA邏輯代碼如下:
module mcu_configure(
clk,
rst,
mcu_rd,
mcu_wr,
mcu_data,
mcu_addr,
mcu_cs,
mcu_int,
serial_baudrate,
serial_stop_bit,
serial_parity,
serial_rfifo_empty,
serial_rfifo_data,
serial_rfifo_rden,
serial_tfifo_data,
serial_tfifo_wren);
//system signal
input clk;
input rst;
//mcu interface
input mcu_rd;//MCU的讀信號
input mcu_wr; //MCU的寫信號
inout [7:0] mcu_data; //MCU的數據信號
input [12:0] mcu_addr; //MCU的地址信號
input mcu_cs; //MCU的片選信號
output reg mcu_int; //MCU的中斷信號
//serial_configure registers
output reg [7:0] serial_baudrate;
//MCU配置的串口波特率,如爲0x30,則爲230400bps;如爲0x60,則爲115200bps;
//如爲0xC0,則爲57600bps,其他爲115200bps;
output reg [1:0] serial_stop_bit;
//MCU配置的串口停止位,如爲10,則停止位爲2位;其他則爲1位;
output reg [1:0] serial_parity;
//MCU配置的串口奇偶校驗位,如爲01,則爲奇校驗;如爲10,則爲偶校驗;其他//爲無校驗
//receive fifo
input serial_rfifo_empty;//接收FIFO的空狀態標誌位;
input [7:0] serial_rfifo_data;//接收FIFO的輸出8位數據;
output reg serial_rfifo_rden;//接收FIFO的讀使能信號;
//transmit fifo
output [7:0] serial_tfifo_data;//發送FIFO的寫入數據信號;
output reg serial_tfifo_wren;//發送FIFO的寫使能信號;
//MCU bidirection control
reg [7:0] mcu_data_reg;
assign mcu_data=(mcu_rd==0 && mcu_cs==0)?mcu_data_reg:8'bZZZZZZZZ;
//MCU的雙向數據總線設置
//transmit fifo data
assign serial_tfifo_data=mcu_data;
reg mcu_rd_reg1;
reg mcu_rd_reg2;
reg mcu_rd_reg3;
reg [3:0] mcu_rd_counter;
always @ (posedge clk or negedge rst)
if(rst==0)
begin
mcu_rd_reg1<=1'b1;
mcu_rd_reg2<=1'b1;
mcu_rd_reg3<=1'b1;
end
else
begin
mcu_rd_reg1<=mcu_rd;
mcu_rd_reg2<=mcu_rd_reg1;
mcu_rd_reg3<=mcu_rd_reg2;
end
//將MCU的讀信號同步3拍,同步2拍和3拍的寄存器信號用來判斷MCU的讀信號的
//下降沿
always @ (posedge clk or negedge rst)
if(rst==0)
mcu_rd_counter<=4'b0;
else if(mcu_rd_reg3==1'b0)
if(mcu_rd_counter==4'd8)
mcu_rd_counter<=4'd8;
else
mcu_rd_counter<=mcu_rd_counter+1;
else
mcu_rd_counter<=4'b0;
//對MCU的讀信號寬度進行計數;
always @ (posedge clk or negedge rst)
if(rst==0)
begin
mcu_data_reg<=8'b0;
serial_rfifo_rden<=1'b1;
end
else if(mcu_rd_reg3==1'b0 && mcu_cs==0)
begin
case(mcu_addr)
13'h0000:
mcu_data_reg<=serial_baudrate;
13'h0001:
mcu_data_reg<={6'b0,serial_parity};
13'h0002:
mcu_data_reg<={6'b0,serial_stop_bit};
13'h0020:
mcu_data_reg<={7'b0,serial_rfifo_empty};
13'h0021:
begin
mcu_data_reg<=serial_rfifo_data;
if(mcu_rd_counter==4'd0)
serial_rfifo_rden<=1'b0;
else
serial_rfifo_rden<=1'b1;
end
default:
mcu_data_reg<=8'b0;
endcase
end
else
begin
mcu_data_reg<=8'b0;
serial_rfifo_rden<=1'b1;
end
//上面這段代碼實現了MCU的讀取的寄存器地址定義
reg mcu_wr_reg1;
reg mcu_wr_reg2;
reg mcu_wr_reg3;
always @ (posedge clk or negedge rst)
if(rst==0)
begin
mcu_wr_reg1<=1'b1;
mcu_wr_reg2<=1'b1;
mcu_wr_reg3<=1'b1;
end
else
begin
mcu_wr_reg1<=mcu_wr;
mcu_wr_reg2<=mcu_wr_reg1;
mcu_wr_reg3<=mcu_wr_reg2;
end
//將MCU的寫信號同步3拍,同步2拍和3拍的寄存器信號用來判斷MCU的寫信號的
//下降沿
always @ (posedge clk or negedge rst)
if(rst==0)
begin
serial_baudrate<=8'h60;
serial_parity<=2'b00;
serial_stop_bit<=2'b00;
serial_tfifo_wren<=1'b1;
end
else if(mcu_wr_reg2==0 && mcu_wr_reg3==1 && mcu_cs==0)
begin
case(mcu_addr)
13'h0000:
serial_baudrate<=mcu_data;
13'h0001:
serial_parity<=mcu_data[1:0];
13'h0002:
serial_stop_bit<=mcu_data[1:0];
13'h0010:
serial_tfifo_wren<=1'b0;
default:
begin
serial_baudrate<=serial_baudrate;
serial_parity<=serial_parity;
serial_stop_bit<=serial_stop_bit;
serial_tfifo_wren<=1'b1;
end
endcase
end
else
begin
serial_baudrate<=serial_baudrate;
serial_parity<=serial_parity;
serial_stop_bit<=serial_stop_bit;
serial_tfifo_wren<=1'b1;
end
//上面這段代碼實現了MCU的寫入的寄存器地址定義
always @ (posedge clk or negedge rst)
if(rst==0)
mcu_int<=1'b1;
else if(serial_rfifo_empty==1'b0)
mcu_int<=1'b0;
else
mcu_int<=1'b1;
//MCU中斷信號的產生,接收FIFO非空則產生中斷信號
endmodule
下圖爲MCU配置模塊仿真波形圖。可以看到MCU向地址0x000寫入0x60後正確讀出,即設置波特率爲115200bps。
串口發送模塊仿真波形圖3(數據爲AA、55、52,奇校驗,1個停止位)