最近想用FPGA数字图像处理加速,Camera采集到图像后直接在硬件上处理后再交给软件做二次处理。所以首先是要把Sensor给跑起来,目前也没有太多要求,采用OV2640 Sensor,IIC+DVP的接口,所以第一步要把I2C调通。虽然FPAG上也带有I2C IP,但自己写一个应该会更有乐趣,所以就有了本文。
IIC时序介绍IIC其实时序非常简单,就俩根线SCL和SDA,逻辑0时电平为GND,逻辑高时为高阻态,一个完整时序如下:
一个完整的时序可以拆分为三个步骤:
(1)开始信号S:保证SCL为逻辑1时,SDA从逻辑1跳变为逻辑0。(后文标识位[S])
(2)数据位+ACK位: SCL下降沿时SDA准备好数据,SCL上升沿时采样。(后文标志[DA])
(3)停止信号P:保证SCL为逻辑1时,SDA从逻辑0跳变为逻辑1。(后文标志[P])
电路设计思想分析IIC, 一般有如下几种组合:
1. 单byte读/写: [S]开始 + [DA]器件地址 + [DA]数据 + [P]停止
2. 多byte读/写: [S]开始 + [DA]器件地址 + [DA]数据*n个 + [P]停止
3. 单byte复合读/写: [S]开始 + [DA]器件地址 + [DA]片内地址 + [DA]数据 + [P]停止
4. 单byte复合读/写: [S]开始 + [DA]器件地址 + [DA]片内地址 + [DA]数据*n个 + [P]停止
5. 寻址扫描: [S]开始 + [DA]器件地址 + [P]停止
根据上述分析,可以设计一个状态机,状态图如下:
在软件上,只需要控制 [S]开始信号、[DA]数据和ACK位、[P]停止信号 状态的切换即可即可实现常用组合
Verilog实现
module iic (
inout wire iic_sda,
inout wire iic_scl,
input wire mod_rst,
input wire mod_clk,
input wire [15:0] mod_div,
output wire [7:0] mod_sta,
input wire [7:0] mod_wdata,
output wire [7:0] mod_rdata,
input wire [7:0] mod_cmd
);
localparam IIC_OUTPUT_VER = 1'bz;
// localparam IIC_OUTPUT_VER = 1'b1;
reg [15:0] reg_div;
always @(posedge mod_cmd[0] or posedge mod_rst) begin
if (mod_rst)
reg_div <= 16'd0;
else
reg_div <= mod_div;
end
reg [15:0] reg_div_cmp;
wire wire_scl_filp;
assign wire_scl_filp = reg_div == reg_div_cmp;
always @(posedge mod_clk or posedge mod_rst) begin
if (mod_rst)
reg_div_cmp <= 16'd0;
else if(wire_scl_filp)
reg_div_cmp <= 16'd0;
else
reg_div_cmp <= reg_div_cmp + 1'b1;
end
reg reg_iic_mode;
localparam IIC_MODE_SLAVE = 1'b0;
localparam IIC_MODE_MASTER = 1'b1;
reg reg_master_scl;
always @(posedge mod_clk or posedge mod_rst) begin
if (mod_rst)
reg_master_scl <= 1'b1;
else if (wire_scl_filp)
reg_master_scl <= ~reg_master_scl;
else;
end
reg [3:0] reg_iic_state;
localparam IIC_STATE_HSCL = 4'd0;
localparam IIC_STATE_LALL = 4'd1;
localparam IIC_STATE_ACK = 4'd2;
localparam IIC_STATE_BIT0 = 4'd3;
localparam IIC_STATE_BIT1 = 4'd4;
localparam IIC_STATE_BIT2 = 4'd5;
localparam IIC_STATE_BIT3 = 4'd6;
localparam IIC_STATE_BIT4 = 4'd7;
localparam IIC_STATE_BIT5 = 4'd8;
localparam IIC_STATE_BIT6 = 4'd9;
localparam IIC_STATE_BIT7 = 4'd10;
localparam IIC_STATE_LSDA = 4'd11;
localparam IIC_STATE_HALL = 4'd12;
reg reg_cmd_start;
wire iic_sta_start;
wire iic_start_zone;
reg reg_start_done;
assign iic_sta_start = reg_iic_state == IIC_STATE_LSDA;
assign iic_start_zone = (reg_iic_state == IIC_STATE_HALL | iic_sta_start) & !reg_start_done;
always @(negedge reg_master_scl or posedge mod_rst) begin
if (mod_rst)
reg_start_done <= 1'b0;
else
reg_start_done <= iic_sta_start;
end
wire startd_or_rst;
assign startd_or_rst = reg_start_done | mod_rst;
always @(posedge mod_cmd[1] or posedge startd_or_rst) begin
if (mod_cmd[1])
reg_cmd_start <= 1'b1;
else if (startd_or_rst)
reg_cmd_start <= 1'b0;
else;
end
reg reg_cmd_ack;
wire iic_sta_ack;
wire iic_ack_zone;
reg reg_ack_done;
assign iic_sta_ack = reg_iic_state == IIC_STATE_ACK;
assign iic_ack_zone = iic_sta_ack & !reg_ack_done;
always @(negedge reg_master_scl or posedge mod_rst) begin
if (mod_rst)
reg_ack_done <= 1'b0;
else
reg_ack_done <= iic_sta_ack;
end
wire ackd_or_rst;
assign ackd_or_rst = mod_rst | reg_ack_done;
always @(posedge mod_cmd[4] or posedge ackd_or_rst) begin
if (mod_cmd[4])
reg_cmd_ack <= 1'b1;
else if (ackd_or_rst)
reg_cmd_ack <= 1'b0;
else;
end
reg reg_cmd_wd;
always @(posedge mod_cmd[3] or posedge ackd_or_rst) begin
if (mod_cmd[3])
reg_cmd_wd <= 1'b1;
else if (ackd_or_rst)
reg_cmd_wd <= 1'b0;
else;
end
reg reg_cmd_rd;
always @(posedge mod_cmd[5] or posedge ackd_or_rst) begin
if (mod_cmd[5])
reg_cmd_rd <= 1'b1;
else if (ackd_or_rst)
reg_cmd_rd <= 1'b0;
else;
end
reg reg_cmd_stop;
wire iic_sta_stop;
wire iic_stop_zone;
reg reg_stop_done;
assign iic_sta_stop = reg_iic_state == IIC_STATE_HSCL;
assign iic_stop_zone = (reg_iic_state == IIC_STATE_LALL | iic_sta_stop) & !reg_stop_done;
always @(negedge reg_master_scl or posedge mod_rst) begin
if (mod_rst)
reg_stop_done <= 1'b0;
else
reg_stop_done <= iic_sta_stop;
end
wire stopd_or_rst;
assign stopd_or_rst = mod_rst | reg_stop_done;
always @(posedge mod_cmd[6] or posedge stopd_or_rst) begin
if (mod_cmd[6])
reg_cmd_stop <= 1'b1;
else if (stopd_or_rst)
reg_cmd_stop <= 1'b0;
else;
end
wire iic_pause;
assign iic_pause = (reg_iic_state==IIC_STATE_HSCL) | (reg_iic_state==IIC_STATE_ACK) | (reg_iic_state==IIC_STATE_LSDA);
always @(negedge reg_master_scl or posedge mod_rst) begin
if (mod_rst)
reg_iic_state <= IIC_STATE_HSCL;
else if (!iic_pause)
reg_iic_state <= reg_iic_state - 1'b1;
else if (reg_cmd_start & !iic_start_zone)
reg_iic_state <= IIC_STATE_HALL;
else if (!iic_ack_zone & (reg_cmd_wd | reg_cmd_rd))
reg_iic_state <= IIC_STATE_BIT7;
else if (reg_cmd_stop & !iic_stop_zone)
reg_iic_state <= IIC_STATE_LALL;
else;
end
wire iic_sta_data;
assign iic_sta_data = (reg_iic_state > IIC_STATE_ACK) & (reg_iic_state < IIC_STATE_LSDA);
wire iic_sda_is_in;
assign iic_sda_is_in = (reg_cmd_rd & iic_sta_data) | (reg_cmd_wd & iic_ack_zone);
reg iic_sda_out;
assign iic_sda = iic_sda_is_in ? 1'bz : (iic_sda_out ? IIC_OUTPUT_VER : 1'b0);
wire iic_sda_in;
assign iic_sda_in = iic_sda_is_in ? iic_sda : 1'bz;
reg [7:0] reg_r_buffer;
always @(posedge reg_master_scl or posedge mod_rst) begin
if (mod_rst)
reg_r_buffer <= 8'd0;
else if (iic_sda_is_in)
reg_r_buffer <= {reg_r_buffer[6:0], iic_sda_in};
else;
end
assign mod_rdata = reg_r_buffer;
reg [7:0] reg_w_buffer;
always @(posedge mod_cmd[2] or posedge mod_rst) begin
if (mod_rst)
reg_w_buffer <= 8'd0;
else
reg_w_buffer <= mod_wdata;
end
always @(*) begin
case (reg_iic_state)
IIC_STATE_HSCL: iic_sda_out = 1'b1;
IIC_STATE_LALL: iic_sda_out = 1'b0;
IIC_STATE_ACK: iic_sda_out = reg_ack_done ? 1'b1 : reg_cmd_ack;
IIC_STATE_BIT0: iic_sda_out = reg_w_buffer[0];
IIC_STATE_BIT1: iic_sda_out = reg_w_buffer[1];
IIC_STATE_BIT2: iic_sda_out = reg_w_buffer[2];
IIC_STATE_BIT3: iic_sda_out = reg_w_buffer[3];
IIC_STATE_BIT4: iic_sda_out = reg_w_buffer[4];
IIC_STATE_BIT5: iic_sda_out = reg_w_buffer[5];
IIC_STATE_BIT6: iic_sda_out = reg_w_buffer[6];
IIC_STATE_BIT7: iic_sda_out = reg_w_buffer[7];
IIC_STATE_LSDA: iic_sda_out = 1'b0;
IIC_STATE_HALL: iic_sda_out = 1'b1;
default:;
endcase
end
reg iic_scl_out;
wire iic_scl_in;
assign iic_scl = reg_iic_mode ? (iic_scl_out ? IIC_OUTPUT_VER : 1'b0) : 1'bz;
assign iic_scl_in = reg_iic_mode ? 1'bz : iic_scl;
always @(*) begin
case (reg_iic_state)
IIC_STATE_HSCL: iic_scl_out = 1'b1;
IIC_STATE_LALL: iic_scl_out = reg_master_scl;
IIC_STATE_ACK: iic_scl_out = reg_ack_done ? 1'b0 : reg_master_scl;
IIC_STATE_BIT0: iic_scl_out = reg_master_scl;
IIC_STATE_BIT1: iic_scl_out = reg_master_scl;
IIC_STATE_BIT2: iic_scl_out = reg_master_scl;
IIC_STATE_BIT3: iic_scl_out = reg_master_scl;
IIC_STATE_BIT4: iic_scl_out = reg_master_scl;
IIC_STATE_BIT5: iic_scl_out = reg_master_scl;
IIC_STATE_BIT6: iic_scl_out = reg_master_scl;
IIC_STATE_BIT7: iic_scl_out = reg_master_scl;
IIC_STATE_LSDA: iic_scl_out = 1'b1;
IIC_STATE_HALL: iic_scl_out = 1'b1;
default:;
endcase
end
wire iic_bus_idle;
assign iic_bus_idle = iic_sda & iic_scl;
assign mod_sta = {
1'b0,
reg_cmd_stop,
reg_cmd_rd,
reg_cmd_wd,
reg_cmd_start,
!reg_r_buffer[0],
1'b0,
iic_bus_idle
};
always @(posedge reg_cmd_start or negedge reg_cmd_stop or posedge mod_rst) begin
if (mod_rst)
reg_iic_mode <= IIC_MODE_SLAVE;
else if (reg_cmd_start)
reg_iic_mode <= IIC_MODE_MASTER;
else if (!reg_cmd_stop)
reg_iic_mode <= IIC_MODE_SLAVE;
else;
end
endmodule
关于原理,代码中都有相应的注释,就不过多解释。
C语言驱动目前再zynq7010上使用AXI接入到ARM,地址做如下映射:
| 名称 | 有效位 | 地址 |
| iic_div | [15:0] | 0x43C10000 |
| iic_state | [7:0] | 0x43C10004 |
| iic_wdata | [7:0] | 0x43C10008 |
| iic_rdata | [7:0] | 0x43C1000C |
| iic_cmd + iic_rst | [7:0] [31] | 0x43C10010 |
实现代码:
#include#include #include "sys.h" typedef struct { uint32_t div; uint32_t status; uint32_t wdata; uint32_t rdata; uint32_t cmd; } IIC; static volatile IIC *iic = (IIC *)0x43C10000; void iic_init(uint32_t freq) { // cmd[31]作为复位信号 iic->cmd = 0x1 << 31; // 设置频率公式: division = clock/2/baud - 1; iic->div = SYS_CLOCK/2/freq - 1; // 设置到iic iic->cmd = 0x1 << 0; } static inline void __start(void) { iic->cmd = 0x1 << 1; // 等待完成 while(iic->status >> 3 & 0x1); } static inline void __stop(void) { iic->cmd = 0x1 << 6; // 等待完成 while(iic->status >> 6 & 0x1); } static inline bool __write(uint8_t data) { // 锁存数据到iic缓存区并且发送数据到iic总线 iic->wdata = data; iic->cmd = (0x1<<2) | (0x1<<3); // 等待完成 while(iic->status >> 4 & 0x1); return (bool) (iic->status >> 2 & 0x1); } static inline uint8_t __read(bool noack) { // 读取iic总线 iic->cmd = ((uint32_t)noack << 4) | (0x1 << 5); // 等待完成 while(iic->status >> 5 & 0x1); return (uint8_t) iic->rdata; } bool iic_check(uint8_t addr) { bool ack; // 发送开始信号 + 锁存数据到iic缓存区 + 发送数据到iic总线 iic->wdata = (addr << 1) | 0x0; iic->cmd = (0x1<<1) | (0x1<<2) | (0x1<<3); // 等待完成(只需等待最后时序的状态位即可) while(iic->status >> 4 & 0x1); // 读取是否有ACK ack = iic->status >> 2 & 0x1; // 发送结束信号并等待完成 iic->cmd = 0x1 << 6; while(iic->status >> 6 & 0x1); return ack; } int iic_write(uint8_t addr, uint8_t byte) { // 发送开始信号 + 锁存数据到iic缓存区 + 发送数据到iic总线 iic->wdata = (addr << 1) | 0x0; iic->cmd = (0x1<<1) | (0x1<<2) | (0x1<<3); // 等待完成(只需等待最后时序的状态位即可) while(iic->status >> 4 & 0x1); // 读取是否有ACK if (!(iic->status >> 2 & 0x1)) { // 发送结束信号并等待完成 iic->cmd = 0x1 << 6; while(iic->status >> 6 & 0x1); return -1; } // 锁存数据到iic缓存区 + 发送数据到iic总线 + 发送iic停止信号 iic->wdata = byte; iic->cmd = (0x1<<2) | (0x1<<3) | (0x1<<6); // 等待完成(只需等待最后时序的状态位即可) while(iic->status >> 6 & 0x1); // 读取是否有ACK if (!(iic->status >> 2 & 0x1)) return -2; return 0; } int iic_read(uint8_t addr, uint8_t *byte) { // 发送开始信号 + 锁存数据到iic缓存区 + 发送数据到iic总线 iic->wdata = (addr << 1) | 0x1; iic->cmd = (0x1<<1) | (0x1<<2) | (0x1<<3); // 等待完成(只需等待最后时序的状态位即可) while(iic->status >> 4 & 0x1); // 读取是否有ACK if (!(iic->status >> 2 & 0x1)) { // 发送结束信号并等待完成 iic->cmd = 0x1 << 6; while(iic->status >> 6 & 0x1); return -1; } // 发送读总线时序并且读完不发送ACK + 发送iic停止信号 iic->cmd = (0x1 << 4) | (0x1 << 5) | (0x1<<6); // 等待完成 while(iic->status >> 6 & 0x1); *byte = (uint8_t) iic->rdata; return 0; } int iic_read_buffer(uint8_t addr, uint8_t in_addr, uint8_t *buffer, int size) { int i; // 寻址, iic写模式 __start(); if (!__write((addr << 1) | 0x0)) { __stop(); return -1; } // 写片内地址 if (!__write(in_addr)) { __stop(); return -2; } // 再次寻址, iic读模式 __start(); if (!__write((addr << 1) | 0x1)) { __stop(); return -3; } // 读总线, 读完不发ACK for (i=0; i Case测试 首先需要初始化iic,复位并配置时钟:
iic_init(400000); // 频率为400KHz单次测试写(无片内地址)函数调用:
iic_write(0x50, 0xAA)抓取的时序:
结论:OK
单次读测试(无片内地址)函数调用:
iic_read(0x50, &byte); uart_printf("byte = %02Xrn", byte);抓取的时序:
串口输出:
byte = A0结论:OK
单次测试写(有片内地址)函数调用:
iic_write_byte(0x50, 0x00, 0xAA);抓取的时序:
结论:OK
单次读测试(有片内地址)函数调用:
iic_read_byte(0x50, 0x00, &byte); uart_printf("byte = %02Xrn", byte);抓取的时序:
串口输出:
byte = AA结论: OK
寻址扫描函数调用:
static void iic_scan(void) { int i, j; uart_printf(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0Frn"); for (i=0; i<8; i++) { uart_printf("%02X ", i<<4); for (j=0; j<=0xF; j++) { if (iic_check(i<<4 | j)) uart_printf("%02X ", i<<4 | j); else uart_printf("-- "); } uart_printf("rn"); } }串口输出:
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 10 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30 30 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --结论:OK,已经扫描到我IIC上挂的EEPROM和OV2640了
尾言目前验证暂且无问题,并且测试过EEPROM、OV2640读写都正常,已经满足我目前的需要。当然时序上还有许多可以优化的地方,以后有时间再折腾吧。



