Ringbuffers en super arrays

Dit onderwerp is min of meer ontstaan uit dit onderwerp: topic
Daaruit kwam een behoefte om ahw een circulaire buffer (array) te maken waardoor je altijd de laatste (array grootte aantal) entries kan uitlezen. In ram is dit allemaal niet zo'n probleem. Echter dan ben je beperkt tot de hoeveelheid ruimte (RAM) die er beschikbaar is.
Een poging om een SD kaartje als geheugen te gebruiken lukte ten dele. Het werkte op zich prima. Echter na wat testen wilde de SD niet meer schrijven. Het gebruikte SD kaartje (heel oud) haalde hooguit 10.000 schrijf operaties. Ik schreef 1.000.000 x.
Nu kwam ik recentelijk F-RAM ic's tegen. Deze IC's hebben een interface met I2C of SPI (tot 20-40 Mhz). Daarnaast is er op dit moment al 1Mbyte (8MBits) beschikbaar.

Op dit moment heb ik een class gemaakt die prima met RAM werkt. Gaat binnenkort naar Github als library.
Als het meezit komt dit weekend een 32Kbyte F-RAM binnen en kan ik kijken of ik een array met redelijke performance daarop kan maken zonder dat je, je daar verder druk over hoeft te maken in de programma code.

Voor hen die niet kunnen wachten:
Hier is de class die je in een .h bestand kan maken:

/*
 * Array.h
 *
 *  Created on: 14 dec. 2018
 *      Author: nicoverduin
 */

#ifndef ARRAY_H_
#define ARRAY_H_

//
// Generic catch-all implementation for determining type of variable
// with thanks to http://arduino.stackexchange.com/questions/3079/how-to-retrieve-the-data-type-of-a-variable
//
template <typename T_ty> struct TypeInfo { static const char * name; };
template <typename T_ty> const char * TypeInfo<T_ty>::name = "unknown";
//
// Handy macro to make querying stuff easier.
//
#define TYPE_NAME(var) TypeInfo< typeof(var) >::name
//
// Handy macro to make defining stuff easier.
//
#define MAKE_TYPE_INFO(type)  template <> const char * TypeInfo<type>::name = #type;
//
// Type-specific implementations. Only float and double need to be tested
//
MAKE_TYPE_INFO( float )
MAKE_TYPE_INFO( double )

// test defines
#define DEBUG

enum {
  RAM,          // in memory array
  FRAM          // array in F-RAM
};

template<class T>
class Array {
protected:
  bool      loopAround;     // loop around has taken place if true
  uint32_t  beginIndex;     // virtual start of array
  uint32_t  endIndex;       // virtual end of array
  uint32_t  arraySize;      // number of array entries
  uint32_t  lastIndex;      // used for operator overloading
  T         value;          // temporary field used for reading
  T *    array = NULL;      // points to our array
  uint8_t   storageType;    // determines where the array is stored


  // functions
public:
  /*
   * @name init
   * @param uint32_t size of array in entry count
   * @param uint8_t storageType determines where the array will be stored
   * @brief initializes andcreates an array of size T types number of entries
   */
  void init(uint32_t size, uint8_t storage = RAM) {

    // initialize our variables
    beginIndex  = 0;
    endIndex    = 0;
    arraySize   = size;
    loopAround  = false;
    storageType = storage;

    if (storageType == RAM) {
      // allocate our array
      array = (T *)malloc(size * sizeof(T));
    }

#ifdef DEBUG
    if (array != NULL) {
      Serial.println("Array allocated");
    } else {
      Serial.println("Array allocation failed!!!");
      return;
    }
#endif

    // initialize our array
    for (uint32_t i = 0; i < arraySize; i++) {
      if (strcmp(TYPE_NAME(T), "double") == 0 || strcmp(TYPE_NAME(T), "float") == 0) {
        if (storageType == RAM) {
          array[i] = 0.0;
        }
      } else {
        if (storage == RAM) {
          array[i] = 0;
        }
      }
    }

#ifdef DEBUG
    printArray(10);
#endif
  }

  /**
   * @name printArray
   * @param uint8_t number of elements on row to print
   * @brief prints the array in rows of 10 elements each
   */
  void printArray(uint8_t noOfElements) {
    uint8_t elementsCtr = 0;                  // number of element per row counter
    if (storageType == RAM) {
      if (array != NULL) {                      // only print if there is an array
        for (uint8_t i = 0; i < arraySize; i++) {
          Serial.print(array[i]);
          Serial.print(" ");
          elementsCtr++;
          if (elementsCtr == noOfElements) {
            Serial.println();
            elementsCtr = 0;
          }
        }
      }
      // extra line after last row
      Serial.println();
    }
  }
  /*
   * @name addEntry
   * @param value variable of type T to be added to at endIndex
   * @brief adds the passed variable to the current endIndex subscript
   */
  void addEntry(T value) {

      // check if we need to do a loop around
      if (endIndex == arraySize) {        // the array is full so we need to
          loopAround = true;              // loop around
      }
      endIndex = endIndex % arraySize;    // calculate the new offset

      if (storageType == RAM) {
        // add our value
        array[endIndex] = value;
      }
      // raise our subscript for the next time
      endIndex++;

      // move the begin index one up if array is full
      if (loopAround) {
        beginIndex++;

        // and calculate our new substript
        beginIndex = beginIndex % arraySize;
      }
  }
  /*
  * @name getEntry
  * @param index subscript of which we want our value
  * @returns T
  * @brief returns the value of type T based on virtual subscript
  */
  T getEntry(uint32_t index) {

      // calculate our virtual index within the array
      uint32_t virtualIndex = beginIndex + index;

      // calculate the real subscript
      virtualIndex = virtualIndex % arraySize;

      // return our value
      if (storageType == RAM) {
        return array[virtualIndex];
      }
  }
  /*
  * @name setEntry
  * @param index virtual subscript in which we want our value
  * @param value T type value to be stored in array
  * @returns none
  * @brief returns the value of type T based on virtual subscript
  */
  void setEntry(uint32_t index, const T value) {

      // calculate our virtual index within the array
      uint32_t virtualIndex = beginIndex + index;

      // calculate the real subscript
      virtualIndex = virtualIndex % arraySize;

      // store our value
      if (storageType == RAM) {
        array[virtualIndex] = value;
      }
  }
  /*
  * @name operator []
  * @param index 32 bit unsigned int that references an array entry
  * @returns variable T virtually referenced in array
  * @brief returns the value of type T based on virtual subscript
  */
  T& operator[](uint32_t index) {
    // calculate our virtual index within the array
    uint32_t virtualIndex = beginIndex + index;

    // calculate the real subscript
    virtualIndex = virtualIndex % arraySize;

    // return our value
    if (storageType == RAM) {
      return array[virtualIndex];
    }
  }

  /**
   * @name wipe
   * @brief deletes the memory allocated to the array
   */
  void wipe(){
    if (storageType == RAM) {
      free(array);
    }
  }

};

#endif /* ARRAY_H_ */

En hier een eenvoudige sketch hoe je ermee kan werken

/*
    Name:       Ringbuffer.ino
    Created:    14-12-2018 08:41:43
    Author:     nico verduin
    Brief:      Eenvoudige manier om met een array  een ringbuffer te maken
*/
// includes
#include "Array.h"

// program defines

#define ARRAY_SIZE 100
#define COLUMNS_PER_ROW 10
#define ARRAY_TYPE double

// global variables
// create an array of type uint32
Array <ARRAY_TYPE> myArray;

/*
 * @name setup()
 * @brief Initializes the program
 */
void setup()
{
    // initialiseer Serial
    Serial.begin(115200);

    // allocate an initialize our array
    myArray.init(ARRAY_SIZE);

    for (uint8_t i = 0; i < ARRAY_SIZE; i++) {
      myArray.addEntry((ARRAY_TYPE)i);
    }

    // change the virtual indxes
    myArray.addEntry(1000);
    myArray.addEntry(2000);

    // now see what has happened
    myArray.printArray(COLUMNS_PER_ROW);

    Serial.println(myArray[12]);
    myArray[12] = 10000;
    Serial.println(myArray[12]);

    // delete allocated memory
    myArray.wipe();
}

// Add the main program code into the continuous loop() function
void loop()
{
}

Met ARRAY_TYPE definieer je het soort array (int, uint32_t, double etc)
de rest spreekt voor zichzelf

Delen is leuk :slight_smile:
Bedankt
Jan

Prachtig initiatief.
Zal wel nog een paar dagen zeg maar week(en) duren voor ik het helemaal snap.
De hogere schrijf snelheid is zeker een plus punt.

Nou meer dat ie langer meegaat. Iets van 14.000.000.000.000 schrijf operaties en een levensduur van slechts 157 jaar. Zouden ze dat garanderen want dan moet ik een berichtje maken voor mijn achter......achter.......achter klein kinderen:)
Maar eerst maar afwachten. Is het wat dan ga ik wel een module maken met 8mbyte arrays...:slight_smile: wel prijzig....

Je timmert aan de weg :wink: Kwam recentelijk ergens een thread tegen waar je naam genoemd werd; geen idee meer om welke library het gaat.

Van de week 2 x 32kByte I2C FRAM's binnen gekregen. Eerste testen zijn veel belovend:

