串口通信也是一個(gè)基礎(chǔ)實(shí)驗(yàn),是FPGA與電腦、單片機(jī)、DSP通信的一種最簡(jiǎn)單的方案,對(duì)通信速率要求不高時(shí)可以選擇UART通信。您可能已經(jīng)知道UART時(shí)序的控制、波特率的配置等方面的內(nèi)容,但在實(shí)際使用時(shí)還是會(huì)遇到一些問題,比如如何才能恰當(dāng)?shù)暮推渌K進(jìn)行銜接?為什么時(shí)序明明沒問題,卻無法和其它控制單元成功通信?本文致力于全面解析在設(shè)計(jì)UART通信時(shí)的思路方法。
UART通信協(xié)議
UART通信的一幀一般由11到12位數(shù)據(jù)組成。1bit的起始位,檢測(cè)為低電平表示數(shù)據(jù)開始傳輸;緊接著8bits的數(shù)據(jù);然后是1bit的奇偶校驗(yàn)位,可以是奇校驗(yàn)或者偶校驗(yàn);最后是1bit或2bits的停止位,必須為高電平,表示一個(gè)字符數(shù)據(jù)的傳輸結(jié)束。
其中校驗(yàn)位是可選的,用來檢驗(yàn)數(shù)據(jù)是否傳輸正確。如果有校驗(yàn)位,則需要保證收發(fā)雙方選擇同樣的一種檢驗(yàn)方式。奇校驗(yàn)就是保證數(shù)據(jù)中的1是奇數(shù),比如如果8bit數(shù)據(jù)中有3bits的1,校驗(yàn)位置0;如果有4bits的1,校驗(yàn)位置1。偶校驗(yàn)就是保證數(shù)據(jù)中的1是偶數(shù)。
?
波特率的配置
波特率表示數(shù)據(jù)傳輸?shù)乃俾?,單位bps,表示位每秒。比如9600bps就表示1s可以傳輸9600bits的數(shù)據(jù)。異步收發(fā)沒有時(shí)鐘打拍來控制數(shù)據(jù)的傳輸,就需要保證收發(fā)雙方在波特率設(shè)置上的一致。確保接收數(shù)據(jù)的完整性。
程序中通常使用16倍速率對(duì)UART通信時(shí)序進(jìn)行采樣,則UART通信所需的時(shí)鐘就是16*bps,如9600bps通信所需的驅(qū)動(dòng)時(shí)鐘大小就是16*9600=153.6kHz。程序中可以使用一個(gè)計(jì)數(shù)器對(duì)系統(tǒng)時(shí)鐘分頻產(chǎn)生UART通信時(shí)鐘。
// 分頻生成UART通信時(shí)鐘
always @(posedge clk50 or negedge rst_n)
if (!rst_n) begin
clkout 《=1‘b0;
cnt《=0;
end
else if(cnt == 16’d162) begin //近似50%占空比
clkout 《= 1‘b1;
cnt 《= cnt + 16’d1;
end
else if(cnt == 16‘d325) begin //50M/(16*9600)
clkout 《= 1’b0;
cnt 《= 16‘d0;
end
else cnt 《= cnt + 16’d1;
UART發(fā)送數(shù)據(jù)時(shí)序設(shè)計(jì)
通常我們程序中都會(huì)設(shè)計(jì)一個(gè)UART發(fā)送數(shù)據(jù)的開始信號(hào),對(duì)這個(gè)開始信號(hào)的處理方法和“FPGA基礎(chǔ)設(shè)計(jì)(二):PS2鍵盤控制及短按、長(zhǎng)按”這篇文章對(duì)按鍵有效信號(hào)處理的方法相同,采用一級(jí)寄存,然后進(jìn)行邏輯判斷,從而產(chǎn)生一個(gè)時(shí)鐘寬度的有效信號(hào)。那么當(dāng)檢測(cè)到有效信號(hào)時(shí)便可以啟動(dòng)UART發(fā)送數(shù)據(jù)的過程。
// 檢測(cè)發(fā)送命令wrsig的上升沿
always @(posedge clk)
begin
wrsigbuf 《= wrsig;
wrsigrise 《= (~wrsigbuf) & wrsig;
end
// 啟動(dòng)串口發(fā)送程序
always @(posedge clk)
if (wrsigrise && (~idle)) //當(dāng)發(fā)送命令有效且線路為空閑時(shí),啟動(dòng)新的數(shù)據(jù)發(fā)送進(jìn)程
send 《= 1‘b1;
else if(cnt == 8’d168) //一幀數(shù)據(jù)發(fā)送結(jié)束
send 《= 1‘b0;
UART是按單bit發(fā)送的,因此在控制時(shí)序時(shí)可以使用一個(gè)計(jì)數(shù)器控制,在對(duì)應(yīng)的計(jì)數(shù)位送出對(duì)應(yīng)的數(shù)據(jù)。由于我們使用的是16倍時(shí)鐘采樣,因此每個(gè)數(shù)據(jù)位之間的計(jì)數(shù)間隔便是16,一次完整的發(fā)送過程如下所示:
// 串口發(fā)送程序, 16個(gè)時(shí)鐘發(fā)送一個(gè)bit
always @(posedge clk or negedge rst_n)
begin
if (!rst_n) begin
tx 《= 1’b0;
idle 《= 1‘b0;
cnt《=8’d0;
presult《=1‘b0;
end
else if(send == 1’b1) begin
case(cnt) //產(chǎn)生起始位
8‘d0: begin
tx 《= 1’b0;
idle 《= 1‘b1;
cnt 《= cnt + 8’d1;
end
8‘d16: begin
tx 《= datain[0]; //發(fā)送數(shù)據(jù)0位
presult 《= datain[0]^paritymode;
idle 《= 1’b1;
cnt 《= cnt + 8‘d1;
end
8’d32: begin
tx 《= datain[1]; //發(fā)送數(shù)據(jù)1位
presult 《= datain[1]^presult;
idle 《= 1‘b1;
cnt 《= cnt + 8’d1;
end
8‘d48: begin
tx 《= datain[2]; //發(fā)送數(shù)據(jù)2位
presult 《= datain[2]^presult;
idle 《= 1’b1;
cnt 《= cnt + 8‘d1;
end
8’d64: begin
tx 《= datain[3]; //發(fā)送數(shù)據(jù)3位
presult 《= datain[3]^presult;
idle 《= 1‘b1;
cnt 《= cnt + 8’d1;
end
8‘d80: begin
tx 《= datain[4]; //發(fā)送數(shù)據(jù)4位
presult 《= datain[4]^presult;
idle 《= 1’b1;
cnt 《= cnt + 8‘d1;
end
8’d96: begin
tx 《= datain[5]; //發(fā)送數(shù)據(jù)5位
presult 《= datain[5]^presult;
idle 《= 1‘b1;
cnt 《= cnt + 8’d1;
end
8‘d112: begin
tx 《= datain[6]; //發(fā)送數(shù)據(jù)6位
presult 《= datain[6]^presult;
idle 《= 1’b1;
cnt 《= cnt + 8‘d1;
end
8’d128: begin
tx 《= datain[7]; //發(fā)送數(shù)據(jù)7位
presult 《= datain[7]^presult;
idle 《= 1‘b1;
cnt 《= cnt + 8’d1;
end
8‘d144: begin
tx 《= presult; //發(fā)送奇偶校驗(yàn)位
presult 《= datain[0]^paritymode;
idle 《= 1’b1;
cnt 《= cnt + 8‘d1;
end
8’d160: begin
tx 《= 1‘b1; //發(fā)送停止位
idle 《= 1’b1;
cnt 《= cnt + 8‘d1;
end
8’d168: begin
tx 《= 1‘b1;
idle 《= 1’b0; //一幀數(shù)據(jù)發(fā)送結(jié)束
cnt 《= cnt + 8‘d1;
end
default: begin
cnt 《= cnt + 8’d1;
end
endcase
end
else begin
tx 《= 1‘b1;
cnt 《= 8’d0;
idle 《= 1‘b0;
end
end
UART接收數(shù)據(jù)時(shí)序設(shè)計(jì)
UART接收數(shù)據(jù)的過程和發(fā)送數(shù)據(jù)的過程是恰好相反的。區(qū)別只在于UART發(fā)送的開始信號(hào)和發(fā)送數(shù)據(jù)端tx是從FPGA內(nèi)部的其它模塊產(chǎn)生的;而UART接收的開始信號(hào)和接收數(shù)據(jù)段rx送來的數(shù)據(jù)來自其它設(shè)備,因此需要對(duì)外部送來的信號(hào)進(jìn)行監(jiān)測(cè)。
一幀數(shù)據(jù)的開始位是低電平有效,因此當(dāng)檢測(cè)到rx線上的下降沿時(shí)就表示數(shù)據(jù)通信的開始:
always @(posedge clk) //檢測(cè)線路的下降沿
begin
rxbuf 《= rx;
rxfall 《= rxbuf & (~rx);
end
// 啟動(dòng)串口接收程序
always @(posedge clk)
if (rxfall && (~idle)) //檢測(cè)到線路的下降沿并且原先線路為空閑,啟動(dòng)接收數(shù)據(jù)進(jìn)程
receive 《= 1‘b1;
else if(cnt == 8’d168) //接收數(shù)據(jù)完成
receive 《= 1‘b0;
接收數(shù)據(jù)的過程和發(fā)送一樣,用一個(gè)計(jì)數(shù)器來控制,這里不再贅述。
發(fā)送與接收數(shù)據(jù)的打包
UART一次通信只能完成一幀,即傳輸一個(gè)8bits的數(shù)據(jù),然而我們通常需要很多幀來組成一次完整的通信。如一種簡(jiǎn)單常用的氣象采集站通信格式為:“FF(開始幀)+雨量+溫度+濕度+氣壓+風(fēng)速+風(fēng)向+CRC校驗(yàn)+FE(結(jié)束幀)”。這種情況下可以建立一組寄存器專門存儲(chǔ)發(fā)送或接收的數(shù)據(jù)。比如發(fā)送時(shí)可以做如下處理:
/********************************************/
//存儲(chǔ)待發(fā)送的串口信息
/********************************************/
reg [7:0] uart_ad [7:0]; //存儲(chǔ)發(fā)送字符
always @(clk)
begin //定義發(fā)送的字符
if(uart_stat==3‘b000) begin
uart_ad[0]《=8’hFF;
uart_ad[1]《=rain;
uart_ad[2]《=temp;
uart_ad[3]《=humi;
uart_ad[4]《=winddir;
uart_ad[5]《=windspeed;
uart_ad[6]《=CRC16;
uart_ad[7]《=8‘hFE;
end
end
接收的原理類似,只不過接收是在通信過程中接收。這樣我們使用一個(gè)計(jì)數(shù)器控制,并檢測(cè)串口通信的狀態(tài),依次將這組寄存器中的值按發(fā)送順序傳入U(xiǎn)ART發(fā)送數(shù)據(jù)模塊即可。
另外需要做好的一件事就是串口模塊與其它模塊的銜接。我們?cè)诮⒑蒙鲜龅募拇嫫鹘M之后,只要把數(shù)據(jù)來源填充到寄存器組中對(duì)應(yīng)的位置即可。但如果數(shù)據(jù)是從RAM、FIFO等模塊中來的,在串口通信時(shí)就應(yīng)該對(duì)使能信號(hào)、FIFO空滿信號(hào)等做出合理的判斷和控制,就像“FPGA數(shù)據(jù)采集-傳輸-顯示系統(tǒng)(一):1.2/50μs沖擊電壓測(cè)量與顯示”和“FPGA數(shù)據(jù)采集-傳輸-顯示系統(tǒng)(二):基于FPGA的溫度采集和以太網(wǎng)傳輸”兩篇文章中做的一樣。
評(píng)論