rostrich's Blog

2010/05/16

NXT and Arduino talking RS-485

Filed under: Uncategorized — Tags: , , — rostrich @ 10:09 am


An Arduino sketch, a NXC sample and hardware setup
for RS-485 communications between a
LEGO Mindstorms NXT and an Arduino via a MAX 485

Hardware:

— NXT
— Arduino Nano V3
— MAX 485 CPA
— Breadboard
— Wires

Programming Environment:

— NXT enhanced firmware 1.28
— NXC nbc-1.2.1.r1
— Arduino 0018
— Mac OS X 10.6.3

Notes:

— I used an Arduino Nano V3, any stock Arduino should do it also
— The wiring is simplistic, not optimized for safety/fault tolerance, since I am no electronic expert
— Inspired by Early stage RS 485 with MAX485 in the Arduino forums and RS485 to RS232 Adapter in the nxtasy.org forums
— The serial functions of the Arduino code are stripped down/hacked up
from the old SoftwareSerial library
— The wire from the NXT-connector ground to Arduino ground (to build a common one) is not necessary.
It can be safely left off (per definitionem of RS-485 I think).
— I tested this for baudrates between 9600 and 57600; higher rates don’t work!

Schematics:

Breadboard wiring:

baudrate.h:

#define RS485_BAUD 57600

NCX Program: (Screenshot)

#include "baudrate.h"
#define TOWAIT 2000
#define MSG "Hello Arduino  "

// -------------------------------------------------------------
void printMsg(byte inbuffer[], int cnt) {
  TextOut(0, LCD_LINE3, "       Baud");
  NumOut(0, LCD_LINE3, RS485_BAUD);
  TextOut(0, LCD_LINE5, "l=    cnt=     ");
  NumOut(12, LCD_LINE5, ArrayLen(inbuffer));
  NumOut(60, LCD_LINE5, cnt);
  //                        "Hi NXT,got  "
  TextOut(0, LCD_LINE6, "rcv:             ");
  TextOut(24, LCD_LINE6, ByteArrayToStr(inbuffer));
}

