STM32多IIC设备的软件IIC实现


前言

在项目中遇到要驱动10个IIC设备其中有8个地址相同并且无法更改的情况,使用宏定义形式的IO口操作不能很好的满足需求,于是想到了面向对象的写法,在C语言中虽然没有对象这一概念,但对象也只是相关的数据结构和一组方法的封装,在C程序中可以提过将相关数据结构封装到结构体中,函数传入应该结构体指针的方式来实现类似的面向对象写法。

一、Software_IIC.h

将IIC协议信息抽象到IIC_TypeDef结构体中,包括一个SDA、SCL引脚以及一个地址信息,这样在使用软件IIC时只需要定义IIC_TypeDef的结构体就可以了,并且可以很好的复用IIC操作的代码。
如我这种8个地址相同的情况就可以定义如下IIC设备:

IIC_Typedef IIC1 = {
	{RCC_AHB1Periph_GPIOH, GPIOH, 4},
	{RCC_AHB1Periph_GPIOH, GPIOH, 5},
	0xA0,
};
IIC_Typedef IIC2 = {
	{RCC_AHB1Periph_GPIOH, GPIOH, 4},
	{RCC_AHB1Periph_GPIOH, GPIOH, 6},
	0xA0,
};
IIC_Typedef IIC3 = {
	{RCC_AHB1Periph_GPIOH, GPIOH, 4},
	{RCC_AHB1Periph_GPIOH, GPIOH, 7},
	0xA0,
};
IIC_Typedef IIC4 = {
	{RCC_AHB1Periph_GPIOH, GPIOH, 4},
	{RCC_AHB1Periph_GPIOH, GPIOH, 5},
	0xA8,
};
/** 
 * @Author: 时间世纪
 * @Date: 2021-08-06 13:35:00
 * @Description: 软件IIC驱动程序
 */
#ifndef _SOFTWARE_IIC_H_
#define _SOFTWARE_IIC_H_

#include "HAL_Driver.h"

#define IIC_DELAY_TIME 1 //延时时间

//一个软件IIC设备
typedef	struct
{
	HAL_GPIO_TypeDef	SCL;//包含操作IO口的信息
	HAL_GPIO_TypeDef	SDA;
	uint8_t ADD;//设备地址,内部使用时没有移位,只设置了读写位
} IIC_TypeDef;

/** 
 * @brief 初始化一个IIC设备
 * @param IIC_TypeDef *pIIC
 * @retval: 
 */
extern void IIC_Init(IIC_TypeDef * const pIIC);
/** 
 * @brief 开始一次IIC通信
 * @param IIC_TypeDef *pIIC
 * @retval 
 */
extern void IIC_Start(IIC_TypeDef *const pIIC);
/** 
 * @brief 停止一次IIC通信
 * @param IIC_TypeDef *pIIC
 * @retval 
 */
extern void IIC_Stop(IIC_TypeDef *const pIIC);
/** 
 * @brief 等待从机应答
 * @param IIC_TypeDef *pIIC
 * @retval 0:有应答, 1:无应答
 */
extern uint8_t IIC_Sack(IIC_TypeDef *const pIIC);
/** 
 * @brief 发送一字节数据
 * @param IIC_TypeDef *pIIC
 * @param uint8_t dat
 * @retval 
 */
extern void IIC_SendByte(IIC_TypeDef *const pIIC, uint8_t dat);
/** 
 * @brief 发送多个数据
 * @param IIC_TypeDef *pIIC
 * @param uint8_t * dat
 * @param uint8_t len 长度
 * @retval 0:发送成功,1: 失败
 */
extern uint8_t IIC_Send(IIC_TypeDef *const pIIC, uint8_t *dat, uint8_t len);
/** 
 * @brief 读取一字节数据
 * @param IIC_TypeDef *pIIC
 * @retval 
 */
extern uint8_t IIC_ReadByte(IIC_TypeDef *const pIIC);
/** 
 * @brief 读取多个数据
 * @param IIC_TypeDef *pIIC
 * @param uint8_t * dat
 * @param uint8_t len 长度
 */
extern void IIC_Read(IIC_TypeDef *const pIIC, uint8_t *dat, uint8_t len);
/** 
 * @brief 发送写地址
 * @param IIC_TypeDef *pIIC
 * @retval 0:有应答,1:无应答
 */
__STATIC_FORCEINLINE uint8_t IIC_SendWriteAddress(IIC_TypeDef *const pIIC)
{
    IIC_SendByte(pIIC, pIIC->ADD);
    return IIC_Sack(pIIC);
}

