用Arduino中Wire库写I2C驱动-提高篇(IP2368芯片驱动为例)

之前写了一篇文章“用Arduino中Wire库写I2C驱动-入门篇”,链接地址:用Arduino中Wire库写I2C驱动-入门篇_arduino wire库-CSDN博客对I2C驱动编写做了一个简单的介绍,这一篇里,我们将使用IP2368这个芯片为例,详细的讲解一个I2C从设备(slave)的驱动编写。

这篇文章需要你具备基本的软硬件基础知识,过于基础的知识不讲解,请自行脑补下。

这个驱动是为ESP32编写的,为其他MCU编写流程大致相同,供参考。

一、芯片及I2C总线时序了解

IP2368是一款支持快充协议的充电芯片,子型号IP2368-I2C-COUT是支持I2C总线接口的型号,可以通过I2C总线设置芯片的工作状态和读取运行数据。

既然要写它的驱动,那么我们就需要研究一下芯片的手册。其中最重要的是:

(1)寄存器地址及寄存器数据格式定义;

(2)I2C总线时序。

1.寄存器地址及寄存器数据格式定义

这个在手册上肯定是有的,长得像这样:

这个是地址为0x00的寄存器每位的定义,其他寄存器定义表类似。

2.I2C总线时序

I2C slave设备的总线时序定义大抵差不多,但是要注意有些细微区别,比如ack和nack、stop信号,以及总线的频率。

细微的区别会导致总线操作有所不同,硬件层面的东西稍微的不同都可能导致失败的结果,所以要特别注意。

比如IP2368的手册上写了那么一句:

这和其他器件是有所不同的。

还有:

这个也是和大多数器件不同的,多数器件支持400k,默认的I2C也是400k,如果不注意的话,就可能出错。

回到总线时序上来,手册上给出的时序图如下:

和大多数slave设备大抵相同。

(1)写数据流程

主机向从机写数据,那么首先肯定要在总线上写从机地址,从机响应后再向从机写我要往你的哪个寄存器写数据,最后才是写真正的数据。具体流程如下:

主机启动总线(start)——>主机向总线写从机地址(slave addr)——>从机响应(sACK)——>主机向总线写寄存器地址(reg addr)——>从机响应(sACK)——>主机向总线写数据(data)——>从机响应(sACK)——>主机发总线停止信号。

(2)读数据流程

主机要从从机读数据,那么还是首先肯定要在总线上写从机地址,从机响应后再向从机写我要读取你的哪个寄存器数据。然后就与写数据有所不同了,从机收到要被读取数据的寄存器地址后,这时主机要做一个总线重启动作,再发送一次从机地址,从机响应后,主机进入读取状态,从SDA引脚读取数据,并且主机读取完毕后,要在总线上发一个NACK信号(mNACK),告诉从机我接收完毕了,然后停止总线。具体流程如下:

主机启动总线(start)——>主机向总线写从机地址(slave addr)——>从机响应(sACK)——>主机向总线写寄存器地址(reg addr)——>从机响应(sACK)——>主机重启总线(restart)——>主机再次向总线写从机地址(slave addr)——>从机响应(sACK)——>主机读取数据(data)——>主机发NACK信号——>主机发总线停止信号。

OK,到此芯片方面需要了解的东西也就差不多了。

二、需要使用到的相关资源

编写arduino下的I2C驱动当然避开不了TwoWire这个东西,当然要编写驱动了,我们得把Wire库翻出来,看看我们需要用到的一些函数是怎么定义的,怎么使用才能与芯片手册上的时序图相匹配,完成数据的读写。arduino中当然是wire.cpp和Wire.h这两个文件需要仔细研究了。

为了方便,这里把这两个文件内容给贴出来。

1.Wire.cpp源文件