--------------------------------------------------------------
Aantal records gemaakt ............ :        10 
Dit gebruikte ..................... :        40 bytes
Dit duurde ........................ :   0.00600 seconden
Het vullen duurde ................. :   0.00300 seconden
Het lezen duurde .................. :   0.00300 seconden
--------------------------------------------------------------
Aantal records gemaakt ............ :       100 
Dit gebruikte ..................... :       400 bytes
Dit duurde ........................ :   0.00900 seconden
Het vullen duurde ................. :   0.02900 seconden
Het lezen duurde .................. :   0.03200 seconden
--------------------------------------------------------------
Aantal records gemaakt ............ :      1000 
Dit gebruikte ..................... :      4000 bytes
Dit duurde ........................ :   0.02700 seconden
Het vullen duurde ................. :   0.28500 seconden
Het lezen duurde .................. :   0.31600 seconden
--------------------------------------------------------------
Aantal records gemaakt ............ :      4000 
Dit gebruikte ..................... :     16000 bytes
Dit duurde ........................ :   0.08800 seconden
Het vullen duurde ................. :   1.13900 seconden
Het lezen duurde .................. :   1.26300 seconden
--------------------------------------------------------------
Aantal records gemaakt ............ :      8000 
Dit gebruikte ..................... :     32000 bytes
Dit duurde ........................ :   0.16900 seconden
Het vullen duurde ................. :   2.27600 seconden
Het lezen duurde .................. :   2.52700 seconden
--------------------------------------------------------------

Deze werkt op I2C waarbij ik de max snelheid op 400kHz heb gezet,

de class wordt al een stuk complexer... als attachement

hierbij wordt gebruik gemaakt van een bestandje Memory.h waarin de configuratie wordt bepaald. Wel zie ik eea nog wel over de kop gaan voordat het helemaal rond is :slight_smile:

/*
    Name:       memory.h
    Created:    15-12-2018 08:41:43
    Author:     nico verduin
    Brief:      Defines the memory to be used for our array class
*/
#ifndef MEMORY_H_
#define MEMORY_H_

// select one and comment the rest out
//#define BUILTIN_RAM       // will use internal RAM memory
//#define SPI_FRAM          // will use F-RAM memory through SPI interface
#define I2C_FRAM            // will use F_RAM memory through I2C interface

// specific I2C F-RAM parameters
#ifdef I2C_FRAM
#define MB85RC_SLAVE_ID       (0xF8)

#endif
// Specific SPI F-RAM connections
#ifdef SPI_FRAM
#define PIN_FRAM_ENABLE     10         // chip select on pin 10
#define PIN_FRAM_ADDRESS_A0 9          // A0
#define PIN_FRAM_ADDRESS_A1 8          // A1
#define PIN_FRAM_ADDRESS_A2 7          // A2

#endif

#endif /* MEMORY_H_ */

Wat overigens opvalt is dat er erg veel tijd verloren gaat in de wire functies Wire.beginTransmission() en Wire.endTransmission(). Een tabel vullen waarbij de begin en end eenmalig worden uitgevoerd duurt veel en veel korter.

test programma

/*
 Name:       Ringbuffer.ino
 Created:    14-12-2018 08:41:43
 Author:     nico verduin
 Brief:      Eenvoudige manier om met een array  een ringbuffer te maken
 */

void printResult(const char * tekst1, const char * aantal,
    const char * tekst2);
void printStreep();
void convertToDoubleString(char * resultaatString, uint32_t aantal, uint32_t deelGetal);


// includes
#include "Array.h"        // Super Array class

// program defines

#define MAX_ARRAY_SIZE 8000
#define COLUMNS_PER_ROW 10
#define ARRAY_TYPE uint32_t

// global variables
// create an array of type uint32
Array<ARRAY_TYPE> myArray;

/*
 * @name setup()
 * @brief Initializes the program
 */
void setup() {
  uint32_t timer;
  uint32_t tijd;
  char werkveld[10];

  // initialiseer Serial
  Serial.begin(115200);

  Serial.println("\nTesten met I2C FRAM chip van adafruit\n\n");
  printStreep();

  // dummy init
  myArray.init(1, 0x50);
  uint32_t aantalEntries  = 10;
  uint32_t increment      = 10;
  while(aantalEntries *sizeof(ARRAY_TYPE) < myArray.getCapacity()) {

    // maak de array
    timer = millis();
    myArray.init(aantalEntries, 0x50);
    tijd = millis() - timer;

    sprintf(werkveld, "%9lu", aantalEntries);
    printResult("Aantal records gemaakt ", werkveld, "");

    sprintf(werkveld, "%9lu", aantalEntries * sizeof(ARRAY_TYPE));
    printResult("Dit gebruikte ", werkveld, "bytes");

    convertToDoubleString(werkveld, tijd , 1000UL);
    printResult("Dit duurde ", werkveld, "seconden");

    // vul de array
    timer = millis();
    for (uint32_t j = 0; j < aantalEntries; j++) {
      myArray.addEntry((ARRAY_TYPE) j);
    }
    tijd = millis() - timer;
    convertToDoubleString(werkveld, tijd , 1000UL);
    printResult("Het vullen duurde ", werkveld, "seconden");

    // lees de array
    timer = millis();
    for (uint32_t j = 0; j < aantalEntries; j++) {
      myArray.getEntry(j);
    }
    tijd = millis() - timer;
    convertToDoubleString(werkveld, tijd , 1000UL);
    printResult("Het lezen duurde ", werkveld, "seconden");
    printStreep();

    // verhoog aantal entries
    aantalEntries = aantalEntries + increment;
    if (aantalEntries == 100) {
      increment = 100;
    }
    if (aantalEntries == 1000) {
      increment = 1000;
    }
  }
}