/** 
 * @brief 发送读地址
 * @param IIC_TypeDef *pIIC
 * @retval 0:有应答,1:无应答
 */
__STATIC_FORCEINLINE uint8_t IIC_SendReadAddress(IIC_TypeDef *const pIIC)
{
    IIC_SendByte(pIIC, pIIC->ADD | 0x01);
    return IIC_Sack(pIIC);
}

/** 
 * @brief 发送一个字节并检查从机应答
 * @param IIC_TypeDef *pIIC
 * @param uint8_t dat
 * @retval 0:有应答,1:无应答
 */
__STATIC_FORCEINLINE uint8_t IIC_SendAndSack(IIC_TypeDef *const pIIC, uint8_t dat)
{
    IIC_SendByte(pIIC, dat);
    return IIC_Sack(pIIC);
}

/** 
 * @brief 读取一字节数据并进行应答操作
 * @param IIC_TypeDef *pIIC
 * @param uint8_t ack 是否对从机进行应答继续读取,0:否停止读取,1:继续读取
 * @retval 
 */
__STATIC_FORCEINLINE  uint8_t IIC_ReadAndAck(IIC_TypeDef *const pIIC, uint8_t ack)
{
	uint8_t dat = 0;
	dat = IIC_ReadByte(pIIC);
	IIC_Ack(IIC, ack);
	return dat;
}
#endif

二、起始时序

注意:程序未采用STM32的HAL库,程序中HAL_GPIO_High,HAL_GPIO_Low函数仅代表当前这款单片机IO口的操作的封装,并向其传递控制IO口的必要参数。
起始时序为在SCL为高时SDA拉低启动一次发送。

/** 
 * @brief 启动一次传输,在SCL高电平时SDA高变低开始传输
 * @param IIC_TypeDef *pIIC
 * @retval 
 */
void IIC_Start(IIC_TypeDef * const pIIC)
{
    HAL_GPIO_SetOutput(&pIIC->SDA);
    HAL_GPIO_High(&pIIC->SDA);
    HAL_GPIO_High(&pIIC->SCL);//拉高SCL
    Delay_us(IIC_DELAY_TIME);
    HAL_GPIO_Low(&pIIC->SDA);//拉低SDA启动一次传输
    Delay_us(IIC_DELAY_TIME);
    HAL_GPIO_Low(&pIIC->SCL);//拉低SCL准备传输
    Delay_us(IIC_DELAY_TIME);
}

三、停止时序

停止时序为SCL为高时SDA由低变高结束一次发送。

/** 
 * @brief 停止本次传输,在SCL高电平时SDA低变高停止传输
 * @param IIC_TypeDef *pIIC
 * @retval 
 */
void IIC_Stop(IIC_TypeDef *const pIIC)
{
    HAL_GPIO_SetOutput(&pIIC->SDA);
    HAL_GPIO_Low(&pIIC->SDA);//拉低SDA
    Delay_us(IIC_DELAY_TIME);
    HAL_GPIO_High(&pIIC->SCL);//拉高SCL
    Delay_us(IIC_DELAY_TIME);
    HAL_GPIO_High(&pIIC->SDA);//拉高SDA结束一次传输
    Delay_us(IIC_DELAY_TIME);
    HAL_GPIO_Low(&pIIC->SCL);
    Delay_us(IIC_DELAY_TIME);
}

四、发送数据时序

发送时序为在SCL为低时SDA可变换,SCL拉高从机读取SDA。

/** 
 * @brief 发送一字节数据
 * @param IIC_TypeDef *pIIC
 * @param uint8_t dat
 */
void IIC_SendByte(IIC_TypeDef *const pIIC, uint8_t dat)
{
    HAL_GPIO_SetOutput(&pIIC->SDA);
    for (uint8_t i = 0; i < 8; i++)
    {
        HAL_GPIO_Low(&pIIC->SCL);//拉低SCL
        Delay_us(IIC_DELAY_TIME);
        if (dat & (0x80 >> i))//传输数据
            HAL_GPIO_High(&pIIC->SDA);
        else
            HAL_GPIO_Low(&pIIC->SDA);
        Delay_us(IIC_DELAY_TIME);
        HAL_GPIO_High(&pIIC->SCL);//拉高SCL从机读取数据
        Delay_us(IIC_DELAY_TIME);
    }
    HAL_GPIO_Low(&pIIC->SCL);
    Delay_us(IIC_DELAY_TIME);
}

