Program the control the fischertechnik robo interface or intelligent interface via tcp socket or via a java gui.

Dependencies:   mbed ConfigFile

tx-bridge.c

Committer:
networker
Date:
2010-12-31
Revision:
0:7f26f0680202

File content as of revision 0:7f26f0680202:

//$Id$
/*****************************************************************************************
following interrupts are used:
module    source            freq    prio    blocking    latency    duration
IR: timer1 capture        low        low        N            <200us    med            when IR active f ~ 1kHz
    timer1 overflow        low        low        Y            <10ms    short        f = 28Hz
ext    int0 (ack)            med        low        N            <50us    long        f ~ 2kHz
    spi                 med     low     Y            <5us    short        f ~ 2kHz
485    RX                    high    high    Y            <5us    medium        f = 92kHz burst
    TX                    high    high    Y            <10us    short        f = 92kHz burst
servo    timer0 comp        low        high    Y            <1us    very short    f = 200Hz @4 servos
timer    timer2 comp        med        low        N            <20us    long        f = 10kHz
distance    ADC            med        low        N            <20us    med            f = 9kHz
tx-bridge    eeprom        low        low        Y

the timer 2 interrupt takes care of timeouts (not critical) and event scheduling (servo and extension).
Because timer2 is interruptable these processes are effectively background.
The IR interrupts could be made non-blocking but a race between capture and overflow will result.
The share variable is 'overflow', possibly the capture int could be made non-blocking.
The servo is very sensitive to jitter probably due to latency caused by RX/TX (jitter is now 16us).
This can only be reduced by making RX/TX non-blocking (but this causes other problems) or a hardware 
solution with external latches reset by the timer output.

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

#if 0 //for reference only

/* Fuse settings:
 * SPIEN (default), allows ISP
 * EESAVE (optional), keeps eeprom settings
 * factory default CKDIV8 must be unprogrammed!
 * for SUK_CKSEL  I used ext X-tal >8MHz 16K CK
 * this resulted in: Ext F9, High D7, Low FF
 */

#define DEBUG        //the debug statements are actually defined to drive a pin
#define HW_VERSION    'A'    //can be 'A', 'B' or 'C'
#define HW_EXTENSION    'B'
#define SW_MAJOR    1L
#define SW_MINOR    6L
#define SW_EXTENSION    0x00010000L
#define TA_VERSION    0x08010101 /* copied from real TX data */
#define REVISION    "$Rev: 39 $"


//#include <stdint.h>
#include <string.h>
#include "data.h"
#include "rs485.h"      //uses UART (PORTD0-1), PORTD4 as transmit enable
#include "robo_ext.h"   //uses PORTD5-7 as a0-a2, PORTD2 as ack, SPI (PORTB3-5) 
#include "timer.h"        //uses timer2 as system timer 100us
#include "servo.h"        //uses timer0 as one shot timer, PORTC0-3 as servo outputs
#include "ir.h"            //uses timer1 as free running counter, ICP1 (PORTB0) as IR input
#include "distance.h"    //uses PORTC0-3 as analog inputs
#include "command.h"
#include "stepper.h"
//#include "i2c.h"        //uses TWI (PORTC4-5)

char timeP[] PROGMEM = __TIME__; 
char dateP[] PROGMEM = __DATE__; 
char revP[] PROGMEM = "$Rev: 39 $"; 

message msg = {{0x5502,{0},0,0,0,2,0,1,0},{{{{0,0,0,0},{0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0}},{0,3}}}};
struct _to_ext to_ext[MAXEXT] = {{0x0D,0xFF,0xFFFFFFFF}, //the 0x0D is mandatory, the 0xFF are actually zero's because data is inverted
                                 {0x0D,0xFF,0xFFFFFFFF},
                                 {0x0D,0xFF,0xFFFFFFFF},
                                 {0x0D,0xFF,0xFFFFFFFF},
                                 {0x0D,0xFF,0xFFFFFFFF},
                                 {0x0D,0xFF,0xFFFFFFFF},
                                 {0x0D,0xFF,0xFFFFFFFF}
                                };
struct _from_ext from_ext[MAXEXT];
uint8_t nrofext = 0;
struct _conf config[SLAVES];

//uint32_t slave_address[SLAVES];
//uint32_t ee_slave_address[SLAVES] __attribute__((section(".eeprom"))) = {3,4,5,6,7,8,9,10};

