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