菜鸟的第一步 发表于 2024-10-30 16:45

软件模拟i2c的使用

软件模拟i2c的使用1、i2c硬件资源不太够的时候,可以用普通gpio模拟i2c的功能,
话不多说,直接上代码,使用时直接替换对应管脚即可。2、代码如下:#include <stdio.h>
#include <stdint.h>
#include "main.h"
#include "type.h"
#include "SwI2c.h"
#include "n32g430.h"
#include "timer.h"

// PB8 scl PB9 sda
#define SWI2C1_SCL_PORT   GPIOB
#define SWI2C1_SCL_PIN      GPIO_PIN_8
#define SWI2C1_SCL_CLOCK    RCC_AHB_PERIPH_GPIOB
#define SWI2C1_SCL_HIGH         GPIO_Pins_Set(SWI2C1_SCL_PORT,SWI2C1_SCL_PIN)
#define SWI2C1_SCL_LOW                 GPIO_Pins_Reset(SWI2C1_SCL_PORT,SWI2C1_SCL_PIN)


#define SWI2C1_SDA_PORT   GPIOB
#define SWI2C1_SDA_PIN      GPIO_PIN_9
#define SWI2C1_SDA_CLOCK    RCC_AHB_PERIPH_GPIOB
#define SWI2C1_SDA_HIGH         GPIO_Pins_Set(SWI2C1_SDA_PORT,SWI2C1_SDA_PIN)
#define SWI2C1_SDA_LOW                GPIO_Pins_Reset(SWI2C1_SDA_PORT,SWI2C1_SDA_PIN)
#define SWI2C1_SDA_READ   GPIO_Input_Pin_Data_Get(SWI2C1_SDA_PORT,SWI2C1_SDA_PIN)
#define BIT0   0x01
#define BIT7   0x80

void SwI2c_Gpio_Init(void)
{
        /* Define a structure of type GPIO_InitType */
        GPIO_InitType GPIO_InitStructure;
       
        /* Enable LED related GPIO peripheral clock */
        RCC_AHB_Peripheral_Clock_Enable(SWI2C1_SCL_CLOCK);
    RCC_AHB_Peripheral_Clock_Enable(SWI2C1_SDA_CLOCK);
       
        /* Assign default value to GPIO_InitStructure structure */
        GPIO_Structure_Initialize(&GPIO_InitStructure);
       
        /* Select the GPIO pin to control */
        GPIO_InitStructure.Pin                  = SWI2C1_SCL_PIN;
        /* Set pin mode to general push-pull output */
        GPIO_InitStructure.GPIO_Mode            = GPIO_MODE_OUT_OD;
    GPIO_InitStructure.GPIO_Pull            = GPIO_PULL_UP;
        /* Set the pin drive current to 4MA*/
        GPIO_InitStructure.GPIO_Current         = GPIO_DS_4MA;
        GPIO_InitStructure.GPIO_Slew_Rate         = GPIO_SLEW_RATE_FAST;
        /* Initialize GPIO */
        GPIO_Peripheral_Initialize(SWI2C1_SCL_PORT, &GPIO_InitStructure);

        /* Select the GPIO pin to control */
        GPIO_InitStructure.Pin                  = SWI2C1_SDA_PIN;
        /* Set pin mode to general push-pull output */
        GPIO_InitStructure.GPIO_Mode            = GPIO_MODE_OUT_OD;
    GPIO_InitStructure.GPIO_Pull            = GPIO_PULL_UP;
        /* Set the pin drive current to 4MA*/
        GPIO_InitStructure.GPIO_Current         = GPIO_DS_4MA;
        GPIO_InitStructure.GPIO_Slew_Rate         = GPIO_SLEW_RATE_FAST;
        /* Initialize GPIO */
        GPIO_Peripheral_Initialize(SWI2C1_SDA_PORT, &GPIO_InitStructure);

        SWI2C1_SDA_HIGH;
        SWI2C1_SCL_HIGH;
}

static void SwI2c_Delay_Us(int32_t us)
{
        TIM3_Delay_Us(us);
}

