如何通过I2C从Arduino读取数据并与Raspberry Pi通信

6

我已经通过双向电平转换器将Raspberry pi 2 model B与Arduino Uno连接起来。

Raspberry pi    GND    ----------   GND     Arduino
                3.3v   ----------   5v
                SCL    ----------   A5
                SDA    ----------   A4

希望我的I2C连接是正确的?
并且我的Arduino已连接到8通道继电器板。
现在我编写了代码,可以通过Raspberry Pi控制继电器板。例如,如果我按下“1”,则继电器1会变高。
现在我想从Arduino向Raspberry Pi发送数据,以便交叉检查继电器1是否处于高电平状态,如果继电器1处于高电平状态,则应向Raspberry Pi发送一些数据,否则不发送。
我的Rpi代码如下:
import smbus
import time
# for RPI version 1, use "bus = smbus.SMBus(0)"
bus = smbus.SMBus(1)

# This is the address we setup in the Arduino Program
address = 0x04

def writeNumber(value):
    bus.write_byte(address, value)
    # bus.write_byte_data(address, 0, value)
    return -1

def readNumber():
    number = bus.read_byte(address)
    # number = bus.read_byte_data(address, 1)
    return number

while True:
    var = input("")
    if not var:
        continue

    writeNumber(var)
    number = readNumber()

我的Arduino代码:

#include <Wire.h>

#define SLAVE_ADDRESS 0x04
#define RELAY1 9

int number = 0;
int state = 0;

void setup() {
    pinMode(RELAY1, OUTPUT);

    Serial.begin(9600); // start serial for output
    // initialize i2c as slave
    Wire.begin(SLAVE_ADDRESS);

    // define callbacks for i2c communication
    Wire.onReceive(receiveData);
    Wire.onRequest(sendData);

    Serial.println("Ready!");
}

void loop() {
    delay(100);
}

// callback for received data
void receiveData(int byteCount){

    while(Wire.available()) {
        number = Wire.read();
        Serial.print("data received: ");
        Serial.println(number);

        if (number == 1){

            if (state == 0){
                digitalWrite(RELAY1, HIGH); // set the LED on
                state = 1;
            }
            else{
                digitalWrite(RELAY1, LOW); // set the LED off
                state = 0;
            }
        }
    }
}

// callback for sending data
void sendData(){
    Wire.write(number);
}

现在,如果我输入1并由于一些松动的连接,继电器1没有上升,那么在这种情况下,我希望Arduino每次从继电器板获取数据并将其发送到树莓派。
如果有人能解释一下它的工作原理,那就太好了。
希望我已经能够解释清楚问题。我已经做了很多研究,但是没有找到答案。
我是Python的初学者,所以请帮帮我。
谢谢您提前的帮助。

你有可以使用的电气测量设备吗?在这种情况下,逻辑分析仪是理想的选择,以确定问题所在的一侧。另外,请提供你的双向电平转换器的零件编号。 - Chad Kennedy
4个回答

2
问题在于你在receiveData函数中做了太多事情,该函数是从I2C实用程序代码twi.c的中断服务例程中调用的。你必须快速处理数据,并且不要调用任何依赖于启用中断的其他例程(在此ISR期间它们被禁用)。
这意味着你不能调用Serial.print,也不能调用任何其他Wire发送方法。即使调用millis()micros()也是不鼓励的,因为它们需要相当长的时间,并且它们取决于TIMER中断的处理。
当然,你可以自由地调用Wire.available()Wire.read()。实际上,byteCount告诉你有多少字节可用,因此你不需要再次调用Wire.available()
基本上,如果你快速处理数据,你的receivedData例程可以在函数内部读取数据。否则,你只能设置一个(易失性)标志,然后在loop中观察它。根据我在你的草图中看到的内容,你可以像这样做:
// variables that allow signalling between receiveData ISR and loop
volatile bool    newData = false;
volatile uint8_t state   = false;

// callback for received data
void receiveData(int byteCount)
{
    // Read all the bytes; only the last one changes the relay state
    while (byteCount-- > 0)
      number = Wire.read();

    if (state != number) {
      state   = number;
      newData = true;
    }
}

// callback for sending data
void sendData(){
    Wire.write(number);
}

void loop()
{
  if (newData) {
    newData = false; // clear the flag for next time

    if (number == 1){
        digitalWrite(RELAY1, HIGH); // set the LED on
    } else {
        digitalWrite(RELAY1, LOW); // set the LED off
    }

    Serial.print("data received: ");
    Serial.println( number );
  }
}
loop 中的 delay 是不必要的,如果你向 loop 添加其他内容,可能会导致问题。

volatile 关键字防止编译器优化 loop。 如果没有该关键字,则在 loop 中对 newData 进行测试将消失,因为编译器认为 newDataloop 中不会改变。 为什么要测试它?volatile newData 告诉编译器,像在 receiveData ISR 中一样,newData 随时可以更改。

请务必按照 pholtz 的建议,在 rpi 代码中打印 number


0
在Arduino代码中,将sendData()函数更改为以下内容。
void sendData(){
    int relay_status;
    relay_status=digitalRead(4);
    Wire.write(relay_status);
    }

同样在硬件上,连接第4个数字引脚(或其他任意可用的I/O引脚)到继电器输入。

希望这有所帮助:)


0

您的i2c总线连接不正确。请移除电平转换器,并在scl和sda线上添加4.7k上拉电阻到3.3v vcc。I2c芯片只能将线路拉低,并需要外部电阻器将线路拉高。这使您可以相当容易地混合i2c总线上的电压级别。然后您可以继续研究您的代码正在做什么。


我在将数据从Arduino发送到树莓派时遇到了问题。 - shivam
我有与 OP 相同的连接(没有上拉电阻器),除了 3.3V<->5V,它工作正常。在我将设备的地线连接在一起之前,它不起作用。 - Joseph Sheedy

0

好的,看起来这是一个不错的开始。我有两个建议想提出来。

首先,在你的Python程序中,你应该打印number,这样你可以看到它的值在改变。它存储了你从Arduino获取的反馈信息,所以你想要把这个反馈信息显示到屏幕上。只需要将number = readNumber()更改为print readNumber()就可以了。

其次,在你的Arduino程序中,你确定调用Wire.read()返回的是你想要的东西吗?在我看来,read()返回的是一个字节。当你输入1时,很可能实际被发送的是'1'而不是1。字符 vs. 整数。明白了吗?

所以你可能想检查的是if(number == '1')。仅供参考。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接