栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

使用Verilog设计I2C控制器

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

使用Verilog设计I2C控制器

背景

最近想用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读写都正常,已经满足我目前的需要。当然时序上还有许多可以优化的地方,以后有时间再折腾吧。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/849114.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号