static void SwI2c_Sda_Config_Output_mode(void)
{
        GPIO_InitType GPIO_InitStructure;

        /* Assign default value to GPIO_InitStructure structure */
        GPIO_Structure_Initialize(&GPIO_InitStructure);

        /* Select the GPIO pin to control */
        GPIO_InitStructure.Pin                  = SWI2C1_SDA_PIN;
        /* Set pin mode to general push-pull output */
        GPIO_InitStructure.GPIO_Mode            = GPIO_MODE_OUT_OD;
    GPIO_InitStructure.GPIO_Pull            = GPIO_PULL_UP;
        /* Set the pin drive current to 4MA*/
        GPIO_InitStructure.GPIO_Current         = GPIO_DS_4MA;
        GPIO_InitStructure.GPIO_Slew_Rate         = GPIO_SLEW_RATE_FAST;
        /* Initialize GPIO */
        GPIO_Peripheral_Initialize(SWI2C1_SDA_PORT, &GPIO_InitStructure);

        SWI2C1_SDA_HIGH;
}


static void SwI2c_Sda_Config_Input_mode(void)
{
        GPIO_InitType GPIO_InitStructure;
       
        /* Assign default value to GPIO_InitStructure structure */
        GPIO_Structure_Initialize(&GPIO_InitStructure);

        /* Select the GPIO pin to control */
        GPIO_InitStructure.Pin                  = SWI2C1_SDA_PIN;
        /* Set pin mode to general push-pull output */
        GPIO_InitStructure.GPIO_Mode            = GPIO_MODE_INPUT;
    GPIO_InitStructure.GPIO_Pull            = GPIO_PULL_UP;
        /* Set the pin drive current to 4MA*/
        GPIO_InitStructure.GPIO_Current         = GPIO_DS_4MA;
        GPIO_InitStructure.GPIO_Slew_Rate         = GPIO_SLEW_RATE_FAST;
        /* Initialize GPIO */
        GPIO_Peripheral_Initialize(SWI2C1_SDA_PORT, &GPIO_InitStructure);
}

void SwI2c_Start(void)
{
    SWI2C1_SCL_LOW;                                                         //先使SCL = 0, 为SDA上的电平改变做准备
    SwI2c_Delay_Us(2);
    SWI2C1_SDA_HIGH;                                                         //SDA = 1
    SwI2c_Sda_Config_Output_mode();                      //sda OD 初始化输出1,
    SWI2C1_SDA_HIGH;                                                         //SDA = 1, 此时SDA的电平变化对通讯双方没有影响
    SwI2c_Delay_Us(2);
    SWI2C1_SCL_HIGH;                                                         //SCL = 1
    SwI2c_Delay_Us(2);
    SWI2C1_SDA_LOW;                                                         //SDA=0,产生下降沿,启动IIC通讯
    SwI2c_Delay_Us(2);
    SWI2C1_SCL_LOW;                                                                //SCL=0,为SDA上的电平改变做准备
    SwI2c_Delay_Us(2);
}

void SwI2c_Stop(void)                                                        //停止信号
{
        SWI2C1_SCL_LOW ;                                                         //先使SCL = 0, 为SDA上的电平改变做准备
    SwI2c_Delay_Us(2);
    SWI2C1_SDA_HIGH;                                                         //SDA = 1
    SwI2c_Sda_Config_Output_mode();                      //sda OD 初始化输出1,
    SWI2C1_SDA_HIGH;
    SwI2c_Delay_Us(2);
    SWI2C1_SDA_LOW;                                                         //SDA=0,此时SDA的电平变化对通讯双方没有影响
    SwI2c_Delay_Us(2);
    SWI2C1_SCL_HIGH;                                                         //SCL=1
    SwI2c_Delay_Us(2);
    SWI2C1_SDA_HIGH;                                                         //SDA=1,结束IIC通讯
    SwI2c_Delay_Us(2);                                //SDA在结束后维持在高电平,如果有干扰脉冲产生而使得SDA
                                                               //变低,则干扰过后会恢复高电平. 此时SCL如果因干扰而处于
    return;   
}

