【Verilog串口篇3】UART自定义应用层协议SLink实现多字节协议帧收发

【Verilog串口篇3】UART自定义应用层协议SLink实现多字节协议帧收发

本文从应用层出发,讲解自定义的通信协议,暂且命名为 SLink,实现串口通信 多字节协议帧收发

1、帧格式
帧是什么?不知道的我想你来错地方了。一帧数据包括多个字节,每个字节的含义由通信协议规定,即为帧格式。 帧格式描述常用的元素有:帧头、数据长度、有效载荷、校验、帧尾等等,当然,我们大可不必拘泥于这些元素,增删名用(增补、删除、命名、运用),仁者见仁,智者见智。

SLink 帧格式

HEAD 帧头,固定值 8’hfa。
MARK 协议标识,固定值 8’hfe。
通常,在消息解析时,我们读到帧头就认为消息开始了,之后就按照格式开始逐字节解析。而这里,我们再加个协议标识判断,只有两者都符合,才认为消息开始。
EXT 扩展信息,内容由用户自定义,长度在参数中设置。
TAG 标签,表示消息类型。
LEN 有效载荷的字节长度
VAL 有效载荷
理论上,我们可配置特定消息类型对应的有效载荷长度固定,这样在解析到长度时,就能知道长度是否有误,若错误,则结束该帧的解析,进入下一帧,实践中,意义不大,留给大家去实现了。

2、消息解析
先说消息解析模块的接口描述,巧妇难为无米之炊,首先得要有数据给你解析,这里采用的是 逐字节解析 方式,所以有一个字节输入端口,还有一个脉冲控制信号输入端口,用于启动解析。消息处理是用户的事情,正所谓众口难调,这里只是将解析到的消息交给用户,管他蒸煮焖炖还是煎烤炒炸,所以还需要消息输出端口。

module SLinkParse #(parameter
    EXT_NBR  = 2,  // 扩展信息长度
    VAL_SIZE = 8   // 最大有效载荷长度
)
(
    input Clock,
    input nRst,
    input Parse,  // 启动解析,由该脉冲信号控制逐字节解析
    input [7:0] Word,  // 待解析字节
    // 解析到的数据:扩展信息(EXT),标签(TAG),有效载荷长度(LEN),有效载荷(VAL)
    output reg [(EXT_NBR>0?EXT_NBR:1)*8-1:0] Ext,
    output reg [7:0] Tag,
    output reg [7:0] Len,
    output reg [(VAL_SIZE>0?VAL_SIZE:1)*8-1:0] Val,
    output reg Cplt  // 解析完成,得到完整消息
);

接下来是解析过程,又是顺序操作,老套路,三段式状态机。主要来看看是如何得到次态的,对于单个字节的元素,我们以 Parse 启动解析信号 作为状态转移的条件,而对于多字节元素,以 接收到的字节数目 count 作为转移条件。另外,在解析帧头和协议标识时,还需判断数据是否相符。至于是否需要解析扩展信息或有效载荷,我们根据二者的长度进行判断,大于零才解析。

/* 得到次态的组合逻辑,用 = 赋值 */
always @(*) begin
    case (cstate)
        S_HEAD: nstate = Parse ? (Word == HEAD ? S_MARK : S_HEAD) : S_HEAD;
        S_MARK: nstate = Parse ? (Word == MARK ? (EXT_NBR ? S_EXT : S_TAG) : S_HEAD) : S_MARK;
        S_EXT: nstate = count == EXT_NBR + 'd1 ? S_TAG : S_EXT;
        S_TAG: nstate = Parse ? S_LEN : S_TAG;
        S_LEN: nstate = Parse ? (Word ? S_VAL : S_CPLT) : S_LEN;
        S_VAL: nstate = count == Len + 'd1 ? S_CPLT : S_VAL;
        S_CPLT: nstate = S_HEAD;
        default: nstate = S_HEAD;
    endcase
end

状态输出,形象的说就是各状态所执行的动作,直接贴上代码,前面说过,我们不对消息做任何处理,直接输出给用户,从中可看出主要就是 存储数据到相应的输出端口

/* 状态输出(即相应状态需要执行的动作) */
always @(posedge Clock or negedge nRst) begin
    if (!nRst) begin
        count <= 'd0;
        Ext <= 'd0;
        Tag <= 'd0;
        Len <= 'd0;
        Val <= 'd0;
        Cplt <= 1'b0;
    end
    else begin
        case (cstate)
            S_HEAD: Cplt <= 1'b0;
            S_MARK: count <= 'd1;  // 为存储扩展信息做准备
            S_EXT: begin
                // 存储扩展信息,由低位到高位存储,先接收到的存在低位
                Ext[count*8-1-:8] <= Parse ? Word : Ext[count*8-1-:8];
                count <= Parse ? count + 'd1 : count;
            end
            S_TAG: Tag <= Parse ? Word : Tag;  // 存储标签
            S_LEN: begin
                Len <= Parse ? Word : Len;  // 存储长度
                count <= 'd1;  // 为存储有效载荷做准备
            end
            S_VAL: begin
                // 存储有效载荷,由低位到高位存储,先接收到的存在低位
                Val[count*8-1-:8] <= Parse ? Word : Val[count*8-1-:8];
                count <= Parse ? count + 'd1 : count;
            end
            S_CPLT: Cplt <= 1'b1;
            default: Cplt <= 1'b0;
        endcase
    end
