// Remote ATU-100 Controller
//
// References:
// https://arduino-pico.readthedocs.io/en/latest/eeprom.html
// https://www.ktron.in/product/rs485-to-ttl-converter/
// https://robu.in/product/ttl-to-rs485-power-supply-converter-board-3-3v-5v-hardware-auto-control-module/
#include <Wire.h>
#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
// L bank
#define L1 3 // GP2 -> actual physical pin 4 on pi pico board
#define L2 4
#define L3 5
#define L4 6
#define L5 7
#define L6 10
#define L7 11
#define L8 12
#define L_MAX 256
// C bank
#define C1 13
#define C2 14
#define C3 15
#define C4 16
#define C5 17
#define C6 18
#define C7 19
#define C8 20
#define C_MAX 256
// Pinouts
#define ENC_A1 (21)
#define ENC_B1 (22)
#define ENC_SW1 (26)
#define ENC_A2 (27)
#define ENC_B2 (28)
#define ENC_SW2 (2)
// Show menu button
#define MENU_BUTTON 1
// Globals
unsigned int L = 0;
unsigned int C = 0;
void pin_setup()
{
pinMode(L1, OUTPUT);
pinMode(L2, OUTPUT);
pinMode(L3, OUTPUT);
pinMode(L4, OUTPUT);
pinMode(L5, OUTPUT);
pinMode(L6, OUTPUT);
pinMode(L7, OUTPUT);
pinMode(L8, OUTPUT);
pinMode(C1, OUTPUT);
pinMode(C2, OUTPUT);
pinMode(C3, OUTPUT);
pinMode(C4, OUTPUT);
pinMode(C5, OUTPUT);
pinMode(C6, OUTPUT);
pinMode(C7, OUTPUT);
pinMode(C8, OUTPUT);
pinMode(ENC_A1, INPUT);
pinMode(ENC_B1, INPUT);
pinMode(ENC_A2, INPUT);
pinMode(ENC_B2, INPUT);
pinMode(ENC_SW1, INPUT_PULLUP);
pinMode(ENC_SW2, INPUT_PULLUP);
pinMode(MENU_BUTTON, INPUT_PULLUP);
}
// Display related variable
char c[16], b[16];
// The generic routine to display one line on the LCD
void printLine(int linenmbr, char *c) {
lcd.setCursor(0, linenmbr);
lcd.print(c);
}
// short cut to print to the first line
void printLine1(char *c) {
printLine(1, c);
}
// short cut to print to the second line
void printLine2(char *c) {
printLine(0, c);
}
// Returns true if the button is pressed
int btnDown(int button)
{
if (digitalRead(button) == HIGH)
return 0;
else
return 1;
}
int enc1_prev_state = 3;
int enc2_prev_state = 3;
byte enc_state(int pin1, int pin2) {
return (digitalRead(pin1) ? 1 : 0) + (digitalRead(pin2) ? 2 : 0);
}
int enc1_read(void) {
int result = 0;
byte newState;
int enc1_speed = 0;
unsigned long stop_by = millis() + 50;
while (millis() < stop_by) { // check if the previous state was stable
newState = enc_state(ENC_A1, ENC_B1); // Get current state
if (newState != enc1_prev_state)
delay(1);
if (enc_state(ENC_A1, ENC_B1) != newState || newState == enc1_prev_state)
continue;
// these transitions point to the encoder being rotated anti-clockwise
if ((enc1_prev_state == 0 && newState == 2) ||
(enc1_prev_state == 2 && newState == 3) ||
(enc1_prev_state == 3 && newState == 1) ||
(enc1_prev_state == 1 && newState == 0)) {
result--;
}
// these transitions point the encoder being rotated clockwise
if ((enc1_prev_state == 0 && newState == 1) ||
(enc1_prev_state == 1 && newState == 3) ||
(enc1_prev_state == 3 && newState == 2) ||
(enc1_prev_state == 2 && newState == 0)) {
result++;
}
enc1_prev_state = newState; // record state for next pulse interpretation
enc1_speed++;
delay(1);
}
return (result);
}
int enc2_read(void) {
int result = 0;
byte newState;
int enc2_speed = 0;
unsigned long stop_by = millis() + 50;
while (millis() < stop_by) { // check if the previous state was stable
newState = enc_state(ENC_A2, ENC_B2); // Get current state
if (newState != enc2_prev_state)
delay(1);
if (enc_state(ENC_A2, ENC_B2) != newState || newState == enc2_prev_state)
continue;
// these transitions point to the encoder being rotated anti-clockwise
if ((enc2_prev_state == 0 && newState == 2) ||
(enc2_prev_state == 2 && newState == 3) ||
(enc2_prev_state == 3 && newState == 1) ||
(enc2_prev_state == 1 && newState == 0)) {
result--;
}
// these transitions point to the encoder being rotated clockwise
if ((enc2_prev_state == 0 && newState == 1) ||
(enc2_prev_state == 1 && newState == 3) ||
(enc2_prev_state == 3 && newState == 2) ||
(enc2_prev_state == 2 && newState == 0)) {
result++;
}
enc2_prev_state = newState; // record state for next pulse interpretation
enc2_speed++;
delay(1);
}
return (result);
}
unsigned int memory_slot_number = 0;
int eeprom_address = 0;
void doSaveMenu() {
int s;
s = enc1_read();
if (s) {
if (s > 0)
memory_slot_number = (memory_slot_number + 1) % 256;
else
memory_slot_number = (memory_slot_number - 1) % 256;
lcd.clear();
memset(c, 0, sizeof(c));
sprintf(c, "%M%d %u %u", memory_slot_number, L, C);
printLine(0, c);
}
}
const int debounceDelay = 10; // milliseconds to wait until button input stable
// debounce returns true if the switch in the given pin is closed and stable
boolean debounce(int pin)
{
boolean state;
boolean previousState;
previousState = digitalRead(pin); // store switch state
for (int counter = 0; counter < debounceDelay; counter++)
{
delay(1); // wait for 1 millisecond
state = digitalRead(pin); // read the pin
if (state != previousState) {
counter = 0; // reset the counter if the state changes
previousState = state; // and save the current state
}
}
// here when the switch state has been stable longer than the debounce period
return state;
}
void doRecallMenu() {
unsigned char LHEX[9];
unsigned char CHEX[9];
memset(c, 0, sizeof(c));
memset(b, 0, sizeof(b));
memset(LHEX, 0, sizeof(LHEX));
memset(CHEX, 0, sizeof(CHEX));
int s;
s = enc2_read();
if (s) {
if (s > 0)
memory_slot_number = (memory_slot_number + 1) % 256;
else
memory_slot_number = (memory_slot_number - 1) % 256;
lcd.clear();
memset(c, 0, sizeof(c));
eeprom_address = memory_slot_number * 2;
Serial.println(eeprom_address);
L = EEPROM.read(eeprom_address);
C = EEPROM.read(eeprom_address + 1);
memset(c, 0, sizeof(c));
hex2binstr((unsigned char*)LHEX, L);
hex2binstr((unsigned char*)CHEX, C);
sprintf(c, "L%03d %s", memory_slot_number, LHEX);
printLine(0, c);
sprintf(b, "C%03d %s", memory_slot_number, CHEX);
printLine(1, b);
}
}
// Show main menu
int checkButton3(int button) {
if (debounce(button))
return 0;
Serial.println("Menu button pressed!");
updateDisplay();
return 1;
}
// Save on button 1
void checkButton1(int button) {
// only if the button is pressed
if (debounce(button))
return;
Serial.println("Button 1 pressed!");
/* Display slot number -> M0, M1, ..., M255 */
lcd.clear();
memset(c, 0, sizeof(c));
sprintf(c, "M%d %u %u", memory_slot_number, L, C);
printLine(0, c);
/* Encoder rotations change slot, save on button 1 press, cancel on button 2 press */
while (1) {
doSaveMenu();
if (checkButton3(MENU_BUTTON))
return;
delay(10);
if (!debounce(ENC_SW2))
break;
if (!debounce(button)) {
// save 'memory slot' logic
Serial.println(("Saving!"));
eeprom_address = memory_slot_number * 2;
EEPROM.write(eeprom_address, L);
EEPROM.write(eeprom_address + 1, C);
EEPROM.commit();
memset(b, 0, sizeof(b));
sprintf(b, "Saved %M%d", memory_slot_number);
printLine(1, b);
delay(1000);
lcd.clear();
printLine(0, c);
}
}
}
// Recall on button 2
void checkButton2(int button) {
unsigned char LHEX[9];
unsigned char CHEX[9];
memset(c, 0, sizeof(c));
memset(b, 0, sizeof(b));
memset(LHEX, 0, sizeof(LHEX));
memset(CHEX, 0, sizeof(CHEX));
if (debounce(button))
return;
Serial.println("Button 2 pressed!");
/* Encoder rotations change slot, recall on button 1 press, cancel on button 1 press */
while (1) {
doRecallMenu();
if (checkButton3(MENU_BUTTON))
return;
delay(10);
if (!debounce(ENC_SW1)) {
break;
}
if (!debounce(button)) {
// save 'memory slot' logic goes here
Serial.println(("Recalling!"));
memset(b, 0, sizeof(b));
sprintf(b, "Recalling %M%d", memory_slot_number);
printLine(1, b);
delay(1000);
lcd.clear();
// Fetch L and C value from EEPROM
eeprom_address = memory_slot_number * 2;
L = EEPROM.read(eeprom_address);
C = EEPROM.read(eeprom_address + 1);
lcd.clear();
memset(c, 0, sizeof(c));
hex2binstr((unsigned char*)LHEX, L);
hex2binstr((unsigned char*)CHEX, C);
sprintf(c, "L%03d %s", memory_slot_number, LHEX);
printLine(0, c);
sprintf(b, "C%03d %s", memory_slot_number, CHEX);
printLine(1, b);
operate_relays(); // note
}
}
}
void num2binstr(unsigned char *str, unsigned char dat) {
unsigned char mask = 0x80;
do {
if (dat & mask)
*str = 1;
else
*str = 0;
str += 1;
mask = mask >> 1;
} while (mask);
}
void operate_relays()
{
// This function operates relays based on L and C
unsigned char LBIN[9];
unsigned char CBIN[9];
memset(LBIN, 0, sizeof(LBIN));
memset(CBIN, 0, sizeof(CBIN));
num2binstr(LBIN, L);
num2binstr(CBIN, C);
// Switch L bank
digitalWrite(L1, LBIN[7]);
digitalWrite(L2, LBIN[6]);
digitalWrite(L3, LBIN[5]);
digitalWrite(L4, LBIN[4]);
digitalWrite(L5, LBIN[3]);
digitalWrite(L6, LBIN[2]);
digitalWrite(L7, LBIN[1]);
digitalWrite(L8, LBIN[0]);
// Switch C bank
digitalWrite(C1, CBIN[7]);
digitalWrite(C2, CBIN[6]);
digitalWrite(C3, CBIN[5]);
digitalWrite(C4, CBIN[4]);
digitalWrite(C5, CBIN[3]);
digitalWrite(C6, CBIN[2]);
digitalWrite(C7, CBIN[1]);
digitalWrite(C8, CBIN[0]);
}
void doTuning() {
int s;
s = enc1_read();
if (s) {
if (s > 0)
L = (L + 1) % L_MAX;
else
L = (L - 1) % L_MAX;
operate_relays();
updateDisplay();
}
s = enc2_read();
if (s) {
if (s > 0)
C = (C + 1) % C_MAX;
else
C = (C - 1) % C_MAX;
operate_relays();
updateDisplay();
}
}
unsigned char *hex2binstr(unsigned char *str, unsigned char dat) {
unsigned char mask = 0x80;
do {
if (dat & mask)
*str = '1';
else
*str = '0';
str += 1;
mask = mask >> 1;
} while (mask);
return str;
}
void updateDisplay()
{
unsigned char LHEX[9];
unsigned char CHEX[9];
memset(c, 0, sizeof(c));
memset(b, 0, sizeof(b));
memset(LHEX, 0, sizeof(LHEX));
memset(CHEX, 0, sizeof(CHEX));
hex2binstr((unsigned char*)LHEX, L);
hex2binstr((unsigned char*)CHEX, C);
lcd.clear();
sprintf(c, "L %d %s", L, LHEX);
printLine(0, c);
sprintf(b, "C %d %s", C, CHEX);
printLine(1, b);
}
void loop()
{
doTuning();
checkButton1(ENC_SW1);
checkButton2(ENC_SW2);
delay(10);
}
void setup() {
pin_setup();
Wire.setSDA(8);
Wire.setSCL(9);
Wire.begin();
EEPROM.begin(4096);
Serial.begin(115200);
lcd.init();
lcd.backlight();
lcd.begin(16, 2);
lcd.print("ATU-Reborn!");
delay(500);
updateDisplay();
}