// Add the main program code into the continuous loop() function
void loop() {
}

void printResult(const char * tekst1, const char * aantal, const char * tekst2) {
  char regel[100];
  char tekstVeld[36];
  strncpy(tekstVeld, tekst1, 35);

  // tekst opvullen met punten
  for (uint8_t i = strlen(tekstVeld); i < 36; i++) {
    tekstVeld[i] = '.';
  }
  tekstVeld[35] = '\0';
  sprintf(regel, "%35s : %9s %s\n", tekstVeld, aantal, tekst2);
  Serial.print(regel);
}

void printStreep() {
  for (uint8_t i = 0; i < 62; i++) {
    Serial.print("-");
  }
  Serial.println();
}

void convertToDoubleString(char * resultaatString, uint32_t aantal, uint32_t deelGetal){
  double resultaat = (double)aantal / double(deelGetal);
  dtostrf(resultaat, 9, 5, resultaatString);
}

suggesties voor performance verbeteringen zijn altijd welkom.

Array.h (11.6 KB)

Als doorbouwend merk je dat er dus veel meer potentie zit in de mogelijkheden van de SPI en I2C RAM ic's. Inmiddels is de hele architectuur alweer overhoop gegooid en is er een nieuw plaatje ontstaan:

De I2C memory controller is gereed. De Virtuele memory controller kan er al naar schrijven. Wat het geheel nu doet is:
Van elk IC bepalen hoeveel geheugen deze heeft. Alle geheugens worden vervolgens achter elkaar gezet om een groot virtueel geheugen te maken. Vanaf de gebruiker gezien maakt het niet uit wat voor IC's het zijn (F-RAM, I2C, SPI, SRAM etc).

Een schrijf operatie van een uint32_t duurt ongeveer 300usec dus dat valt mee. Er komt nog een malloc en een free functie op de virtuele memory controller zodat je geheugen uit de VM kan alloceren. Tja en dan kan je in feite het werk geheugen enorm uitbreiden. Er is een limiet echter. Elke virtuele variabele moet minimaal een 32bits adres opslaan in het lokale werk geheugen. En als je wat wil overpompen of ontvangen moet je wel ruimte reserveren om deze op te slaan.

Het beschikbare geheugen wordt in dit bestandje aangegeven:

/*
 Name:       MemoryConfiguration
 Created:    25-12-2018 10:45:00
 Author:     nico verduin
 Brief:      Defines the structure of all the memory chips used
 */

#ifndef MEMORYCONFIGURATION_H_
#define MEMORYCONFIGURATION_H_

// includes
#include <Arduino.h>

// our I2C chips. Make this static or the compiler will fail
static uint8_t I2CChipList[] = { 0x50, 0x51};

#endif /* MEMORYCONFIGURATION_H_ */

Wordt vervolgt....

Inmiddels een werkende Virtual Memory controller gereed. Werkt prima met I2C ic's. Volgende week krijg ik 2 x SPI 128kb x8 bit ic's binnen. Dan kan ik verder. Op dit moment werkt het met 2 x Adafruit 32kbx8bit FRAM modules.
Er zijn 2 classes.
a) I2CMemory controller:

I2CMemoryController.h

/*
 Name:       I2CChipController.h
 Created:    24-12-2018 12:36:00
 Author:     nico verduin
 Brief:      Controller to handle I2C FRAM IC's
 */


#ifndef I2CMEMORYCONTROLLER_H_
#define I2CMEMORYCONTROLLER_H_

// includes
#include <Arduino.h>
#include <Wire.h>

// opcodes used for reading and writing
enum {
  READ,
  WRITE
};

class I2CMemoryController {

protected:
  uint32_t  capacity;         // contains the capacity of chip memory
  char      thisI2CAddress;   // I2C address of the chip

  // functions
public:
  I2CMemoryController(uint8_t);
  void        init();
  void        processOperation(uint8_t , uint32_t , uint32_t, char *);
  uint32_t    getCapacity();
};

#endif /* I2C_CHIPCONTROLLER_H_ */

I2CMemoryController.cpp

