0

Arduino tečaj: Arduino i RPI surađuju

12. lesson

Ovo je dio cjelovitog tečaja Arduino-a.
  1. Štampajte dijelove koristeći ovaj Sketchup model RescueLine.zip.
  2. Sastavite robota koristeći upute za sastavljanje.
  3. Spojite kablove koristeći plan spajanja.
  4. Programirajte robota.
    1. Osnove.
    2. Praćenje crte.
    3. Naredbe.
    4. LIDARi.
    5. Prekidač, IMU, 8x8 LED.
    6. Praćenje zida.
    7. Srebro.
    8. 8x8 LED programiranje.
    9. Robotska ruka.
    10. Raspberry Pi.
    11. Prepoznavanje kamerom, OpenCV.
    12. Arduino i RPI surađuju (ova stranica).

Idea

If you want the Arduino and Raspberry Pi to be useful, they must cooperate. This lesson will show how. They will be exhanging messages over UART (serial) port, synchronising their states and conveying other information. Each party must know how to send and receive a message. Sending is easier: You send a message when You like. Receiving is more tricky because You do not know when a message will arrive so You will have to be constantly checking for that event (polling). There is other option, using interrupts, which will not be covered here. In this example Arduino will send a start message, ordering RPI to start tracking a line. RPI will receive the message and start sending a string of messages, informing Arduino about the centre of the line it is tracking. This will keep going on till Arduino receives a message that the line is on the far left or right. Arduino will send a stop message and they will both go into idle state.

In order to enable efficient information flow (bigger ratio [information delivered] / [number of bytes spent]), UART library uses a special format, in a form of class Message. You can check the library to see what it looks like. It serializes different C++ types into byte arrays and, on the other end, serializes them again into original types. What the meaning of the particular parts of the message is - it is up to the user. We chose that the first byte will identify the type of the command and the rest will be payload.

The programs are not simple. A knowledge of classes and pointers is very advisable.

Arduino part

Make sure You have UART library version 0.1 or higher. The library contains UART.ino:

/**
  Purpose: UART communication example
  @author ML-R team
  @version 0.0 2018-08-08
  Licence: You can use this code any way you like.
*/

#include <UART.h>

enum State {IDLE, LINE, RED_ROOM}; // State machine pattern - change states of Your robot

UART uart(&Serial1); // Communication to Raspberry Pi using Serial1
Message message; // A message - used to carry commands to RPI and back
State state; // Present state of the robot
uint16_t x = 40; // x coordinate of the object of interest, like a ball or line

void setup() {
  Serial.begin(115200); // Communication to PC
  delay(1000); // Wait 1 sec. to be sure that Serial.print works.
  Serial.println("Start");

  uart.add(); // Initialize uart object.

  // Change state into LINE and immediately notify RPI. It should be done in a separate function so that all the 3 commands execute as a group. The group should change LED display, etc.
  state = LINE;
  message.append((uint8_t)'L'); // Build a message with command code 'L' (change the state into LINE). (uint8_t) is needed - study parameter overloading in C++.
  uart.write(message, true); // Send the message and RPI will change the state into LINE as well.
}

void loop() {
  doSomethingUseful(); // A command or many of them. It is essential that all of them together execute fast, for example less than 30 ms in total.
  messagesInboundHandle(); // If the previous commands execute in less than 30 ms, this function will execute 33 times per second and that will be enough to track a line or a ball.
}

/** Your program's logic.
 */
void doSomethingUseful() {
  if (state != IDLE) {

    //Print the position of the line or the ball
    for (uint16_t i = 0; i < x; i++)
      Serial.print(' ');
    Serial.println(state == LINE ? 'L' : 'R');

    // When x reaches left or right bound, send a message to RPI to stop and stop.
    if (x == 0 || x == 80){
      message.reset();
      message.append((uint8_t)'I'); /// 'I' command - go into IDLE state
      uart.write(message);
      state = IDLE;
    }
  }
  delay(200);
}

/** Error handler
@param message - error message
*/
void error(String message) {
  Serial.println(message);
  while (true)
    ;
}

/** Reads messages and triggers appropriate actions
*/
void messagesInboundHandle() {
  if (uart.available()) { // if any data arrived
    message = uart.readMessage(); // read the command
    uint8_t messageId = message.readUInt8(); // 1. byte determines the command.
    switch (messageId) {
      case 'l': // 'l' (line) command carries x position
        x = message.readUInt16();
        break;
      case 'L': // RPI cannot command Arduino to change state
        error("L impossible for Arduino.");
        break;
      case 'r': // 'r' (red room) command carries x position
        x = message.readUInt16();
        break;
      case 'R':
        error("R impossible for Arduino.");
        break;
      default: // RPI cannot command Arduino to change state
        error("Impossible message id: " + (String)(int)messageId);
        break;
    }
  }
}

RPI part

The whole program can be downloaded from GIT.

The program starts in main.cpp. TEST_UART_MESSAGES value ensures that the part for our test will be used:

///Configuration
const int thresh = 20; /// Canny algorithm threshold
const bool saveImages = false; /// For tests later
Robot::State state = Robot::TEST_UART_MESSAGES; /// Check Robot::State to see all the options

int main(int argc, char *argv[])
{
    Robot robot(state, thresh, saveImages); /// Object robot
    robot.run(); /// Start the program
    return 0;
}
It creates a Robot object ("robot") and calls method run(). Let's take a look at run():

/** Start and choose action
*/
void Robot::run(){
    if (state == TEST_UART)
        uartTest();
    else if (state == TEST_UART_MESSAGES)
        uartMessagesTest();
    else if (state == FIND_CIRCLES)
        camera->findCirclesUsingTrackbars();
    else if (state == CALIBRATE_BALL)
        camera->calibrateBall();
    else if (state == CROSSING_SINGLE)
        camera->crossing(true);
    else if (state == CROSSING_CONTINUOUS)
        camera->crossing(false);
    else
        exit(9);
}


So, robot's method uartMessagesTest() will be called:
/** Part of the test initiated from Arduino UART.ino in UART library.
*/
void Robot::uartMessagesTest(){
    uint32_t lastSentMs = 0;
    int16_t x = 40;
    while (state != IDLE){
        switch(state){
            case LINE:
                if (millis() - lastSentMs > 100){ /// Every 100 ms
                    lastSentMs = millis();

                    x += rand() % 11 - 5; /// Add between -5 and 5
                    if (x < 0)
                        x = 0;
                    if (x > 80)
                        x = 80;

                    /// Construct and send a message: new x position
                    message->reset();
                    message->append((uint8_t)'l');
                    message->append((uint16_t)x);
                    uart->write(*message, true);
                }
                break;
            case RED_ROOM:
                break;
            case TEST_UART_MESSAGES:
                break;
            default:
                exit(8);
        }
        uartMessagesInboundHandle(true);
    }
}


The program will be iterating the while loop above till the state changes into IDLE. During the iterations, it will perform actions specified for differnet states. In this example only LINE state will do something: it will monitor line and report x position using messages. In this simple sample no actual searching for line (using a camera) is done. Instead, a random generated positions are reported. After the switch, uartMessagesInboundHandle() is called, to check if a message from Arduino arrived in the meantime. So, it is time to review uartMessagesInboundHandle():

/** Reads messages and triggers appropriate actions
@param verbose - detailed output
 */
void Robot::uartMessagesInboundHandle(bool verbose){
    if (uart->available()){
        Message message = uart->readMessage(verbose);
        uint8_t messageId = message.readUInt8();
        switch (messageId) {
            case 'I': /// New state: IDLE
                stateSet(IDLE);
                break;
            case 'l': /// Impossible for RPI
                exit(11);
            case 'L': /// New state: LINE
                stateSet(LINE);
                break;
            case 'r': /// Impossible for RPI
                exit(12);
            case 'R': /// New state: RED_ROOM
                stateSet(RED_ROOM);
                break;
            default: // RPI cannot command Arduino to change state
                cerr << "Impossible message id: " << messageId << endl;
                exit(11);
        }
    }
}


If UART reports at least 1 available (received) byte, the message will be read. Message's id is in the first byte (obtained by calling readUInt8()). In the next switch, the state will be changed if Arduino demanded so.

Let's start RPI part as before:



No big deal, nothing happened. But the better part follows after we start Arduino:



RPI received a start (change state) message and started reporting x position. On the other side, Arduino is receiving them, displaying x position:



Finally, Arduino receives far-right x message and instructs RPI to stop:



Previous lesson
Next lesson