Software i2c

Mastering Software i2c: A Detailed Guide for Embedded Enthusiasts

Introduction

Hey there, embedded system enthusiasts! Today, we’re diving into the world of Software i2c—a nifty way to communicate between your microcontroller and peripheral devices without needing dedicated hardware i2c support. Whether you’re new to embedded systems or a seasoned developer, this guide will walk you through the ins and outs of setting up and using Software i2c.

Why Software i2c?

Sometimes, your microcontroller’s hardware i2c is already in use, or perhaps your MCU doesn’t have i2c hardware support. That’s where Software i2c steps in, giving you the flexibility to implement i2c communication using regular GPIO pins. It’s like giving your MCU a superpower it didn’t know it had!

Let’s Get Our Hands Dirty

We’re going to explore a Software i2c library implementation that works seamlessly with FreeRTOS. The code handles i2c initialization, reading, and writing, making it a robust solution for your next project.

The Core of Our Software i2c Library

Here’s a sneak peek into the i2c.c file, which contains all the essential functions for our Software i2c library:

/**
 * \file            i2c.c
 * \brief           Software i2c library
 */

/*
 * Copyright (c) 2024 Jasper
 *
 * This file contrains all the software i2c functions.
 *
 * Author:          Jasper <JasperZhangSE@gmail.com>
 * Version:         v1.0.0
 */

#include <cmsis_os.h>
#include "Debug/Debug.h"
#include "FreeRTOS.h"
#include "i2c/i2c.h"
#include "Include/Include.h"

/* Debug config */
#if I2C_DEBUG
#undef TRACE
#define TRACE(...) DebugPrintf(__VA_ARGS__)
#else
#undef TRACE
#define TRACE(...)
#endif /* I2C_DEBUG */
#if I2C_ASSERT
#undef ASSERT
#define ASSERT(a)                                                                                                      \
    while (!(a)) {                                                                                                     \
        DebugPrintf("ASSERT failed: %s %d\n", __FILE__, __LINE__);                                                     \
    }
#else
#undef ASSERT
#define ASSERT(...)
#endif /* I2C_ASSERT */

/* Local defines */
#if SW_I2C_RTOS
#define SW_I2C_MUTEX_INIT()                                                                                            \
    do {                                                                                                               \
        osMutexDef(I2CMutex);                                                                                          \
        s_xI2CMutex = osMutexCreate(osMutex(I2CMutex));                                                                \
    } while (0)
#define SW_I2C_LOCK()   osMutexWait(s_xI2CMutex, osWaitForever)
#define SW_I2C_UNLOCK() osMutexRelease(s_xI2CMutex)
#else
#define SW_I2C_MUTEX_INIT()
#define SW_I2C_LOCK()
#define SW_I2C_UNLOCK()
#endif /* SW_I2C_RTOS */

#if SW_I2C_RTOS
static osMutexId s_xI2CMutex;
#endif

#if SORTWARE_I2C_ENABLE

static void i2c_delay(void) {
#if USE_SYS_TICK_DELAY_US
    delay_us(45);
#else
    // for (volatile uint32_t i = 0; i < 3000; i++); // 根据系统频率调整
    osDelay(1);
#endif
}

Status_t i2c_init(void) {
    SW_I2C_MUTEX_INIT();
    return STATUS_OK;
}

Status_t i2c_start(void) {
    /*    __________
     *SCL           \________
     *    ______
     *SDA       \_____________
     */
    i2c_SDA(high);
    i2c_SCL(high);
    i2c_delay();
    if (I2C_SDA_READ() == low) {
        return STATUS_ERR; /* 检查总线忙 */
    }
    i2c_SDA(low);
    i2c_delay();
    i2c_SCL(low);
    i2c_delay();
    return STATUS_OK;
}

Status_t i2c_stop(void) {
    /*          ____________
     *SCL _____/
     *               _______
     *SDA __________/
     */
    i2c_SCL(low);
    i2c_delay();
    i2c_SDA(low);
    i2c_delay();
    i2c_SCL(high);
    i2c_delay();
    i2c_SDA(high);
    i2c_delay();
    return STATUS_OK;
}

