原理
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 high
delay_us(5);
}
//等待应答信号,收到应答信号返回0,非应答返回1
u8 IIC_Wait_Ack(void)
{
u8 timeout=0;
SDA_IN();//SDA设置为输入模式
IIC_SCL=1;//时钟线输出1
delay_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;
else
IIC_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;//左移,最低位填充0
if(READ_SDA)
{
receive++;//如果收到1,最低位置1
}
delay_us(1);
}
if(ack)
IIC_Ack();//应答
else
IIC_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个字节,往最后一个地址写入一个字节0xFF
u8 temp = AT24C02_ReadOneByte(255);
if(temp==0XFF)
return 0;
else
return 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();//初始化AT24C02
while(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);
}
}
版权声明:本文为CSDN博主「神奇的大喵」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37443229/article/details/121499067
暂无评论