Arduino serial works fine with Debian but hangs with Raspbian

183 views Asked by At

I'm working on a personal home automation project.

On the server side, I have an Arduino Pro Mini with:

  • a 433 MHz TX module on pin 2
  • a 433 MHz RX module on pin 3
  • a DHT22 probe on pin 4 (with 10k pull-up)
  • a DHT22 probe on pin 5 (with 10k pull-up)

I have two absolutely identical of these modules; one will be the radio relay (and DHT "server") and the other a secondary DHT "server".

When it is linked to my laptop (Debian Wheezy) through an FTDI cable, it can read both local probes and switch my wall plugs on/off thanks to a C program I wrote. I'd like to use it from a Raspberry Pi. But on the Raspberry Pi (with the same FTDI cable on USB), it executes the first command I send and then hangs my terminal, forcing me to use CTRL+C.

Here is the sketch on the Arduino side (header) :

/**
 * probe.h
 * 
 * @author David Mézière <...>
 */

/**
 * DHT probe result
 * 
 */
struct Probe {
  float temperature;
  float humidity;
};

Main file :

/**
 * probe.ino
 * 
 * @author David Mézière <...>
 */

#include "probe.h"

/**
 * Uses DHT sensor library, from Adafruit.
 * @see https://github.com/adafruit/DHT-sensor-library
 */
#include <DHT.h>

/**
 * Uses RC Switch library, from sui77.
 * @see https://github.com/sui77/rc-switch
 */
#include <RCSwitch.h>

// Pinout definitions
#define TX      2 // 433 MHz transmitter pin number
#define RX      3 // 433 MHz receiver pin number
#define PROBE1  4 // First DHT22 probe pin number
#define PROBE2  5 // Second DHT22 probe pin number
#define LED    13 // On-board status LED pin number

RCSwitch radio = RCSwitch();

// DHT probes definition
DHT dht1(PROBE1, DHT22);
DHT dht2(PROBE2, DHT22);

// Incomming command buffer
byte cmd[9];

/**
 * Setup
 * 
 * @return void
 */
void setup()
{
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);

  Serial.begin(9600);
  Serial.setTimeout(1000); // doesn't fix the problem

  // Attach receiver to interrupt 1, meaning pin 3
  radio.enableReceive(1);

  radio.enableTransmit(TX);

  dht1.begin();
  dht2.begin();

  // Debug: Internal LED will blink 3 times rapidly to show when a reboot occurs.
  for (int i = 0; i < 3; i++) {
    digitalWrite(LED, HIGH);
    delay(250);
    digitalWrite(LED, LOW);
    delay(250);
  }
}

/**
 * Loop
 * 
 * @return void
 */
void loop()
{
  if (Serial.available() == 9 && readCommand()) {

    // Lights-up internal LED to show when a command has been executed
    digitalWrite(LED, HIGH);
    delay(1000);
    digitalWrite(LED, LOW);
  }
}

/**
 * Query probe
 * 
 * Query provided [dht] probe until [retry] times for both temperature and humidity.
 * 
 * @param DHT dht Pointer to DHT object
 * @param int retry Number of tries
 * @return Probe Probe result (float temperature in °C, float humidity in %)
 */
Probe queryProbe(DHT* dht, int retry)
{
  Probe probe;

  // Query DHT22 probe for temperature, a maximum of [retry] times.
  for (int t = 0; t < retry; t++) {
    probe.temperature = dht->readTemperature(false);
    if (!isnan(probe.temperature)) {
      break;
    }
    delay(50);
  }

  // Query DHT22 probe for humidity, a maximum of [retry] times.
  for (int h = 0; h < retry; h++) {
    probe.humidity = dht->readHumidity();
    if (!isnan(probe.humidity)) {
      break;
    }
    delay(50);
  }

  return probe;
}

/**
 * Read command
 * 
 * If serial buffer contains 2 bytes, move them to a local buffer and return true. else return false.
 * 
 * @return boolean
 */