Status_t i2c_write_byte(uint8_t byte) {
    TRACE("i2c write byte = ");
    for (uint8_t i = 0; i < 8; i++) {
        if (byte & 0x80) {
            i2c_SDA(high);
        }
        else {
            i2c_SDA(low);
        }
        TRACE("%d ", (byte & 0x80) >> 7);
        i2c_delay();
        i2c_SCL(high);
        i2c_delay();
        i2c_SCL(low);
        i2c_delay();
        byte <<= 1;
    }
    TRACE("\r\n");

    /* 等待 ACK */
    i2c_SDA(high);
    i2c_delay();
    i2c_SCL(high);
    i2c_delay();
    if (I2C_SDA_READ() == high) {
        TRACE("i2c_write_byte error\r\n");
        return STATUS_ERR;
    }
    i2c_SCL(low);
    return STATUS_OK;
}

Status_t i2c_read_byte(uint8_t *byte, uint8_t ack) {
    /* 检查空指针 */
    if (!byte) {
        return STATUS_ERR;
    }

    TRACE("i2c read byte = ");

    *byte = 0;
    i2c_delay();

    for (uint8_t i = 0; i < 8; i++) {
        *byte <<= 1;
        i2c_SCL(high);
        i2c_delay();
        if (I2C_SDA_READ() == high) {
            *byte |= 0x01;
        }
        TRACE("%d ", (*byte & 0x01));

        i2c_SCL(low);
        i2c_delay();
    }
    TRACE("\n");

    /* 发送 ACK 或 NACK */
    if (ack) {
        i2c_SDA(high);
    }
    else {
        i2c_SDA(low);
    }
    i2c_delay();
    i2c_SCL(high);
    i2c_delay();
    i2c_SCL(low);
    i2c_delay();
    i2c_SDA(high);

    return STATUS_OK;
}

Status_t i2c_write_data(uint8_t device_address, uint8_t *data, uint16_t length) {
    /* 检查空指针 */
    if (!data) {
        return STATUS_ERR;
    }

    /* 发送启动信号 */
    if (i2c_start() != STATUS_OK) {
        return STATUS_ERR;
    }

    /* 发送设备地址(写模式) */
    if (i2c_write_byte(device_address << 1 & 0xFE) != STATUS_OK) {
        i2c_stop();
        return STATUS_ERR;
    }

    /* 发送数据 */
    for (uint16_t i = 0; i < length; i++) {
        if (i2c_write_byte(data[i]) != STATUS_OK) {
            i2c_stop();
            return STATUS_ERR;
        }
        TRACE("data[%d] = %d\r\n", i, data[i]);
    }
    TRACE("\r\n");

    /* 发送停止信号 */
    i2c_stop();
    return STATUS_OK;
}

Status_t i2c_read_data(uint8_t device_address, uint8_t *data, uint16_t length, uint8_t ack) {
    /* 检查空指针 */
    if (!data) {
        TRACE("data is NULL\r\n");
        return STATUS_ERR;
    }

    /* 发送启动信号 */
    if (i2c_start() != STATUS_OK) {
        TRACE("start error\r\n");
        return STATUS_ERR;
    }

    /* 发送设备地址(读模式) */
    if (i2c_write_byte(device_address << 1 | 0x01) != STATUS_OK) {
        TRACE("write address error\r\n");
        i2c_stop();
        return STATUS_ERR;
    }

    /* 接收数据 */
    for (uint16_t i = 0; i < length; i++) {
        if (i2c_read_byte(&data[i], (i < length - 1) ? ack : 1) != STATUS_OK) {
            TRACE("i2c_read_data error\r\n");
            i2c_stop();
            return STATUS_ERR;
        }
        TRACE("data[%d] = %d\r\n", i, data[i]);
    }
    TRACE("\r\n");

    /* 发送停止信号 */
    i2c_stop();
    return STATUS_OK;
}

Status_t i2c_write_register(uint8_t device_address, uint8_t register_address, uint8_t *data, uint8_t length) {
    /* 检查空指针 */
    if (!data) {
        return STATUS_ERR;
    }

    SW_i2c_LOCK();

    /* 发送启动信号 */
    if (i2c_start() != STATUS_OK) {
        return STATUS_ERR;
    }

    /* 发送设备地址(写模式) */
    if (i2c_write_byte(device_address << 1 & 0xFE) != STATUS_OK) {
        i2c_stop();
        return STATUS_ERR;
    }

    /* 发送寄存器地址 */
    if (i2c_write_byte(register_address) != STATUS_OK) {
        i2c_stop();
        return STATUS_ERR;
    }

    /* 发送数据 */
    for (uint8_t i = 0; i < length; i++) {
        /* 使用数组下标访问数据 */
        if (i2c_write_byte(data[i]) != STATUS_OK) {
            i2c_stop();
            return STATUS_ERR;
        }
    }

    /* 发送停止信号 */
    i2c_stop();

    SW_i2c_UNLOCK();

    return STATUS_OK;
}

