Запись 9-битных значений в массив байтов (или EEPROM) без потери оставшегося бита в следующем байте

Я не смог найти ответ в Google, поэтому я пошел на это и запрограммировал довольно много часов.

Я хочу сохранить 9-битные значения в eeprom, не теряя остальные 7 бит. Я сохраняю значения до 500, и у меня осталось не так много EEPROM.

Тот же принцип можно применить и к массивам, что я и сделал, чтобы не перегружать EEPROM.

Итак, я сделал эту небольшую программу:

/*
 * Write only a certain number of bits to EEPROM.
 *
 * keeps the other bit in the byte of the eeprom as they are.
 *
 *  Working version with 9 bits:
 *          2019-10-03 15:57
 *          2019-10-03 22:09 tested with chars too
 *          2019-10-04 08:25 works with 7 bit chars also!
 *          2019-10-04 12:27 fixed the combining of oldByte and new values in writeBitsToEEPROM(), because chars like 'ö' altered previous bit (left side) that should not have been altered.
 *
 */


#include "arduino.h"
#include "EEPROM.h"
#include "math.h"


#define BIT_BLOCKS_COUNT 15
#define BLOCK_BYTE_COUNT 17



#define ARRAY_SIZE BLOCK_BYTE_COUNT+2

//TODO: change back to original value
#define EEPROM_SIZE ARRAY_SIZE

byte fakeEEPROM[ARRAY_SIZE] = {0};


String byteToString(byte value){
  char byteChar[9];
  byteChar[8] = '\0'; //we need a terminator

  for(int i=7; i>=0; i--){
    byteChar[7-i] = (value & (1 << i)) ? '1' : '0';
  }

  return String(byteChar);
}


String byteToString(unsigned long value, byte bytesToRead){
    String str1 = byteToString(value >> 8);
    String str2 = byteToString(value & 0xFF);

    return str1 + " " + str2;
}


int globBlockStartAdress = 0;
byte globNumberOfBits = 0;
int globBlockSizeBytes = 0;

bool initBitBlock(int blockStartAdress, int blockCount, byte numberOfBits) {

    globBlockStartAdress = blockStartAdress;
    globNumberOfBits = numberOfBits;

    // calc needed number of bytes and roud up
    int tempBlockSize = blockCount * numberOfBits / 8;
    if(blockCount * numberOfBits % 8)
        tempBlockSize++;

    // make number of bytes even
    if(tempBlockSize % 2)
        tempBlockSize++;

    globBlockSizeBytes = tempBlockSize;

    if(blockStartAdress + globBlockSizeBytes > EEPROM_SIZE)
        return false;

    return true;
}

/*
 * Writes 1 to 9 bits to "internalAdress" within a designated block in eeprom
 */
void writeBitsToEEPROM(unsigned int bitsToBeWritten, int internalAdress){

    //TODO: check if value is not higher than what can be stored
//  if(bitsToBeWritten){
//
//  }

    int trueEEPROMAdress = globBlockStartAdress + internalAdress * globNumberOfBits / 8;

    if(trueEEPROMAdress + 1 >= ARRAY_SIZE || internalAdress * globNumberOfBits / 8 >= globBlockSizeBytes){
        Serial.print("globBlockSizeBytes: ");
        Serial.println(globBlockSizeBytes);

        Serial.println("FEHLER writeBitsToEEPROMWTF: ");
        Serial.println(trueEEPROMAdress + 1);
        Serial.println(internalAdress * globNumberOfBits / 8 );

    }

    byte startBitOfEEPROMByte = (internalAdress * globNumberOfBits) % 8;

    unsigned int oldIntFromEEPROM = (fakeEEPROM[trueEEPROMAdress] << 8) | fakeEEPROM[trueEEPROMAdress + 1];

    //Todo: change to eeprom
    //filter out only the bits that need to be kept.
    //EEPROM.get(trueEEPROMAdress, oldEEPROMByteBits);

    // there might be bits in the byte that we dont want to change. left side and right side
    unsigned int mask1KeepFromEEPROM = (0xFFFF <<  (16 - startBitOfEEPROMByte));
    unsigned int mask2KeepFromEEPROM = (0xFFFF >>  (startBitOfEEPROMByte + globNumberOfBits));

    //if(16 - startBitOfEEPROMByte - numberOfBits > 0)
    //mask2KeepFromEEPROM= (0xFFFF >>  (startBitOfEEPROMByte + numberOfBits));

    // masks combined
    unsigned int maskIntToKeepFromEEPROM = mask1KeepFromEEPROM | mask2KeepFromEEPROM;


    int newEEPROMInt = (oldIntFromEEPROM & maskIntToKeepFromEEPROM) | ((bitsToBeWritten << (16 - globNumberOfBits - startBitOfEEPROMByte) & ~maskIntToKeepFromEEPROM));


    //Todo: change to eeprom
    //write
    //EEPROM.update(trueEEPROMAdress, newEEPROMByteBitsA);
    fakeEEPROM[trueEEPROMAdress] =      (newEEPROMInt >> 8);
    fakeEEPROM[trueEEPROMAdress + 1] =  (byte) newEEPROMInt;

    if(trueEEPROMAdress + 1 > BLOCK_BYTE_COUNT){
        Serial.println("FEHLER writeBitsToEEPROM");
        Serial.println(trueEEPROMAdress + 1);

        Serial.println("blockStartAdress");
        Serial.println(globBlockStartAdress);

        Serial.println("internalAdress");
        Serial.println(internalAdress);

        Serial.println("numberOfBits");
        Serial.println(globNumberOfBits);
    }


//  Serial.print("trueEEPROMAdress: ");
//  Serial.println(trueEEPROMAdress);
//
//  Serial.print("internalAdress: ");
//  Serial.println(internalAdress);
//
//  Serial.print("globNumberOfBits: ");
//  Serial.println(globNumberOfBits);
//
//  Serial.print("bitsToBeWritten:         ");
//  Serial.println(byteToString(bitsToBeWritten,2));
//
//  Serial.print(" mask1KeepFromEEPROM:    ");
//  Serial.println(byteToString(mask1KeepFromEEPROM,2));
//
//  Serial.print("mask2KeepFromEEPROM:     ");
//  Serial.println(byteToString(mask2KeepFromEEPROM,2));
//
//  Serial.print("maskIntToKeepFromEEPROM: ");
//  Serial.println(byteToString(maskIntToKeepFromEEPROM,2));
//
//  Serial.print("oldIntFromEEPROM:        ");
//  Serial.println(byteToString(oldIntFromEEPROM,2));
//
//  Serial.print("newEEPROMInt:            ");
//  Serial.println(byteToString(newEEPROMInt,2));
//
//  Serial.print("512:                     ");
//  Serial.println(byteToString(512, 2));
//
//  Serial.print("65535:                   ");
//  Serial.println(byteToString(65535, 2));
}


unsigned int ReadBitsFromEEPROM(int internalAdress){

    int trueEEPROMAdress = globBlockStartAdress + internalAdress * globNumberOfBits / 8;
    byte startBitOfEEPROMByte = (internalAdress * globNumberOfBits) % 8;

    if(trueEEPROMAdress + 1 > BLOCK_BYTE_COUNT)
        Serial.println("FEHLER readBits");
    unsigned int oldIntFromEEPROM = (fakeEEPROM[trueEEPROMAdress] << 8) | fakeEEPROM[trueEEPROMAdress + 1];

    //Todo: change to eeprom
    //filter out only the bits that need to be kept.
    //EEPROM.get(trueEEPROMAdress, oldEEPROMByteBits);

    unsigned int mask1KeepFromEEPROM = (0xFFFF <<  (16 - startBitOfEEPROMByte));
    unsigned int mask2KeepFromEEPROM = (0xFFFF >>  (startBitOfEEPROMByte + globNumberOfBits));

    unsigned int maskIntToKeepFromEEPROM = mask1KeepFromEEPROM | mask2KeepFromEEPROM;

    unsigned int valueFromEEPROM = ~maskIntToKeepFromEEPROM & oldIntFromEEPROM;


//  Serial.print("trueEEPROMAdress: ");
//  Serial.println(trueEEPROMAdress);
//
//  Serial.print("internalAdress: ");
//  Serial.println(internalAdress);
//
//  Serial.print("numberOfBits: ");
//  Serial.println(numberOfBits);
//
//  Serial.print(" mask1KeepFromEEPROM:    ");
//  Serial.println(byteToString(mask1KeepFromEEPROM,2));
//
//  Serial.print("mask2KeepFromEEPROM:     ");
//  Serial.println(byteToString(mask2KeepFromEEPROM,2));
////
//  Serial.print("maskIntToKeepFromEEPROM: ");
//  Serial.println(byteToString(maskIntToKeepFromEEPROM,2));
////
//  Serial.print("oldIntFromEEPROM:        ");
//  Serial.println(byteToString(oldIntFromEEPROM,2));

    return (valueFromEEPROM >> (16 - globNumberOfBits - startBitOfEEPROMByte));
}



