Nibble aligned EEPROM routines?

Most of the things I need to store in EEPROM seem to be four bit (nibble) or twelve bit values, with a few bytes for good measure.

Storing these as eight and sixteen bits seems wasteful.

Has anyone implemented nibble oriented routines? I've googled, but failed

I'm thinking of a parallel set of functions that would simply use a nibble argument rather than a byte argument, so that I can store/retrieve from half byte boundaries.

I've written some routines to get the rough effect, but they're certainly not high quality yet.

hawk, would also love native 1 bit booleans and a native 4 bit unsigned int

As long as you stay with Arduino, you are stuck with 8-bit wide access to anything. Even if you change 4 bits of a byte, you can only read/write the whole 8 bits.

I guess you can go to a 4-bit wide microprocessor, if you can find one nowdays. If you can find an old Burroughs medium-system, it's memory was nibble accessed, unless you used even addresses, then you can access a byte or longer word. But they are hard to find and even harder to maintain.

Paul

I'd sure hate to run a sprinkler system off that . . . :astonished:

I'm just looking at memory efficiency, and how much sprinkler program I can have in the EEPROM. It's not crowded yet, but . . .

I'm thinking in terms of a function set that would just call the EEPROM routines if a full number of bytes on byte boundaries is specified, and would retrieve and manipulate for half bytes (e.g., to right one nibble, pull the byte, clear the other nibble, OR the new nibble in, and then write. For larger variables, it would assemble them from multiple reads. Etc.)

I'm seeing a single "program" being a byte with a nibble each for how many times the program runs, and another for how many sprinklers. Next is a byte whose bits indicate which days to run (or, an interval of days if bit 7 high). Another byte for runtime, and then each sprinkler then takes a nibble, and a byte for each start time (two bytes if the length indicates "hourly", or some such, with start and end periods).

So to run my four sprinkler banks four times a day is probably nine bytes, running all four every hour would take seven bytes. Five and a half bytes for the garden twice a day, four and a half for the trees daily or twice daily, and another five and a half for the wine/berry circuit.

Not a lot at the moment, but that goes up by about two-thirds using whole bytes to store nibbles. The EEPROM will also need to contain a key or two (I really want to send encrypted data so some yahoo can't just turn on my sprinklers . . .).

So this project would survive, but others might really be able to use the tighter storage.

I2C EEPROMs being so cheap and simple to integrate,
IMHO it does not make sense to squeeze the last bit out of the builtin EEPROM.

But, but . . . that hardly seems sporting . . .

Part of the appeal of these MCUs is pushing the hardware limit, and seeing how much I can do with how little . . . I actually started this by ordering a pi 0w, and realized that it was beyond overkill for this . . . [it will become a central unit, monitoring and herding the smaller autonomous parts.

And learning of hardware when I did, when it was tight, I just naturally try to scrimp bits (but I haven't worried about the wasted bit of every word when storing text on a PDP-10 for some years now . . )

But, hmm, $1.84 for five 24LC512 delivered . . . (a whopping 24 cents more than a set of ten 24LC64s . . . but what will I ever do with that much memory, Mr. Gates?)

Nothing stops you from implementing a routine that reads a byte from EEPROM, and then provides the high or low 4 bits from it, or one that writes a nybble by reading a byte, modifying the appropriate 4 bites, and then writing it again if it's different.

That's roughly my current implementation. But then I started wondering if I was reinventing the wheel.

It finally occurred to me that EEPROM.put() and .get() were quite similar to what I was doing, and began wondering if there was already an implementation.

You can pack your data in the most sophisticated way in a structure and then put and get the structure.

put uses update so only changed bytes will be written.

dochawk:
Storing these as eight and sixteen bits seems wasteful.

Then don’t…

https://www.google.com/search?q=c%2B%2B+bit+field

Just use bitfields:

struct foo
{
  uint8_t nibble_hi:4;
  uint8_t nibble_lo:4;
};

Wow.

Don't use a language for 20 years, and it's amazing what it pops up with :). (Jumping from Fortran 77 to 90/95 was another interesting experience . . .)

I'd never even heard of these before, though I'd suggested something along these lines should exist so many times over the years.

I don't suppose there's an automatic way to stack them in an array for referencing by an array index. I.e., if I made an array[4] of these foos, referring to nibbles 0-7?

But these are definitely going into my toolkit . . .

For most of the things I think of, the computational expense isn't that important, such as turning on a sprinkler for a couple of minutes, and then going to the next (although if I get around to that pipe dream of my own ignition for my 500 cubic inch Cadillac engine, timing will get tight [but I also understand that arduinos aren't appropriate for automotive use {fwiw, a spark would have to fire every other millisecond or so}]).

dochawk:
I don’t suppose there’s an automatic way to stack them in an array for referencing by an array index. I.e., if I made an array[4] of these foos, referring to nibbles 0-7?

No.

dochawk:
…but I also understand that arduinos aren’t appropriate for automotive use…

The board is not. The processor is.

Perhaps the built-in insert_bits could be put to work here.

A demo:

// from avrfreaks.net https://www.avrfreaks.net/comment/204080#comment-204080
// post #17

// https://gcc.gnu.org/onlinedocs/gcc/AVR-Built-in-Functions.html

