学习笔记:FPGA之多终端点歌系统设计三:PS/2通信协议
一,ps/2基本概述
http://www.baike.com/wiki/PS/2%E6%8E%A5%E5%8F%A3
二,PS/2通信协议
PS/2是鼠标和键盘履行的一种双向同步串行协议。通信的两端通过CLOCK(时钟脚)同步,并通过Data(数据脚)交换数据。键盘/鼠标可以发送数据到主机,而主机也可以发送数据到设备,但主机总是在总线上有优先权,它可以在任何时候抑制来自于键盘/鼠标的通信。只要拉低时钟即可。该协议采用的短帧格式,每一数据帧包含11~12个位。
表中,如果数据位中1的个数为偶数,校验位就位1;如果数据位中1的个数为奇数,校验位就位0;在一帧数据的通信过程中,主机在时钟的下降沿读取来自外设发来的数据,外设在时钟的上升沿读取主机发来的数据,无论是主机端发送信息还是外设端发送信息,同步时钟都是由外设来产生的,一般两设备间传输数据的同步时钟最大频率为33KHZ,大多数ps/2设备工作在10~20KHZ。外设每接受到主机发来的一帧数据,都需要紧随该帧的停止位发送一个应答位数据,然后外设还要发送一帧应答数据,表示外设已经完整地接受到了主机的命令。而主机每收到外设发来的一帧数据则不需要发送这个应答位,也不需要另外发送应答帧。
当键盘或鼠标要发送信息时,它首先检测时钟线是否为高,如果时钟线不是高电平,那么主机则禁止外设发送,外设需要把发送的信息存储起来等待总线空闲时再发送数据。如果时钟线为高,那么表面总线空闲,外设可以发送数据。
从该时序图中,外设在时钟高电平时期将数据放到数据线上,主机在接受到时钟下降沿后到数据线上读取数据。
(1)外设检测时钟线,如果时钟线为低,系统禁止外设发送;这时,我们延迟等待主机释放时钟线。如果时钟线为高,接着向下执行。
(2) 外设检测数据线,如果数据线为低,则主机准备发送数据,外设改变状态,准备接受主机的发送信息,如果数据线为高,接着执行。
(3)外设将数据线拉低,发送起始位,5~25us后开始产生时钟进行数据发送。
(4)外设在发送过程中的前十个时钟周期里以固定的时间间隔定期地在时钟的高电平时期检测时钟线,如果外设发现时钟线已经被主机强行拉低,则该发送过程被终止。
(5)外设最后一次检测时钟线发生在外设产生了第10个时钟上升沿5us后。
(6)主机在时钟的下降沿到数据线上读取数据,在收到一个完整的信息后,将时钟线拉低禁止外设的下一个发送过程以便处理收到的信息。
(7)主机释放时钟线表示允许下一个外设发送过程。
从该书序图中,主机在时钟的低电平时期将数据放到数据线上,外设在发送了时钟的上升沿后到数据线上取。
(1)主机检测外设是否处于发送过程。若正在发送且已经发送了第10个时钟,则主机必须接受外设发送的信息,若正在进行的发送过程没有超过第10个时钟,则主机强行通过将时钟线拉低终止该数据发送过程。
(2) 主机将时钟线拉低至少60us,然后主机拉低数据线,以此低电平作为发送起始位通知外设主机有数据要发送。
(3)主机释放时钟线(将时钟线置为高),然后等待外设将时钟线拉低。
(4)主机根据待发数据位是0或1将数据线拉低或置高。
(5)主机等待外设将时钟线置高,时钟线被拉高后则等待外设将时钟线拉低。
(6)重复步骤4-5,直到发送完奇校验位。
(7)主机释放数据线,该高电平即作为停止位。
(8)主机等待外设将数据线拉低(外设发给主机的握手应答位)
(9)主机等待外设将时钟线拉低。
(10)主机等待外设释放时钟线和数据线。
三,PS/2键盘的数据包格式。PS/2键盘的状态每改变一次,键盘至少会发出三个字节的数据包,在有键按下时会向主机发送该键的通码,当按键释放时会发送断码。
四,实际应用
(1)发送0~9数字,改变蜂鸣器的音调
(2)功能模块
(3)verilog代码:
module Ps2_Module
(
//输入端口
CLK_50M,RST_N,PS2_CLK,PS2_DATA,
//输出端口
o_ps2_data
);
//---------------------------------------------------------------------------
//-- 外部端口声明
//---------------------------------------------------------------------------
input CLK_50M; //时钟的端口,开发板用的50M晶振
input RST_N; //复位的端口,低电平复位
input PS2_CLK; //PS2的时钟端口
input PS2_DATA; //PS2的数据端口
output reg [15:0] o_ps2_data; //从PS2数据线中解析完后的数据。
//---------------------------------------------------------------------------
//-- 内部端口声明
//---------------------------------------------------------------------------
reg [ 1:0] detect_edge; //记录PS2的开始脉冲,即第一个下降沿
wire [ 1:0] detect_edge_n; //detect_edge的下一个状态
reg [ 3:0] bit_cnt; //记录PS2的时钟个数,即数据位数
reg [ 3:0] bit_cnt_n; //bit_cnt的下一个状态
reg [10:0] bit_shift; //数据移位寄存器,用于读出PS2线上数据
reg [10:0] bit_shift_n; //bit_shift的下一个状态
reg [39:0] data_shift; //数据移位寄存器,用于记录数据帧数
reg [39:0] data_shift_n; //data_shift的下一个状态
reg [15:0] o_ps2_data_n; //o_ps2_data的下一个状态
reg negedge_reg; //下降沿标志
wire negedge_reg_n; //negedge_reg的下一个状态
//---------------------------------------------------------------------------
//-- 逻辑功能实现
//---------------------------------------------------------------------------
//时序电路,用来给detect_edge寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
detect_edge <= 2'b11; //初始化detect_edge值
else
detect_edge <= detect_edge_n; //用来给detect_edge赋值
end
//组合电路,检测下降沿
assign detect_edge_n = {detect_edge[0] , PS2_CLK}; //接收PS2的时钟信号
//时序电路,用来给negedge_reg寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
negedge_reg <= 1'b0; //初始化negedge_reg值
else
negedge_reg <= negedge_reg_n; //用来给negedge_reg赋值
end
//组合电路,判断下降沿,如果detect_edge等于10,negedge_reg_n就置1
assign negedge_reg_n = (detect_edge == 2'b10) ? 1'b1 : 1'b0;
//时序电路,用来给bit_cnt寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
bit_cnt <= 4'b0; //初始化bit_cnt值
else
bit_cnt <= bit_cnt_n; //用来给bit_cnt赋值
end
//组合电路,判断下降沿,并记录下降沿个数
always @ (*)
begin
if(bit_cnt == 4'd11) //判断时钟个数
bit_cnt_n = 4'b0; //如果等于11,bit_cnt_n就置0
else if(negedge_reg) //判断下降沿
bit_cnt_n = bit_cnt + 4'b1; //如果下降沿到来,bit_cnt_n就加1
else
bit_cnt_n = bit_cnt; //否则,保持不变
end
//时序电路,用来给bit_shift寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
bit_shift <= 11'b0; //初始化bit_shift值
else
bit_shift <= bit_shift_n; //用来给bit_shift赋值
end
//组合电路,读出PS2线上数据并放到移位寄存器中
always @ (*)
begin
if(negedge_reg) //判断下降沿
bit_shift_n = {PS2_DATA , bit_shift[10:1]}; //如果下降沿到来,就将PS2数据线上的值存入移位寄存器中
else
bit_shift_n = bit_shift; //否则,保持不变
end
//时序电路,用来给data_shift寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
data_shift <= 40'b0; //初始化data_shift值
else
data_shift <= data_shift_n; //用来给data_shift赋值
end
//组合电路,将字节拼合成帧
always @ (*)
begin
if(bit_cnt == 4'd11) //判断时钟个数
data_shift_n = {data_shift[31:0] , bit_shift[8:1]}; //如果等于11,就将8位数据存入帧的移位寄存器中
else
data_shift_n = data_shift; //否则,保持不变
end
//时序电路,用来给o_ps2_data寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
o_ps2_data <= 16'b0; //初始化o_ps2_data值
else
o_ps2_data <= o_ps2_data_n; //用来给o_ps2_data赋值
end
//组合电路,用来判断通码和断码,并将数据输出
always @ (*)
begin
if((data_shift_n[15:8] == 8'hF0) && (data_shift_n[23:16] == 8'hE0)) //判断断码和通码
o_ps2_data_n = {8'hE0 , data_shift_n[7:0]}; //如果相等就将E0和数据位输出
else if((data_shift_n[15:8] == 8'hF0) && (data_shift_n[23:16] != 8'hE0)) //判断断码和通码
o_ps2_data_n = {8'h0, data_shift_n[7:0]}; //如果通码不等于E0,就将数据位输出
else
o_ps2_data_n = o_ps2_data; //否则,保持不变
end
endmodule
module Beep_Module
(
//输入端口
CLK_50M,RST_N,KEY,
//输出端口
BEEP
);
//---------------------------------------------------------------------------
//-- 外部端口声明
//---------------------------------------------------------------------------
input CLK_50M; //时钟的端口,开发板用的50MHz晶振
input RST_N; //复位的端口,低电平复位
input [ 7:0] KEY; //按键端口
output BEEP; //蜂鸣器端口
//---------------------------------------------------------------------------
//-- 内部端口声明
//---------------------------------------------------------------------------
reg [19:0] time_cnt; //用来控制蜂鸣器发声频率的定时计数器
reg [19:0] time_cnt_n; //time_cnt的下一个状态
reg [15:0] freq; //各种音调的分频值
reg [15:0] freq_n; //各种音调的分频值
reg beep_reg; //用来控制蜂鸣器发声的寄存器
reg beep_reg_n; //beep_reg的下一个状态
//---------------------------------------------------------------------------
//-- 逻辑功能实现
//---------------------------------------------------------------------------
//时序电路,用来给time_cnt寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
time_cnt <= 1'b0; //初始化time_cnt值
else
time_cnt <= time_cnt_n; //用来给time_cnt赋值
end
//组合电路,判断频率,让定时器累加
always @ (*)
begin
if(time_cnt == freq) //判断分频值
time_cnt_n = 1'b0; //定时器清零操作
else
time_cnt_n = time_cnt + 1'b1; //定时器累加操作
end
//时序电路,用来给beep_reg寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
beep_reg <= 1'b0; //初始化beep_reg值
else
beep_reg <= beep_reg_n; //用来给beep_reg赋值
end
//组合电路,判断频率,使蜂鸣器发声
always @ (*)
begin
if(time_cnt == freq) //判断分频值
beep_reg_n = ~beep_reg; //改变蜂鸣器的状态
else
beep_reg_n = beep_reg; //蜂鸣器的状态保持不变
end
//时序电路,用来给beep_reg寄存器赋值
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N) //判断复位
freq <= 1'b0; //初始化beep_reg值
else
freq <= freq_n; //用来给beep_reg赋值
end
//组合电路,按键选择分频值来实现蜂鸣器发出不同声音
//中音do的频率为523.3hz,freq = 50 * 10^6 / (523 * 2) = 47774
always @ (*)
begin
case(KEY)
8'h70: freq_n = 16'd0; //没有声音
8'h69: freq_n = 16'd47774; //中音1的频率值262Hz
8'h72: freq_n = 16'd42568; //中音2的频率值587.3Hz
8'h7A: freq_n = 16'd37919; //中音3的频率值659.3Hz
8'h6B: freq_n = 16'd35791; //中音4的频率值698.5Hz
8'h73: freq_n = 16'd31888; //中音5的频率值784Hz
8'h74: freq_n = 16'd28409; //中音6的频率值880Hz
8'h6C: freq_n = 16'd25309; //中音7的频率值987.8Hz
8'h75: freq_n = 16'd23889; //高音1的频率值1046.5Hz
8'h7D: freq_n = 16'd21276; //高音2的频率值1175Hz
default : freq_n = freq;
endcase
end
assign BEEP = beep_reg; //最后,将寄存器的值赋值给端口BEEP
endmodule
上一篇: 开源软件质量报告:连续两年高于行业平均值