原理
IIC介绍
I2C(Inter-Integrated Circuit)总线是由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是同步通信的一种特殊形式,由于管脚少、硬件实现简单、可扩展性强等特点,被广泛的使用在各大集成芯片内。
主机:启动数据传送并产生时钟信号的设备
从机:被主机寻址的器件
主模式:用 I2CNDAT 支持自动字节计数的模式;位 I2CRM,I2CSTT,I2CSTP控制数据的接收和发送
从模式:发送和接收操作都是由 I2C 模块自动控制的
发送器:发送数据到总线的器件
接收器:从总线接收数据的器件
IIC协议层:
- 数据有效性规定:时钟信号为高电平期间,数据线上的数据必须保持稳定,IIC发送数据以字节为单位,从高位开始发送
- 起始停止信号:SCL 线为高电平期间,SDA 线由高向低电平的变化表示起始信号,SDA 线由低向高电平的变化表示终止信号。
- 应答信号:响应包括“应答(ACK)”和“非应答(NACK)”两种信号。当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号即特定的低电平脉冲,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号即特定的高电平脉冲,发送方接收到该信号后会产生一个停止信号,结束信号传输。
- 总线的寻址方式:从机的地址由固定部分4位和可编程部分3位组成,D7~D1 位组成从机的地址。D0 位是数据传送方向位,为“0”时表示主机向从机写数据,为“1”时表示主机由从机读数据。
- 数据传输:有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。A 表示应答,A 非表示非应答(高电平)。S 表示起始信号,P表示终止信号。
- 主机向从机发送数据
-
- 主机向从机读数据
-
- 改变方向
AT24C02芯片
AT24C02是一个2K串行CMOS,AT24C02(EEPROM)芯片具有 C 通信接口,芯片内保存的数据在掉电情况下都不丢失,所以
通常用于存放一些比较重要的数据等。
A0、A1、A2:地址输入引脚,开发板已接地,即地址为000
SDA:串行数据输入输出
SCL:串行时钟输入
WP:写保护,1只读0读写,开发板已接地
GND:地
VCC:正电源
步骤
- 编写USART驱动程序(STM32F1系列通用)
- 将固件库文件stm32f10x_usart.c添加至工程
- 编写头文件:函数声明
- 编写驱动文件:
- 初始化函数:
- 使能端口时钟,串口时钟:RCC_APB2PeriphClockCmd
- 配置GPIO口Tx、Rx引脚:GPIO_InitTypeDef
- 配置串口:波特率、字长、停止位、校验位
- 使能串口:USART_Cmd(USART1, ENABLE)
- fputc函数覆写:printf中循环调用fputc
- 初始化函数:
- 编写IIC驱动程序
- 编写头文件:
- IIC_SCL端口、引脚、时钟定义
- IIC_SDA端口、引脚、时钟定义
- IIC_SCL位带操作定义:输出
- IIC_SDA位带操作定义:输入输出
- 函数声明
- 编写驱动文件:
- IIC初始化函数:开启端口时钟,配置SCL、SDA为推挽输出,SCL、SDA电平上拉
- SDA方向IN函数:工作模式为推挽输出
- SDA方向OUT函数:工作模式为上拉输入
- 产生起始信号函数
- 产生停止信号函数
- 产生应答信号函数
- 产生非应答信号函数
- 等待应答信号函数
- 发送一个字节函数
- 读取一个字节函数
- 编写头文件:
- 编写EEPROM驱动程序
- 编写头文件:包含IIC头文件,函数声明
- 编写驱动文件:
- AT24C02初始化函数:即IIC初始化
- 从指定位置读取一个字节函数
- 向指定位置写一个字节函数
- 从指定位置读取N个字节函数
- 向指定位置写N个字节函数
- 主函数:初始化串口,初始化滴答定时器,初始化AT24C02,检测AT24C02,读写AT24C02
代码
//usart.h#ifndef _usart_H
#define _usart_H#include "system.h"void USART1_Init(u32 bound);#endif//usart.c#include "usart.h"
#include "stdio.h"//printf重定向,printf中循环调用fputc
int fputc(int ch, FILE* p)
{USART_SendData(USART1, (u8)ch);while(USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET);//发送数据寄存器为空,已传至发送移位寄存器,但不一定发送完成return ch;
}void USART1_Init(u32 bound)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;//开启端口时钟、串口时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//配置Tx引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);//配置Rx引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);//配置串口USART_InitStructure.USART_BaudRate = bound;//波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位USART_InitStructure.USART_Parity = USART_Parity_No;//校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;//工作模式USART_Init(USART1, &USART_InitStructure);USART_Cmd(USART1, ENABLE);//使能串口}
//iic.h
#ifndef _iic_H
#define _iic_H#include "system.h"/* IIC_SCL时钟端口、引脚定义 */
#define IIC_SCL_PORT GPIOB
#define IIC_SCL_PIN (GPIO_Pin_6)
#define IIC_SCL_PORT_RCC RCC_APB2Periph_GPIOB/* IIC_SDA时钟端口、引脚定义 */
#define IIC_SDA_PORT GPIOB
#define IIC_SDA_PIN (GPIO_Pin_7)
#define IIC_SDA_PORT_RCC RCC_APB2Periph_GPIOB//IO位带操作
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA,GPIOx_ODR寄存器
#define READ_SDA PBin(7) //输入SDA,GPIOx_IDR寄存器//IIC操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(u8 ack); //IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号#endif//iic.c
#include "iic.h"
#include "SysTick.h"void IIC_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;//开启端口时钟RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC,ENABLE);//配置SCL、SDA引脚GPIO_InitStructure.GPIO_Pin=IIC_SCL_PIN;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);IIC_SCL=1; IIC_SDA=1; //所有设备空闲,总线拉高电平
}//切换SDA引脚工作模式为推挽输出
void SDA_OUT(void)
{GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}//切换SDA引脚工作模式为上拉输入
void SDA_IN(void)
{GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
}//产生起始信号
void IIC_Start(void)
{SDA_OUT();//SDA线输出模式IIC_SDA=1; IIC_SCL=1;delay_us(5);IIC_SDA=0;//START:when CLK is high,DATA change form high to low delay_us(5);IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
} //产生停止信号
void IIC_Stop(void)
{SDA_OUT();//SDA线输出模式IIC_SDA=0; IIC_SCL=1;delay_us(5);IIC_SDA=1;//STOP:when CLK is high,DATA change form low to highdelay_us(5);
}//等待应答信号,收到应答信号返回0,非应答返回1
u8 IIC_Wait_Ack(void)
{u8 timeout=0;SDA_IN();//SDA设置为输入模式 IIC_SCL=1;//时钟线输出1delay_us(1); for(READ_SDA=1; timeout<250; timeout++)//等待应答信号,即低电平0{if(READ_SDA==0){IIC_SCL=0;//时钟线输出0 return 0;}}IIC_Stop();//超时未收到应答信号,即收到非应答信号,发送方则发送停止信号return 1;
} //产生应答信号
void IIC_Ack(void)
{ SDA_OUT();//SDA设置为输出模式IIC_SCL=0;IIC_SDA=0;//发送低电平delay_us(2);IIC_SCL=1;delay_us(5);IIC_SCL=0;
}//不产生应答
void IIC_NAck(void)
{SDA_OUT();//SDA设置为输出模式IIC_SCL=0;IIC_SDA=1;//发送高电平delay_us(2);IIC_SCL=1;delay_us(5);IIC_SCL=0;
} //发送一个字节(IIC发送数据以字节为单位,从高位开始发送)
void IIC_Send_Byte(u8 txd)
{ SDA_OUT();//SDA设置为输出模式IIC_SCL=0;//拉低时钟开始数据传输for(u8 i=0; i<8; i++){ if(txd&0x80) //比较最高位,结果1000 0000(非0值为真)或 0000 0000(0)IIC_SDA=1;elseIIC_SDA=0;txd<<=1;//左移 delay_us(2); IIC_SCL=1;//拉高时钟线delay_us(2); IIC_SCL=0;//拉低时钟线 delay_us(2);}
} //读取一个字节,参数传入1为应答,0为非应答
u8 IIC_Read_Byte(u8 ack)
{u8 receive=0;//存放读取的字节SDA_IN();//SDA设置为输入模式for(u8 i=0; i<8; i++){IIC_SCL=0; delay_us(2);IIC_SCL=1;receive<<=1;//左移,最低位填充0if(READ_SDA){receive++;//如果收到1,最低位置1 } delay_us(1); }if(ack)IIC_Ack();//应答elseIIC_NAck(); //非应答 return receive;
}
//eeprom.h
#ifndef _eeprom_H
#define _eeprom_H#include "system.h"
#include "iic.h"void AT24C02_Init(void); //初始化IIC
u8 AT24C02_Check(void); //检查器件是否可读写u8 AT24C02_ReadOneByte(u8 ReadAddr); //指定地址读取一个字节
void AT24C02_WriteOneByte(u8 WriteAddr,u8 DataToWrite); //指定地址写入一个字节
void AT24C02_Write(u8 WriteAddr,u8 *pBuffer,u8 NumToWrite); //从指定地址开始写入指定长度的数据
void AT24C02_Read(u8 ReadAddr,u8 *pBuffer,u8 NumToRead); //从指定地址开始读出指定长度的数据#endif//eeprom.c
#include "eeprom.h"
#include "SysTick.h"//初始化
void AT24C02_Init(void)
{IIC_Init();//IIC初始化
}//检查AT24C02是否可读写
u8 AT24C02_Check(void)
{AT24C02_WriteOneByte(255,0XFF);//AT24C02存储容量为256个字节,往最后一个地址写入一个字节0xFFu8 temp = AT24C02_ReadOneByte(255); if(temp==0XFF)return 0;elsereturn 1;
}//从指定位置读一个字节
u8 AT24C02_ReadOneByte(u8 ReadAddr)
{ u8 receive=0;IIC_Start(); IIC_Send_Byte(0XA0); //总线寻址:向总线发送IIC设备地址0XA0,读写方向为0写数据 IIC_Wait_Ack(); //等待应答,建立通信连接 IIC_Send_Byte(ReadAddr); //向设备发送要操作的地址IIC_Wait_Ack(); IIC_Start(); //读写方向改变时,需要重新发送起始信号 IIC_Send_Byte(0XA1); //读写方向为1读数据 IIC_Wait_Ack();receive=IIC_Read_Byte(0); //接收一个字节,发送非应答信号 IIC_Stop(); return receive;
}//向指定位置写一个字节
void AT24C02_WriteOneByte(u8 WriteAddr,u8 DataToWrite)
{ IIC_Start(); IIC_Send_Byte(0XA0); //总线寻址:向总线发送IIC设备地址0XA0,读写方向为0写数据IIC_Wait_Ack(); //等待应答,建立通信连接 IIC_Send_Byte(WriteAddr); //向设备发送要操作的地址IIC_Wait_Ack(); IIC_Send_Byte(DataToWrite); //发送一个字节 IIC_Wait_Ack(); IIC_Stop();
}//从指定位置读NumToRead个字节
void AT24C02_Read(u8 ReadAddr,u8 *pBuffer,u8 NumToRead)
{while(NumToRead){*pBuffer++=AT24C02_ReadOneByte(ReadAddr++); NumToRead--;}
} //向指定位置写NumToWrite个字节
void AT24C02_Write(u8 WriteAddr,u8 *pBuffer,u8 NumToWrite)
{while(NumToWrite--){AT24C02_WriteOneByte(WriteAddr,*pBuffer);WriteAddr++;pBuffer++;}
}
//main.c#include <stdlib.h>
#include <stdio.h>
#include "usart.h"
#include "Systick.h"
#include "eeprom.h"int main()
{u8 writedat;u8 readdat;USART1_Init(115200);//初始化串口SysTick_Init(72);//初始化滴答定时器AT24C02_Init();//初始化AT24C02while(AT24C02_Check())//判断AT24C02可否读写,阻塞{printf("AT24C02读写异常!\r\n");delay_ms(500);}printf("AT24C02读写正常!\r\n");while(1){writedat = rand();AT24C02_WriteOneByte(0,writedat);printf("地址0写入 = %d\r\n",writedat);delay_ms(1000);readdat = AT24C02_ReadOneByte(0);printf("地址0读取 = %d\r\n",readdat);delay_ms(1000);}
}