基于STM32的软件IIC实现

嵌入式_基于STM32的软件IIC实现

本文是基于STM32标准库的IIC实现,并再此基础上实现了AT24C02的读写,关于IIC的原理简介请参见上篇IIC协议理解,



前言

基于STM32F407 标准库的软件IIC实现方式,亲测可用。


提示:以下是本篇文章正文内容,下面案例可供参考

一、头文件简介

主要定义SDA和SCL的引脚定义,输入输出模式,高低电平函数,延时函数等

#ifndef __I2C_S_H
#define __I2C_S_H

#include "Gpio_Dev.h"			//包含初始化GPIO,初始化时钟

/*******************************************
 *Soft I2C 
 ********************************************/
#define SDA_IN()    {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;}//PB9 输入模式
#define SDA_OUT()   {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;}//PB9 输出模式

#define SW_I2C_SCL_PORT         GPIOB
#define SW_I2C_SCL_PIN          GPIO_Pin_8
#define SW_I2C_SDA_PORT         GPIOB
#define SW_I2C_SDA_PIN          GPIO_Pin_9

#define I2C_WAIT_TIMEOUT     	(uint8_t)250	//等待时间
#define I2C_DELAY             	(uint8_t)5		//延时时间

#define I2C_N_ACK               (0U)   //不发ACK
#define I2C_ACK 				(1U)   //发送ACK

#define SW_I2C_SCL_LOW          GPIO_ResetBits(SW_I2C_SCL_PORT,SW_I2C_SCL_PIN) 
#define SW_I2C_SCL_HIGH         GPIO_SetBits(SW_I2C_SCL_PORT,SW_I2C_SCL_PIN) 
#define SW_I2C_SDA_LOW          GPIO_ResetBits(SW_I2C_SDA_PORT,SW_I2C_SDA_PIN) 
#define SW_I2C_SDA_HIGH         GPIO_SetBits(SW_I2C_SDA_PORT,SW_I2C_SDA_PIN)

#define I2C_SDA_STATUS          GPIO_ReadInputDataBit(SW_I2C_SDA_PORT,SW_I2C_SDA_PIN)		//读取SDA状态
#define I2C_SCL_STATUS          GPIO_ReadInputDataBit(SW_I2C_SCL_PORT,SW_I2C_SCL_PIN)		//读取SCL状态

#define SW_I2C_SDA_INPUT        sw_i2c_set_sda_input()		//设置SDA为输入模式
#define SW_I2C_SDA_OUTPUT       sw_i2c_set_sda_output()		//设置SDA为输出模式
#define SW_I2C_SDA_STATUS       sw_i2c_sda_status()			//设置SDA状态
#define SW_I2C_BusInit          sw_i2c_busInit()			//总线初始化
  
#define i2c_delay_us(a)         SysCtlDelayus(a)			//延时函数us

extern void sw_i2c_start(void);
extern void sw_i2c_stop(void);
extern void Send_Ack(void);
extern void Send_N_Ack(void);
extern uint8_t sw_i2c_wait_ack(void);
extern void I2C_Write_Byte(uint8_t aByte);
extern uint8_t I2C_Read_Byte(uint8_t Ack);
extern ReturnType I2C_Send_nByte(uint8_t I2C_Address,uint8_t *Data, uint16_t Datalen);
extern ReturnType I2C_Read_nByte(uint8_t I2C_Address,uint8_t *Data, uint16_t Datalen);

#endif

二、延时函数

IIC延时采用的汇编延时,延时精准,如下为汇编延时函数:
该函数由三条汇编指令构成,
当参数ulcount = 1时,只执行一次三条汇编指令,
执行一条汇编指令时间为:1/系统时钟(MHz),所以ulcount = 1时,延时为delayus = (1/SystemCoreClock(MHz)* 3)us
当参数ulcount = n时,执行n次三条汇编指令,
执行一条汇编指令时间为:1/系统时钟(MHz),所以ulcount = n时,延时为delayus = (1/SystemCoreClock (MHz)* 3 * n)us

