一.I2C 简介
I2C(内部集成电路)总线接口用作微控制器和 I2C 串行总线之间的接口。它提供多主模式功
能,可以控制所有 I2C 总线特定的序列、协议、仲裁和时序。它支持标准和快速模式。它还
与 SMBus 2.0 兼容。
它可以用于多种用途,包括 CRC 生成和验证、 SMBus(系统管理总线)以及 PMBus(电源
管理总线)。
根据器件的不同,可利用 DMA 功能来减轻 CPU 的工作量
二.IIC物理层
它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总
线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线
(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之
间的访问。
(4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空
闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用
总线。
(6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式
下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制
三.在IIC通信前,先把串口的代码贴出来,因为需要用到串口调试
1.bsp_debug_usart.h
#ifndef __DEBUG_USART_H
#define __DEBUG_USART_H
#include "stm32f4xx.h"
#include
//引脚定义
/*******************************************************/
#define DEBUG_USART USART1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define DEBUG_USART_RX_PIN GPIO_Pin_10
#define DEBUG_USART_RX_AF GPIO_AF_USART1
#define DEBUG_USART_RX_SOURCE GPIO_PinSource10
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define DEBUG_USART_TX_PIN GPIO_Pin_9
#define DEBUG_USART_TX_AF GPIO_AF_USART1
#define DEBUG_USART_TX_SOURCE GPIO_PinSource9
/************************************************************/
//串口波特率
#define DEBUG_USART_BAUDRATE 115200
void Debug_USART_Config(void);
#endif /* __USART1_H */
2.bsp_debug_usart.c
#include "bsp_debug_usart.h"
/*DEBUG_USART GPIO 配置,工作模式配置。115200 8-N-1*/
void Debug_USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_AHB1PeriphClockCmd( DEBUG_USART_RX_GPIO_CLK|DEBUG_USART_TX_GPIO_CLK, ENABLE);
/* 使能 UART 时钟 */
RCC_APB2PeriphClockCmd(DEBUG_USART_CLK, ENABLE);
/* 连接 PXx 到 USARTx_Tx*/
GPIO_PinAFConfig(DEBUG_USART_RX_GPIO_PORT,DEBUG_USART_RX_SOURCE, DEBUG_USART_RX_AF);
/* 连接 PXx 到 USARTx__Rx*/
GPIO_PinAFConfig(DEBUG_USART_TX_GPIO_PORT,DEBUG_USART_TX_SOURCE,DEBUG_USART_TX_AF);
/* 配置Tx引脚为复用功能 */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
/* 配置Rx引脚为复用功能 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_PIN;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
/* 配置串DEBUG_USART 模式 */
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
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(DEBUG_USART, &USART_InitStructure);
USART_Cmd(DEBUG_USART, ENABLE);
}
///重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口DEBUG_USART */
USART_SendData(DEBUG_USART, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_TXE) == RESET);
return (ch);
}
///重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USART);
}
四.IIC读写EEPROM过程
1.跟硬件相关的GPIO先用宏封装起来,方便移植,如图所示,我的原理图是用PB8和PB9作为IIC的两个引脚,下图是在STM32数据手册截图而来,我们设计原理图就要看这个
代码如下,包含AT24C02的设备地址读写位,IIC引脚的封装和一下函数的声明
IIC_GPIO.h
#ifndef _I2C_GPIO_H
#define _I2C_GPIO_H
#include "stm32f4xx.h"
#include "inttypes.h"
/*EEPROM写方向*/
#define EEPROM_I2C_WR 0
/*EEPROM读方向*/
#define EEPROM_I2C_RD 1
#define EEPROM_I2C_GPIO_PORT GPIOB
#define EEPROM_I2C_GPIO_CLK RCC_AHB1Periph_GPIOB
#define EEPROM_I2C_SCL_PIN GPIO_Pin_8
#define EEPROM_I2C_SDA_PIN GPIO_Pin_9
#define EEPROM_I2C_SCL_1() GPIO_SetBits(EEPROM_I2C_GPIO_PORT,EEPROM_I2C_SCL_PIN)
#define EEPROM_I2C_SCL_0() GPIO_ResetBits(EEPROM_I2C_GPIO_PORT,EEPROM_I2C_SCL_PIN)
#define EEPROM_I2C_SDA_1() GPIO_SetBits(EEPROM_I2C_GPIO_PORT,EEPROM_I2C_SDA_PIN)
#define EEPROM_I2C_SDA_0() GPIO_ResetBits(EEPROM_I2C_GPIO_PORT,EEPROM_I2C_SDA_PIN)
/*读取SDA引脚的电平状态*/
#define EEPROM_I2C_SDA_READ() GPIO_ReadInputDataBit(EEPROM_I2C_GPIO_PORT,EEPROM_I2C_SDA_PIN)
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
2.GPIO初始化以及IIC起始,结束,应答,非应答,IIC读一个字节和写一个字节
时序图如下:
IIC_GPIO.c
代码如下:
#include "i2c_gpio.h"
/* GPIO结构体初始化 */
static void i2c_cfgGpio(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*使能IIC时钟*/
RCC_AHB1PeriphClockCmd(EEPROM_I2C_GPIO_CLK,ENABLE);
/*设置为输出模式*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
/*设置为开漏模式*/
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
/*无上下拉*/
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
/* 速度为50MHZ */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN;
GPIO_Init(EEPROM_I2C_GPIO_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SDA_PIN;
GPIO_Init(EEPROM_I2C_GPIO_PORT,&GPIO_InitStructure);
/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
i2c_Stop();
}
/*写一个IIC总线位延时延时函数,根据下图AT24C02脉冲宽度的时间,工作环境168M,IIC速度最快400khz*/
static void i2c_Delay(void)
{
uint8_t i;
for(i = 0; i < 40; i++);
}
/*CPU发起IIC总线的起始信号*/
void i2c_Start(void)
{
/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
EEPROM_I2C_SCL_1();
EEPROM_I2C_SDA_1();
i2c_Delay();
EEPROM_I2C_SDA_0();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
/*CPU发起IIC总线的停止信号*/
void i2c_Stop(void)
{
/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
EEPROM_I2C_SDA_0();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SDA_1();
i2c_Delay();
}
/*CPU产生一个ACK信号*/
void i2c_Ack(void)
{
EEPROM_I2C_SDA_0();
i2c_Delay();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
EEPROM_I2C_SDA_1(); /* CPU释放SDA总线 */
i2c_Delay();
}
/*CPU产生1个NACK信号*/
void i2c_NAck(void)
{
EEPROM_I2C_SDA_1();
i2c_Delay();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_WaitAck
* 功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
* 形 参:无
* 返 回 值: 返回0表示正确应答,1表示无器件响应
*********************************************************************************************************
*/
uint8_t i2c_WaitAck(void)
{
uint8_t re;
/* CPU释放SDA总线 */
EEPROM_I2C_SDA_1();
i2c_Delay();
/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
EEPROM_I2C_SCL_1();
i2c_Delay();
/* CPU读取SDA口线状态 */
if(EEPROM_I2C_SDA_READ())
{
re = 1;
}
else
{
re = 0;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
return re;
}
/*
*********************************************************************************************************
* 函 数 名: i2c_SendByte
* 功能说明: CPU向I2C总线设备发送8bit数据
* 形 参:_ucByte : 等待发送的字节
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_SendByte(uint8_t _ucByte)
{
uint8_t i;
/* 先发送字节的高位bit7 */
for (i = 0; i < 8; i++)
{
if (_ucByte & 0x80)
{
EEPROM_I2C_SDA_1();
}
else
{
EEPROM_I2C_SDA_0();
}
i2c_Delay();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
if(i == 7)
{
EEPROM_I2C_SDA_1();
}
_ucByte <<= 1;
i2c_Delay();
}
}
/*
*********************************************************************************************************
* 函 数 名: i2c_ReadByte
* 功能说明: CPU从I2C总线设备读取8bit数据
* 形 参:无
* 返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(void)
{
uint8_t i;
uint8_t value;
/* 读到第1个bit为数据的bit7 */
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
EEPROM_I2C_SCL_1();
i2c_Delay();
if (EEPROM_I2C_SDA_READ())
{
value++;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
}
return value;
}
/*
*********************************************************************************************************
* 函 数 名: 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;
}
3.关于EEPROM读写的头文件
IIC_EE.h
#ifndef __I2C_EE_H
#define __I2C_EE_H
#include "stm32f4xx.h"
/*
* AT24C02 2kb = 2048bit = 2048/8 B = 256 B
* 32 pages of 8 bytes each
*
* Device Address
* 1 0 1 0 A2 A1 A0 R/W
* 1 0 1 0 0 0 0 0 = 0XA0
* 1 0 1 0 0 0 0 1 = 0XA1
*/
/* AT24C01/02每页有8个字节
* AT24C04/08A/16A每页有16个字节
*/
#define EEPROM_DEV_ADDR 0xA0 /* 24xx02的设备地址 */
#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 /* __I2C_EE_H */
4.读写EEPROM
#include "_i2c_ee.h"
#include "_i2c_gpio.h"
#include "bsp_debug_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_ReadBytes
* 功能说明: 从串行EEPROM指定地址处开始读取若干数据
* 形 参:_usAddress : 起始地址
* _usSize : 数据长度,单位为字节
* _pReadBuf : 存放读到的数据的缓冲区指针
* 返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i;
/* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */
/* 第1步:发起I2C总线启动信号 */
i2c_Start();
/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); /* 此处是写指令 */
/* 第3步:等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
i2c_SendByte((uint8_t)_usAddress);
/* 第5步:等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第6步:重新启动I2C总线。前面的代码的目的向EEPROM传送地址,下面开始读取数据 */
i2c_Start();
/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_RD); /* 此处是读指令 */
/* 第8步:发送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第9步:循环读取数据 */
for (i = 0; i < _usSize; i++)
{
_pReadBuf[i] = i2c_ReadByte(); /* 读1个字节 */
/* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
if (i != _usSize - 1)
{
i2c_Ack(); /* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */
}
else
{
i2c_NAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
}
}
/* 发送I2C总线停止信号 */
i2c_Stop();
return 1; /* 执行成功 */
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
i2c_Stop();
return 0;
}
/*
*********************************************************************************************************
* 函 数 名: ee_WriteBytes
* 功能说明: 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
* 形 参:_usAddress : 起始地址
* _usSize : 数据长度,单位为字节
* _pWriteBuf : 存放读到的数据的缓冲区指针
* 返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i,m;
uint16_t usAddr;
/*
写串行EEPROM不像读操作可以连续读取很多字节,每次写操作只能在同一个page。
对于24xx02,page size = 8
简单的处理方法为:按字节写操作模式,没写1个字节,都发送地址
为了提高连续写的效率: 本函数采用page wirte操作。
*/
usAddr = _usAddress;
for (i = 0; i < _usSize; i++)
{
/* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
if ((i == 0) || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0)
{
/* 第0步:发停止信号,启动内部写操作 */
i2c_Stop();
/* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
CLK频率为200KHz时,查询次数为30次左右
*/
for (m = 0; m < 1000; m++)
{
/* 第1步:发起I2C总线启动信号 */
i2c_Start();
/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); /* 此处是写指令 */
/* 第3步:发送一个时钟,判断器件是否正确应答 */
if (i2c_WaitAck() == 0)
{
break;
}
}
if (m == 1000)
{
goto cmd_fail; /* EEPROM器件写超时 */
}
/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
i2c_SendByte((uint8_t)usAddr);
/* 第5步:等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
}
/* 第6步:开始写入数据 */
i2c_SendByte(_pWriteBuf[i]);
/* 第7步:发送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
usAddr++; /* 地址增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];
uint8_t read_buf[EEPROM_SIZE];
/*-----------------------------------------------------------------------------------*/
if (ee_CheckOk() == 0)
{
/* 没有检测到EEPROM */
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;
}
五.main函数
main.c
#include "stm32f4xx.h"
#include "bsp_debug_usart.h"
#include "_i2c_ee.h"
#include "bsp_led.h"
int main(void)
{
LED_GPIO_Config();
/*初始化USART1*/
Debug_USART_Config();
printf("\r\n 这是一个I2C外设(AT24C02)读写测试例程 \r\n");
if(ee_Test() == 1)
{
LED_GREEN;
}
else
{
LED_RED;
}
}
六.代码参考了野火的例程,然后在原子的板子上跑起来,是因为觉得野火的代码风格很好,这是对自己学习IIC协议的总结,这些代码都是通过验证的,可以直接在407的板子上跑,但是移植过程中发现一些问题,虽然CPU型号一样,引脚设计也一样,但是在串口接受那里确实出了问题,因为原子的407的时钟是8M,而野火的是25M,这样就导致时钟出现问题,串口接收不了,解决办法就是,修改两个地方,一是system_stm32f4xx.h里的宏定义PLL_M的值改为8,二是修改stm32f4xx.h文件的HSE_VALUE的值改0x8000000