void setup() {
    Serial.begin(57600);
    Serial.print(F("\n# Programversion: "));

    Serial.print(__TIME__);
    Serial.print(" ");
    Serial.println(__DATE__);



    Serial.println("Setup finished");
    delay(1000);
}


void printEEPROM(){

    for(int i = 0; i < ARRAY_SIZE; i++){
        byte b;

        //Todo: change to eeprom
        //EEPROM.get(i, b);
        b = fakeEEPROM[i];

        Serial.print(byteToString(b));
        Serial.print(" ");
    }
    Serial.println();
}


void testNumbers() {

    Serial.println("bits?");
    while( ! Serial.available());
    String input = Serial.readString();

    unsigned int value = input.toInt();


    initBitBlock(1, 15, 9);

    //  Serial.print("value:              ");
    //  Serial.println(byteToString(value));



    for(int i = 0; i < BIT_BLOCKS_COUNT;i++){

        for(int j = 0; j < BLOCK_BYTE_COUNT; j++){
            fakeEEPROM[j] = 0xFF;
            if(j > BLOCK_BYTE_COUNT)
                Serial.println("FEHLER testNumbers");
        }

//      Serial.print("EEPROM before: ");
//      printEEPROM();

        writeBitsToEEPROM(value, i);
        Serial.print("Returned: ");
        Serial.println(ReadBitsFromEEPROM(i));

//      Serial.print("EEPROM after:  ");
//      printEEPROM();
//      Serial.println();
    }
    delay(1000);
}


#define CHAR_COUNT 16


void testChars() {

//  Serial.println("bits?");
//  while( ! Serial.available());
//  String input = Serial.readString();
//
//  unsigned int value = input.toInt();

    initBitBlock(1, CHAR_COUNT, 7);

    Serial.println("string?");
    while( ! Serial.available());
    String input = Serial.readString();

    Serial.println(input);

    char testString[CHAR_COUNT] = {'\0'};

    input.toCharArray(testString, CHAR_COUNT, 0);

    for(int j = 0; j < ARRAY_SIZE; j++){
            fakeEEPROM[j] = 0;//xFF;
        }


    for(int i = 0; i < CHAR_COUNT; i++){



        Serial.print("EEPROM before: ");
        printEEPROM();

        writeBitsToEEPROM(testString[i], i);


        Serial.print("EEPROM after:  ");
        printEEPROM();
        Serial.println();
    }


    Serial.println("Returned: ");
    for(int i = 0; i < CHAR_COUNT; i++){


        Serial.print((char) ReadBitsFromEEPROM(i));

    }

    Serial.println();


    delay(1000);

}

void loop(){
    testChars();
    testNumbers();

}


что, конечно, не полное. Это просто для сохранения этих 9-битных значений.

Мой вопрос: кто-нибудь еще запрограммировал подобную функцию - или знает, где ее найти - которая не ограничена 9 битами (10 бит будут занимать более 3 байтов)?