const TA_INFO  my_interface_info[SLAVES] PROGMEM = {
  {"ROBO I/O Ext 1","00:00:00:00:00:00",0,0,0,0,{{HW_EXTENSION},{SW_EXTENSION},{TA_VERSION},"\0\0\0"}},
  {"ROBO I/O Ext 2","00:00:00:00:00:00",0,0,0,0,{{HW_EXTENSION},{SW_EXTENSION},{TA_VERSION},"\0\0\0"}},
  {"ROBO I/O Ext 3","00:00:00:00:00:00",0,0,0,0,{{HW_EXTENSION},{SW_EXTENSION},{TA_VERSION},"\0\0\0"}},
  {"ROBO I/O Ext 4","00:00:00:00:00:00",0,0,0,0,{{HW_EXTENSION},{SW_EXTENSION},{TA_VERSION},"\0\0\0"}},
  {"ROBO I/O Ext 5","00:00:00:00:00:00",0,0,0,0,{{HW_EXTENSION},{SW_EXTENSION},{TA_VERSION},"\0\0\0"}},
  {"ROBO I/O Ext 6","00:00:00:00:00:00",0,0,0,0,{{HW_EXTENSION},{SW_EXTENSION},{TA_VERSION},"\0\0\0"}},
  {"ROBO I/O Ext 7","00:00:00:00:00:00",0,0,0,0,{{HW_EXTENSION},{SW_EXTENSION},{TA_VERSION},"\0\0\0"}},
  {"ROBO-TX bridge","00:00:00:00:00:00",0,0,0,0,{{HW_VERSION},{SW_MINOR<<24 | SW_MAJOR<<16},{TA_VERSION},{0x01,0x01,0x01,0x04}}}
};

char ee_names[SLAVES][17] __attribute__((section(".eeprom"))) = { "ROBO I/O Ext 1",
                                                                  "ROBO I/O Ext 2",
                                                                    "ROBO I/O Ext 3",
                                                                  "ROBO I/O Ext 4",
                                                                  "ROBO I/O Ext 5",
                                                                  "ROBO I/O Ext 6",
                                                                  "ROBO I/O Ext 7",
                                                                  "ROBO-TX bridge"
                                                                };
unsigned char ee_thisindex __attribute__((section(".eeprom"))) = 7;//7 corresponds to extension 8, any higher number will not be recognised (slave not present)
unsigned char thisindex = 7; //ram copy of ee_thisindex                                                             
volatile int8_t tx_message=-3;
UINT16 my_session_id[SLAVES]; //rolling session IDs for each slave
static char name[20]; //eeprom buffer
static char *eeptr = name;
static unsigned eesiz;
char eeprom_busy = 0;

UINT16 checksum(UINT16 n)
{ UINT16 sum = 0;
  n += 2; //include length in checksum
  char *p = (char*)&msg.hdr.bytes;
  do
  { sum -= *p++;
  } while (--n);
  return (sum<<8)|(sum>>8);
}

void copy_from_ext(short int idx)
{ short int i;
  if (idx == thisindex)
  { 
#ifdef USE_IR
    get_IR();
#endif
#ifdef USE_DISTANCE
    for (i = 4; i < 8; i++)
      if (config[idx].dist & (1<<i))
        msg.body.cmd102.input.uni[i] = get_distance(i-4);
#endif
#ifdef USE_STEPPER
//copy position reached info and counter values
    for (i = 0; i < 4; i++)
    { msg.body.cmd102.input.counter[i] = steppers[i].pos;
      msg.body.cmd102.input.motor_pos_reached[i] = steppers[i].pos == steppers[i].dest;
    }
#endif
  }
  else //it's an io extension
  { 
#ifdef USE_ROBO_EXT
    for (i = 0; i < 8; i++)
      msg.body.cmd102.input.uni[i] = from_ext[idx].inputs & (1<<i) ? 0 : 1;
    if (!(config[idx].uni & 1))
    { int v = 950 - (from_ext[idx].ax + ((int)(from_ext[idx].high&0x03)<<8));
      if (v<0)
        v = 0;
      else
        v = (v<<2) +  v + (v>>2);
      msg.body.cmd102.input.uni[0] = v;
    }
    if (!(config[idx].uni & 2))
      msg.body.cmd102.input.uni[1] = 1023 - (from_ext[idx].az + ((int)(from_ext[idx].high&0x0c)<<6));
    if (!(config[idx].uni & 4))
    { int v = 1023 - (from_ext[idx].av + ((int)(from_ext[idx].high&0x30)<<4));
      msg.body.cmd102.input.uni[2] = 3*v;
    }
#endif
  }
  //fill the other data with zeros
  for (i = 0; i < N_CNT; i++)
  { msg.body.cmd102.input.cnt_in[i] = 0;
    msg.body.cmd102.input.counter[i] = 0;
  }
  msg.body.cmd102.input.display_button_left = 0;
  msg.body.cmd102.input.display_button_right = 0;
  for (i = 0; i < N_MOTOR; i++)
  { msg.body.cmd102.input.motor_pos_reached[i] = 0;
  }
}

