STM32模拟IIC

本文介绍了一个基于STM32微控制器的I2C通信实现方案,通过软件模拟I2C总线进行数据收发。主要内容包括:I2C通信的初始化配置、I2C基本操作函数如启动与停止、数据的发送与接收等。

IIC.h

#ifndef _MD_IIC_h_
#define _MD_IIC_h_

#include "stm32f10x.h"

/* Pin Definiton ---------- */

#define IIC_GPIO (GPIOC)
#define IIC_GOIO_SDA (GPIOC)
#define IIC_GPIO_SCL (GPIOC)
#define IIC_SDA (GPIO_Pin_2)
#define IIC_SCL (GPIO_Pin_1)

//初始化调用IIC_GPIO_Configuration( IIC_GOIO_SDA , IIC_SDA , IIC_GPIO_SCL , IIC_SCL );
extern void IIC_GPIO_Configuration( GPIO_TypeDef * GPIOx_SDA , uint16_t SDA_Pin , GPIO_TypeDef * GPIOx_SCL , uint16_t SCL_Pin );

//使用软件模拟I2C
#define SET_SDA          { GPIO_SetBits  ( IIC_GPIO , IIC_SDA ); }
#define RESET_SDA        { GPIO_ResetBits( IIC_GPIO , IIC_SDA ); }

#define SET_SCL          { GPIO_SetBits  ( IIC_GPIO , IIC_SCL ); }
#define RESET_SCL        { GPIO_ResetBits( IIC_GPIO , IIC_SCL) ; }

#define IIC_SDA_STATE    (IIC_GPIO->IDR&IIC_SDA)
#define IIC_SCL_STATE    (IIC_GPIO->IDR&IIC_SDA)

#define IIC_DELAY        { IIC_Delay(); }

enum IIC_REPLAY_ENUM
{
    IIC_NACK = 0,
    IIC_ACK = 1
};

enum IIC_BUS_STATE_ENUM
{
    IIC_BUS_READY = 0,
    IIC_BUS_BUSY=1,
    IIC_BUS_ERROR=2
};

//IIC 延时
extern void IIC_Delay(void);
//IIC 启动函数
extern u8 IIC_Start(void);
//IIC 停止函数
extern void IIC_Stop(void);
//IIC 发送动作
extern void IIC_SendACK(void);
//IIC 停止动作
extern void IIC_SendNACK(void);
//IIC 发送单字节
extern u8 IIC_SendByte(u8 Data);
//IIC 接收单字节
extern u8 IIC_RecvByte(void);
//IIC 写入单字节
extern void Single_Write_IIC(u8 SlaveAddress,u8 REG_Address,u8 REG_data);
//IIC 读取单字节
extern u8 Single_Read_IIC(u8 SlaveAddress, u8 REG_Address);


//GPIO 过滤器
extern uint16_t GPIO_Filter( GPIO_TypeDef * GPIOx );

#endif /* _MD_IIC_H_ */

/* End of file ------------------------------------------------------------- */

IIC.c

#include "stm32f10x.h"
#include "MD_IIC.h"

void IIC_GPIO_Configuration( GPIO_TypeDef * GPIOx_SDA , uint16_t SDA_Pin , GPIO_TypeDef * GPIOx_SCL , uint16_t SCL_Pin )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    uint32_t RCC_GPIOx_SDA = 0;
    uint32_t RCC_GPIOx_SCL = 0;

    //得到滤波后的引脚端口
    RCC_GPIOx_SDA = GPIO_Filter( GPIOx_SDA );
    RCC_GPIOx_SCL = GPIO_Filter( GPIOx_SCL );

    //使能时钟
    RCC_APB2PeriphClockCmd(RCC_GPIOx_SDA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_GPIOx_SCL,ENABLE);

    //配置引脚
    GPIO_InitStructure.GPIO_Pin = SDA_Pin;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
    GPIO_Init(GPIOx_SDA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = SCL_Pin;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
    GPIO_Init(GPIOx_SCL, &GPIO_InitStructure);

    //初始化ICC的模式
    SET_SDA;
    SET_SCL;  
}

void IIC_Delay(void)
{
    u32 i = 5;
    while( i-- );
}

u8 IIC_Start(void)
{
    SET_SDA;
    IIC_DELAY;

    SET_SCL;
    IIC_DELAY;

    if( IIC_SDA_STATE == RESET )
    {
        return IIC_BUS_BUSY;
    }

    RESET_SDA;
    IIC_DELAY;

    RESET_SCL;
    IIC_DELAY;

    if( IIC_SDA_STATE == SET )
    {
        return IIC_BUS_ERROR;
    }

    return IIC_BUS_READY;
}

