串口通信协议的简单介绍
1.协议介绍
UART通信只有两根信号线,一-根是发送数据端口线叫tx ,一 根是接收数据端口线叫rx ,对于上位机来说它的tx要和对于FPGA来说的rx连接,同样上位机的rx要和FPGA的tx连接,如果是两个tx或者两个rx连接那数据就不能正常被发送出去和接收到。UART可以实现全双工,即可以同时进行发送数据和接收数据。
2.协议的数据格式
串口的一帧数据包括:起始位,数据位,奇偶校验位和停止位。如上图所示,起始位表示数据开始传输,数据位表示传输的数据,校验位分为奇校验和偶校验,用于检测数据在传输过程中是否出错。停止位,表示数据传输完成。在设置好上面的一系列参数之后,设备就可以通过串口协议通信了,而协议的主要作用就是完成数据的并串转换。
3.串口通信链接示意图
注意:这里的信号链接细节,上位机的tx应该链接到fpga的rx,上位机的rx应该链接到fpga的tx,如果链接反了,就会出现数据通信不成功的情况。
4.状态的实现
状态机的介绍在fpga专栏第一个博客已经介绍完毕,如果还有不懂的,请参考野火或者正点原子的状态机的教程,哪里会十分详细的介绍,我写这些博客的目的更在意的是和大家分享怎么用,以及交流。
由状态机的数据格式我们可以知道,数据的传输是有明显的先后顺序的,所以我们就应该仔细想一下,用状态机是否可以将其实现出来。答案是显然的。
4.1接受状态机的实现
状态转移图:由状态转移图可知:最开始是”IDLE“状态,当数据线出现下降沿时,跳转到"START"状态,"START"状态的持续时间应该是协议中的一bit的数据持续时间,这里的时间计算单位应该是用输入的时间频率除以你的波特率,这里我输入的时钟频率为50MHZ,波特率为115200,所以一bit的数据应该计数最大值为,50_000_000/115200=434。这里的知识建议大家直接百度或者学一下通信的知识,不做过多的介绍。当计数器记到一个周期的计数值时,跳到"RX_DATA"状态,数据位是八位的,所以应该经过八个计数周期,计数完成之后跳转到"STOP"状态,单独靠文字来说,可能有点迷糊,下面将状态跳转的波形图也贴上,方便大家理解。
这些变量的赋值我相信大家应该都可以轻易做到,这里不做过多的介绍,下面上状态的代码:
/*************************************************
说明:时序逻辑表示状态寄存
************************************************/
always @(posedge sys_clk or negedge sys_rst) begin
if(!sys_rst)
current_state <= IDLE;
else
current_state <= next_state;
end
/********************************************
说明:组合逻辑表示状态转移
*********************************************/
always @(*) begin
next_state = IDLE;
case(current_state)
IDLE:
if(work_en == 1'b1)
next_state = START;
else
next_state = IDLE;
START:
if(boud_cnt == BAUD_CNT_MAX - 1'b1)
next_state = RD_DATA;
else
next_state = START;
RD_DATA:
if((bit_cnt == 4'd7)&&(boud_cnt == BAUD_CNT_MAX - 1'b1))
next_state = STOP;
else
next_state = RD_DATA;
STOP:
if(boud_cnt == BAUD_CNT_MAX/2- 1'b1)
next_state = IDLE;
else
next_state = STOP;
default:next_state = IDLE;
endcase
end
由于接受模块没有输出,所以输出这里可以不写。同时接受模块中,由于是输入输出的时钟不同,所以应该对输入信号进行跨时钟域的处理,以消除亚稳态,这一部分详细见野火的教程。跨时钟域代码:
/***********************************************
对异步信号的同步处理
**********************************************/
always @(posedge sys_clk or negedge sys_rst) begin
if(!sys_rst)
rx_reg1 <= 1'b1;
else
rx_reg1 <= Rx_data;
end
always @(posedge sys_clk or negedge sys_rst) begin
if(!sys_rst)
begin
rx_reg2 <= 1'b1;
rx_reg3 <= 1'b1;
end
else
begin
rx_reg2 <= rx_reg1;
rx_reg3 <= rx_reg2;
end
end
打拍后的结果:
同时状态机最重要的是,要知道每一个状态下的输入和输出。那么由"IDLE"状态跳转到"START"状态就应该有一个下降沿的出现,这里还有检测数据跳转的下降沿。
assign start_flag = ~rx_reg1 && (rx_reg2)&&(~work_en) ; //下降沿检测 注意检测的是reg2的下降沿
最后在数据的采集也有相应的细节需要注意:一般来说要在时钟信号的中间进行数据的采样,这里的话不多说直接上代码:
//输出数据拼接
always @(posedge sys_clk or negedge sys_rst) begin
if(!sys_rst)
rx_data_r <= 8'b0;
else if((current_state == RD_DATA )&&(boud_cnt == BAUD_CNT_MAX/2 - 1'd1))
rx_data_r[bit_cnt] <= Rx_data;
else
rx_data_r <= rx_data_r;
end
代码的思路是在计数器记到一半的时候进行的数据的采样。
在完成所有的准备之后,也就是状态在"STOP"结束时将数据输出:
//接受数据完成信号和数据输出标志位
always @(posedge sys_clk or negedge sys_rst) begin
if(!sys_rst)
begin
po_flag <= 1'b0;
po_data <= 8'b0;
end
else if((boud_cnt == BAUD_CNT_MAX/2 - 1'd1)&&(current_state == STOP )) begin
po_flag <= 1'b1;
po_data <= rx_data_r;
end
else
begin
po_flag <= 1'b0;
po_data <= 8'b0;
end
end
完整的波形图:
4.2发送状态机的实现
发送的状态机原理和接受的状态机大同小异:状态的转移也是比较类似的,其状态的转移波形图也是类似的。
变量的赋值以及代码的编写不做介绍,看图说话。
这里直接上代码:
/********************
说明:时序逻辑表示状态寄存
******************/
always @(posedge sys_clk or negedge sys_rst) begin
if(!sys_rst)
current_state <= IDLE;
else
current_state <= next_state;
end
/********************************************
说明:组合逻辑表示状态转移
*********************************************/
always @(*) begin
case(current_state)
IDLE:
if(work_en == 1'b1)
next_state = START;
else
next_state = IDLE;
START:
if(Baud_cnt == BAUD_CNT_MAX - 1'b1)
next_state = TX_DATA;
else
next_state = START;
TX_DATA:
if((Bit_cnt == 4'd7)&&(Baud_cnt == BAUD_CNT_MAX - 1'b1))
next_state = STOP;
else
next_state = TX_DATA;
STOP:
if((Baud_cnt == BAUD_CNT_MAX/2- 1'd1))
next_state = IDLE;
else
next_state = STOP;
default:;
endcase
end
/********************************************
说明:数据寄存
*********************************************/
always@(posedge sys_clk or negedge sys_rst)
begin
if(!sys_rst)
begin
tx_data_latch <= 8'd0;
end
else if(current_state == TX_DATA)
tx_data_latch <= Pi_data;
end
/********************************************
说明:时序逻辑表示状态输出
*********************************************/
always @(posedge sys_clk or negedge sys_rst) begin
if(!sys_rst)
tx_data_r <= 1'b1;
else
case(current_state)
IDLE:
tx_data_r <= 1'b1;
START:
tx_data_r <= 1'b0;
TX_DATA:
tx_data_r <= tx_data_latch[Bit_cnt];
STOP:
tx_data_r <= 1'b1;
default: tx_data_r <= 1'b1;
endcase
end
完整的波形图:文章来源:https://www.toymoban.com/news/detail-775979.html
状态输出的一些说明:
不应该在时钟的上升沿经行输出,这里延迟了一拍进行输出。
以上是用状态机来实现串口通信,亲测有效,由于本人太懒,测试的时候忘记截图,下次一定截图。
详细的工程如果大伙需要,可以私信我,我发给你们,拜拜。下一节更新IIC通信的状态机。文章来源地址https://www.toymoban.com/news/detail-775979.html
到了这里,关于FPGA以状态机实现串口通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!