end

3、打包发送
看到这里,本协议的核心已经讲完了,打包发送实际就是 串口的多字节发送,对串口多字节发送有问题的小伙伴可以看一看。接口描述涉及到底层的 UART 发送模块,因为我们最终调用它来发送数据。

module SLinkPack #(parameter
    EXT_NBR  = 2,  // 扩展信息长度
    VAL_SIZE = 8   // 最大有效载荷长度
)
(
    input Clock,
    input nRst,
    input Pack,  // 启动打包发送
    // 待发送消息:扩展信息(EXT),标签(TAG),有效载荷长度(LEN),有效载荷(VAL)
    input [(EXT_NBR>0?EXT_NBR:1)*8-1:0] Ext,
    input [7:0] Tag,
    input [7:0] Len,
    input [(VAL_SIZE>0?VAL_SIZE:1)*8-1:0] Val,
    input TransCplt,        // 由底层串口发送模块反馈的字节传输完成信号
    output reg [7:0] Word,  // 待发送字节,按照帧格式逐字节发送
    output reg Trans,       // 输出给底层串口发送模块的启动传输信号
    output reg PackCplt  // 打包发送完成
);

功能实现用的也是三段式状态机,总体思路就是:准备一串数据,发送一个字节,等待发送完成,再发送下一个字节。。。何时发送结束呢?加个计数器记录一下已发送的字节数,计数值到了,就发送完成了。得到次态的组合逻辑:

/* 得到次态的组合逻辑,用 = 赋值 */
always @(*) begin
    case (cstate)
        S_IDLE: nstate = Pack ? S_HEAD : S_IDLE;
        S_HEAD: nstate = S_MARK;
        S_MARK: nstate = TransCplt ? (EXT_NBR ? S_EXT : S_TAG) : S_MARK;
        S_EXT: nstate = count == EXT_NBR + 'd1 ? S_TAG : S_EXT;
        S_TAG: nstate = TransCplt ? S_LEN : S_TAG;
        S_LEN: nstate = TransCplt ? (Len ? S_VAL : S_CPLT) : S_LEN;
        S_VAL: nstate = count == Len + 'd1 ? S_CPLT : S_VAL;
        // 等待最后一个字节发送完成
        S_CPLT: nstate = TransCplt ? S_IDLE : S_CPLT;
        default: nstate = S_IDLE;
    endcase
end

状态输出是什么呢?当然是根据帧格式,准备数据并启动底层的串口发送模块将数据发送出去。除了第一次传输,以后每次传输,都需要等待前一次传输完成。

/* 状态输出(即相应状态需要执行的动作) */
always @(posedge Clock or negedge nRst) begin
    if (!nRst) begin
        count <= 'd0;
        Word <= 'd0;
        Trans <= 1'b0;
        PackCplt <= 1'b0;
    end
    else begin
        case (cstate)
            S_IDLE: begin Trans <= 1'b0; PackCplt <= 1'b0; end
            S_HEAD: begin
                Word <= 'hfa;   // 帧头
                Trans <= 1'b1;  // 启动传输
            end
            S_MARK: begin
                Word <= TransCplt ? 'hfe : Word;
                Trans <= TransCplt ? 1'b1 : 1'b0;
                count <= 'd1;  // 为扩展信息计数做准备
            end
            S_EXT: begin
                Word <= TransCplt ? Ext[count*8-1-:8] : Word;  // 发送扩展信息,低位先发,谨记,实践中容易搞反
                Trans <= TransCplt ? 1'b1 : 1'b0;
                count <= TransCplt ? count + 'd1 : count;
            end
            S_TAG: begin
                Word <= TransCplt ? Tag : Word;
                Trans <= TransCplt ? 1'b1 : 1'b0;
            end        
            S_LEN: begin
                Word <= TransCplt ? Len : Word;
                Trans <= TransCplt ? 1'b1 : 1'b0;
                count <= 'd1;  // 为有效载荷计数做准备
            end
            S_VAL: begin
                Word <= TransCplt ? Val[count*8-1-:8] : Word;  // 发送有效载荷,低位先发,谨记,实践中容易搞反
                Trans <= TransCplt ? 1'b1 : 1'b0;
                count <= TransCplt ? count + 'd1 : count;
            end
            S_CPLT: begin
                Trans <= 1'b0;
                PackCplt <= TransCplt ? 1'b1 : PackCplt;  // 等待最后一个字节发送完成
            end
            default: begin Trans <= 1'b0; PackCplt <= 1'b0; end
        endcase
    end
end
  • 6
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

硬件拾遗

感谢道友

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值