void IIC_Stop(void)
{
    RESET_SDA;
    IIC_DELAY;

    SET_SCL;
    IIC_DELAY;

    SET_SDA;
    IIC_DELAY;
}

/* 收到数据,发送NACK - */
void IIC_SendNACK(void)
{
    RESET_SDA;
    IIC_DELAY;
    SET_SCL;
    IIC_DELAY;
    RESET_SCL; 
    IIC_DELAY; 
}

/* 收到数据,发送ACK -- */
void IIC_SendACK(void)
{
    SET_SDA;
    IIC_DELAY;
    SET_SCL;
    IIC_DELAY;
    RESET_SCL; 
    IIC_DELAY;
}

/* 发送一个字节 ----------------------------------------------------------- */
u8 IIC_SendByte(u8 Data)
{
     u8 i;
     RESET_SCL;
     for(i=0;i<8;i++)
     {  
        //数据建立
        if(Data&0x80)
        {
            SET_SDA;
        }
        else
        {
            RESET_SDA;
        } 
        Data<<=1;
        IIC_DELAY;    //数据建立保持一定延时
        SET_SCL;      //产生一个上升沿[正脉冲] 
        IIC_DELAY;
        RESET_SCL;
        IIC_DELAY;    //延时,防止SCL还没变成低时改变SDA,从而产生START/STOP信号 
     }
     //接收从机的应答 
     SET_SDA; 
     IIC_DELAY;
     SET_SCL;
     IIC_DELAY;   
     if(IIC_SDA_STATE)
     {
        RESET_SCL;
        return IIC_NACK;
     }
     else
     {
        RESET_SCL;
        return IIC_ACK;  
     }    
}


/* 接收一个字节 ----------------------------------------- */
u8 IIC_RecvByte(void)
{
     u8 i,Dat = 0;
     SET_SDA;
     RESET_SCL; 
     Dat=0;
     for(i=0;i<8;i++)
     {
        SET_SCL;           //产生时钟上升沿[正脉冲],让从机准备好数据 
        IIC_DELAY; 
        Dat<<=1;
        if(IIC_SDA_STATE)  //读引脚状态
        {
            Dat|=0x01; 
        }   
        RESET_SCL;         //准备好再次接收数据  
        IIC_DELAY;         //等待数据准备好         
     }
     return Dat;
}

/* 单字节写入 -------------------------------------------------------- */
void Single_Write_IIC(u8 SlaveAddress,u8 REG_Address,u8 REG_data)
{
    IIC_Start();                  //起始信号
    IIC_SendByte(SlaveAddress);   //发送设备地址+写信号
    IIC_SendByte(REG_Address);    //内部寄存器地址, //请参考中文pdf22页 
    IIC_SendByte(REG_data);       //内部寄存器数据, //请参考中文pdf22页 
    IIC_Stop();                   //发送停止信号
}

/* 单字节读取 --------------------------------------------------------- */
u8 Single_Read_IIC(u8 SlaveAddress, u8 REG_Address)
{  
    u8 REG_data;
    IIC_Start();                          //起始信号
    IIC_SendByte(SlaveAddress);           //发送设备地址+写信号
    IIC_SendByte(REG_Address);            //发送存储单元地址,//从0开始 
    IIC_Start();                          //起始信号
    IIC_SendByte(SlaveAddress+1);         //发送设备地址+读信号
    REG_data = IIC_RecvByte();            //读出寄存器数据
    IIC_SendACK();   
    IIC_Stop();                           //停止信号
    return REG_data; 
}


//引脚端口过滤器 返回值为 引脚端口的时钟编号
uint16_t GPIO_Filter( GPIO_TypeDef * GPIOx )
{    
    uint32_t RCC_GPIOx = 0; 

    if( GPIOx == GPIOA )
    {
        RCC_GPIOx = RCC_APB2Periph_GPIOA;
    }
    else if( GPIOx == GPIOA )
    {
        RCC_GPIOx = RCC_APB2Periph_GPIOA;
    }
    else if( GPIOx == GPIOB )
    {
        RCC_GPIOx = RCC_APB2Periph_GPIOB;
    }
    else if( GPIOx == GPIOC )
    {
        RCC_GPIOx = RCC_APB2Periph_GPIOC;
    }
    else if( GPIOx == GPIOD )
    {
        RCC_GPIOx = RCC_APB2Periph_GPIOD;
    }
    else if( GPIOx == GPIOE )
    {
        RCC_GPIOx = RCC_APB2Periph_GPIOE;
    }
    else if( GPIOx == GPIOF )
    {
        RCC_GPIOx = RCC_APB2Periph_GPIOF;
    }
    else if( GPIOx == GPIOG )
    {
        RCC_GPIOx = RCC_APB2Periph_GPIOG;
    }

    return RCC_GPIOx;
}