void setup() {
  Serial.begin(115200);
  uint8_t argument = 0xc5;
  Serial.print(argument, BIN);
  Serial.print("\t");
  Serial.println(argument, HEX);
  // use AVR-provided function to reverse bits
  // Example: if map = 0xffff0123, upper four bits are unchanged,
  // bit order of lower four bits is reversed.
  // 2nd and 3rd function parameters can be different variables
  
  // swap nybbles
  argument = __builtin_avr_insert_bits (0x32107654, argument, argument);
  Serial.print(argument, BIN);
  Serial.print("\t\t");
  Serial.println(argument, HEX);
}

void loop() {}

With this you could manipulate nybbles. It could get convoluted.

That is indeed a nifty way to do it.

I’m currently doing things like

  if ( sprinklerNextNbl % 2 == 0) {
    //even nibble boundary.
    //first nibble will be the sprinkler, the second the high order nibble of the time, and the
    //  first nibble of the next byte the lower
    spr = EEPROM.read(sprinklerNextNbl / 2) / 16;
    if (spr = sprinklerCurrent) {
      //then this is the current sprinkler repeated, the end of this program
      //no runtime
      runtime = 0;
      //the second nibble goes unused, go to the next byte
      sprinklrNextNbl += 2;
      spr = 0xFF
    } else {
      //there really is another sprinkler
      runtime = 16 * (EEPROM.read(sprinklerNextNbl / 2)  % 16);
      //advance to next byte. this actually points to the second nible for next use
      sprinklrNextNbl += 3;
      runtime += (EEPROM.read(sprinklerNextNbl / 2)  / 16) ;
    }

  } else {
    //odd nibble boundary
    // second nibble will be sprinkler, and the next byte the runtime
    spr = EEPROM.read(sprinklerNextNbl / 2) % 16;
    if (spr = sprinkerCurrent) {
      //then this is the current sprinkler repeated, the end of this program
      //no runtime
      runtime = 0;

    } else {
      //there really is another sprinkler

      if (runTime < 240) then {
        //a normal runtime.

        // note that 240, second midnight, is prohibitted.


      } else {
        //a repeating runtime

        byte runEnd
      }
    }
  }

That is, checking for odd/even nibble address, and dividing by 16 if i need the upper nibble, and using mod sixteen for the lower nibble. (this code actually has some termination checking that needs to remove. I initially used a repeated sprinkler to indicate the end of sequence, but I don’t have to be that clever, as it doesn’t cost any more to use a dedicated nibble for how many).

OK, that’s intriguing . . . and to wander completely off topic: 6000rpm is an easy upper limit–a caddy big block can’t hit that even with performance (and would be traveling 170mph if it did . . .) and also divides easily.

So 100rps, and therefore 36,000 degrees/second. If .1 degree is the tightest that the spark timing can be set manually, that’s to within 1/360,000 of a second (about 2.7 microseconds), or about 70 processor cycles, with another plug coming in 45 degrees, or 45/36,000 or 1.25 millisecond, during which time there is a valve to open and another to close, and a fuel injector to squirt, though not with quite as much timing precision. Also, the spark and fuel only happen half the time. Sounds conceivably doable . . . (a company [megasquirt?] has implemented this on a 68k based mcu)

dochawk:
I don’t suppose there’s an automatic way to stack them in an array for referencing by an array index. I.e., if I made an array[4] of these foos, referring to nibbles 0-7?

Create a class, and use getter and setter methods to translate whatever values you want into whatever bitfields you want. EEPROM.get and EEPROM.put are template methods, so they’ll work for literally anything that does not use dynamic allocation.

For an array, if you want to seriously class it up, create a template class and overload the operator to return a class that can get or set the bitfield value from a byte array. I threw this together in about 10 minutes and it is completely untested and definitely not as clean as it could be. Use at your own risk/amusement.

template<uint8_t BITSIZE>
class BitFieldRef
{
public:
 const uint8_t MASK = ((0x1<<BITSIZE)-1);
 BitFieldRef( uint8_t& byte_ref, uint8_t off ) : offset(off), byte_pointer(&byte_ref) {}
 operator uint8_t() const {return (*byte_pointer >> offset) & MASK;}
 BitFieldRef<BITSIZE>& operator=(const uint8_t& value)
 {
 uint8_t temp = *byte_pointer;
 temp &= ~(MASK<<offset);
 temp |= (value&MASK)<<offset;
 *byte_pointer = temp;
 
 return *this;
 }
 
private:
 size_t offset;
 uint8_t* byte_pointer;
};

template<size_t BITSIZE, size_t ELEMENTS>
class
{
public:
 const uint8_t ELEMENTS_PER_BYTE = 8/BITSIZE;
 const size_t BYTE_AMOUNT = ELEMENTS / ELEMENTS_PER_BYTE;
 
 BitFieldRef<BITSIZE> operator[](size_t idx)
 {
 uint8_t arrIdx = idx / ELEMENTS_PER_BYTE;
 uint8_t offset = (idx % ELEMENTS_PER_BYTE) * BITSIZE;
 return BitFieldRef<BITSIZE>(dataArray[arrIdx], offset);
 }
private:
 uint8_t dataArray[BYTE_AMOUNT];
};

dochawk:
Wow.

Don't use a language for 20 years, and it's amazing what it pops up with :). (Jumping from Fortran 77 to 90/95 was another interesting experience . . .)

I'd never even heard of these before, though I'd suggested something along these lines should exist so many times over the years.

Bitfields have been there since the '70s.