/*
 Name:       I2CMemoryController.h
 Created:    24-12-2018 12:36:00
 Author:     nico verduin
 Brief:      Controller to handle I2C FRAM IC's
 */

#include "I2CMemoryController.h"


/**
 * @name I2CMemoryController
 * @param uint8_t I2C address of chip
 * @brief Constructor
 */
I2CMemoryController::I2CMemoryController(uint8_t address) {

  // save this chip's address
  thisI2CAddress = address;

  // initialize other parameters
  capacity  = 0;
}

/**
 * @name init
 * @brief Initializes the chip by determining how much memory the chip
 * contains.
 */
void I2CMemoryController::init() {

  // determine memory capacity of the chip
  // put a 0x00 on address 0
  Wire.beginTransmission(thisI2CAddress);
  Wire.write(0);
  Wire.write(0);
  Wire.write(0x00);
  Wire.endTransmission();

  // now put on every power of 2 address an 0xFF
  // we can handle a max of 2 GigaBytes
  uint32_t i = 1;

  // once i = 0 we have looped through 2 Gigabytes address space
  while (i != 0){

    // write our 0xFF
    Wire.beginTransmission(thisI2CAddress);
    Wire.write(i >> 8);
    Wire.write(i & 0xFF);
    Wire.write(0xFF);
    Wire.endTransmission();

    // assuming the memory capacity is a factor of 2^n, we will reach a loop
    // around to address 0. So if we have written a 0xFF on address 0x0000,
    // i contains the max capacity of the chip. And we are done searching for
    // the capacity
    Wire.beginTransmission(thisI2CAddress);
    Wire.write(0x00);
    Wire.write(0x00);
    Wire.endTransmission();

    // ask for our byte
    Wire.requestFrom(thisI2CAddress, 1, true);
    uint8_t result = Wire.read();

    // check if wrap around
    if (result == 0xFF) {

      // save capacity
      capacity = i;

      // if there is no chip, Wire returns a 0xFF. This will happen
      // on the first value of i (which is 1). Thus we need to
      // subtract 1 from the capacity to return 0 bytes capacity
      if (capacity == 1) {
        // no chip recognized on this address
        capacity = 0;
      }

      // loop is finished
      break;
    }

    // i * 2
    i  = i << 1;
  }
}


/**
 * processOperation
 * @param opcode operation type (READ or WRITE)
 * @param char * pointer to where data is read from or sent to
 * @brief reads or write a block from/to chip to/from local
 * memory
 */
void I2CMemoryController::processOperation( uint8_t   opcode,
                                            uint32_t addressPtr,
                                            uint32_t blockSize,
                                            char * ptr) {

  char bufferCtr   = 0;      // max 32 bytes counter

  // process the whole block within this chip
  while (blockSize != 0UL) {

    // if the buffer is empty we need to setup the chip
    if (bufferCtr == 0UL) {

      // setup start address
      Wire.beginTransmission(thisI2CAddress);

      // set our address within the chip
      Wire.write(addressPtr >> 8);
      Wire.write(addressPtr & 0xFF);

      // determine the size of our I2C buffer to use
      if (opcode == READ) {
        if (blockSize > BUFFER_LENGTH) {
          bufferCtr = BUFFER_LENGTH;
        } else {
          bufferCtr = blockSize;
        }
        // close transmission
        Wire.endTransmission();

        // ask for our bytes.
        Wire.requestFrom(thisI2CAddress, bufferCtr, true);
      }

      // for Writing we are already using 2 bytes of the buffer
      // for the memory address
      if (opcode == WRITE) {
        if (blockSize > (BUFFER_LENGTH - 2)) {
          bufferCtr = BUFFER_LENGTH - 2;
        } else {
          bufferCtr = blockSize;
        }
      }
    }

    // decrease total to transfer bytes in advance and increase
    // address Pointer
    blockSize   -= bufferCtr;
    addressPtr  += bufferCtr;

    // process bufferCnt number of bytes
    while (bufferCtr != 0UL){

      if (opcode == READ){
        if (Wire.available()) {
          *ptr = Wire.read();
          ptr++;
          bufferCtr--;
        }
      }
      if (opcode == WRITE){
        Wire.write(*ptr);
        ptr++;
        bufferCtr--;
      }
    }

    // I2C command at end of writing
    if (opcode == WRITE){
      // close transmission
      Wire.endTransmission();
    }
  }
}

/*
 * @name getCapacity
 * @return uint32_t capacity of the chip
 * @brief returns the chip memory capacity
 */
uint32_t I2CMemoryController::getCapacity() {
  return capacity;
}

VirtualMemoryController.h

/*
 Name:       VirtualMemoryController
 Created:    25-12-2018 10:45:00
 Author:     nico verduin
 Brief:      Controller to create one large virtual memory bank out of
             different memory chips using the MemoryControllers
 */