boolean readCommand()
{
  // Reads the current buffer
  Serial.readBytes(cmd, 9);

  // Calculates the check sum of payload
  int sum = cmd[2] ^ cmd[3] ^ cmd[4] ^ cmd[5] ^ cmd[6] ^ cmd[7];

  // Checking header and checksum a header of 0xBA 0xB1 means DHT query
  if (cmd[0] == 0xBA && cmd[1] == 0xB1 && cmd[8] == sum) {

    unsigned int module = cmd[2];
    unsigned int probe  = (cmd[4] << 24) + (cmd[5] << 16) + (cmd[6] << 8) + cmd[7];

    Probe result;

    switch (module) {

      case 1:
        // Selects the right probe
        if (probe == 1) {
          result = queryProbe(&dht1, 3);
        } else if (probe == 2) {
          result = queryProbe(&dht2, 3);
        }

        // Send status repport to client
        Serial.print("1;");
        Serial.print(module);
        Serial.print(";");
        Serial.print(probe);
        Serial.print(";");
        Serial.print(result.temperature);
        Serial.print(";");
        Serial.println(result.humidity);
        Serial.flush(); // Doesn't fix the problem
        break;
    }

    return true;

  // A header of 0xBA 0xB2 means rf wall plugs query
  } else if (cmd[0] == 0xBA && cmd[1] == 0xB2 && cmd[8] == sum) {

    unsigned int proto = cmd[2];
    unsigned int length = cmd[3];
    unsigned int value = (cmd[4] << 24) + (cmd[5] << 16) + (cmd[6] << 8) + cmd[7];
    radio.send(value, length);

    // Send status repport to client
    Serial.print("2;");
    Serial.print(proto);
    Serial.print(";");
    Serial.print(length);
    Serial.print(";");
    Serial.print(value);
    Serial.print(";");
    Serial.println("OK");
    Serial.flush(); // Doesn't fix the problem

    return true;

  } else {
    Serial.println("KO");
    Serial.flush(); // Doesn't fix the problem

    return false;
  }
}

And on the client side :

