我有一个带有4个盆的Arduino。另一个Arduino通过i2c接收这4个值并将它们打印在显示器上。问题是我不知道如何发送奴隶所知道的4个值,知道哪个值属于哪个Pot。
奴隶代码:
#include <Wire.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
void setup()
{
Wire.begin(5);
Wire.onReceive(receiveEvent);
Serial.begin(9600);
lcd.begin(16,2);
}
void loop()
{
}
void receiveEvent(int)
{
while(Wire.available())
{
//How to create this part? How does the Slave know wich value belongs to which pot?
}
}
主码:
#include <Wire.h>
void setup()
{
Serial.begin(9600);
Wire.begin();
delay(2000);
}
void loop()
{
int sensor1 = analogRead(A1);
Wire.beginTransmission(5);
Wire.write(sensor1);
Serial.print(sensor1);
Wire.endTransmission();
delay(100);
int sensor2 = analogRead(A2);
Wire.beginTransmission(5);
Wire.write(sensor2);
Serial.print(sensor2);
Wire.endTransmission();
delay(500);
}
答案 0 :(得分:13)
啊,我们这里有一个关于如何设计I2C通信的基本问题。遗憾的是,Arduino IDE中包含的I2C主设备和从设备的示例过于局限,无法提供有关此问题的明确指导。
首先,在您的示例中,主角和从角色交换并应切换。从器件应读取模拟输入的值,主器件应该请求它们。为什么?因为它是主人,应该决定何时请求值并正确解码请求。奴隶应该为给定的请求提供正确的答案,消除数据解释的问题。
I2C通信基于由主控制的requestFunction-(wait)-requestResponse序列。 请参阅range finder example on arduino page。简而言之:
首先:master请求一个测量距离的函数:
// step 3: instruct sensor to return a particular echo reading
Wire.beginTransmission(112); // transmit to device #112
Wire.write(byte(0x02)); // sets register pointer to echo #1 register (0x02)
Wire.endTransmission(); // stop transmitting
(有时奴隶需要一些时间,例如10到50毫秒来处理请求,但在示例中我指的是主不会延迟读取)
第二:主要请求回复:
// step 4: request reading from sensor
Wire.requestFrom(112, 2); // request 2 bytes from slave device #112
第三:master尝试阅读和分析响应。
您应该以类似的方式设计可靠的I2C通信。
我是这样做的;您可以遵循我的模式并获得可扩展的从属实现,它将支持一个功能:读取模拟输入,但可以通过向从属主循环添加额外的功能代码和所需的处理实现来轻松扩展
初步评论
需要某种简单的协议来控制奴隶 - 例如它应该支持请求功能。在读取四个模拟输入这样简单的场景中,并非绝对需要支持函数请求,但我所描述的是您可能在其他项目中使用的更通用的模式。
Slave不应对请求响应执行任何其他操作(如读取输入),因为I2C通信可能会中断(由于延迟)并且您将获得部分响应等。这是影响从设计的非常重要的要求。
响应(以及请求,如果需要)可以包含CRC,就像主机等待的时间不够长,它可能会得到空响应。如果没有其他人会使用您的代码,则不需要这样的对策,这里不再描述。其他重要的是Wire库缓冲区限制,它是32字节并且在不修改缓冲区长度的情况下实现CRC校验和将可用数据长度限制为两个字节(如果使用crc16)。
奴隶:
#include <WSWire.h> // look on the web for an improved wire library which improves reliability by performing re-init on lockups
// >> put this into a header file you include at the beginning for better clarity
enum {
I2C_CMD_GET_ANALOGS = 1
};
enum {
I2C_MSG_ARGS_MAX = 32,
I2C_RESP_LEN_MAX = 32
};
#define I2C_ADDR 0
#define TWI_FREQ_SETTING 400000L // 400KHz for I2C
#define CPU_FREQ 16000000L // 16MHz
extern const byte supportedI2Ccmd[] = {
1
};
// << put this into a header file you include at the beginning for better clarity
int argsCnt = 0; // how many arguments were passed with given command
int requestedCmd = 0; // which command was requested (if any)
byte i2cArgs[I2C_MSG_ARGS_MAX]; // array to store args received from master
int i2cArgsLen = 0; // how many args passed by master to given command
uint8_t i2cResponse[I2C_RESP_LEN_MAX]; // array to store response
int i2cResponseLen = 0; // response length
void setup()
{
// >> starting i2c
TWBR = ((CPU_FREQ / TWI_FREQ_SETTING) - 16) / 2;
Wire.begin(I2C_ADDR); // join i2c bus
Wire.onRequest(requestEvent); // register event
Wire.onReceive(receiveEvent);
// << starting i2c
}
void loop()
{
if(requestedCmd == I2C_CMD_GET_ANALOGS){
// read inputs and save to response array; example (not tested) below
i2cResponseLen = 0;
// analog readings should be averaged and not read one-by-one to reduce noise which is not done in this example
i2cResponseLen++;
i2cResponse[i2cResponseLen -1] = analogRead(A0);
i2cResponseLen++;
i2cResponse[i2cResponseLen -1] = analogRead(A1);
i2cResponseLen++;
i2cResponse[i2cResponseLen -1] = analogRead(A2);
i2cResponseLen++;
i2cResponse[i2cResponseLen -1] = analogRead(A3);
// now slave is ready to send back four bytes each holding analog reading from a specific analog input; you can improve robustness of the protocol by including e.g. crc16 at the end or instead of returning just 4 bytes return 8 where odd bytes indicate analog input indexes and even bytes their values; change master implementation accordingly
requestedCmd = 0; // set requestd cmd to 0 disabling processing in next loop
}
else if (requestedCmd != 0){
// log the requested function is unsupported (e.g. by writing to serial port or soft serial
requestedCmd = 0; // set requestd cmd to 0 disabling processing in next loop
}
}
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent(){
Wire.write(i2cResponse, i2cResponseLen);
}
// function that executes when master sends data (begin-end transmission)
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{
//digitalWrite(13,HIGH);
int cmdRcvd = -1;
int argIndex = -1;
argsCnt = 0;
if (Wire.available()){
cmdRcvd = Wire.read(); // receive first byte - command assumed
while(Wire.available()){ // receive rest of tramsmission from master assuming arguments to the command
if (argIndex < I2C_MSG_ARGS_MAX){
argIndex++;
i2cArgs[argIndex] = Wire.read();
}
else{
; // implement logging error: "too many arguments"
}
argsCnt = argIndex+1;
}
}
else{
// implement logging error: "empty request"
return;
}
// validating command is supported by slave
int fcnt = -1;
for (int i = 0; i < sizeof(supportedI2Ccmd); i++) {
if (supportedI2Ccmd[i] == cmdRcvd) {
fcnt = i;
}
}
if (fcnt<0){
// implement logging error: "command not supported"
return;
}
requestedCmd = cmdRcvd;
// now main loop code should pick up a command to execute and prepare required response when master waits before requesting response
}
主:
#include <WSWire.h>
#define I2C_REQ_DELAY_MS 2 // used for IO reads - from node's memory (fast)
#define I2C_REQ_LONG_DELAY_MS 5 //used for configuration etc.
#define TWI_FREQ_SETTING 400000L
#define CPU_FREQ 16000000L
enum {
I2C_CMD_GET_ANALOGS = 1
};
int i2cSlaveAddr = 0;
void setup(){
// joining i2c as a master
TWBR = ((CPU_FREQ / TWI_FREQ_SETTING) - 16) / 2;
Wire.begin();
}
void loop(){
//requesting analogs read:
Wire.beginTransmission(i2cSlaveAddr);
Wire.write((uint8_t)I2C_CMD_GET_ANALOGS);
Wire.endTransmission();
delay(I2C_REQ_DELAY_MS);
// master knows slave should return 4 bytes to the I2C_CMD_GET_ANALOGS command
int respVals[4];
Wire.requestFrom(i2cSlaveAddr, 4);
uint8_t respIoIndex = 0;
if(Wire.available())
for (byte r = 0; r < 4; r++)
if(Wire.available()){
respVals[respIoIndex] = (uint8_t)Wire.read();
respIoIndex++;
}
else{
// log or handle error: "missing read"; if you are not going to do so use r index instead of respIoIndex and delete respoIoIndex from this for loop
break;
}
// now the respVals array should contain analog values for each analog input in the same order as defined in slave (respVals[0] - A0, respVals[1] - A1 ...)
}
我希望我的榜样会有所帮助。它基于代码工作数周,从多个从属设备每秒进行40次读取,但是我没有编译它来测试您需要的功能。 如果由于某种原因slave不会响应请求,请使用WSWire库作为Wire(至少和Arduino 1.0.3一样)可能偶尔冻结你的master。
编辑: WSWire lib需要用于I2C的外部上拉电阻,除非您修改源并启用内部上拉电路,例如Wire。
编辑:您可以尝试使用EasyTransfer库,而不是创建i2c slave实现。我没有尝试过,但如果发送四个字节就是你需要的一切,那么它可能更容易使用它。
编辑[12.2017]:该块上有一个新玩家 - PJON - 一个适合轻松进行多主通信的库,非常适合交换底池值(等等)。它已经存在了一段时间,但最近几个月获得了大量的开发速度。我部分参与了它的开发,并将我目前使用的所有现场级和本地总线(I2C,MODBUS RTU)通过单线,硬件串行或RF切换到PJON。
答案 1 :(得分:0)
查看GitHub-I2CBus,我做了完全相同的事情。希望它可以提供帮助