简体   繁体   English

如何通过I2C使用Raspberry pi从Arduino读取数据

[英]How to Read Data from Arduino with Raspberry pi via I2C

I have connected Raspberry pi 2 model B with arduino uno via Bi-Directional Level shifter. 我已通过双向液位转换器将Raspberry pi 2 B型与arduino uno连接。

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

Hope my I2C connection is correct ? 希望我的I2C连接正确吗?

and my Arduino is connected to 8-Channel Relay Board. 我的Arduino已连接到8通道中继板。

Now I have written code in which I can control the Relay board by Raspberry pi. 现在,我已经编写了可以通过Raspberry pi控制中继板的代码。 For ex if i Press '1' the Relay 1 goes high. 例如,如果我按“ 1”,则继电器1变高。

Now I want to send data back from arduino to raspberry pi in order to cross check if Relay 1 is high or not, if Relay 1 is high then it should send some data back to Raspberry pi or else not. 现在,我想将数据从arduino发送回覆盆子pi,以便交叉检查Relay 1是否为高电平,如果Relay 1为高电平,则应该将一些数据发送回Raspberry pi或否则。

My Rpi code is 我的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()

My Arduino code: 我的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);
}

Now if I type 1 and due to some loose connection Relay 1 doesn't goes high, So in this case I want the arduino to take data from relay board and send it to Raspberry pi every time. 现在,如果我键入1并且由于连接松动,中继1不会变高,那么在这种情况下,我希望arduino每次从中继板上获取数据并将其发送到Raspberry pi。

It will be great if someone can explain also that how it works. 如果有人也可以解释它是如何工作的,那就太好了。

Hope I was able to explain the problem. 希望我能解释这个问题。 I have done lots of research but was not able to find some answer. 我做了很多研究,但找不到答案。

I am a beginner in python so please help me. 我是python的初学者,所以请帮助我。

Thanks in advance. 提前致谢。

The problem is that you are doing too much inside receiveData , which is called from the Interrupt Service Routine of the I2C utility code, twi.c . 问题在于,您在receiveData里面做的太多了,这是从I2C实用程序代码twi.c的中断服务例程twi.c You must handle the data quickly, and don't call any other routines that depend on interrupts being enabled (they are disabled during this ISR). 必须快速处理数据,并且不要调用任何其他取决于使能中断的例程(在此ISR期间它们被禁用)。

This means you can't call Serial.print , and you can't call any other Wire sending methods. 这意味着您不能调用Serial.print ,也不能调用任何其他Wire发送方法。 Even calling millis() or micros() is discouraged, as they do take a fair amount of time, and they depend on the TIMER interrupts being handled. 甚至不鼓励调用millis()micros() ,因为它们确实花费大量时间,并且它们依赖于正在处理的TIMER中断。

Of course, you are free call Wire.available() and Wire.read() . 当然,您可以免费调用Wire.available()Wire.read() Actually, byteCount tells you how many bytes are available, so you don't need to call Wire.available() again. 实际上, byteCount告诉您有多少字节可用,因此您无需再次调用Wire.available()

Essentially, your receivedData routine can read the data inside the routine if you're quick about processing it. 从本质上讲,你receivedData如果你快点处理这程序可读取的程序里面的数据。 Otherwise , you can only set a (volatile) flag and then watch for it in loop . 否则 ,您只能设置(volatile)标志,然后在loop对其进行监视。 From what I see in your sketch, you could do something like this: 根据我在草图中看到的内容,您可以执行以下操作:

// 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 );
  }
}

The delay in loop is unnecessary, and may cause problems if you add something else to loop . delayloop是不必要的,如果你添加别的东西可能会引起问题loop

The volatile keyword keeps the compiler from optimizing loop . volatile关键字使编译器无法优化loop Without that keyword, the test for newData in loop would disappear because the compiler thinks that newData doesn't change during loop . 没有该关键字,对newData in loop的测试将消失,因为编译器认为newDataloop期间不会改变。 Why test it? 为什么要测试? volatile newData tells the compiler that newData can change at any time, like during the receiveData ISR. volatile newData告诉编译器newData可以随时更改,例如在receiveData ISR期间。

And be sure to print the number in the rpi code, as pholtz suggested! 并确保按照pholtz的建议在rpi代码中打印number

In arduino code change sendData() function like this 在arduino代码中更改如下的sendData()函数

void sendData(){
    int relay_status;
    relay_status=digitalRead(4);
    Wire.write(relay_status);
    }

Also in hardware connect one 4th digital pin(or any other free I/O pins) to relay input. 同样在硬件中,将第四个数字引脚(或任何其他空闲的I / O引脚)连接到继电器输入。

hope it helps:) 希望能帮助到你:)

Ok, looks like a pretty good start. 好的,看起来是个不错的开始。 Two things I want to suggest here. 我想在这里建议两件事。

First, in your python program you should print number so that you can see it's value changing. 首先,在python程序中,您应该打印number以便可以看到它的值在变化。 It's storing your feedback from the Arduino, so you want to display that feedback to the screen. 它存储了来自Arduino的反馈,因此您希望将该反馈显示在屏幕上。 This is as simple as changing number = readNumber() to print readNumber() . 这就像更改number = readNumber()print readNumber()一样简单。

Second, in your Arduino program, are you sure that calling Wire.read() returns what you think it does? 其次,在您的Arduino程序中,您确定调用Wire.read()返回您认为的内容吗? It looks to me like read() returns a byte. 在我看来,read()返回一个字节。 Chances are that when you type 1 it's really being sent as '1', not 1. Char vs. Int. 可能是,当您键入1时,它实际上是作为“ 1”而不是1发送的。Char vs. Int。 Make sense? 说得通?

So you might want to check if(number == '1') instead. 因此,您可能想改为检查if(number == '1') Just my 2¢. 只是我的2美分。

Your i2c bus is not connected properly. 您的i2c总线未正确连接。 Remove the level shifter, and add 4.7k pull-ups to the 3.3v vcc on scl and sda lines. 拆下电平转换器,并在scl和sda线路的3.3v vcc上增加4.7k上拉电阻。 I2c chips only drive the line low, and require external resistors to pull the line high. I2c芯片仅将线驱动为低电平,并且需要外部电阻将线拉高。 This lets you mix voltage levels on your i2c bus fairly easily. 这使您可以轻松地在i2c总线上混合电压电平。 Then you can go back to figuring out what your code is doing. 然后,您可以返回来确定您的代码在做什么。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM