Testing getting PJON working on mbed https://github.com/gioblu/PJON

Dependencies:   mbed

pjon.cpp

Committer:
earlz
Date:
2016-01-10
Revision:
1:bd0ee507dd4c
Parent:
0:fa2f348efd7e
Child:
2:5648483c5dbc

File content as of revision 1:bd0ee507dd4c:


 /*-O//\         __     __
   |-gfo\       |__| | |  | |\ |
   |!y°o:\      |  __| |__| | \| v1.0
   |y"s§+`\     Giovanni Blu Mitolo 2012 - 2015
  /so+:-..`\    gioscarab@gmail.com
  |+/:ngr-*.`\
  |5/:%&-a3f.:;\     PJON is a device communications bus system that connects up to 255
  \+//u/+g%{osv,,\    arduino boards over one wire up to 5.29kB/s data communication speed.
    \=+&/osw+olds.\\   Contains acknowledge, collision detection, CRC and encpryption all done
       \:/+-.-°-:+oss\  with micros() and delayMicroseconds(), with no use of interrupts or timers.
        | |       \oy\\  Pull down resistor on the bus is generally used to reduce interference.
        > <
       -| |-

Copyright (c) 2012-2015, Giovanni Blu Mitolo All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

-  Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

-  All advertising materials mentioning features or use of this software
   must display the following acknowledgement:
   This product includes PJON software developed by Giovanni Blu Mitolo.

-  Neither the name of PJON, PJON_ASK nor the
   names of its contributors may be used to endorse or promote products
   derived from this software without specific prior written permission.

This software is provided by the copyright holders and contributors"as is"
and any express or implied warranties, including, but not limited to, the
implied warranties of merchantability and fitness for a particular purpose
are disclaimed. In no event shall the copyright holder or contributors be
liable for any direct, indirect, incidental, special, exemplary, or consequential
damages (including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on any
theory of liability, whether in contract, strict liability, or tort (including
negligence or otherwise) arising in any way out of the use of this software, even if
advised of the possibility of such damage. */

#include "pjon.h"

/* Initiate PJON passing pin number:
   Device's id has to be set through set_id()
   before transmitting on the PJON network.  */

PJON::PJON(PinName input_pin) : _input_pin(input_pin){
  //_input_pin =  DigitalInOut(input_pin);
  this->initialize();
}


/* Initiate PJON passing pin number and the device's id: */

PJON::PJON(PinName input_pin, uint8_t device_id) : _input_pin(input_pin) {
  _device_id = device_id;
  this->initialize();
}


/* Initialization tasks: */

void PJON::initialize() {
  this->set_error(dummy_error_handler);
  _reg_timer.start();
  for(int i = 0; i < MAX_PACKETS; i++) {
    packets[i].state = NULL;
    packets[i].timing = 0;
    packets[i].attempts = 0;
  }
}


/* Set the device id, passing a single byte (watch out to id collision) */

void PJON::set_id(uint8_t id) {
  _device_id = id;
}


/* Pass as a parameter a void function you previously defined in your code.
   This will be called when a correct message will be received.
   Inside there you can code how to react when data is received.

   void receiver_function(uint8_t length, uint8_t *payload) {
    for(int i = 0; i < length; i++)
      Serial.print((char)payload[i]);

    Serial.print(" ");
    Serial.println(length);
  };

  network.set_receiver(receiver_function); */

void PJON::set_receiver(receiver r) {
  _receiver = r;
}


/* Pass as a parameter a void function you previously defined in your code.
   This will be called when an error in communication occurs

void error_handler(uint8_t code, uint8_t data) {
  Serial.print(code);
  Serial.print(" ");
  Serial.println(data);
};

network.set_error(error_handler); */

void PJON::set_error(pjon_error e) {
  _error = e;
}


/* Check if the channel is free for transmission:
 If an entire byte received contains no 1s it means
 that there is no active transmission */

bool PJON::can_start() {
  _input_pin.input(); //pinMode(_input_pin, INPUT);
  this->send_bit(0, 2);
  if(!this->read_byte())
    return true;

  return false;
}


/* Send a bit to the pin:
 digitalWriteFast is used instead of standard digitalWrite
 function to optimize transmission time */

void PJON::send_bit(uint8_t VALUE, int duration) {
  _input_pin = VALUE > 0; //digitalWrite(_input_pin, VALUE);
  wait_us(duration);
}


/* Every byte is prepended with 2 synchronization padding bits. The first
   is a longer than standard logic 1 followed by a standard logic 0.
   __________ ___________________________
  | SyncPad  | Byte                      |
  |______    |___       ___     _____    |
  | |    |   |   |     |   |   |     |   |
  | | 1  | 0 | 1 | 0 0 | 1 | 0 | 1 1 | 0 |
  |_|____|___|___|_____|___|___|_____|___|
    |
   ACCEPTANCE

The reception tecnique is based on finding a logic 1 as long as the
first padding bit within a certain threshold, synchronizing to its
falling edge and checking if it is followed by a logic 0. If this
pattern is recognised, reception starts, if not, interference,
synchronization loss or simply absence of communication is
detected at byte level. */

void PJON::send_byte(uint8_t b) {
  _input_pin = 1; //digitalWriteFast(_input_pin, HIGH);
  wait_us(BIT_SPACER);
  _input_pin = 0; //digitalWriteFast(_input_pin, LOW);
  wait_us(BIT_WIDTH);

  for(uint8_t mask = 0x01; mask; mask <<= 1) {
    _input_pin = (b & mask) > 0; //digitalWriteFast(_input_pin, b & mask);
    wait_us(BIT_WIDTH);
  }
}


/* An Example of how the string "@" is formatted and sent:

 ID 12            LENGTH 4         CONTENT 64       CRC 130
 ________________ ________________ ________________ __________________
|Sync | Byte     |Sync | Byte     |Sync | Byte     |Sync | Byte       |
|___  |     __   |___  |      _   |___  |  _       |___  |  _      _  |
|   | |    |  |  |   | |     | |  |   | | | |      |   | | | |    | | |
| 1 |0|0000|11|00| 1 |0|00000|1|00| 1 |0|0|1|000000| 1 |0|0|1|0000|1|0|
|___|_|____|__|__|___|_|_____|_|__|___|_|_|_|______|___|_|_|_|____|_|_|

A standard packet transmission is a bidirectional communication between
two devices that can be divided in 3 different phases:

Channel analysis   Transmission                            Response
    _____           _____________________________           _____
   | C-A |         | ID | LENGTH | CONTENT | CRC |         | ACK |
<--|-----|---------|----|--------|---------|-----|--> <----|-----|
   |  0  |         | 12 |   4    |   64    | 130 |         |  6  |
   |_____|         |____|________|_________|_____|         |_____|  */

int PJON::send_string(uint8_t id, char *string, uint8_t length) {
  if (!*string) return FAIL;

  if(!this->can_start()) return BUSY;

  uint8_t CRC = 0;
  _input_pin.output(); //pinModeFast(_input_pin, OUTPUT);

  this->send_byte(id);
  CRC ^= id;
  this->send_byte(length + 3);
  CRC ^= length + 3;

  for(uint8_t i = 0; i < length; i++) {
    this->send_byte(string[i]);
    CRC ^= string[i];
  }

  this->send_byte(CRC);
  _input_pin = 0; //digitalWriteFast(_input_pin, LOW);

  if(id == BROADCAST) return ACK;
  Timer t;
  t.start();
  //unsigned long time = micros();
  int response = FAIL;

  /* Receive byte for an initial BIT_SPACER bit + standard bit total duration.
     (freak condition used to avoid micros() overflow bug) */
  while(response == FAIL && !(t.read_us() >= BIT_SPACER + BIT_WIDTH))
    response = this->receive_byte();

  t.stop();
  if (response == ACK || response == NAK) return response;

  return FAIL;
};


/* Insert a packet in the send list:
 The added packet will be sent in the next update() call.
 Using the timing parameter you can set the delay between every
 transmission cyclically sending the packet (use remove() function stop it)

 int hi = network.send(99, "HI!", 3, 1000000); // Send hi every second
   _________________________________________________________________________
  |           |        |         |       |          |        |              |
  | device_id | length | content | state | attempts | timing | registration |
  |___________|________|_________|_______|__________|________|______________| */

int PJON::send(uint8_t id, char *packet, uint8_t length, unsigned long timing) {

  char *str = (char *) malloc(length);

  if(str == NULL) {
    this->_error(MEMORY_FULL, FAIL);
    return FAIL;
  }

  memcpy(str, packet, length);

  for(uint8_t i = 0; i < MAX_PACKETS; i++)
    if(packets[i].state == NULL) {
      packets[i].content = str;
      packets[i].device_id = id;
      packets[i].length = length;
      packets[i].state = TO_BE_SENT;
      if(timing > 0) {
        packets[i].registration = _reg_timer.read_us();
        packets[i].timing = timing;
      }
      return i;
    }

  this->_error(PACKETS_BUFFER_FULL, MAX_PACKETS);
  return FAIL;
}


/* Update the state of the send list:
   check if there are packets to be sent or to be erased
   if correctly delivered */

void PJON::update() {
  for(uint8_t i = 0; i < MAX_PACKETS; i++) {
    if(packets[i].state != NULL)
      if(_reg_timer.read_us() - packets[i].registration > packets[i].timing + (packets[i].attempts * packets[i].attempts)) //pow((float)packets[i].attempts, 2)) //avoid float operation
        packets[i].state = send_string(packets[i].device_id, packets[i].content, packets[i].length);

    if(packets[i].state == ACK) {
      if(!packets[i].timing)
        this->remove(i);
      else {
        packets[i].attempts = 0;
        packets[i].registration = _reg_timer.read_us();
        packets[i].state = TO_BE_SENT;
      }
    }
    if(packets[i].state == FAIL) {
      packets[i].attempts++;

      if(packets[i].attempts > MAX_ATTEMPTS) {
        this->_error(CONNECTION_LOST, packets[i].device_id);
        if(!packets[i].timing)
          this->remove(i);
        else {
          packets[i].attempts = 0;
          packets[i].registration = _reg_timer.read_us();
          packets[i].state = TO_BE_SENT;
        }
      }
    }
  }
}


/* Remove a packet from the send list: */

void PJON::remove(int id) {
  free(packets[id].content);
  packets[id].attempts = 0;
  packets[id].device_id = NULL;
  packets[id].length = NULL;
  packets[id].state = NULL;
  packets[id].registration = NULL;
}


/* Syncronize with transmitter:
 This function is used only in byte syncronization.
 READ_DELAY has to be tuned to correctly send and
 receive transmissions because this variable shifts
 in which portion of the bit, the reading will be
 executed by the next read_byte function */

uint8_t PJON::syncronization_bit() {
  wait_us((BIT_WIDTH / 2) - READ_DELAY);
  uint8_t bit_value = _input_pin; //digitalReadFast(_input_pin);
  wait_us(BIT_WIDTH / 2);
  return bit_value;
}


/* Check if a byte is coming from the pin:
 This function is looking for padding bits before a byte.
 If value is 1 for more than ACCEPTANCE and after
 that comes a 0 probably a byte is coming:
  ________
 |  Init  |
 |--------|
 |_____   |
 |  |  |  |
 |1 |  |0 |
 |__|__|__|
    |
  ACCEPTANCE */

int PJON::receive_byte() {
  Timer t;
  /* Initialize the pin and set it to LOW to reduce interference */
  _input_pin.input(); //pinModeFast(_input_pin, INPUT);
  _input_pin = 0; //digitalWriteFast(_input_pin, LOW);
  //unsigned long time = micros();
  t.start();
  int time=0;
  /* Do nothing until the pin stops to be HIGH or passed more time than
     BIT_SPACER duration (freak condition used to avoid micros() overflow bug) */ //MBED needed?
  while (_input_pin && !(t.read_us() >= BIT_SPACER));
  /* Save how much time passed */
  time = t.read_us();
  /* is for sure less than BIT_SPACER, and if is more than ACCEPTANCE
     (a minimum HIGH duration) and what is coming after is a LOW bit
     probably a byte is coming so try to receive it. */
     t.stop();
  if(time >= ACCEPTANCE && !this->syncronization_bit()){
    return (int)this->read_byte();
  }

  return FAIL;
}


/* Read a byte from the pin */

uint8_t PJON::read_byte() {
  uint8_t byte_value = 0xB00000000;
  wait_us(BIT_WIDTH / 2);
  for (uint8_t i = 0; i < 8; i++) {
    byte_value += _input_pin << i;
    wait_us(BIT_WIDTH);
  }
  return byte_value;
}


/* Try to receive a packet from the pin: */

int PJON::receive() {
  int package_length = PACKET_MAX_LENGTH;
  uint8_t CRC = 0;

  for (uint8_t i = 0; i <= package_length; i++) {
    data[i] = this->receive_byte();

    if (data[i] == FAIL) return FAIL;

    if(i == 0 && data[i] != _device_id && data[i] != BROADCAST)
      return BUSY;

    if(i == 1)
      if(data[i] > 0 && data[i] < PACKET_MAX_LENGTH)
        package_length = data[i];
      else return FAIL;

    CRC ^= data[i];
  }

  _input_pin.output(); //pinModeFast(_input_pin, OUTPUT);

  if (!CRC) {
    if(data[0] != BROADCAST) {
      this->send_byte(ACK);
      _input_pin = 0; //digitalWriteFast(_input_pin, LOW);
    }
    this->_receiver(data[1] - 3, data + 2);
    return ACK;
  } else {
    if(data[0] != BROADCAST) {
      this->send_byte(NAK);
      _input_pin = 0; //digitalWriteFast(_input_pin, LOW);
    }
    return NAK;
  }
}


/* Try to receive a packet from the pin repeatedly with a maximum duration: */

int PJON::receive(unsigned long duration) {
  int response;
  Timer t;
  t.start();
  //long time = micros();
  /* (freak condition used to avoid micros() overflow bug) */
  while(!(t.read_us() >= duration)) {
    response = this->receive();
    if(response == ACK){
      t.stop();
      return ACK;
    }
  }
  return response;
}