/* the length as set in the block is the length of the header minus the 4 byte leader
   and minus the TA-id (4 bytes) + the size of the payload data + the size of the TA-id
*/

void send_102(short int idx)
{ //msg.hdr.bytes = sizeof(header)+sizeof(TA_INPUT) - 4;
  msg.hdr.bytesH = (sizeof(header)+sizeof(TA_INPUT) - 4) >> 8;
  msg.hdr.bytesL = (sizeof(header)+sizeof(TA_INPUT) - 4) & 0xFF;
  msg.hdr.cmd = 102;
  msg.hdr.rec = msg.hdr.snd;
//  msg.hdr.snd = slave_address[idx];
  msg.hdr.snd = idx+3;
  msg.hdr.session = my_session_id[idx];
  copy_from_ext(idx); 
  msg.body.cmd102.trl.chksum = checksum(sizeof(header)+sizeof(TA_INPUT) - 4); 
  msg.body.cmd102.trl.etx = 3;
//  send_rs485(); 
  rs485_delay = RX_TO_TX_DELAY;
}

void send_105(short int idx)
{ //msg.hdr.bytes = sizeof(header) - 4;
  msg.hdr.bytesH = (sizeof(header) - 4) >> 8;
  msg.hdr.bytesL = (sizeof(header) - 4) & 0xFF;
  msg.hdr.cmd = 105;
  msg.hdr.rec = msg.hdr.snd;
//  msg.hdr.snd = slave_address[idx];
  msg.hdr.snd = idx+3;
  msg.body.cmd105.trl.etx = 3;
//  send_rs485(); 
  if (msg.hdr.session == 0) //first config req
  { msg.hdr.session = ++my_session_id[idx];//reply with the next SID
    msg.body.cmd105.trl.chksum = checksum(sizeof(header) - 4); 
    rs485_delay = 110;
  }
  else //second config req
  { msg.hdr.session = my_session_id[idx];//reply with the same SID
    msg.body.cmd105.trl.chksum = checksum(sizeof(header) - 4); 
    rs485_delay = 55;
  }
}

void send_106(short int idx)
{ //msg.hdr.bytes = sizeof(header)+sizeof(TA_INFO) - 4;
  msg.hdr.bytesH = (sizeof(header)+sizeof(TA_INFO) - 4) >> 8;
  msg.hdr.bytesL = (sizeof(header)+sizeof(TA_INFO) - 4) & 0xFF;
  msg.hdr.cmd = 106;
  msg.hdr.rec = msg.hdr.snd;
//  msg.hdr.snd = slave_address[idx];
  msg.hdr.snd = idx+3;
  my_session_id[idx]++; //bump the session-id 
  my_session_id[idx] |= 1; //and make sure it is odd
  msg.hdr.session = my_session_id[idx]; 
  if (idx==thisindex)//if the bridge is addressed
    idx = 7; //always use the name of the bridge
  memcpy_P(&msg.body.cmd106.info, (PGM_P)&my_interface_info[idx], sizeof(TA_INFO)); 
  if (!eeprom_busy)
    eeprom_read_block(&msg.body.cmd106.info, &ee_names[idx][0], DEV_NAME_LEN+1); //overwrite the name
  //else tough luck, use the original name 
  msg.body.cmd106.trl.chksum = checksum(sizeof(header)+sizeof(TA_INFO) - 4); 
  msg.body.cmd106.trl.etx = 3;
//  send_rs485(); 
  rs485_delay = RX_TO_TX_DELAY;
}

void send_empty_reply(short int idx)
{ msg.hdr.bytesH = (sizeof(header) - 4) >> 8;
  msg.hdr.bytesL = (sizeof(header) - 4) & 0xFF;
  msg.hdr.cmd += 100;
  msg.hdr.rec = msg.hdr.snd;
  msg.hdr.snd = idx+3;
  msg.hdr.session = my_session_id[idx];
  msg.body.empty.trl.chksum = checksum(sizeof(header) - 4); 
  msg.body.empty.trl.etx = 3;
//  send_rs485(); 
  rs485_delay = RX_TO_TX_DELAY;
}

