【Day17】Uart_TX 状态机的实现

Uart 是什麽?

UART(Universal Asynchronous Receiver/Transmitter),是一种非同步的传输协定,非同步传输的意思是,不管是接收端还是传送端都有自己传输资料的速度(鲍率(Baud Rate)),传输时两边必须以同样的鲍率来收发资料才不会出错。

UART 它的好处是线路简单,仅两条线路(RX&TX),但缺点是只能一对一连接,以及速度不是很快,一般而言最高为 115.2kbps(常规鲍率为 9600)


Uart 的 Timing Diagram

图片出处

从图片中可以看到,Uart 的闲置状态(IDLE)是高电平的,而起始位元则是 1 bit 的低电平,再来是 8 bit 资料的传输,而且是由 LSB 开始传输的,最後则是 1 bit 的高电平当作是结束位元。

例如现在要传输一个 0x55 (0101_0101) 的资料:

图片出处


设计Uart状态机

我们刚刚看完了 timing diagram 後发现,Uart 大致可以切成四个状态,分别为 IDLE(闲置状态)、START(起始位元)、SHIFT(8bit的输出移位)以及STOP(停止位元)。

定义四个状态:

/*---------parameter---------*/
localparam IDLE  = 2'd0;
localparam START = 2'd1;
localparam SHIFT = 2'd2;
localparam STOP  = 2'd3;

再来定义输入输出,记得!这边还不是最外层的 Uart 模块,别搞混了(想整个写在一起也很好!)。

输入:

  • clk_50M
  • rst_n
  • en(状态机 enable)
  • tick_uart(9600 鲍率的 tick (因为是同步式设计,所以不能用自己除出来的频率来当作 always 触发),由外模块发送)
  • count(计数移位 8 次的计数器,由外模组来数所以这里是输入)

输出:

  • LDEN(LoadEnable,告诉外部模块要 Load 资料)
  • SHEN(ShiftEnable,告诉外部模块要移位)
  • rstcount(reset count,告诉外部模块将计数器归零)
  • countEN(CountEnable,告诉外部模块将计数器往上计数)
  • busy(告诉外部模块现在处於非 IDLE 状态)
module Uart_TX(
  tick_uart, 
  clk_50M, 
  rst_n, 
  count, 
  rstcount, 
  countEN, 
  TX_D, 
  LDEN, 
  SHEN, 
  en, 
  busy
);
/*---------ports declaration---------*/
input       clk_50M;
input       rst_n;
input       en;
input       tick_uart;
input [2:0] count;
output      TX_D;
output      LDEN;
output      SHEN;
output      rstcount;
output      countEN;
output      busy;
reg         TX_D;
reg         LDEN;
reg         SHEN;
reg         rstcount;
reg         countEN;
wire        busy;
/*---------assign wire---------*/
assign busy = (fstate!=IDLE);

宣告跑状态的变数:

/*---------variables---------*/	
reg [1:0] fstate;

状态逻辑:

  • 重置时要能回到 IDLE 状态。
  • IDLE(收到 enable 讯号後开始到下一个状态)
  • START(只有一个鲍率的周期(1 bit 的低电平),所以等待 9600 的 tick 来後就往 SHIFT 走)
  • SHIFT(count 数满 8 次後并且最後一次也要待满一个鲍率的时间,状态才往 STOP 走)
  • STOP(跟 START 相同,要待满一个鲍率周期,再回到 IDLE)