/** 
 * probe.c
 * 
 * @author David Mézière <...>
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <getopt.h>

const char* device;
static int module  = 0; // uint_8 ?
static int probe   = 0; // uint_8 ?
const char* proto;
static int length  = 0; // uint_8 ?
static int value   = 0; // uint_32 ?
static int verbose = 0; // uint_8 ?

void help()
{
    printf("usage:\n");
    printf("\n");
    printf("probe [options] [arguments]\n");
    printf("\n");
    printf("options:\n");
    printf("  -h|--help:    Displays this help and exit\n");
    printf("  -v|--verbose: Be more verbose\n");
    printf("\n");
    printf("arguments:\n");
    printf("  -d|--device:  string Serial device to use (ex: /dev/ttyUSB0)\n");
    printf("  -m|--module:  integer DHT22 module to query\n");
    printf("  -p|--probe:   integer DHT22 probe to query\n");
    printf("  -r|--proto:   string Radio / IR protocol\n");
    printf("  -l|--length:  integer Radio / IR value length in bits\n");
    printf("  -a|--value:   integer Radio / IR value\n");
    printf("\n");
    printf("examples:\n");
    printf("  probe --device /dev/ttyUSB0 --module 1 --probe 1 : Will query first DHT22 probe of first module\n");
    printf("  probe --proto radio1 --length 12 --value 5393 : Will send value 5393 on 12 bits over the air using protocol 1\n");
    printf("  probe --proto ir11 --length 64 --value 3772793023 : Will send value 3772793023 on 64 bits by infra red using protocol 11\n");
}

void parseArgs(int argc, char **argv)
{
    int c;

    while (1) {
        static struct option long_options[] = {
            {"device",  required_argument, 0, 'd'},
            {"help",    no_argument,       0, 'h'},
            {"module",  required_argument, 0, 'm'},
            {"probe",   required_argument, 0, 'p'},
            {"proto",   required_argument, 0, 'r'},
            {"length",  required_argument, 0, 'l'},
            {"value",   required_argument, 0, 'a'},
            {"verbose", no_argument,       0, 'v'},
            {0, 0, 0, 0}
        };
        /* getopt_long stores the option index here. */
        int option_index = 0;

        c = getopt_long(argc, argv, "d:hm:p:v", long_options, &option_index);

        /* Detect the end of the options. */
        if (c == -1) {
            break;
        }

        switch (c) {
            case 0:
                /* If this option set a flag, do nothing else now. */
                if (long_options[option_index].flag != 0) {
                    break;
                }
                printf("option %s", long_options[option_index].name);
                if (optarg) {
                    printf (" with arg %s", optarg);
                }
                printf("\n");
                break;

            case 'd':
                device = optarg;
                break;

            case 'h':
                help();
                exit(0);
                break;

            case 'm':
                module = atoi(optarg);
                break;

            case 'p':
                probe = atoi(optarg);
                break;

            case 'r':
                proto = optarg;
                break;

            case 'l':
                length = atoi(optarg);
                break;

            case 'a':
                value = atoi(optarg);
                break;

            case 'v':
                verbose = 1;
                break;

            case '?':
                /* getopt_long already printed an error message. */
                break;

            default:
                abort();
        }

    }

    /* Print any remaining command line arguments (not options). */
    if (optind < argc) {
        printf("non-option ARGV-elements: ");
        while (optind < argc) {
            printf("%s ", argv[optind++]);
        }
        putchar('\n');
    }

    if (&device[0] == '\0') {
        fprintf(stderr, "--device is mandatory\n");
        exit(1);
    } else if (verbose) {
        printf("Device: %s\n", device);
    }

    if (verbose) {
        printf("Querying probe %i of module %i.\n", probe, module);
    }
}

void initSerial(int fd)
{
    struct termios toptions;

    /* get current serial port settings */
    tcgetattr(fd, &toptions);
    /* set 9600 baud both ways */
    cfsetispeed(&toptions, B9600);
    cfsetospeed(&toptions, B9600);
    /* 8 bits, no parity, no stop bits */
    toptions.c_cflag &= ~PARENB;
    toptions.c_cflag &= ~CSTOPB;
    toptions.c_cflag &= ~CSIZE;
    toptions.c_cflag |= CS8;
    /* Canonical mode */
    toptions.c_lflag |= ICANON;
    /* commit the serial port settings */
    tcsetattr(fd, TCSANOW, &toptions);
}

