文章目录
前言
本文基于野火STM32F103VET6开发板,实现软件模拟i2c
一、芯片手册和原理图
由原理图可以知道
I2C的时钟线和数据线在GIOPB端口
芯片手册
GPIOB挂载在APB2总线上,I2C1挂载在APB1总线上
将BSRR寄存器某位置1,相当于ODR寄存器的相应位置1
将BRR寄存器某位置1,相当于ODR寄存器的相应位置0
读SDA_IO口的数据
二、编程要点
起始条件和停止条件
数据的有效性:
数据格式:数据8位,高位先行
应答信号:
三、代码
只附上有关I2C的代码
代码如下(示例):
bsp_i2c_gpio.c
#include "bsp_i2c_gpio.h"
static void i2c_CfgGpio(void);
static void i2c_Delay(void)
{
uint8_t i;
/*
下面的时间是通过逻辑分析仪测试得到的。
工作条件:CPU主频72MHz ,MDK编译环境,1级优化
循环次数为10时,SCL频率 = 205KHz
循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us
循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us
*/
for (i = 0; i < 10; i++);
}
/* I2C起始信号 */
void i2c_Start(void) {
EEPROM_I2C_SDA_1(); //一开始SDA是高电平
EEPROM_I2C_SCL_1(); //一开始SCL是高电平
i2c_Delay();
EEPROM_I2C_SDA_0(); //将SDA数据线拉低
i2c_Delay();
EEPROM_I2C_SCL_0(); //将SCL时钟线拉低
i2c_Delay();
return ;
}
/* I2C停止信号 */
void i2c_Stop(void) {
/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
EEPROM_I2C_SDA_0(); //将SDA数据线拉低
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SDA_1(); //将SDA数据线拉高
return ;
}
/* 功能说明: CPU向I2C总线设备发送8bit数据 */
void i2c_SendByte(uint8_t _ucByte) {
uint8_t i;
EEPROM_I2C_SCL_0();
i2c_Delay();
/* 先发送字节的高位bit7 */
for (i = 0; i < 8; i++)
{
if (_ucByte & 0x80) {
EEPROM_I2C_SDA_1();
} else {
EEPROM_I2C_SDA_0();
}
_ucByte <<= 1; /* 左移一个bit */
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
EEPROM_I2C_SDA_1(); // 释放总线 (方便应答信号)
i2c_Delay();
return;
}
/* 功能说明: CPU从I2C总线设备读取8bit数据 */
uint8_t i2c_ReadByte(void) {
//数据是串行的
uint8_t i;
uint8_t value;
/* 读到第1个bit为数据的bit7 */
value = 0;
/* SCL不断拉高拉低,拉高读取数据,拉低允许数据变化 */
for(i = 0; i < 8; i++) {
value <<= 1;
EEPROM_I2C_SCL_1();
i2c_Delay();
if (EEPROM_I2C_SDA_READ()) { //如果读到的bit为1,那么就置1
value++;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
}
return value;
}
/* 功能说明: CPU产生一个时钟,并读取器件的ACK应答信号 */
uint8_t i2c_WaitAck(void) {
uint8_t re;
EEPROM_I2C_SDA_1(); /* CPU释放SDA总线 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
i2c_Delay();
//读取SDA数据线的第9bit
if( EEPROM_I2C_SDA_READ() ) {
re = 1;
} else {
re = 0;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
return re;
}
/* 功能说明: CPU产生一个ACK信号 */
void i2c_Ack(void) {
EEPROM_I2C_SDA_0(); /* CPU驱动SDA = 0 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
EEPROM_I2C_SDA_1(); /* CPU释放SDA总线 */
}
/* 功能说明: CPU产生1个NACK信号 */
void i2c_NAck(void) {
EEPROM_I2C_SDA_1(); /* CPU驱动SDA = 1 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
/* 功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现 */
static void i2c_CfgGpio(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(EEPROM_I2C_GPIO_PORT_RCC, ENABLE); /* 打开GPIOB时钟 */
GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN | EEPROM_I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; /* 开漏输出 */
GPIO_Init(EEPROM_I2C_GPIO_PORT, &GPIO_InitStructure);
/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
i2c_Stop();
return ;
}
/*
*********************************************************************************************************
* 函 数 名: i2c_CheckDevice
* 功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
* 形 参:_Address:设备的I2C总线地址
* 返 回 值: 返回值 0 表示正确, 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t i2c_CheckDevice(uint8_t _Address) {
uint8_t ucAck;
i2c_CfgGpio(); /* 配置GPIO */
i2c_Start(); /* 发送启动信号 */
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
i2c_SendByte(_Address | EEPROM_I2C_WR);
ucAck = i2c_WaitAck(); /* 检测设备的ACK应答 */
//i2c_Stop(); /* 发送停止信号 */
return ucAck;
}
bsp_i2c_gpio.h
#ifndef __BSP_I2C_GPIO_H__
#define __BSP_I2C_GPIO_H__
#include "stm32f10x.h"
/* i2c挂载在APB1总线上,有I2C1和I2C2。本程序用的是I2C1 */
/* SCL在PB6, SDA在PB7 , 主机:stm32 从机:EEPROM*/
#define EEPROM_I2C_WR 0 /* 主机 写 数据到从机 控制bit */
#define EEPROM_I2C_RD 1 /* 主机 从 从机中读数据 控制bit */
#define EEPROM_I2C_GPIO_PORT GPIOB /* GPIO端口 */
#define EEPROM_I2C_GPIO_PORT_RCC RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
#define EEPROM_I2C_SCL_PIN GPIO_Pin_6 /* 连接到SCL时钟线的GPIO */
#define EEPROM_I2C_SDA_PIN GPIO_Pin_7 /* 连接到SDA数据线的GPIO */
/* 直接操作寄存器实现IO读写 */
#define EEPROM_I2C_SCL_1() EEPROM_I2C_GPIO_PORT->BSRR |= EEPROM_I2C_SCL_PIN /* SCL = 1 */
#define EEPROM_I2C_SCL_0() EEPROM_I2C_GPIO_PORT->BRR |= EEPROM_I2C_SCL_PIN /* SCL = 0 */
#define EEPROM_I2C_SDA_1() EEPROM_I2C_GPIO_PORT->BSRR |= EEPROM_I2C_SDA_PIN /* SDA = 1 */
#define EEPROM_I2C_SDA_0() EEPROM_I2C_GPIO_PORT->BRR |= EEPROM_I2C_SDA_PIN /* SDA = 0 */
#define EEPROM_I2C_SDA_READ() ((EEPROM_I2C_GPIO_PORT->IDR & EEPROM_I2C_SDA_PIN) != 0)
void i2c_Start(void);
void i2c_Stop(void);
void i2c_SendByte(uint8_t _ucByte);
uint8_t i2c_ReadByte(void);
uint8_t i2c_WaitAck(void);
void i2c_Ack(void);
void i2c_NAck(void);
uint8_t i2c_CheckDevice(uint8_t _Address);
#endif /* __BSP_I2C_GPIO_H__ */
bsp_i2c_ee.c
#include "bsp_i2c_ee.h"
#include "bsp_i2c_gpio.h"
#include "bsp_usart.h"
/*
*********************************************************************************************************
* 函 数 名: ee_CheckOk
* 功能说明: 判断串行EERPOM是否正常
* 形 参:无
* 返 回 值: 1 表示正常, 0 表示不正常
*********************************************************************************************************
*/
uint8_t ee_CheckOk(void)
{
if (i2c_CheckDevice(EEPROM_DEV_ADDR) == 0) {
return 1;
} else {
/* 失败后,切记发送I2C总线停止信号 */
i2c_Stop();
return 0;
}
}
/*
*********************************************************************************************************
* 函 数 名: ee_WriteBytes
* 功能说明: 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
* 形 参:_usAddress : 起始地址
* _usSize : 数据长度,单位为字节
* _pWriteBuf : 存放读到的数据的缓冲区指针
* 返 回 值: 1表示成功
*********************************************************************************************************
*/
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize) {
uint16_t i, m, usAddr;
usAddr = _usAddress;
for(i = 0; i < _usSize; i++) {
/* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
if(0 == i || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0 ) {
i2c_Stop();
for(m = 0; m < 1000; m++) {
i2c_Start();
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); /* 发送设备地址+写方向 */
if (i2c_WaitAck() == 0) {
break;
}
}
if (m == 1000) {
goto cmd_fail; /* EEPROM器件写超时 */
}
i2c_SendByte((uint8_t)usAddr);
if (i2c_WaitAck() != 0) {
goto cmd_fail; /* EEPROM器件无应答 */
}
}
/* 开始写入数据 */
i2c_SendByte(_pWriteBuf[i]);
if (i2c_WaitAck() != 0) {
goto cmd_fail; /* EEPROM器件无应答 */
}
usAddr++; /* 地址增1 */
}
/* 命令执行成功,发送I2C总线停止信号 */
i2c_Stop();
return 1;
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
i2c_Stop();
return 0;
}
/*
*********************************************************************************************************
* 函 数 名: ee_ReadBytes
* 功能说明: 从串行EEPROM指定地址处开始读取若干数据
* 形 参:_usAddress : 起始地址
* _usSize : 数据长度,单位为字节
* _pReadBuf : 存放读到的数据的缓冲区指针
* 返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize) {
uint16_t i;
i2c_Start();
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); /* 此处是写指令 */
if (i2c_WaitAck() != 0) {
goto cmd_fail; /* EEPROM器件无应答 */
}
i2c_SendByte((uint8_t)_usAddress);//发送在哪读的地址
if (i2c_WaitAck() != 0) {
goto cmd_fail; /* EEPROM器件无应答 */
}
i2c_Start();
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_RD); /* 此处是读指令 */
if (i2c_WaitAck() != 0) {
goto cmd_fail; /* EEPROM器件无应答 */
}
for (i = 0; i < _usSize; i++) {
_pReadBuf[i] = i2c_ReadByte(); /* 读1个字节 */
if(i != _usSize-1 ) {
i2c_Ack(); //主机发送应答信号
} else {
i2c_NAck(); /* 最后1个字节读完后,主机发送非应答信号*/
}
}
/* 发送I2C总线停止信号 */
i2c_Stop();
return 1;
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
i2c_Stop();
return 0;
}
void ee_Erase(void)
{
uint16_t i;
uint8_t buf[EEPROM_SIZE];
/* 填充缓冲区 */
for (i = 0; i < EEPROM_SIZE; i++)
{
buf[i] = 0xFF;
}
/* 写EEPROM, 起始地址 = 0,数据长度为 256 */
if (ee_WriteBytes(buf, 0, EEPROM_SIZE) == 0)
{
printf("擦除eeprom出错!\r\n");
return;
}
else
{
printf("擦除eeprom成功!\r\n");
}
}
/*--------------------------------------------------------------------------------------------------*/
static void ee_Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
/*
* eeprom AT24C02 读写测试
* 正常返回1,异常返回0
*/
uint8_t ee_Test(void) {
uint16_t i;
uint8_t write_buf[EEPROM_SIZE] = {0};
uint8_t read_buf[EEPROM_SIZE] = {0};
if (ee_CheckOk() == 0) {
printf("没有检测到串行EEPROM!\r\n");
return 0;
}
/* 填充测试缓冲区 */
for(i = 0; i < EEPROM_SIZE; i++) {
write_buf[i] = i;
}
if( ee_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0 ) {
printf("写eeprom出错!\r\n");
return 0;
} else {
printf("写eeprom成功!\r\n");
}
/*写完之后需要适当的延时再去读,不然会出错*/
ee_Delay(0x0FFFFF);
/*-----------------------------------------------------------------------------------*/
if (ee_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0) {
printf("读eeprom出错!\r\n");
return 0;
} else {
printf("读eeprom成功,数据如下:\r\n");
}
for(i = 0; i < EEPROM_SIZE; i++) {
if(read_buf[i] != write_buf[i]) {
printf("0x%02X ", read_buf[i]);
printf("错误:EEPROM读出与写入的数据不一致");
return 0;
}
printf(" %02X", read_buf[i]);
if ((i & 15) == 15) {
printf("\r\n");
}
}
printf("eeprom读写测试成功\r\n");
return 1;
}
/*********************************************END OF FILE**********************/
bsp_i2c_gpio.h
#ifndef __BSP_I2C_EE_H__
#define __BSP_I2C_EE_H__
#include "stm32f10x.h"
#define EEPROM_DEV_ADDR 0xA0 /* 24xx02的设备地址:10100000 */
#define EEPROM_PAGE_SIZE 8 /* 24xx02的页面大小 */
#define EEPROM_SIZE 256 /* 24xx02总容量 */
uint8_t ee_CheckOk(void);
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize);
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize);
void ee_Erase(void);
uint8_t ee_Test(void);
#endif /* __BSP_I2C_EE_H__ */
main.c
#include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_i2c_gpio.h"
#include "bsp_i2c_ee.h"
extern uint8_t ucTemp;
void Delay(unsigned int n) {
unsigned int i,j;
for(j = 0; j<5000; j++)
{
for(i = 0;i<n;i++);
}
return ;
}
uint8_t Test(void) {
//uint8_t a = 0xfe;
uint8_t val;
if (ee_CheckOk() == 0)
{
/* 没有检测到EEPROM */
printf("没有检测到串行EEPROM!\r\n");
return 0;
} else {
printf("测试成功!\n");
}
/* 写 */
i2c_Stop();
i2c_Start();
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); /* 此处是写设备地址 */
while(i2c_WaitAck() == 1);
i2c_SendByte(0);
while(i2c_WaitAck() == 1);
i2c_SendByte(0xff);
while(i2c_WaitAck() == 1);
i2c_Stop();
Delay(10000);
/* 读 */
i2c_Stop();
i2c_Start();
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); /* 发送设备地址 */
while(i2c_WaitAck() == 1); //等待应答
i2c_SendByte(0);
while(i2c_WaitAck() == 1);
i2c_Start();
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_RD); /* 发送读方向地址 */
while(i2c_WaitAck() == 1);
val = i2c_ReadByte();
i2c_Ack();
i2c_Stop();
Delay(1000);
return val;
}
int main(void) {
// 来到这里的时候,系统的时钟已经被配置成72M。
LED_GPIO_Config();//LED的GPIO初始化配置
USART_Config(); //初始化串口配置
LED_BLUE;
printf("开发板是stm32f103\n");
printf("eeprom 软件模拟i2c测试例程 \r\n");
if( ee_Test() == 1 ) {
LED_GREEN;
} else {
LED_RED;
}
while(1);
}
2.结果
结果如图所示