Little Deamon.

猴子也能懂的iic裸板程序

字数统计: 1.1k阅读时长: 4 min
2018/10/15 Share

iic裸机程序运行机制
本文代码适用于AT24cxx平台E2PROM

使用I2C实现的AT24cxx系列的读写函数

读操作

1
2
3
4
5
6
7
8
9
10
unsigned char at24cxx_read(unsigned char address)
{
unsigned char val;
printf("at24cxx_read address = %d\r\n", address);
i2c_write(0xA0, &address, 1);
printf("at24cxx_read send address ok\r\n");
i2c_read(0xA0, (unsigned char *)&val, 1);
printf("at24cxx_read get data ok\r\n");
return val;
}

i2c读
上图为读,根据要求,我们要发出device address,和word address。i2c_write(0xA0, &address, 1); 三个参数:

  1. 第一个参数是device address。
  2. 第二个参数为word address-存放的地址,在这里是先做数据存放的buf缓冲区,通常为参数传入。
  3. 第三个参数是数据长度

所以我们完成一个读的操作,需要先写入word地址,之后再进行读操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* 主机接收
* slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度
*/
void i2c_read(unsigned int slvAddr, unsigned char *buf, int len)
{
g_tS3C24xx_I2C.Mode = RDDATA; // 读操作
g_tS3C24xx_I2C.Pt = -1; // 索引值初始化为-1,表示第1个中断时不接收数据(地址中断)
g_tS3C24xx_I2C.pData = buf; // 保存缓冲区地址
g_tS3C24xx_I2C.DataCount = len; // 传输长度

IICDS = slvAddr;
IICSTAT = 0xb0; // 主机接收,启动

/* 等待直至数据传输完毕 */
while (g_tS3C24xx_I2C.DataCount != 0);
}

在这之后会触发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
void I2CIntHandle(void)
{
unsigned int iicSt,i;

// 清中断
SRCPND = BIT_IIC;
INTPND = BIT_IIC;

iicSt = IICSTAT;

if(iicSt & 0x8){ printf("Bus arbitration failed\n\r"); }

switch (g_tS3C24xx_I2C.Mode)
{

case RDDATA:
{
if (g_tS3C24xx_I2C.Pt == -1)
{
// 这次中断是发送I2C设备地址后发生的,没有数据
// 只接收一个数据时,不要发出ACK信号
g_tS3C24xx_I2C.Pt = 0;
if(g_tS3C24xx_I2C.DataCount == 1)
IICCON = 0x2f; // 恢复I2C传输,开始接收数据,接收到数据时不发出ACK
else
IICCON = 0xaf; // 恢复I2C传输,开始接收数据
break;
}

g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++] = IICDS;
g_tS3C24xx_I2C.DataCount--;

if (g_tS3C24xx_I2C.DataCount == 0)
{

// 下面两行恢复I2C操作,发出P信号
IICSTAT = 0x90;
IICCON = 0xaf;
Delay(10000); // 等待一段时间以便P信号已经发出
break;
}
else
{
// 接收最后一个数据时,不要发出ACK信号
if(g_tS3C24xx_I2C.DataCount == 1)
IICCON = 0x2f; // 恢复I2C传输,接收到下一数据时无ACK
else
IICCON = 0xaf; // 恢复I2C传输,接收到下一数据时发出ACK
}
break;
}
}
}

我们先把关于读的部分去掉不看。根据上面设置的g_ts3c24xx_I2C.Mode。我们会进入case RDDATA中。

  • g_tS3C24xx_I2C.Pt == -1。是我们一开始设置的索引。因为第一个发出的是设备地址,没有数据,所以要单独处理。

  • g_tS3C24xx_I2C.DataCount == 1。若满足,则说明只接受一个数据。只接收一个数据的时候NO ACK(看图可知)

  • 所以发出IICCON = 0x2f(具体相关含义查看芯片手册即可,不赘述!要是实在想不明白也不想去看芯片手册就把这个当成一个指令:恢复I2C传输,开始接收数据,接收到数据时不发出ACK

  • 之后的break弹出了此次的操作。

  • 因为没有发出stop的信号,在下一个数据到来的时候,会再次进入中断。

  • g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++] = IICDS; g_tS3C24xx_I2C.DataCount–; 。接收数据

  • g_tS3C24xx_I2C.DataCount == 0。说明数据已经全部读完。所以

    1
    2
    3
    4
    IICSTAT = 0x90;
    IICCON = 0xaf;
    Delay(10000); // 等待一段时间以便P信号已经发出
    break;

    发出P信号(终止信号)退出本次通讯。

写操作

i2c写操作
i2c写操作写操作就相对简单一些,我们只需要直接将word address和data打包一股脑扔过去就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* 主机发送
* slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度
*/
void i2c_write(unsigned int slvAddr, unsigned char *buf, int len)
{
g_tS3C24xx_I2C.Mode = WRDATA; // 写操作
g_tS3C24xx_I2C.Pt = 0; // 索引值初始为0
g_tS3C24xx_I2C.pData = buf; // 保存缓冲区地址
g_tS3C24xx_I2C.DataCount = len; // 传输长度

IICDS = slvAddr;
IICSTAT = 0xf0; // 主机发送,启动

/* 等待直至数据传输完毕 */
while (g_tS3C24xx_I2C.DataCount != -1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case WRDATA:
{
if((g_tS3C24xx_I2C.DataCount--) == 0)
{
// 下面两行用来恢复I2C操作,发出P信号
IICSTAT = 0xd0;
IICCON = 0xaf;
Delay(10000); // 等待一段时间以便P信号已经发出
break;
}

IICDS = g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++];

// 将数据写入IICDS后,需要一段时间才能出现在SDA线上
for (i = 0; i < 10; i++);

IICCON = 0xaf; // 恢复I2C传输
break;
}

这里我们直接看case WRDATA就好了。
逻辑其实很清晰

  • (g_tS3C24xx_I2C.DataCount–) == 0。说明已经读完,发出p信号停止本次通讯

  • IICDS = g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++];。数据接收。

  • 1
    2
    IICCON = 0xaf;      // 恢复I2C传输
    break;

裸板的代码就到此为止了。没有太分析i2c的硬件逻辑,毕竟还是写软件为主。未来不排除会专门讲讲i2c咕咕咕
下一步是关于linux中的i2c驱动。

CATALOG
  1. 1. 使用I2C实现的AT24cxx系列的读写函数
    1. 1.1. 读操作
    2. 1.2. 写操作