I'm working on a Simon Says like project for my 5 year old grandson that uses voice commands to instruct him to do things like; clap your hands, raise your left arm, etc.
Introduction
I'm working on a project similar to the old Simon Says game that kids used to play many years ago. I thought my 5 year old grandson would like it and it seemed a fairly easy project to throw together for when he comes to visit Papa. The Simon Says game is played by two people and one person gives the other an instruction such as: "Simon Says, Hop on one foot." and the other person responds by obeying the command. I had recently purchased a DY-HV20T Voice Playback board from AliExpress and thought it would be a good choice for the project. Most modules of this kind that come out of China provide very little in the way of documentation, but in this case, there is a fairly useful manual. (See references)
A list of components needed for this project is listed in the following table. Since I've been writing lately about the STMicroelectronics ARM processors and this is such a simple application I thought that I would continue the series using the NUCLEO-C031C6 board featuring the STM32C031C6 Cortex-MO+ processor.
Description | Where to Buy |
NUCLEO-C031C6 | st.com |
Speaker 8 OHMS @ 10 WATTS | amazon.com |
DY-HV20T Voice Playback Module | aliexpress.com |
8GB-32GB Micro SD Card | Parts Bin |
SPST Momentary Switch | Parts Bin |
10K resistor | Parts Bin |
1K resistor | Parts Bin |
10uF electrolytic capacitor | Parts Bin |
74HC14N Hex Inverter with Schmitt-Trigger inputs | Parts Bin |
9V 1A Power Supply | Your choice |
2 x .1uF capacitors | Parts Bin |
7805 Voltage Regulator | Parts Bin |
The Voice Playback board, see Figure 1, is a nice little board that has a lot of functionality including: a connection for speakers, a jack for audio output, volume adjustment, TF Card socket and various modes for playing WAV and MP3 files.
Figure 1: DY-HV20T Voice Playback Board
I got this board from AliExpress but they are available in various forms on Amazon and eBay. I had the board for a while before I figured out what I wanted to do with it and after talking with my significant other, I discovered a great application for the board. She told me a story of a game she used to play with her son when he was a small child and the weather was such that they couldn't do anything outside. (At the time, she was stationed in Germany.)
The game was played by her giving an instruction for her son to do, things like: hold up your left arm, stick out your right foot, etc. It got the name "What's Next?" because every time he finished doing a task, he would say "What's Next?".
So, it got me thinking that if I could program a processor to select pre-recorded questions in a random order using the Voice Playback module that it wouldn't be hard to hook up a button to start the program and at the same time, seed a Random Number generator thus making the instructions truly random.
As I stated earlier, the board can be configured to run in several different modes and for this application, I chose the Integrated Mode 0 mode because it allowed me to make a selection and it would play the voice file until it ended. Referring to Figure 2, it can be seen that using Integrated Mode 0 allows me to use a maximum of 255 files, pay close attention to the file names as shown in the image in the I/O Integrated Mode 0 section. Notice that there is no 00000.mp3 available, starts at 00001.mp3. This is a tad misleading because you can also play WAV files which is what I mainly use in this application, although I do have a couple of MP3 files that have a child's tune that the player can dance too.
Figure 2: DY-HV20T Work Mode Configuration
The board has a 8 pin parallel port to select the file to play and according to the manual once the input is released, the file will play until it finishes and while it is playing, it holds the BUSY pin high. This seems like it would be a simple process but it took me a while to figure out because results were not consistent. After struggling with this for some time, trying various solutions without any luck, I got out my Saleae Logic Analyzer and viewed the results. (I can't recommend this device and the people at Saleae high enough, great bunch) The reason I was having problems is that either I misunderstood the documentation or it was just plain wrong. Instead of pulsing the selection lines, I ended up making the selection, by setting the appropriate pins LOW waiting until the selection finished, BUSY pin is HIGH during play and goes LOW when done, then setting the selection pins back HIGH. This process works consistently! According to the manual, this is what Integrated Mode 1 is supposed to do?
void Play(uint8_t val)
{
GPIOA->ODR &= ~val;
if (GPIOB->IDR & BUSY_PIN)
while (GPIOB->IDR & BUSY_PIN);
GPIOA->ODR |= val;
}
Listing 1. Code to Play the current selection
Hardware
Figure 3 shows the working hardware configuration, the only thing missing is the power supply. I'm using a triple output supply to debug but if I get enough interest, I'll put everything in a nice box with a single power supply. The NUCLEO board has the ability to accept 7V-12V by using the VIN pin and setting jumper JP5, the 5V Power Selection jumper. The DY-HV20T also uses a 6V-35V supply voltage so a 9V Walwart will probably be ideal since neither draw very much current.
Figure 3: Complete circuit
The table below is the pinout for wiring the NUCLEO board to the DY-HV20T board. It's a pretty simple setup, the STM32C031C6 has plenty of pins available for the parallel communication but if another processor is used with less pins available a 74HC595 Shift Register could be used to reduce the number of pins needed to 5.
Description | NUCLEO-C031C6 | Direction |
I/O 0 | CN7 Pin 28 (GPIOA Pin0) | Output |
I/O 1 | CN7 Pin 30 (GPIOA Pin1) | Output |
I/O 2 | CN7 Pin 37 (GPIOA Pin2) | Output |
I/O 3 | CN10 Pin 10 (GPIOA Pin3) | Output |
I/O 4 | CN7 Pin 32 (GPIOA Pin4) | Output |
I/O 5 | CN10 Pin 11 (GPIOA Pin5) | Output |
I/O 6 | CN10 Pin 13 (GPIOA Pin6) | Output |
I/O 7 | CN10 Pin 15 (GPIOA Pin7) | Output |
Button input to NUCLEO | CN10 Pin 17 (GPIOB Pin0) | Input |
BUSY | CN7 Pin 34 (GPIOB Pin1) | Input |
The button debounce circuit, shown in Figure 4, is a common circuit that I got from an excellent two page article by Jack Ganslle, A Guide to Debouncing - Part 1, or, How to Debounce a Contact in Two Easy Pages. I'm basically a software guy with just enough understanding of electronics to be dangerous.
Figure 4: Momentary switch debounce circuit
Software
The sound clips are played in a random manner and I use the SysTick
timer to provide the seed for the Random Number Generator. I got the RNG from an excellent article on CodeProject, see references. I had to modify it a tad to get it to work with the STM32C031C6, the original version used the C# TimeDate
function to seed the generator. I thought that if I started the Systick timer and used a invitation clip to have the user press a button when they were ready to play, the time would be random enough to seed the generator and in practice, it works rather well. Listing 2 is the code I used to initialize the SysTick
timer.
void InitSysTick(uint ticks)
{
SysTick->CTRL = 0;
SysTick->LOAD = ticks - 1;
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}
Listing 2. Initialize the SysTick timer
Listing 3 is the code to Initialize the Pin Change interrupt for the Play button. The interrupt logic detects the rising edge on PORTB Pin0 and fires the interrupt routine in the second half of the code segment.
void ConfigurePinChangeInterrupt()
{
__disable_irq();
RCC->IOPENR |= RCC_IOPENR_GPIOBEN;
RCC->APBENR2 |= RCC_APBENR2_SYSCFGEN;
GPIOB->MODER &= ~(3 << 0);
GPIOB->PUPDR |= 2;
EXTI->EXTICR[0] &= ~0x00ff;
EXTI->EXTICR[0] |= EXTI_EXTICR1_EXTI0_0;
EXTI->IMR1 |= EXTI_IMR1_IM0;
EXTI->RTSR1 |= EXTI_RTSR1_RT0_Msk;
NVIC_EnableIRQ(EXTI0_1_IRQn);
__enable_irq();
}
volatile bool g_flg = false;
void EXTI0_1_IRQHandler (void)
{
g_flg = true;
EXTI->RPR1 |= 1;
}
Listing 3. Initialize the Pin Change Interrupt for the Play button
The Main routine, in listing 4, waits in the while
loop for the g_flg
to get set, then grabs a Random number and plays the corresponding sound clip. It's pretty simple logic and I will probably looking into putting the processor to sleep while waiting, but this will work for a one off to amuse my grandson.
int main(void)
{
uint r = 1;
ConfigurePinChangeInterrupt();
InitRandom();
SimonInit();
Play(INTRO_SOUND);
SetSeedFromTimer();
while (1)
{
if (g_flg)
{
r = GetUintInRange(SOUND_FILES_MAX);
Play(r);
g_flg = false;
}
}
}
Listing 4. Main routine
Conclusion
This has been a fun project; but, a little frustrating. First, I started to use an ATMega4809 processor and spent way too long trying to get it to work, the sound clip wouldn't play when the timer was running, still haven't figured out why. Second, the manual is very confusing, I don't understand why the Chinese don't get someone that can create documentation that is understandable, instead they use some kind of translator and what comes out is garbage most of the time. In this case, I either didn't understand their meaning or they were just plain wrong.
References
History
- 3rd April, 2023: Initial version