结合上次的串口和LCD,这次在中间加了一个IIC。
流程图,如下:
看着好像挺复杂的样子。其实,在上次,已经实现了右下角的部分和串口的部分。只需要实现IIC部分,在和已经实现好的模块连接就行了。
首先说下功能:
串口将接收到是数据发送到IIC_FIFO中,然后IIC_FIFO控制器控制IIC控制器,将IIC_FIFO中暂存的数据给写到EEPROM中。当外部有按键按下时,按键检测模块会检测到这个输入,然后控制IIC控制器进行EEPROM的数据读取,读取到的数据发送给FIFO中。后面就和上次的一样,LCD从FIFO中取出数据,然后进行显示。
IIC控制器的程序,之前写过博客介绍过。
这里就列出顶层信号
+ 查看代码
module AT24C02_module(
input clk,
input rst_n,
input start,
input [2:0] device_address, //the device AT24C02 address
input [7:0] rom_address, //the rom address
input [7:0] rom_write_data, //write 8-bits data to the AT24C02
input write_or_read, //write mode or read mode 0 write 1 read
input [7:0] write_data_number, // write data number
input [7:0] read_data_number, //read data number
output reg[7:0] row_read_data, //read 8-bits data from AT24C02 in rom_address
output reg read_finish, //read data finish,mean external can read data
output reg write_finish, //write one byte data finsih.
output reg write_idle, //write data state, 1 mean external can write new data
inout i2c_sda, //I2C data , bidirectional port
output reg i2c_scl, //I2C clk 250K
output reg operate_busy, // IIC is or bot busy ,1 mean busy ,0 mean not busy
output enable_read // now state is read or write. 1 mean read, 0 mean write
);
可以实现IIC的多数据或单个数据的读写。
下面就是IIC_FIFO控制器。
采用状态机设计。
总共有三个状态。第一个状态是空闲状态,第二个状态是等待fifo接收串口数据状态,第三个状态就是控制写IIC状态。
所以,代码也是比较简单的。
+ 查看代码
module FIFO_IIC_control(
input clk,
input rst_n,
input rxd_finish, // uart receive finish signal . 1 mean read a uart data
input iic_write_finish, //IIC write 1 byte data signal. 1 mean write finish
input [5:0] iFifo_count, //IIC_FIFO save data number
output reg fifo_rd_en, //fifo read enable, 1 mean read data from fifo
output reg iic_start, //IIC statt signal, 1 mean start
output reg [5:0] oFifo_count // output to IIC show how many data will be write
);
localparam idle_state = 'd0;
localparam fifo_rxd_state = 'd1;
localparam iic_write_state = 'd2;
localparam time_value = 27'b100_0000_0000_0000_0000_0000_0000;
//localparam time_value = 27'd2000;
reg [26:0] time_counter;
reg [1:0] state;
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
oFifo_count <= 'd16;
fifo_rd_en <= 'd0;
iic_start <='d0;
time_counter <= 'd0;
state <= idle_state;
end
else
case(state)
//idle state nothing to do
//detect uart reveice siganl ,state go to fifo_rxd_state
idle_state: begin
time_counter <= 'd0;
if(rxd_finish == 1)
state <= fifo_rxd_state;
end
//fifo_rxd_state wait uart receive data finsih
//if exceed time_value ,mean uart receive data is finish, so state go to iic_write_state
fifo_rxd_state: begin
if( time_counter >= time_value )
begin
state <= iic_write_state;
oFifo_count <= iFifo_count;
iic_start <= 1'b1;
fifo_rd_en <= 1'b1;
end
else if(rxd_finish == 1)
time_counter <= 'd0;
else
time_counter <= time_counter + 1'b1;
end
//iic_write_state , control IIC write data
iic_write_state: begin
iic_start <= 1'b0;
fifo_rd_en <= iic_write_finish;
if(iFifo_count == 0)
state <= idle_state;
end
endcase
end
endmodule
这里说明一下oFifo_count <= 'd16; 这条语句。
oFifo_count这个是输出给IIC控制器的,指示这次操作读或写是要操作多少个数据。这里复位值是16.因为我们知道EEPROM器件数据是掉电不丢失的。所以上次写的数据在掉电后是不丢失的。所以在给开发板重新上电后,我们可以直接读取数据,而不用串口重新写数据之后才能读。所以这里将这个数据个数给复位为16.意思就在上电复位后,当按下按键后,就读取IIC的16个数据进行显示。
然后将IIC部分的代码给封装成一个模块
+ 查看代码
module fifo_iic(
input clk,
input rst_n,
input iuart_send_flag, //uart receive finish signal
input [7:0] iuart_data, //uart receive 8-bits data
input key, //external buttom
output iic_read_finish, //IIC read 1-byte data finish
output [7:0] iic_read_data, //IIC read 1-byte data
inout i2c_sda, //I2C data , bidirectional port
output i2c_scl , //I2C clk 250K
output [7:0] fifo_data_o,
output fifo_rd_en
);
wire iic_write_idle;
wire iic_write_finish;
wire [5:0] data_count;
wire [5:0] oFifo_count;
reg write_or_read;
wire key_down;
AT24C02_module u1 (
.clk(clk),
.rst_n(rst_n),
.start(iic_start | key_down),
.device_address(3'b000),
.rom_address('d0),
.rom_write_data(fifo_data_o),
.write_or_read(write_or_read),
.write_data_number({2'b0,oFifo_count}),
.read_data_number({2'b0,oFifo_count}),
.row_read_data(iic_read_data),
.read_finish(iic_read_finish),
.write_finish(iic_write_finish),
.write_idle(iic_write_idle),
.i2c_sda(i2c_sda),
.i2c_scl(i2c_scl),
.operate_busy(),
.enable_read()
);
FIFO_IIC_control u2 (
.clk(clk),
.rst_n(rst_n),
.rxd_finish(iuart_send_flag),
.iic_write_finish(iic_write_finish),
.iFifo_count(data_count),
.fifo_rd_en(fifo_rd_en),
.iic_start(iic_start),
.oFifo_count(oFifo_count)
);
fifo_module u3 (
.clk(clk),
.rst_n(rst_n),
.idata(iuart_data),
.ird_en(fifo_rd_en),
.iwr_en(iuart_send_flag),
.odata(fifo_data_o),
.empty(),
.data_count(data_count) // output [5 : 0] data_count
);
key_button u4 (
.clk(clk),
.rst_n(rst_n),
.key(~key),
.key_down(key_down)
);
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
write_or_read <= 1'b0; //default IIC is write operate
else
if(key_down == 1)
write_or_read <= 1'b1;
else if(iic_write_idle == 1 && write_or_read == 1'b1)
write_or_read <= 1'b0;
end
endmodule
在然后,将串口模块,IIC模块,LCD模块给连接起来。也就是进行封装。
+ 查看代码
module uart_iic_lcd(
input clk,
input rst_n,
//uart interface
input uart_rxd, //input serial rxd data
output uart_txd , //output serial txd data
input key,
inout i2c_sda, //I2C data , bidirectional port
output i2c_scl, //I2C clk 250K
// LCD Interface
output [3:0] LCD_DATA, //
output LCD_RW, //
output LCD_EN, //
output LCD_RS , //
output [7:0] oled
);
wire [7:0] iic_read_data;
wire iic_read_finish;
wire iuart_send_flag;
wire [7:0] iuart_data;
wire fifo_rd_en;
wire [7:0] fifo_data_o;
wire clk_50M;
uart_top u0 (
.clk(clk_50M),
.rst_n(rst_n),
.uart_rxd(uart_rxd),
.tx_start(fifo_rd_en ),
.tx_data(fifo_data_o[7:0]),
.tx_finish(),
.rx_finish(iuart_send_flag),
.uart_txd(uart_txd),
.receive_data(iuart_data[7:0])
);
assign oled = iuart_data[7:0];
fifo_iic u1 (
.clk(clk_50M),
.rst_n(rst_n),
.iuart_send_flag(iuart_send_flag),
.iuart_data(iuart_data),
.key(key),
.iic_read_finish(iic_read_finish),
.iic_read_data(iic_read_data[7:0]),
.i2c_sda(i2c_sda),
.i2c_scl(i2c_scl),
.fifo_data_o(),
.fifo_rd_en()
);
LCD_top u2 (
.clk(clk_50M),
.rst_n(rst_n),
.iuart_data(iic_read_data[7:0]),
.iuart_send_flag(iic_read_finish),
.LCD_DATA(LCD_DATA[3:0]),
.LCD_RW(LCD_RW),
.LCD_EN(LCD_EN),
.LCD_RS(LCD_RS),
.fifo_data_o(fifo_data_o[7:0]),
.fifo_rd_en(fifo_rd_en)
);
dcm_100_50 u3 (
.clk(clk),
.rst_n(rst_n),
.clk_out(clk_50M)
);
endmodule
这里多一个dcm_100_50模块,因为virtex5的板子的晶振是100M的。而设计的代码是基于50M时钟的,所以需要一个dcm将时钟进行二分频。为什么不自己写一个二分频代码了。因为用dcm分频出来的时钟的质量比直接用代码写生成出来的时钟质量好。
下面就开始测试
首先发送数据。
使用逻辑分析仪抓紧的IIC写数据波形
最前面两个发的是器件地址和写数据的地址。后面是写的数据。
然后读取数据,
逻辑分析仪抓取的IIC读取波形
前两个是器件地址和读取数据的地址。后面一个是重新发送器件地址,但是发送的数据最后一位要为1,表示是读数据。后面就是读取的数据。
设计,还有些小bug。
EEPROM在多字节写入的时候,一次操作最多只能写16个数据,超过16个数据后,写的数据会覆盖之前写的数据,但是在读的时候,是没有限制的。所以,当串口发送的数据超过16个后,之前写入的数据会被覆盖掉,而读取的时候,是读取发送那么多数据的个数的。所以,就会读到无效数据,显示就会出现乱码。
例如,我发送18个数据,那么最后两个数据会覆盖最先写的两个数据。读的时候,会读取18个数据,这样就读到了无效的后两个数据。
这里,在程序中,就需要加入,当写入的数据超过16个的时候,进行写第二次。
用逻辑分析仪抓波形
然后读取数据
抓取的波形
发送的数据,总共有34个。所以最终写入到IIC的第一个数据是第33个,也就是7.因为被后面写的数据覆盖了。所以读取到的数据的第一个就是7。至于后面读到的乱码,那是因为地址超过16了,而地址超过16的数据,我们都没有写,所以不知道这些数据是什么。就有可能是乱码了。