/*TwoWire.cpp - TWI/I2C library for Arduino & WiringCopyright (c) 2006 Nicholas Zambetti.  All right reserved.This library is free software; you can redistribute it and/ormodify it under the terms of the GNU Lesser General PublicLicense as published by the Free Software Foundation; eitherversion 2.1 of the License, or (at your option) any later version.This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNULesser General Public License for more details.You should have received a copy of the GNU Lesser General PublicLicense along with this library; if not, write to the Free SoftwareFoundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USAModified 2012 by Todd Krein (todd@krein.org) to implement repeated startsModified December 2014 by Ivan Grokhotkov (ivan@esp8266.com) - esp8266 supportModified April 2015 by Hrsto Gochkov (ficeto@ficeto.com) - alternative esp8266 supportModified Nov 2017 by Chuck Todd (ctodd@cableone.net) - ESP32 ISR SupportModified Nov 2021 by Hristo Gochkov <Me-No-Dev> to support ESP-IDF API*/extern "C" {
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
}#include "esp32-hal-i2c.h"
#include "esp32-hal-i2c-slave.h"
#include "Wire.h"
#include "Arduino.h"TwoWire::TwoWire(uint8_t bus_num):num(bus_num & 1),sda(-1),scl(-1),bufferSize(I2C_BUFFER_LENGTH) // default Wire Buffer Size,rxBuffer(NULL),rxIndex(0),rxLength(0),txBuffer(NULL),txLength(0),txAddress(0),_timeOutMillis(50),nonStop(false)
#if !CONFIG_DISABLE_HAL_LOCKS,nonStopTask(NULL),lock(NULL)
#endif,is_slave(false),user_onRequest(NULL),user_onReceive(NULL)
{}TwoWire::~TwoWire()
{end();
#if !CONFIG_DISABLE_HAL_LOCKSif(lock != NULL){vSemaphoreDelete(lock);}
#endif
}bool TwoWire::initPins(int sdaPin, int sclPin)
{if(sdaPin < 0) { // default param passedif(num == 0) {if(sda==-1) {sdaPin = SDA;    //use Default Pin} else {sdaPin = sda;    // reuse prior pin}} else {if(sda==-1) {
#ifdef WIRE1_PIN_DEFINEDsdaPin = SDA1;
#elselog_e("no Default SDA Pin for Second Peripheral");return false; //no Default pin for Second Peripheral
#endif} else {sdaPin = sda;    // reuse prior pin}}}if(sclPin < 0) { // default param passedif(num == 0) {if(scl == -1) {sclPin = SCL;    // use Default pin} else {sclPin = scl;    // reuse prior pin}} else {if(scl == -1) {
#ifdef WIRE1_PIN_DEFINEDsclPin = SCL1;
#elselog_e("no Default SCL Pin for Second Peripheral");return false; //no Default pin for Second Peripheral
#endif} else {sclPin = scl;    // reuse prior pin}}}sda = sdaPin;scl = sclPin;return true;
}bool TwoWire::setPins(int sdaPin, int sclPin)
{
#if !CONFIG_DISABLE_HAL_LOCKSif(lock == NULL){lock = xSemaphoreCreateMutex();if(lock == NULL){log_e("xSemaphoreCreateMutex failed");return false;}}//acquire lockif(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");return false;}
#endifif(!i2cIsInit(num)){initPins(sdaPin, sclPin);} else {log_e("bus already initialized. change pins only when not.");}
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);
#endifreturn !i2cIsInit(num);
}bool TwoWire::allocateWireBuffer(void)
{// or both buffer can be allocated or none will beif (rxBuffer == NULL) {rxBuffer = (uint8_t *)malloc(bufferSize);if (rxBuffer == NULL) {log_e("Can't allocate memory for I2C_%d rxBuffer", num);return false;}}if (txBuffer == NULL) {txBuffer = (uint8_t *)malloc(bufferSize);if (txBuffer == NULL) {log_e("Can't allocate memory for I2C_%d txBuffer", num);freeWireBuffer();  // free rxBuffer for safety!return false;}}// in case both were allocated before, they must have the same size. All good.return true;
}void TwoWire::freeWireBuffer(void)
{if (rxBuffer != NULL) {free(rxBuffer);rxBuffer = NULL;}if (txBuffer != NULL) {free(txBuffer);txBuffer = NULL;}
}size_t TwoWire::setBufferSize(size_t bSize)
{// Maximum size .... HEAP limited ;-)if (bSize < 32) {    // 32 bytes is the I2C FIFO Len for ESP32/S2/S3/C3log_e("Minimum Wire Buffer size is 32 bytes");return 0;}#if !CONFIG_DISABLE_HAL_LOCKSif(lock == NULL){lock = xSemaphoreCreateMutex();if(lock == NULL){log_e("xSemaphoreCreateMutex failed");return 0;}}//acquire lockif(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");return 0;}
#endif// allocateWireBuffer allocates memory for both pointers or just free themif (rxBuffer != NULL || txBuffer != NULL) {// if begin() has been already executed, memory size changes... data may be lost. We don't care! :^)if (bSize != bufferSize) {// we want a new buffer size ... just reset buffer pointers and allocate new onesfreeWireBuffer();bufferSize = bSize;if (!allocateWireBuffer()) {// failed! Error message already issuedbSize = 0; // returns errorlog_e("Buffer allocation failed");}} // else nothing changes, all set!} else {// no memory allocated yet, just change the size value - allocation in begin()bufferSize = bSize;}
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);#endifreturn bSize;
}// Slave Begin
bool TwoWire::begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency)
{bool started = false;
#if !CONFIG_DISABLE_HAL_LOCKSif(lock == NULL){lock = xSemaphoreCreateMutex();if(lock == NULL){log_e("xSemaphoreCreateMutex failed");return false;}}//acquire lockif(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");return false;}
#endifif(is_slave){log_w("Bus already started in Slave Mode.");started = true;goto end;}if(i2cIsInit(num)){log_e("Bus already started in Master Mode.");goto end;}if (!allocateWireBuffer()) {// failed! Error Message already issuedgoto end;}if(!initPins(sdaPin, sclPin)){goto end;}i2cSlaveAttachCallbacks(num, onRequestService, onReceiveService, this);if(i2cSlaveInit(num, sda, scl, addr, frequency, bufferSize, bufferSize) != ESP_OK){log_e("Slave Init ERROR");goto end;}is_slave = true;started = true;
end:if (!started) freeWireBuffer();
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);
#endifreturn started;
}// Master Begin
bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency)
{bool started = false;esp_err_t err = ESP_OK;
#if !CONFIG_DISABLE_HAL_LOCKSif(lock == NULL){lock = xSemaphoreCreateMutex();if(lock == NULL){log_e("xSemaphoreCreateMutex failed");return false;}}//acquire lockif(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");return false;}
#endifif(is_slave){log_e("Bus already started in Slave Mode.");goto end;}if(i2cIsInit(num)){log_w("Bus already started in Master Mode.");started = true;goto end;}if (!allocateWireBuffer()) {// failed! Error Message already issuedgoto end;}if(!initPins(sdaPin, sclPin)){goto end;}err = i2cInit(num, sda, scl, frequency);started = (err == ESP_OK);end:if (!started) freeWireBuffer();
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);
#endifreturn started;}bool TwoWire::end()
{esp_err_t err = ESP_OK;
#if !CONFIG_DISABLE_HAL_LOCKSif(lock != NULL){//acquire lockif(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");return false;}
#endifif(is_slave){err = i2cSlaveDeinit(num);if(err == ESP_OK){is_slave = false;}} else if(i2cIsInit(num)){err = i2cDeinit(num);}freeWireBuffer();
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);}
#endifreturn (err == ESP_OK);
}uint32_t TwoWire::getClock()
{uint32_t frequency = 0;
#if !CONFIG_DISABLE_HAL_LOCKS//acquire lockif(lock == NULL || xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");} else {
#endifif(is_slave){log_e("Bus is in Slave Mode");} else {i2cGetClock(num, &frequency);}
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);}
#endifreturn frequency;
}bool TwoWire::setClock(uint32_t frequency)
{esp_err_t err = ESP_OK;
#if !CONFIG_DISABLE_HAL_LOCKS//acquire lockif(lock == NULL || xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");return false;}
#endifif(is_slave){log_e("Bus is in Slave Mode");err = ESP_FAIL;} else {err = i2cSetClock(num, frequency);}
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);
#endifreturn (err == ESP_OK);
}void TwoWire::setTimeOut(uint16_t timeOutMillis)
{_timeOutMillis = timeOutMillis;
}uint16_t TwoWire::getTimeOut()
{return _timeOutMillis;
}void TwoWire::beginTransmission(uint16_t address)
{if(is_slave){log_e("Bus is in Slave Mode");return;}
#if !CONFIG_DISABLE_HAL_LOCKSif(nonStop && nonStopTask == xTaskGetCurrentTaskHandle()){log_e("Unfinished Repeated Start transaction! Expected requestFrom, not beginTransmission! Clearing...");//release lockxSemaphoreGive(lock);}//acquire lockif(lock == NULL || xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");return;}
#endifnonStop = false;txAddress = address;txLength = 0;
}/*
https://www.arduino.cc/reference/en/language/functions/communication/wire/endtransmission/
endTransmission() returns:
0: success.
1: data too long to fit in transmit buffer.
2: received NACK on transmit of address.
3: received NACK on transmit of data.
4: other error.
5: timeout
*/
uint8_t TwoWire::endTransmission(bool sendStop)
{if(is_slave){log_e("Bus is in Slave Mode");return 4;}if (txBuffer == NULL){log_e("NULL TX buffer pointer");return 4;}esp_err_t err = ESP_OK;if(sendStop){err = i2cWrite(num, txAddress, txBuffer, txLength, _timeOutMillis);
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);
#endif} else {//mark as non-stopnonStop = true;
#if !CONFIG_DISABLE_HAL_LOCKSnonStopTask = xTaskGetCurrentTaskHandle();
#endif}switch(err){case ESP_OK: return 0;case ESP_FAIL: return 2;case ESP_ERR_TIMEOUT: return 5;default: break;}return 4;
}size_t TwoWire::requestFrom(uint16_t address, size_t size, bool sendStop)
{if(is_slave){log_e("Bus is in Slave Mode");return 0;}if (rxBuffer == NULL || txBuffer == NULL){log_e("NULL buffer pointer");return 0;}esp_err_t err = ESP_OK;if(nonStop
#if !CONFIG_DISABLE_HAL_LOCKS&& nonStopTask == xTaskGetCurrentTaskHandle()
#endif){if(address != txAddress){log_e("Unfinished Repeated Start transaction! Expected address do not match! %u != %u", address, txAddress);return 0;}nonStop = false;rxIndex = 0;rxLength = 0;err = i2cWriteReadNonStop(num, address, txBuffer, txLength, rxBuffer, size, _timeOutMillis, &rxLength);if(err){log_e("i2cWriteReadNonStop returned Error %d", err);}} else {
#if !CONFIG_DISABLE_HAL_LOCKS//acquire lockif(lock == NULL || xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");return 0;}
#endifrxIndex = 0;rxLength = 0;err = i2cRead(num, address, rxBuffer, size, _timeOutMillis, &rxLength);if(err){log_e("i2cRead returned Error %d", err);}}
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);
#endifreturn rxLength;
}size_t TwoWire::write(uint8_t data)
{if (txBuffer == NULL){log_e("NULL TX buffer pointer");return 0;}if(txLength >= bufferSize) {return 0;}txBuffer[txLength++] = data;return 1;
}size_t TwoWire::write(const uint8_t *data, size_t quantity)
{for(size_t i = 0; i < quantity; ++i) {if(!write(data[i])) {return i;}}return quantity;}int TwoWire::available(void)
{int result = rxLength - rxIndex;return result;
}int TwoWire::read(void)
{int value = -1;if (rxBuffer == NULL){log_e("NULL RX buffer pointer");return value;}if(rxIndex < rxLength) {value = rxBuffer[rxIndex++];}return value;
}int TwoWire::peek(void)
{int value = -1;if (rxBuffer == NULL){log_e("NULL RX buffer pointer");return value;}if(rxIndex < rxLength) {value = rxBuffer[rxIndex];}return value;
}void TwoWire::flush(void)
{rxIndex = 0;rxLength = 0;txLength = 0;//i2cFlush(num); // cleanup
}size_t TwoWire::requestFrom(uint8_t address, size_t len, bool sendStop)
{return requestFrom(static_cast<uint16_t>(address), static_cast<size_t>(len), static_cast<bool>(sendStop));
}uint8_t TwoWire::requestFrom(uint8_t address, uint8_t len, uint8_t sendStop)
{return requestFrom(static_cast<uint16_t>(address), static_cast<size_t>(len), static_cast<bool>(sendStop));
}uint8_t TwoWire::requestFrom(uint16_t address, uint8_t len, uint8_t sendStop)
{return requestFrom(address, static_cast<size_t>(len), static_cast<bool>(sendStop));
}/* Added to match the Arduino function definition: https://github.com/arduino/ArduinoCore-API/blob/173e8eadced2ad32eeb93bcbd5c49f8d6a055ea6/api/HardwareI2C.h#L39* See: https://github.com/arduino-libraries/ArduinoECCX08/issues/25
*/
uint8_t TwoWire::requestFrom(uint16_t address, uint8_t len, bool stopBit)
{return requestFrom((uint16_t)address, (size_t)len, stopBit);
}uint8_t TwoWire::requestFrom(uint8_t address, uint8_t len)
{return requestFrom(static_cast<uint16_t>(address), static_cast<size_t>(len), true);
}uint8_t TwoWire::requestFrom(uint16_t address, uint8_t len)
{return requestFrom(address, static_cast<size_t>(len), true);
}uint8_t TwoWire::requestFrom(int address, int len)
{return requestFrom(static_cast<uint16_t>(address), static_cast<size_t>(len), true);
}uint8_t TwoWire::requestFrom(int address, int len, int sendStop)
{return static_cast<uint8_t>(requestFrom(static_cast<uint16_t>(address), static_cast<size_t>(len), static_cast<bool>(sendStop)));
}void TwoWire::beginTransmission(int address)
{beginTransmission(static_cast<uint16_t>(address));
}void TwoWire::beginTransmission(uint8_t address)
{beginTransmission(static_cast<uint16_t>(address));
}uint8_t TwoWire::endTransmission(void)
{return endTransmission(true);
}size_t TwoWire::slaveWrite(const uint8_t * buffer, size_t len)
{return i2cSlaveWrite(num, buffer, len, _timeOutMillis);
}void TwoWire::onReceiveService(uint8_t num, uint8_t* inBytes, size_t numBytes, bool stop, void * arg)
{TwoWire * wire = (TwoWire*)arg;if(!wire->user_onReceive){return;}if (wire->rxBuffer == NULL){log_e("NULL RX buffer pointer");return;}for(uint8_t i = 0; i < numBytes; ++i){wire->rxBuffer[i] = inBytes[i];    }wire->rxIndex = 0;wire->rxLength = numBytes;wire->user_onReceive(numBytes);
}void TwoWire::onRequestService(uint8_t num, void * arg)
{TwoWire * wire = (TwoWire*)arg;if(!wire->user_onRequest){return;}if (wire->txBuffer == NULL){log_e("NULL TX buffer pointer");return;}wire->txLength = 0;wire->user_onRequest();if(wire->txLength){wire->slaveWrite((uint8_t*)wire->txBuffer, wire->txLength);}
}void TwoWire::onReceive( void (*function)(int) )
{user_onReceive = function;
}// sets function called on slave read
void TwoWire::onRequest( void (*function)(void) )
{user_onRequest = function;
}TwoWire Wire = TwoWire(0);
TwoWire Wire1 = TwoWire(1);