void store_config(short int idx)
{ short int i;
  config[idx].motor = 0;
  config[idx].uni = 0;
  config[idx].dist = 0;
  for (i = 0; i < 4; i++)
    if (msg.body.cmd005.config.motor[i])
      config[idx].motor |= 1<<i;
  for (i = 0; i < 8; i++)
  { if (msg.body.cmd005.config.uni[i].digital)
      config[idx].uni |= 1<<i;
    if (msg.body.cmd005.config.uni[i].mode == 3)
      config[idx].dist |= 1<<i;
  }
#ifdef USE_SERVO
  if (idx == thisindex)
    enable_servos(~config[idx].dist>>4);
#endif
}

/*
short int find_ad(uint32_t ad)
{ short int i;
  for (i = 0; i < SLAVES; i++)
    if (slave_address[i] == ad)
      return i;
  return -1;
}
*/

#if 0
void copy_to_ext(short int idx)
{ //copy motor data from tx msg to io ext
  short int i;
  uint32_t pwm;
  uint32_t ext_pwm = 0;
  char m = 0;
  if (idx == 7)
  { set_servos();
    return;
  }
  for (i = 0; i < 4; i++)
  { if (config[idx].motor & (1<<i))
    { //treat as motor, this must be fixed!!!
      //in fact it does not need a fix because it works fine
      pwm = msg.body.cmd002.output.duty[i<<1];
      pwm /= 57; //reduce from 9 bits to 3 bits
      if (pwm)
      { m |= 1<<(2*i);
        pwm--;
      }
      ext_pwm |= pwm<<(6*i);

      pwm = msg.body.cmd002.output.duty[(i<<1)+1];
      pwm /= 57; //reduce from 9 bits to 3 bits
      if (pwm)
      { m |= 1<<(2*i+1);
        pwm--;
      }
      ext_pwm |= pwm<<(6*i+3);
    }
    else
    { //treat as 2 outputs
      pwm = msg.body.cmd002.output.duty[i<<1];
      pwm /= 57; //reduce from 9 bits to 3 bits
      if (pwm)
      { m |= 1<<(2*i);
        pwm--;
      }
      ext_pwm |= pwm<<(6*i);

      pwm = msg.body.cmd002.output.duty[(i<<1)+1];
      pwm /= 57; //reduce from 9 bits to 3 bits
      if (pwm)
      { m |= 1<<(2*i+1);
        pwm--;
      }
      ext_pwm |= pwm<<(6*i+3);
    }
  }
  to_ext[idx].pwm = ~ext_pwm & 0x00FFFFFF;
  to_ext[idx].motors = ~m;
}
#else
void copy_to_ext(short int idx)
{ //copy motor data from tx msg to io ext
  short int i;
  uint32_t pwm;
  uint32_t ext_pwm = 0;
  char m = 0;
  if (idx == thisindex)
  { 
#ifdef USE_SERVO
    set_servos();
#endif
#ifdef USE_STEPPER
//copy position info and speed info
    for (i = 0; i < 4; i++)
    { steppers[i].dest = msg.body.cmd002.output.distance[i];
      steppers[i].speed = msg.body.cmd002.output.duty[2*i] - msg.body.cmd002.output.duty[2*i+1];
      if (msg.body.cmd002.output.cnt_reset[i])
        steppers[i].pos = 0;
    }
#endif
    return;
  }
#ifdef USE_ROBO_EXT
  for (i = 0; i < 8; i++)
  {   pwm = msg.body.cmd002.output.duty[i];
      pwm /= 57; //reduce from 9 bits to 3 bits, 0-511 is mapped to 0-8, 0 means off and 1-8 are pwm values 0-7
      if (pwm)
      { m |= 1<<i;
        pwm--;
      }
      ext_pwm |= pwm<<(3*i);
  }
  to_ext[idx].pwm = ~ext_pwm & 0x00FFFFFF;
  to_ext[idx].motors = ~m;
#endif
}
#endif

#ifndef COMMAND_H
ISR(EE_READY_vect)//don't make this non_blocking, the eeprom interrupt flag is still pending!
{ EEDR = *eeptr;
  EEAR++;//address must be set BEFORE starting write, therefore we start at address-1
  EECR = _BV(EEMPE) | _BV(EERIE);
  EECR |= _BV(EEPE);
  //if timing is really critical, interrupts could be re-enabled here
  if (*eeptr)//this actually wrong because eeprom_busy is cleared while the last write is still in progress
  { eeptr++;
  }
  else
  { EECR = 0; //disable interrupt
    eeprom_busy = 0;
  }
}
#else
ISR(EE_READY_vect)//don't make this non_blocking, the eeprom interrupt flag is still pending!
{ if (eesiz)
  { EEDR = *eeptr;
    EEAR++;//address must be set BEFORE starting write, therefore we start at address-1
    EECR = _BV(EEMPE) | _BV(EERIE);
    EECR |= _BV(EEPE);
  //if timing is really critical, interrupts could be re-enabled here
    eesiz--;
    eeptr++;
  }
  else
  { EECR = 0; //disable interrupt
    eeprom_busy = 0;
  }
}
#endif

