以前下了创造力电子网的uart 代码,其设计方式采用了状态机编码,第一次见,很容易理解,也减少了很多的if else。但将它综合产生出代码后一运行,就发现,回传的数据有丢失现象,具体如下。
电脑串口与开发板相连,使用sscom32(丁丁)作为调试工具,想法是让开发板传回发给他的数据。但结果却是每回传8位后,会丢8位数据,然后再传下一个8位,再丢下一个8位。真是有得必有失-_-||||
想了想,应该是中间少一个缓冲,以至于在发送端的速度慢于接受端的时刻里,数据只有丢掉的份。肯定了,人家的判断是依据中断,我用的串口调试工具只是简单的发送,所以只好自己在txd端加一个缓冲了,正规来讲应该加fifo,但还是简单些吧,加一个8位寄存器当作缓冲,再加一个标志位用来通知发送的那个寄存器。经过一番测试,可以发送字符串后得到完整的数据。代码如下:
三个文件
/*********************************************************************************************************
** All right reserve 2008-2009(C)
** Created &maintain by http://www.edaok.net
**=========================================================================
** 模 块 名: division
** 描 述: 实现时钟的分频, 分频系数由用户输入
** 原 作 者: Adeko (http://www.edaok.net)
** 参 与 者: (...welcome you join in)
**
**=========================================================================
********************************************************************************************************/
`define CNT_WIDTH 12
module divider (
clk,
rst_n,
tick_out
);
input clk; // 全局时钟线, (系统时钟)
input rst_n; // 全局复位线
//input enable; // 模块使能线
//input [`CNT_WIDTH-1 : 0] factor; // 分频系数
output tick_out; // 分频输出
reg [`CNT_WIDTH-1 : 0] rCnt;
reg rPlsTick;
/********************************************************************************************************
** 软件仿真时初始化寄存器值
********************************************************************************************************/
initial
begin
rCnt <= `CNT_WIDTH'h0;
rPlsTick <= 1'b0;
end
/********************************************************************************************************
** 计数分频计数器, 输出分频结果脉冲
********************************************************************************************************/
always @(posedge clk or negedge rst_n)
begin : DIV_CNT
if (~rst_n) begin
rCnt <= `CNT_WIDTH'h0;
rPlsTick <= 1'b0;
end
else // if (enable)
begin
rCnt <= rCnt - `CNT_WIDTH'h1;
if (rCnt == `CNT_WIDTH'h0) begin
rPlsTick <= 1'b1;
rCnt <= 12'd27; //factor must be 50MHZ/16/boundrate note:devide two things
end
else begin
rPlsTick <= 1'b0;
end
end
// else begin
// rPlsTick <= 1'b0;
// rCnt <= factor;
// end
end
/*
* 输出信号
*/
assign tick_out = rPlsTick;
endmodule
/*********************************************************************************************************
** End Of File
********************************************************************************************************/
/*********************************************************************************************************
** All right reserve 2008-2009(C)
** Created & maintain by http://www.edaok.net
**=======================================================================================================
** 模 块 名: uart.v
** 描 述: 实现UART的发送功能, 数据帧字节长度可设置, 支持奇偶检验功能; 实现了AVR单片机的通用异步串口
** 的大部分功能, (没有多机通信, 同步发送方式) 8位数据,1个停止位,没有校验位。
**
** 原 作 者: Adeko (http://www.edaok.net)
** 参 与 者: (...welcome you join in)
**
**=======================================================================================================
********************************************************************************************************/
module txd_me ( data_i, clk, rst_n, clk_en,enable, txd_xo,busy_o);
input [7:0] data_i; // 将要发送到UART总线上的数据
input clk; // 全局时钟时
input rst_n; // 全局复位时, 低有效
input clk_en;
input enable; // 模块使能线
output txd_xo; // UART数据发送端口
output busy_o; // 发送器正在工作, 非空闲 1:free 0 :busy
/*********************************************************************************************************
** UART发送器状态机状态定义
********************************************************************************************************/
`define TX_STAT_WIDTH 8
parameter [`TX_STAT_WIDTH - 1 : 0]
TX_IDLE = `TX_STAT_WIDTH'b0000_0001, // 总线空闲状态, 等待发送数据
TX_READY = `TX_STAT_WIDTH'b0000_0010, // 准备发送状态, 把数据放入发送缓冲区
TX_START = `TX_STAT_WIDTH'b0000_0100, // 发送起始化状态, 把总线拉低(清0)
TX_DATA = `TX_STAT_WIDTH'b0000_1000, // 发送数据位状态, 往总线逐位输出数据, LSB
// TX_PARITY = `TX_STAT_WIDTH'b0001_0000, // 发送校验位状态, 根据帧格式配置输出
TX_STOP1 = `TX_STAT_WIDTH'b0010_0000, // 发送停止位状态, 往总线写"1"
TX_STOP2 = `TX_STAT_WIDTH'b0100_0000,
TX_DONE = `TX_STAT_WIDTH'b1000_0000; // 发送完志状态
reg [`TX_STAT_WIDTH - 1 : 0] rStatTxCur, // UART发送器当前的状态, 默认为空闲状态
rStatTxNext; // UART发送器下一个状态
reg [7:0] rTxDatSft; //send memory 1 ,the last
reg [7:0] rTxDatTmp; //send memory 2, the first
reg [3:0] rTxClkCnt;
reg [3:0] rTxBitCnt;
reg rPlsBaudTick;
reg rPlsStatChanged;
initial // 为仿真测试初始化
begin
rStatTxCur <= TX_IDLE;
rStatTxNext <= TX_IDLE;
rTxDatSft <= 8'h00;
rTxDatTmp <= 8'h00;
rTxClkCnt <= 4'h0;
rTxBitCnt <= 4'h0;
rPlsBaudTick <= 1'b0;
rPlsStatChanged <= 1'b0;
end
reg rFlgDone; // 发送器完成发送标记
reg rFlgNewDat; // 发送器的发送缓冲器数据已载入, 可以申请新的数据
reg rFlgTmphave; //0 表示缓冲器无数据,1表示有
initial
begin
rFlgDone <= 1'b0;
rFlgNewDat <= 1'b0;
end
/*********************************************************************************************************
** 更新当前的状态机, 并处理相关的关键数据
********************************************************************************************************/
always @(posedge clk or negedge rst_n)
begin : TX_STAT_UPDATE
if (~rst_n) begin
rStatTxCur <= TX_IDLE;
rPlsStatChanged <= 1'b0;
end
else begin
rStatTxCur <= rStatTxNext; // 更新当前发送器的状态机状态
rPlsStatChanged <= (rStatTxCur != rStatTxNext);
end
end
/*********************************************************************************************************
** 根据多个信号和当前状态机的状态, 判断发送器状态机的下一个状态
**
** [特别注意]:
** 状态值使用OneHot编码, 而这种编码一般会优为移位寄存器, 为了防止状态出现00000的情况(如时钟质量差),
** 在综合器中要设置'safe stat mechine = on'(安全状态机)
********************************************************************************************************/
always @(
rPlsBaudTick or
rStatTxCur or
rTxClkCnt or
rTxBitCnt or
rFlgTmphave or
clk_en
)
begin : TX_NEXT_STAT_JUDGE
case (rStatTxCur) // 根据当前状态机的状态, 判断输入信号, 得
// 到发送器状态机的下一个状态
TX_IDLE: begin
if ( rFlgTmphave) // 当模块端口的发送数据标志有效, 则启动发送事件
rStatTxNext <= TX_READY;
else
rStatTxNext <= TX_IDLE;
end
TX_READY: begin
if (clk_en) begin // 同步UART时钟
rStatTxNext <= TX_START; // 进入发送器状态机的发送起始位状态
end
else begin
rStatTxNext <= TX_READY;
end
end
TX_START: begin
if (rPlsBaudTick) // 持续一个波特位, 进入帧的数据位状态
rStatTxNext <= TX_DATA;
else
rStatTxNext <= TX_START;
end
TX_DATA:
begin
if ( (rTxBitCnt == 4'd9) && (rPlsBaudTick) ) begin // 当逐位发送完帧格式设置的位数后, 进入下一状态
rStatTxNext <= TX_STOP1; // 进入停止位状态
end
else begin
rStatTxNext <= TX_DATA;
end
end
TX_STOP1: begin
if (rPlsBaudTick) begin
rStatTxNext <= TX_DONE;
end
else begin
rStatTxNext <= TX_STOP1;
end
end
TX_DONE: begin
rStatTxNext <= TX_IDLE; // 再次进入空闲状态
end
default: begin
rStatTxNext <= TX_IDLE;
end
endcase
end
reg rTxdTmp;
reg rFlgBusy;
always @(posedge clk or negedge rst_n)
begin : TX_STAT_PROCESS
if (~rst_n) begin
rTxDatSft <= 8'h00;
rTxDatTmp <= 8'h00;
rTxdTmp <= 1'b1;
rFlgDone <= 1'b0;
rFlgNewDat <= 1'b0;
rFlgTmphave <= 1'h0;
// rParVal <= 1'b0;
rFlgBusy <= 1'b1;
rTxClkCnt <= 4'h0;
rTxBitCnt <= 4'h0;
rPlsBaudTick <= 1'b0;
end
else begin
if(enable) begin
rTxDatTmp <= data_i;
rFlgTmphave <= 1'd1;
end
rPlsBaudTick <= 1'b0; //he decide the status change. first,it is 0;
if ( (rStatTxCur == TX_IDLE) ||
(rStatTxCur == TX_READY) ) begin // 下一状态为空闲时, 复位计数器
rTxClkCnt <= 4'h0;
rTxBitCnt <= 4'h0;
end
else if (clk_en) begin
rTxClkCnt <= rTxClkCnt + 4'h1;
if (rTxClkCnt == 4'hF) begin // UART波特率为clk_en / 16
rTxBitCnt <= rTxBitCnt + 4'h1; // 发送器帧位的计数
rPlsBaudTick <= 1'b1; // 产生波特率时钟脉冲 after 16 clk_en ,it is 1;
end
end
rFlgBusy <= 1'b1;
rFlgNewDat <= 1'b0;
rFlgDone <= 1'b0; // 则复位UART发送器发送完成标志
case (rStatTxCur)
TX_IDLE: begin
rTxdTmp <= 1'b1; //缓冲区,,维持一个高电?
rFlgBusy <= 1'b0;
end
TX_READY: begin
rTxdTmp <= 1'b1; //维持一个高电平
if (rPlsStatChanged) begin //statchange means from idle to ready,为什么要加这个呢,有可能是双重保险
rTxDatSft <= rTxDatTmp;
rFlgNewDat <= 1'b1;
rFlgTmphave <= 1'd0;
end
end
TX_START: begin
rTxdTmp <= 1'b0; //起始位,低电平
end
TX_DATA: begin
if ( (rPlsBaudTick || rPlsStatChanged) && (rTxBitCnt != 4'd9) ) begin //**********************为什么这里是9,还待进一步考证
rTxdTmp <= rTxDatSft[0];
rTxDatSft <= rTxDatSft >> 1;
end
end
TX_DONE: begin
rTxdTmp <= 1'b1; //结束位,高电平
rFlgDone <= 1'b1; // 标志UART发送器发送数据完成
rFlgBusy <= 1'b1;
end
default: begin
rTxdTmp <= 1'b1;
end
endcase
end
end
assign txd_xo = rTxdTmp; // 输出UART的发送数据 (TxD)
assign busy_o = rFlgBusy;
endmodule
/**=======================================================================================================
** 模 块 名: rxd_me
** 描 述: 实现UART的接收功能, 数据位8位, 无奇偶检验, 无帧错误检查功能
**
**
**=======================================================================================================*/
`define STAT_WIDTH 7
module rxd_me(clk,rst_n, data_o, rxd_xi,stat_o, clk_en);
input clk; // 全局时钟线
input rst_n; // 全局复位线, 低电平有效
input clk_en;
output [7:0] data_o; // 数据的输出口(8bits)
input rxd_xi; // 串口接收引脚
output stat_o; // 状态信号输出 decide txd can work or not
//input enable; // 模块使能线 0:useable 1:disable
wire wRxdVal;
reg [7:0] rRxDat; // 串口接收到的数据
reg rPlsBaudTick; // UART波特率标记, 持续一个全局时钟
reg rFlgDone; // 完成一帧数据的接收
reg [3:0] rRxClkCnt; // 波特数的计数器, 输入的时钟是波特率的16倍
reg [3:0] rRxBitCnt; // 接收rxd数据位的计数值
reg [7:0] rRxDatBuf; // 接收数据的缓冲寄存器, 数据接收过程中的临时值
/********************************************************************************************************
** 检测总线上的电平
** 原理: 在中间取三个点的电平, 取它的多数值(两个或以上个点为高电平, 则认为取到的是高电平, 否则认为
** 是低电平)
********************************************************************************************************/
reg rRxdVal[2 : 0];
always @(posedge clk or negedge rst_n)
begin : RXD_VAL_READ
integer i;
if (~rst_n) begin
for (i = 0; i < 3; i = i+1)
rRxdVal[i] <= 0;
end
else begin
case (rRxClkCnt) // 根据当前的UART时钟, 读取RXD线上的电平
4'h7:
rRxdVal[0] <= rxd_xi;
4'h8:
rRxdVal[1] <= rxd_xi;
4'h9:
rRxdVal[2] <= rxd_xi;
endcase
end
end
assign wRxdVal = (rRxdVal[0] & rRxdVal[1]) | (rRxdVal[1] & rRxdVal[2]) | (rRxdVal[2] & rRxdVal[0]);
/********************************************************************************************************
** 定义UART的接收器的状态机编码, 使用OneHot coding
** 技巧:状态机的状态值可以不用自己编码, 综合可以根据你选择的coding style, 重新对状态机进行编码,
** 这里为了方便阅读, 手动进行了编码
** _______n________n________n________n________n________n________n________n________n
** _______ 0 _________________ ________ ___________
** IDLE /________/ /___~~___<________>--------<________/
** 空闲 | 开始位 | <---- 5~8位数据 -------> | parity | stop |
********************************************************************************************************/
parameter
RX_IDLE = `STAT_WIDTH'b000_0001, // 空闲状态, 等侍开始位(start bit)
RX_SYNC = `STAT_WIDTH'b000_0010, // 同步状态, 检查开始位是否有效等...
RX_DATA = `STAT_WIDTH'b000_0100, // 接收数据状态
// RX_PARITY = `STAT_WIDTH'b000_1000, // 校验位检查, 检查校验错误等...
RX_STOP = `STAT_WIDTH'b001_0000, // 停止位, 检查帧错误等...
RX_ENDING = `STAT_WIDTH'b010_0000, // 帧接收结束, 检查缓冲超出等...
RX_DONE = `STAT_WIDTH'b100_0000; // 完成一帧接收, 保存数据等...
reg [`STAT_WIDTH - 1 : 0] rStatRxNext; // 状态机的下一个状态
reg [`STAT_WIDTH - 1 : 0] rStatRxCur; // 状态机的当前状
/********************************************************************************************************
** 更新状态机状态, 同时更新状态的相关条件寄存器
**
** [注意点]:
** 这里状态机的表达方式使用为大家熟悉的三段式状态, 同步设计, 状态的切换和状态的动作执行滞后2个时钟
********************************************************************************************************/
always @(posedge clk or negedge rst_n)
begin : RX_STATE_ASSIGN
if (~rst_n) begin
rStatRxCur <= RX_IDLE; // 设置寄存器的初始值(复位值)
end
else begin
rStatRxCur <= rStatRxNext; // 更新状态值
end
end
/********************************************************************************************************
** 根据条件,计算下一个状态机的状态值, (综合成组合逻辑)
**
** [特别注意]:
** 状态值使用OneHot编码, 这种编码一般会优为移位寄存器, 为了防止状态出现00000的情况(如时钟质量差),
** 在综合器中要设置'safe stat mechine = on'(安全状态机)
**
********************************************************************************************************/
always @(
rStatRxCur or
rxd_xi or
rRxClkCnt or
rRxBitCnt or
rPlsBaudTick or
wRxdVal
)
begin : RX_STAT_CALCULATE
case (rStatRxCur)
RX_IDLE: begin
if (~rxd_xi) // 当检测到有低电平, 启动状态机, 开始同步
rStatRxNext <= RX_SYNC;
else
rStatRxNext <= RX_IDLE;
end
RX_SYNC: begin
if (rPlsBaudTick) begin
if (wRxdVal) // 检查'开始位'是否有效(低电平有效)
rStatRxNext <= RX_IDLE;
else
rStatRxNext <= RX_DATA;
end
else begin
rStatRxNext <= RX_SYNC;
end
end
RX_DATA: begin
if (rPlsBaudTick) begin // 当接收的位数满时, 进入接收停止位
if (rRxBitCnt[3:0] == 4'd9) begin // 8位数据接收完
// if (wParEn) // 使能奇偶校验,这里将其屏蔽,直接是停止位
// rStatRxNext <= RX_PARITY;
// else
rStatRxNext <= RX_STOP;
end
else begin
rStatRxNext <= RX_DATA;
end
end
else begin
rStatRxNext <= RX_DATA;
end
end
RX_STOP: begin
if (rRxClkCnt >= 4'd10)
rStatRxNext <= RX_ENDING;
else
rStatRxNext <= RX_STOP;
end
RX_ENDING: begin
rStatRxNext <= RX_DONE;
end
RX_DONE: begin
rStatRxNext <= RX_IDLE;
end
default: begin
rStatRxNext <= RX_IDLE;
end
endcase
end
/********************************************************************************************************
** 根据不同的状态, 处理相应的数据
********************************************************************************************************/
always @(posedge clk or negedge rst_n)
begin : RX_STATE_ACTION
if (~rst_n) begin
rRxDatBuf <= 8'h00;
rRxDat <= 8'h00;
rFlgDone <= 1'b0;
rRxClkCnt <= 4'h0;
rRxBitCnt <= 4'h0;
rPlsBaudTick <= 1'b0;
end
else begin
rFlgDone <= 1'b0;
rPlsBaudTick <= 1'b0; //UART波特率标记, 持续一个全局时钟
if (rStatRxCur == RX_IDLE) begin
rRxClkCnt <= 4'h00;
rRxBitCnt <= 4'h00;
end
else if (clk_en) begin
rRxClkCnt <= rRxClkCnt + 4'h01;
if (rRxClkCnt == 4'h0F) begin
rPlsBaudTick <= 1'b1;
rRxBitCnt <= rRxBitCnt + 4'h1;
end
end
case (rStatRxCur)
RX_IDLE:begin
end
RX_SYNC:begin
end
RX_DATA: begin
if (rPlsBaudTick) begin
rRxDatBuf <= {wRxdVal, rRxDatBuf[7:1]}; //lsb,数据应该先放在高位,随后跟进的才会向右走
end
end
RX_STOP: begin
end
RX_ENDING: begin
end
RX_DONE: begin
rRxDat <= rRxDatBuf[7:0];
rFlgDone <= 1'b1; // 标志帧接收完成标记
end
endcase
end
end
assign data_o = rRxDat;
assign stat_o = rFlgDone;
endmodule
/*********************************************************************************************************
** End Of File
********************************************************************************************************/