s3c2440裸机编程-I2C

1 I2C原理#

1.1 硬件电路#

I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。

img

SDA(串行数据线)和SCL(串行时钟线)都是双向I/O线,需通过上拉电阻接电源VCC.当总线空闲时.两根线都是高电平。

I2C 总线标准模式下速度可以达到 100Kb/S,快速模式下可以达到 400Kb/S。SDA 和 SCL 这两根线必须要接一个上拉电阻,一般是 4.7K。

1.2 i2c协议#

传输过程如下:

img

1
2
3
4
5
6
7
8
1. 主控发送start讯号(S)
2. 主控发送从设备地址(slave dev addr)
3. 主控发送方向(W/R)
4. 从设备应答(ack)
5. 主控(or从设备)发送数据(data)
6. 从设备(or主控)应答(ack)
...
7. 主控发送停止信号(P)

s3c2440 一次i2c读写过程:

img

1.2.1 S/P信号#

img

start信号:SCL是高电平,SDA被主控拉低。
stop信号:SCL是高电平,SDA被主控拉高。

示波器测量出start信号:
img

示波器测量出stop信号:
img

1.2.2 ACK信号#

img

第9个时钟周期,SDA被拉低表示ack讯号。

1.2.3 DATA格式#

img

用 9个clk传输8bit数据(7bit 从设备地址 + 1bit方向 ),MSB高位先出。第9个clk是ack讯号。

1.2.4 数据有效性#

SDA 线上的数据必须在SCL高电平周期保持稳定,在 SCL 低电平时才能允许改变

1.3 一次完整的I2C数据传输举例#

img

  1. 主控发送了S信号;
  2. 发送地址0x34,包含读写位;
  3. 发送数据0x30, 0x00, 0x01共3个字节数据;
  4. 最后SDA被拉高发送P信号。

这里我是用了带I2C解码的示波器,能将I2C协议解码出来方便调试者阅读分析。

1.4 一条SDA上实现双向传输的原理#

电路设计内部结构使用开极电路。如下图:

img

条件

  1. 主设备发送时,从设备不发送(通过SCL控制即可,比如让前8个clk主控发送数据到SDA,让第9个clk从设备发送数据到SDA)

  2. 主设备发送数据时,从设备的“发送引脚”不能影响SDA数据。反之,从设备发送数据时,主设备的”发送引脚”不能影响到SDA数据。那么如何做到?

    1
    SDA内部电路用三极管,开集电路,原理如下图:

    img

    从上图知道:

    1. 当A,B都为低电平时,三极管不导通,SDA的电平取决于外部电路,这里SDA有上拉电阻,所以对应高电平;

    2. 当主控拉高A时,三极管导通,此时SDA接地,电平被拉低

    3. 同理,当从设备拉高B时,三极管导通,此时SDA接地,电平被拉低

那么电平真值表如下:

img

所以,要实现双向传输:

1
2
3
如果要master-> slave进行数据传输,那么让主控驱动三极管,拉低SDA。
如果要slave-> master进行数据传输,那么让从设备驱动三极管,拉低SDA。
否则,都不驱动三极管,SDA一直输出高电平,处于idle状态。

从下面的例子可以看看数据是怎么传的(实现双向传输)。

举例:主设备发送(8bit)给从设备:

1
2
3
4
5
6
8 个 clk
◼ 从设备不要影响 SDA,从设备不驱动三极管
◼ 主设备决定数据,主设备要发送 1 时不驱动三极管,要发送 0 时驱动三极管
9 个 clk,由从设备决定数据
◼ 主设备不驱动三极管
◼ 从设备决定数据,要发出回应信号的话,就驱动三极管让 SDA 变为 0

从这里也可以知道 ACK 信号是低电平从上面的例子,就可以知道怎样在一条线上实现双向传输,这就是 SDA 上要使用上拉电阻的原因。