2.Wire.h源文件

/*TwoWire.h - TWI/I2C library for Arduino & WiringCopyright (c) 2006 Nicholas Zambetti.  All right reserved.This library is free software; you can redistribute it and/ormodify it under the terms of the GNU Lesser General PublicLicense as published by the Free Software Foundation; eitherversion 2.1 of the License, or (at your option) any later version.This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNULesser General Public License for more details.You should have received a copy of the GNU Lesser General PublicLicense along with this library; if not, write to the Free SoftwareFoundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USAModified 2012 by Todd Krein (todd@krein.org) to implement repeated startsModified December 2014 by Ivan Grokhotkov (ivan@esp8266.com) - esp8266 supportModified April 2015 by Hrsto Gochkov (ficeto@ficeto.com) - alternative esp8266 supportModified November 2017 by Chuck Todd <stickbreaker on GitHub> to use ISR and increase stability.Modified Nov 2021 by Hristo Gochkov <Me-No-Dev> to support ESP-IDF API
*/#ifndef TwoWire_h
#define TwoWire_h#include <esp32-hal.h>
#if !CONFIG_DISABLE_HAL_LOCKS
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#endif
#include "Stream.h"// WIRE_HAS_BUFFER_SIZE means Wire has setBufferSize()
#define WIRE_HAS_BUFFER_SIZE    1
// WIRE_HAS_END means Wire has end() 
#define WIRE_HAS_END 1#ifndef I2C_BUFFER_LENGTH#define I2C_BUFFER_LENGTH 128  // Default size, if none is set using Wire::setBuffersize(size_t)
#endif
typedef void(*user_onRequest)(void);
typedef void(*user_onReceive)(uint8_t*, int);class TwoWire: public Stream
{
protected:uint8_t num;int8_t sda;int8_t scl;size_t bufferSize;uint8_t *rxBuffer;size_t rxIndex;size_t rxLength;uint8_t *txBuffer;size_t txLength;uint16_t txAddress;uint32_t _timeOutMillis;bool nonStop;
#if !CONFIG_DISABLE_HAL_LOCKSTaskHandle_t nonStopTask;SemaphoreHandle_t lock;
#endif
private:bool is_slave;void (*user_onRequest)(void);void (*user_onReceive)(int);static void onRequestService(uint8_t, void *);static void onReceiveService(uint8_t, uint8_t*, size_t, bool, void *);bool initPins(int sdaPin, int sclPin);bool allocateWireBuffer(void);void freeWireBuffer(void);public:TwoWire(uint8_t bus_num);~TwoWire();//call setPins() first, so that begin() can be called without arguments from librariesbool setPins(int sda, int scl);bool begin(int sda, int scl, uint32_t frequency=0); // returns true, if successful init of i2c busbool begin(uint8_t slaveAddr, int sda, int scl, uint32_t frequency);// Explicit Overload for Arduino MainStream API compatibilityinline bool begin(){return begin(-1, -1, static_cast<uint32_t>(0));}inline bool begin(uint8_t addr){return begin(addr, -1, -1, 0);}inline bool begin(int addr){return begin(static_cast<uint8_t>(addr), -1, -1, 0);}bool end();size_t setBufferSize(size_t bSize);void setTimeOut(uint16_t timeOutMillis); // default timeout of i2c transactions is 50msuint16_t getTimeOut();bool setClock(uint32_t);uint32_t getClock();void beginTransmission(uint16_t address);void beginTransmission(uint8_t address);void beginTransmission(int address);uint8_t endTransmission(bool sendStop);uint8_t endTransmission(void);size_t requestFrom(uint16_t address, size_t size, bool sendStop);uint8_t requestFrom(uint16_t address, uint8_t size, bool sendStop);uint8_t requestFrom(uint16_t address, uint8_t size, uint8_t sendStop);size_t requestFrom(uint8_t address, size_t len, bool stopBit);uint8_t requestFrom(uint16_t address, uint8_t size);uint8_t requestFrom(uint8_t address, uint8_t size, uint8_t sendStop);uint8_t requestFrom(uint8_t address, uint8_t size);uint8_t requestFrom(int address, int size, int sendStop);uint8_t requestFrom(int address, int size);size_t write(uint8_t);size_t write(const uint8_t *, size_t);int available(void);int read(void);int peek(void);void flush(void);inline size_t write(const char * s){return write((uint8_t*) s, strlen(s));}inline size_t write(unsigned long n){return write((uint8_t)n);}inline size_t write(long n){return write((uint8_t)n);}inline size_t write(unsigned int n){return write((uint8_t)n);}inline size_t write(int n){return write((uint8_t)n);}void onReceive( void (*)(int) );void onRequest( void (*)(void) );size_t slaveWrite(const uint8_t *, size_t);
};extern TwoWire Wire;
extern TwoWire Wire1;#endif

