一、IIC基础知识总结。
1、IIC通讯需要两条线就可以,SCL、SDA。
2、IIC的数据传输的速率,不同的ic是不同的,根据电平维持的延时函数的时间来确定IIC数据传输的速率.
3、IIC的延时函数可以使用延时函数,延时函数一般使用系统滴答时钟产生延时,也是在Sysclk频率总线的基础上产生的延时。这个延时和“__NOP();”指令产生的延时是一样的,“__NOP();”也是依靠Sysclk频率产生延时。使用场景:“__NOP();”指令是一个汇编指令的运行产生延时,是占用cpu的,短时间且精确的延时是可以使用的;较长时间的精准的延时还是需要使用系统滴答时钟的定时器实现延时的。
4、标准的IIC传输节拍信号是由7种的:起始信号、停止信号、产生ACK应答信号、产生NACK应答信号、等待ACK应答信号、接收1byte字节信号、发送1byte字节信号。
5、在标准IIC信号中分为两种形式:边沿信号,上升沿或者下降沿(起始信号、停止信号)。电平信号,高电平或者低电平(产生ACK应答信号、产生NACK应答信号、等待ACK应答信号、接收1byte字节信号、发送1byte字节信号)。
6、上面的两类信号,也就是7种信号中,SDA的信号必须在SCL为高电平的时候有效。
7、在上面的7种基础信号的基础上,根据不同的芯片封装不同的数据发送和接收的函数,下面将简单介绍一般的数据发送和接收协议形式,大部分ic芯片都是相同的。
ic数据的发送:
(1)发送起始位。
(2)发送写控制字节,写控制字节的最后一位表示“写”,其他的位表示IC的id。
(3)等待IC的ACK回应。
(4)发送地址字节。
(5)等待IC的ACK回应。
(6)发送写入的数据字节。
(7)等待IC的ACK回应。
(8)如果单字节写入,只能写入一次,Pag页的写入,5,6可以进行多次。
(9)最后给IC发送停止位。
ic数据的接收:
(1)发送起始位。
(2)发送写控制字节,写控制字节的最后一位表示“写”,其他的位表示IC的id。
(3)等待IC的ACK回应。
(4)写入地址高字节(如果是16位地址数据)。
(5)等待IC的ACK回应。
(6)写入地址低字节
(7)等待IC的ACK回应。
(8)发送起始位。
(9)发送读控制字节,读控制字节的最后一位表示“读”,其他的位表示IC的id。
(10)等待IC的ACK回应。
(11)接收数据。
(12)数据没有接收完毕,继续接收,发送ACK回应信号。
(13)接收数据。
(14)数据接收完毕,发送NACK回应信号。
(15)发送停止位。
8、停止信号,最后保持SCL为高电平;其他信号,函数结束的最后一定要保持SCL为低电平。
二、IIC使用引脚的配置电平的配置。
1、SDA的GPIO输入
static void i2c_sda_in(void)
{
GPIO_InitTypeDef gpio_cfg;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_cfg.Pin = bus_i2c->sda_pin;
gpio_cfg.Mode = GPIO_MODE_INPUT;
// gpio_cfg.Pull = GPIO_PULLUP;
HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->sda_port, &gpio_cfg);
}
2、SDA的GPIO输出
static void i2c_sda_out(void)
{
GPIO_InitTypeDef gpio_cfg;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_cfg.Pin = bus_i2c->sda_pin;
gpio_cfg.Mode = GPIO_MODE_OUTPUT_OD;
gpio_cfg.Pull = GPIO_PULLUP;
gpio_cfg.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->sda_port, &gpio_cfg);
}
3、SCL的GPIO输出
static void i2c_scl_out(void)
{
GPIO_InitTypeDef gpio_cfg;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_cfg.Pin = bus_i2c->scl_pin;
gpio_cfg.Mode = GPIO_MODE_OUTPUT_OD;
gpio_cfg.Pull = GPIO_PULLUP;
gpio_cfg.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->scl_port, &gpio_cfg);
}
4、SDA输出高低电平
static void i2c_sda_write(unsigned char value)
{
HAL_GPIO_WritePin((GPIO_TypeDef*)bus_i2c->sda_port, bus_i2c->sda_pin, value?GPIO_PIN_SET:GPIO_PIN_RESET);
}
5、SCL输出高低电平
static void i2c_scl_write(unsigned char value)
{
HAL_GPIO_WritePin((GPIO_TypeDef*)bus_i2c->scl_port, bus_i2c->scl_pin, value?GPIO_PIN_SET:GPIO_PIN_RESET);
}
6、SDA电平读取
static unsigned int i2c_sda_read(void)
{
return HAL_GPIO_ReadPin((GPIO_TypeDef*)bus_i2c->sda_port, bus_i2c->sda_pin);
}
三、IIC基础信号
1、起始信号(边沿信号)
刚开始的时候SDA和SCL引脚信号应该都是高电平(原因:iic的两条线在电路上是加了上拉电电阻的,并且对应GPIO初始化的时候,设置的是上拉模式,所以刚开始的时候SDA和SCL引脚都是高电平),起始信号之后要保持SCL为低电平。
在SCL高电平的时候,SDA产生下降沿。
(1)SAD拉高+延时函数
(2)SCL拉高+延时函数
(3)SDA拉低+延时函数
(4)SCL拉低+延时函数
static void i2c_start(void)
{
i2c_sda_out();
i2c_sda_write(1);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_sda_write(0);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
2、停止信号(边沿信号)
结束的时候SCL的电平一定是低电平(原因:停止信号一般是在IIC发送或者读取数据之后进行发送的,数据发送或者接收的时候SDA在SCL为高电平的时候有效,发送结束之后,SCL必须为低电平,让SDA引脚的信号电平是无效的,所以发停止信号之前,SCL的电平一定是低电平),但是SDA的电平是不确定的,所以应该先把SDA电平拉低。
在SCL为高电平的时候,SDA产生上升沿。
(1)SDA拉低+延时函数
(2)SCL拉高+延时函数
(3)SDA拉高+延时函数
static void i2c_stop(void)
{
i2c_sda_out();
i2c_sda_write(0);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_sda_write(1);
i2c_delay();
}
3、延时函数
延时函数就使用汇编指令“__NOP()”。具体知识参考其他文章。
static void i2c_delay(void)
{
__NOP();
}
4、发送ACK应答信号(电平信号)
SCL电平一定是低电平(原因:发送ACK是在IIC读取数据的时候,需要继续读取数据,给IC的回应信号,实在读取一个电平信号之后发送的,读取信号SDA是在SCL为低电平的时候有效,所以的读取之后,SCL必须为低电平。),SDA电平未知。SCL为高电平的时候,SDA为低电平,为ACK应答信号。但是这个信号必须在数据接受完之后发送才有效。
(1)SDA拉低+延时函数
(2)SCL拉高+延时函数
(3)SCL拉低+延时函数
static void i2c_write_ack(void)
{
i2c_sda_out();
i2c_sda_write(0);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
5、发送NACK应答信号(电平信号)
SCL电平一定为低电平(原因:NACK信号是在IIC读取数据的时候,读取结束,告诉IC芯片读取结束,不用在发送数据的信号,所以也是在IIC数据读取完成之后,所以SCL的电平在数据位读取完之后一定低电平。),SDA的状态确定。在SCL为高电平的时候,SDA为高电平。这个信号也是只有在读取完数据之后发送才可以。
(1)SDA拉高+延时函数
(2)SCL拉高+延时函数
(3)SCL拉低+延时函数
static void i2c_write_nack(void)
{
i2c_sda_out();
i2c_sda_write(1);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
6、等待ACK应答信号(电平信号)
SCL一定为高电平,SDA电平不确定(但是一般IIC都是有上拉电阻的,所以空闲状态SDA的电平也是高电平,这里不知道是因为SDA受从设备发送数据电平控制)。在SCL为高电平的时候,读取SDA电平,当读取到SDA为低电平的时候,就说明接收到了ACK信号。
(1)SCL拉高+延时函数
(2)读取SDA电平+延时函数
(3)SCL拉低+延时函数
static unsigned char i2c_read_ack(void)
{
unsigned char level = 0;
i2c_sda_in();
i2c_scl_write(1);
i2c_delay();
if(i2c_sda_read())
level = 1;
i2c_scl_write(0);
i2c_delay();
return level;
}
7、发送数据(电平信号)
只有在SCL为高电平的时候SDA电平才有效,在SCL为高电平的时候,必须保持SDA电平稳定,所以SCL电平变化之前,SDA应该先变化。
(1)SDA电平变化+延时函数(根绝写入数据位设置电平)
(2)SCL拉高+延时函数
(3)SCL拉低+延时函数
static void i2c_write_byte(unsigned short data)
{
int i;
unsigned char temp = (unsigned char)(data & 0xFF);
i2c_sda_out();
for(i=0;i<8;i++)
{
if(temp & 0x80)
i2c_sda_write(1);
else
i2c_sda_write(0);
temp <<= 1;
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
}
8、接收数据(电平信号)
数据接收和数据发送是一样的,SDA在SCL为高电平的时候有效,所以SCL为高电平的时候读取SDA引脚的电平状态。
(1)SCL拉高+延时函数
(2)读取SDA电平+延时函数
(3)SCL拉低+延时函数
static unsigned char i2c_read_byte(void)
{
int i;
unsigned char temp = 0;
i2c_sda_in();
for(i=0;i<8;i++)
{
i2c_scl_write(1);
i2c_delay();
temp <<= 1;
if(i2c_sda_read())
temp |= 0x01;
i2c_scl_write(0);
i2c_delay();
}
return temp;
}
四、针对IC的写入数据指令和读书数据指令流程
下面是针对IC的一般情况的数据写入和读出的操作流程。
通过IIC对IC芯片进行操作的时候,不管是数据的读或者数据的写,都会先写入IC的控制字节,控制字节的8bit中,最低位为读、写控制标志位,剩余的高7个bit是IC的器件地址,是专属于这个IC的,对挂载在IC总线上的IC期间,就是通过这个器件地址进行读写区分的。
1、IIC对IC的数据写入(单字节写入)
下图所示的就是IIC对IC芯片的数据写入的基本逻辑。除了两个边沿信号(起始、停止信号)是不需要等待IC给ACK回应的。写入数据或者地址都是需等待IC的ACK回应,确认IC收到了数据。
(1)写入启动。
(2)写入“ic写控制字节”。
(3)等待ACK响应。
(4)写入寄存器地址。
(5)等待ACK响应。(如果没等到就写入stop位并返回)。
(6)写入要写入的数据(可以使用循环写入多个byte)。
(7)每次写入数据都需要等待ACK响应。
(8)写入stop位。
uint8_t PCT2075DP_Write(uint8_t reg, void* data,uint8_t size)
{
int i;
uint8_t* pData = (uint8_t*)data;
i2c_scl_out();
i2c_start();
i2c_write_byte(0x90);
if(i2c_read_ack)
{
i2c_stop();
return -1;
}
i2c_write_byte(reg);
if(i2c_read_ack)
{
i2c_stop();
return -3;
}
for(i=0; i<size; i++)
{
i2c_write_byte(pData[i]);
if(i2c_read_ack)
{
i2c_stop();
return i;
}
}
i2c_stop();
return i;
}
2、IIC对IC的数据读出(单字节读出)
IIC对IC的数据读取除了两个边沿信号(起始、停止信号)是不需要给IC一个ACK回应信号的。进行数据的读取的时候,每读取一个字节都是需要给IC发送一个ACK回应,代表已经接收到数据,还需要继续接收数据;如果接收到的是最后一个数据,并且不在接收数据,那么就回应NACK信号。
(1)写起始信号位。
(2)写入“ic写控制字节”。
(3)等待IC的ACK回应。
(4)写入地址高字节(如果地址16位就写高字节)。
(5)等待IC的ACK回应。
(6)写入地址低字节。
(7)等待IC的ACK回应。
(8)写入起始信号(本次是重启IIC总线)。
(9)写入“ic读控制字节”。
(10)等待IC的ACK回应。
(11)读取数据字节。
(12)写入ACK回应信号(表示继续读取)。
(13)读取数据字节。
(14)写入NACK回应信号(表示数据读取结束)。
(15)写入停止位。
uint8_t PCT2075DP_Read(uint8_t reg, void* data,uint8_t size, uint8_t poit)
{
int i;
uint8_t* pData = (uint8_t*)data;
i2c_scl_out();
i2c_start();
i2c_write_byte(0x90);
if(i2c_read_ack())
{
i2c_stop();
return -1;
}
i2c_write_byte(0x00);
if(i2c_read_ack())
{
i2c_stop();
return -3;
}
i2c_start();
i2c_write_byte(0x91);
if(i2c_read_ack())
{
i2c_stop();
return -4;
}
for(i=0; i<size; i++)
{
pData[i] = i2c_read_byte(); //需要继续读的时候就回复i2c_write_ack()。
if(i == size - 1)
{
i2c_write_nack();
}else
{
i2c_write_ack();
}
}
i2c_stop();
return i;
}
下面是总体代码:
#ifndef __MYIIC_H__
#define __MYIIC_H__
#include "stm32l0xx_hal.h"
#include "stdint.h"
#include <stdio.h>
#include "delay.h"
#include "485.h"
typedef struct sIIC_IO {
unsigned int scl_port;
unsigned int scl_pin;
unsigned int sda_port;
unsigned int sda_pin;
}g_tIIC_IO;
extern void myTest(float *pvalue);
extern int pct7075_read(float *pvalue);
#endif
#include "Myiic.h"
#include <string.h>
/* 模拟IIC,7个函数。
*(1)iic函数发送数据注意发送多少位的兼容。
*(2)iic函数发送两个字节的还是一个字节的地址。
*(3)SDA的数据电平只有在SCL为高电平的时候有效。
*(4)iic功能函数:起始信号、停止信号、产生ACK应答、产生NACK应答,等待ACK应答,接收数据,发送数据
*(5)利用面对对象思想,结构体封装模拟iic使用端口和与引脚
*(6)引脚的输出输出初始化,引脚电平变化的函数,结构体的封装管理。
*/
g_tIIC_IO i2c1 = {
.scl_port = (unsigned int)GPIOB,
.scl_pin = (unsigned int)GPIO_PIN_6,
.sda_port = (unsigned int)GPIOB,
.sda_pin = (unsigned int)GPIO_PIN_7
};
g_tIIC_IO *bus_i2c = &i2c1;
static void i2c_delay(void)
{
__NOP();
}
static void i2c_sda_out(void)
{
GPIO_InitTypeDef gpio_cfg;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_cfg.Pin = bus_i2c->sda_pin;
gpio_cfg.Mode = GPIO_MODE_OUTPUT_OD;
gpio_cfg.Pull = GPIO_PULLUP;
gpio_cfg.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->sda_port, &gpio_cfg);
}
static void i2c_sda_write(unsigned char value)
{
HAL_GPIO_WritePin((GPIO_TypeDef*)bus_i2c->sda_port, bus_i2c->sda_pin, value?GPIO_PIN_SET:GPIO_PIN_RESET);
}
static void i2c_sda_in(void)
{
GPIO_InitTypeDef gpio_cfg;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_cfg.Pin = bus_i2c->sda_pin;
gpio_cfg.Mode = GPIO_MODE_INPUT;
// gpio_cfg.Pull = GPIO_PULLUP;
HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->sda_port, &gpio_cfg);
}
static unsigned int i2c_sda_read(void)
{
return HAL_GPIO_ReadPin((GPIO_TypeDef*)bus_i2c->sda_port, bus_i2c->sda_pin);
}
static void i2c_scl_out(void)
{
GPIO_InitTypeDef gpio_cfg;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_cfg.Pin = bus_i2c->scl_pin;
gpio_cfg.Mode = GPIO_MODE_OUTPUT_OD;
gpio_cfg.Pull = GPIO_PULLUP;
gpio_cfg.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->scl_port, &gpio_cfg);
}
static void i2c_scl_write(unsigned char value)
{
HAL_GPIO_WritePin((GPIO_TypeDef*)bus_i2c->scl_port, bus_i2c->scl_pin, value?GPIO_PIN_SET:GPIO_PIN_RESET);
}
static void i2c_start(void)
{
i2c_sda_out();
i2c_sda_write(1);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_sda_write(0);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
static void i2c_stop(void)
{
i2c_sda_out();
i2c_sda_write(0);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_sda_write(1);
i2c_delay();
}
static void i2c_write_nack(void)
{
i2c_sda_out();
i2c_sda_write(1);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
static void i2c_write_ack(void)
{
i2c_sda_out();
i2c_sda_write(0);
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
static unsigned char i2c_read_ack(void)
{
unsigned char level = 0;
i2c_sda_in();
i2c_scl_write(1);
i2c_delay();
if(i2c_sda_read())
level = 1;
i2c_scl_write(0);
i2c_delay();
return level;
}
static void i2c_write_byte(unsigned short data)
{
int i;
unsigned char temp = (unsigned char)(data & 0xFF);
i2c_sda_out();
for(i=0;i<8;i++)
{
if(temp & 0x80)
i2c_sda_write(1);
else
i2c_sda_write(0);
temp <<= 1;
i2c_delay();
i2c_scl_write(1);
i2c_delay();
i2c_scl_write(0);
i2c_delay();
}
}
static unsigned char i2c_read_byte(void)
{
int i;
unsigned char temp = 0;
i2c_sda_in();
for(i=0;i<8;i++)
{
i2c_scl_write(1);
i2c_delay();
temp <<= 1;
if(i2c_sda_read())
temp |= 0x01;
i2c_scl_write(0);
i2c_delay();
}
return temp;
}
/* 上面就是iic的标准操作函数 */
/*****************************************************************/
/*
*address:地址
*reg:寄存器指针指令
*
*/
uint8_t PCT2075DP_Write(uint8_t reg, void* data,uint8_t size)
{
int i;
uint8_t* pData = (uint8_t*)data;
i2c_scl_out();
i2c_start();
i2c_write_byte(0x90);
if(i2c_read_ack)
{
i2c_stop();
return -1;
}
i2c_write_byte(reg);
if(i2c_read_ack)
{
i2c_stop();
return -3;
}
for(i=0; i<size; i++)
{
i2c_write_byte(pData[i]);
if(i2c_read_ack)
{
i2c_stop();
return i;
}
}
i2c_stop();
return i;
}
uint8_t PCT2075DP_Read(uint8_t reg, void* data,uint8_t size, uint8_t poit)
{
int i;
uint8_t* pData = (uint8_t*)data;
i2c_scl_out();
i2c_start();
i2c_write_byte(0x90);
if(i2c_read_ack())
{
i2c_stop();
return -1;
}
i2c_write_byte(0x00);
if(i2c_read_ack())
{
i2c_stop();
return -3;
}
i2c_start();
i2c_write_byte(0x91);
if(i2c_read_ack())
{
i2c_stop();
return -4;
}
for(i=0; i<size; i++)
{
pData[i] = i2c_read_byte(); //需要继续读的时候就回复i2c_write_ack()。
if(i == size - 1)
{
i2c_write_nack();
}else
{
i2c_write_ack();
}
}
i2c_stop();
return i;
}
void myTest(float *pvalue)
{
uint16_t temp,data=0;
int retry = 3;
float value;
/* run in normal mode */
PCT2075DP_Write(0x01, &data, 1);
while(retry --)
{
if (PCT2075DP_Read(0x00, &temp, 2, 0) == 2)
break;
}
temp = ((temp&0xFF00)>>8)|((temp&0x00FF)<<8); // 传感器读取数据高8位与低8位位置调转,返回值直接short型
value = temp;
temp=0;
value = value / 256;
*pvalue= value;
}
int i2c_write(g_tIIC_IO* bus, unsigned char address, unsigned short reg, void* pbuf, int size, unsigned char reg_16bit)
{
int i = 0;
unsigned char* buf_ptr;
if(pbuf == NULL) return i;
buf_ptr = (unsigned char*)pbuf;
bus_i2c = bus;
i2c_scl_out();
i2c_start();
i2c_write_byte(address | 0);
if(i2c_read_ack()) {
i2c_stop();
return -1;
}
if(reg_16bit) {
i2c_write_byte(reg >> 8);
if(i2c_read_ack()) {
i2c_stop();
return -2;
}
}
i2c_write_byte(reg);
if(i2c_read_ack()) {
i2c_stop();
return -3;
}
for(i=0;i<size;i++) {
i2c_write_byte(buf_ptr[i]);
if(i2c_read_ack()) {
i2c_stop();
return i;
}
}
i2c_stop();
return i;
}
int i2c_read(g_tIIC_IO* bus, unsigned char address, unsigned short reg, void* pbuf, int size, unsigned char reg_16bit)
{
int i = 0;
unsigned char* buf_ptr;
if(pbuf == NULL) return i;
buf_ptr = (unsigned char*)pbuf;
bus_i2c = bus;
i2c_scl_out();
i2c_start();
i2c_write_byte(address | 0);
if(i2c_read_ack()) {
i2c_stop();
return -1;
}
if(reg_16bit) {
i2c_write_byte(reg >> 8);
if(i2c_read_ack()) {
i2c_stop();
return -2;
}
}
i2c_write_byte(reg);
if(i2c_read_ack()) {
i2c_stop();
return -3;
}
i2c_start();
i2c_write_byte(address | 1);
if(i2c_read_ack()) {
i2c_stop();
return -4;
}
for(i=0;i<size;i++) {
buf_ptr[i] = i2c_read_byte();
if(i == size - 1)
i2c_write_nack();
else
i2c_write_ack();
}
i2c_stop();
return i;
}
int pct7075_read(float *pvalue)
{
int retry = 3; // 3次读取失败则传感器数据读取失效,回填0xFF
short temp;
float value;
#ifdef PWR_CTRL
unsigned char cfg;
/* run in normal mode */
cfg = 0x00;
i2c_write(&PTC2075_I2C_BUS, PTC2075_SLV_ADDR, 0x01, &cfg, 1, 0);
#endif
/* 多次读取,方式有时候读取失败 */
while(retry --)
{
if (i2c_read(&i2c1, 0x90, 0x00, &temp, 2, 0) == 2)
break;
}
#ifdef PWR_CTRL
/* run in shutdown mode */
cfg = 0x01;
i2c_write(&i2c1, 0x90, 0x01, &cfg, 1, 0);
#endif
if(!retry) //读取数据失败的情况。
{
memset(pvalue, 0xFF, 4);
return -1;
}
temp = ((temp&0xFF00)>>8)|((temp&0x00FF)<<8); // 传感器读取数据高8位与低8位位置调转,返回值直接short型
value = temp;
value = value / 256;
if(pvalue != 0) *pvalue = value;
return 0;
}
stm32的硬件iic的通讯信息的时序不好调整,所以这里就先不花时间研究了。
五、模拟IIC封装函数
上面模拟IIC的操作函数移植比较不方便,下面的代码使用的时候,只需要在结构体中修改对应得GPIO引脚就可以了。
5.1、h头文件
#ifndef __MY_IIC_H__
#define __MY_IIC_H__
#include "sys.h"
#include "stm32f4xx.h"
#include "usart.h"
#include <stdio.h>
#include "delay.h"
#include "myiic.h"
/* 软件iic实现的时序图 */
/* IIC的SDA、SCL引脚的GPIO初始化函数 */
typedef struct IIC_IO {
GPIO_TypeDef * scl_port;
unsigned int scl_pin;
GPIO_TypeDef * sda_port;
unsigned int sda_pin;
}g_tIIC_IO;
/* IIC的时序操作和初始化函数 */
typedef struct IIC_OPERATION
{
void(*My_IIC_delay)(uint16_t count);
void(*My_IIC_Init)(void);
void(*My_IIC_start)(void);
void(*My_IIC_stop)(void);
void(*My_IIC_write_nack)(void);
void(*My_IIC_write_ack)(void);
uint8_t(*My_IIC_read_ack)(void);
void(*My_IIC_write_byte)(uint8_t data);
uint8_t(*My_IIC_read_byte)(void);
}g_tIIC_OPERATION;
void My_IIC_delay(uint16_t count); //IIC使用的延时函数
void My_IIC_Init(void); //IIC的对应GPIO的初始化函数
void My_IIC_start(void); //IIC起始边沿信号函数
void My_IIC_stop(void); //IIC写入停止函数
void My_IIC_write_nack(void); //写入NACK函数
void My_IIC_write_ack(void); //写入ACK函数
uint8_t My_IIC_read_ack(void); //IIC的等待ack函数
void My_IIC_write_byte(uint8_t data); //IIC的数据写入函数
uint8_t My_IIC_read_byte(void); //IIC的数据读取函数
/* IIC操作的结构体函数 */
extern g_tIIC_OPERATION iic_operation;
#endif
5.2、c源文件
#include "my_iic.h"
/* 模拟IIC,7个函数。
*(1)iic函数发送数据注意发送多少位的兼容。
*(2)iic函数发送两个字节的还是一个字节的地址。
*(3)SDA的数据电平只有在SCL为高电平的时候有效。
*(4)iic功能函数:起始信号、停止信号、产生ACK应答、产生NACK应答,等待ACK应答,接收数据,发送数据
*(5)利用面对对象思想,结构体封装模拟iic使用端口和与引脚
*(6)引脚的输出输出初始化,引脚电平变化的函数,结构体的封装管理。
*/
/* 模拟IIC的时序操作函数 */
g_tIIC_OPERATION iic_operation =
{
.My_IIC_delay = My_IIC_delay ,
.My_IIC_Init = My_IIC_Init ,
.My_IIC_start = My_IIC_start ,
.My_IIC_stop = My_IIC_stop ,
.My_IIC_write_nack = My_IIC_write_nack,
.My_IIC_write_ack = My_IIC_write_ack ,
.My_IIC_read_ack = My_IIC_read_ack ,
.My_IIC_write_byte = My_IIC_write_byte,
.My_IIC_read_byte = My_IIC_read_byte ,
};
/* 模拟IIC对应的GPIO的引脚,不同得代码使用移植得时候,只需要修改下面得结构体对应得GPIO口即可 */
/*
WM8978芯片的驱动IIC对应引脚:
(1)SDA:i2c1.scl_port PIN9
(2)SDL:i2c1.scl_port PIN8
*/
g_tIIC_IO i2c1 = {
.scl_port = GPIOB,
.scl_pin = (unsigned int)8,
.sda_port = GPIOB,
.sda_pin = (unsigned int)9,
};
/* IIC使用的延时函数,延时的函数系统时钟的倒数,1/AHB=1/168MHz=0.00059us*/
void My_IIC_delay(uint16_t count)
{
uint16_t i;
//__NOP()汇编指令实现延时,适合短暂延时
for(i=0; i<count*10; i++)
{
__NOP();
}
//适合长延时
//delay_ms(1);
}
/* IIC的SDA、SCL对应GPIO的初始化函数 */
void My_IIC_Init(void)
{
RCC->AHB1ENR|=1<<1; //使能PORTB时钟
/* SDA:GPIOB9 */
i2c1.sda_port->MODER &= ~(uint32_t)(3<<(i2c1.sda_pin*2)); //清空寄存器对应位
i2c1.sda_port->MODER |= (uint32_t)(1<<(i2c1.sda_pin*2)); //通用输出
i2c1.sda_port->OTYPER &= ~(uint32_t)(1<<i2c1.sda_pin); //清除原来配置位
i2c1.sda_port->OTYPER |= (uint32_t)(1<<i2c1.sda_pin); //推挽输出
i2c1.sda_port->OSPEEDR &= ~(uint32_t)(3<<(i2c1.sda_pin*2)); //清除原来配置位
i2c1.sda_port->OSPEEDR |= (uint32_t)(2<<(i2c1.sda_pin*2)); //设置速率位50M
i2c1.sda_port->PUPDR &= ~(uint32_t)(3<<(i2c1.sda_pin*2)); //清除原来配置位
i2c1.sda_port->PUPDR |= (uint32_t)(1<<(i2c1.sda_pin*2)); //上拉输出
/* SCL:GPIOB8 */
i2c1.scl_port->MODER &= ~(uint32_t)(3<<(i2c1.scl_pin*2)); //清空寄存器对应位
i2c1.scl_port->MODER |= (uint32_t)(1<<(i2c1.scl_pin*2)); //通用输出
i2c1.scl_port->OTYPER &= ~(uint32_t)(1<<i2c1.scl_pin); //清除原来配置位
i2c1.scl_port->OTYPER |= (uint32_t)(1<<i2c1.scl_pin); //推挽输出
i2c1.scl_port->OSPEEDR &= ~(uint32_t)(3<<(i2c1.scl_pin*2)); //清除原来配置位
i2c1.scl_port->OSPEEDR |= (uint32_t)(2<<(i2c1.scl_pin*2)); //设置速率位50M
i2c1.scl_port->PUPDR &= ~(uint32_t)(3<<(i2c1.scl_pin*2)); //清除原来配置位
i2c1.scl_port->PUPDR |= (uint32_t)(1<<(i2c1.scl_pin*2)); //上拉输出
i2c1.sda_port->ODR = (uint32_t)(1<<i2c1.sda_pin); //SDA置1
i2c1.scl_port->ODR = (uint32_t)(1<<i2c1.scl_pin); //SCL置1
}
/* IIC写入起始边沿信号函数 */
void My_IIC_start(void)
{
i2c1.scl_port->MODER &= ~(uint32_t)(3<<(i2c1.sda_pin*2)); //清空寄存器对应位
i2c1.scl_port->MODER |= (uint32_t)(1<<(i2c1.sda_pin*2)); //通用输出
i2c1.sda_port->ODR |= (uint16_t)(1<<i2c1.sda_pin); //SDA置1
i2c1.scl_port->ODR |= (uint16_t)(1<<i2c1.scl_pin); //SCL置1
My_IIC_delay(4);
i2c1.sda_port->ODR &= ~(uint32_t)(1<<i2c1.sda_pin); //SDA置0
My_IIC_delay(4);
i2c1.scl_port->ODR &= ~(uint32_t)(1<<i2c1.scl_pin); //SCL置0
}
/* IIC写入停止函数 */
void My_IIC_stop(void)
{
i2c1.sda_port->MODER &= ~(uint32_t)(3<<(i2c1.sda_pin*2)); //清空寄存器对应位
i2c1.sda_port->MODER |= (uint32_t)(1<<(i2c1.sda_pin*2)); //通用输出
i2c1.scl_port->ODR &= ~(uint32_t)(1<<i2c1.scl_pin); //SCL置0
i2c1.sda_port->ODR &= ~(uint32_t)(1<<i2c1.sda_pin); //SDA置0
My_IIC_delay(4);
i2c1.scl_port->ODR |= (uint32_t)(1<<i2c1.scl_pin); //SCL置1
i2c1.sda_port->ODR |= (uint32_t)(1<<i2c1.sda_pin); //SDA置1
My_IIC_delay(4);
}
/* IIC写入NACK响应函数 */
void My_IIC_write_nack(void)
{
i2c1.sda_port->MODER &= ~(uint32_t)(3<<(i2c1.sda_pin*2)); //清空寄存器对应位
i2c1.sda_port->MODER |= (uint32_t)(1<<(i2c1.sda_pin*2)); //通用输出
i2c1.scl_port->ODR &= ~(uint32_t)(1<<i2c1.scl_pin); //SCL置0
/* 下面SDA置1的设置是可有可无的,因为这时候不知道SDA的电平,
所以设置为1,等到ic芯片将其拉为零 */
i2c1.sda_port->ODR |= (uint32_t)(1<<i2c1.sda_pin); //SDA置1
My_IIC_delay(2);
i2c1.scl_port->ODR |= (uint32_t)(1<<i2c1.scl_pin); //SCL置1
My_IIC_delay(2);
i2c1.scl_port->ODR &= ~(uint32_t)(1<<i2c1.scl_pin); //SCL置0
}
/* IIC写入ACK响应函数 */
void My_IIC_write_ack(void)
{
i2c1.scl_port->ODR &= ~(uint32_t)(1<<i2c1.scl_pin); //SCL置0
i2c1.sda_port->MODER &= ~(uint32_t)(3<<(i2c1.sda_pin*2)); //清空寄存器对应位
i2c1.sda_port->MODER |= (uint32_t)(1<<(i2c1.sda_pin*2)); //通用输出
i2c1.sda_port->ODR &= ~(uint32_t)(1<<i2c1.sda_pin); //SDA置0
My_IIC_delay(2);
i2c1.scl_port->ODR |= (uint32_t)(1<<i2c1.scl_pin); //SCL置1
My_IIC_delay(2);
i2c1.scl_port->ODR &= ~(uint32_t)(1<<i2c1.scl_pin); //SCL置0
}
/* 返回0表示成功 */
/* IIC等待读取ACK响应函数 */
uint8_t My_IIC_read_ack(void)
{
unsigned char ErrTime =0;
i2c1.sda_port->MODER &= ~(uint32_t)(3<<(i2c1.sda_pin*2)); //清空寄存器对应位
i2c1.sda_port->ODR |= (uint32_t)(1<<i2c1.sda_pin); //SDA置1
i2c1.scl_port->ODR |= (uint32_t)(1<<i2c1.scl_pin); //SCL置1
while((i2c1.sda_port->IDR &= (uint32_t)(1<<i2c1.sda_pin)))
{
if(ErrTime>250)
{
My_IIC_stop();
return 1;
}
ErrTime++;
}
i2c1.scl_port->ODR &= ~(uint32_t)(1<<i2c1.scl_pin); //SCL置0
return 0;
}
/* IIC数据写入函数 */
void My_IIC_write_byte(uint8_t txd)
{
int i;
i2c1.sda_port->MODER &= ~(uint32_t)(3<<(i2c1.sda_pin*2)); //清空寄存器对应位
i2c1.sda_port->MODER |= (uint32_t)(1<<(i2c1.sda_pin*2)); //通用输出
i2c1.scl_port->ODR &= ~(uint32_t)(1<<i2c1.scl_pin); //SCL置0
for(i=0;i<8;i++)
{
if((txd & 0x80)>>7)
i2c1.sda_port->ODR |= (uint32_t)(1<<i2c1.sda_pin); //SDA置1
else
i2c1.sda_port->ODR &= ~(uint32_t)(1<<i2c1.sda_pin); //SDA置0
txd <<= 1;
My_IIC_delay(2);
i2c1.scl_port->ODR |= (uint32_t)(1<<i2c1.scl_pin); //SCL置1
My_IIC_delay(2);
i2c1.scl_port->ODR &= ~(uint32_t)(1<<i2c1.scl_pin); //SCL置0
My_IIC_delay(2);
}
}
/* IIC数据读取函数 */
uint8_t My_IIC_read_byte(void)
{
int i;
unsigned char temp = 0;
i2c1.sda_port->MODER &= ~(uint32_t)(3<<(i2c1.sda_pin*2)); //清空寄存器对应位
for(i=0;i<8;i++)
{
i2c1.scl_port->ODR |= (uint32_t)(1<<i2c1.scl_pin); //SCL置1
My_IIC_delay(1);
temp <<= 1;
if(i2c1.sda_port->IDR &= (uint32_t)(1<<i2c1.sda_pin)) //SDA置0
temp |= 0x01;
i2c1.scl_port->ODR &= ~(uint32_t)(1<<i2c1.scl_pin); //SCL置0
My_IIC_delay(1);
}
return temp;
}