IIC(Inter-Integrated Circuit)其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。
   本文主要实现在Arduino或者ESP32(使用Arduino IDE开发)中使用wire库驱动两路IIC的功能。
一、硬件平台

  • ESP32 核心板
  • esp32 pwn 频率 arduino esp32 arduino iic_#include

  • 1.3 寸 OLED SH1106 IIC接口(地址0x3c)
  • esp32 pwn 频率 arduino esp32 arduino iic_嵌入式_02

  • 0.96寸 OLED SSD1306 IIC接口(地址0x3c)
  • 杜邦线 *8

二、软件实现

   该实验在VSCode集成开发环境下使用 PlatFormIo插件开发 。两块屏幕的驱动使用的是*ESP8266 and ESP32 OLED Dirver*,可在Arduino库里面下载到。

esp32 pwn 频率 arduino esp32 arduino iic_引脚_03


   两块屏幕开发均需要使用到wire,首先看看这个驱动是如何调用wire库的。以SSD1306wire.h为例,

private:
      uint8_t             _address;
      int                 _sda;
      int                 _scl;
      bool                _doI2cAutoInit = false;
      TwoWire*            _wire = NULL;
      int                 _frequency;

  public:
SSD1306Wire(uint8_t _address, int _sda = -1, int _scl = -1, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64, HW_I2C _i2cBus = I2C_ONE, int _frequency = 700000) {
      setGeometry(g);

      this->_address = _address;
      this->_sda = _sda;
      this->_scl = _scl;
#if !defined(ARDUINO_ARCH_ESP32)
      this->_wire = &Wire;
#else
      this->_wire = (_i2cBus==I2C_ONE) ? &Wire : &Wire1;
#endif
      this->_frequency = _frequency;
    }

    bool connect() {
#if !defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH_ESP8266)
      _wire->begin();
#else
      // On ESP32 arduino, -1 means 'don't change pins', someone else has called begin for us.
      if(this->_sda != -1)
        _wire->begin(this->_sda, this->_scl);
#endif
      // Let's use ~700khz if ESP8266 is in 160Mhz mode
      // this will be limited to ~400khz if the ESP8266 in 80Mhz mode.
      if(this->_frequency != -1)
        _wire->setClock(this->_frequency);
      return true;
    }

   这个是SSD1306的构造函数,通过uint8_t _address, int _sda = -1, int _scl = -1, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64, HW_I2C _i2cBus = I2C_ONE, int _frequency = 700000这样一些参数来配置IIC,并且定义了私有成员TwoWire* _wire = NULL;在构造函数中使用this->_wire = (_i2cBus==I2C_ONE) ? &Wire : &Wire1;这样一句话来选择使用的Wire还是Wire1这两个的定义在wire.cpp的最后两行,外部定义在wire.h的最后两行

esp32 pwn 频率 arduino esp32 arduino iic_#include_04


   这里我们只需要修改_i2cBus 这样一个参数就可以实现选择Wire还是Wire1,然后通过_wire->begin(this->_sda, this->_scl);来启动Wire总线,下面是最重要的一步,修改wire库,我们需要修改的只有一个函数TwoWire::begin

bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency)
{
    if(sdaPin < 0) { // default param passed
        if(num == 0) {
            if(sda==-1) {
                sdaPin = SDA;    //在pin_arduino.h中定义
            } else {
                sdaPin = sda;    // reuse prior pin
            }
        } else {
            if(sda==-1) {
                // sdaPin = SDA1;
                log_e("no Default SDA Pin for Second Peripheral");
                return false; //no Default pin for Second Peripheral
            } else {
                sdaPin = sda;    // reuse prior pin
            }
        }
    }

    if(sclPin < 0) { // default param passed
        if(num == 0) {
            if(scl == -1) {
                sclPin = SCL;    // 在pin_arduino.h中定义
            } else {
                sclPin = scl;    // reuse prior pin
            }
        } else {
            if(scl == -1) {
                // sclPin = SCL1;
                log_e("no Default SCL Pin for Second Peripheral");
                return false; //no Default pin for Second Peripheral
            } else {
                sclPin = scl;    // reuse prior pin
            }
        }
    }

    sda = sdaPin;
    scl = sclPin;
    i2c = i2cInit(num, sdaPin, sclPin, frequency);
    if(!i2c) {
        return false;
    }

    flush();
    return true;

}

   在SSD1306库文件中,在调用begin函数的时候将SDA SCL的引脚号传了进来,但是其他的传感器的库文件有很多直接调用的是wire.begin(),其参数默认是-1,这个时候默认使用引脚配置在pin_arduino.h中定义,我们再后面添加上Wire1 也就是第二个IIC总线的两个引脚。

