结合上次的串口和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的数据,我们都没有写,所以不知道这些数据是什么。就有可能是乱码了。