串口通信
小梅哥资料第五章
1.协议::UART(通用异步收发传输器) (最为基础的就是 UART,因其 电路结构简单、成本较低,所以在注重性价比的情况下,使用非常广泛),IIC(双向两线总线),SPI(串行外围总线), USB2.0/3.00(通 用串行总线), Ethernet(以太网)
2.UART: 发送时将并转串传输,接收时串转并,可以实现全双工(=打电话,同时收发)传输和接收。
包括 RS232、RS449、RS423、 RS422 和 RS485 等接口标准规范和总线标准规范
3.RS232:
使用前设置 数据位数、波特率大小(一般通信两端设备都要设为 相同的波特率,但有些设备也可设置为自动检测波特率)、奇偶校验类型(验证数据的正确性)和停止位数(标志着一次数据传输完成)。
4.设计
4.1设计输入。根据上图设计即可。
4.2.设计逻辑:
功能描述:
1.发送八位数据位,一个起始位,一个结束位,共十位。
2.空闲时输出高电平
3.有一个使能端,高电平时传输数据,低电平时空闲(不传送)。
4.用户可以选择波特率。
细节:
1.用户可选择的波特率为常用速率,他只需要选择0,1..等按键就可以直接选择某个对应速率:9600,126000等,所以这里对应有一个寄存器用来存对应的速率。
2.为了区别空闲时和工作时,管理输出位的计数器应该留一个位给空闲状态,在使能端生效时立刻从空闲位进入起始位。使能端无效时停留在空闲位输出对应电平。(20ns的误差,让空闲位进入起始位,是可以接受的)
3.空闲位跟工作起始位的电平必然是不同的,这样可直观区分。
4. 仿真时可以用 @(posedge/negedge 信号),功能是:等到信号变化沿到来时再进行下一条语句,否则一直等待。用这条语句就不用在仿真时去计算发送一串数据需要多少时间。
5.代码
module uart_1( //设计输入
clk,
reset,
data,
send_en,
baud_rate,
uart_tx,
tx_done
);
input clk;
input reset;
input [7:0]data;
input send_en;
input [2:0]baud_rate;
output reg uart_tx;
output reg tx_done;
reg [17:0]bit_tim;
//设计逻辑
//把波特率转化为一位的持续时间 //单位时间内通过信道传输的码元数称为码元传输速率,即波特率,码元/s,一个码元可能由多个位组成。而比特率即 ‘位/s’
always@(baud_rate) //在这里一个 码元由一位组成,所以波特率=比特率
begin
case(baud_rate) //常见的串口传输波特率
3'd0 : bit_tim = 1000000000/300/20 ; //波特率为300
3'd1 : bit_tim = 1000000000/1200/20 ; //波特率为1200
3'd2 : bit_tim = 1000000000/2400/20 ; //波特率为2400
3'd3 : bit_tim = 1000000000/9600/20 ; //波特率为9600
3'd4 : bit_tim = 1000000000/19200/20 ; //波特率为19200
3'd5 : bit_tim = 1000000000/115200/20 ; //波特率为115200
default bit_tim = 1000000000/9600/20 ; //多余的寄存器位置放什么:默认速率
endcase
end
reg [17:0]counter1 ;//用来计数每一位的持续时间
always@(posedge clk or negedge reset)
begin
if(!reset)//复位清零
counter1 <=17'b0 ;
else if (send_en )//使能端有效,计数
if( counter1 == bit_tim - 1'b1 )//位持续时间到达时归零
counter1 <= 17'b0 ;
else
counter1 <= counter1 + 1'b1 ;//位持续时间没达到时继续进行
else counter1 <= 17'b0 ; //使能端无效时,清零
end
reg [3:0]counter2 ; //如果忘了考虑归零,那么计数器会出现溢出归零,在这里是加到15然后归零
always@(posedge clk or negedge reset)
begin
if(!reset)//复位
counter2 <= 4'b0 ;
else if ( send_en )//使能端有效
if(counter2 == 0)//消耗20ns,进入起始位。这个挺重要的,没有这个的话得消耗一位的时间进入起始位
counter2 <= counter2 +1'b1 ;
else if( counter1 == bit_tim - 1'b1 )//开始进行位移
counter2 <= counter2 + 4'b1 ;
else
counter2 <= counter2 ;
else//使能端无效,归零,进入空闲位
counter2 <= 4'b0 ;
end
always@(posedge clk or negedge reset)
begin
if(!reset)//复位
begin
uart_tx <= 4'b1 ;
tx_done <= 1'b0 ;
end
else if ( send_en )//使能端有效,输出每一位
case(counter2)
0:uart_tx <= 1'b1 ;//设定第一位为空闲位。没有空闲位的话,使能端无效时,counter停留在0,不能保持输出高电平(取决于要输出的数据),不符合要求。
1:begin uart_tx <= 1'b0 ; tx_done <= 1'b0 ; end//起始位
2:uart_tx <= data[0] ;
3:uart_tx <= data[1] ;
4:uart_tx <= data[2] ;
5:uart_tx <= data[3] ;
6:uart_tx <= data[4] ;
7:uart_tx <= data[5] ;
8:uart_tx <= data[6] ;
9:uart_tx <= data[7] ;
10:uart_tx <= 1'b1 ;//结束位
11:begin uart_tx <= 1'b1 ; tx_done <= 1'b1 ; end//为了让结束位跑满,设置11,作为第11个点,定第十位长度。
default uart_tx <= 1'b1 ;
endcase
else
uart_tx <= 1'b1 ;
end
endmodule
仿真测试代码
`timescale 1ns / 1ns
module uart_1_tb( );
reg s_clk ;
reg s_reset ;
reg [7:0]s_data ;
reg s_send_en ;
reg [2:0]s_baud_rate ;
wire s_uart_tx ;
wire s_tx_done ;
uart_1 uart_1_sim(
.clk(s_clk),
.reset(s_reset),
.data(s_data),
.send_en( s_send_en ),
.baud_rate(s_baud_rate),
.uart_tx( s_uart_tx),
.tx_done(s_tx_done )
);
initial s_clk = 1 ;
always #10 s_clk = ! s_clk ;
initial
begin
s_reset = 0 ;
s_data = 8'b0000_0000 ;
s_send_en = 0 ;
s_baud_rate =3'b000 ;
#201 ;
s_reset = 1 ;
s_data = 8'b0110_1001 ;
s_baud_rate = 3'b101 ;
s_send_en = 1 ;
#20 ;
@(posedge s_tx_done);
s_send_en = 0 ;
#8000 ;
s_data = 8'b1010_0101 ;
s_send_en = 1 ;
#20 ;
@(posedge s_tx_done) ;
s_send_en = 0 ;
#1000 ;
$stop ;
end
endmodule
问题:如何设计,使得使能端一直有效时,可以输入多端数据时,都可以输出,(上面的代码是需要使能端归零再变1来重置计数器counter2,从而输出第二段数据。如果没有重置,则需要计数器计满溢出才开始传输第二段数据)

浙公网安备 33010602011771号