MI_U8 SwI2c_Wait_Ack(void)                                                //等待应答信号:0-应答;1-非应答
{
    MI_U8 uc_time = 0;
        SwI2c_Sda_Config_Input_mode();                                 //SDA定义为输入

        SWI2C1_SDA_HIGH;
        SwI2c_Delay_Us(1);
        SWI2C1_SCL_HIGH;
        SwI2c_Delay_Us(1);

    while (SWI2C1_SDA_READ)
    {
      uc_time++;
      if (uc_time > 250)
      {
            SwI2c_Stop();
            return 1;
      }
    }
        SWI2C1_SCL_LOW;
        return 0;
}

void SwI2c_Ack(void)                                        //产生 ACK 应答
{
    SWI2C1_SDA_HIGH;                                         //SDA = 1
    SwI2c_Sda_Config_Output_mode();   //sda OD 初始化输出1,
    SWI2C1_SDA_HIGH;                                         //SDA输出高电平
    SWI2C1_SDA_LOW;                                         //清SDA="0",CPU发低电平确认信号,
    SwI2c_Delay_Us(2);
    SWI2C1_SCL_HIGH;                                         //置SCL="1", 产生上升沿,发送一位确认数据
    SwI2c_Delay_Us(2);
    SWI2C1_SCL_LOW;                                         //清SCL="0",为SDA上的电平改变做准备
    return;
}

void Swi2c_No_Ack(void)                                        //产生 NACK 非应答
{
    SWI2C1_SDA_HIGH;                                         //SDA = 1
    SwI2c_Sda_Config_Output_mode();            //sda OD 初始化输出1,
    SWI2C1_SDA_HIGH;                                         //置SDA=1, CPU发"高电平非应答确认"信号
    SwI2c_Delay_Us(2);
    SWI2C1_SCL_HIGH;                                         //置SCL="1", 产生上升沿,发送一位确认数据
    SwI2c_Delay_Us(2);
    SWI2C1_SCL_LOW;                                         //清SCL="0",为SDA上的电平改变做准备
    return;
}

//IIC 发送一个字节
MI_BOOL SwI2c_Write_Byte(MI_U8 data)
{
    MI_U8 m;                        //SDA = 1
    SwI2c_Sda_Config_Output_mode(); //sda OD 初始化输出1,

    for (m = 0; m < 8; m++)
    {
      SWI2C1_SCL_LOW;                         //SCL=0,为SDA上的电平改变做准备
      SwI2c_Delay_Us(2);

      if (data & BIT7)                         //由最高位开始发送
      {
            SWI2C1_SDA_HIGH;
      }
      else
      {
            SWI2C1_SDA_LOW;
      }

      SwI2c_Delay_Us(2);
      SWI2C1_SCL_HIGH;                         //SCL="1",产生上升沿,发送一位数据
      SwI2c_Delay_Us(2);
      data <<= 1;
    }

    SWI2C1_SCL_LOW;                                 //清SCL="0",产生下降沿, 器件使SDA="0"
    SwI2c_Delay_Us(2);
    SwI2c_Sda_Config_Input_mode();         //SDA改为输入,准备接收确认应答

    SWI2C1_SCL_HIGH;                               //SCL="1",让CPU在此期间读取SDA上的信号

    for (m = 0; m < 8; m++)
    {
      SwI2c_Delay_Us(2);

      if (SWI2C1_SDA_READ == 0)
      {
            SWI2C1_SCL_LOW;                 //清SCL="0",为SDA上的电平改变做准备
            SwI2c_Delay_Us(2);
            return MI_TRUE;                         //收到正确的低电平应答
      }
    }

        SWI2C1_SCL_LOW;                                 //清SCL="0",为SDA上的电平改变做准备
    return MI_FALSE;
}

//读一个字节
MI_U8 SwI2c_Read_Byte()
{
    MI_U8 m, data;

    SwI2c_Sda_Config_Input_mode();         //SDA改为输入,准备接收数据
    data = 0;

    for (m = 0; m < 8; m++)
    {
      SWI2C1_SCL_LOW;                         //SCL="0",产生下降沿, 器件串出一位数据
      SwI2c_Delay_Us(2);
      SWI2C1_SCL_HIGH;                         //置SCL="1", 让CPU在此期间读取SDA上的信号
      SwI2c_Delay_Us(2);
      data <<= 1;

      if (SWI2C1_SDA_READ)
      {
            data |= BIT0;
      }
      else
      {
            data &= (~BIT0);
      }
    }

    SWI2C1_SCL_LOW;                                 //清SCL="0",为SDA上的电平改变做准备
    return data;
}