/*---------fstate state---------*/
always@(posedge clk_50M or negedge rst_n)begin
  if(!rst_n)fstate <= IDLE;
  else begin
    case(fstate)
      IDLE:begin
        if(en)fstate <= START;
        else  fstate <= IDLE;
      end
      START:begin
        if(tick_uart==1'b1)fstate <= SHIFT;
        else               fstate <= START;
      end
      SHIFT:begin
        if(tick_uart==1'b1&&count==3'd6)fstate <= STOP;
        else                            fstate <= SHIFT;
      end
      STOP:begin
        if(tick_uart==1'b1)fstate <= IDLE;
        else               fstate <= STOP;
      end
      default:fstate <= IDLE;
    endcase
  end
end

输出逻辑:

  • 重置时
    • TX_D = 1(闲置状态为 1,故重置也是 1)
    • 其余都是 0
  • IDLE
    • TX_D = 1(闲置状态为 1,故重置也是 1)
    • 其余都是 0
  • START
    • 此时要 Load 要传的 8 bit 资料,所以LDEN = 1
    • 其余都是 0
  • SHIFT
    • 这个状态要开始数 bit 数,也要在最後一个 bit 将计数暂存器归零。
    • countEN = 1
    • 在最後一 bit 时 countEN = 1
    • 移位讯号 SHEN = 1
    • 此时 TX_D 不重要,此模块没有 8 bit 的传输资料,移位时会 assign 到外部模块上
  • STOP
    • 结束位元是 "1",TX_D = 1
    • 其余都是 0
/*---------fstate output---------*/
always@(posedge clk_50M or negedge rst_n)begin
  if(!rst_n)begin
    TX_D     <= 1'b1;
    SHEN     <= 1'b0;
    LDEN     <= 1'b0;
    rstcount <= 1'b0;
    countEN  <= 1'b0;
  end 
  else begin
    if(tick_uart==1'b1)begin
      case(fstate)
        IDLE:begin
          TX_D     <= 1'b1;
          SHEN     <= 1'b0;
          LDEN     <= 1'b0;
          rstcount <= 1'b0;
          countEN  <= 1'b0;
        end
        START:begin
          TX_D     <= 1'b0;
          SHEN     <= 1'b0;
          LDEN     <= 1'b1;
          rstcount <= 1'b0;
          countEN  <= 1'b0;
        end
        SHIFT:begin
          TX_D     <= 1'b0;
          SHEN     <= 1'b1;
          LDEN     <= 1'b0;
          if(count==3'd6)    rstcount <= 1'b1;
          else if(count<3'd6)rstcount <= 1'b0;
          else               rstcount <= 1'b0;//prevent latch
          countEN  <= 1'b1;
        end
        STOP:begin
          TX_D     <= 1'b1;
          SHEN     <= 1'b0;
          LDEN     <= 1'b0;
          rstcount <= 1'b0;
          countEN  <= 1'b0;
        end
        default:begin
          TX_D     <= 1'b1;
          SHEN     <= 1'b0;
          LDEN     <= 1'b0;
          rstcount <= 1'b0;
          countEN  <= 1'b0;
        end
      endcase
    end
    else begin
      TX_D     <= TX_D;
      SHEN     <= SHEN;
      LDEN     <= LDEN;
      rstcount <= rstcount;
      countEN  <= countEN;
    end
  end
end
endmodule

这边要注意的是,虽然说要数到 7 才跳下一个状态,以及要数到 7 时 rstcount 才等於 1,但是!,状态机模组发送讯号到外模组,外模组要在下一个 clk 才会收到,因此如果打 7 的话,会让 SHEN 延长至 9 个鲍率周期,导致输出移位的次数错误

以上就是我整个 Uart_TX 状态机设计的方法~~


<<:  Day23 什麽是 HTTP 状态码(HTTP Status Code)?

>>:  Day 19 [Python ML、资料视觉化] Seaborn介绍

[Python 爬虫这样学,一定是大拇指拉!] DAY26 - 实战演练:多执行绪 - 抓取多个个股日成交资讯

多执行绪(multithreading) 所以我们的多执行绪在程序是怎麽运作呢? 一般情况: 假设 ...

Day 16 Azure cognitive service: Face recognition- 让你的机器人认得你

Azure face service: Face recognition- 让你的机器人认得你 人脸...

学习使用 Node 和 React 进行全栈开发

网站涵盖了商业网点提供的一系列有益健康的设施和功能。无论是教育、新闻、博客还是电子商务商品,在线门...

Day04 - Amazon ECS Anywhere 基础说明与建置(下)

先前将主机已经注册上去了 那接下来就是进到『Task Definitions』开始来建立服务 点选『...

DAY12 Kotlin基础 函式

欸!?这个不是在 hello world 的时候讲过了ㄇ?! 对。 其实函式还是有其他东西可以讲解的...