使用之前使用以下语句进行Wire库声明
#include <Wire.h>
1.主读从写案例
主设备读取请求从设备,
// 引入Wire库文件
#include <Wire.h>
void setup()
{
// Wire初始化, 加入i2c总线
// 如果未指定,则以主机身份加入总线。
Wire.begin();
// 初始化串口并设置波特率为9600
Serial.begin(9600);
}
void loop()
{
// 向从设备#8请求6个字节
Wire.requestFrom(8, 6); //Wire.requestFrom(从机地址, 字节数);
// 当从从设备接收到信息时值为true
while (Wire.available())
{
// 接受并读取从设备发来的一个字节的数据
char c = Wire.read();
// 向串口打印该字节
Serial.print(c);
}
// 延时500毫秒
delay(500);
}
从设备发送
// 引入Wire库文件
#include <Wire.h>
void setup()
{
// Wire初始化, 并以从设备地址#8的身份加入i2c总线
Wire.begin(8);
// 注册请求响应事件
Wire.onRequest(requestEvent);
}
void loop()
{
delay(100);
}
// 每当接收到来自主机的数据时执行的事件函数
// 此函数被注册为事件,调用请见setup()
void requestEvent()
{
// 用6个字节的消息进行响应
Wire.write("hello ");
// 以此回应主设备的请求
}
2.主写从读案例
主设备写入总线
// 引入Wire库文件
#include <Wire.h>
void setup()
{
// Wire初始化, 加入i2c总线
// 如果未指定,则以主机身份加入总线。
Wire.begin();
}
// 定义一个byte变量以便串口调试
byte x = 0;
void loop()
{
// 将数据传送到从设备#8
Wire.beginTransmission(8);
// 发送5个字节
Wire.write("x is ");
// 发送一个字节
Wire.write(x);
// 停止传送
Wire.endTransmission();
x++;
delay(500);
}
从设备接受
// 引入Wire库文件
#include <Wire.h>
void setup()
{
// Wire初始化, 并以从设备地址#8的身份加入i2c总线
Wire.begin(8);
// 注册接受事件函数
Wire.onReceive(receiveEvent);
// 初始化串口并设置波特率为9600
Serial.begin(9600);
}
void loop()
{
delay(100);
}
// 每当接收到来自主机的数据时执行的事件函数
// 此函数被注册为事件,调用请见setup()
void receiveEvent(int howMany)
{
// 循环读取数据(除了最后一个字符)
while (1 < Wire.available())
{
// 接收字节数据并赋值给变量c(char)
char c = Wire.read();
// 打印该字节
//太极创客团队 / Taichi-Maker (www.taichi-maker.com)
Serial.print(c);
}
// 以int整数的形式接受字节数据并赋值给x(int)
int x = Wire.read();
// 打印该int变量x
Serial.println(x);
}
3.单独函数详解
3.1 begin()
初始化Wire库,并以主机或从机身份加入I2C
总线。
通常来说这个函数只调用一次。
Wire.begin()
Wire.begin(address)
参数
address(可选):7位从机的地址; 如果这个参数未指定,则默认以主机身份加入总线。
当填写了参数时,设备会以从机模式加入IIC总线,address可以设置为0~127中的任意地址。
注:地址从0到7 被保留了, 因此您在开发的时候请不要使用它们!!! 可以从8开始使用。
3.2 requestFrom()
由主设备用来向从设备请求字节使用。
请求发送之后可以使用available()
和read()
来接受并读取数据。
从Arduino 1.0.1开始,requestFrom()
接受一个布尔参数来适配某些I2C设备来达到兼容的目的。
如果为true
,则requestFrom()
在请求之后发送停止消息,从而释放I2C总线。
如果为false
,则requestFrom()
在请求之后发送重启消息。 总线不会释放,这个操作就阻止了另一个主设备在消息之间请求。 这样一来,一台主设备就可以在控制下发送多个请求。
默认值是true。
Wire.requestFrom(address, quantity)
Wire.requestFrom(address, quantity, stop)
参数
address: 设备的7位地址,用于请求字节
quantity: 请求的字节数
stop (bool): 值为true则在请求后发送停止消息,释放总线。值为 false则在请求后发送重启信息,
以保持连接处于活动状态。
返回值类型 :byte
返回从设备响应的字节数
3.3 beginTransmission()
使用指定的地址开始向I2C从设备进行传输。
在调用了Wire.beginTransmission(address)
函数之后,使用write()
函数对要传输的字节进行队列,并通过调用endTransmission()
进行传输。
Wire.beginTransmission(address)
参数
address: 要传输数据的目的设备的7位地址
3.4 write()
对于从设备来说: write()
用于响应来自主设备的请求,即从设备写入数据
对于主设备来说: write()
将数据进行队列, 用以从主设备传输到从设备, 这个函数通常在beginTransmission()
和endTransmission()
之间进行调用。
Wire.write(value)
Wire.write(string)
Wire.write(data, length)
参数
value: 一个要发送的单字节
string: 一系列要发送的字符串
data: 要作为字节发送的数组数据
length: 要传输的字节数
返回值类型 : byte
write()将返回写入的字节数,虽然读取这个返回值是不必要的
3.5 endTransmission()
停止与从机的数据传输。
Wire.endTransmission()
Wire.endTransmission(stop)
参数
stop(bool): 参数值为true时将在请求后发送停止指令并释放总线;
参数值为false时将在请求后发送重新启动的指令,保持连接状态。
返回值类型:byte
返回传输的状态值:
0: 成功
1: 数据量超过传送缓存容纳限制
2: 传送地址时收到 NACK
3: 传送数据时收到 NACK
4: 其它错误
3.6 available()
available() 函数可用于检查是否接收到数据。该函数将会返回等待读取的数据字节数。
应该在调用requestFrom()
之后再在主设备上调用此函数,或者在从设备的onReceive()
的事件处理函数内的调用此函数。
if(Wire.available()){
}
返回缓冲区中可读取的字节数。如果有字节就不为0
3.7 read()
读取在requestFrom()
调用后从从设备响应发送到主设备的字节,或从主设备发送到从设备的字节。
Wire.read()
读取的下一个字符,返回值为接读取到的数据流中的1个字符。
如果没有数据时,返回值为-1
3.8 SetClock()
SetClock()
函数用于修改I2C通信的时钟频率。
I2C从设备没有最低的工作时钟频率,但是通常以100KHz为基准。
Wire.setClock(clockFrequency)
clockFrequency:所需通信时钟的值(以赫兹为单位)。
可接受的值为100000(标准模式)和400000(快速模式)。一些处理器还支持10000(低速模式),
1000000(加快速模式)和3400000(高速模式)。
3.9 onReceive()
当从设备接收到来自主机的传输时,注册要调用的函数。
Wire.onReceive(handler)
handler:从机接收数据时要调用的函数;
这个函数应该使用一个int参数(用于从主设备读取字节数),并且这个函数不应返回任何内容
3.10 onRequest()
当主设备请求从从设备发送数据时,从设备通过onRequest设置调用的函数。
Wire.onRequest(handler)
handler:当主设备请求从从设备发送数据时,从设备要调用的函数。此函数不带参数,不返回任何值,
4. Arduino 搭建其他开发板的烧录环境
4.1 Arduino IDE搭建ESP8266烧录环境
Step1、下载Arduino IDE 官网:Software | Arduino
如下图进入官网下载安装即可。
Step2、安装完成后,打开软件下载ESP8266的开发板,
Step3、插上开发板并在工具—>开发板选择esp8266,找一个闪灯程序烧录试试,在实例里找一个烧录上
4.2 Arduino IDE搭建STM32烧录环境
恭喜你上述4.1你已经安装了Arduino IDE,下面安装Arduino - STM32的烧录环境。
Step1、在文件—>首选项—>添加开发板地址,github的地址在国内不翻墙不一定能访问,所以添加上面的地址。
http://dan.drown.org/stm32duino/package_STM32duino_index.json
https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json
Step2、完了之后,在开发板管理器,输入STM32,你的开发板是F1系的就安装F1,F4系的就安装F4。
Step3、要烧录STM32,还需要安装32位的芯片处理器,不然烧录会报错的;在开发板输入SAM,然后选择如下图,-M3的安装即可。
Step4、选择烧录方式,一般用串口Serial、StLink烧录。
Step5、烧录一个闪灯程序,看能否烧录成功了,注意查看你的STM32开发板的板载LDE灯引脚是不是PC13,不是的话按照板载LED定义改引脚。
5.ESP8266-12F 与 STM32F103C8T6 的 i2C 通信
最终目标:用一块ESP8266作主机 与 一块或多块 STM32作从机 i2C通信。
5.1 ESP8266 wire库详解
当安装了ESP8266开发板后,可以在Arduino的AppData—>Arduino15—>packages—>ESP8266—>hardware找到开发板libraries文件夹所有内置库都在里面了,打开Wire后打开里面的.h和.cpp文件查看一下该库文件的函数方法和数据结构,这两个文件分别是该库的头文件和执行文件。
看代码用notepad++很方便哦,如下图,notepad资源打包放在后面了;.h文件里就能看到所有的函数方法及其里面的数据结构。
5.1.1 示例程序
1.将ESP8266作为主机角色并向从机写入数据
//将ESP8266作为主机角色并向从机写入数据
#include <Wire.h>
#include <PolledTimeout.h>
#define SDA_PIN 4 //数据线
#define SCL_PIN 5 //时钟线
const int16_t I2C_MASTER = 0x42; //主机地址
const int16_t I2C_SLAVE = 0x08; //从机地址
void setup() {
Wire.begin(SDA_PIN, SCL_PIN, I2C_MASTER); //初始化总线,不填入参数以主机身份初始化总线
}
byte x = 0;
void loop() {
using periodic = esp8266::polledTimeout::periodicMs; //设置响应超时时间
static periodic nextPing(1000);
if (nextPing) {
Wire.beginTransmission(I2C_SLAVE); // 开始向地址为0x08的从机设备写入数据,呼叫8号
Wire.write("x is "); // 写入5字节数据,空格是一个字节
Wire.write(x); // 写入1字节数据
Wire.endTransmission(); // 传输完成
x++;
}
}
打开头文件看看,初始化begin,要定义数据线和时钟线。
2. 将ESP8266作为主机角色读取从机数据
// 将ESP8266作为主机角色并向从机写入数据
#include <Wire.h>
#include <PolledTimeout.h>
#define SDA_PIN 4 //数据线
#define SCL_PIN 5 //时钟线
const int16_t I2C_MASTER = 0x42; //主机地址
const int16_t I2C_SLAVE = 0x08; //从机地址
void setup() {
Serial.begin(115200); // 初始化串口波特率115200
Wire.begin(SDA_PIN, SCL_PIN, I2C_MASTER); //初始化总线,不填入参数以主机身份初始化总线
}
void loop() {
using periodic = esp8266::polledTimeout::periodicMs; //设置响应超时时间
static periodic nextPing(1000);
if (nextPing) {
Wire.requestFrom(I2C_SLAVE, 6); // 向从属设备#8请求6个字节
while (Wire.available()) { // 判断总线是否有数据
char c = Wire.read(); // 读取一个字符
Serial.print(c); // 串口打印出字符
}
}
}
3. 将ESP8266作为从机角色读取数据
// 将ESP8266作为从机角色读取数据
#include <Wire.h>
#define SDA_PIN 4
#define SCL_PIN 5
const int16_t I2C_MASTER = 0x42;
const int16_t I2C_SLAVE = 0x08;
void setup() {
Serial.begin(115200); // 初始化串口波特率115200
Wire.begin(SDA_PIN, SCL_PIN, I2C_SLAVE); // 初始化总线,从机必须设置地址
Wire.onReceive(receiveEvent); // 申请加入到总线
}
void loop() {
}
// 每当从主服务器接收到数据时执行的函数
// 此函数已注册为事件
void receiveEvent(size_t howMany) {
(void) howMany;
while (1 < Wire.available()) { // 循环遍历除所有内容
char c = Wire.read(); // 读取字符
Serial.print(c); // 打印字符
}
int x = Wire.read(); // 读取为整形字符
Serial.println(x); // 打印字符
}
4. 将ESP8266作为从机角色写入数据
// 将ESP8266作为从机角色写入数据
#include <Wire.h>
#define SDA_PIN 4
#define SCL_PIN 5
const int16_t I2C_MASTER = 0x42;
const int16_t I2C_SLAVE = 0x08;
void setup() {
Wire.begin(SDA_PIN, SCL_PIN, I2C_SLAVE); // 初始化总线,从机必须设置地址
Wire.onRequest(requestEvent); // 申请加入到总线
}
void loop() {
}
//每当主服务器请求数据时执行的函数
//此函数已注册为事件
void requestEvent() {
Wire.write("hello\n"); // 用6字节的消息进行响应
}
5.2 STM32 wire库详解
5.2.1 示例程序
1. 将STM32作为从机读取数据
// 将STM32作为从机读取数据
#include <Wire_slave.h>
void setup(){
Serial.begin(115200); // 初始化串口波特率115200
Wire.begin(4); // 使用地址#4加入i2c总线
Wire.onReceive(receiveEvent); // 申请加入到总线
}
void loop(){
delay(100);
}
//每当从主服务器接收到数据时执行的函数
//此函数已注册为事件
void receiveEvent(int howMany){
while(1 < Wire.available()){ // 循环判断总线是否有数据
char c = Wire.read(); // 读取一个字符
Serial.print(c); // 打印字符
}
int x = Wire.read(); // 读取一个整数
Serial.println(x); // 打印字符
}
2. 将STM32作为从机写入数据
// 将STM32作为从机写入数据
#include <Wire_slave.h>
void setup()
{
Wire.begin(8); // 使用地址#8加入i2c总线
Wire.onRequest(requestEvent); // 申请加入总线
}
void loop()
{
delay(100);
}
//当主机请求时回复
void requestEvent()
{
Wire.write("hello "); // 响应6个字节
}
3. STM32 作主机扫描总线上的从设备
// STM32 扫描总线上的设备
#include <Wire_slave.h>
void setup() {
Serial.begin(115200); // 初始化串口波特率115200
Wire.begin(); // 初始化总线,不填入地址作为主机
Serial.println("\nI2C Scanner");
}
void loop() {
byte error, address;
int nDevices = 0; //响应设备计数变量
Serial.println("Scanning...");
for (address = 1; address < 127; address++) { //向1~127地址尝试发送数据,尝试呼叫
Wire.beginTransmission(address);
error = Wire.endTransmission(); //返回发送状态,0成功,1数据量过大,2呼叫成功,3发送成功,4未知错误
if (error == 0) { //如果发送成功,打印出从机地址,计数变量+1
Serial.print("I2C device found at address 0x");
if (address < 16)
Serial.print("0");
Serial.println(address, HEX);
nDevices++;
} else if (error == 4) { //如果遇到错误,打印从机地址并报错
Serial.print("Unknown error at address 0x");
if (address < 16)
Serial.print("0");
Serial.println(address, HEX);
}
}
if (nDevices == 0)
Serial.println("No I2C devices found");
else
Serial.println("done");
delay(5000);
}
4. STM32作为主机SoftWire扫描总线上的所有设备地址
// STM32扫描总线上的所有设备地址
#include <SoftWire.h>
SoftWire SWire(PB6, PB7, SOFT_FAST);
void setup() {
Serial.begin(115200);
SWire.begin();
Serial.println("\nSoftware I2C.. Scanner");
}
void loop() {
byte error, address;
int nDevices;
Serial.println("Scanning...");
nDevices = 0;
for(address = 1; address < 127; address++) {
SWire.beginTransmission(address);
error = SWire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at address 0x");
if (address < 16)
Serial.print("0");
Serial.println(address, HEX);
nDevices++;
}
else if (error == 4) {
Serial.print("Unknown error at address 0x");
if (address < 16)
Serial.print("0");
Serial.println(address, HEX);
}
}
if (nDevices == 0)
Serial.println("No I2C devices found");
else
Serial.println("done");
delay(5000);
}
5.3 ESP8266 和 STM32 i2C测试
ESP8266主机,STM32从机,读取搞定了。
如下现场施工图,有点杂乱.......
ESP8266 和 STM32 接线图:
电池接线示意供电,ESP8266直接用microUSB插电脑供电和监视串口输出。
特多扩展
参考或源码出处声明:
1.太极创客网:Arduino – Wire 库 – 太极创客
2.CSDN:Arduino中Wire类库介绍_arduino wire库-CSDN博客
3.CSDN:使用Arduino IDE来编写上传STM32以及STM8代码,STM32Duino教程
4.文心快码:文心快码-STM32 i2C 引脚
下章预告:
Arduino IDE下Arduino、ESP32、STM32测试Lora模块(亿佰特Lora E22-400T30-10公里通信模块-EByte-LoRa-E22-library-1库详解)