static const uint8_t SDA = 21;
static const uint8_t SCL = 22;

static const uint8_t SDA1 = 18;
static const uint8_t SCL1 = 19;

   回到begin函数,该函数实现的主要功能就行SDA SCL引脚配置以及IIC初始化,修改的部分是两个关于引脚的if判断,以SDA为例。

if(sdaPin < 0) { // default param passed
        if(num == 0) {
            if(sda==-1) {
                sdaPin = SDA;    //use Default Pin
            } else {
                sdaPin = sda;    // reuse prior pin
            }
        } else {
            if(sda==-1) {
                // sdaPin = SDA1;
                log_e("no Default SDA Pin for Second Peripheral");
                return false; //no Default pin for Second Peripheral
            } else {
                sdaPin = sda;    // reuse prior pin
            }
        }
    }

如果我们传入了SDA 和 SCL 的引脚号,则默认会使用我们配置的引脚并不需修改。如果是默认参数传入(-1)则进入IIC_num 的判断,这里的num参数是由

TwoWire::TwoWire(uint8_t bus_num)
    :num(bus_num & 1)

TwoWire构造函数参数决定的,在定义wire1的时候参数是1,所以进入第一个else判断,进来之后检测sdaPin参数的值,如果是 -1 的话,这里就会打印出提示信息(no Default SDA Pin for Second Peripheral),不是-1的话使用默认的构造函数中的参数的默认值,但在构造函数中还是-1啊

TwoWire::TwoWire(uint8_t bus_num)
    :num(bus_num & 1)
    ,sda(-1)
    ,scl(-1)
    ,i2c(NULL)
    ,rxIndex(0)
    ,rxLength(0)
    ,rxQueued(0)
    ,txIndex(0)
    ,txLength(0)
    ,txAddress(0)
    ,txQueued(0)
    ,transmitting(0)
    ,last_error(I2C_ERROR_OK)
    ,_timeOutMillis(50)
{}

因此我们就需要修改一下这个地方,注释掉提示信息那两句,将SDA配置我们在pin_arduino.h里面添加的SDA1,结果如下:

if(sda==-1) {
    sdaPin = SDA1;
    // log_e("no Default SDA Pin for Second Peripheral");
    // return false; //no Default pin for Second Peripheral
} else {
    sdaPin = sda;    // reuse prior pin
}

这样就会使用我们配置的引脚进行设置,SCL的设置大同小异。
至此Wire库的修改已经完成,
总结一下就是:

  1. 在pin_arduino.h中添加引脚定义
  2. 修改wire.cpp中的begin函数
  3. 修改相关传感器的驱动文件
  4. 附上成功驱动的图片
    三、实验代码
#include <Arduino.h>
#include <ESP32_DEF.h>
#include <LED.h>
#include <myMQTT.h>
#include <WiFi.h>
#include <Ticker.h>
#include <OneWire.h>
#include <DS18B20.h>
#include <DHT.h>
#include <Wire.h>
#include <SH1106Wire.h>
#include <SSD1306Wire.h>
#include <pctolcd.h>
#include <pins_arduino.h>

boolean res;
boolean led_state = 0;
MyMQTT myMqtt;
Ticker tickerTimer;
Ticker timer_2s;


float temp = 0.0;
float hum = 0.0;
#define DHTTYPE DHT11   // DHT 11
#define DHTPIN 4
#define ONEWIRE_BUS 15
#define IIC_SCL   22 
#define IIC_SDA   21
#define LINE_HIGH 8
OneWire onewire(ONEWIRE_BUS);
DS18B20 ds18b20(&onewire);
DHT dht11(DHTPIN,DHT11);
SH1106Wire oled_display(0x3c,IIC_SDA,IIC_SCL);
SSD1306Wire display(0x3c,18,19);
//
/*************************************************
Function: SysTemInit
Description: 系统初始化(传感器)
Return: NULL 
Others: NULL
********************************************/
void SysTemInit(void)
{
    LED_init();    
    ds18b20.begin();
    dht11.begin();

    //OLED init
    oled_display.init();
    oled_display.flipScreenVertically();
    oled_display.setFont(ArialMT_Plain_10);

    display.init();
    display.flipScreenVertically();
    display.setFont(ArialMT_Plain_10);
    
}

