使用Verilog设计I2C控制器

5 篇文章 0 订阅
4 篇文章 4 订阅

背景

最近想用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实现

/**
 * qinhuqiang @2022-3-20
 * 后续需要考虑到的问题:
 *  (2) DMA 自动传输
 *  (3) 考虑10bit iic器件地址
 *  (4) 增加slave模式
 */

module iic (
    inout wire              iic_sda,        /* serial date line */
    inout wire              iic_scl,        /* serial clock line */
    input wire              mod_rst,        /* i2c module reset signal */
    input wire              mod_clk,        /* i2c module clock signal */
    input wire [15:0]       mod_div,        /* iic dividing frequency signal */
    output wire [7:0]       mod_sta,        /* iic status signal */
    /*
     * mod_sta [only read] 位分布:
     *  [0]: 1:iic总线空闲          0:iic总线忙
     *  [1]: 1:slave被寻址          0:slave没有被寻址
     *  [2]: 1:收到ACK              0:没有收到ACK
     *  [3]: 1:mod_cmd[1]未完成     0:mod_cmd[1]操作完成
     *  [4]: 1:mod_cmd[3]未完成     0:mod_cmd[3]操作完成
     *  [5]: 1:mod_cmd[5]未完成     0:mod_cmd[5]操作完成
     *  [6]: 1:mod_cmd[6]未完成     0:mod_cmd[6]操作完成
     */
    input wire [7:0]        mod_wdata,      /* iic write date */
    output wire [7:0]       mod_rdata,      /* iic read date */
    input wire [7:0]        mod_cmd         /* iic command input */
    /*
     * mod_cmd [only write] 位分布:
     *  [0]: 0->1 跳变时将mod_div设置到reg_div
     *  [1]: 0->1 跳变时发送start信号, 开始占用总线 (自动从slave模式切换到master模式)
     *  [2]: 0->1 跳变时将mod_wdata内容锁存到移位寄存器
     *  [3]: 0->1 跳变时将产生9bit写总线时钟 (8bit 数据 + 1bit ACK)
     *  [4]: 0->1 跳变时, 设置读总线时, stop信号前不需要发送ACK (master模式和slave模式都可用)
     *  [5]: 0->1 跳变时将产生9bit读总线时钟 (8bit 数据 + 1bit ACK)
     *  [6]: 0->1 跳变时发送stop信号, 释放总线 (自动从master模式切换到slave模式)
     */
);

/* iic输出参考状态, iic标准是高阻态, 如果设置wei1, 就只是为了调试方便, 仅此而已 */
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

/* 当前iic模式 */
reg reg_iic_mode;
localparam IIC_MODE_SLAVE = 1'b0;         /* slave模式 */
localparam IIC_MODE_MASTER = 1'b1;        /* master模式 */

/* 构建iic master时钟 */
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

/* iic master状态机 */
reg [3:0] reg_iic_state;
localparam IIC_STATE_HSCL = 4'd0;         /* 发送停止位2, 拉高SCL */
localparam IIC_STATE_LALL = 4'd1;         /* 发送停止位1, 俩跟线拉低 */
localparam IIC_STATE_ACK  = 4'd2;         /* 操作ACK位 */
localparam IIC_STATE_BIT0 = 4'd3;         /* 操作第0位 */
localparam IIC_STATE_BIT1 = 4'd4;         /* 操作第1位 */
localparam IIC_STATE_BIT2 = 4'd5;         /* 操作第2位 */
localparam IIC_STATE_BIT3 = 4'd6;         /* 操作第3位 */
localparam IIC_STATE_BIT4 = 4'd7;         /* 操作第4位 */
localparam IIC_STATE_BIT5 = 4'd8;         /* 操作第5位 */
localparam IIC_STATE_BIT6 = 4'd9;         /* 操作第6位 */
localparam IIC_STATE_BIT7 = 4'd10;        /* 操作第7位 */
localparam IIC_STATE_LSDA = 4'd11;        /* 发送开始位2, 拉低SDA */
localparam IIC_STATE_HALL = 4'd12;        /* 发送开始位1, 俩根线拉高 */

/* 同步到iic时钟: iic start 触发信号 */
reg reg_cmd_start;
wire iic_sta_start;
wire iic_start_zone;  /* start时间段 */
reg reg_start_done;   /* start是否发送完成 */
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

/* 同步到iic时钟: iic ack 设置信号 */
reg reg_cmd_ack;
wire iic_sta_ack;
wire iic_ack_zone;  /* ack时间段 */
reg reg_ack_done;   /* ack是否发送完成 */
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