为何 SCL 也要使用上拉电阻?在第 9 个时钟之后,如果有某一方需要更多的时间来处理数据,它可以一直驱动三极管把 SCL 拉低。

当 SCL 为低电平时候,大家都不应该使用 IIC 总线,只有当 SCL 从低电平变为高电平的时候,IIC 总线才能被使用。当它就绪后,就可以不再驱动三极管,这是上拉电阻把 SCL 变为高电平,其他设备就可以继续使用 I2C 总线了。

1.5 SCL被从设备拉低表示busy状态#

img

第9个clk 后i2c会产生中断,此时SCL被拉低,表示busy状态,表示谁都不允许再使用i2c, 然后等到中断处理结束了,也就是处于idle状态了,此时会释放出SCL,那么主控可以继续发送SCL讯号表示可以继续进行i2c通信了。

2 I2C控制器#

2.1 I2c主从设备关系#

img

对于写操作,主控作为transmitter,从设备作为receiver。
对于读操作,主控作为receiver, 从设备作为transmitter。

2.2 s3c2440 I2C控制器#

2.2.1 控制器框图#

img

1
2
3
4
5
6
7
8
9
10
11
Pclk = 50Mhz, 经过prescaler分频,可以得到SCL。

IICSTAT: 发出S(start)信号或者P(stop)信号。

Data Bus可以把数据写入IICDS寄存器,然后会自动产生SCL,并且会将8位数据从SDA同步给slave dev,

在数据发送出去后,在第9个SCL时钟,会受到slave dev的ack应答,可以通过查询IICSTAT来判断是否有ACK回应。

当slave dev回应ACK后,那么又可以继续发送数据,继续写入据到IICDS。

当主控想结束,设置IICSTAT发出P信号。

2.2.2 寄存器介绍#

2.2.2.1 IICCON-时钟配置#

img

1
2
3
4
5
6
7
8
9
Bit[7]: 对于发送模式,不需要配置ack信号,ack是接收者发送回来的应答。对于接受模式,设置成1,让它在第9个CLK发出ack讯号(拉低sda)。

Bit[6]:SCL时钟源,pclk分频即可

Bit[5]:中断使能,使用i2c时要去enable

Bit[4]:中断状态标识 表示中断有没有结束,当该bit读出来是1时,SCL被拉低表示busy,也就是i2c中断还在处理中。当i2c中断处理结束后,可以将该bit 清0,释放出SCL。

Bit[3:0]:i2c时钟分频系数配置,SCL时钟 = IICCLK/(IICCON[3:0]+1)

2.2.2.2 IICSTAT-模式配置#

img

1
2
3
4
5
6
7
8
9
bit[7:6]:模式选择

Bit[5]:当读的时候,0表示not busy,1表示busy, 当写的时候,0表示写入STOP, 1表示写入START

Bit[4] : 数据输出使能,0:表示disable, 1表示enable

Bit[3]:仲裁flag

Bit[0]:表示i2c总线上的第9个时钟周期有没有ack,1表示有ack, 0表示无ack

2.2.2.3 IICADD-从机地址配置#

img

2.2.2.4 IICDS-数据寄存器#

img

3 I2C读写操作流程#

The following steps must be executed before any IIC Tx/Rx operations.

  1. Write own slave address on IICADD register, if needed.
  2. Set IICCON register.
    1. Enable interrupt
    2. Define SCL period
  3. Set IICSTAT to enable Serial Output

在操作tx,rx前,要先执行以下几步骤:

  1. IICADD写入从设备地址
  2. 设置IICCON,设置时钟,使能中断
  3. 设置IICSTAT,使能传输

3.1 I2C操作模式#

The S3C2440A IIC-bus interface has four operation modes:
— Master transmitter mode
— Master receive mode
— Slave transmitter mode
— Slave receive mode

3.1.1 主发Master/Transmitter Mode#