/*************************************************
Function: oled_displayString
Description: oled 上显示字符串
param:fontdata 字体大小 10  16  24
    textalign : 对齐方式 
    x,y :显示文本左上角坐标
    text: 显示文本
Return:   
Others: NULL
********************************************/
void oled_displayString(int fontdata,OLEDDISPLAY_TEXT_ALIGNMENT textalign,
                                int16_t x ,int16_t y,String text)
{
    //设置字体大小,默认10
    if(fontdata == 24)
        oled_display.setFont(ArialMT_Plain_24);
    else if (fontdata == 16)
        oled_display.setFont(ArialMT_Plain_16);
    else 
        oled_display.setFont(ArialMT_Plain_10);

    //设置对齐方式
    oled_display.setTextAlignment(textalign);

    //显示字符串
    oled_display.drawString(x, y, text);
}

/*************************************************
Function: dispaly_logo
Description: 显示 pctolcd.h中的logo
Return: NULL 
Others: NULL
********************************************/
void dispaly_logo(void)
{
    oled_display.clear();
    oled_display.drawXbm(8, 10, logo_width, logo_height,logo);
    oled_displayString(10,TEXT_ALIGN_CENTER,64,38,"ESP32");
    oled_display.display();

    display.setTextAlignment(TEXT_ALIGN_CENTER);
    display.drawString(64,38,"ESP32");
    display.display();
}

void display_wifi_config(void)
{
    oled_display.clear();
    oled_display.drawXbm(34, 0, WiFi_Logo_width, WiFi_Logo_height,WiFi_Logo_bits);
    oled_displayString(10,TEXT_ALIGN_CENTER,64, 35,String("SSID:")+wifi_ssid);
    oled_displayString(10,TEXT_ALIGN_CENTER,64, 50,String("WIFI_IP:")+WiFi.localIP().toString());
    oled_display.display();
}


/*************************************************
Function: DS18B20_rd_temperature
Description: DS18B20温度读取
Return: NULL 
Others: NULL
********************************************/
void DS18B20_rd_temperature(void)
{
    //精度设置为 0.25---- 10   0.0625 --- 12
    ds18b20.setResolution(10);
    ds18b20.requestTemperatures();
    Serial.print("DS18B20 is Conversion ...");
    while(!ds18b20.isConversionComplete());
    temp = ds18b20.getTempC();
    Serial.println(temp,2);
}
/*************************************************
Function: tickerCallback
Description: 定时器回调函数
Return: NULL 
Others: NULL
********************************************/
void tickerCallback(void)
{
    res = myMqtt.topic_property_post(3.3,temp,hum);
    Serial.println(res);
    led_state = !led_state;
    led_state?LED_ON:LED_OFF;
    DS18B20_rd_temperature();
}

/*************************************************
Function: timer_2s_callback
Description: 定时器回调函数
Return: NULL 
Others: NULL
********************************************/
void timer_2s_callback(void)
{
    hum = dht11.readHumidity();
    // // Read temperature as Celsius (the default)
    // float t = dht11.readTemperature();
    // //判断是不是非法数字
    if(isnan(hum))
    {
        Serial.println("Failed to read from DHT11 sensor!");
    }

    Serial.print(F("Humidity: "));
    Serial.print(hum);
    // Serial.print(F("%  Temperature: "));
    // Serial.print(t);
    // Serial.print(F("°C "));
}

/*************************************************
Function: setup
Description: 初始化
Return: NULL 
Others: NULL
********************************************/
void setup() 
{
    
    SysTemInit();
    Serial.begin(115200);
    Serial.println();
    dispaly_logo();
    delay(3000);
    WiFi.begin(wifi_ssid,wifi_psd);
    
    while (WiFi.status() != WL_CONNECTED) //等待网络连接成功
    {
      delay(500);
      Serial.print(".");
    }
    Serial.println("WiFi connected!");

    Serial.println("IP address: ");
    Serial.println(WiFi.localIP()); //打印模块IP
    display_wifi_config();
    delay(3000);
    if(myMqtt.connect_to_MQTT_Server())      
        Serial.println("connect MQTT Server Success!");
    tickerTimer.attach_ms(1000,tickerCallback);
    timer_2s.attach(2,timer_2s_callback);
}

/*************************************************
Function: loop
Description: 循环
Return: NULL 
Others: NULL
********************************************/
void loop() 
{
    myMqtt.call_to_callback();
    if(!myMqtt.connected_status())
        myMqtt.reconnect();
}