OpenMV-Code
# Arduino 作为I2C主设备, OpenMV作为I2C从设备。
#
# 请把OpenMV和Arduino按照下面连线:
#
# OpenMV Cam Master I2C Data (P5) - Arduino Uno Data (A4) SDA数据线
# OpenMV Cam Master I2C Clock (P4) - Arduino Uno Clock (A5) SCL时钟线
# OpenMV Cam Ground - Arduino Ground GND地线
import pyb, ustruct # ustruct 用于打包和解压原始数据类型 的 "类"
text = "Hello World!\n"
data = ustruct.pack("<%ds" % len(text), text)
# 使用 "ustruct" 来生成需要发送的数据包
#ustruct.pack(fmt, v1, v2, ...):根据格式字符串fmt,打包 v1, v2, … 值。返回值为一个解码该值的字节对象
# "<" 把数据以小端序放进struct中
# "%ds" 把字符串放进数据流,比如:"13s" 对应的 "Hello World!\n" (13 chars).
# 详见 https://docs.python.org/3/library/struct.html
# READ ME!!!
#
# 请理解,当您的OpenMV摄像头不是I2C主设备,所以不管是使用中断回调,
# 还是下方的轮循,都可能会错过响应发送数据给主机。当这种情况发生时,
# Arduino会获得NAK,并且不得不从OpenMV再次读数据。请注意,
# OpenMV和Arduino都不擅长解决I2C的错误。在OpenMV和Arduino中,
# 你可以通过释放I2C外设,再重新初始化外设,来恢复功能。
# 构造I2C对象
# OpenMV上的硬件I2C总线都是2
bus = pyb.I2C(2, pyb.I2C.SLAVE, addr=0x12)
bus.deinit() # 完全关闭设备
bus = pyb.I2C(2, pyb.I2C.SLAVE, addr=0x12)
print("Waiting for Arduino...")
# 请注意,为了正常同步工作,OpenMV Cam必须 在Arduino轮询数据之前运行此脚本。
# 否则,I2C字节帧会变得乱七八糟。所以,保持Arduino在reset状态,
# 直到OpenMV显示“Waiting for Arduino...”。
while(True):
try:
bus.send(ustruct.pack("<h", len(data)), timeout=10000) # 首先发送长度 (16-bits).
try:
bus.send(data, timeout=10000) # 然后发送数据
print("Sent Data!") # 没有遇到错误时,会显示
except OSError as err: # 如果遇到错误 触发异常
pass # 不用担心遇到错误,会跳过
# 请注意,有3个可能的错误。 超时错误(timeout error),
# 通用错误(general purpose error)或繁忙错误
#(busy error)。 “err.arg[0]”的错误代码分别
# 为116,5,16。
except OSError as err:
pass # 不用担心遇到错误,会跳过
# 请注意,有3个可能的错误。 超时错误(timeout error),
# 通用错误(general purpose error)或繁忙错误
#(busy error)。 “err.arg[0]”的错误代码分别
# 为116,5,16。
Arduino-Code
// Arduino Code
#include <Wire.h>
#define BAUD_RATE 19200 // 设置波特率为19200
#define CHAR_BUF 128
void setup()
{
Serial.begin(BAUD_RATE); //设置波特率
Wire.begin(); // begin():无输入参数,表示以 Master 形式加入总线。 有输入参数,表示以从机形式加入总线,设备地址为address(7-bit)
delay(1000); // 给OpenMV一个启动的时间
}
void loop()
{
int32_t temp = 0; //int32_t:32位int型数据 不同平台下,使用以下名称可以保证固定长度
char buff[CHAR_BUF] = {0};
// requestFrom函数用于实现 Master Read From Slave 操作:主设备请求从设备一个字节,这个字节可以被主设备用 read()或available()接受
Wire.requestFrom(0x12, 2); // requestFrom(address, quantity):从 address 设备读取 quantity 个字节,结束后,产生 STOP 信号
//available 函数用于统计 Master Read From Slave 操作后, read 缓存区剩余的字节数 每当缓存区的数据被读走 1 个字节,available 函数的返回值减一
if (Wire.available() == 2) // 如果成功执行了上面的操作(从0x12设备读取2个字节)
{
//read 函数用于在 Master Read From Slave 操作后,读取缓存区的数据。
temp = Wire.read() | (Wire.read() << 8);
delay(1); // Give some setup time...
Wire.requestFrom(0x12, temp);
if (Wire.available() == temp)
{ // got full message?
temp = 0;
while (Wire.available())
buff[temp++] = Wire.read();
}
else
{
while (Wire.available())
Wire.read(); // Toss garbage bytes.
}
}
else
{
while (Wire.available())
Wire.read(); // Toss garbage bytes.
}
Serial.print(buff);
delay(1); // Don't loop to quickly.
}
用到了Arduino上的wire.h
库
Wire.begin()
功能:初始化IIC
连接,并作为主机或者从机设备加入IIC
总线
当没有填写参数时Wire.begin()
,设备会以主机模式加入IIC
总线;
当填写了参数时Wire.begin(adress)
,设备以从机模式加入IIC
总线,address
可以设置为0~127 中任意地址
例子
Wire.requesFrom()
功能:主设备请求从设备一个字节,这个字节可以被主设备用 read()或available()接受
使用Wire.requesFrom()
后,从机端可以使用 onReceive ()
注册一个事件以响应主机的请求;主机可以通过Wire.available()
和 Wire.read()
函数读取这些数据
Wire.requrstFrom(addtess,quantity,stop)
参数
addtess
: 7位的器件地址quantity
: 请求得到的数量stop
:布尔形,‘1’ 则在请求结束后发送一个停止命令,并释放总线。‘0’则继续发送请求保持连接Wire.beginTransmission()
功能:发出开始传输数据的信号,设定传输数据到指定的从机设备 (开始一次传输数据,发送一个I2C开始字符)
wire.beginTransmission(address)
beginTransmission()
函数调用后,(再调用 write()
函数进行数据写入), 最后再调用 endTransmission()
函数方能产生 Start 信号 和发送 Slave Address 及通讯时序。
Wire.endTransmission()
功能:发出结束数据传输的信号(结束一个由beginTransmission()
开始的并且由write()
排列的从机的传输)
默认参数为stop:Wire.endTransmission(stop)
:如果为1 则endTransmission()
发送一个停止信息;如果为0 则发送开始信息
endTransmission(0)
:当输入参数为 0 时,将在通讯结束后,不产生 STOP 信号。
endTransmission(!0)
:当输入参数为 !0 时,在通讯结束后,生成 STOP 信号。(释放总线)
endTransmission()
:当无输入参数时,在通讯结束后,产生 STOP 信号。(释放总线)
Wire.write()
功能:write()
函数用于向 从机 写入数据。共有 3 种调用形式:
write(value)
:写入单字节
write(string)
:写入字符串
write(data, length)
:写入 length 个字节
例子
#include <Wire.h>
byte val = 0;
void setup()
{
Wire.begin(); // join i2c bus
}
void loop()
{
Wire.beginTransmission(44); // transmit to device #44 (0x2c)
// device address is specified in datasheet
Wire.write(val); // sends value byte
Wire.endTransmission(); // stop transmitting
val++; // increment value
if(val == 64) // if reached 64th position (max)
{
val = 0; // start over from lowest value
}
delay(500);
}
Wire.available()
功能:Wire.requestFrom()
请求从机数据后,可以使用available()
接收
Wire.read()
功能:Wire.requestFrom()
请求从机数据后,可以使用read()接收
Wire.onReceive()
功能:该函数可以在从机端注册一个事件,当从机收到主机发送的数据时即触发该函数
Wire.onRequest()
功能:该函数可以在注册一个事件,当主机收到从机发送数据请求时触发
在这个示例中,将使用两块开发板通过I2C通讯协议
在主设备读取/从设备发送的方式进行通信。
Arduino UNO主设备通过使用Wire库, 可以请求并读取从唯一寻址的从设备 Arduino UNO发送来的6字节数据。 收到该消息后,便可以在Arduino IDE 串口监视器窗中进行查看。
将主设备开发板的引脚4(或SDA数据引脚)和引脚5(或SCL时钟引脚)连接到从设备开发板的对应引脚上。图示电路图以两块UNO为例进行连接
为了保证串口通信的运行,必须通过USB将开发板连接到计算机上并保证各个开发板的供电。
Code
主设备读取
// 引入Wire库文件
#include <Wire.h>
void setup()
{
// Wire初始化, 加入i2c总线
// 如果未指定,则以主机身份加入总线。
Wire.begin();
// 初始化串口并设置波特率为9600
Serial.begin(9600);
}
void loop()
{
// 向从设备#8请求6个字节
Wire.requestFrom(8, 6);
// 当从从设备接收到信息时值为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 ");
// 以此回应主设备的请求
}
效果演示
通过主设备UNO硬件串口打开的串口监视器窗口视图
在这个示例中,将使用两个开发板通过I2C同步串行协议
以主机写入从机接受的方式相互通信。
Arduino UNO(主设备) 经过编程,每半秒向唯一寻址的从设备发送6个字节的数据。 从设备收到该消息后,可以在Arduino IDE 的串口监视器窗口中查看该数据。
将主设备开发板的引脚4(或SDA数据引脚)和引脚5(或SCL时钟引脚)连接到从设备开发板的对应引脚上。图示电路图以两块UNO为例进行连接
为了保证串口通信的运行,必须通过USB将开发板连接到计算机上并保证各个开发板的供电。
Code
主设备发送
// 引入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)
{
// 循环读取数据(除了最后一个字符,因为最后一个字符是数字,我们要用int来接收)
while (Wire.available() > 1)
{
// 接收字节数据并赋值给变量c(char)
char c = Wire.read();
// 打印该字节
Serial.print(c);
}
// 以int整数的形式接受字节数据并赋值给x(int)
int x = Wire.read();
// 打印该int变量x
Serial.println(x);
}
效果演示
文章参考:太极创客