AT24C256 EEProm Module (take two)
You could be forgiven for having a feeling of Déjà vu as I drag out the old AT24C256 module again for this blog entry. The origin of my interest in this component is the idea of either data logging (e.g. temperature, humidity, water level) or reading large amounts of data (e.g. music, voice, text) where the data is processed by a smaller chip that lacks memory (such as the Attiny85 I'll be using this week).
So last week I rather flippantly wrote "next week I think I'll look at using a small memory attiny chip to play some music using the external memory of this EEProm chip", thinking that this blog entry should be a natural and easy extension of last week when I wrote and retrieved single characters to and from the EEProm.
Not so!
Deciding to use the tone library to make music, it soon became evident that the data I wrote to the EEProm was coming back as weird noise. The problem was that the data was writing as single bytes (0-255), but I need integer values (0 to 65535) to represent frequencies audible to the human ear. So each note (and it's duration) needed two EEProm addresses.
I was futzing about with the code and having a few head scratching moments, but as usual a good sleep and a bit of spreadsheet magic helped the thought processes. I wanted to write two different songs on two EEProm modules using a beefy Arduino Uno and then swap them in and out to an Attiny85 doing the reading and playing. The "songs" as notes and durations look something like:
// imperial march const int song[] PROGMEM = { 17, 2, 440, 2, 17, 2, 440, 2, 17, 2,
440, 2, 17, 2, 349, 2, 523, 2, 440, 2,
17, 2, 349, 2, 523, 2, 440, 8, 17, 2, 17, 8 };
// random notes const int song[] PROGMEM = { 218, 4, 559, 3, 17, 2, 459, 4, 17, 2, 342, 3, 302, 4, 893, 2, 17, 2, 952,3,
779, 4, 17, 2, 992, 2, 285, 2, 17,2, // ...etc... 657, 2, 17, 2, 535, 4, 17, 2, 862, 2, 744, 2, 417, 3, 17,2, 615, 3, 17, 2 };
The appearance of the {17, 2} pair signals the player to "play" a pause. The critical piece of code splits each note (and duration) into a upper and lower byte of a 16 byte integer as shown by this spreadsheet snippet.
As each integer is written (or read) the EEProm address is incremented and the integer split (or combined) as follows.
Writing the song:
void writeonce() { int eepromaddress; for (int thisone = 0; thisone < sizeofarray; thisone++) { int thisnumber = pgm_read_word_near(&song[thisone]); eepromaddress = thisone * 2; writeEEPROM(ext_eeprom, eepromaddress, highByte(thisnumber)); writeEEPROM(ext_eeprom, eepromaddress + 1, lowByte(thisnumber)); } }
Reading the song:
void readonce() { int eepromaddress, mynote, myduration; for (int thisone = 0; thisone < 511; thisone++) { int mynote, myduration; byte readhigh, readlow; eepromaddress = thisone * 2; readhigh = readEEPROM(ext_eeprom, eepromaddress); readlow = readEEPROM(ext_eeprom, eepromaddress + 1); mynote = word(readhigh,readlow); thisone++; //duration eepromaddress = thisone * 2; readhigh = readEEPROM(ext_eeprom, eepromaddress); readlow = readEEPROM(ext_eeprom, eepromaddress + 1); myduration = word(readhigh,readlow)*tempo; } }
The entire code for writing the song can be found here, and for reading and then playing the song here.
So here is a picture of the final circuit showing the USBASP programmer on the right connected to the lovely development board from Łukasz and then onto a small blue breadboard with the EEProm module, speaker and a smoothing capacitor. I'm not sure why there is a smoothing capacitor as the music quality is not really an issue when the notes are a bit random!
And finally let the noise begin, here is the "music" being played live.