img

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 配置成master tx mode(也就是IICSTAT[7:6]配置成11)
2. 把从设备地址写入IICDS,(第一次传输地址)
3. IICSTAT写入0xF0(使能传输,发S信号,使能tx/rx)
3. IICDS中配置的数据(从设备地址7bit + 读写位1bit)就被发送出去了(每传输完一个数据将产生一个中断)
5. 判断第9个clk从设备是否有ack
5.1 如果从设备有ack,恢复i2c传输
IICDS = buf
Clear pending bit
数据被发送出去,继续i2c传输
5.2 如果没有ack, stop,返回错误
IICSTAT = 0xd0
Clear pending bit(IICCON[4])
Delay一会儿等待停止条件生效

3.1.2 主收Master/Receiver Mode#

img

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 配置成master rx mode(也就是IICSTAT[7:6]配置成10)
2. 把从设备地址写入IICDS,(第一次传输地址)
3. IICSTAT写入0xB0(使能传输)
4. IICDS中配置的数据(从设备地址7bit + 读写位1bit)就被发送出去了(每传输完一个数据将产生一个中断)
5. 判断第9个clk从设备是否有ack
5.1 如果从设备有ack,恢复i2c传输
Buf = IICDS
Clear pending bit
数据被接受到,继续i2c传输
5.2 如果没有ack, stop,返回错误
IICSTAT = 0x90
Clear pending bit
Delay一会儿

3.1.3 从发Slave/Transmitter Mode#

img

3.1.4 从收Slave/Receiver Mode#

img

4 I2C程序示例#

4.1 I2C从设备介绍#

IIC控制器只提供了传输数据的能力,至于数据有什么含义,IIC控制器并不知道,数据的含义有外部i2c从设备,我们需要阅读芯片手册,才知道IIC控制器应该发出怎样的数据。

4.1.1 AT24CXX EEPROM#

AT24Cxx系列EEPROM是由美国Mcrochip公司出品,1-512K位的支持I2C总线数据传送协议的串行CMOS E2PROM。I2c传输规则如下:

img

4.2 程序框架#

我们的程序应该分为两层(IIC设备层,IIC控制器层),框架如下图所示:

img

1
2
3
4
5
6
7
最上层是i2c_test层,用来对i2c的功能进行测试和验证。

第2层是i2c设备层,用来对具体某一型号的从设备进行i2c读写。

第3层是通用i2c控制器层,用来提供对具体某一型号的i2c主控进行管理操作。

最底层是i2c控制器具体的型号层。

在通用i2c控制层,我们提供一个统一的接口i2c_transfer,不关使用哪个芯片,他最终都会调用i2c_transfer,来选择某一款I2C控制器,把数据发送出去,或者从I2c设备读到数据。这种层次分明的架构是作为软件开发人员必备的素养和技能。这里也是借鉴了linux内核I2C子系统的模型。

4.2.1 i2c_msg结构体#

我们借鉴Linux I2C子系统的数据结构定义。对于每一次传输的数据都可以用一个i2c_msg结构体来表示。但是,读某个地址的数据时,就要用两个i2c_msg结构体来描述它,因为一个i2c_msg结构体只能描述一个传输方向(读/写),我们读取ac24ccxx某个地址上的数据时,要先写出要读取的地址,然后来读取设备地址上的数据。

img

4.2.2 i2c_test.c#

1
2
3
4
void i2c_test(void) {
/* 初始化: 选择I2C控制器 */
/* 提供菜单供测试 */
}

这个菜单会调用到at24cxx.c里面的函数进行i2c外设读写。

4.2.3 at24cxx.c#

定义描述at24cxx外设,并且实现该外设的操作,里面会使用标准的接口i2c_transfer来启动I2C传输。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#define AT24CXX_ADDR 0x50
int at24cxx_write(unsigned int addr, unsigned char *data, int len) {
i2c_msg msg;
int i;
int err;
unsigned char buf[2];

for (i = 0; i < len; i++) {
buf[0] = addr++;
buf[1] = data[i];
/* 构造i2c_msg */
msg.addr = AT24CXX_ADDR;
msg.flags = 0; /* write */
msg.len = 2;
msg.buf = buf;
msg.err = 0;
msg.cnt_transferred = -1;
/* 调用i2c_transfer */
err = i2c_transfer(&msg, 1);
if (err)
return err;
}
return 0;
}

