FastArduino  v1.7
C++ library to build fast but small Arduino/AVR projects
eeprom.h
Go to the documentation of this file.
1 // Copyright 2016-2021 Jean-Francois Poilpret
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
16 
21 #ifndef EEPROM_H
22 #define EEPROM_H
23 
24 #include "boards/board_traits.h"
25 #include <avr/interrupt.h>
26 #include <avr/eeprom.h>
27 #include "interrupts.h"
28 #include "utilities.h"
29 #include "queue.h"
30 
36 #define REGISTER_EEPROM_ISR() \
37  ISR(EE_READY_vect) \
38  { \
39  eeprom::isr_handler::eeprom_ready(); \
40  }
41 
53 #define REGISTER_EEPROM_ISR_METHOD(HANDLER, CALLBACK) \
54  ISR(EE_READY_vect) \
55  { \
56  eeprom::isr_handler::eeprom_ready_method<HANDLER, CALLBACK>(); \
57  }
58 
69 #define REGISTER_EEPROM_ISR_FUNCTION(CALLBACK) \
70  ISR(EE_READY_vect) \
71  { \
72  eeprom::isr_handler::eeprom_ready_function<CALLBACK>(); \
73  }
74 
81 #define DECL_EEPROM_ISR_HANDLERS_FRIEND \
82  friend struct eeprom::isr_handler; \
83  friend void ::EE_READY_vect();
84 
89 namespace eeprom
90 {
126  class EEPROM
127  {
128  public:
129  EEPROM() = default;
130  EEPROM(const EEPROM&) = delete;
131  EEPROM& operator=(const EEPROM&) = delete;
132 
149  template<typename T> static bool read(const T* address, T& value)
150  {
151  return read((uint16_t) address, value);
152  }
153 
169  template<typename T> static bool read(uint16_t address, T& value)
170  {
171  if (!check(address, sizeof(T))) return false;
172  uint8_t* pvalue = (uint8_t*) &value;
173  for (uint16_t i = 0; i < sizeof(T); ++i) blocked_read(address++, *pvalue++);
174  return true;
175  }
176 
194  template<typename T> static bool read(const T* address, T* value, uint16_t count)
195  {
196  return read((uint16_t) address, value, count);
197  }
198 
215  template<typename T> static bool read(uint16_t address, T* value, uint16_t count)
216  {
217  if (!check(address, count * sizeof(T))) return false;
218  uint8_t* pvalue = (uint8_t*) value;
219  for (uint16_t i = 0; i < (count * sizeof(T)); ++i) blocked_read(address++, *pvalue++);
220  return true;
221  }
222 
235  static bool read(const uint8_t* address, uint8_t& value)
236  {
237  return read((uint16_t) address, value);
238  }
239 
251  static bool read(uint16_t address, uint8_t& value)
252  {
253  if (!check(address, 1))
254  {
255  value = UINT8_MAX;
256  return false;
257  }
258  blocked_read(address, value);
259  return true;
260  }
261 
278  template<typename T> static bool write(const T* address, const T& value)
279  {
280  return write((uint16_t) address, value);
281  }
282 
298  template<typename T> static bool write(uint16_t address, const T& value)
299  {
300  if (!check(address, sizeof(T))) return false;
301  uint8_t* pvalue = (uint8_t*) &value;
302  for (uint8_t i = 0; i < sizeof(T); ++i) blocked_write(address++, *pvalue++);
303  return true;
304  }
305 
323  template<typename T> static bool write(const T* address, const T* value, uint16_t count)
324  {
325  return write((uint16_t) address, value, count);
326  }
327 
344  template<typename T> static bool write(uint16_t address, const T* value, uint16_t count)
345  {
346  if (!check(address, count * sizeof(T))) return false;
347  uint8_t* pvalue = (uint8_t*) value;
348  for (uint8_t i = 0; i < (count * sizeof(T)); ++i) blocked_write(address++, *pvalue++);
349  return true;
350  }
351 
363  static bool write(const uint8_t* address, uint8_t value)
364  {
365  return write((uint16_t) address, value);
366  }
367 
378  static bool write(uint16_t address, uint8_t value)
379  {
380  if (!check(address, 1)) return false;
381  blocked_write(address, value);
382  return true;
383  }
384 
390  static void erase()
391  {
392  for (uint16_t address = 0; address < size(); ++address)
393  {
395  erase_address(address);
396  }
397  }
398 
402  static constexpr uint16_t size()
403  {
404  return E2END + 1;
405  }
406 
413  static void wait_until_ready()
414  {
415  EECR_.loop_until_bit_clear(EEPE);
416  }
417 
418  protected:
420  static bool constexpr check(uint16_t address, uint16_t size)
421  {
422  return (size != 0) && (address <= E2END) && (size <= (E2END + 1)) && ((address + size) <= (E2END + 1));
423  }
424 
425  static void blocked_read(uint16_t address, uint8_t& value)
426  {
428  read_byte(address, value);
429  }
430 
431  static void read_byte(uint16_t address, uint8_t& value)
432  {
433  EEAR_ = address;
434  EECR_ = bits::BV8(EERE);
435  value = EEDR_;
436  }
437 
438  static void blocked_write(uint16_t address, uint8_t value)
439  {
441  write_byte(address, value);
442  }
443 
444  // In order to optimize write time we read current byte first, then compare it with new value
445  // Then we choose between erase, write and erase+write based on comparison
446  // This approach is detailed in ATmel note AVR103: Using the EEPROM Programming Modes
447  // http://www.atmel.com/images/doc2578.pdf
448  static void write_byte(uint16_t address, uint8_t value)
449  {
450  EEAR_ = address;
451  EECR_ = bits::BV8(EERE);
452  uint8_t old_value = EEDR_;
453  uint8_t diff = old_value ^ value;
454  if (diff & value)
455  {
456  // Some bits need to be erased (ie set to 1)
457  if (value == UINT8_MAX)
458  // Erase only
459  EECR_ = bits::BV8(EEPM0);
460  else
461  // Combine Erase/Write operation
462  EECR_ = 0;
463  }
464  else
465  {
466  // No bit to be erased
467  if (diff)
468  // Some bits to be programmed (set to 0): Write only
469  EECR_ = bits::BV8(EEPM1);
470  else
471  // old value == new value => do nothing
472  return;
473  }
474  EEDR_ = value;
475  synchronized
476  {
477  EECR_ |= bits::BV8(EEMPE);
478  EECR_ |= bits::BV8(EEPE);
479  }
480  }
481 
482  static bool erase_address(uint16_t address)
483  {
484  EEAR_ = address;
485  EECR_ = bits::BV8(EERE);
486  uint8_t value = EEDR;
487  // Some bits need to be erased (ie set to 1)
488  if (value == UINT8_MAX) return false;
489  EECR_ = bits::BV8(EEPM0);
490  EEDR_ = UINT8_MAX;
491  synchronized
492  {
493  EECR_ |= bits::BV8(EEMPE);
494  EECR_ |= bits::BV8(EEPE);
495  }
496  return true;
497  }
499 
500  private:
501  using REG8 = board_traits::REG8;
502  using REG16 = board_traits::REG16;
503  static constexpr const REG16 EEAR_{EEAR};
504  static constexpr const REG8 EECR_{EECR};
505  static constexpr const REG8 EEDR_{EEDR};
506  };
507 
530  class QueuedWriter : private EEPROM
531  {
532  public:
533  QueuedWriter(const QueuedWriter&) = delete;
534  QueuedWriter& operator=(const QueuedWriter&) = delete;
535 
543  template<uint16_t SIZE>
544  explicit QueuedWriter(uint8_t (&buffer)[SIZE]) : buffer_{buffer}
545  {
547  }
548 
566  template<typename T> bool write(const T* address, const T& value)
567  {
568  return write((uint16_t) address, value);
569  }
570 
587  template<typename T> bool write(uint16_t address, const T& value)
588  {
589  if (!check(address, sizeof(T))) return false;
590  synchronized return write_data(address, (uint8_t*) &value, sizeof(T));
591  }
592 
611  template<typename T> bool write(const T* address, const T* value, uint16_t count)
612  {
613  return write((uint16_t) address, value, count);
614  }
615 
633  template<typename T> bool write(uint16_t address, const T* value, uint16_t count)
634  {
635  if (!check(address, count * sizeof(T))) return false;
636  synchronized return write_data(address, (uint8_t*) value, count * sizeof(T));
637  }
638 
651  bool write(const uint8_t* address, uint8_t value)
652  {
653  return write((uint16_t) address, value);
654  }
655 
667  bool write(uint16_t address, uint8_t value)
668  {
669  if (!check(address, 1)) return false;
670  synchronized return write_data(address, &value, 1);
671  }
672 
680  void erase()
681  {
682  // First remove all pending writes
683  synchronized
684  {
685  buffer_.clear_();
686  current_.size = 0;
687  }
688  // Wait until current byte write is finished
689  wait_until_done();
690  synchronized
691  {
692  // Start erase
693  erase_ = true;
694  done_ = false;
695  current_.address = 0;
696  current_.size = size();
697  // Start transmission if not done yet
698  EECR_ = bits::BV8(EERIE);
699  }
700  }
701 
709  void wait_until_done() const
710  {
711  while (!done_)
712  ;
713  }
714 
718  bool is_done() const
719  {
720  return done_;
721  }
722 
723  private:
724  static const uint16_t ITEM_SIZE = 3;
725 
726  bool on_ready()
727  {
728  if (erase_)
729  {
730  if (current_.size)
731  erase_next();
732  else
733  {
734  // All erases are finished
735  erase_ = false;
736  // Mark all EEPROM work as finished if no write is pending in the queue
737  if (buffer_.empty_())
738  {
739  done_ = true;
740  EECR_ = 0;
741  }
742  }
743  }
744  else if (current_.size)
745  // There is one item being currently written, write next byte
746  write_next();
747  else if (!buffer_.empty_())
748  {
749  // Current item is finished writing but there is another item to be written in the queue
750  // Get new item and start transmission of first byte
751  current_ = next_item();
752  write_next();
753  }
754  else
755  {
756  // All writes are finished
757  done_ = true;
758  EECR_ = 0;
759  }
760  return done_;
761  }
762 
763  void write_next()
764  {
765  uint8_t value;
766  buffer_.pull_(value);
767  EEPROM::write_byte(current_.address++, value);
768  --current_.size;
769  EECR_ |= bits::BV8(EERIE);
770  }
771 
772  void erase_next()
773  {
774  EEPROM::erase_address(current_.address++);
775  --current_.size;
776  EECR_ |= bits::BV8(EERIE);
777  }
778 
779  bool write_data(uint16_t address, uint8_t* value, uint16_t size)
780  {
781  // First check if there is enough space in buffer_ for this queued write
782  if ((buffer_.free_() < (size + ITEM_SIZE)) || (size == 0)) return false;
783  done_ = false;
784  // Add new queued write to buffer
785  buffer_.push_(WriteItem::value1(address, size));
786  buffer_.push_(WriteItem::value2(address, size));
787  buffer_.push_(WriteItem::value3(address, size));
788  for (uint16_t i = 0; i < size; ++i) buffer_.push_(*value++);
789 
790  // Start transmission if not done yet
791  EECR_ = bits::BV8(EERIE);
792  return true;
793  }
794 
795  struct WriteItem
796  {
797  WriteItem() = default;
798  WriteItem(uint8_t value1, uint8_t value2, uint8_t value3)
799  : address{uint16_t(uint16_t(value1 << 4) | uint8_t(value2 >> 4))},
800  size{uint16_t(utils::is_zero(((value2 & 0x0F) << 8) | value3, E2END + 1))} {}
801 
802  static uint8_t value1(uint16_t address, uint16_t size UNUSED)
803  {
804  return address >> 4;
805  }
806  static uint8_t value2(uint16_t address, uint16_t size)
807  {
808  return (address << 4) | (size >> 8);
809  }
810  static uint8_t value3(uint16_t address UNUSED, uint16_t size)
811  {
812  return size;
813  }
814 
815  uint16_t address = 0;
816  uint16_t size = 0;
817  };
818 
819  WriteItem next_item()
820  {
821  uint8_t value1;
822  buffer_.pull_(value1);
823  uint8_t value2;
824  buffer_.pull_(value2);
825  uint8_t value3;
826  buffer_.pull_(value3);
827  return WriteItem{value1, value2, value3};
828  }
829 
830  using REG8 = board_traits::REG8;
831  using REG16 = board_traits::REG16;
832  static constexpr const REG16 EEAR_{EEAR};
833  static constexpr const REG8 EECR_{EECR};
834  static constexpr const REG8 EEDR_{EEDR};
835 
837  WriteItem current_;
838  volatile bool erase_ = false;
839  volatile bool done_ = true;
840 
841  friend struct isr_handler;
842  };
843 
845  struct isr_handler
846  {
847  static void eeprom_ready()
848  {
849  interrupt::HandlerHolder<eeprom::QueuedWriter>::handler()->on_ready();
850  }
851 
852  template<void (*CALLBACK)()> static void eeprom_ready_function()
853  {
854  if (interrupt::HandlerHolder<eeprom::QueuedWriter>::handler()->on_ready())
855  interrupt::CallbackHandler<void (*)(), CALLBACK>::call();
856  }
857 
858  template<typename HANDLER, void (HANDLER::*CALLBACK)()> static void eeprom_ready_method()
859  {
860  if (interrupt::HandlerHolder<eeprom::QueuedWriter>::handler()->on_ready())
861  interrupt::CallbackHandler<void (HANDLER::*)(), CALLBACK>::call();
862  }
863  };
865 }
866 
867 #endif /* EEPROM_H */
868 
containers::Queue::free_
uint8_t free_() const
Tell the current number of available locations for items to be pushed to this queue.
Definition: queue.h:275
eeprom::QueuedWriter::wait_until_done
void wait_until_done() const
Block until all pending operations (queued in the ring buffer) are complete.
Definition: eeprom.h:709
containers::Queue< uint8_t, uint8_t >
eeprom::EEPROM::write
static bool write(const T *address, const T &value)
Write the content of value of type T to the EEPROM at address.
Definition: eeprom.h:278
eeprom::EEPROM::wait_until_ready
static void wait_until_ready()
Block until the current EEPROM operation, whetever it is (e.g.
Definition: eeprom.h:413
eeprom::EEPROM
Collection of static methods to read or write the AVR EEPROM.
Definition: eeprom.h:127
containers::Queue::pull_
bool pull_(T &item)
Pull an item from the beginning of this queue, if not empty, and copy it into item.
eeprom::QueuedWriter::write
bool write(const T *address, const T *value, uint16_t count)
Write value, an array of count values of type T to the EEPROM at address.
Definition: eeprom.h:611
eeprom::EEPROM::read
static bool read(const uint8_t *address, uint8_t &value)
Read one byte stored in EEPROM at address.
Definition: eeprom.h:235
eeprom::EEPROM::write
static bool write(const uint8_t *address, uint8_t value)
Write one byte to the EEPROM at address.
Definition: eeprom.h:363
bits::BV8
static constexpr uint8_t BV8(uint8_t bit)
Create a uint8_t bitmask for the given bit number.
Definition: bits.h:41
eeprom::QueuedWriter
API that allows asynchronous writing to EEPROM; this can be useful when you have large amount of data...
Definition: eeprom.h:531
eeprom::QueuedWriter::is_done
bool is_done() const
Tell if there is no queued, nor on-going write operation.
Definition: eeprom.h:718
eeprom::QueuedWriter::write
bool write(const T *address, const T &value)
Write the content of value of type T to the EEPROM at address.
Definition: eeprom.h:566
eeprom::EEPROM::read
static bool read(uint16_t address, T *value, uint16_t count)
Read an array of count values of type T stored in EEPROM at address.
Definition: eeprom.h:215
eeprom::EEPROM::read
static bool read(const T *address, T *value, uint16_t count)
Read an array of count values of type T stored in EEPROM at address.
Definition: eeprom.h:194
utils::is_zero
constexpr T is_zero(T value, T default_value)
Replace value by default_value if not "true" (also known as "Elvis operator").
Definition: utilities.h:307
eeprom::EEPROM::write
static bool write(uint16_t address, const T &value)
Write the content of value of type T to the EEPROM at address.
Definition: eeprom.h:298
containers::Queue::empty_
bool empty_() const
Tell if this queue is currently empty.
Definition: queue.h:233
eeprom::EEPROM::size
static constexpr uint16_t size()
Return the size (in bytes) of the embedded EEPROM.
Definition: eeprom.h:402
UNUSED
#define UNUSED
Specific GCC attribute to declare an argument or variable unused, so that the compiler does not emit ...
Definition: defines.h:45
queue.h
Utility API to handle ring-buffer queue containers.
eeprom::EEPROM::erase
static void erase()
Erase the full EEPROM content.
Definition: eeprom.h:390
utilities.h
General utilities API that have broad application in programs.
containers::Queue::push_
bool push_(TREF item)
Push item to the end of this queue, provided there is still available space in its ring buffer.
eeprom::EEPROM::write
static bool write(uint16_t address, const T *value, uint16_t count)
Write value, an array of count values of type T to the EEPROM at address.
Definition: eeprom.h:344
eeprom
Defines the API for accessing the EEPROM embedded in each AVR MCU.
Definition: eeprom.h:90
eeprom::QueuedWriter::write
bool write(uint16_t address, const T &value)
Write the content of value of type T to the EEPROM at address.
Definition: eeprom.h:587
eeprom::QueuedWriter::erase
void erase()
Erase the full EEPROM content.
Definition: eeprom.h:680
interrupts.h
General API for handling AVR interrupt vectors.
eeprom::EEPROM::read
static bool read(uint16_t address, T &value)
Read value of type T stored in EEPROM at address.
Definition: eeprom.h:169
eeprom::QueuedWriter::QueuedWriter
QueuedWriter(uint8_t(&buffer)[SIZE])
Construct a QueuedWriter from a given buffer array.
Definition: eeprom.h:544
interrupt::register_handler
void register_handler(Handler &handler)
Register a class instance containing methods that shall be called back by an ISR.
Definition: interrupts.h:157
eeprom::EEPROM::read
static bool read(const T *address, T &value)
Read value of type T stored in EEPROM at address.
Definition: eeprom.h:149
eeprom::EEPROM::write
static bool write(const T *address, const T *value, uint16_t count)
Write value, an array of count values of type T to the EEPROM at address.
Definition: eeprom.h:323
eeprom::QueuedWriter::write
bool write(uint16_t address, uint8_t value)
Write one byte to the EEPROM at address.
Definition: eeprom.h:667
eeprom::EEPROM::read
static bool read(uint16_t address, uint8_t &value)
Read one byte stored in EEPROM at address.
Definition: eeprom.h:251
eeprom::QueuedWriter::write
bool write(const uint8_t *address, uint8_t value)
Write one byte to the EEPROM at address.
Definition: eeprom.h:651
containers::Queue::clear_
void clear_()
Completely clear this queue.
Definition: queue.h:291
eeprom::QueuedWriter::write
bool write(uint16_t address, const T *value, uint16_t count)
Write value, an array of count values of type T to the EEPROM at address.
Definition: eeprom.h:633
eeprom::EEPROM::write
static bool write(uint16_t address, uint8_t value)
Write one byte to the EEPROM at address.
Definition: eeprom.h:378