int main(int argc, char **argv)
{
    // Parses command line arguments
    parseArgs(argc, argv);

    int fd, n, i;
    char buf[64] = "temp text";

    /* open serial port */
    fd = open(device, O_RDWR | O_NOCTTY);

    if (verbose) {
        printf("Device %s opened as %i\n", device, fd);
    }

    /* 
     * Note: Most Arduinos models will reboot upon connection, and they need
     * some time for it. I use a pro/mini that doesn't, so i commented it out.
     */
    // usleep(3500000);

    // Sets the serial port settings (9600 bps, 8 bits, no parity, no stop bits)
    initSerial(fd);

    /**
     * 72 bits
     *                       | Header    | Param 1 | Param 2 | Param 3 | sum |
     *                       | 16 b      | 8 b     | 8 b     | 32 b    | 8 b |
     * Cas 1 : Requête DHT   | 0xba 0xb1 | module  | 0x00    | sonde   | sum |
     * Cas 2 : Requête radio | 0xba 0xb2 | proto   | length  | value   | sum |
     * Cas 3 : Requête IR    | 0xba 0xb3 | proto   | length  | value   | sum |
     */

    unsigned char oBuf[9];

    // printf("%s\n", proto);
    // printf("%i\n", length);

    if (module > 0 && probe > 0) {
        if (verbose) {
            printf("DHT mode\n");
        }
        oBuf[0] = 0xBA;
        oBuf[1] = 0xB1; // DHT query
        oBuf[2] = module;
        oBuf[3] = 0x00;
        oBuf[4] = (probe >> 24) & 0xFF;
        oBuf[5] = (probe >> 16) & 0xFF;
        oBuf[6] = (probe >> 8) & 0xFF;
        oBuf[7] = probe & 0xFF;
        oBuf[8] = oBuf[2];
        oBuf[9] = '\n';

        // Calculates the XOR sum
        for (i = 3; i < 8; i++) {
            oBuf[8] ^= oBuf[i];
        }

        // sprintf(oBuff, "%c%c%c%c%c%c", 0xba, 0xb1, module, 0x00, probe, sum);
    } else if (strcmp((const char*)proto, "radio1") == 0 && length > 0) {
        if (verbose) {
            printf("Radio mode\n");
        }
        oBuf[0] = 0xBA;
        oBuf[1] = 0xB2; // Radio query
        oBuf[2] = 0x01; // Protocol 1
        oBuf[3] = length;
        oBuf[4] = (value >> 24) & 0xFF;
        oBuf[5] = (value >> 16) & 0xFF;
        oBuf[6] = (value >> 8) & 0xFF;
        oBuf[7] = value & 0xFF;
        oBuf[8] = oBuf[2];
        oBuf[9] = '\n';

        // Calculates the XOR sum
        for (i = 3; i < 8; i++) {
            oBuf[8] ^= oBuf[i];
        }
    } else {
        if (verbose) {
            printf("Unknown mode\n");
        }

    }

    /* Send the buffer */
    write(fd, oBuf, 9);

    /* Receive string from Arduino */
    n = read(fd, buf, 64);

    /* insert terminating zero in the string */
    buf[n] = 0;

    if (verbose) {
        printf("%i bytes read, buffer contains: %s\n", n, buf);
    } else {
        printf("%s", buf);
    }

    return 0;
}

I compile it using just gcc probe.c -o probe.

On Debian, I can use the system as much as I want, it works:

dmeziere@portable2-wlan:~/dev/probe$ gcc probe.c -o probe
dmeziere@portable2-wlan:~/dev/probe$ ./probe --device /dev/ttyUSB0 --module 1 --probe 1
1;1;1;23.60;43.10
dmeziere@portable2-wlan:~/dev/probe$ ./probe --device /dev/ttyUSB0 --module 1 --probe 2
1;1;2;23.60;38.50
dmeziere@portable2-wlan:~/dev/probe$ ./probe --device /dev/ttyUSB0 --proto radio1 --length 24 --value 5396
2;1;24;5396;OK
dmeziere@portable2-wlan:~/dev/probe$ ./probe --device /dev/ttyUSB0 --proto radio1 --length 24 --value 5393
2;1;24;5393;OK

On Raspbian, the first call works, but the second hangs my terminal and I have to do CTRL+C:

dmeziere@raspberrypi:~/probe$ ./probe --device /dev/ttyUSB0 --module 1 --probe 1
1;1;1;23.90;39.00
dmeziere@raspberrypi:~/probe$ ./probe --device /dev/ttyUSB0 --module 1 --probe 2
^C
1

There are 1 answers

0
Moonchild On BEST ANSWER

I found it !

It seems like on Raspbian, the communications were terminated by a NULL character. But not on Debian. And this NULL character was polluting my calculations of incomming buffer length. So i ended up by eventually suppress it on the Arduino side :

boolean readCommand()
{
  // Reads the current buffer
  Serial.readBytes(cmd, 9);

  // If sender sends a null character, remove it.
  int test = Serial.peek();
  if (test == 0x00) {
    test = Serial.read();
  }

  // ...
}