IIC(Inter-Integrated Circuit)其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。
本文主要实现在Arduino或者ESP32(使用Arduino IDE开发)中使用wire库驱动两路IIC的功能。
一、硬件平台
- ESP32 核心板
- 1.3 寸 OLED SH1106 IIC接口(地址0x3c)
- 0.96寸 OLED SSD1306 IIC接口(地址0x3c)
- 杜邦线 *8
二、软件实现
该实验在VSCode集成开发环境下使用 PlatFormIo插件开发 。两块屏幕的驱动使用的是*ESP8266 and ESP32 OLED Dirver*
,可在Arduino库里面下载到。
两块屏幕开发均需要使用到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的最后两行
这里我们只需要修改_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库的修改已经完成,
总结一下就是:
- 在pin_arduino.h中添加引脚定义
- 修改wire.cpp中的begin函数
- 修改相关传感器的驱动文件
- 附上成功驱动的图片
三、实验代码
#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();
}