函数void SysCtlDelayus(unsigned long ulCount),参数表示延时多少us
计算方法:系统中SystemCoreClock为CPU时钟频率,单位为Hz,所以SystemCoreClock(MHz)= SystemCoreClock(Hz)/1000000
根据上述,
因为延时 (1/SystemCoreClock(MHz)* 3)us需要ucount = 1,
所以延时1us,需要ucount = 1/(1/SystemCoreClock(MHz)* 3),
根据计算方法所述,带入公式,延时1us,需要ucount = 1/(1/(SystemCoreClock(Hz)/1000000)* 3)
化简得:
延时1 us ucount = SystemCoreClock(Hz)/3000000)
延时x us ucount = x * SystemCoreClock(Hz)/3000000),得到函数void SysCtlDelayus(unsigned long ulCount),

用示波器测试,延时比较精准,在使用前需要明确板子时钟是以兆为时钟单位,可打开或关闭系统中断会更加精准

/***************************************************
*@brief:SysTickDelayMs
*@author:Yw  2022-02-12
*@param:None
*@retval:None
****************************************************/
#if defined   (__CC_ARM) /*!< ARM Compiler */
/*
delayus = (1/SystemCoreClock * 3	* ulCount)us
*/
__ASM volatile void SysCtlDelay(unsigned long ulCount)
{
    subs    r0, #1;
    bne     SysCtlDelay;
    bx      lr;
} 

void SysCtlDelayus(unsigned long ulCount)
{
	//CPU_INI_DISABLE();			//关闭系统中断
	SysCtlDelay(ulCount * (SystemCoreClock/3000000)); 
	//CPU_INI_ENABLE();				//打开系统中断
}
#endif /* __CC_ARM */

三、函数实现

1.起始信号和结束信号

代码如下(示例):

/************************************************************************************
*@fuction	:sw_i2c_start
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
void sw_i2c_start(void)
{
    //I2C开始时序:SDL输出模式 SDL = 1 SCL = 1时,SDA由1变成0.
    SDA_OUT();
    SW_I2C_SDA_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SDA_LOW;
    i2c_delay_us(I2C_DELAY);
}

/************************************************************************************
*@fuction	:sw_i2c_stop
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
void sw_i2c_stop(void)
{
    //I2C 停止时序:SDL输出模式 SDL = 0 SCL = 1时,SDA由0变成1.
    SDA_OUT();
    SW_I2C_SDA_LOW;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SDA_HIGH;
}

2.发送ACK与无ACK

代码如下(示例):

/************************************************************************************
*@fuction	:Send_Ack
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
void Send_Ack(void)
{
		//发送ACK就是在SDA=0时,SCL由0变成1
    SDA_OUT();
	SW_I2C_SCL_LOW;
    SW_I2C_SDA_LOW;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_LOW;
    i2c_delay_us(I2C_DELAY);
}

/************************************************************************************
*@fuction	:Send_N_Ack
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
void Send_N_Ack(void)
{
		//发送nACK就是在SDA=1时,SCL由0变成1
    SDA_OUT();
	SW_I2C_SCL_LOW;
    SW_I2C_SDA_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_LOW;
    i2c_delay_us(I2C_DELAY);
}

3.等待ACK

/************************************************************************************
*@fuction	:sw_i2c_wait_ack
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
uint8_t sw_i2c_wait_ack(void)
{
	/*IIC总线上接收方在完成接收一个字节后(8bit),在第9个SCL时钟脉冲的上升沿期间,通过SDA响应ACK(SDA=0)或NACK(SDA=1)信号*/
    uint8_t wait_time = 0;
    uint8_t ack_nack  = 1;
		
	//SDA输入模式
    SDA_IN();
	//等待SDA脚被从机拉低
    while(I2C_SDA_STATUS)
    {
        wait_time++;
        if(wait_time >= I2C_WAIT_TIMEOUT)
        {
			//如果等待时间超时,则退出等待
            ack_nack = 0;
            break;
        }    
    }
	//SCL由0变为1,读入ACK状态
    //如果此时SDA=0,则是ACK
    //如果此时SDA=1,则是NACK
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_HIGH;
    i2c_delay_us(I2C_DELAY);
	//再次将SCL=0,并且将SDA设置为输出
    SW_I2C_SCL_LOW;
    i2c_delay_us(I2C_DELAY);
    SDA_OUT();
    i2c_delay_us(I2C_DELAY);
	//返回ACK状态
    return ack_nack;
}

4.读写一个字节