### STM32 模拟 IIC (I²C) 协议实现方法 #### 开启时钟配置 为了使能STM32上的IIC功能,需先开启相应的外设时钟。对于通过GPIO模拟IIC的情况,同样需要打开对应的GPIO端口时钟。 ```c RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); ``` 上述代码片段展示了如何启用I2C2以及关联的GPIO端口时钟[^1]。 #### GPIO初始化设置 接下来定义SCL和SDA引脚模式为开漏输出,并确保它们处于高电平状态: ```c void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; // 配置PB6作为SCL GPIO_InitStruct.Pin = GPIO_PIN_6; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // 配置PB7作为SDA GPIO_InitStruct.Pin = GPIO_PIN_7; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); IIC_SCL_HIGH(); IIC_SDA_HIGH(); } ``` 这段程序完成了对两个关键引脚的功能设定并拉高初始电压水平[^5]。 #### 发送起始条件 当主机想要发起一次传输过程时,会发出一个特殊的起始信号给所有连接到该总线上的从机识别。 ```c void IIC_Start(void) { IIC_SDA_OUT(); /* 设置SDA为输出 */ IIC_SCL_HIGH(); delay_us(4); /* tHD;STA >= 4us */ IIC_SDA_HIGH(); delay_us(4); /* tSU;STA >= 4us */ IIC_SDA_LOW(); delay_us(4); /* tLOW;(START) >= 4.7us */ IIC_SCL_LOW(); delay_us(4); /* tHD;DAT >= 4us */ } ``` 此函数实现了标准规定的启动序列,即在保持SCL高位期间将SDA由高变低的操作[^4]。 #### 结束条件处理 每次数据交换完成后都需要发送停止位告知其他节点当前事务已经结束。 ```c void IIC_Stop(void) { IIC_SDA_OUT(); /* 设置SDA为输出 */ IIC_SCL_LOW(); delay_us(4); /* tSU;STO >= 4us */ IIC_SDA_LOW(); delay_us(4); /* tBUF >= 4.7us */ IIC_SCL_HIGH(); delay_us(4); /* tHIGH(min) >= 4us */ IIC_SDA_HIGH(); delay_us(4); /* tSU;STO >= 4us */ } ``` 这里描述了释放总线控制权的标准流程,在SCL变为高之前让SDA先行回到高电平位置。 #### 数据收发操作 针对每一个字节的数据交互都遵循特定的时间安排来进行同步化管理。 ```c uint8_t IIC_SendByte(uint8_t byte) { uint8_t i, ack; for(i=0 ; i<8 ; i++) { if(byte&0x80) IIC_SDA_HIGH(); else IIC_SDA_LOW(); byte <<= 1; IIC_SCL_HIGH(); delay_us(1); IIC_SCL_LOW(); delay_us(1); } IIC_SDA_IN(); /* 准备接收ACK/NACK */ IIC_SCL_HIGH(); delay_us(1); ack=(READ_PIN(GPIOB, GPIO_PIN_7)==SET)?ERROR:SUCCESS;/* 判断是否收到应答*/ IIC_SCL_LOW(); delay_us(1); return ack; } uint8_t IIC_ReadByte(unsigned char last) { unsigned char i,receive=0; IIC_SDA_IN(); /* 将SDA设置成输入*/ for(i=0;i<8;i++) { receive<<=1; IIC_SCL_LOW(); delay_us(1); IIC_SCL_HIGH(); delay_us(1); if(READ_PIN(GPIOB, GPIO_PIN_7)) receive |= 0x01; } if(!last){ IIC_NAck(); /* 如果不是最后一个字节,则返回NACK */ }else{ IIC_Ack(); /* 否则返回ACK */ } return receive; } ``` 这两部分分别负责向目标设备写入单个字符以及从中读取信息,同时包含了必要的握手确认机制以维持通信链路稳定运行[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值