summaryrefslogtreecommitdiff
path: root/digital
diff options
context:
space:
mode:
authorNicolas Schodet2013-03-21 00:28:37 +0100
committerNicolas Schodet2013-03-21 00:28:37 +0100
commit8e5af42aae242f6391d4b2aaef6df156dbd60bcb (patch)
treeb68e67e83cd558dbb7d2fdc36987eacfac342ae4 /digital
parent05c638f85ece1a0479dc4b2771b91878c18f2750 (diff)
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.
Diffstat (limited to 'digital')
-rw-r--r--digital/io-hub/src/common-cc/i2c_queue.cc304
-rw-r--r--digital/io-hub/src/common-cc/i2c_queue.hh180
2 files changed, 484 insertions, 0 deletions
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 <algorithm>
+
+/// 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<unsigned int> (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