/* 同步到iic时钟: iic 9bit 写总线 触发信号 */
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

/* 同步到iic时钟: iic 9bit 读总线 触发信号 */
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

/* 同步到iic时钟: iic stop 触发信号 */
reg reg_cmd_stop;
wire iic_sta_stop;
wire iic_stop_zone;  /* stop时间段 */
reg reg_stop_done;   /* stop是否发送完成 */
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;        /* 开始产生9bit读/写总线时钟 */
    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);

/* 处理sda线 */
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;

/* 读总线, iic从总线读数据操作, 如果是读模式, 读完之后[7:0]是有效数据, 如果是写模式, 写完后[0]是有效ack位 */
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;

/* 写总线, 锁存数据到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

/* 写总线, sda选择输出 */
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

/* 处理iic_scl线, master模式时输出时钟 */
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;

/* 写总线, scl选择输出 */
always @(*) begin
    case (reg_iic_state)
        IIC_STATE_HSCL: iic_scl_out = 1'b1;
        IIC_STATE_LALL: iic_scl_out = reg_master_scl;   /* L -> H */
        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;

/* 处理mod_sta线 */
assign mod_sta = {
    1'b0,                       /* 保留, 未使用 */
    reg_cmd_stop,               /* mod_cmd[6] */
    reg_cmd_rd,                 /* mod_cmd[5] */
    reg_cmd_wd,                 /* mod_cmd[3] */
    reg_cmd_start,              /* mod_cmd[1] */
    !reg_r_buffer[0],           /* ack */
    1'b0,                       /* 暂时不支持slave模式 */
    iic_bus_idle
};

/* 调整iic模式, 复位时默认为slave模式, 接收地址0x00寻址 */
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

/**************** 用于FPGA zynq调试使用, 因为有inout线, 所以放在模块里边来例化, 避免报错误 ******************/
/* 软件仿真或者实际使用时必须去掉 *//*
ila_0 ila0_inst (
    .clk            (mod_clk),
    .probe0         (iic_sda),
    .probe1         (iic_scl),
    .probe2         (mod_rst),
    .probe3         (reg_master_scl),
    .probe4         (mod_div),
    .probe5         (mod_sta),
    .probe6         (mod_wdata),
    .probe7         (mod_rdata),
    .probe8         (mod_cmd),
    .probe9         (reg_iic_state),
    .probe10        (reg_iic_mode),
    .probe11        (reg_ack_done)
);*/

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

 实现代码:

/*
 * iic.c
 *
 *  Created on: 2022年4月20日
 *      Author: HuXiang
 */

#include <stdint.h>
#include <stdbool.h>
#include "sys.h"

/**
 * 寄存器分布:
 * 	iic_div[15:0];
 * 	iic_state[7:0];
 * 	iic_wdata[7:0];
 * 	iic_rdata[7:0];
 * 	iic_cmd[7:0];
 */

/**
 * state [only read] 位分布:
 *  [0]: 1:iic总线空闲          0:iic总线忙
 *  [1]: 1:slave被寻址          0:slave没有被寻址
 *  [2]: 1:收到ACK              0:没有收到ACK
 *  [3]: 1:mod_cmd[1]未完成     0:mod_cmd[1]操作完成
 *  [4]: 1:mod_cmd[3]未完成     0:mod_cmd[3]操作完成
 *  [5]: 1:mod_cmd[5]未完成     0:mod_cmd[5]操作完成
 *  [6]: 1:mod_cmd[6]未完成     0:mod_cmd[6]操作完成
 */

/**
 * cmd [only write] 位分布:
 *  [0]: 0->1 跳变时将mod_div设置到reg_div
 *  [1]: 0->1 跳变时发送start信号, 开始占用总线 (自动从slave模式切换到master模式)
 *  [2]: 0->1 跳变时将mod_wdata内容锁存到移位寄存器
 *  [3]: 0->1 跳变时将产生9bit写总线时钟 (8bit 数据 + 1bit ACK)
 *  [4]: 0->1 跳变时, 设置读总线时, stop信号前不需要发送ACK (master模式和slave模式都可用)
 *  [5]: 0->1 跳变时将产生9bit读总线时钟 (8bit 数据 + 1bit ACK)
 *  [6]: 0->1 跳变时发送stop信号, 释放总线 (自动从master模式切换到slave模式)
 */

typedef struct {
	uint32_t div;
	uint32_t status;
	uint32_t wdata;
	uint32_t rdata;
	uint32_t cmd;
} IIC;