int at24cxx_read(unsigned int addr, unsigned char *data, int len) {
i2c_msg msg[2];
int err;
/* 构造i2c_msg */
msg[0].addr = AT24CXX_ADDR;
msg[0].flags = 0; /* write */
msg[0].len = 1;
msg[0].buf = &addr;
msg[0].err = 0;
msg[0].cnt_transferred = -1;
msg[1].addr = AT24CXX_ADDR;
msg[1].lags = 1; /* read */
msg[1].len = len;
msg[1].buf = data;
msg[1].err = 0;
msg[1].cnt_transferred = -1;
/* 调用i2c_transfer */
err = i2c_transfer(&msg, 2);
if (err)
return err;
return 0;
}

4.2.4 i2c_controller.h#

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct i2c_msg {
unsigned int addr; /* 7bits */
int flags; /* 0 - write, 1 - read */
int len;
int cnt_transferred;
unsigned char *buf;
}i2c_msg, *p_i2c_msg;
typedef struct i2c_controller {
int (*int)(void);
int (*master_xfer)(i2c_msg msgs, int num);
char *name;
}i2c_controller, *p_i2c_controller;

构造i2c_msg和i2c_controller结构。

4.2.5 i2c_controller.c#

实现通用i2c控制器管理,用来注册具体i2c控制器,调用具体控制器去做i2c通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#define I2C_CONTROLLER_NUM 10
/* 有一个i2c_controller数组用来存放各种不同芯片的操作结构体 */
static p_i2c_controller p_i2c_controllers[I2C_CONTROLLER_NUM];
static p_i2c_controller p_i2c_con_selected;

void register_i2c_controller(p_i2c_controller *p) {
int i;
for (i = 0; i < I2C_CONTROLLER_NUM; i++) {
if (!p_i2c_controllers[i]) {
p_i2c_controllers[i] = p;
return;
}
}
}

/* 根据名字来选择某款I2C控制器 */
int select_i2c_controller(char *name) {
int i;
for (i = 0; i < I2C_CONTROLLER_NUM; i++)
{
if (p_i2c_controllers[i] && !strcmp(name, p_i2c_controllers[i]->name))
{
p_i2c_con_selected = p_i2c_controllers[i];
return 0;
}
}
return -1;
}

/* 实现 i2c_transfer 接口函数 */
int i2c_transfer(i2c_msg msgs, int num) {
return p_i2c_con_selected->master_xfer(msgs, num);
}

void i2c_init(void) {
/* 注册下面的I2C控制器 */
s3c2440_i2c_con_add();
/* 选择某款I2C控制器 */
select_i2c_controller("s3c2440");
/* 调用它的init函数 */
p_i2c_con_selected->init();
}

4.2.6 s3c2440_i2c_controller.c#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
void i2c_interrupt_func(int irq) {
/* 每传输完一个数据将产生一个中断 */
/* 对于每次传输, 第1个中断是"已经发出了设备地址" */
}

void s3c2440_i2c_con_init(void)
{
/* 配置引脚用于I2C*/
GPECON &= ~((3<<28) | (3<<30));
GPECON |= ((2<<28) | (2<<30));

/* 设置时钟 */
/* [7] : IIC-bus acknowledge enable bit, 1-enable in rx mode
* [6] : 时钟源, 0: IICCLK = fPCLK /16; 1: IICCLK = fPCLK /512
* [5] : 1-enable interrupt
* [4] : 读出为1时表示中断发生了, 写入0来清除并恢复I2C操作
* [3:0] : Tx clock = IICCLK/(IICCON[3:0]+1).
* Tx Clock = 100khz = 50Mhz/16/(IICCON[3:0]+1)
*/
IICCON = (1<<7) | (0<<6) | (1<<5) | (30<<0);

/* 注册中断处理函数 */
register_irq(27, i2c_interrupt_func);
}