person Tobey66    schedule 03.10.2019    source источник
comment
Мне трудно понять, о чем вы спрашиваете, но я сомневаюсь, что ваша EEPROM имеет битовую адресацию, поэтому записано 1 или 8 бит - это по крайней мере потеряет байт.   -  person Eugene Sh.    schedule 03.10.2019
comment
Большинство EEPROM, о которых я знаю, запрограммированы в 32-битных фрагментах, некоторые даже в 64-битных фрагментах. Вы, конечно, можете нарезать их как угодно, маскируя и сдвигая нужные вам биты, но вам все равно придется программировать их по мере их выравнивания (в частности, чтобы изменить значение, вы должны сначала прочитать слово целиком, стереть целое слово, затем перепрограммируйте его, даже если изменен только один бит).   -  person Lee Daniel Crocker    schedule 03.10.2019
comment
Буферизируйте свой вывод в ОЗУ, а затем запишите всю информацию сразу в аппаратную EEPROM N-байт за раз, насколько позволяет ваше аппаратное обеспечение. При последней записи вам придется добавить несколько дополнительных нулевых битов, чтобы дополнить предел записи. Не пишите кучу маленьких кусков повсюду. Это неэффективно, а также увеличивает срок службы EEPROM. Нам постоянно приходилось делать подобные вещи на черно-белом Gameboy для сохранения игровых данных.   -  person Michael Dorgan    schedule 03.10.2019
comment
Облом по поводу вашей проблемы с вставкой. Я не могу отследить вашу ссылку, поэтому, боюсь, нам не повезло.   -  person Steve Summit    schedule 03.10.2019
comment
Вы также можете использовать битовые поля внутри структур для упрощения и/или математики для выравнивания ваших битов. Затем вы можете просто вывести всю структуру сразу.   -  person Michael Dorgan    schedule 03.10.2019
comment
Оптимальное решение может зависеть от того, как вам нужно обновить EEPROM, от размера EEPROM и от того, сколько оперативной памяти у вас есть. Вам, например, нужно написать произвольный доступ или все значения записываются последовательно одновременно?   -  person Clifford    schedule 04.10.2019


Ответы (2)


Эта функция должна брать количество битов, заданное bitsPerVal, из каждого значения во входном массиве pVals, и упаковывать их в массив байтов, на который указывает pOutBytes:

#include <stdint.h>

void pack_bits(uint32_t *pVals, size_t numVals, int bitsPerVal, uint8_t *pOutBytes)
{
    uint32_t mask = ~(UINT32_MAX << bitsPerVal);
    int outBitsLeft = 8;
    int inBitsLeft = bitsPerVal;

    while(numVals > 0)
    {
        if(inBitsLeft > outBitsLeft)
        {
            inBitsLeft -= outBitsLeft;
            *pOutBytes |= (*pVals & mask) >> inBitsLeft; 
            mask >>= outBitsLeft;
            outBitsLeft = 0;
        }
        else
        {
            outBitsLeft -= inBitsLeft;
            *pOutBytes |= (*pVals & mask) << outBitsLeft;
            mask = ~(UINT32_MAX << bitsPerVal);
            inBitsLeft = bitsPerVal;
            --numVals;
            ++pVals;
        }

        if(0 == outBitsLeft)
        {
            outBitsLeft = 8;
            ++pOutBytes;
        }
    }
}

Массив, на который указывает pOutBytes, должен иметь подходящий размер (т.е. ((numVals*bitsPerVal) + 7) / 8) и инициализироваться нулем перед вызовом. Вы можете записать его в свою EEPROM после.

Надеюсь, это работает хорошо, хотя я провел много тестов на нем.

person Graeme    schedule 03.10.2019

Вот пример того, как 10 бит (на самом деле 16 бит при записи...) из 2 разных полей могут быть записаны в 16 бит вывода.

struct EEPROM_Output 
{
 uint16_t a   : 9;  // 0 - 511 can be stored here
 uint16_t b   : 1;  // 0 or 1 here.
 uint16_t pad : 6;  // Future use - we place this here to make it obvious that there are bits remaining.
};

void foo()
{
    struct EEPROM_Output save;
    save.a = 100;
    save.b = 1;

    WriteToEEPROM(&save, sizeof(save));
}
person Michael Dorgan    schedule 03.10.2019
comment
Но как я могу связать больше 9-битных значений подряд в EEPROM или массив с этим? - person Tobey66; 05.10.2019