Status_t i2c_read_register(uint8_t device_address, uint8_t register_address, uint8_t *data, uint8_t length) {
    /* 检查空指针 */
    if (!data) {
        return STATUS_ERR;
    }

    SW_i2c_LOCK();

    /* 发送启动信号 */
    if (i2c_start() != STATUS_OK) {
        return STATUS_ERR;
    }

    /* 发送设备地址和写位(左移一位并将最低位清零) */
    if (i2c_write_byte((device_address << 1) & 0xFE) != STATUS_OK) {
        i2c_stop();
        return STATUS_ERR;
    }

    /* 发送寄存器地址 */
    if (i2c_write_byte(register_address) != STATUS_OK) {
        i2c_stop();
        return STATUS_ERR;
    }

    /* 发送起始条件(重复开始) */
    if (i2c_start() != STATUS_OK) {
        return STATUS_ERR;
    }

    /* 发送设备地址和读位 (左移一位并将最低位置 1) */
    if (i2c_write_byte((device_address << 1) | 0x01) != STATUS_OK) {
        i2c_stop();
        return STATUS_ERR;
    }

    for (uint8_t i = 0; i < length; i++) {
        if (i == length - 1) {
            // 最后一个字节,发送NACK // 第二个参数1表示NACK
            if (i2c_read_byte(&data[i], 1) != STATUS_OK) {
                i2c_stop();
                return STATUS_ERR;
            }
        }
        else {
            // 不是最后一个字节,发送ACK// 第二个参数0表示ACK
            if (i2c_read_byte(&data[i], 0) != STATUS_OK) {
                i2c_stop();
                return STATUS_ERR;
            }
        }
    }

    /* 发送停止条件 */
    i2c_stop();

    SW_i2c_UNLOCK();

    return STATUS_OK;
}

Key Functions Explained

Let’s break down the key functions in our Software i2c library:

Initialization: i2c_init

The i2c_init function initializes the mutex if we’re using FreeRTOS to manage concurrency. It’s crucial to ensure that multiple tasks don’t mess up the i2c communication by accessing it simultaneously.

Starting Communication: i2c_start

This function generates the start condition required to initiate i2c communication. It pulls the SDA line low while keeping the SCL line high, signaling the start of a transmission.

Stopping Communication: i2c_stop

The i2c_stop function generates the stop condition, ending the communication. It releases the SDA line after pulling the SCL line high, which tells the i2c devices that the communication has ended.

Writing a Byte: i2c_write_byte

This function sends a byte of data over the i2c bus. It shifts each bit of the byte to the SDA line and toggles the SCL line to clock the data out. After sending all bits, it waits for an ACK from the slave device.

Reading a Byte: i2c_read_byte

The i2c_read_byte function reads a byte of data from the i2c bus. It shifts bits into a byte while toggling the SCL line. After receiving the byte, it sends an ACK or NACK depending on the ack parameter.

Advanced Functions

Apart from the basic read and write operations, our library also supports reading and writing multiple bytes and registers. This makes it versatile for various i2c devices, from simple sensors to complex peripherals.

Writing Multiple Bytes: i2c_write_data

This function sends multiple bytes of data to a specified device address. It starts with the device address in write mode, followed by the data bytes.

Reading Multiple Bytes: i2c_read_data

The i2c_read_data function reads multiple bytes of data from a specified device address. It sends the device address in read mode and receives the specified number of bytes.

Writing to Registers: i2c_write_register

This function writes data to a specific register of a device. It’s handy for devices like sensors where you need to configure settings before starting measurements.

Reading from Registers: i2c_read_register

Similar to i2c_write_register, this function reads data from a specific register. It’s useful for fetching sensor readings or status bytes from peripherals.

Wrapping Up

Software i2c is a powerful tool in your embedded systems arsenal, giving you the flexibility to implement i2c communication without hardware constraints. With the provided library and detailed explanation, you’re well-equipped to add i2c communication to your next project.

Got any questions or need further clarification? Drop a comment below or reach out via email. Happy coding!


Feel free to adapt and expand this guide according to your specific needs and preferences. Let me know if you have any other questions or need further assistance!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值