MCU向FIFO写入数据后,则发送FIFO的空状态标志位变为非空,串口发送模块监测到非空状态后,读取1个8位并行数据,按照MCU配置的波特率向串口芯片发送数据,发送的格式为:1位起始位,8位数据位(数据位按照从低位到高位的方式,即发送bit0,bit1……,bit7),奇偶校验位(如MCU配置无奇偶校验位,则该位不发送,该奇偶校验位可设为奇校验或偶校验),停止位(可设为1位或2位),发送结束后,重新检测FIFO的空状态位,如果非空则继续读取数据向串口芯片发送串行数据,直到FIFO为空状态。
串口发送模块端口信号说明双端口
端口名称 | 位宽 | 类型 | 功能 |
clk | 1 | 输入 | 时钟(上升沿) |
rst | 1 | 输入 | 复位信号(低有效) |
fifo_empty | 1 | 输入 | 发送FIFO的空状态标志位(高有效,即为空) |
fifo_data | 8 | 输入 | 发送FIFO的8位输出数据信号 |
fifo_rden | 1 | 输出 | 发送FIFO的读使能信号 |
serial_tx | 1 | 输出 | 串口输出信号 |
serial_baudrate | 8 | 输入 | 串口波特率 |
serial_stop_bit | 2 | 输入 | 串口停止位 |
serial_parity | 2 | 输入 | 串口奇偶校验位 |
复位期间,状态机处于IDLE状态,在该状态下,串口的输出一直为高电平;
当检测到待发送FIFO为非空状态时,进入RD_FIFO状态,该状态维持5个时钟周期,在该状态完成读取FIFO中的一个8位数据的功能。首先产生FIFO的读使能有效信号,然后将FIFO的输出数据寄存下来,在该状态下,串口的输出一直为高电平;
接下来是START_BIT状态(发送起始位),该状态维持的时间为一个波特率宽度,即MCU如果将波特率配置为0x30,则维持48个时钟周期,如果将波特率设置为0xC0,则维持192个时钟周期。在该状态下,串口的输出一直为低电平;
接下来是SEND_DATA状态(发送数据位),该状态维持的时间为8个波特率宽度,8位数据中的每bit占用1个波特率的时间宽度。发送时,首先bit0,依次bit1……,bit6,bit7。需要说明的是:虽然RS232串口为负逻辑,但是如果待发送bit位为高电平,则FPGA仍然输出高电平,如果待发送bit位为低电平,则FPGA仍然输出低电平。负逻辑的转换在外部串口接口芯片完成;
SEND_DATA状态结束后,状态机判断serial_parity(MCU设置的奇偶校验位),如果为10或者01(进行奇偶校验),则进入PARITY_BIT状态,否则直接进入STOP_BIT状态。在PARITY_BIT状态维持一个波特率时间宽度,结束后进入STOP_BIT状态。在该状态产生校验位,并且将校验位输出给串口接口芯片;
最后一个状态是STOP_BIT状态。该状态保持的时间宽度为一个或两个波特率宽度,取决于MCU配置的serial_stop_bit信号。在该状态下,串口的输出一直为高电平。
串口发送模块的状态转移图见下图。
串口发送模块状态转移图
串口发送模块的逻辑代码如下:
module serial_t(
clk,
rst,
fifo_empty,
fifo_data,
fifo_rden,
serial_tx,
serial_baudrate,
serial_stop_bit,
serial_parity
);
//system signal
input clk;
input rst;
//fifo signal
input fifo_empty;//待发送FIFO的空状态标志
input [7:0] fifo_data; //待发送FIFO的8位输出数据
output reg fifo_rden; //待发送FIFO的读使能信号
//transmit serial signal
output reg serial_tx; //串口输出信号
//serial_configure registers
input [7:0] serial_baudrate;
//MCU配置的串口波特率,如为0x30,则为230400bps;如为0x60,则为115200bps;
//如为0xC0,则为57600bps,其他为115200bps;
input [1:0] serial_stop_bit;
//MCU配置的串口停止位,如为10,则停止位为2位;其他则为1位;
input [1:0] serial_parity;
//MCU配置的串口奇偶校验位,如为01,则为奇校验;如为10,则为偶校验;其他
//为无校验
reg [7:0] data;//将从FIFO读出的数据进行寄存的寄存器
reg [3:0] bit_cnt;//8位数据中bit位
parameter IDLE =6'h01;
parameter RD_FIFO =6'h02;
parameter START_BIT =6'h04;
parameter SEND_DATA =6'h08;
parameter PARITY_BIT =6'h10;
parameter STOP_BIT =6'h20;
reg [5:0] cstate;//当前状态
reg [5:0] nstate;//下一状态
reg [2:0] rfifo_cnt;//读FIFO计数器
reg [7:0] start_counter; //发送起始位计数器
reg [8:0] stop_counter; //发送停止位计数器
reg [7:0] send_data_counter; //发送数据位计数器
reg [8:0] stop_reg; //停止位总数
reg [7:0] parity_counter; //发送奇偶校验位计数器
wire start_done; //发送起始位结束
wire parity_done; //发送奇偶校验位结束
wire stop_done; //发送停止位结束
always @ (posedge clk or negedge rst)
if(rst==0)
rfifo_cnt<=3'b0;
else
if(nstate==RD_FIFO || cstate==RD_FIFO)
rfifo_cnt<=rfifo_cnt+1;
else
rfifo_cnt<=3'b0;
//产生读FIFO计数器
always @ (posedge clk or negedge rst)
if(rst==0)
start_counter<=8'b0;
else
if(nstate==START_BIT || cstate==START_BIT)
start_counter<=start_counter+1;
else
start_counter<=8'b0;
//产生发送起始位计数器
always @ (posedge clk or negedge rst)
if(rst==0)
send_data_counter<=8'b0;
else
if(nstate==SEND_DATA || cstate==SEND_DATA)
begin
if(send_data_counter==serial_baudrate)
send_data_counter<=8'b0;
else
send_data_counter<=send_data_counter+1;
end
else
send_data_counter<=8'b0;
//产生发送数据位计数器
always @ (posedge clk or negedge rst)
if(rst==0)
parity_counter<=8'b0;
else
if(nstate==PARITY_BIT || cstate==PARITY_BIT)
parity_counter<=parity_counter+1;
else
parity_counter<=8'b0;
//产生发送奇偶校验位计数器
always @ (posedge clk or negedge rst)
if(rst==0)
stop_counter<=9'b0;
else
if(nstate==STOP_BIT || cstate==STOP_BIT)
stop_counter<=stop_counter+1;
else
stop_counter<=9'b0;
//产生发送停止位位计数器
always @ (posedge clk or negedge rst)
if(rst==0)
cstate<=IDLE;
else
cstate<=nstate;
//时钟沿到来时当前状态等于下一状态
assign start_done=(start_counter==serial_baudrate)?1'b1:1'b0;
assign parity_done=(parity_counter==serial_baudrate)?1'b1:1'b0;
assign stop_done=(stop_counter==stop_reg);
always @ (cstate,fifo_empty,rfifo_cnt,bit_cnt,start_done,parity_done,stop_done,serial_parity)
begin
nstate=IDLE;
case(cstate)
IDLE:
if(fifo_empty==1'b0)
nstate=RD_FIFO;
else
nstate=IDLE;
RD_FIFO:
if(rfifo_cnt==3'd5)
nstate=START_BIT;
else
nstate=RD_FIFO;
START_BIT:
if(start_done)
nstate=SEND_DATA;
else
nstate=START_BIT;
SEND_DATA:
if(bit_cnt==4'd8)
begin
if(serial_parity==2'b01 || serial_parity==2'b10)
nstate=PARITY_BIT;
else
nstate=STOP_BIT;
end
else
nstate=SEND_DATA;
PARITY_BIT:
if(parity_done)
nstate=STOP_BIT;
else
nstate=PARITY_BIT;
STOP_BIT:
if(stop_done)
nstate=IDLE;
else
nstate=STOP_BIT;
default:nstate=IDLE;
endcase
end
//产生下一状态
always @ (posedge clk or negedge rst)
if(rst==0)
begin
serial_tx<=1'b1;
end
else
case(nstate)
IDLE:
begin
serial_tx<=1'b1;
end
RD_FIFO:
begin
serial_tx<=1'b1;
end
START_BIT:serial_tx<=1'b0;
SEND_DATA:serial_tx<=data[bit_cnt[2:0]];
PARITY_BIT:
begin
if(serial_parity==2'b01)
serial_tx<=!(^data);
else
serial_tx<=^data;
end
STOP_BIT:serial_tx<=1'b1;
default:serial_tx<=1'b1;
endcase
//输出串口信号
always @ (posedge clk or negedge rst)
if(rst==0)
bit_cnt<=4'b0;
else if(nstate==IDLE)
bit_cnt<=4'b0;
else if(nstate==SEND_DATA && send_data_counter==serial_baudrate)
bit_cnt<=bit_cnt+1;
else
bit_cnt<=bit_cnt;
//产生8位数据中bit位
always @ (posedge clk or negedge rst)
if(rst==0)
fifo_rden<=1'b1;
else if(rfifo_cnt==3'd1)
fifo_rden<=1'b0;
else
fifo_rden<=1'b1;
//产生FIFO的读信号
always @ (posedge clk or negedge rst)
if(rst==0)
data<=8'b0;
else if(rfifo_cnt==3'd4)
data<=fifo_data;
else
data<=data;
//将FIFO输出的数据进行寄存
always @ (posedge clk or negedge rst)
if(rst==0)
stop_reg<={1'b0,serial_baudrate};
else if(serial_stop_bit==2'b10)
stop_reg<={serial_baudrate,1'b0};
else
stop_reg<={1'b0,serial_baudrate};
//生成停止位总数
endmodule
串口发送模块仿真波形图见下图。
串口发送模块仿真波形图1(数据为33、44、52,无奇偶偶校验,2个停止位)
串口发送模块仿真波形图2(数据为AA、55,偶校验,2个停止位)
该模块接口信号包括mcu_rd、mcu_wr、mcu_data、mcu_addr、mcu_cs、mcu_int等。
异步时钟域之间传输数据在FPGA设计中是一个非常重要的技术,处理不好很容易出现不稳定,或者某次布局布线后功能正常,重新布局布线后,功能就不正常,并且多数情况下,错误的现象没有明确的规律,问题定位非常困难。解决的办法是由本地时钟进行同步化处理。
mcu读模块中首先检测mcu_rd的下降沿,检测到下降沿之后,再判断片选信号和地址信号,给不同的地址分配特定的寄存器或者FIFO空间;
mcu写模块中首先检测mcu_wr的下降沿,检测到下降沿之后,再判断片选信号和地址信号,给不同的地址分配特定的寄存器或者FIFO空间。
具体的FPGA逻辑代码如下:
module mcu_configure(
clk,
rst,
mcu_rd,
mcu_wr,
mcu_data,
mcu_addr,
mcu_cs,
mcu_int,
serial_baudrate,
serial_stop_bit,
serial_parity,
serial_rfifo_empty,
serial_rfifo_data,
serial_rfifo_rden,
serial_tfifo_data,
serial_tfifo_wren);
//system signal
input clk;
input rst;
//mcu interface
input mcu_rd;//MCU的读信号
input mcu_wr; //MCU的写信号
inout [7:0] mcu_data; //MCU的数据信号
input [12:0] mcu_addr; //MCU的地址信号
input mcu_cs; //MCU的片选信号
output reg mcu_int; //MCU的中断信号
//serial_configure registers
output reg [7:0] serial_baudrate;
//MCU配置的串口波特率,如为0x30,则为230400bps;如为0x60,则为115200bps;
//如为0xC0,则为57600bps,其他为115200bps;
output reg [1:0] serial_stop_bit;
//MCU配置的串口停止位,如为10,则停止位为2位;其他则为1位;
output reg [1:0] serial_parity;
//MCU配置的串口奇偶校验位,如为01,则为奇校验;如为10,则为偶校验;其他//为无校验
//receive fifo
input serial_rfifo_empty;//接收FIFO的空状态标志位;
input [7:0] serial_rfifo_data;//接收FIFO的输出8位数据;
output reg serial_rfifo_rden;//接收FIFO的读使能信号;
//transmit fifo
output [7:0] serial_tfifo_data;//发送FIFO的写入数据信号;
output reg serial_tfifo_wren;//发送FIFO的写使能信号;
//MCU bidirection control
reg [7:0] mcu_data_reg;
assign mcu_data=(mcu_rd==0 && mcu_cs==0)?mcu_data_reg:8'bZZZZZZZZ;
//MCU的双向数据总线设置
//transmit fifo data
assign serial_tfifo_data=mcu_data;
reg mcu_rd_reg1;
reg mcu_rd_reg2;
reg mcu_rd_reg3;
reg [3:0] mcu_rd_counter;
always @ (posedge clk or negedge rst)
if(rst==0)
begin
mcu_rd_reg1<=1'b1;
mcu_rd_reg2<=1'b1;
mcu_rd_reg3<=1'b1;
end
else
begin
mcu_rd_reg1<=mcu_rd;
mcu_rd_reg2<=mcu_rd_reg1;
mcu_rd_reg3<=mcu_rd_reg2;
end
//将MCU的读信号同步3拍,同步2拍和3拍的寄存器信号用来判断MCU的读信号的
//下降沿
always @ (posedge clk or negedge rst)
if(rst==0)
mcu_rd_counter<=4'b0;
else if(mcu_rd_reg3==1'b0)
if(mcu_rd_counter==4'd8)
mcu_rd_counter<=4'd8;
else
mcu_rd_counter<=mcu_rd_counter+1;
else
mcu_rd_counter<=4'b0;
//对MCU的读信号宽度进行计数;
always @ (posedge clk or negedge rst)
if(rst==0)
begin
mcu_data_reg<=8'b0;
serial_rfifo_rden<=1'b1;
end
else if(mcu_rd_reg3==1'b0 && mcu_cs==0)
begin
case(mcu_addr)
13'h0000:
mcu_data_reg<=serial_baudrate;
13'h0001:
mcu_data_reg<={6'b0,serial_parity};
13'h0002:
mcu_data_reg<={6'b0,serial_stop_bit};
13'h0020:
mcu_data_reg<={7'b0,serial_rfifo_empty};
13'h0021:
begin
mcu_data_reg<=serial_rfifo_data;
if(mcu_rd_counter==4'd0)
serial_rfifo_rden<=1'b0;
else
serial_rfifo_rden<=1'b1;
end
default:
mcu_data_reg<=8'b0;
endcase
end
else
begin
mcu_data_reg<=8'b0;
serial_rfifo_rden<=1'b1;
end
//上面这段代码实现了MCU的读取的寄存器地址定义
reg mcu_wr_reg1;
reg mcu_wr_reg2;
reg mcu_wr_reg3;
always @ (posedge clk or negedge rst)
if(rst==0)
begin
mcu_wr_reg1<=1'b1;
mcu_wr_reg2<=1'b1;
mcu_wr_reg3<=1'b1;
end
else
begin
mcu_wr_reg1<=mcu_wr;
mcu_wr_reg2<=mcu_wr_reg1;
mcu_wr_reg3<=mcu_wr_reg2;
end
//将MCU的写信号同步3拍,同步2拍和3拍的寄存器信号用来判断MCU的写信号的
//下降沿
always @ (posedge clk or negedge rst)
if(rst==0)
begin
serial_baudrate<=8'h60;
serial_parity<=2'b00;
serial_stop_bit<=2'b00;
serial_tfifo_wren<=1'b1;
end
else if(mcu_wr_reg2==0 && mcu_wr_reg3==1 && mcu_cs==0)
begin
case(mcu_addr)
13'h0000:
serial_baudrate<=mcu_data;
13'h0001:
serial_parity<=mcu_data[1:0];
13'h0002:
serial_stop_bit<=mcu_data[1:0];
13'h0010:
serial_tfifo_wren<=1'b0;
default:
begin
serial_baudrate<=serial_baudrate;
serial_parity<=serial_parity;
serial_stop_bit<=serial_stop_bit;
serial_tfifo_wren<=1'b1;
end
endcase
end
else
begin
serial_baudrate<=serial_baudrate;
serial_parity<=serial_parity;
serial_stop_bit<=serial_stop_bit;
serial_tfifo_wren<=1'b1;
end
//上面这段代码实现了MCU的写入的寄存器地址定义
always @ (posedge clk or negedge rst)
if(rst==0)
mcu_int<=1'b1;
else if(serial_rfifo_empty==1'b0)
mcu_int<=1'b0;
else
mcu_int<=1'b1;
//MCU中断信号的产生,接收FIFO非空则产生中断信号
endmodule
下图为MCU配置模块仿真波形图。可以看到MCU向地址0x000写入0x60后正确读出,即设置波特率为115200bps。
串口发送模块仿真波形图3(数据为AA、55、52,奇校验,1个停止位)