目录
I2C 总线介绍
电路图
I2C 是个主从结构,即所有的传输都是从主机发起。从机不可能主动引起数据的传输。
对于 I2C 协议来说,它只能规定到发出的第一个数据是“地址”,后面发出的内容是什
么,每个 I2C 可能不同。
下面是一种情况:
1,平时 SDA 和 SCL 都是高电平。
2,开始信号:当 SCL 为高电平的同时, SDA 有个脉冲。
3,数据在 SCL 低电平时变化 ,在 SCL 脉冲为高电平间稳定。
4,发出地址,每个 I2C 芯片里面都有一个地址,是固化在芯片里面。
1.设备地址:寻址过程 与 写数据
1,设备地址总共是 7bit,“1010”固定,后面“A2,A1,A0”可以从硬件上敲定。
看开发板实际的接线。
开发板上, A0,A1,A2 都是接地,即此三位都是 0.
2,则此 AT24C08 的地址:
发出的前面 7 位就是设备地址。此时后面接的 I2C 设备,接收到一个 start 信号时,
就知道下面紧接着的就是7bit 的设备地址。若发现此地址与固化在设备内的地址相同时,就知道是访问自已。
3,访问时,是“读”还是“写”:由 7bit 后的第 8 位决定。
启动一个传输时,主机先发出 S 信号,然后发出 8 位 数据。这 8 位数据的前 7bit 为从机的地址,第 8bit 表示传输的方向(0 表示写操作, 1 表示读操作)。
4, ACK 回应信号:
在第 9 个 CLK 里, I2C 主机释放 SDA,由从机驱动SDA.若从机发现该“设备地址”是自已的,是把 SDA 拉
为低电平。这时主机就能知道此“设备地址”的设备是存在的,后面就能再发数据了。
5.后面接着再 8 个 CLK 时钟,是具体的数据,是与设备有关的。后面还有一个第 9bit,是从机把 SDA 拉低确认 ACK。
若为主机到从机的读:
寻址后的 8 个时钟是由 从机响应,从机会把设备驱动到 SDA 中,最后的第 9 个时钟也是 ACK,但是由主机响应。就是说主机已经接收到数据了,就到 ACK 拉为低电平。
结束传输:
SCL 时钟信号在高电平期间, SDA 由低电平变成高电平时结束传输。
I2C 裸机程序
看一个裸板程序从“入口函数”开始看: head.S
一,开发板上电初始化工作:
假设 NAND 启动,一上电,程序的代码前 4K 就会复制到 2440 片内内存。
1,第一步:跳转到 reset 处:
2, reset 处代码工作:
①,设置栈、关看门狗、设置时钟、设置内存控制器、初始化 NANDFLASH。
这些函数现在是在 0 地址(片内内存)处运行。
②,把代码复制到 0x30000000 地址处。
复制到 0x30000000 处,是因为“链接地址”规定
之前是把程序分成了两段,实际上可以分成一段。修改如下:
a,链接地址: 0x3000000
b,代码段:
c,只读数据段:所有文件的只读数据段。
d,所有文件的数据段:
e, bss 段:
所有文件的的 bss 和 COMMON 段。
3,链接地址:
ldr sp, =4096
bl disable_watch_dog
bl clock_initbl memsetup
bl nand_init
这些函数现在是在 0 地址(片内内存)处运行。它们的链接地址是“0x30000000”。链
接地址并不等于它们当前所在的位置。所以它们就需要以“位置无关码”来写。就是说写
这些函数时,要用“位置无关码”来写,不能用全局变量等。
①,关看门狗:
②,初始化时钟:
③,内存设置:
④,初始化 NAND:
位置无关码不能用全局变量,这里的 "S3C2410_NAND”是局部变量。所以这个
“nand.c”中的代码都要修改。看修改过的“nand.c”把全局变量全换成局部变量,即
把全局变量放到局部中(函数里)。
4, head.S 重定位:
把 NANDFLASH 0 地址从 0 地址拷贝。拷贝的长度是"__bss_start = ."减去
“.=0x30000000” .这样便有足够长了。
以前为了简单代码,直接把长度写了 16K:
修改为如下:就是不包括 bss 段的长度。
bss 段是放那些初始值为 0 的全局变量的。假设有 1W 个 0 时,没有必要把所有的 0 全放
在最后的二进制文件里面,要是带上这 1W 个 0 就会很浪费空间。那么二进制文件里面就
不去含有 bss 段。 bss 段部分需要我们自已来清零。
4, head.S 接着调用: CopyCode2SDRAM 函数,将代码拷贝到内存中去。
用"nand_read"函数来拷贝。
5, head.S 中将代码拷贝到内存中后,接着清 bss 段:
就是将“ __bss_start”到“ __bss_end;”间全部清为 0.
这里在链接时会分配链接地址。就把这段清为 0.那么以前访问这些全局变量时就都是 0.
6, head.S 中其他就是中断相关的设置,最后是 main 函数。
二,主函数: main.c
1,初始化串口和 I2C 设置:
a,初始化串口:
b,初始化 I2C:
3,两个读写函数:
Random Read 随机读的时序:
前 7bit 发出设备地址找到从机,第 8bit 从机回应。再接着发出存储地址写给从机。
接着就开始一个“START”信号,再发出设备地址,这时候表示读。最后读到此数据(WORD ADDRESS n 处)。
AT24108,是 8K(1024*8)即要 10 位地址才能表示 1024。“WORD ADDRESSn”是 8位根本无法寻址 1K 的空间。 2 的 8 次方是最多访问到 256 个地址。但这里 E2PROM 的容量有 1K、 2K 时,显然 8 位的地址是不够的。
Device Addressing 设备寻址:
里面的“128”、“256”可以用 8 位来寻址到。
而 512, 1024 则有 2 页或 4 页数据。
2K是256B字节时,它所有的存储地址就可以用 8位来寻址。
4K即512字节时, P0等于0时表示访问第一页。 P1等于1时表示访问第1页。
8K即1024字节时, P1,P0总共就可以表示 4 页。
16K即2048字节时, P2,P1,P0共表示8页。
对于小容量的 AT24C02 为 256 字节,就可以用“WORD ADDRESS” 8 位地址来寻址。但
对于上面 AT24C08 这个 1024 字节的大容量 E2PROM 设备,则"WORD ADDRESS"只能寻
址它的“PAGE0”空间。对于其他页的空间,以前面发出“START”信号后“设备地址”:
A2 是硬件引脚, P1,P0 可以变。只要发出“1010+A2”就能访问到设备(A2 硬件上接低
电平时,则 A2 为 0;硬件上接的是高电平,则 A2 为 1), P1,P0 就是用来表示访问哪
一页。 AT20C08 有 4 页(每页 256 字节)。
最后编译测试:
得到了 bin 有 10K。
链接脚本要保证“head.o” "init.o"“nand.o”三个文件位于前面 4K,因为这个三个
文件在重定位之前,都得位于片内内存里面。可以看反汇编文件: 4K=4096=0x1000,即
是 0x30001000 处:
链接脚本中最后一个 nand.o 的最后一个函数是:
在反汇编文件中搜索这个“nand_read”的链接地址:
链接地址是“3e4”没有超过“4K”。
i2c.c
/*
* FILE: i2c.c
* 用于主机发送/接收
*/
#include <stdio.h>
#include "s3c24xx.h"
#include "i2c.h"
void Delay(int time);
#define WRDATA (1)
#define RDDATA (2)
typedef struct tI2C {
unsigned char *pData; /* 数据缓冲区 */
volatile int DataCount; /* 等待传输的数据长度 */
volatile int Status; /* 状态 */
volatile int Mode; /* 模式:读/写 */
volatile int Pt; /* pData中待传输数据的位置 */
}tS3C24xx_I2C, *ptS3C24xx_I2C;
static tS3C24xx_I2C g_tS3C24xx_I2C;
/*
* I2C初始化
*/
void i2c_init(void)
{
GPEUP |= 0xc000; // 禁止内部上拉
GPECON |= 0xa0000000; // 选择引脚功能:GPE15:IICSDA, GPE14:IICSCL
INTMSK &= ~(BIT_IIC);
/* bit[7] = 1, 使能ACK
* bit[6] = 0, IICCLK = PCLK/16
* bit[5] = 1, 使能中断
* bit[3:0] = 0xf, Tx clock = IICCLK/16
* PCLK = 50MHz, IICCLK = 3.125MHz, Tx Clock = 0.195MHz
*/
IICCON = (1<<7) | (0<<6) | (1<<5) | (0xf); // 0xaf
IICADD = 0x10; // S3C24xx slave address = [7:1]
IICSTAT = 0x10; // I2C串行输出使能(Rx/Tx)
}
/*
* 主机发送
* 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);
}
/*
* 主机接收
* 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中断服务程序
* 根据剩余的数据长度选择继续传输或者结束
*/
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 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 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;
}
default:
break;
}
}
/*
* 延时函数
*/
void Delay(int time)
{
for (; time > 0; time--);
}
at24cxx.c
#include <string.h>
#include "i2c.h"
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;
}
void at24cxx_write(unsigned char address, unsigned char data)
{
unsigned char val[2];
val[0] = address;
val[1] = data;
i2c_write(0xA0, val, 2);
}
i2c.h
//====================================================================
// File Name : 2410IIC.h
// Function : S3C2410 IIC Test Program Head file
// Program : Shin, On Pil (SOP)
// Date : March 20, 2002
// Version : 0.0
// History
// 0.0 : Programming start (March 11, 2002) -> SOP
//====================================================================
#ifndef __2410IIC_H__
#define __2410IIC_H__
void i2c_init(void);
void i2c_write(unsigned int slvAddr, unsigned char *buf, int len);
void i2c_read(unsigned int slvAddr, unsigned char *buf, int len);
void I2CIntHandle(void);
#endif //__2410IIC_H__
main.c
#include <stdio.h>
#include "serial.h"
#include "i2c.h"
unsigned char at24cxx_read(unsigned char address);
void at24cxx_write(unsigned char address, unsigned char data);
int main()
{
char c;
char str[200];
int i;
int address;
int data;
uart0_init(); // 波特率115200,8N1(8个数据位,无校验位,1个停止位)
i2c_init();
while (1)
{
printf("\r\n##### AT24CXX Menu #####\r\n");
printf("[R] Read AT24CXX\n\r");
printf("[W] Write AT24CXX\n\r");
printf("Enter your selection: ");
c = getc();
printf("%c\n\r", c);
switch (c)
{
case 'r':
case 'R':
{
printf("Enter address: ");
i = 0;
do
{
c = getc();
str[i++] = c;
putc(c);
} while(c != '\n' && c != '\r');
str[i] = '\0';
while(--i >= 0)
{
if (str[i] < '0' || str[i] > '9')
str[i] = ' ';
}
sscanf(str, "%d", &address);
printf("\r\nread address = %d\r\n", address);
data = at24cxx_read(address);
printf("data = %d\r\n", data);
break;
}
case 'w':
case 'W':
{
printf("Enter address: ");
i = 0;
do
{
c = getc();
str[i++] = c;
putc(c);
} while(c != '\n' && c != '\r');
str[i] = '\0';
printf("\r\n");
while(--i >= 0)
{
if (str[i] < '0' || str[i] > '9')
str[i] = ' ';
}
sscanf(str, "%d", &address);
//printf("get str %s\r\n", str);
printf("Enter data: ");
i = 0;
do
{
c = getc();
str[i++] = c;
putc(c);
} while(c != '\n' && c != '\r');
str[i] = '\0';
printf("\r\n");
//printf("get str %s\r\n", str);
while(--i >= 0)
{
if (str[i] < '0' || str[i] > '9')
str[i] = ' ';
}
sscanf(str, "%d", &data);
//address = 12;
//data = 13;
printf("write address %d with data %d\r\n", address, data);
at24cxx_write(address, data);
break;
}
}
}
return 0;
}
serial.c
#include "s3c24xx.h"
#include "serial.h"
#define TXD0READY (1<<2)
#define RXD0READY (1)
#define PCLK 50000000 // init.c中的clock_init函数设置PCLK为50MHz
#define UART_CLK PCLK // UART0的时钟源设为PCLK
#define UART_BAUD_RATE 115200 // 波特率
#define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
/*
* 初始化UART0
* 115200,8N1,无流控
*/
void uart0_init(void)
{
GPHCON |= 0xa0; // GPH2,GPH3用作TXD0,RXD0
GPHUP = 0x0c; // GPH2,GPH3内部上拉
ULCON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)
UCON0 = 0x05; // 查询方式,UART时钟源为PCLK
UFCON0 = 0x00; // 不使用FIFO
UMCON0 = 0x00; // 不使用流控
UBRDIV0 = UART_BRD; // 波特率为115200
}
/*
* 发送一个字符
*/
void putc(unsigned char c)
{
/* 等待,直到发送缓冲区中的数据已经全部发送出去 */
while (!(UTRSTAT0 & TXD0READY));
/* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */
UTXH0 = c;
}
/*
* 接收字符
*/
unsigned char getc(void)
{
/* 等待,直到接收缓冲区中的有数据 */
while (!(UTRSTAT0 & RXD0READY));
/* 直接读取URXH0寄存器,即可获得接收到的数据 */
return URXH0;
}
/*
* 判断一个字符是否数字
*/
int isDigit(unsigned char c)
{
if (c >= '0' && c <= '9')
return 1;
else
return 0;
}
/*
* 判断一个字符是否英文字母
*/
int isLetter(unsigned char c)
{
if (c >= 'a' && c <= 'z')
return 1;
else if (c >= 'A' && c <= 'Z')
return 1;
else
return 0;
}
serial.h
void uart0_init(void);
void putc(unsigned char c);
unsigned char getc(void);
int isDigit(unsigned char c);
int isLetter(unsigned char c);