#ifndef VIRTUALMEMORYCONTROLLER_H_
#define VIRTUALMEMORYCONTROLLER_H_

// includes
#include "I2CMemoryController.h"
#include "MemoryConfiguration.h"

enum {
  I2C,
  SPI
};

struct VirtualMemoryBlock {
   uint32_t startAddress;           // start address of the chip
   uint32_t capacity;               // capacity of the chip
   uint8_t  memoryControllerIndex;  // subscript to actual memory controller
   uint8_t  interface;              // interface type
};


class VirtualMemoryController {
protected:
  I2CMemoryController ** myChips; // array of pointers to memory controllers
  VirtualMemoryBlock *  myMemory; // pointer array of pointers to memory blocks
  uint8_t totalChips;             // shows the total number of chips connected
  uint32_t startAddress;          // start address within chip
  int8_t  controller;             // controller to start in

public:
  VirtualMemoryController();
  virtual   ~VirtualMemoryController();
  uint32_t  getTotalCapacity();
  int8_t    writeBytes(char * ptr, const uint32_t address, const uint32_t size);
  int8_t    readBytes(char * ptr, const uint32_t address, const uint32_t size);
  int8_t    determineController(uint32_t);
};

#endif /* VIRTUALMEMORYCONTROLLER_H_ */

VirtualMemoryController.cpp

/*
 Name:       VirtualMemoryController
 Created:    25-12-2018 10:45:00
 Author:     nico verduin
 Brief:      Controller to create one large virtual memory bank out of
             different memory chips using the MemoryControllers
 */


#include "VirtualMemoryController.h"

//#define DEBUG

/**
 * @name VirtualMemoryController()
 * @brief Constructor
 */
VirtualMemoryController::VirtualMemoryController() {

  // general initialization
  totalChips  = 0;
  myChips     = NULL;
  myMemory  = NULL;


  // create our array I2CMemoryController pointers
  myChips = (I2CMemoryController **) malloc(sizeof(I2CMemoryController *) *
      sizeof(I2CChipList));

  // fill our array of memory chips
  for (uint8_t i = 0; i < sizeof(I2CChipList); i++){
    myChips[i] = new I2CMemoryController(I2CChipList[i]);
  }

  // initialize the controllers and get our memory capacities
  for (uint8_t i = 0; i < sizeof(I2CChipList); i++) {
    myChips[i]->init();
    uint32_t chipCapacity = myChips[i]->getCapacity();


    // only add actual connected chips
    if (chipCapacity != 0UL) {
      totalChips++;
    }
  }

  // TODO : Add SPI chips

  uint8_t currentBlock = 0;

  // now that we know the total number of chips connected let's create
  // our virtual memory array
  myMemory = (VirtualMemoryBlock *) malloc(sizeof(VirtualMemoryBlock) *
                                                              totalChips);

  // add our I2C memory blocks
  for (uint8_t i = 0; i < sizeof(I2CChipList); i++) {

    uint32_t chipCapacity = myChips[i]->getCapacity();

    // only if there is something to add
    if (chipCapacity != 0UL){

      // if this is our first entry
      if (currentBlock == 0) {
        myMemory[currentBlock].startAddress = 0UL;
      } else {
        myMemory[currentBlock].startAddress =
            myMemory[currentBlock-1].startAddress +
            myMemory[currentBlock-1].capacity;
      }

      // setup other block data
      myMemory[currentBlock].capacity               = chipCapacity;
      myMemory[currentBlock].memoryControllerIndex  = i;
      myMemory[currentBlock].interface              = I2C;
      currentBlock++;
    }
  }
}

/**
 * @name getTotalCapacity
 * @return uint32 Total capacity of our virtual memory
 * @brief calculates and returns the total capacity of our virtual memory
 */
uint32_t VirtualMemoryController::getTotalCapacity() {
  return (myMemory[totalChips - 1].startAddress +
      myMemory[totalChips - 1].capacity);
}

/**
 * @name writeBytes
 * @param char * to meory where to write from
 * @param uint32_t start address in virtual memory
 * @param uint32_t length of data
 * @brief writes data starting at the adress for the specified length from
 * the ptr location
 */
