That does make sense, I’ll definitely give that a go.
I had to stare at this for some time because I am more used to seeing the construct as in post #15 but, of course, it works. An array decays to a pointer to its first element so dereferencing the first element yields, in this case, a uint8_t.
I made a simple Braille trainer, that could be expanded, which might have value: https://wokwi.com/projects/428317527578434561
Very interesting code, I’m sure that I will get some inspiration from it, thank you for sharing.
i'm curious to see more complete code which provides a better idea of what the final program does or intends to do
guessing that the random value selects a braille symbol from a table indicating the 6-bit value for that symbol but unclear how the character (e.g. 'a') is input
I’m curious to exactly how the game will work.
Given the intent you’ll find plenty of volunteers here to help you write the code if needed or get ideas for making the game more fun. Don’t hesitate to ask for ideas or help.
The outcome could also go into the showcase category and give ideas and ready to make instructions for other parents in a similar situation.
this short program demonstrates the use of a table
struct Braille {
unsigned char sym; // from top-left, across each row
char letter;
}
braille [] = {
{ 0x20, 'a' },
{ 0x28, 'b' },
{ 0x30, 'c' },
{ 0x34, 'd' },
{ 0x24, 'e' },
{ 0x38, 'f' },
{ 0x3c, 'g' },
{ 0x2c, 'h' },
{ 0x18, 'i' },
{ 0x1c, 'j' },
{ 0x22, 'k' },
{ 0x2a, 'l' },
{ 0x32, 'm' },
{ 0x36, 'n' },
{ 0x26, 'o' },
{ 0x3a, 'p' },
{ 0x3e, 'q' },
{ 0x2e, 'r' },
{ 0x1a, 's' },
{ 0x1e, 't' },
{ 0x23, 'u' },
{ 0x2b, 'v' },
{ 0x1d, 'w' },
{ 0x33, 'x' },
{ 0x37, 'y' },
{ 0x27, 'z' },
};
const int Nbraille = sizeof(braille)/sizeof(Braille);
// -----------------------------------------------------------------------------
#include <stdio.h>
void draw (
unsigned char sym )
{
for (int row = 0; row < 3; row++) {
printf ("%4s", "");
for (int col = 0; col < 2; col++) {
printf ("%c", sym & 0x20 ? '*' : ' ');
sym <<= 1;
}
printf ("\n");
}
}
// -----------------------------------------------------------------------------
int main ()
{
int cnt [100] = {};
for (int n = 0; n < Nbraille; n++) {
printf (" %c %2x\n", braille [n].letter, braille [n].sym);
if (cnt [braille [n].sym]++)
printf (" dup\n");
draw (braille [n].sym);
}
}
output
a 20
*
b 28
*
*
c 30
**
d 34
**
*
e 24
*
*
f 38
**
*
g 3c
**
**
h 2c
*
**
i 18
*
*
j 1c
*
**
k 22
*
*
l 2a
*
*
*
m 32
**
*
n 36
**
*
*
o 26
*
*
*
p 3a
**
*
*
q 3e
**
**
*
r 2e
*
**
*
s 1a
*
*
*
t 1e
*
**
*
u 23
*
**
v 2b
*
*
**
w 1d
*
**
*
x 33
**
**
y 37
**
*
**
z 27
*
*
**
Most certainly! I have a working prototype, but I need to improve on the code and the physical game box itself, it’s not doing quite what I want it to do. I can supply photos of the game and a video of my grandson using it. I also have a document with the rules of the game. All need refining.
is it possible for me to post all that on this topic?
i'd just like to see the code, it will expose how you're thinking about the problem.
Definitely for the documentation , rules, code, etc
The video might be more touchy (usually post on YouTube and post a link here but do you want to expose your family members to the Internet ?…)
Ok, I’ll post the code here, and redo a video of the game box including the inside showing the birds nest of cables etc. I’ll do what I can with the code before posting. I won’t post the old code because it’s so untidy and unstructured, using arrays. I’ve got my mind set on using shift registers for the input and output values, so all I need do is compare a couple of variables to obtain a result. Final input value matches random number value is all that is needed to play audio track with same value. For instance for “a”, Random number given as 1, suggested audio track played 00001.mp3, final button input value= 000001, gives a correct input so a random “correct” track is sounded. BTW I have the current button presses sounding as per iPhone keypad sounds. All will be revealed when I post the video.
seems like you may be more focused on "how" to do things rather than "what".
i'm not sure what the random # selects (why would some values need to be ignored) and what the LEDs are for? what is the goal, identify a braille character similar to identifying a morse code character?
I have created a short YouTube video, which shows the game in action. This might answer a few questions for you all.
If necessary, I will post the complete code for this version. However, my aim, as I have said earlier, is to simplify the code using shift registers for the input and output. I will create a further video showing the inside of the console and identify the items used to construct the console.
Any feedback is appreciated.
Is there a reason why the buttons are not laid out in the same pattern as the Braille shape ?
Yes, that’s the way the braille typewriter is laid out for the visually impaired - although they use the button in the middle as a SPC Bar.
Example:
Thank you for the explanation
The link does not work for me but I have found examples of brailers here
Thank you for the link.
i suggest you post your code.
many of us are use to seeing poorly written code to understand "what" the OP is trying to do and can often suggest much simpler and more maintainable code
Ok, no problem. When you see the code you will realise that I am using an array to match the input to the output. By using shift registers I can omit the array entirely by using just 2 variables which contain the bit patterns, one for the challenge “number” has been suggested, and one for the input, say “keyedNumbers”.
/*
Current Sketch: BrailleGameV5_3
FULLY WORKING! - NOW TIDY UP CODE
Rename Sketch to BrailleGameV10 to match new sound files
26/06/2025
Code now works - need to adjust sound volume
Code needs to be tidied up, putting functions in a different tab
Make use of Debug and Serial.print - done
Place button pressed into function - done
Make better use of arrays: States, LEDs, Buttons
Need to remove old obsolete code
Added timer routine to turn LEDs off after each turn (needs tuning)
Last tested working 15 Mar 2025
*/
#define CurrentSketch "BrailleGameV5_3"
#define SketchDate "14 Mar 2025"
#define DEBUG 0 // Set to 1 for serial print feedback
#if DEBUG == 1 // Set to 0 when not needed
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#define debugw(x) Serial.write(x)
#else
#define debug(x)
#define debugln(x)
#define debugw(x)
#endif
#include <Tone.h>
#include "Led.h"
#include "PushButton.h"
#include "SoftwareSerial.h"
SoftwareSerial Serial2(9, 10); // RX, TX with voltage divider pin 10
Tone freq1;
Tone freq2;
// Dial numbers 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 (To align with array 0)
const int DTMF_freq1[] = { 1209, 1336, 1477, 1209, 1336, 1477, 1209, 1336, 1477, 1336 };
const int DTMF_freq2[] = { 697, 697, 697, 770, 770, 770, 852, 852, 852, 941 };
#define LED0 A0
#define LED1 A1
#define LED2 A2
#define LED3 A3
#define LED4 A4
#define LED5 A5
#define BUTTON0 2
#define BUTTON1 3
#define BUTTON2 4
#define BUTTON3 5
#define BUTTON4 6
#define BUTTON5 7
#define BUTTON6 8
// #define busyPin 11 // Busy Feedback from Soundboard - no longer used
int delayRate = 100; // for testing LEDs
PushButton button0(BUTTON0, true, true); // Internal Pullup, Pullup (pin now HIGH)
PushButton button1(BUTTON1, true, true); // Internal Pullup, Pullup
PushButton button2(BUTTON2, true, true); // Internal Pullup, Pullup
PushButton button3(BUTTON3, true, true); // Internal Pullup, Pullup
PushButton button4(BUTTON4, true, true); // Internal Pullup, Pullup
PushButton button5(BUTTON5, true, true); // Internal Pullup, Pullup
PushButton button6(BUTTON6, true, true); // Internal Pullup, Pullup
Led ledA0(LED0);
Led ledA1(LED1);
Led ledA2(LED2);
Led ledA3(LED3);
Led ledA4(LED4);
Led ledA5(LED5);
// #############################################################
// from original sketch
// #############################################################
bool brailleButtonStates[6]; // was int
unsigned long currentMillis;
unsigned long gameMillis;
unsigned long countdownMillis;
int gameMode;
int gameTimer = 12500; //12.5 seconds to find letter by default (to match duration of countdown track)
int chosenChar; // initially 0
int countdownState;
int countdownTimer = 300;
int countdownTrack = 31; // revised to allow for extra braille
int gameVolume = 27; //volume value between 1 and 30...edit if too loud/quiet
int brailleNumLetters = 30; //may be inscreased if numbers or other characters are added
int brailleMP3array[30][7] = {
//6 x buttons, mp3 track number(letter)
{ 1, 0, 0, 0, 0, 0, 1 }, //a playing track 00001
{ 1, 1, 0, 0, 0, 0, 2 }, //b playing track 00002
{ 1, 0, 0, 1, 0, 0, 3 }, //c playing track 00003
{ 1, 0, 0, 1, 1, 0, 4 }, //d playing track 00004
{ 1, 0, 0, 0, 1, 0, 5 }, //e playing track 00005
{ 1, 1, 0, 1, 0, 0, 6 }, //f playing track 00006
{ 1, 1, 0, 1, 1, 0, 7 }, //g playing track 00007
{ 1, 1, 0, 0, 1, 0, 8 }, //h playing track 00008
{ 0, 1, 0, 1, 0, 0, 9 }, //i playing track 00009
{ 0, 1, 0, 1, 1, 0, 10 }, //j playing track 000010
{ 1, 0, 1, 0, 0, 0, 11 }, //k playing track 000011
{ 1, 1, 1, 0, 0, 0, 12 }, //l playing track 000012
{ 1, 0, 1, 1, 0, 0, 13 }, //m playing track 000013
{ 1, 0, 1, 1, 1, 0, 14 }, //n playing track 000014
{ 1, 0, 1, 0, 1, 0, 15 }, //o playing track 000015
{ 1, 1, 1, 1, 0, 0, 16 }, //p playing track 000016
{ 1, 1, 1, 1, 1, 0, 17 }, //q playing track 000017
{ 1, 1, 1, 0, 1, 0, 18 }, //r playing track 000018
{ 0, 1, 1, 1, 0, 0, 19 }, //s playing track 000019
{ 0, 1, 1, 1, 1, 0, 20 }, //t playing track 000020
{ 1, 0, 1, 0, 0, 1, 21 }, //u playing track 000021
{ 1, 1, 1, 0, 0, 1, 22 }, //v playing track 000022
{ 0, 1, 0, 1, 1, 1, 23 }, //w playing track 000023
{ 1, 0, 1, 1, 0, 1, 24 }, //x playing track 000024
{ 1, 0, 1, 1, 1, 1, 25 }, //y playing track 000025
{ 1, 0, 1, 0, 1, 1, 26 }, //z playing track 000026
{ 1, 1, 1, 1, 0, 1, 27 }, //[ playing track 000027 "AND"
{ 1, 1, 1, 1, 1, 1, 28 }, //\ playing track 000028 "FOR"
{ 1, 1, 1, 0, 1, 1, 29 }, //] playing track 000029 "OF"
{ 0, 1, 1, 1, 0, 1, 30 } //^ playing track 000030 "THE"
};
void setup() {
Serial.begin(9600);
Serial2.begin(9600); // Start Software Serial
debugln("Setup complete");
debugln(CurrentSketch);
debugln(SketchDate);
// initialise objects:
freq1.begin(13); // 560 ohm resistor - resistor not used
freq2.begin(12); // 560 ohm resistor - resistor not used
button0.init(); // Braille Button 1
button1.init(); // Braille Button 2
button2.init(); // Braille Button 3
button3.init(); // Braille Button 4
button4.init(); // Braille Button 5
button5.init(); // Braille Button 6
button6.init(); // Green Button
ledA0.init(); // Braille LED Indicator 1
ledA1.init(); // Braille LED Indicator 2
ledA2.init(); // Braille LED Indicator 3
ledA3.init(); // Braille LED Indicator 4
ledA4.init(); // Braille LED Indicator 5
ledA5.init(); // Braille LED Indicator 6
// Check button sound
chimeAll();
// Check LEDs - Run twice to leave LEDs in the OFF state
checkLeds();
checkLeds();
}
void loop() {
//int q;
currentMillis = millis();
randomSeed(currentMillis); //random seed generator to randomise character sequence
switch (gameMode) {
case 1: //choose letter
debug("gameMode: ");
debugln(gameMode);
// randomSeed(currentMillis); //random seed generator to randomise character sequence
// //choose a random letter (A to Carat - circumflex)
chosenChar = random(30);
//Start track playing here
gameMillis = currentMillis;
playTrack(brailleMP3array[chosenChar][6]);
gameMode = 2;
countdownState = 0; //reset so countdown starts when letter sound has ended
//countdownMillis = currentMillis;
debug("Chosen Char: ");
debugw(65 + chosenChar); // HTML Number - A = 'A'
debugln(" ");
delay(1500); // 1.5 second delay to allow sound to complete (aka countdown state)
break;
case 2: //wait for timer to end to read buttons
debug("gameMode: ");
debugln(gameMode);
playTrack(countdownTrack); // keeps repeating?
brailleButtonStates[6] = 0; // Reset Start Button to use as LED reset button
gameMode = 3;
case 3:
if (currentMillis - gameMillis < gameTimer) { //still time to choose may need for loop
checkButtonsPressed(); // In a loop for 12.5 seconds
} else {
gameMode = 4;
}
debug("gameMode: ");
debugln(gameMode);
break;
case 4: //did they pick correctly?
delay(1000); // Delay for 1 second for suspense
debug("gameMode: ");
debugln(gameMode);
debug("Braille Button 0 State: ");
debugln(brailleButtonStates[0]);
debug("Braille Button 1 State: ");
debugln(brailleButtonStates[1]);
debug("Braille Button 2 State: ");
debugln(brailleButtonStates[2]);
debug("Braille Button 3 State: ");
debugln(brailleButtonStates[3]);
debug("Braille Button 4 State: ");
debugln(brailleButtonStates[4]);
debug("Braille Button 5 State: ");
debugln(brailleButtonStates[5]);
//do the buttons pressed match the braille code
if (brailleButtonStates[0] == brailleMP3array[chosenChar][0] && brailleButtonStates[1] == brailleMP3array[chosenChar][1] && brailleButtonStates[2] == brailleMP3array[chosenChar][2] && brailleButtonStates[3] == brailleMP3array[chosenChar][3] && brailleButtonStates[4] == brailleMP3array[chosenChar][4] && brailleButtonStates[5] == brailleMP3array[chosenChar][5]) {
//play success sound
playTrack(random(32, 41));
//playTrack(7);
debugln("Success");
} else {
//play fail sound
playTrack(random(42, 51));
//playTrack(10);
debugln("Fail");
}
gameMode = 0;
delay(8000); // wait 8 secomds for result sound to finish before resetting LEDs
resetLEDs();
break;
default: //waiting for start button to get pressed
debug("gameMode: ");
debugln(gameMode);
brailleButtonStates[6] = 0; // Reset Start Button State
if (button6.isPressed()) { //start button pressed
debugln("Start button pressed");
resetLEDs(); // Reset LEDs and Button States
gameMode = 1;
}
break;
}
}
// === END OF LOOP ========================================
// ========================================================
void checkLeds() {
// Toggle each LED (on or off, or off or on)
ledA0.toggle();
delay(delayRate);
ledA0.toggle();
delay(delayRate);
ledA1.toggle();
delay(delayRate);
ledA1.toggle();
delay(delayRate);
ledA2.toggle();
delay(delayRate);
ledA2.toggle();
delay(delayRate);
ledA3.toggle();
delay(delayRate);
ledA3.toggle();
delay(delayRate);
ledA4.toggle();
delay(delayRate);
ledA4.toggle();
delay(delayRate);
ledA5.toggle();
delay(delayRate);
ledA5.toggle();
delay(delayRate);
// Toggle each LED (on or off) in sequence
ledA0.toggle();
delay(delayRate);
ledA1.toggle();
delay(delayRate);
ledA2.toggle();
delay(delayRate);
ledA3.toggle();
delay(delayRate);
ledA4.toggle();
delay(delayRate);
ledA5.toggle();
delay(delayRate);
}
// =========================================================
void chimeAll() {
// Chime all frequencies
int i;
uint8_t phone_number[] = {
0,
1,
2,
3,
4,
5,
};
debug("Button numbers: ");
for (i = 0; i < sizeof(phone_number); i++) {
debug(phone_number[i]);
playDTMF(phone_number[i], 500);
delay(600);
}
debugln(" ");
}
// =========================================================
void playDTMF(uint8_t number, long duration) {
freq1.play(DTMF_freq1[number], duration);
freq2.play(DTMF_freq2[number], duration);
}
// =========================================================
void checkButtonsPressed() {
if (button0.isPressed() && !brailleButtonStates[0]) {
ledA0.on();
brailleButtonStates[0] = 1;
playDTMF(0, 500);
debugln("Button0 is pressed");
}
if (button1.isPressed() && !brailleButtonStates[1]) {
ledA1.on();
brailleButtonStates[1] = 1;
playDTMF(1, 500);
debugln("Button1 is pressed");
}
if (button2.isPressed() && !brailleButtonStates[2]) {
ledA2.on();
brailleButtonStates[2] = 1;
playDTMF(2, 500);
debugln("Button2 is pressed");
}
if (button3.isPressed() && !brailleButtonStates[3]) {
ledA3.on();
brailleButtonStates[3] = 1;
playDTMF(3, 500);
debugln("Button3 is pressed");
}
if (button4.isPressed() && !brailleButtonStates[4]) {
ledA4.on();
brailleButtonStates[4] = 1;
playDTMF(4, 500);
debugln("Button4 is pressed");
}
if (button5.isPressed() && !brailleButtonStates[5]) {
ledA5.on();
brailleButtonStates[5] = 1;
playDTMF(5, 500);
debugln("Button5 is pressed");
}
// Reset all Button States if green button is pressed
if (button6.isPressed() && !brailleButtonStates[6]) {
brailleButtonStates[6] = 1;
debugln("Button6 is pressed");
resetLEDs(); // Resets LEDs and Button States
}
}
///mp3 player variables and functions
byte commandLength; //length in bytes of command being sent
byte command[6]; //array that stores the bytes of the command
int16_t checkSum = 0; //checksum of command
void playNext() {
command[0] = 0xAA; //first byte says it's a command
command[1] = 0x06;
command[2] = 0x00;
command[3] = 0xB0;
commandLength = 4;
sendCommand();
}
void playTrack(int soundTrack) { //play a selected track
//select track
debug("soundTrack: ");
debugln(soundTrack);
command[0] = 0xAA; //first byte says it's a command
command[1] = 0x07;
command[2] = 0x02;
command[3] = (soundTrack >> 8) & 0xFF; //snh...track HIGH bit
command[4] = soundTrack & 0xFF; //SNL... track low bit
checkSum = 0;
for (int q = 0; q < 5; q++) {
checkSum += command[q];
}
command[5] = (checkSum & 0xFF); //SM check bit... low bit of the sum of all previous values
commandLength = 6;
sendCommand();
}
//sets the device volume...0 - 30
void playbackVolume(int vol) {
if (vol > 30) { //check within limits
vol = 30;
}
command[0] = 0xAA; //first byte says it's a command
command[1] = 0x13;
command[2] = 0x01;
command[3] = vol; //volume
checkSum = 0;
for (int q = 0; q < 4; q++) {
checkSum += command[q];
}
command[4] = (checkSum & 0xFF); //SM check bit... low bit of the sum of all previous values
commandLength = 5;
sendCommand();
}
void sendCommand() {
int q;
for (q = 0; q < commandLength; q++) {
Serial2.write(command[q]);
Serial.print(command[q], HEX);
}
debugln("End");
}
//end mp3 player functions
void resetLEDs() {
ledA0.off();
ledA1.off();
ledA2.off();
ledA3.off();
ledA4.off();
ledA5.off();
brailleButtonStates[0] = 0;
brailleButtonStates[1] = 0;
brailleButtonStates[2] = 0;
brailleButtonStates[3] = 0;
brailleButtonStates[4] = 0;
brailleButtonStates[5] = 0;
}
Code for Led.cpp
// Led.cpp
// Implementation (what is included in the functions)
// 23 Feb 2024
#include "led.h"
Led::Led(byte pin) {
this->pin = pin;
}
void Led::init() {
pinMode(pin, OUTPUT);
}
void Led::init(byte defaultState) {
init();
if (defaultState == HIGH) {
on();
} else {
off();
}
}
void Led::on() {
digitalWrite(pin, HIGH);
}
void Led::off() {
digitalWrite(pin, LOW);
}
void Led::toggle() {
digitalWrite(pin, !digitalRead(pin));
}
Led.h
// Led.h
// Interface (Class)
// 23 Feb 2024
#ifndef LED_H // Header Guard!
#define LED_H // All Uppercase
#include <arduino.h> // normally automatically included in main programme
class Led
{
private:
byte pin;
public:
Led() {} // construcor do not use
Led(byte pin); // constructor
void init(); // initialise the pin
void init(byte defaultState); // initialise the pin
void on(); // function to turn on the LED
void off(); // function to turn off the LED
void toggle(); // function to toggle state of the LED
};
#endif
Code for PushButton.cpp
/*
PushButton.cpp (Implementation)
De-bounce function implemented
23 Feb 2024
*/
#include "PushButton.h"
PushButton::PushButton(byte pin, bool isPullUp, bool internalPullUpActivated) {
this->pin = pin;
this->isPullUp = isPullUp;
this->internalPullUpActivated = internalPullUpActivated;
lastTimeStateChanged = millis();
debounceDelay = 50; // 50 milliseconds
}
void PushButton::init() {
if (isPullUp && internalPullUpActivated) {
pinMode(pin, INPUT_PULLUP);
} else {
pinMode(pin, INPUT);
}
state = digitalRead(pin);
}
void PushButton::readState() {
unsigned long timeNow = millis();
if (timeNow - lastTimeStateChanged > debounceDelay) {
byte newState = digitalRead(pin);
if (newState != state) {
state = newState;
lastTimeStateChanged = timeNow;
}
}
}
bool PushButton::isPressed() {
readState();
if (isPullUp) {
return (state == LOW);
} else {
return (state == HIGH);
}
}
Code for PushButton.h
/*
PushButton.h (Interface)
De-bounce function implemented
23 Feb 2024
*/
#ifndef PUSH_BUTTON_H
#define PUSH_BUTTON_H
#include <Arduino.h>
class PushButton
{
private:
byte pin;
byte state;
bool isPullUp;
bool internalPullUpActivated;
unsigned long lastTimeStateChanged;
unsigned long debounceDelay;
void readState();
public:
PushButton() {} // do not use
PushButton(byte pin, bool isPullUp, bool internalPullUpActivated);
void init();
bool isPressed();
};
#endif
Hope this helps?
thanks for posting the code.
i probably missed in the video, but how does the code know which card was selected?