MI_BOOL SwI2c_Device_Write_Data(MI_U8 device_addr,MI_U8 *reg_addr,
                                    uint16_t reg_len,const MI_U8 *buf,MI_U8 len)
{
           SwI2c_Start();
    SwI2c_Write_Byte(device_addr);

        while (reg_len != 0)
    {
      if (SwI2c_Write_Byte(*reg_addr++) == MI_FALSE) //发送一字节数据
      {
            SwI2c_Stop();
            return MI_FALSE;
      }
      reg_len--;
    }

    while (len != 0)
    {
      if (SwI2c_Write_Byte( *buf++) == MI_FALSE) //发送一字节数据
      {
            SwI2c_Stop();
            return MI_FALSE;
      }
      len--;
    }

    SwI2c_Stop();
    return MI_TRUE;
}

MI_U8 SwI2c_Device_Read_Data(MI_U8 device_addr,MI_U8 *reg_addr,
                                    uint16_t reg_len, MI_U8 *buf,MI_U8 len)
{
    SwI2c_Start();
    SwI2c_Write_Byte(device_addr);
    while (reg_len != 0)
    {
      if (SwI2c_Write_Byte(*reg_addr++) == MI_FALSE) //发送一字节数据
      {
            SwI2c_Stop();
            return MI_FALSE;
      }
      reg_len--;
    }

    SwI2c_Start();
    SwI2c_Write_Byte(device_addr + 1);

    while (1)
    {
      *buf++ = SwI2c_Read_Byte();                                        //接收一字节
      len--;

      if (0 == len)
      {
            break;
      }
      SwI2c_Ack();                                                                 //未读完,CPU发低电平确认应答信号,以便读取下8位数据
    }

    Swi2c_No_Ack();                                                                 //已读完所有的数据,CPU发"高电平非应答确认"信号
    SwI2c_Stop();
    return MI_TRUE;
}

.h文件#ifndef _SWI2C_H__
#define _SWI2C_H__


void SwI2c_Gpio_Init(void);
MI_U8 SwI2c_Device_Read_Data(MI_U8 device_addr,MI_U8 *reg_addr,
                                    uint16_t reg_len,MI_U8 *buf,MI_U8 len);
MI_BOOL SwI2c_Device_Write_Data(MI_U8 device_addr,MI_U8 *reg_addr,
                                    uint16_t reg_len,const MI_U8 *buf,MI_U8 len);                     
#endif//_SWI2C_H__
3、延时函数换成自己的即可,楼主为了准确性,延时函数开了一个定时器。
prescaler = 64-1 ,1us加一次,Period设置最大,最大延时数就是这个。
相应代码如下void TIM3_Init(void)
{
    RCC_APB1_Peripheral_Clock_Enable(RCC_APB1_PERIPH_TIM3);
    TIM_TimeBaseInitType TIM_TimeBaseStructure;
    TIM_Base_Struct_Initialize(&TIM_TimeBaseStructure);
    TIM_TimeBaseStructure.Period    = 65535-1;
    TIM_TimeBaseStructure.Prescaler = 64-1;
    TIM_TimeBaseStructure.ClkDiv    = 0;
    TIM_TimeBaseStructure.CntMode   = TIM_CNT_MODE_UP;

    TIM_Base_Initialize(TIM3, &TIM_TimeBaseStructure);
    TIM_Base_Reload_Mode_Set(TIM2, TIM_PSC_RELOAD_MODE_IMMEDIATE);
}

void TIM3_Delay_Us(__IO uint32_t us)
{
    TIM_Base_Count_Set(TIM3,0);
    TIM_On(TIM3);
    while(TIM_Base_Count_Get(TIM3) < us);
    TIM_Off(TIM3);
}



申小林一号 发表于 2024-10-30 20:34

软件一致性很强
页: [1]
查看完整版本: 软件模拟i2c的使用