int do_master_tx(p_i2c_msg msg)
{
p_cur_msg = msg;
msg->cnt_transferred = -1;
msg->err = 0;

/* 设置寄存器启动传输 */
/* 1. 配置为 master tx mode */
IICCON |= (1<<7); /* TX mode, 在ACK周期释放SDA */
IICSTAT = (1<<4); /*IIC-bus data output enable/disable(1: Enable Rx/Tx)*/

/* 2. 把从设备地址写入IICDS */
IICDS = msg->addr<<1;//[slave addr [7:1], addr[0] is trans dir]

/* 3. IICSTAT = 0xf0 (启动传输), slave addr数据即被发送出去,当到达第9个clk,无论是否有ack, 将导致中断产生 */
IICSTAT = 0xf0;

/* 后续的传输由中断驱动 */
/* 循环等待中断处理完毕 */
while (!msg->err && msg->cnt_transferred != msg->len);
if (msg->err)
return -1;
else
return 0;
}

int do_master_rx(p_i2c_msg msg)
{
p_cur_msg = msg;
msg->cnt_transferred = -1;
msg->err = 0;

/* 设置寄存器启动传输 */
/* 1. 配置为 Master Rx mode */
IICCON |= (1<<7); /* RX mode, 在ACK周期回应ACK */
IICSTAT = (1<<4); /*IIC-bus data output enable/disable*/

/* 2. 把从设备地址写入IICDS */
IICDS = (msg->addr<<1)|(1<<0);

/* 3. IICSTAT = 0xb0 , 从设备地址即被发送出去, 将导致中断产生 */
IICSTAT = 0xb0;
/* 后续的传输由中断驱动 */
/* 循环等待中断处理完毕 */
while (!msg->err && msg->cnt_transferred != msg->len);
if (msg->err)
return -1;
else
return 0;
}

int s3c2440_master_xfer(p_i2c_msg msgs, int num)
{
int i;
int err;
for (i = 0; i < num; i++)
{
if (msgs[i].flags == 0)/* write */
err = do_master_tx(&msgs[i]);
else
err = do_master_rx(&msgs[i]);
if (err)
return err;
}
return 0;
}

void s3c2440_i2c_con_add(void)
{
register_i2c_controller(&s3c2440_i2c_con);
}
static i2c_controller s3c2440_i2c_con = {
.name = "s3c2440",
.init = s3c2440_i2c_con_init,
.master_xfer = s3c2440_master_xfer,
};

s3c2440_i2c_con_add函数:注册 s3c2440的i2c控制器, 当调用i2c_init就会对选中的这款控制器初始化,也就是调用s3c2440_i2c_con_init。

s3c2440_i2c_con_init函数:

1
2
1).IICCON = (0<<6) | (1<<5) | (30<<0); 设置IICCON控制寄存器。选择发送时钟,使能中断。设置ACK应答使能,bit[7]。
2).register_irq(27, i2c_interrupt_func):注册中断处理函数,当发生I2C中断的时候就会调用i2c_interrupt_func中断处理函数。

s3c2440_master_xfer函数:

当发起i2c传输时,调用i2c_transfer,进而调用s3c2440_master_xfer进行数据传输。写的话do_master_tx,读的话do_master_rx。

do_master_rx函数:

1
IICDS = (msg->addr<<1)|(1<<0):把从设备地址写入IICDS,前7位是从机地址,第8位表示传输方向(0表示写操作,1表示读操作)。

do_master_tx函数:

1
2
1. IICDS = msg->addr<<1: 把从机地址(高7位,所以需要向右移一位)写入到IICDS寄存器中。
2. IICSTAT = 0xf0:设置IICSTAT寄存器,将s3c2440设为主机发送器,并发出S信号后,紧接着就发出从机地址。后续的传输工作将在中断服务程序中完成。

4.3 程序框架总结#

对应程序框架的4层架构。

img

4.4 I2C中断服务程序#

Start信号之后,发出设备地址,在第9个时钟就会产生第一个中断,我们根据i2c的流程图来编写中断程序。每传输完一个数据将又产生一个中断,I2C操作的主体在中断服务程序,它可以分为两部分:写操作,读操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
static p_i2c_msg p_cur_msg;

int isLastData(void)
{
if (p_cur_msg->cnt_transferred == p_cur_msg->len - 1)
return 1; /* 正要开始传输最后一个数据 */
else
return 0;
}

void resume_iic_with_ack(void)
{
unsigned int iiccon = IICCON;
iiccon |= (1<<7); /* 回应ACK */
iiccon &= ~(1<<4); /* 恢复IIC操作 */
IICCON = iiccon;
}

void resume_iic_without_ack(void)
{
unsigned int iiccon = IICCON;
iiccon &= ~((1<<7) | (1<<4)); /* 不回应ACK, 恢复IIC操作 */
IICCON = iiccon;
}

void i2c_interrupt_func(int irq)
{
int index;
unsigned int iicstat = IICSTAT;
unsigned int iiccon;

//printf("i2c_interrupt_func! flags = %d\n\r", p_cur_msg->flags);

p_cur_msg->cnt_transferred++;

/* 每传输完一个数据将产生一个中断 */

/* 对于每次传输, 第1个中断是"已经发出了设备地址" */

if (p_cur_msg->flags == 0) {//write
/* 对于第1个中断, 它是发送出设备地址后产生的
* 需要判断是否有ACK
* 有ACK : 设备存在
* 无ACK : 无设备, 出错, 直接结束传输
*/
if (p_cur_msg->cnt_transferred == 0) { /* 第1次中断 */
if (iicstat & (1<<0)) {/*iicstat [0] == 1表示no ack*/
/* no ack */
/* 停止传输 */
IICSTAT = 0xd0;
IICCON &= ~(1<<4); //clear pending bit
p_cur_msg->err = -1;
printf("tx err, no ack\n\r");
delay(1000);
return;
}
}
if (p_cur_msg->cnt_transferred < p_cur_msg->len) {
/* 对于其他中断, 要继续发送下一个数据
*/
IICDS = p_cur_msg->buf[p_cur_msg->cnt_transferred];
IICCON &= ~(1<<4);//clear pending bit
} else {
/* 停止传输 */
IICSTAT = 0xd0;
IICCON &= ~(1<<4);
delay(1000);
}
} else {//read
/* 对于第1个中断, 它是发送出设备地址后产生的
* 需要判断是否有ACK
* 有ACK : 设备存在, 恢复I2C传输, 这样在下一个中断才可以得到第1个数据
* 无ACK : 无设备, 出错, 直接结束传输
*/
if (p_cur_msg->cnt_transferred == 0) {/* 第1次中断 */
if (iicstat & (1<<0)) {/* no ack */
/* 停止传输 */
IICSTAT = 0x90;
IICCON &= ~(1<<4); //clear pending bit
p_cur_msg->err = -1;
printf("rx err, no ack\n\r");
delay(1000);
return;
} else { /* ack */
/* 如果是最后一个数据, 启动传输时要设置为不回应ACK */
/* 恢复I2C传输 */
if (isLastData())
resume_iic_without_ack();
else
resume_iic_with_ack();
return;
}
}

/* 非第1个中断, 表示得到了一个新数据
* 从IICDS读出、保存
*/
if (p_cur_msg->cnt_transferred < p_cur_msg->len) {
index = p_cur_msg->cnt_transferred - 1;
p_cur_msg->buf[index] = IICDS;

/* 如果是最后一个数据, 启动传输时要设置为不回应ACK */
/* 恢复I2C传输 */
if (isLastData())
resume_iic_without_ack();
else
resume_iic_with_ack();
} else {
/* 发出停止信号 */
IICSTAT = 0x90;
IICCON &= ~(1<<4);
delay(1000);
}
}
}

