Jordan Earls
/
pjon_testing
Testing getting PJON working on mbed https://github.com/gioblu/PJON
Diff: pjon.cpp
- Revision:
- 0:fa2f348efd7e
- Child:
- 1:bd0ee507dd4c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pjon.cpp Sun Jan 10 07:26:52 2016 +0000 @@ -0,0 +1,448 @@ + + /*-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; //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; //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; +} \ No newline at end of file