Created
October 27, 2017 02:59
-
-
Save anonymous/b197c8b70bb9482c9bc5ff8511f9bf89 to your computer and use it in GitHub Desktop.
Arduino UNO R3 code for AK4490 DAC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <LiquidCrystal.h> | |
#include <rotary.h> | |
#include <Wire.h> | |
#include <EEPROM.h> | |
#include <LcdBarGraph.h> | |
/* | |
This sketch uses the rotary encoder handler written by Ben Buxton. | |
At this time, the code can be downloaded from https://github.com/brianlow/Rotary | |
Also it uses LcdBarGraph by Balazs Kelemen available at this time from: | |
http://www.arduinolibraries.info/libraries/lcd-bar-graph | |
The rotary shaft encoder similar to the one I used is described at: | |
https://wemakethings.net/2014/05/26/rotary-encoder-teardown/ | |
If you search aliexpress.com for "optical rotary encoder 600" you should be able | |
to find a source for one. | |
I opened up the encoder and bypassed the 5V regulator so that I could operat it | |
directly from a 5V supply. | |
I used a level shifter between the AKM4490 board and the Arduino Uno as | |
described here: | |
http://joe-ideas.blogspot.com/2013/09/bidirectional-33v-5v-evel-shifter.html | |
*/ | |
/* | |
LCD connections: | |
* LCD RS pin to digital pin 12 | |
* LCD Enable pin to digital pin 11 | |
* LCD D4 pin to digital pin 5 | |
* LCD D5 pin to digital pin 4 | |
* LCD D6 pin to digital pin 3 | |
* LCD D7 pin to digital pin 2 | |
* LCD R/W pin to ground | |
* 10K resistor: | |
* ends to +5V and ground | |
* wiper to LCD VO pin (pin 3) | |
*/ | |
/* persistence: | |
The EEPROM in the Arduino's microcontroller will be used to store the current volume. Since a memory address | |
in the EEPROM is guaranteed for a maximum of 100,000 erase/write cycles, we will use the entire kilobyte (Arduino | |
UNO) as a circular buffer to minimize the number of cycles at any one location. And, since the value of the | |
volume will never be 0 by design, we will erase the entire EEPROM to all 0 values one time using a separate | |
sketch before flashing this program into the Arduino's memory. | |
On startup of this program, we will look for the first byte in the EEPROM that differs from 0. That is the | |
current value of the volume, and the location of that byte is the current read/write location in the circular | |
buffer. When the time comes to write a new volume value, the byte at the current location will be erased to | |
0xff, the location will be incremented (or wrapped around to 0 again) and the new value will be written to the | |
new location. | |
If the EEPROM ever wears out, I will just install a new microcontroller chip or buy a new Arduino board to | |
replace it. We need to protect ourselves from the worn-out case, though. One way to do that might be to read | |
back a byte that was written and compare it to the value that was intended to be written. If they're different, | |
the EEPROM has failed. After a failure such as this, I think the best thing might be to set the volume to the | |
last known good value and start flashing the Arduino's built-in LED to signal failure. As the user, I should | |
notice that volume adjustments are no longer possible, and I should look at the Arduino's LED to see if it is | |
flashing. | |
I calculate the EEPROM will wear out in approximately 100 years with 1 minute updates, but my calcs are | |
probably wrong. | |
*/ | |
//#define DEBUG true | |
#define LCD_ROWS 4 | |
#define LCD_COLS 20 | |
#define VOL_MIN 1 // 0 is used as the EEPROM escape character; 0 value mutes the AK4490, and there are other ways to do that | |
#define VOL_MAX 0xff | |
#define RotaryDecimationFactor 10 // pick a value that gives you the attenuation change rate you like when you turn the rotary encoder | |
#define AK4490_0_CHIP_ADDRESS 0x12 // CAD0 and CAD1 == 'L' | |
#define CONTROL1_REGISTER 0 | |
#define LEFT_VOL_REGISTER 3 | |
#define RIGHT_VOL_REGISTER 4 | |
#define EEPROM_SIZE 1024 /* the Arduino Uno has 1KB of EEPROM */ | |
#define EEPROM_ERASE_BYTE 0 // use this value as an escape character in the EEPROM | |
#define WRITE_VOL_DELAY 60000 /* delay 1 minute (60000 ms) after volume change before sending value to EEPROM */ | |
// initialize the library with the numbers of the interface pins | |
LiquidCrystal lcd(12, 11, 10, 9, 8, 7); | |
LcdBarGraph *lbg; | |
Rotary RotaryEncoder = Rotary(2, 3); /* The pins the rotary encoder uses must be interrupt pins. */ | |
volatile byte CurrentVolume = VOL_MIN; // declared volatile because ISR changes value | |
byte PreviousVolume = VOL_MAX; | |
volatile byte RotaryCount = 0; // declared volatile because ISR changes value | |
int CurrentBufferPosition = 0; | |
boolean TriggerVolumePersist = false; | |
boolean EEPROMMalfunction = false; | |
unsigned long TriggerStartMS = 0L; /* persistence delay start time in milliseconds */ | |
ISR(PCINT2_vect) | |
{ | |
unsigned char result = RotaryEncoder.process(); | |
if (result) | |
{ | |
if(RotaryCount < RotaryDecimationFactor) | |
{ | |
++RotaryCount; | |
if(RotaryCount >= RotaryDecimationFactor) | |
{ | |
if(result == DIR_CW) | |
{ | |
if(CurrentVolume < VOL_MAX) | |
{ | |
CurrentVolume++; | |
} | |
} | |
else | |
{ | |
if(CurrentVolume > VOL_MIN) | |
{ | |
CurrentVolume--; | |
} | |
} | |
RotaryCount = 0; | |
} | |
} | |
} | |
} | |
byte haveAK4490() | |
{ | |
Wire.beginTransmission(AK4490_0_CHIP_ADDRESS); | |
byte Error = Wire.endTransmission(); | |
if (Error == 0) | |
{ | |
return true; | |
} | |
return false; | |
} | |
uint8_t readI2C(uint8_t devAddr, uint8_t regAddr) | |
{ | |
Wire.beginTransmission(devAddr); | |
Wire.write(regAddr); // Queues the address of the register | |
Wire.endTransmission(); // Sends the address of the register | |
Wire.requestFrom(devAddr, (uint8_t)1); // request one byte from address | |
if(Wire.available()) // Wire.available indicates if data is available | |
{ | |
return Wire.read(); // Wire.read() reads the data on the wire | |
} | |
return 0; // If no data in the wire, then return 0 to indicate error | |
} | |
void writeI2C(byte devAddr, byte regAddr, byte regVal) | |
{ | |
Wire.beginTransmission(devAddr); | |
Wire.write(regAddr); // Specifying the address of register | |
Wire.write(regVal); // Writing the value into the register | |
Wire.endTransmission(); | |
} | |
int findCurrentBufferPosition() | |
{ | |
for(int i = 0; i < EEPROM_SIZE; i++) | |
{ | |
byte v = EEPROM.read(i); | |
if(v != EEPROM_ERASE_BYTE) | |
{ | |
return i; | |
} | |
} | |
return 0; | |
} | |
byte getPersistedVolume() | |
{ | |
return EEPROM.read(CurrentBufferPosition); | |
} | |
void setVolume(byte Volume) | |
{ | |
writeI2C(AK4490_0_CHIP_ADDRESS, LEFT_VOL_REGISTER, Volume); | |
writeI2C(AK4490_0_CHIP_ADDRESS, RIGHT_VOL_REGISTER, Volume); | |
} | |
boolean persistVolume() | |
{ | |
#ifdef DEBUG | |
Serial.print("Persisting attenuation: "); | |
Serial.println(CurrentVolume, HEX); | |
#endif | |
EEPROM.write(CurrentBufferPosition, EEPROM_ERASE_BYTE); | |
if(EEPROM.read(CurrentBufferPosition) != EEPROM_ERASE_BYTE) | |
{ | |
#ifdef DEBUG | |
Serial.print("Cannot erase EEPROM at "); | |
Serial.println(CurrentBufferPosition, HEX); | |
#endif | |
return false; | |
} | |
if(++CurrentBufferPosition >= EEPROM_SIZE) | |
{ | |
CurrentBufferPosition = 0; | |
} | |
EEPROM.write(CurrentBufferPosition, CurrentVolume); | |
if(EEPROM.read(CurrentBufferPosition) != CurrentVolume) | |
{ | |
#ifdef DEBUG | |
Serial.print("Cannot write to EEPROM at "); | |
Serial.println(CurrentBufferPosition, HEX); | |
#endif | |
return false; | |
} | |
#ifdef DEBUG | |
Serial.print("Persisted attenuation: "); | |
Serial.print(CurrentVolume, HEX); | |
Serial.print(" at: "); | |
Serial.println(CurrentBufferPosition, HEX); | |
#endif | |
return true; | |
} | |
void setupAK4490() | |
{ | |
writeI2C(AK4490_0_CHIP_ADDRESS, CONTROL1_REGISTER, 0x87); // auto clock sensing, 24 bit I2S, RSTN normal | |
} | |
void setup() { | |
int i = 0; | |
Wire.begin(); // Join the I2C bus as a master | |
#ifdef DEBUG | |
Serial.begin(9600); /* set up Serial library at 9600 bps */ | |
#endif | |
// set up the LCD's number of columns and rows | |
lcd.begin(LCD_COLS, LCD_ROWS); | |
lbg = new LcdBarGraph(&lcd, LCD_COLS); // couldn't make this work as a global; got random garbage in partial characters | |
lcd.clear(); // put LCD back into DDRAM mode; see https://forum.arduino.cc/index.php?topic=459931.0 | |
// connect pullups to rotary encoder pins | |
digitalWrite(2, HIGH); | |
digitalWrite(3, HIGH); | |
// setup rotary encoder interrupt | |
PCICR |= (1 << PCIE2); | |
PCMSK2 |= (1 << PCINT18) | (1 << PCINT19); | |
sei(); | |
CurrentBufferPosition = findCurrentBufferPosition(); /* position of last write in circular buffer */ | |
/* init attenuation */ | |
CurrentVolume = getPersistedVolume(); | |
#ifdef DEBUG | |
Serial.print("Startup persisted attenuation: "); | |
Serial.println(CurrentVolume, HEX); | |
#endif | |
PreviousVolume = EEPROM_ERASE_BYTE; // kick off display of volume during 1st pass through loop | |
if(CurrentVolume > VOL_MAX || CurrentVolume < VOL_MIN) | |
{ | |
CurrentVolume = VOL_MIN; | |
} | |
// wait for AK4490 I2C | |
for(i = 0; i < 1000; i++) | |
{ | |
if(haveAK4490()) | |
{ | |
break; | |
} | |
delay(1); | |
} | |
if(i == 1000) | |
{ | |
lcd.setCursor(0, 2); | |
lcd.print("AK4490 Not Found"); | |
} | |
else | |
{ | |
setupAK4490(); | |
setVolume(CurrentVolume); | |
} | |
} | |
void loop() | |
{ | |
if(!EEPROMMalfunction) | |
{ | |
if(PreviousVolume != CurrentVolume) | |
{ | |
PreviousVolume = CurrentVolume; | |
setVolume(CurrentVolume); | |
lbg->drawValue(CurrentVolume, VOL_MAX); // update bar graph | |
lcd.setCursor(0, 1); | |
lcd.print(" "); | |
String StrAttn("-"); | |
StrAttn += ((byte)~CurrentVolume)/2; | |
if(!(CurrentVolume % 2)) | |
{ | |
StrAttn += ".5"; | |
} | |
StrAttn += (" dB"); | |
lcd.setCursor(9 - StrAttn.length(), 1); | |
lcd.print(StrAttn); | |
if(EEPROM.read(CurrentBufferPosition) != CurrentVolume) | |
{ | |
TriggerStartMS = millis(); | |
TriggerVolumePersist = true; | |
} | |
} | |
if(TriggerVolumePersist) | |
{ | |
if(millis() - TriggerStartMS >= WRITE_VOL_DELAY) /* this will accomodate wrap-around of millis() return value */ | |
{ | |
if(!persistVolume()) /* write to EEPROM and check for malfunction */ | |
{ | |
/* EEPROM not functioning correctly */ | |
EEPROMMalfunction = true; | |
} | |
TriggerVolumePersist = false; | |
} | |
} | |
} | |
else | |
{ | |
/* blink the Arduino UNO's internal yellow LED, which also happens to be connected internally to the SCK pin on the UNO */ | |
digitalWrite(SCK, HIGH); | |
delay(1000); | |
digitalWrite(SCK, LOW); | |
lcd.setCursor(0, 1); | |
lcd.print("EEPROM Error"); | |
delay(1000); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* EEPROM Erase | |
* | |
* Sets all of the bytes of the EEPROM to 0. | |
* This example code is in the public domain. | |
*/ | |
#include <EEPROM.h> | |
void setup() | |
{ | |
// write a 0xff to all 1024 bytes of the UNO R3 EEPROM | |
for (int i = 0; i < 1024; i++) | |
{ | |
if(0xff != EEPROM.read(i)) | |
{ | |
EEPROM.write(i, 0); | |
} | |
} | |
// turn the LED on when we're done | |
digitalWrite(13, HIGH); | |
} | |
void loop() | |
{ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment