From 8e5af42aae242f6391d4b2aaef6df156dbd60bcb Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Thu, 21 Mar 2013 00:28:37 +0100 Subject: digital/io-hub/src/common-cc: add I2C queue This code is responsible for querying each child board, queuing message for them, and checking that messages are well received. --- digital/io-hub/src/common-cc/i2c_queue.cc | 304 ++++++++++++++++++++++++++++++ digital/io-hub/src/common-cc/i2c_queue.hh | 180 ++++++++++++++++++ 2 files changed, 484 insertions(+) create mode 100644 digital/io-hub/src/common-cc/i2c_queue.cc create mode 100644 digital/io-hub/src/common-cc/i2c_queue.hh diff --git a/digital/io-hub/src/common-cc/i2c_queue.cc b/digital/io-hub/src/common-cc/i2c_queue.cc new file mode 100644 index 00000000..9fba519c --- /dev/null +++ b/digital/io-hub/src/common-cc/i2c_queue.cc @@ -0,0 +1,304 @@ +// io-hub - Modular Input/Output. {{{ +// +// Copyright (C) 2013 Nicolas Schodet +// +// APBTeam: +// Web: http://apbteam.org/ +// Email: team AT apbteam DOT org +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// }}} +#include "i2c_queue.hh" + +#include "ucoolib/utils/crc.hh" + +#include + +/// Update is done asynchronously. At each cycle, the result of the previous +/// update is given to slave classes to update their status, then the next +/// update is started. This frees CPU while I2C messages are exchanged. +/// +/// There are three stages in the update: +/// - receive status from each slave, +/// - send one command from the queue if any, +/// - send one transient command for each synchronised slave if any. +/// +/// A synchronised slave is a slave for which there is no command pending, +/// which means that the seq read from its status is the same as the last sent +/// command and there is no command for this slave in the queue. + +I2cQueue::Slave::Slave (I2cQueue &queue, uint8_t address, int status_size) + : next_ (0), queue_ (queue), + raw_status_size_ (status_size ? header_size + status_size : 0), + address_ (address), seq_ (0), last_status_valid_ (false), + transient_commands_index_ (0) +{ + queue.register_slave (*this); + ucoo::assert (status_size <= status_size_max); + transient_commands_[0].raw_size = 0; + transient_commands_[1].raw_size = 0; +} + +inline uint8_t +I2cQueue::Slave::seq_next () +{ + if (seq_ == 255) + seq_ = 1; + else + seq_++; + return seq_; +} + +I2cQueue::I2cQueue (ucoo::I2cMaster &i2c) + : i2c_ (i2c), slaves_ (0), update_state_ (IDLE), + queue_head_ (0), queue_tail_ (0), queue_timeout_ (0) +{ + i2c_.register_finished (*this); +} + +bool +I2cQueue::sync () +{ + // Wait for end of all transfers. + i2c_.wait (); + // Update status with received data. + bool all_slave_sync = true; + for (Slave *s = slaves_; s; s = s->next_) + { + if (s->raw_status_size_) + { + if (s->last_status_valid_) + { + s->recv_status (s->last_raw_status_ + header_size); + // On initialisation, copy sequence number. + if (!s->seq_) + s->seq_ = s->last_raw_status_[1]; + // Then, slave is synchronised if status sequence number match + // the current one. + else if (s->last_raw_status_[1] != s->seq_) + all_slave_sync = false; + } + else + all_slave_sync = false; + } + } + // Start next update cycle. + update_slave_ = slaves_; + start_update_status (); + // Was sync? + return all_slave_sync; +} + +void +I2cQueue::register_slave (Slave &slave) +{ + slave.next_ = slaves_; + slaves_ = &slave; +} + +void +I2cQueue::send (Slave &slave, const uint8_t *command, int size, + CommandType type) +{ + Command *c; + ucoo::assert (size <= command_size_max); + unsigned int transient_commands_index = 0; + // Find command buffer. + if (type == TRANSIENT) + { + transient_commands_index = (slave.transient_commands_index_ + 1) % 2; + c = &slave.transient_commands_[transient_commands_index]; + } + else + { + ucoo::assert (queue_head_ != queue_next (queue_tail_)); + c = &queue_[queue_tail_]; + } + // Fill and copy data. + // Warning: transient commands need double buffering so that it can be + // updated even if being transmitted. However, this can fail if two + // updates are done in a really short interval (if both updates are done + // during a transfer). Don't do that! + c->slave = &slave; + c->type = type; + if (type != RAW) + { + c->raw_size = header_size + size; + std::copy (command, command + size, c->raw + header_size); + if (type == RELIABLE) + c->raw[1] = c->slave->seq_next (); + else + c->raw[1] = 0; + c->raw[0] = ucoo::crc8_compute (c->raw + 1, c->raw_size - 1); + } + else + { + c->raw_size = size; + std::copy (command, command + size, c->raw); + } + // Commit. + ucoo::barrier (); + if (type == TRANSIENT) + slave.transient_commands_index_ = transient_commands_index; + else + queue_tail_ = queue_next (queue_tail_); +} + +void +I2cQueue::finished (int status) +{ + switch (update_state_) + { + case RECV_STATUS: + end_update_status (status); + start_update_status (); + break; + case SEND_QUEUE: + end_send_queue (status); + start_send_queue (); + break; + case SEND_TRANSIENT: + end_send_transient (status); + start_send_transient (); + break; + default: + ucoo::assert_unreachable (); + } +} + +void +I2cQueue::start_update_status () +{ + // Skip over slaves with no status. + while (update_slave_ && !update_slave_->raw_status_size_) + update_slave_ = update_slave_->next_; + // Update if found, else next step. + if (update_slave_) + { + update_state_ = RECV_STATUS; + i2c_.recv (update_slave_->address_, (char *) update_slave_->last_raw_status_, + update_slave_->raw_status_size_); + } + else + { + update_slave_ = slaves_; + start_send_queue (); + } +} + +void +I2cQueue::end_update_status (int status) +{ + bool ok = false; + if (status == update_slave_->raw_status_size_) + { + // Check CRC. + Slave *s = update_slave_; + uint8_t crc = ucoo::crc8_compute (s->last_raw_status_ + 1, + s->raw_status_size_ - 1); + if (crc == s->last_raw_status_[0]) + { + ok = true; + // Check pending command. + if (queue_head_ != queue_tail_) + { + Command *c = &queue_[queue_head_]; + if (c->slave == update_slave_ + && c->type == RELIABLE + && c->raw[1] == s->last_raw_status_[1]) + { + queue_head_ = queue_next (queue_head_); + queue_timeout_ = 0; + } + } + } + } + update_slave_->last_status_valid_ = ok; + update_slave_ = update_slave_->next_; +} + +void +I2cQueue::start_send_queue () +{ + if (!queue_timeout_ + && queue_head_ != queue_tail_) + { + update_state_ = SEND_QUEUE; + update_command_ = &queue_[queue_head_]; + i2c_.send (update_command_->slave->address_, + (char *) update_command_->raw, update_command_->raw_size); + } + else + { + if (queue_timeout_) + queue_timeout_--; + start_send_transient (); + } +} + +void +I2cQueue::end_send_queue (int status) +{ + if (status == update_command_->raw_size) + { + if (update_command_->type == RAW) + // OK, next. + queue_head_ = queue_next (queue_head_); + else + // Wait for acknowledgement. + queue_timeout_ = retry_timeout; + } + start_send_transient (); +} + +void +I2cQueue::start_send_transient () +{ + // Skip over slaves with no transient command, or not synchronised. + for (; update_slave_; update_slave_ = update_slave_->next_) + { + if (!update_slave_->last_status_valid_ + || update_slave_->last_raw_status_[1] != update_slave_->seq_) + continue; + update_command_ = &update_slave_->transient_commands_ + [update_slave_->transient_commands_index_]; + if (update_command_->raw_size != 0) + break; + } + // If one found, send it, else done. + if (update_slave_) + { + update_state_ = SEND_TRANSIENT; + i2c_.send (update_slave_->address_, (char *) update_command_->raw, + update_command_->raw_size); + } + else + { + update_slave_ = slaves_; + update_state_ = IDLE; + } +} + +void +I2cQueue::end_send_transient (int status) +{ + if (status == update_command_->raw_size) + { + // Transient commands are sent once. + update_command_->raw_size = 0; + } +} + diff --git a/digital/io-hub/src/common-cc/i2c_queue.hh b/digital/io-hub/src/common-cc/i2c_queue.hh new file mode 100644 index 00000000..d2a0e584 --- /dev/null +++ b/digital/io-hub/src/common-cc/i2c_queue.hh @@ -0,0 +1,180 @@ +#ifndef i2c_queue_hh +#define i2c_queue_hh +// io-hub - Modular Input/Output. {{{ +// +// Copyright (C) 2013 Nicolas Schodet +// +// APBTeam: +// Web: http://apbteam.org/ +// Email: team AT apbteam DOT org +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// }}} +#include "ucoolib/common.hh" +#include "ucoolib/intf/i2c.hh" + +/// Handle communication as a master to several slaves. +/// +/// The communication protocol is always based on: +/// - a message sent to the slave with a command to execute, +/// - a status read from the slave containing the current slave state. +/// +/// The first byte of all messages is a CRC of the following bytes. +/// +/// The next byte is the command sequence number. It is used by the master to +/// know if its command has been handled. The last handled command sequence +/// number is available in the slave status. +/// +/// All other bytes are the message payload. +/// +/// As long as the slave last command sequence number is not equal to the last +/// sent sequence number, the master can not send any other command. If the +/// slave do not acknowledge the command after a time out, the command is sent +/// again. +/// +/// Several commands can be stored by this class, it will defer their +/// transmission until the first command is acknowledged. +/// +/// This class also support unreliable transient command delivery. There can +/// be only one transient command per slave and it is sent only if the slave +/// is synchronised. +/// +/// There can be slaves which do not use this protocol. In this case, the +/// message is sent without any CRC or acknowledgement support. +class I2cQueue : public ucoo::I2cMaster::FinishedHandler +{ + public: + /// Maximum status size. + static const int status_size_max = 14; + /// Maximum command size. + static const int command_size_max = 14; + /// Message header size. + static const int header_size = 2; + /// Maximum messages in queue. + static const int queue_size = 15; + /// Number of update between retries. + static const int retry_timeout = 3; + public: + class Slave; + /// Command type. + enum CommandType { RELIABLE, TRANSIENT, RAW }; + /// Command buffer. + struct Command + { + /// Associated slave. + Slave *slave; + /// Command raw size, or 0 for invalid. + int raw_size; + /// Command type. + CommandType type; + /// Command header + payload. + uint8_t raw[header_size + command_size_max]; + }; + /// Slave classes should inherit from this. + class Slave + { + protected: + /// Initialise I2cQueue parameters, use 0 status_size if no status. + Slave (I2cQueue &queue, uint8_t address, int status_size); + /// Send a command. + void send (const uint8_t *command, int size, + CommandType type = RELIABLE) + { queue_.send (*this, command, size, type); } + /// Called when a status has been received and synchronisation is + /// done. + virtual void recv_status (const uint8_t *status) = 0; + private: + /// Increment and return seq. + inline uint8_t seq_next (); + private: + friend class I2cQueue; + /// Next slave in list. + Slave *next_; + /// Attached I2cQueue. + I2cQueue &queue_; + /// Size of status. + int raw_status_size_; + /// Slave address. + uint8_t address_; + /// Last command sequence number. + uint8_t seq_; + /// Whether last received status was valid. + bool last_status_valid_; + /// Last received status. + uint8_t last_raw_status_[header_size + status_size_max]; + /// Transient command slots. + Command transient_commands_[2]; + /// Current active transient command. + unsigned int transient_commands_index_; + }; + public: + /// Constructor. + I2cQueue (ucoo::I2cMaster &i2c); + /// Synchronise all slaves, return true if synchronised (no message in + /// queue). + bool sync (); + /// Add a new slave in the list of slaves. + void register_slave (Slave &slave); + /// Send a command. + void send (Slave &slave, const uint8_t *command, int size, + CommandType type = RELIABLE); + /// See I2cMaster::FinishedHandler::finished. + void finished (int status); + private: + void start_update_status (); + void end_update_status (int status); + void start_send_queue (); + void end_send_queue (int status); + void start_send_transient (); + void end_send_transient (int status); + /// Return next index in queue. + static int queue_next (int index) + { + return (static_cast (index) + 1) % (queue_size + 1); + } + private: + /// I2C interface. + ucoo::I2cMaster &i2c_; + /// Chained list of slaves. + Slave *slaves_; + /// State of update FSM. + enum UpdateState + { + /// Not started. + IDLE, + /// Receiving a status. + RECV_STATUS, + /// Sending top of command queue. + SEND_QUEUE, + /// Sending transient command. + SEND_TRANSIENT, + }; + UpdateState update_state_; + /// Currently addressed slave. + Slave *update_slave_; + /// Currently sent command. + Command *update_command_; + /// Command queue circular buffer. + Command queue_[queue_size + 1]; + /// Index to head command, next command to be sent. + ucoo::int_atomic_t queue_head_; + /// Index to tail of queue, free space for next command to enqueue. + ucoo::int_atomic_t queue_tail_; + /// Number of update before next retransmission from queue. + int queue_timeout_; +}; + +#endif // i2c_queue_hh -- cgit v1.2.3