这两个源文件里面关于函数的定义需要仔细看,才能理解后面我们总线操作的顺序。

3.几个关键函数解读

(1)beginTransmission()

首先是beginTransmission(),在wire.cpp中定义如下:

void TwoWire::beginTransmission(uint16_t address)
{if(is_slave){log_e("Bus is in Slave Mode");return;}
#if !CONFIG_DISABLE_HAL_LOCKSif(nonStop && nonStopTask == xTaskGetCurrentTaskHandle()){log_e("Unfinished Repeated Start transaction! Expected requestFrom, not beginTransmission! Clearing...");//release lockxSemaphoreGive(lock);}//acquire lockif(lock == NULL || xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");return;}
#endifnonStop = false;txAddress = address;txLength = 0;
}

中间关于锁的部分我们忽略,是操作系统的资源锁逻辑代码,可以不关心。这个函数并没有做硬件层面的操作,只是初始化了几个变量nonStop、txAddress、txLength而已。

(2)write()

write的在Wire.cpp中定义如下:

size_t TwoWire::write(uint8_t data)
{if (txBuffer == NULL){log_e("NULL TX buffer pointer");return 0;}if(txLength >= bufferSize) {return 0;}txBuffer[txLength++] = data;return 1;
}

可以看到,write函数其实也没有操作硬件,只是把要写的数据往buffer里面放了。

(3)endTransmission()

endTransmission在Wire.cpp中定义如下:

/*
https://www.arduino.cc/reference/en/language/functions/communication/wire/endtransmission/
endTransmission() returns:
0: success.
1: data too long to fit in transmit buffer.
2: received NACK on transmit of address.
3: received NACK on transmit of data.
4: other error.
5: timeout
*/
uint8_t TwoWire::endTransmission(bool sendStop)
{if(is_slave){log_e("Bus is in Slave Mode");return 4;}if (txBuffer == NULL){log_e("NULL TX buffer pointer");return 4;}esp_err_t err = ESP_OK;if(sendStop){err = i2cWrite(num, txAddress, txBuffer, txLength, _timeOutMillis);
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);
#endif} else {//mark as non-stopnonStop = true;
#if !CONFIG_DISABLE_HAL_LOCKSnonStopTask = xTaskGetCurrentTaskHandle();
#endif}switch(err){case ESP_OK: return 0;case ESP_FAIL: return 2;case ESP_ERR_TIMEOUT: return 5;default: break;}return 4;
}

可以看到,这个函数才是真正通过i2cWrite()去操作硬件总线了。

并且,这个i2cWrite()完成的工作是I2C总线的整个写数据操作时序流程。

注意了(敲黑板):这个函数向总线写的是buffer里面的所有数据,包括寄存器地址、寄存器数据,但是不包括设备地址。这些数据是通过前面的write()函数,写入到buffer里面去的。

前面讲到的写入和读取时序,我们可以看到:不管是写还是读,主机前面都有写的动作,都向总线写了设备地址、寄存器地址。不同之处在于写入数据后面跟着的是写数据,读取数据后面跟着的是重启总线和读数据。

图中这个蓝色框部分:

这个知识对于后面程序编写很重要。

(4)requestFrom()

requestFrom在Wire.cpp中定义如下:

size_t TwoWire::requestFrom(uint16_t address, size_t size, bool sendStop)
{if(is_slave){log_e("Bus is in Slave Mode");return 0;}if (rxBuffer == NULL || txBuffer == NULL){log_e("NULL buffer pointer");return 0;}esp_err_t err = ESP_OK;if(nonStop
#if !CONFIG_DISABLE_HAL_LOCKS&& nonStopTask == xTaskGetCurrentTaskHandle()
#endif){if(address != txAddress){log_e("Unfinished Repeated Start transaction! Expected address do not match! %u != %u", address, txAddress);return 0;}nonStop = false;rxIndex = 0;rxLength = 0;err = i2cWriteReadNonStop(num, address, txBuffer, txLength, rxBuffer, size, _timeOutMillis, &rxLength);if(err){log_e("i2cWriteReadNonStop returned Error %d", err);}} else {
#if !CONFIG_DISABLE_HAL_LOCKS//acquire lockif(lock == NULL || xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){log_e("could not acquire lock");return 0;}
#endifrxIndex = 0;rxLength = 0;err = i2cRead(num, address, rxBuffer, size, _timeOutMillis, &rxLength);if(err){log_e("i2cRead returned Error %d", err);}}
#if !CONFIG_DISABLE_HAL_LOCKS//release lockxSemaphoreGive(lock);
#endifreturn rxLength;
}

这个函数是调用 i2cWriteReadNonStop()或者i2cRead()从总线读取数据的。

要注意的是,这个函数只会完成读取数据的操作,并不包含之前的写寄存器地址操作!

也就是下图红方框内的部分:

这个知识对后面程序编写也很重要!

三、驱动编写

下面就是正式着手驱动编写了。

首先在arduino的library中新建文件夹IP2368,并且在里面新建两个文件IP2368.h和IP2368.cpp。下面将这两个文件称为头文件和源文件。

在头文件中,首先把芯片的寄存器地址都定义好,这个是必须要用的:

#define IP2368_ADR_WRITE     0xEA
#define IP2368_ADR_READ      0xEB
#define IP2368_ADDR          0xEA#define IP2368_SYS_CTL0      0x00 // charge 使能寄存器
#define IP2368_SYS_CTL1      0x01 // 串联节数设置、电池类型、电流设置模式
#define IP2368_SYS_CTL2      0x02 // VSET 充满电压设定
#define IP2368_SYS_CTL3      0x03 // ISET 充电功率或电流设置
#define IP2368_SYS_CTL4      0x04 // 电池容量设置
#define IP2368_SYS_CTL5      0x05 // 初始电量
#define IP2368_SYS_CTL6      0x06 // 当前电量
#define IP2368_SYS_CTL7      0x07 // 涓流充电电流、阈值和充电超时设置
#define IP2368_SYS_CTL8      0x08 // 停充电流和再充电阈值设置
#define IP2368_SYS_CTL9      0x09 // 待机使能和低电电压设置
#define IP2368_SYS_CTL10     0x0A // 电池低电电压设置
#define IP2368_SYS_CTL11     0x0B // 输出使能寄存器#define IP2368_TYPEC_CTL8    0x22 // TYPE-C 模式控制寄存器
#define IP2368_TYPEC_CTL9    0x23 // 输出 PDO 电流设置寄存器
#define IP2368_TYPEC_CTL10   0x24 // 5VPDO 电流设置寄存器
#define IP2368_TYPEC_CTL11   0x25 // 9VPDO 电流设置寄存器
#define IP2368_TYPEC_CTL12   0x26 // 12VPDO 电流设置寄存器
#define IP2368_TYPEC_CTL13   0x27 // 15VPDO 电流设置寄存器
#define IP2368_TYPEC_CTL14   0x28 // 20VPDO 电流设置寄存器
#define IP2368_TYPEC_CTL23   0x29 // PPS1 PDO 电流设置寄存器
#define IP2368_TYPEC_CTL24   0x2A // PPS2 PDO 电流设置寄存器
#define IP2368_TYPEC_CTL17   0x2B // 输出 PDO 设置寄存器#define IP2368_SOC_CAP_DATA  0x30 // 电芯电量数据寄存器
#define IP2368_STATE_CTL0    0x31 // 充电状态控制寄存器
#define IP2368_STATE_CTL1    0x32 // 充电状态控制寄存器
#define IP2368_STATE_CTL2    0x33 // 输入 PD 状态控制寄存器
#define IP2368_TYPEC_STATE0  0x34 // 系统状态指示寄存器
#define IP2368_MOS_STATE     0x35 // 输入 MOS 状态指示寄存器
#define IP2368_STATE_CTL3    0x38 // 系统过流指示寄存器#define IP2368_BATVADC       0x50 // VBAT 电压
#define IP2368_VSYSVADC      0x52 // VSYS 电压
#define IP2368_IVBUS_IADC    0x54 // 充电输入电流
#define IP2368_IBATIADC      0x6E // 电芯端电流
#define IP2368_ISYS_IADC     0x70 // IVSYS 端电流
#define IP2368_VSYS_POW      0x74 // VSYS 端功率#define IP2368_INTC_IADC     0x77 // NTC 输出电流寄存器
#define IP2368_VGPIO0_NTC    0x78 // NTC端电压#define IP2368_VGPIO1_ISET   0x7A // 电流设置
#define IP2368_VGPIO2_VSET   0x7C // 单节电压设置
#define IP2368_VGPIO3_FCAP   0x7E // 电池容量设置
#define IP2368_VGPIO4_BATNUM 0x80 // 电池节数设置

这里,我们要考虑一下,应用程序调用这个驱动的时候,采用类对象的方式是比较方便的。

1.构造函数定义

那么定义类的时候就要首先考虑类的构造函数,也就是应用程序在定义类对象的时候首先需要给这个对象传一个什么参数。

I2C从设备的话,那一般包含两个必须的参数:总线引脚、总线号。其他的参数比如设备地址、总线频率这些可以在类的内部直接定义好,应用程序可以不关心,特殊情况除外。

OK,这里我们先定义构造函数要用的几个私有变量,即I2C总线和引脚。那么把构造函数写成这样:

IP2368::IP2368(uint8_t SDAPin, uint8_t SCLPin,uint8_t INTPin, TwoWire * pI2CBus)
{_SDAPin   = SDAPin;_SCLPin   = SCLPin;_INTPin   = INTPin;_pI2CBus  = pI2CBus;
}

其中_pI2CBus是一个指针,应用程序调用的时候把总线的地址传过来即可,形如:

IP2368 myIP2368(5,4,7,&Wire);       //0号总线地址或

2.硬件层函数

硬件层面我们首先要考虑如何实现一个字节的读写,这个也是最重要最基础的。

(1)1字节的写入函数:
int IP2368::WriteByte(uint8_t regAddr, uint8_t *pData)
{int ack;_ptrI2CBus->beginTransmission(IP2368_ADDR);_ptrI2CBus->write(regAddr);_ptrI2CBus->write(*pData);ack=_ptrI2CBus->endTransmission(true);return(ack);
}

这里,我们应该去了解下I2C总线的这几个函数究竟在做什么事情,以便我们更好理解这些函数调用时怎么样与I2C总线时序匹配上的。

几个函数的解读前面已经做了,没看到的往前翻。

首先,beginTransmission()会初始化一些变量,包括发送buffer;

然后,第一个write()把寄存器地址写入发送buffer;

然后,第二个write()把要写入寄存器的数据也写入到发送buffer,到此,其实总线上并没有操作发生;

最后,endTransmission()会执行从总线start到最后的一整套流程,把数据写入到从设备中。

注意endTransmission()有个参数true,这是表示数据传输后要在总线上发一个Stop信号释放总线。

(2)1字节读取函数
int IP2368::ReadByte(uint8_t regAddr, , uint8_t *pData)
{int ack;_ptrI2CBus->beginTransmission(IP2368_ADDR);_ptrI2CBus->write(regAddr);ack=_ptrI2CBus->endTransmission(false);    uint8_t bytesReceived=0;bytesReceived=_ptrI2CBus->requestFrom(IP2368_ADDR,1,true);if(bytesReceived==1){*pData=_ptrI2CBus->read();ack = 0;}else{Serial.println("LTC2944_read reply error!");ack = -1;}return(ack);    
}

我们分部分讲解。

首先是这几句:

  _ptrI2CBus->beginTransmission(IP2368_ADDR);_ptrI2CBus->write(regAddr);ack=_ptrI2CBus->endTransmission(false); 

这几个函数在前面详细讲过了。与总线时序对应的是完成写设备地址、写寄存器地址操作。

注意这个endTransmission(false)里的参数false,它代表着最后不写停止信号。函数的定义中该参数默认是true,也就是要写停止信号。为什么要用false呢?我们看时序图蓝框部分:

第一个蓝框那里即是这几行代码执行完毕处,这里主机是不能发Stop信号的,只有在所有操作完毕后(第二个蓝框),主机才会发Stop信号。如果这里endTransmission()的参数错了,总线就提前停止了,无法完成后面的数据传输操作。

然后讲后面几句:

  uint8_t bytesReceived=0;bytesReceived=_ptrI2CBus->requestFrom(IP2368_ADDR,1,true);if(bytesReceived==1){*pData=_ptrI2CBus->read();ack = 0;}else{Serial.println("LTC2944_read reply error!");ack = -1;}return(ack);  

requestFrom函数前面讲过了,作用是完成读时序的后半部分。

同时要注意,requestFrom(IP2368_ADDR,1,true)中的参数true,其作用也是和endTransmission()中的true一样,发总线停止信号,释放总线。参数1是读取1字节。

可能你要问了,总线操作都已经完成了,怎么后面还有*pData=_ptrI2CBus->read()这个操作?

其实requestFrom只是把数据读回到buffer了,所以还需要这个read()操作把数据从buffer读取到用户变量中。这个read()只是在读buffer,并不是在操作总线,这个和write()函数是一样的。

这个函数里ack被重复赋值有点问题,不过没关系。

(3)双字节读取函数
int IP2368::readWord(uint8_t regAddr, uint16_t *pData)
{delay(50);int ack;_pI2CBus->beginTransmission(IP2368_ADDR);_pI2CBus->write(regAddr);ack=_pI2CBus->endTransmission(false);    if(ack!=0){Serial.println("IP2368 readWord endTransmission error!");return(ack);}delay(20);uint8_t bytesReceived=0;uint8_t temp[2];//bytesReceived=_pI2CBus->requestFrom(uint8_t(IP2368_ADDR),size_t(2),true);bytesReceived=_pI2CBus->requestFrom(uint8_t(IP2368_ADDR),size_t(2));if(bytesReceived==2){//IP2368手册中定义12bit的数据低8位在地址较低的寄存器temp[0]=_ptrI2CBus->read();temp[1]=_ptrI2CBus->read();*pData=temp[0]*256+temp[1];//*pData=(*pData) | (_pI2CBus->read());//*pData=(*pData) | ((uint16_t(0) | (_pI2CBus->read()))<<8 );ack = 0;}else{Serial.println("IP2368 readWord reply error!");ack = -1;}return(ack);     
}

双字节读取函数也是非常常用的功能,因为很多16bit的数据会存放在连续两个字节中。一般高8位在前,低8位在后。

(3)多字节读取函数
uint16_t IP2368::ReadBytes(uint8_t regAddr,uint8_t *pData, uint8_t len)
{//连续读多个字节数据,向下兼容,当len=1时,和ReadByte作用是一样的//注意能够读取的最大最多字节数受RxBuffer限制,应该是32字节,待查证int ack;_ptrI2CBus->beginTransmission(IP2368_ADDR);_ptrI2CBus->write(regAddr);ack=_ptrI2CBus->endTransmission(false);    uint8_t bytesReceived=0;bytesReceived=_ptrI2CBus->requestFrom(IP2368_ADDR,len,true);if(bytesReceived==len){for(uint8_t i=0;i<len;i++){*(pData[i])=_ptrI2CBus->read();}ack = 0;}else{Serial.println("IP2368 reply error!");ack = -1;}return(ack);   
}

函数从设备中连续读取多个字节数据。因为很多时候我们需要读取16bits的数据或者更多,所以定义这么一个函数更为方便。

这个函数本身没什么特别的,但是有个知识点要了解,即大多数I2C设备是支持连续数据读取的。

这里的寄存器地址是第一个数据的寄存器地址,连续读多个字节的话,设备会自动把地址加1连续输出每个寄存器的值。

存储型I2C设备是肯定支持这种方式的,提高读写速度。其他类型的设备支不支持,要看手册和测试。

(4)单bit操作函数

为方便操作寄存器的某一个bit,编写这个函数。这个函数是基于我们有了单byte的读写函数来进行的。

芯片手册里也强调了,写某个寄存器的时候,只对需要写的位进行操作,其他位你不能去改变,否则后果不可预料。

为实现这个目的,我们需要先把寄存器的值读取出来,对需要写的位操作,然后反写回去。代码如下:

int IP2368::writeBit(uint8_t regAddr, uint8_t bitNumber,bool bitStatus)
{//写IP2368的一个bit。按照手册要求,操作1bit要把该寄存器的值8bits读出来,仅改变需要写的bit,然后再反写回去。即你不关心的bits你不能去改它//bitNumber从0-7delay(50);if(bitNumber>7){Serial.println("bitNumber must lower than 8!");return(1);}int ack;uint8_t tmp;ack=readByte(regAddr,&tmp);if(ack!=0){Serial.println("IP2368 readByte reply error in writeBit!");return(2);}  delay(50);uint8_t x;x=uint8_t(1)<<bitNumber;if(bitStatus){tmp=tmp | x;}else{tmp=tmp & (~x);}ack=ack | writeByte(regAddr,&tmp);if(ack!=0){Serial.println("IP2368 writeByte reply error in writeBit!");return(3);}  return(0);
}

3.应用层函数

底层数据读写函数完成后,我们就要考虑应用层面的函数了。

这个应用层函数与芯片的作用强相关,每个芯片都不一样,所以这个视每个芯片情况写,没有一定的套路,这里就不做详细讲解了。

这里只是给出我给这个IP2368写的一个应用层函数供参考。

void IP2368::getPower(void)
{readWord(IP2368_BATVADC,&Power.V_Bat);readWord(IP2368_VSYSVADC,&Power.V_Sys);readWord(IP2368_IVBUS_IADC,&Power.I_Input);readWord(IP2368_IBATIADC,&Power.I_Bat);readWord(IP2368_ISYS_IADC,&Power.I_Sys);readWord(IP2368_VSYS_POW,&Power.P_Sys);if (Power.I_Sys * Power.V_Sys / 1000 > 65536) {Power.P_Sys += 65535;}readByte(IP2368_SOC_CAP_DATA,&Power.Percent);
}

作用是读取IP2368的一些状态信息。

四、完整代码

1.IP2368.h

#ifndef _IP2368_H
#define _IP2368_H#include "arduino.h"
#include "Wire.h"#define IP2368_ADR_WRITE     0xEA
#define IP2368_ADR_READ      0xEB
#define IP2368_ADDR          0x75#define IP2368_SYS_CTL0      0x00 // charge 使能寄存器
#define IP2368_SYS_CTL1      0x01 // 串联节数设置、电池类型、电流设置模式
#define IP2368_SYS_CTL2      0x02 // VSET 充满电压设定
#define IP2368_SYS_CTL3      0x03 // ISET 充电功率或电流设置
#define IP2368_SYS_CTL4      0x04 // 电池容量设置
#define IP2368_SYS_CTL5      0x05 // 初始电量
#define IP2368_SYS_CTL6      0x06 // 当前电量
#define IP2368_SYS_CTL7      0x07 // 涓流充电电流、阈值和充电超时设置
#define IP2368_SYS_CTL8      0x08 // 停充电流和再充电阈值设置
#define IP2368_SYS_CTL9      0x09 // 待机使能和低电电压设置
#define IP2368_SYS_CTL10     0x0A // 电池低电电压设置
#define IP2368_SYS_CTL11     0x0B // 输出使能寄存器#define IP2368_TYPEC_CTL8    0x22 // TYPE-C 模式控制寄存器
#define IP2368_TYPEC_CTL9    0x23 // 输出 PDO 电流设置寄存器
#define IP2368_TYPEC_CTL10   0x24 // 5VPDO 电流设置寄存器
#define IP2368_TYPEC_CTL11   0x25 // 9VPDO 电流设置寄存器
#define IP2368_TYPEC_CTL12   0x26 // 12VPDO 电流设置寄存器
#define IP2368_TYPEC_CTL13   0x27 // 15VPDO 电流设置寄存器
#define IP2368_TYPEC_CTL14   0x28 // 20VPDO 电流设置寄存器
#define IP2368_TYPEC_CTL23   0x29 // PPS1 PDO 电流设置寄存器
#define IP2368_TYPEC_CTL24   0x2A // PPS2 PDO 电流设置寄存器
#define IP2368_TYPEC_CTL17   0x2B // 输出 PDO 设置寄存器#define IP2368_SOC_CAP_DATA  0x30 // 电芯电量数据寄存器
#define IP2368_STATE_CTL0    0x31 // 充电状态控制寄存器
#define IP2368_STATE_CTL1    0x32 // 充电状态控制寄存器
#define IP2368_STATE_CTL2    0x33 // 输入 PD 状态控制寄存器
#define IP2368_TYPEC_STATE0  0x34 // 系统状态指示寄存器
#define IP2368_MOS_STATE     0x35 // 输入 MOS 状态指示寄存器
#define IP2368_STATE_CTL3    0x38 // 系统过流指示寄存器#define IP2368_BATVADC       0x50 // VBAT 电压
#define IP2368_VSYSVADC      0x52 // VSYS 电压
#define IP2368_IVBUS_IADC    0x54 // 充电输入电流
#define IP2368_IBATIADC      0x6E // 电芯端电流
#define IP2368_ISYS_IADC     0x70 // IVSYS 端电流
#define IP2368_VSYS_POW      0x74 // VSYS 端功率#define IP2368_INTC_IADC     0x77 // NTC 输出电流寄存器
#define IP2368_VGPIO0_NTC    0x78 // NTC端电压#define IP2368_VGPIO1_ISET   0x7A // 电流设置
#define IP2368_VGPIO2_VSET   0x7C // 单节电压设置
#define IP2368_VGPIO3_FCAP   0x7E // 电池容量设置
#define IP2368_VGPIO4_BATNUM 0x80 // 电池节数设置//#define IP2368_I2C hi2c1
struct IP2368_Power_t {uint16_t V_Bat;   // 0x50, mVuint16_t V_Sys;   // 0x52, mVuint16_t I_Input; // 0x54, mAuint16_t I_Bat;   // 0x6E, mAuint16_t I_Sys;   // 0x70, mAuint16_t P_Sys;     // 0x74, mWuint8_t Percent;  // 0x30, %
};struct IP2368_ADC_t {uint16_t I_NTC;uint16_t NTC;uint16_t ISET;uint16_t VSET;uint16_t FCAP;uint16_t BatNum;
};struct IP2368_State_t {uint8_t MOS_FAST_OCDT;uint8_t Charge;uint8_t PD;uint8_t SINK;
};class IP2368
{public:IP2368(uint8_t SDAPin, uint8_t SCLPin,uint8_t INTPin, TwoWire * pI2CBus);int       begin(void);int       readByte(uint8_t regAddr,  uint8_t *pData);int       writeByte(uint8_t regAddr, uint8_t *pData);int       readWord(uint8_t regAddr, uint16_t *pData);int       writeBit(uint8_t regAddr, uint8_t bitNumber,bool bitStatus); int       readBytes(uint8_t regAddr,uint8_t *pData, uint8_t len);void      getPower(void);struct IP2368_Power_t   Power;struct IP2368_ADC_t     ADC;struct IP2368_State_t   State;  private:TwoWire * _pI2CBus;     uint8_t _SDAPin;uint8_t _SCLPin;uint8_t _INTPin;};#endif

2.IP2368.cpp

#include "IP2368.h"
#include "arduino.h"IP2368::IP2368(uint8_t SDAPin, uint8_t SCLPin,uint8_t INTPin, TwoWire * pI2CBus)
{_SDAPin   = SDAPin;_SCLPin   = SCLPin;_INTPin   = INTPin;_pI2CBus  = pI2CBus;
}int IP2368::begin(void)
{if(!(_pI2CBus->begin(_SDAPin,_SCLPin,150000)))//IP2368只能支持到200k,不支持高速400k!{//Wire.begin() 返回true代表OKreturn 1;}//以下几行只能在arduino环境编译pinMode(_INTPin,OUTPUT);digitalWrite(_INTPin,1);delay(200);//手册要求INT引脚拉高100ms后才能使用I2C//需要增加检测芯片是否在位的函数uint8_t error;  _pI2CBus->beginTransmission(IP2368_ADDR);error = _pI2CBus->endTransmission(true);if (error!=0){return 2;//芯片没有响应}else{return(0); }
}int IP2368::writeByte(uint8_t regAddr, uint8_t *pData)
{delay(50);int ack;_pI2CBus->beginTransmission(IP2368_ADDR);_pI2CBus->write(regAddr);_pI2CBus->write(*pData);ack=_pI2CBus->endTransmission(true);if(ack!=0){Serial.println("IP2368 writeByte endTransmission error!");}return(ack);  
}int IP2368::readByte(uint8_t regAddr,  uint8_t *pData)
{delay(50);int ack;_pI2CBus->beginTransmission(IP2368_ADDR);_pI2CBus->write(regAddr);ack=_pI2CBus->endTransmission(false);    if(ack!=0){Serial.println("IP2368 readByte endTransmission error!");return(ack);}delay(20);uint8_t bytesReceived=0;//bytesReceived=_pI2CBus->requestFrom(uint8_t(IP2368_ADDR),size_t(1),true);bytesReceived=_pI2CBus->requestFrom(uint8_t(IP2368_ADDR),size_t(1));if(bytesReceived==1){*pData=_pI2CBus->read();ack = 0;}else{Serial.println("IP2368 readByte reply error!");ack = -1;}return(ack);    
}int IP2368::readWord(uint8_t regAddr, uint16_t *pData)
{delay(50);int ack;_pI2CBus->beginTransmission(IP2368_ADDR);_pI2CBus->write(regAddr);ack=_pI2CBus->endTransmission(false);    if(ack!=0){Serial.println("IP2368 readWord endTransmission error!");return(ack);}delay(20);uint8_t bytesReceived=0;uint8_t temp[2];//bytesReceived=_pI2CBus->requestFrom(uint8_t(IP2368_ADDR),size_t(2),true);bytesReceived=_pI2CBus->requestFrom(uint8_t(IP2368_ADDR),size_t(2));if(bytesReceived==2){//IP2368手册中定义12bit的数据低8位在地址较低的寄存器temp[0]=_ptrI2CBus->read();temp[1]=_ptrI2CBus->read();*pData=temp[0]*256+temp[1];//*pData=(*pData) | (_pI2CBus->read());//*pData=(*pData) | ((uint16_t(0) | (_pI2CBus->read()))<<8 );ack = 0;}else{Serial.println("IP2368 readWord reply error!");ack = -1;}return(ack);     
}int IP2368::writeBit(uint8_t regAddr, uint8_t bitNumber,bool bitStatus)
{//写IP2368的一个bit。按照手册要求,操作1bit要把该寄存器的值8bits读出来,仅改变需要写的bit,然后再反写回去。即你不关心的bits你不能去改它//bitNumber从0-7delay(50);if(bitNumber>7){Serial.println("bitNumber must lower than 8!");return(1);}int ack;uint8_t tmp;ack=readByte(regAddr,&tmp);if(ack!=0){Serial.println("IP2368 readByte reply error in writeBit!");return(2);}  delay(50);uint8_t x;x=uint8_t(1)<<bitNumber;if(bitStatus){tmp=tmp | x;}else{tmp=tmp & (~x);}ack=ack | writeByte(regAddr,&tmp);if(ack!=0){Serial.println("IP2368 writeByte reply error in writeBit!");return(3);}  return(0);
}int IP2368::readBytes(uint8_t regAddr,uint8_t *pData, uint8_t len)
{//连续读多个字节数据,向下兼容,当len=1时,和ReadByte作用是一样的//注意能够读取的最大最多字节数受RxBuffer限制,应该是32字节,待查证//写这个函数对IP2368好像没什么卵用delay(50);int ack;_pI2CBus->beginTransmission(IP2368_ADDR);_pI2CBus->write(regAddr);ack=_pI2CBus->endTransmission(false);    uint8_t bytesReceived=0;//bytesReceived=_pI2CBus->requestFrom(uint8_t(IP2368_ADDR),size_t(len),true);bytesReceived=_pI2CBus->requestFrom(uint8_t(IP2368_ADDR),size_t(len));if(bytesReceived==len){for(uint8_t i=0;i<len;i++){*(pData+i)=_pI2CBus->read();}ack = 0;}else{Serial.println("IP2368 reply error!");ack = -1;}return(ack);   
}void IP2368::getPower(void)
{readWord(IP2368_BATVADC,&Power.V_Bat);readWord(IP2368_VSYSVADC,&Power.V_Sys);readWord(IP2368_IVBUS_IADC,&Power.I_Input);readWord(IP2368_IBATIADC,&Power.I_Bat);readWord(IP2368_ISYS_IADC,&Power.I_Sys);readWord(IP2368_VSYS_POW,&Power.P_Sys);if (Power.I_Sys * Power.V_Sys / 1000 > 65536) {Power.P_Sys += 65535;}readByte(IP2368_SOC_CAP_DATA,&Power.Percent);}

3.arduino测试代码

#include "Wire.h"
#include "IP2368.h"static IP2368 myIP2368(42,41,40,&Wire);void setup() {Serial.begin(115200);int tmp;tmp=myIP2368.begin();if(tmp==0){Serial.println("IP2368 init OK!");}else if(tmp==1){Serial.println("IP2368 I2C Bus error!");}else if(tmp==2){Serial.println("IP2368 chip not found on bus!");}uint8_t IP2368Data_byte;uint16_t IP2368Data_word;//单字节读测试myIP2368.readByte(uint8_t(0x50), &IP2368Data_byte);Serial.printf("IP2368 read  0x%02X\n",  IP2368Data_byte);delay(50);myIP2368.readByte(uint8_t(0x51), &IP2368Data_byte);Serial.printf("IP2368 read  0x%02X\n",  IP2368Data_byte);  //双字节读测试delay(50);myIP2368.readWord(uint8_t(0x50), &IP2368Data_word);Serial.printf("IP2368 read  0x%04X\n",  IP2368Data_word); //单bit设置/*delay(50);myIP2368.readByte(uint8_t(0x07), &IP2368Data_byte);Serial.printf("IP2368 read  0x%02X before bit write\n",  IP2368Data_byte);    delay(50);myIP2368.writeBit(uint8_t(0x07), uint8_t(1),false);delay(50);myIP2368.readByte(uint8_t(0x07), &IP2368Data_byte);Serial.printf("IP2368 read  0x%02X after bit write\n",  IP2368Data_byte); */}void loop() {byte error, address;int nDevices = 0;delay(1000);/*Serial.println("Scanning for I2C devices ...");for(address = 0x01; address < 0x7f; address++){Wire.beginTransmission(address);error = Wire.endTransmission();if (error == 0){Serial.printf("I2C device found at address 0x%02X\n", address);nDevices++;} else if(error != 2){Serial.printf("Error %d at address 0x%02X\n", error, address);}}if (nDevices == 0){Serial.println("No I2C devices found");}*/myIP2368.getPower();Serial.printf("V_Bat=%5dmV\n",    myIP2368.Power.V_Bat  );Serial.printf("V_Sys=%5dmV\n",    myIP2368.Power.V_Sys  );Serial.printf("I_Input=%5dmA\n",  myIP2368.Power.I_Input);Serial.printf("I_Bat=%5dmA\n",    myIP2368.Power.I_Bat  );Serial.printf("I_Sys=%5dmA\n",    myIP2368.Power.I_Sys  );Serial.printf("P_Sys=%5dmW\n",    myIP2368.Power.P_Sys  );Serial.printf("Percent=%5d%%\n",  myIP2368.Power.Percent);Serial.printf("\n");}

 补充一点:Wire.cpp中关于requestFrom()函数中最后一个参数还没有完全研究透彻,源代码中感觉最后一个参数没有意义,继续跟进中。。。

重要提示:以上代码仅供测试,尚有许多不完善的地方,仅供个人学习研究使用,请勿用于任何商业用途!若用于任何非学习研究用途而导致的后果与本人无关!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/511249.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Prometheus结合Grafana监控MySQL,这篇不可不读!

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

枚举——完美立方算法

枚举 基于逐个尝试答案的一种问题求解策略 例如&#xff1a;求小于N的最大素数 找不到一个数学公式&#xff0c;使得根据N就可以计算出这个素数 N-1是素数吗&#xff1f;N-2是素数吗&#xff1f; …… 判断N-i是否是素数的问题 转化成求小于N的全部素数&#xff08;可以用筛法…

【笔记】Android Telephony 漫游SPN显示定制(Roaming Alpha Tag)

一、功能名词简介和显示规则 Alpha Tag&#xff1a;运营商名称标识符&#xff0c;也是用于标识运营商的一个名称。客户需求描述常用名词&#xff0c;对开发而言都是SPN/PLMN功能模块的内容&#xff0c;状态栏左上角的运营商名称显示。 SPN相关文章&#xff1a; 【笔记】SPN和…

从零开始学习Netty - 学习笔记 -Netty入门【协议设计和解析】

2.协议设计和解析 协议 在计算机中&#xff0c;协议是指一组规则和约定&#xff0c;用于在不同的计算机系统之间进行通信和数据交换。计算机协议定义了数据传输的格式、顺序、错误检测和纠正方法&#xff0c;以及参与通信的各个实体的角色和责任。计算机协议可以在各种不同的层…

【C++】STL容器string详解

string详解 一&#xff0c;STL简介1. 版本2. 六大组件 二&#xff0c;string类的使用1. string类的常用构造2. string类容量相关2.1 size和capacity接口2.2 reserve和resize 3. string类对象的访问和遍历&#xff0c;迭代器3.1 运算符重载[]3.2 string迭代器 4. string类对象的…

十九 超级数据查看器 讲解稿 分栏功能

十九 超级数据查看器 讲解稿 分栏功能 点击此处观看视频教程 讲解稿全文: 大家好&#xff0c;这讲介绍一下 &#xff0c;超级数据查看器的分栏功能。 分栏功能设计的初衷是为了让用户同时同地查询两个表格的数据&#xff0c;方便比较&#xff0c;获得更清晰的查询结果 分栏功…

gin gorm学习笔记

代码仓库 https://gitee.com/zhupeng911/go-advanced.git https://gitee.com/zhupeng911/go-project.git 1. gin介绍 Gin 是使用纯 Golang 语言实现的 HTTP Web框架&#xff0c;Gin接口设计简洁&#xff0c;提供类似Martini的API&#xff0c;性能极高&#xff0c;现在被广泛使用…

BUUCTF:[MRCTF2020]ezmisc

题目地址&#xff1a;https://buuoj.cn/challenges#[MRCTF2020]ezmisc 下载附件打开是一张照片&#xff1a; 放到kali中发现crc校验错误&#xff0c;修改照片宽高&#xff1a; 保存即可发现flag flag为&#xff1a; flag{1ts_vEryyyyyy_ez!}

【代码】Python3|无GUI环境中使用Seaborn作图的学习路线及代码(阴影折线图)

我有个需求是需要画图&#xff0c;让GPT帮我生成了一下学习计划。 学习路线依照GPT的来的&#xff0c;使用的Prompt工具是https://github.com/JushBJJ/Mr.-Ranedeer-AI-Tutor。 文章目录 PrerequisiteMain Curriculum1.1 Seaborn介绍Seaborn基础保存图形为文件练习 1.2 单变量数…

适用于 Windows 的 5 款最佳免费数据恢复软件榜单

每个计算机用户都曾经历过数据丢失的情况。很容易错误地删除重要的文件和文件夹&#xff0c;当发生这种情况时&#xff0c;可能会导致不必要的心痛和压力。值得庆幸的是&#xff0c;可以恢复 Windows PC 上丢失的数据。在本文中&#xff0c;我们将分享您可以使用的五种最佳 Win…

SwiftUI之CoreData详解(一)

coreData 是一种数据持久化的方案&#xff0c;是对SQLite的一种封装。一说到这种桌面化的数据库&#xff0c;我就无比的怀念Foxbase|Foxpro, 多好的数据库产品&#xff0c;被微软扼杀了&#xff0c;相当年教大学生妹子们国家二级数据库时都是手把手教的&#xff0c;呃~~~&#…

Linux-信号3_sigaction、volatile与SIGCHLD

文章目录 前言一、sigaction__sighandler_t sa_handler;__sigset_t sa_mask; 二、volatile关键字三、SIGCHLD方法一方法二 前言 本章内容主要对之前的内容做一些补充。 一、sigaction #include <signal.h> int sigaction(int signum, const struct sigaction *act,struc…