// -------------------------------------------------------------
task main() {
  int i = 0;
  int cnt = 0;
  byte outbuffer[];
  byte inbuffer[];
  char result;

  SetSensorType(IN_4, SENSOR_TYPE_HIGHSPEED);
  Wait(100);

  // we use odd parity on the Arduino side
  // see: http://forums.nxtasy.org/index.php?showtopic=3871
  RS485Uart(HS_BAUD_##RS485_BAUD, HS_MODE_8_DATA|HS_MODE_O_PARITY|HS_MODE_10_STOP);
  Wait(100);
  StrToByteArray(MSG, outbuffer);
  TextOut(0, LCD_LINE1, "RS485 <> Arduino");

  while (true) {
    outbuffer[14] = '0' + i;
    i++; if (i==10) i = 0;

    result = RS485Write(outbuffer);
    while (!RS485DataAvailable()) {}
    result = RS485Read(inbuffer);

    printMsg(inbuffer, cnt);                                  
    cnt++; if (cnt==1000) cnt = 0;                            
    Wait(TOWAIT);                                             
  }                                                           
}                                                             

Arduino sketch: (Screenshot)

#include "baudrate.h"

// Pins and wires
//         Arduino                Wire       MAX485            
const byte RS485_OUT_PIN = 10; // yellow --> DI
const byte RS485_IN_PIN  = 11; // green  --> RO
const byte RS485_CTL_PIN = 12; // grey   --> RE+DE
//         VCC                           --> VCC
//         ------------------------------------------
//         NXT Sensor Port 4      Wire       MAX485
//         DIGIAI1                blue   --> B
//         DIGIAI0                yellow --> A
//         GND                    black  --> GND

const byte RECV_RS485_BIT =  B00001000;
const byte SEND_RS485_BIT =  B00000100;
#define readRS485inPin (PINB & RECV_RS485_BIT)

const unsigned int BUFLEN = 128;
const unsigned int outnbuf = 12;
byte outBuf[BUFLEN];
byte inBuf [BUFLEN];
unsigned int innbuf;
const char msg[] = "Hi NXT,got  ";

const byte SER_TIMEOUT = 7;
unsigned int RS485_LEN;
unsigned int bitDelay;
unsigned int halfBitDelay;

const byte CYCLES = 45;
// ------------------------------------------------------------------
void serSetup(unsigned long baud) {
  pinMode(RS485_OUT_PIN, OUTPUT);
  digitalWrite(RS485_OUT_PIN, LOW);
  pinMode(RS485_IN_PIN, INPUT);
  digitalWrite(RS485_IN_PIN, LOW);
  pinMode(RS485_CTL_PIN, OUTPUT);
  digitalWrite(RS485_CTL_PIN, LOW);

  RS485_LEN = 1000000 / baud;
  bitDelay = RS485_LEN - clockCyclesToMicroseconds(CYCLES);
  halfBitDelay = RS485_LEN/2 - clockCyclesToMicroseconds(CYCLES);
  Serial.print("\nRS-485 configured, ");
  Serial.print(baud, DEC);
  Serial.print(" Baud, Bit length ");
  Serial.print(RS485_LEN, DEC);
  Serial.println(" uSec");
}

// Startbit of 1st byte of incoming message encountered
// ------------------------------------------------------------------
byte serAvail() {
  return readRS485inPin;
}

// Read a byte
// ------------------------------------------------------------------
unsigned int serRead() {
  byte i;
  int val = 0;
  byte parity = 0, stopbit = 0;

  long start = millis();
  while (true) {
    if (millis()-start>SER_TIMEOUT) return -1;
    if (readRS485inPin) break;     // startbit hit
  }

  delayMicroseconds(halfBitDelay); // jump to middle of startbit
  for (i=0; i<8; i++) {
    delayMicroseconds(bitDelay);
    val |= !readRS485inPin << i;   // data bits
  }
  delayMicroseconds(bitDelay);
  parity = !readRS485inPin;        //
  delayMicroseconds(bitDelay);
  stopbit = !readRS485inPin;       //
  return val;                                               
}                                                             

// Write one bit
// -----------------------------------------------------------------
void serBit(byte mark) {
  if (mark) PORTB &= ~ SEND_RS485_BIT;
  else      PORTB |=   SEND_RS485_BIT;
//  delayMicroseconds(RS485_LEN);
  delayMicroseconds(bitDelay);
}

// Write a byte
// ------------------------------------------------------------------
void serWrite(byte data) {
  byte mask = 1;
  byte bitcount = 0;

  serBit(LOW);               // startbit
  for (byte i=0; i<8; i++) { // data from LSB to MSB
    if (data & mask) {
      serBit(HIGH);
      bitcount++;
    } else {
      serBit(LOW);
    }
    mask <<= 1;
  }
  serBit((bitcount%2)==0);   // odd parity bit
  serBit(HIGH);              // stop bit
}

// ------------------------------------------------------------------
void printMsg(const char *s, byte buf[], int nbuf) {
  Serial.print(s);
  Serial.print(": \"");
  for (int i=0; i<nbuf; i++)  
    Serial.print(buf[i], BYTE);
  Serial.println("\"");
}

// ------------------------------------------------------------------
void serSendMsg(byte buf[], int nbuf) {
  int i;

  digitalWrite(RS485_CTL_PIN, HIGH);
  for (i=0; i<nbuf; i++)
    serWrite(buf[i]);
  digitalWrite(RS485_CTL_PIN, LOW);
}

// ------------------------------------------------------------------
int serRecvMsg(byte buf[]) {
  int c;
  int nbuf = 0;

  while ((c = serRead())!=-1)
    buf[nbuf++] = c;
  return nbuf;
}

// ------------------------------------------------------------------
void measureComms(byte stage) {
  static unsigned long old = 0; 
  unsigned long now; 

  if (!stage) {
    old = millis();
  } else {
    now = millis();
    Serial.print("comms took ");
    Serial.print(now-old, DEC);
    Serial.println(" mSec");
  } 
}

// ------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  for (int i=0; i<outnbuf; i++) 
    outBuf[i] = msg[i];
  outBuf[12] = 0;
  serSetup(RS485_BAUD);
}

// ------------------------------------------------------------------
void loop() {
//  measureComms(0);
  if (serAvail()) {
    innbuf = serRecvMsg(inBuf);
    outBuf[11] = inBuf[innbuf-1];
    serSendMsg(outBuf, outnbuf);

//    measureComms(1);
    printMsg("Recv", inBuf, innbuf);
    printMsg("Sent", outBuf, outnbuf);
  }
}

Future ideas:

RCX-bridge with MAX485 only (no Arduino), IR-LED and IR-receiver

Blog at WordPress.com.