4.4.1 写操作#

1
2
3
4
5
6
7
8
9
10
1. p_cur_msg->cnt_transferred初始值为-1(do_master_tx启动时设置)。
2. p_cur_msg->cnt_transferred == 0表示是第一次传输数据完后产生的中断,即发送从设备地址产生的中断。
3. iicstat & (1<<0)表示主机没有接受到ACK信号(即发出的设备地址不存在),需要停止传输。
4. IICSTAT = 0xd0置IICSTAT寄存器的[5]写为0,产生P信号。但是由于这时IICCON[4]仍为1,P信号没有实际发出,当执行IICCON &= ~(1<<4);清除IICCON[4]后,P信号才真正发出。
5. 等待一段时间,确保P信号已经发送完毕。


1).假如if (p_cur_msg->cnt_transferred < p_cur_msg->len)条件成立,表示数据还没有发送完毕,需要继续发送数据。
2).执行IICDS = p_cur_msg->buf[p_cur_msg->cnt_transferred]把要发送的数据写入到IICDS寄存器中,经过执行IICCON &= ~(1<<4);清除中断标志后后,紧接着就自动把数据发送出去了,这将触发下一个中断。
3).如果条件不成立表示数据传输完毕,发出P信号,停止数据的传输。

4.4.2 读操作#

见注释。

4.5 测试#

4.5.1 i2c_test.c#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
void do_write_at24cxx(void) {
unsigned int addr;
unsigned char str[100];
int err;

/* 获得地址 */
printf("Enter the address of sector to write: ");
addr = get_uint();

if (addr > 256) {
printf("address > 256, error!\n\r");
return;
}

printf("Enter the string to write: ");
gets(str);

printf("writing ...\n\r");
err = at24cxx_write(addr, str, strlen(str)+1);
printf("at24cxx_write ret = %d\n\r", err);
}

void do_read_at24cxx(void) {
unsigned int addr;
int i, j;
unsigned char c;
unsigned char data[100];
unsigned char str[16];
int len;
int err;
int cnt = 0;

/* 获得地址 */
printf("Enter the address to read: ");
addr = get_uint();

if (addr > 256) {
printf("address > 256, error!\n\r");
return;
}

/* 获得长度 */
printf("Enter the length to read: ");
len = get_int();

err = at24cxx_read(addr, data, len);
printf("at24cxx_read ret = %d\n\r", err);

printf("Data : \n\r");
/* 长度固定为64 */
for (i = 0; i < 4; i++) {
/* 每行打印16个数据 */
for (j = 0; j < 16; j++) {
/* 先打印数值 */
c = data[cnt++];
str[j] = c;
printf("%02x ", c);
}

printf(" ; ");

for (j = 0; j < 16; j++) {
/* 后打印字符 */
if (str[j] < 0x20 || str[j] > 0x7e) /* 不可视字符 */
putchar('.');
else
putchar(str[j]);
}
printf("\n\r");
}
}

void i2c_test(void)
{
char c;

/* 初始化 */
i2c_init();

while (1) {
/* 打印菜单, 供我们选择测试内容 */
printf("[w] Write at24cxx\n\r");
printf("[r] Read at24cxx\n\r");
printf("[q] quit\n\r");
printf("Enter selection: ");

c = getchar();
printf("%c\n\r", c);

/* 测试内容:
* 3. 编写某个地址
* 4. 读某个地址
*/
switch (c) {
case 'q':
case 'Q':
return;
break;

case 'w':
case 'W':
do_write_at24cxx();
break;

case 'r':
case 'R':
do_read_at24cxx();
break;
default:
break;
}
}
}