模拟IIC通讯协议(stm32)(硬件iic后面在补)

这篇具有很好参考价值的文章主要介绍了模拟IIC通讯协议(stm32)(硬件iic后面在补)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、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发送停止位。

stm32 模拟iic,stm32,嵌入式硬件,单片机

        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)发送停止位。

stm32 模拟iic,stm32,嵌入式硬件,单片机        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产生下降沿。

stm32 模拟iic,stm32,嵌入式硬件,单片机

(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产生上升沿。

stm32 模拟iic,stm32,嵌入式硬件,单片机

(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应答信号。但是这个信号必须在数据接受完之后发送才有效。

stm32 模拟iic,stm32,嵌入式硬件,单片机

(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为高电平。这个信号也是只有在读取完数据之后发送才可以。

stm32 模拟iic,stm32,嵌入式硬件,单片机

(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电平不确定。在SCL为高电平的时候,读取SDA电平,当读取到SDA为低电平的时候,就说明接收到了ACK信号。

stm32 模拟iic,stm32,嵌入式硬件,单片机

(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应该先变化。

stm32 模拟iic,stm32,嵌入式硬件,单片机

(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引脚的电平状态。

stm32 模拟iic,stm32,嵌入式硬件,单片机

(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收到了数据。

stm32 模拟iic,stm32,嵌入式硬件,单片机

(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信号。stm32 模拟iic,stm32,嵌入式硬件,单片机

(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引脚就可以了。文章来源地址https://www.toymoban.com/news/detail-852540.html

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;
}



到了这里,关于模拟IIC通讯协议(stm32)(硬件iic后面在补)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用STM32CubeMX创建STM32工程(串口,硬件IIC配置)

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 最近教研室的项目需要使用STM32做处理器,对环境数据进行采集处理,在拿到板子后,需要根据单片机具体型号(STM32F411CEU6)创建工程,再进行上层应用的开发。因为以前都是用的F1系列的开发板,用的

    2023年04月18日
    浏览(51)
  • stm32之软件模拟IIC

            在之前的文章中分析过在52上的IIC时序,也测试过stm32的自带IIC功能,这里大致写下如何模拟stm32上的IIC。实验硬件基于stm32f103c8t6 废话不多说,先直接上代码。 头文件 .m文件  实验用了PB8和PB9,设置的都是开漏输出式,因为在这个模式下有如下说明: 在配置成开

    2024年02月07日
    浏览(63)
  • stm32之IIC协议

    主要通过两个层面来讲: 物理层、协议层。 I IC 是一个同步半双工串行总线协议。 一、物理层(通信模型) 1、最早是 飞利浦 公司开发的这个协议,最早应用到其产品上去。 2、两线制(两根信号线) 其中SCL为时钟线,SDA为数据线。   3、挂载在IIC总线上的设备有主从之分

    2024年02月10日
    浏览(35)
  • STM32 IIC协议基础概念

    本篇文章将带大家学习IIC通信协议的一些基础概念和使用。 I2C(Inter-Integrated Circuit),通常也称为IIC(Inter-IC),是一种串行通信协议,用于在集成电路之间进行数据传输。它是由飞利浦半导体(现在的恩智浦半导体)开发的,并且在各种芯片和模块中被广泛采用。I2C协议的

    2024年03月18日
    浏览(43)
  • 【STM32+HAL库】---- 硬件IIC驱动0.96OLED

    代码借鉴学习于以下文章: STM32 使用硬件IIC驱动0.96寸4针IOLED显示器(HAL库) 略... 点击查看代码 首先在 main.c 中引入头文件 随即根据要求在 main函数 中添加对应功能函数即可 【 注意 】使用时应将编译器编码选项选择为 GBK系列 ,否则编译报错,尤其注意通过keil修改后,再

    2024年02月19日
    浏览(38)
  • STM32硬件IIC卡死问题和DMA发送数据异常问题

    问题1描述: 一直听说STM32的硬件IIC有问题,我平时做项目一直没有遇到过,这次做项目发现硬件IIC居然会自己卡死,现象就是IIC发不出数据,用逻辑分析仪捕捉不到任何电平,必须要重启单片机才能正常,接下来说一下我的硬件环境:STM32作为主机,IIC总线上挂载这AT24C02和一

    2024年02月03日
    浏览(33)
  • STM32 硬件IIC 控制OLED I2C卡死问题

    #更新通知:2023-09-06 STM32L151 固件库 使用I2C 太难了,又宕机了,建议不要在固件库版本上尝试硬件IIC 了,一般人真用不了,直接使用软件模拟的,或者不要使用固件库了,用HAL 库吧,据说HAL 库没这么多问题,不死心的我还是死心了,等有空再研究吧 3.1 I2C模式,我这里选的

    2024年02月09日
    浏览(50)
  • stm32之27.iic协议oled显示

    屏幕如果无法点亮,需要用GPIO_OType_PP推挽输出,加并上拉电阻 1.显示字符串代码 2.显示图片代码(+unsigned+强制转换(char*)) 汉字显示

    2024年02月10日
    浏览(43)
  • STM32单片机初学4-IIC通信(软件模拟)

    IIC ( Inter-Integrated Circuit )又称I2C(习惯读“I方C”),是 IIC Bus简称,中文名为 集成电路总线 ,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。适用于IC间的短距离数据传输。 最初的IIC通信速

    2024年02月05日
    浏览(75)
  • STM32软件模拟IIC时序实现与EEPROM的通信

                       IIC简介  IIC物理层 用软件模拟IIC时序         一、空闲状态(初始化):SCL 和SDA都保持高电平         二、开始信号 :SCL为高电平期间,SDA由高电平变为低电平。         三、停止信号:SCL为高电平期间,SDA由低电平变为高电平   

    2024年02月09日
    浏览(80)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包