五、读取时序

在SCL为低时从机将数据放在SDA上。

/** 
 * @brief 接收一字节数据
 * @param IIC_TypeDef *pIIC
 * @retval 
 */
uint8_t IIC_ReadByte(IIC_TypeDef *const pIIC)
{
    uint8_t dat = 0;

    HAL_GPIO_SetInput(&pIIC->SDA);
    for (uint8_t i = 0; i < 8; i++)
    {
        HAL_GPIO_Low(&pIIC->SCL);
        Delay_us(IIC_DELAY_TIME);
        HAL_GPIO_High(&pIIC->SCL);
        Delay_us(IIC_DELAY_TIME);
        dat = (dat << 1) | HAL_GPIO_Read(&pIIC->SDA);
    }
    HAL_GPIO_Low(&pIIC->SCL);
    return dat;
}

六、应答时序

/** 
 * @brief 等待从机应答,在SCL高时从机会拉低SDA进行应答
 * @param IIC_TypeDef *pIIC
 * @retval 0:有应答,1:无应答
 */
uint8_t IIC_Sack(IIC_TypeDef *const pIIC)
{
    uint8_t result;

    HAL_GPIO_SetInput(&pIIC->SDA);//设置SDA为输入
    Delay_us(IIC_DELAY_TIME);
    HAL_GPIO_High(&pIIC->SCL);
    Delay_us(IIC_DELAY_TIME);
    for (result = 0; result <= 10; result++)
    {
        if (!HAL_GPIO_Read(&pIIC->SDA))
            break;
        Delay_us(IIC_DELAY_TIME);
    }
    HAL_GPIO_Low(&pIIC->SCL);
    Delay_us(IIC_DELAY_TIME);
    return result >= 10 ? 1 : 0;
}

/** 
 * @brief 产生一个应答信号对从机应答
 * @param IIC_TypeDef *pIIC
 * @param uint8_t ack 0: 结束不应答,1:应答
 * @retval: 
 */
static void IIC_Ack(IIC_TypeDef *const pIIC, uint8_t ack)
{
    HAL_GPIO_Low(&pIIC->SCL);
    Delay_us(IIC_DELAY_TIME);
    HAL_GPIO_SetOutput(&pIIC->SDA);
    if (ack)
        HAL_GPIO_Low(&pIIC->SDA);
    else
        HAL_GPIO_High(&pIIC->SDA);
    Delay_us(IIC_DELAY_TIME);
    HAL_GPIO_High(&pIIC->SCL);
    Delay_us(IIC_DELAY_TIME);
    HAL_GPIO_Low(&pIIC->SCL);
    Delay_us(IIC_DELAY_TIME);
}

程序代码

  • 8
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
STM32中,软件模拟IIC是一种常见的通信方式。相比硬件IIC软件模拟IIC有一些优点。首先,软件模拟IIC方便移植,可以兼容各种不同的MCU,只需要修改引脚设置即可。而硬件IIC则需要重新设计和调整。其次,使用软件模拟IIC可以更好地理解IIC的时序和流程,有利于学习。\[2\] 在使用软件模拟IIC时,需要使用相应的头文件和函数。例如,在STM32中,可以使用以下头文件和函数来实现软件模拟IIC: ```c #ifndef __MYIIC_H #define __MYIIC_H #include "sys.h" #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 IIC_SCL PBout(8) //SCL #define IIC_SDA PBout(9) //SDA #define READ_SDA PBin(9) //输入SDA void IIC_Init(void); //初始化IIC的IO口 void IIC_Start(void); //发送IIC开始信号 void IIC_Stop(void); //发送IIC停止信号 void IIC_Send_Byte(u8 txd); //IIC发送一个字节 u8 IIC_Read_Byte(unsigned char ack); //IIC读取一个字节 u8 IIC_Wait_Ack(void); //IIC等待ACK信号 void IIC_Ack(void); //IIC发送ACK信号 void IIC_NAck(void); //IIC不发送ACK信号 #endif ``` 以上是一个示例的头文件和函数,你可以根据自己的需求进行修改和使用。\[3\] #### 引用[.reference_title] - *1* [软件模拟IIC通信(STM32)](https://blog.csdn.net/m0_58832575/article/details/125478913)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [STM32通信口(二)IIC--软件模拟](https://blog.csdn.net/a568713197/article/details/80156868)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值