/* IIC控制器 */
static volatile IIC *iic = (IIC *)0x43C10000;

/**
 * @brief 初始化iic
 * 
 * @param freq 配置iic时钟频率
 */
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;
}

/**
 * @brief 触发iic开始信号时序
 * 
 */
static inline void __start(void)
{
	iic->cmd = 0x1 << 1;
	// 等待完成
	while(iic->status >> 3 & 0x1);
}

/**
 * @brief 触发iic停止信号时序
 * 
 */
static inline void __stop(void)
{
	iic->cmd = 0x1 << 6;
	// 等待完成
	while(iic->status >> 6 & 0x1);
}

/**
 * @brief 触发向iic总线写数据时序
 * 
 * @param data 8bit数据
 * @return true 写完后收到ACK
 * @return false 写完后没有收到ACK
 */
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);
}

/**
 * @brief 触发从iic总线读数据时序
 * 
 * @param noack 读完收据后不发送ACK
 * @return uint8_t 读到的数据
 */
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;
}

/**
 * @brief 检查addr处是否有器件
 * 
 * @param addr 器件地址
 * @return true 有ACK响应
 * @return false 无ACK响应
 */
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;
}

/**
 * @brief 写一个字节到iic器件
 * 
 * @param addr iic器件地址
 * @param byte 需要写入的数据
 * @return int -1:器件寻址失败  -2:写入数据失败  0:成功
 */
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;
}

/**
 * @brief 读一个字节到iic器件
 * 
 * @param addr iic器件地址
 * @param byte 读到的数据
 * @return int -1:器件寻址失败  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;
}

/**
 * @brief 读数据到buffer
 * 
 * @param addr 器件地址
 * @param in_addr 片内地址
 * @param buffer 数据缓存区
 * @param size 需要读取的大小
 * @return int 0:成功  !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<size; i++) {
		buffer[i] = __read((bool)(i+1 == size));
	}

	__stop();
	return 0;
}

/**
 * @brief 写数据到buffer
 * 
 * @param addr 器件地址
 * @param in_addr 片内地址
 * @param buffer 数据缓存区
 * @param size 需要读取的大小
 * @return int 0:成功  !0:失败
 */
int iic_write_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;
	}

	// 写数据
	for (i=0; i<size; i++) {
		if (!__write(buffer[i])) {
			__stop();
			return -3;
		}
	}

	__stop();
	return 0;
}

/**
 * @brief 从iic器件读单个byte
 * 
 * @param addr 器件地址
 * @param in_addr 片内地址
 * @param byte 读取的数据
 * @return int 0:成功  !0:失败
 */
int iic_read_byte(uint8_t addr, uint8_t in_addr, uint8_t *byte)
{
	// 寻址, 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
	*byte = __read(true);

	__stop();
	return 0;
}

/**
 * @brief 写单个byte到iic器件
 * 
 * @param addr 器件地址
 * @param in_addr 片内地址
 * @param byte 发送的数据
 * @return int 0:成功  !0:失败
 */
int iic_write_byte(uint8_t addr, uint8_t in_addr, uint8_t byte)
{
	// 寻址, iic写模式
	__start();
	if (!__write((addr << 1) | 0x0)) {
		__stop();
		return -1;
	}

	// 写片内地址
	if (!__write(in_addr)) {
		__stop();
		return -2;
	}

	// 写数据
	if (!__write(byte)) {
		__stop();
		return -3;
	}

	__stop();
	return 0;
}

Case测试

首先需要初始化iic,复位并配置时钟:

iic_init(400000); // 频率为400KHz

单次测试写(无片内地址)

函数调用:

iic_write(0x50, 0xAA)

抓取的时序:

结论:OK

单次读测试(无片内地址)

函数调用:

iic_read(0x50, &byte);
uart_printf("byte = %02X\r\n", byte);

抓取的时序:

串口输出:

byte = A0

结论:OK

单次测试写(有片内地址)

函数调用:

iic_write_byte(0x50, 0x00, 0xAA);

抓取的时序:

结论:OK

单次读测试(有片内地址)

函数调用:

iic_read_byte(0x50, 0x00, &byte);
uart_printf("byte = %02X\r\n", byte);

抓取的时序:

串口输出:

byte = AA

结论: OK

寻址扫描

函数调用:

/**
 * @brief 扫描器件
 * 
 * @note 0x30地址是ov2640地址
 * @note 0x50地址是eeprom地址
 */
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 0F\r\n");
	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("\r\n");
	}
}

串口输出:

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

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值