Implement functionality like this to external i2c EEPROMs
First I was to say thank you for your excellent work on this an many other libraries. I see myself as a "skilled amateur programmer" so I wonder; how can I learn to write so compact (and so hard to understand ;-) ) libraries like this? I mean, the super "high level" EEPROM library you've written compile Wil less than a kilobyte on the UNO. Hands down, I'm impressed.
But enough with the worshipping. Would it be possible to create a library like this to work with external i2c EEPROMs (24c32, 24c16, they exist from 128B to 2048kB)? IIRC they're also bye orientated. I think the whole community would benefit from a library like that! I'd love to be able to write something like this myself, but I doesn't got what it takes (yet!)
Hi,
Thanks for the kind words. Its always good to hear words of appreciation.
how can I learn to write so compact (and so hard to understand ;-) ) libraries like this?
Lots of practice and research :smile: This library uses a paradigm known as "generic programming" . As you can see in the code, actual defined types do not come into play until the very end of the file:
typedef EEWrap< char > int8_e; //char
typedef EEWrap< unsigned char > uint8_e; //unsigned char
//...
This means that basically the whole library, when used is based on the type it is 'templated' for. And this allows for the compiler to generate very specific code for the types used.
This is exploited in all the objects used by EEWrap like the EEMode struct. It will select a method of dealing with the EEPROM based on the size of the template type (EESingleByte & EEMultiByte). If you haven't dealt with templates or forms of generic programming other than to make a function accept different types, I can fully understand why the code looks hard to understand.
When making decisions in compile time, you can't use values to make decisions on how the code is compiled. You have to use types. As a quick example, here is a compile time if-statement:
/***
Select different types based on a condition, an 'if statement' for types.
The type Select<V,T,F>::Result is type T when V is true, otherwise it is set to type F.
***/
template< bool V, typename T, typename F > struct select{ typedef T type; };
template< typename T, typename F > struct select< false, T, F >{ typedef F type; };
This can be used for simple, to very complex decisions. For instance, based on some compile time value I can decide if a variable is going to be an int or float:
#define USE_FLOAT true
typedef typename select< USE_FLOAT, float, int >::type MyType;
MyType var; //Is a float if USE_FLOAT is true, otherise it is an int.
You could also use it to select what functionality a class by using select to determine which base the class uses. This is what EEWrap does.
struct LongDelay{
void doDelay(){ delay( 5000 ); }
};
struct ShortDelay{
void doDelay(){ delay( 100 ); }
};
//Maybe an AVR needs a shorter delay than an ARM due to processor speeds.
#ifdef __AVR__
#define USE_SHORT_DELAY true
#else
#define SHORT_DELAY false
#endif
struct Foo : select<SHORT_DELAY, ShortDelay, LongDelay >::type {
//doDelay is inherited
};
Foo f;
//...
void loop(){
f.doDelay();
}
If you use OOP regularly, you may notice the design is almost backwards. Rather than having the most derived class providing the unique code for that object, the derived class now is generic and its implementation is built by the bases it has inherited.
This can go further using CRTP to provide 'static polymorphism'. The concepts I have used is a very large topic and not something that can be learned in a day, but definitely something worth learning especially for micro controllers or scenarios that require very efficient code.
Feel free to ask more questions if you want me to clear other stuff up.
Would it be possible to create a library like this to work with external i2c EEPROMs (24c32, 24c16, they exist from 128B to 2048kB)?
Yes, it can be done quite easily. However it will not be as efficient as it stands. The size shouldn't be affected, but the speed will.
For each byte the address is effectively set and then the byte is read/written. The AVR EEPROM only requires a few cycles to set the registers so this is quite optimal. However external devices may take many cycles for the communication protocols. If a small overhead isn't an issue then the port is quite easy.
If the EEPROM supports setting an address, then writing a stream of consecutive bytes, an extension to the code will be needed to take advantage of this. Looking at the datasheet for the 24c32 it can do 32byte page writes.
I've been thinking of adding support for external EEPROMS as selectable modules, so over the weekend I can give certainly modify it to support a simple byte-write addition for the 24c16/24c32 chips. The page write feature may have to wait a bit as that might require a large change to the code.
Hi, this is exactly what I would like to have with external SPI SRAM. I'm testing now Arduino Nano with 23LC1024 SPI SRAM (128 KB):
//---- Wiring -----
// Arduino -- 23LC1024
// D13 <------> SCK
// D12 <------> MISO
// D11 <------> MOSI
// D10 <------> CS
// 5V <------> VCC
// 5V <------> HOLD
// 5V <-10K-> CS Resistor
// GND <------> Vss
#include <SPI.h>
//SRAM opcodes
#define RDSR 5
#define WRSR 1
#define READ 3
#define WRITE 2
uint8_t Spi23LC1024Read8(uint32_t address)
{
uint8_t read_byte;
PORTB &= ~(1 << PORTB2); // Set SPI_SS low
SPI.transfer(READ);
SPI.transfer((uint8_t)(address >> 16) & 0xff);
SPI.transfer((uint8_t)(address >> 8) & 0xff);
SPI.transfer((uint8_t)address);
read_byte = SPI.transfer(0x00);
PORTB |= (1 << PORTB2); // Set SPI_SS high
return read_byte;
}
void Spi23LC1024Write8(uint32_t address, uint8_t data_byte)
{
PORTB &= ~(1 << PORTB2); // Set SPI_SS low
SPI.transfer(WRITE);
SPI.transfer((uint8_t)(address >> 16) & 0xff);
SPI.transfer((uint8_t)(address >> 8) & 0xff);
SPI.transfer((uint8_t)address);
SPI.transfer(data_byte);
PORTB |= (1 << PORTB2); // Set SPI_SS high
}
...
Having access there through similar wrapper will be a killing feature.