#ifndef COMMAND_H
void eeprom_write_string_noblock(char s[], char *eadr)
{ eeprom_busy = 1;
  eeptr = s;
  EEAR = (unsigned)eadr-1;
  EECR |= _BV(EERIE);
}
#else
void eeprom_write_string_noblock(char s[], char *eadr)
{ eeprom_write_block_nonblocking(s, eadr, strlen(s)+1);
}
#endif

void eeprom_write_block_nonblocking(char *src, char *dst, int size)
{ if (eeprom_busy || size == 0)
    return;
  eeprom_busy = 1;
  eeptr = src;
  eesiz = size;
  EEAR = (unsigned)dst - 1;
  EECR |= _BV(EERIE);
}

void store_index()
{ if (eeprom_busy)
    return;
  eeprom_write_byte(&ee_thisindex, thisindex);
}

void init()
{ DDRB = _BV(PB1) | _BV(PB2); //debug on pins 15 and 16, for historical reasons the macros to set/clear these pins refer to pins 11 and 12
  PORTD = _BV(PD0); //enable the internal pullup on the RX input, pullup not possible on int0 => external pullup
  //recommended by atmel to activate pullup for unused pins
  //unused pins are: pc4-6 (sda,scl,reset), pd3 (int1)
  //pb1-2 (debug) are not really unused because they are outputs
  PORTC = _BV(PC4) | _BV(PC5) | _BV(PC6);
  PORTD |= _BV(PD3);
}

int main()
{ init();
  init_rs485();
#ifdef USE_ROBO_EXT
  init_extension();
#endif
#ifdef USE_IR
  init_IR();
#endif
#ifdef USE_SERVO
  init_servos();
#endif
  init_timer();
#ifdef USE_DISTANCE
  init_distance();
#endif
#ifdef USE_STEPPER
  init_stepper();
#endif
//  eeprom_read_block(slave_address, ee_slave_address, sizeof(slave_address));
  thisindex = eeprom_read_byte(&ee_thisindex);
  sei(); //enable interrupts
  for(;;)
  { if (tx_message>=0)//is there a message?, if there is then rx and tx are disabled
    { int idx = tx_message;//find_ad(msg.hdr.rec);
      if ((/* idx >= 0 && */ idx < nrofext) || idx==thisindex) //is the message for us?
      { switch (msg.hdr.cmd)
        { case 2: if (msg.body.cmd002.trl.chksum != checksum(msg.hdr.bytes))
                  { enable_rx_rs485();
                    break;
                  }
                  copy_to_ext(idx);
                  send_102(idx); //data transfer
                  break;
          case 5: if (msg.body.cmd005.trl.chksum != checksum(msg.hdr.bytes))
                  { enable_rx_rs485();
                    break;
                  }
                  store_config(idx);
                  send_105(idx); //configuration
                  break;
          case 6: if (msg.body.cmd006.trl.chksum != checksum(msg.hdr.bytes))
                  { enable_rx_rs485();
                    break;
                  }
                  send_106(idx); //discovery
                  break;
          case 8: if (msg.body.cmd008.trl.chksum != checksum(msg.hdr.bytes))
                  { enable_rx_rs485();
                    break;
                  }
#ifdef COMMAND_H
                  handle_command(idx);
#else
                  //completely ignore this message
#endif
                  send_empty_reply(idx); //display message
                  break;
          case 9: if (msg.body.cmd009.trl.chksum != checksum(msg.hdr.bytes))
                  { enable_rx_rs485();
                    break;
                  }
#if 0
                  //the next function is too slow and should be replaced by a non-blocking function
                  eeprom_write_block(msg.body.cmd009.name, &ee_names[idx][0], DEV_NAME_LEN+1);
#else
                  strcpy(name, msg.body.cmd009.name); 
                  eeprom_write_string_noblock(name, &ee_names[idx][0]);
#endif
                  send_empty_reply(idx); //name change
                  break;
          default: //SETPIN12; //trigger the logic analyser so the command can be investigated
                   enable_rx_rs485();
                   break;
        }//switch
        //if we arrive here, one of the following is true: 1)the checksum was wrong (we are in receive mode); 
        //                                                 2)a reply was initiated (we are in send mode);
        //                                                 3)an unknown cmd was received (we are in receive mode).
      }//if idx
      else
      { 
        enable_rx_rs485(); //go back to receive mode when msg not for us
      } 
        tx_message = -3; //clear message
    }//if message
  } //for
}//main

#endif