// File: CascadeSwitch16Inputs-74HC165.ino
//
// Version 1, 5 August 2021, by Koepel
// Initial version.
// Version 2, 5 August 2021, by Koepel
// Layout of the wiring made better.
// Version 3, 13 August 2021, by Koepel
// Changed 'SCK' to 'clockPin'.
// Version 4, 6 August 2023 by Gabriel Balbuena
// Changed to use push buttons and 16 inputs
//
// Cascade of two 74HC165 shift-in registers.
// Only three pins are used on the Arduino board, to read 16 switches.
//
// Using the 74HC165 is safe, because a pulse to the Latch pin
// ('PL' on the 74HC165) will make a new start every time.
// In case of an error or a wrong clock pulse by noise,
// it synchronizes the data when inputs are read the next time.
//
// Based on:
// (1)
// Demo sketch to read from a 74HC165 input shift register
// by Nick Gammon, https://www.gammon.com.au/forum/?id=11979
// (2)
// 74HC165 Shift register input example
// by Uri Shaked, https://wokwi.com/arduino/projects/306031380875182657
//
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
void displayInit() {
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
display.clearDisplay(); // Clear the buffer
display.setTextColor(SSD1306_WHITE); // Set text color
display.setTextSize(2); // Set text size
display.setCursor(0, 0); // Set text position
display.println("Hello, world!"); // Print text to buffer
display.display(); // Show buffer contents on the display
}
void displayText(String txt) {
display.clearDisplay();
// Set text color
display.setTextColor(SSD1306_WHITE);
// Set text size
display.setTextSize(2);
// Set text position
display.setCursor(0, 0);
// Print text to buffer
display.println(txt);
// Show buffer contents on the display
display.display();
}
const int buzzer = 7;
const byte latchPin = 9;// to latch the inputs into the registers
const byte clockPin = 13; // I choose the SCK pin
const byte dataPin = 12; // I choose the MISO pin
uint16_t oldOptionSwitch = 0; // previous state of all the inputs
const int pulseWidth = 10; // pulse width in microseconds
void inputInit() {
// Serial.println("Top row is switch 0 (right) to switch 7 (left)");
// Serial.println("Second row is 8 to 15, and so on");
pinMode(clockPin, OUTPUT); // clock signal, idle LOW
pinMode(latchPin, OUTPUT); // latch (copy input into registers), idle HIGH
digitalWrite(latchPin, HIGH);
}
void setup() {
Serial.begin(115200);
displayInit();
inputInit();
}
void loop() {
// Give a pulse to the parallel load latch of all 74HC165
digitalWrite(latchPin, LOW);
delayMicroseconds(pulseWidth);
digitalWrite(latchPin, HIGH);
// Reading one 74HC165 at a time and combining them into a 16 bit variable
uint16_t optionSwitch = 0;
for (int i = 8; i >= 0; i -= 8) {
optionSwitch |= ((uint16_t)ReadOne165()) << i;
}
for (int i = 0; i < 16; i++) {
if (bitRead(optionSwitch, i) != bitRead(oldOptionSwitch, i)) {
// Serial.print("Switch ");
// if (i < 10) {
// Serial.print(" ");
// }
// Serial.print(i);
// Serial.print(" is now ");
// Serial.println(bitRead(optionSwitch, i) == 0 ? "down ↓" : "up ↑");
if (bitRead(optionSwitch, i) != 0) {
play(i);
} else {
noTone(buzzer);
}
}
}
oldOptionSwitch = optionSwitch;
delay(25); // slow down the sketch to avoid switch bounce
}
// The ReadOne165() function reads only 8 bits,
// because of the similar functions shiftIn() and SPI.transfer()
// which both use 8 bits.
//
// The shiftIn() can not be used here, because the clock is set idle low
// and the shiftIn() makes the clock high to read a bit.
// The 74HC165 require to read the bit first and then give a clock pulse.
byte ReadOne165()
{
byte ret = 0x00;
// The first one that is read is the highest bit (input D7 of the 74HC165).
for (int i = 7; i >= 0; i--)
{
if (digitalRead(dataPin) == HIGH)
bitSet(ret, i);
digitalWrite(clockPin, HIGH);
delayMicroseconds(pulseWidth);
digitalWrite(clockPin, LOW);
}
return (ret);
}
#define NUM_NOTES 12
#define NUM_OCTAVES 7
char* noteNames[NUM_NOTES] = {"C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab", "A", "A#/Bb", "B"};
char* noteNamesSpanish[NUM_NOTES] = {"Do", "Do#/Reb", "Re", "Re#/Mib", "Mi", "Fa", "Fa#/Solb", "Sol", "Sol#/Lab", "La", "La#/Sib", "Si"};
// This function returns the frequency of a note.
float getNoteFrequency(int noteNumber) {
return 440.0 * pow(2.0, (noteNumber - 57) / 12.0); // A4 is the 57th key on the piano and it's usually tuned to 440Hz.
}
#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_D5 587
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_G5 784
#define NOTE_A5 880
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_D6 1175
const int TONES[] = {
NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4,
NOTE_C5, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_B5,
NOTE_C6, NOTE_D6
};
String getNote(int freq) {
for(int octave = 0; octave < NUM_OCTAVES; octave++) {
for(int note = 0; note < NUM_NOTES; note++) {
int noteNumber = octave * NUM_NOTES + note; // Get the note number
float noteFreq = getNoteFrequency(noteNumber); // Compute the frequency
if(abs(freq - noteFreq) < 10) { // tolerance of 10Hz
return noteNames[note]+String(octave);
// return noteNamesSpanish[note]+String(octave);
}
}
}
}
void play(int16_t btn) {
int freq = TONES[btn];
tone(buzzer, freq);
String note = getNote(TONES[btn]);
Serial.println("btn="+String(btn)+", note="+note);
displayText(note);
}