Program Listing for File BlackBox_I2C.hpp

Return to documentation for file (include/library/BlackBox_I2C.hpp)

#pragma once

#include "library/BlackBox_pinout.hpp"
#include "driver/i2c.h"
#include <atomic>
#include <cstdint>
#include <mutex>

namespace I2C {

enum ACKCheck {
    DisableACKCheck = false,
    EnableACKCheck = true,
};

enum ACKValue {
    ACK = I2C_MASTER_ACK,
    NACK = I2C_MASTER_NACK,
    LastNACK = I2C_MASTER_LAST_NACK,
};

class Transmission {
private:
    Transmission(const Transmission&) = delete;
    Transmission& operator=(const Transmission&) = delete;

    mutable std::recursive_mutex m_mutex;
    const char* m_tag = "I2C_Transmission";

    i2c_cmd_handle_t m_handle;
    bool m_sent = false;

public:
    Transmission();

    explicit Transmission(i2c_cmd_handle_t);

    Transmission(Transmission&&);
    Transmission& operator=(Transmission&&);

    ~Transmission();

    esp_err_t startBit();

    esp_err_t stopBit();

    esp_err_t writeByte(std::uint8_t data, bool ACKCheck = EnableACKCheck);

    esp_err_t readByte(std::uint8_t* data, ACKValue = ACK);

    esp_err_t write(std::uint8_t* data, size_t dataLength, bool ackCheck = EnableACKCheck);

    esp_err_t read(std::uint8_t* data, size_t dataLength, ACKValue = ACK);

    esp_err_t send(i2c_port_t i2cNum, TickType_t ticksToWait = 1000 / portTICK_RATE_MS);

    void reset();

    i2c_cmd_handle_t raw();

    void detach();
};

class Device { // FIXME: This has to change to be implemented as "universal", write and read functions need to be public, but what with derived classes should we not derive them but nest them?
protected:
    std::uint16_t m_address;

    i2c_port_t m_port;

    Device() = delete;
public:
    virtual ~Device() = default;

    std::uint8_t readByte(std::uint8_t registerAddress);

    void readBytes(std::uint8_t registerAddress, std::uint8_t* data, size_t dataLength);

    std::uint16_t readWord(std::uint8_t registerAddress);

    void writeByte(std::uint8_t registerAddress, std::uint8_t data);

    void writeBytes(std::uint8_t registerAddress, std::uint8_t* data, size_t dataLength);

    void writeWord(std::uint8_t registerAddress, std::uint16_t data);

    Device(std::uint16_t address, i2c_port_t);

    std::uint16_t address() const;

    i2c_port_t port() const;

    virtual void init(); // FIXME: Write init for i2c device
};

namespace Ports {
constexpr char const* tag = "I2C_Port_Guard";

static std::atomic<bool> initializedPorts[I2C_NUM_MAX];

constexpr i2c_config_t defaultConfig = {
    // FIXME: Make this overritable easily
    .mode = I2C_MODE_MASTER,
    .sda_io_num = ::BlackBox::Pins::I2C::SDA,
    .scl_io_num = ::BlackBox::Pins::I2C::SCL,
    .sda_pullup_en = true,
    .scl_pullup_en = true,
    .master = {
        .clk_speed = 400000, // FIXME: Do all devices support this speed?
    },
};

// FIXME: Should I implement counting on inits and call deinit after all I2C::Devices are destroyed?
void init(i2c_port_t, const i2c_config_t& = defaultConfig, size_t slaveRxBuffer = 0, size_t slaveTxBuffer = 0, int intrAllocationFlag = 0);

void config(i2c_port_t, const i2c_config_t& config = defaultConfig);

void deinit(i2c_port_t);

bool isInitialized(i2c_port_t);
};

}