SPCoast
Railroading on the Southern Pacific Coast

Arduino Loconet Example1

From SPCoast

Jump to: navigation, search

[edit] Arduino demo sketch showing LocoNet Receive functionality

This sketch is hardcoded to use ICP pin 8 (port PINB bit PB0) for input from LocoNet via a "usual" LM311 or LM393 comparator circuit:


Loconet Interface Schematic
Loconet Interface Schematic
Loconet Interface
Loconet Interface
Arduino + Loconet
Arduino + Loconet
Packet as seen by the Arduino
Packet as seen by the Arduino
<code>
// Copyright 2008, John Plocher and Stefan Bormann, GPL v2.1 or later
 
#include <SoftwareSerial.h>
#include <avr/wdt.h>
#include <avr/io.h>
#include <stdint.h>

// #define LNtxPin         6        // Loconet Transmit is not implemented yet

void setup() {
    LnRxOnlyInit();
    pinMode(13,       OUTPUT);   // Flash LED to show progress
    digitalWrite(13, 0);

    Serial.begin(9600);         // set up Serial library at 9600 bps
    Serial.println("Loconet read test");
}

void loop() {  
   unsigned char opcode = LnRxOnlyOpcode();
   unsigned char opcodefamily = (opcode >> 5);
   switch (opcodefamily) {
     case B100:  // 2 data bytes, inc checksum
                       get_packet(opcode, 2); break;
     case B101:  // 4 data bytes, inc checksum
                       get_packet(opcode, 4); break;
     case B110:  // 6 data bytes, inc checksum
                       get_packet(opcode, 6); break;
     case B111:  // N data bytes, inc checksum, next octet is N
                       int N = LnRxOnlyData();
                       get_packet(opcode, N-1);
                       break; 
   }
}

// Read a N-sized packet
// 1st byte already read, last is checksum
void get_packet(unsigned char opcode, int n) {
    unsigned char d[20];
    d[0] = (unsigned char) n;
    d[1] = opcode;
  
    for (unsigned char x = 2; x <= n; x++) {
        d[x] = LnRxOnlyData();
    }
    Serial.print("Packet[");   Serial.print((int)d[0]);   Serial.print("]: ");
    for (unsigned char x = 1; x <= d[0]; x++) {
        Serial.print(" 0x");
        Serial.print((int)d[x], HEX);
    }
    Serial.println("");
}

// ====  Originally from Embedded Loconet: LnRxOnlySw.[hc]             ====
// ====  17-Mar-2006 version by Stefan Bormann                         ====
// ====  Modified 02-Dec-2008 by John Plocher for Arduino use          ====

/****************************************************************************
    Copyright (C) 2006 Stefan Bormann

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*****************************************************************************

    IMPORTANT:

    Note: The sale any LocoNet device hardware (including bare PCB's) that
    uses this or any other LocoNet software, requires testing and certification
    by Digitrax Inc. and will be subject to a licensing agreement.

    Please contact Digitrax Inc. for details.

*****************************************************************************/


#define LN_RX_PORT      PINB
#define LN_RX_BIT       PB0

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

// define the timer interrupt flag register if necessary
#if !defined(TIFR)
  #if defined(TIFR1)
    #define TIFR TIFR1
  #else
    #error AVR device not supported
  #endif
#endif

#define LN_BIT_PERIOD       (F_CPU / 16666)
          // The Start Bit period is a full bit period + half of the next bit period
          // so that the bit is sampled in middle of the bit
#define LN_TIMER_RX_START_PERIOD    LN_BIT_PERIOD + (LN_BIT_PERIOD / 2)
#define LN_TIMER_RX_RELOAD_PERIOD   LN_BIT_PERIOD 

unsigned short lnCompareTarget;        // calculated bit sample time
unsigned char ucData;                  // one received message byte
unsigned char ucLnRxOnlyChecksum;      // non static for access from inline function

static void inline LnRxOnlyInit(void)
{
        pinMode(8,  INPUT);    // LocoNet on ICP, PB0
        TCCR1A = 0;
	TCCR1B = 0x01;     // no prescaler, normal mode
}

void LnRxOnlySevereError(void)
{
        Serial.println("Loconet ERROR");
        digitalWrite(13, 1); 
	wdt_enable(WDTO_1S);  // prepare for reset
	while (1) {}            // stop
}

unsigned char LnRxOnlyOpcode(void)
{
	do {
		ucLnRxOnlyChecksum = 0;
                loop_until_bit_is_set  (LN_RX_PORT, LN_RX_BIT);    // wait for IDLE=1 state
		LnRxOnlyData();
	} while (bit_is_clear(ucData, 7));  // loop if not opcode or byte framing error
	return ucData;
}

unsigned char LnRxOnlyData(void)
{
	unsigned char ucLoop;

//--- wait for start bit
        digitalWrite(13, 0);
	loop_until_bit_is_clear(LN_RX_PORT, LN_RX_BIT);
        digitalWrite(13, 1);

        //--- Get the Current Timer1 Count and Add the offset for the Compare target to find the middle of the first data bit
	lnCompareTarget = TCNT1 + LN_TIMER_RX_START_PERIOD;
	OCR1A = lnCompareTarget ;

        //--- Loop over data bits
	for (ucLoop=0; ucLoop<8; ucLoop++) {
                //--- Waiting for data bit
	        ucData >>= 1;   // Loconet is LSB first, shifting data in from left
                digitalWrite(13, 0);
		sbi(TIFR, OCF1A);
		loop_until_bit_is_set(TIFR, OCF1A);  // wait for middle of data bit
               digitalWrite(13, 1);

                //--- Copy data bit from input to MSB of data buffer
		if (bit_is_set(LN_RX_PORT, LN_RX_BIT)) {
			sbi(ucData, 7);
		}

                //--- Calculate timer target for next data (or stop) bit
		lnCompareTarget += LN_TIMER_RX_RELOAD_PERIOD;
		OCR1A = lnCompareTarget;
	}
       //--- Check stop bit
        digitalWrite(13, 0);
	sbi(TIFR, OCF1A);
	loop_until_bit_is_set(TIFR, OCF1A);  // wait for middle of stop bit
        digitalWrite(13, 1);
	if (bit_is_clear(LN_RX_PORT, LN_RX_BIT)) {
		LnRxOnlySevereError();
	}
	ucLnRxOnlyChecksum ^= ucData;
        digitalWrite(13, 0);
	return ucData;
}


void LnRxOnlyChecksum(void)
{
	LnRxOnlyData();

	if (ucLnRxOnlyChecksum!=0xff)
		LnRxOnlySevereError();
}
</code>