/************************************************************************************
*@fuction	:I2C_Write_Byte
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
void I2C_Write_Byte(uint8_t aByte)
{		
	//写模式:在SCKL = 0时候,可改变SDA状态
    uint8_t i = 0;
	//将SDA设置为输出模式
    SDA_OUT();
    for(i = 0;i < 8;i++)
    {
		//先将SCL拉低,再改变SDA上的数据
        SW_I2C_SCL_LOW;
        i2c_delay_us(I2C_DELAY);
        if(aByte&0x80)
        {	
			//最高位为1
            SW_I2C_SDA_HIGH; 
        }
        else
        {	//最高位为2
            SW_I2C_SDA_LOW;
        }
        i2c_delay_us(I2C_DELAY);
		//将SCL拉高,数据稳定,等待读取
        SW_I2C_SCL_HIGH;
        i2c_delay_us(I2C_DELAY);
		//数据左移一位,下一循环将下一个bit发送出去
        aByte <<= 1;
    }
	//最后字节完成,将SCL拉低
    SW_I2C_SCL_LOW;
    i2c_delay_us(I2C_DELAY);
}

/************************************************************************************
*@fuction	:I2C_Read_Byte
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
uint8_t I2C_Read_Byte(uint8_t Ack)
{
	//读模式:在SCKL = 1时候,可读取SDA状态
    uint8_t i = 0 ,aByte;
	//SDA为输入模式
    SDA_IN();
    for (i = 0; i < 8; i++)
    {
        aByte <<= 1;
        SW_I2C_SCL_LOW;
        i2c_delay_us(I2C_DELAY);
		//将SCL拉高,读取数据稳定
        SW_I2C_SCL_HIGH;
        i2c_delay_us(I2C_DELAY);
        if(I2C_SDA_STATUS)
        {
            aByte |= 0x01;
        }
		//拉低SCL,解除锁定
        SW_I2C_SCL_LOW;
    }
	//读取一个字节,根据实际情况,是否发送ACK
    SDA_OUT();
    if(Ack)
    {
        Send_Ack();
    }
    else
    {
        Send_N_Ack();
    }
		
    return aByte;
}

5.读写n字节

/************************************************************************************
*@fuction	:I2C_Send_nByte
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
ReturnType I2C_Send_nByte(uint8_t I2C_Address,uint8_t *Data, uint16_t Datalen)
{
    ReturnType ret = E_OK;
    uint16_t i;

	//如果使用的是7bit地址,需要左位移一位
    #ifdef I2C_USE_7BIT_ADDR 
        I2C_Address <<= 1;
    #endif
		
	//启动I2C
    sw_i2c_start();
	//写I2C地址,
    I2C_Write_Byte((uint8_t)I2C_Address);
	//等待ACK
    if(!sw_i2c_wait_ack())
    {
	//无ACK,则发送结束信号并退出
      sw_i2c_stop();
      ret = E_NOT_OK;
    }
    else
    {
			//有ACK,则继续发送
        for (i = 0; i < Datalen; i++)
        {
            I2C_Write_Byte(Data[i]);
            if(!sw_i2c_wait_ack())
            {
                sw_i2c_stop();
                ret = E_NOT_OK;
                break;
            }
        }   
    }
    sw_i2c_stop();

    return ret;
}

/************************************************************************************
*@fuction	:I2C_Read_nByte
*@brief		:
*@param		:--
*@return	:uint8_t
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
ReturnType I2C_Read_nByte(uint8_t I2C_Address,uint8_t *Data, uint16_t Datalen)
{
    ReturnType ret = E_OK;
    uint16_t i;
	
	//如果使用的是7bit地址,需要左位移一位
    #ifdef I2C_USE_7BIT_ADDR 
        I2C_Address <<= 1;
    #endif
	//启动I2C
    sw_i2c_start();
	//写I2C地址
    I2C_Write_Byte((uint8_t)I2C_Address);
	//等待ACK
    if(!sw_i2c_wait_ack())
    {
		//无ACK,则发送结束信号并退出
        sw_i2c_stop();
        ret = E_NOT_OK;
    }
    else
    {	//有ACK,则继续接收
        for(i = 0; i < (Datalen-1); i++)
        {
			//直到倒数第二个字节,都有ACK回复
            Data[i] = I2C_Read_Byte(I2C_ACK);
        }        
    }
	//最后一个字节,无ACK回复,标识不再接收数据
    Data[Datalen-1] = I2C_Read_Byte(I2C_N_ACK);
	//发送停止信号
    sw_i2c_stop();

    return ret;  
}

总结

以上就是基于STM32F103的标准库软件IIC操作,便携易操作。
如有错误,欢迎指正,原创不易,转载留名!

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值