int8_t VirtualMemoryController::writeBytes( char * ptr,
                                            const uint32_t address,
                                            const uint32_t size) {

  // save our parameters
  char * charPtr        = ptr;
  uint32_t bytesToWrite = size;
  uint32_t addressPtr   = address;
  uint32_t start        = 0;
  uint32_t currentBlock = 0;

  // check if we get out of range
  if ((address + size) > getTotalCapacity()){
    return -1;
  }

  // get our start controller
  controller  = determineController(addressPtr);
  start       = startAddress;

  while(bytesToWrite != 0){

    // if we exceed the chips memory boundary we need to split up in smaller
    // parts to divide block over more chips
    if (start + bytesToWrite > myChips[controller]->getCapacity()) {
      currentBlock = myChips[controller]->getCapacity() - start;
    } else {
      currentBlock = bytesToWrite;
    }
   // process our steps
    myChips[controller]->processOperation(WRITE, start, currentBlock, charPtr);

    // update our parameters
    bytesToWrite  -= currentBlock;
    charPtr       += currentBlock;
    start         += currentBlock;

    // check if we have to go to a next controller
    if (bytesToWrite != 0UL) {
      // next controller
      controller++;

      // start at address 0
      start = 0UL;
    }
  }
  return 0;
}

/**
 * @name readBytes
 * @param char * to local memory where to write to
 * @param uint32_t start address in virtual memory
 * @param uint32_t length of data
 * @brief reads  data starting at the adress for the specified length to
 * the ptr location
 */
int8_t VirtualMemoryController::readBytes(char * ptr,
                                          const uint32_t address,
                                          const uint32_t size) {

  // save our parameters
  char * charPtr        = ptr;
  uint32_t bytesToRead  = size;
  uint32_t addressPtr   = address;
  uint32_t start        = 0;
  uint32_t currentBlock = 0;

  // check if we get out of range
  if ((address + size) > getTotalCapacity()){
    return -1;
  }

  // get our start controller
  controller  = determineController(addressPtr);
  start       = startAddress;     // was determined in determineController()

  while(bytesToRead != 0){

    // if we exceed the chips memory boundary we need to split up in smaller
    // parts to divide block over more chips

    if (start + bytesToRead > myChips[controller]->getCapacity()) {
      currentBlock = myChips[controller]->getCapacity() - start;
    } else {
      currentBlock = bytesToRead;
    }

    // process our steps
    myChips[controller]->processOperation(READ, start, currentBlock, charPtr);

    // update our parameters
    bytesToRead   -= currentBlock;
    charPtr       += currentBlock;
    start         += currentBlock;


    // check if we have to go to a next controller
    if (bytesToRead != 0) {
      // next controller
      controller++;

      // start at address 0
      start = 0UL;
    }
  }
  return 0;
}

/**
 * @name determineController
 * @param VM address
 * @return controller index or -1
 * @brief determines the controller where this memory is to  be accessed
 * within the Virtual memory
 */
int8_t VirtualMemoryController::determineController(uint32_t address) {

  uint8_t controller  = -1;      //return value
  uint32_t addressCount        = 0;

  // determine in which controller we have to start
  for (uint8_t i = 0; i < totalChips; i++) {

    // check if it fits in this range
    if (address < (addressCount +  myMemory[i].capacity)) {
      // found it
      startAddress = address - myMemory[i].startAddress;
      controller      = i;
      break;
    } else {
      // no go to the next one but keep incrementing total to subtract from
      // address
      addressCount += myMemory[i].capacity;
    }
  }

  return controller;

}


/**
 * @name VirtualMemoryController()
 * @brief Destructor
 */
VirtualMemoryController::~VirtualMemoryController() {
  // cleanup allocated memory
  for (uint8_t i = 0; i < sizeof(I2CChipList); i++){

    // first delete individual controllers
    free(myChips[i]);
  }

  // and the controller array
  free(myChips);
}

Testprogramma

/**
 * Test program for virtual memory controller
 * Nico Verduin
 * 29-12-2018
 */

// includes
#include "Arduino.h"
#include "VirtualMemoryController.h"

// create a virtual memory controller pointer
VirtualMemoryController * myMemory;

// suppose we will make an array of doubles
#define ARRAY_TYPE double

/**
 * @name setup()
 * @brief initializes the program
 */
void setup() {

  // open Serial
  Serial.begin(115200);

// if we are using I2C chips, initiate Wire
#ifdef I2CMEMORYCONTROLLER_H_
  Wire.begin();                 // start I2C.
  Wire.setClock(400000);        // set to max available I2C speed
#endif

  // create our virtual memory controller
  myMemory = new VirtualMemoryController;

  // display the total capacity
  Serial.print("Total virtual space is ");
  Serial.print(myMemory->getTotalCapacity());
  Serial.println(" bytes");

  // determine the max amount of entries in our memory block
  uint32_t totalEntries = myMemory->getTotalCapacity() / sizeof(ARRAY_TYPE);

  ARRAY_TYPE x;                 // single variable
  ARRAY_TYPE arrayWindow[10];   // array of entries

  // create an array of ARRAY_TYPE based on the maximum of our virtual storage
  for (uint32_t i = 0; i < totalEntries; i++) {
    x = (ARRAY_TYPE) i;         // cast i to our ARRAY_TYPE

    // parameters: Addres in local memory, address in virtual memory, size in bytes
    myMemory->writeBytes((char *) &x, i * sizeof(ARRAY_TYPE),
        sizeof(ARRAY_TYPE));
  }

  Serial.print("Total entries written is ");
  Serial.println(totalEntries);

  // print 10 random values from our virtual memory
  randomSeed(analogRead(0));
  for (uint8_t i = 0; i < 10; i++) {

    // get a random index
    uint32_t address = random(0, totalEntries);

    // read and print our data
    Serial.print("data at address : ");
    Serial.print(address);
    Serial.print(" = ");

    myMemory->readBytes((char *) &x, address * sizeof(ARRAY_TYPE),
        sizeof(ARRAY_TYPE));

    // x is now filled with our external memory value
    Serial.println(x);
  }

  // copy 10 values from our huge array into a local array. This improves
  // performance
  randomSeed(analogRead(0));
  // get a random index within our array
  uint32_t address = random(0, totalEntries);

  myMemory->readBytes((char *) &arrayWindow, address * sizeof(ARRAY_TYPE),
      sizeof(ARRAY_TYPE) * 10);

  for (uint8_t i = 0; i < 10; i++) {
    Serial.print("data at index : ");
    Serial.print(address + i);
    Serial.print(" = ");
    Serial.println(arrayWindow[i]);
  }



}

// The loop function is called in an endless loop
void loop() {
//Add your repeated code here
}

In dit voorbeeld ga ik eerst de tabel per waarde vullen en idem weer uitlezen.
Daarna gewoon 10 random waardes pakken.
Tenslotte een iets andere aanpak die wel een verbeterde performance geeft (het blijft allemaal millisecondes :grin:)
Hier heb ik een kleine tabel gemaakt in local RAM van 10 entries. Daarna kopieer ik in een keer 10 waardes in de tabel (het is tenslotte allemaal aaneengesloten geheugen) en ga vanuit lokaal werken.

Wordt vervolgt.

Nou het heeft even geduurd, maar de Proof of Concept (POC) werkt!!!!!
De hele software is herschreven omdat ik dacht dat daar toch een probleem in zat. Waar komt het op nier:
Ik gebruik nu SRAM ic's van 128KByte via de SPI bus. Echter ik wil hebben dat ik voor elke chip een aparte IO lijn nodig heb voor de Chip Select. Dus maak ik gebruik van een IO-Expander (8 bits). Ook via de SPI bus waarbij de /CS van de geheugen ic's via de IO Expander lopen.
Dus: Arduino selecteert chip via de IO Expander en heb ik in feite 8 lijnen over dezelfde /CS pin op deArduino.
Tot zover gaat het goed.
Het lezen en schrijven van de SRAM ic's gebeurt in zgn. Sequential mode. Dat betekent dat de memory counter van het ic automatisch een plek ophoogt als er gelezen wordt of geschreven wordt. Echter kom je voorbij de laatste locatie van de chip wordt deze counter automatisch op 0000 gezet.
Nog steeds een problemen.
Totdat je een deel van de data in de ene chip krijgt en de rest in de volgende. Dat gaat prima, maar in de vorige ic (waarbij de counter op 0000 werd gezet werden nog eens 3 extra adressen overschreven met rommel. Tenminste dat dacht ik.

De oorzaak:
Zolang een SRAM IC in Sequential mode zit en de /CS lijn is laag blijft deze gewoon de data doorschrijven.
Als ik een andere chip wil hebben trek ik de /CSPin van de expander laag en schrijf ik de nieuwe data naar de expander (3 bytes). Die schakelt de oude chip uit (/CS wordt hoog) en kiest de nieuwe.
Maar terwijl ik dus die 3 bytes naar die expander schrijf zit die SRAM chip nog gewoon door te luisteren.

De oplossing was vrij eenvoudig. De /CS lijn naar de expander aftappen en inverteren (gewoon met een BC547 in verzadiging) en dat signaal 'OR'en met de uitgang van de expander. Die uitgang gaat daan naar de RAMs.

Nu de pcb ontwerpen en laten maken. Hij loopt als een tierelier. 1K wegschrijven en lezen duurt ongeveer 8ms. Dus stel je maakt een RTOS dan kun je elke taak zijn eigen stuk werkgeheugen geven. Gewoon de oude wegschrijven en de nieuwe binnen halen.

Ook ga ik nog kijken of ik op deze manier meerdere SD kaarten kan koppelen aan een Arduino. Mogelijkheden te over.

Het huidige ontwerp kan straks 4 x 1Mb modules aan op een /CS pin van de Arduino.

Foto van het prototype

En dit moet het volgende week worden :grin: Dan komen de printen binnen.
![|500x350](http://www.verelec.com/tijdelijk/images/SRAM panel.jpg)

En werkt als een zonnetje. ![|375x500](http://www.verelec.com/tijdelijk/images/SRAM 1MB.JPG)