Simon Says,一款儿童游戏





5.00/5 (2投票s)
类似“
引言
我正在做一个类似于很多年前孩子们玩过的旧版 Simon Says 游戏的项目。我想我 5 岁的孙子会喜欢它,而且这似乎是一个相当容易完成的项目,可以在他来 Papa 家玩的时候给他。Simon Says 游戏由两人玩,一个人给另一个人一个指示,例如:“Simon Says,单脚跳。”另一人通过服从命令来响应。我最近从 AliExpress 购买了一个 DY-HV20T 语音播放板,并认为它将是该项目的不错选择。中国产的大部分此类模块提供的文档非常少,但在这个案例中,有一份相当有用的手册。(请参阅参考文献)
该项目所需的组件列表在下表中列出。由于我最近一直在撰写有关 STMicroelectronics ARM 处理器的文章,而且这是一个非常简单的应用,我想我将继续使用 NUCLEO-C031C6 板,该板配备 STM32C031C6 Cortex-MO+ 处理器。
描述 | 购买地点 |
NUCLEO-C031C6 | st.com |
扬声器 8 欧姆 @ 10 瓦 | amazon.com |
DY-HV20T 语音播放模块 | aliexpress.com |
8GB-32GB Micro SD 卡 | 零件盒 |
SPST 瞬时开关 | 零件盒 |
10K 电阻 | 零件盒 |
1K 电阻 | 零件盒 |
10uF 电解电容器 | 零件盒 |
74HC14N 六反相器带施密特触发输入 | 零件盒 |
9V 1A 电源 | 任选 |
2 x 0.1uF 电容器 | 零件盒 |
7805 稳压器 | 零件盒 |
语音播放板(参见图 1)是一个不错的小板,具有很多功能,包括:扬声器连接、音频输出插孔、音量调节、TF 卡插槽以及用于播放 WAV 和 MP3 文件的各种模式。
我从 AliExpress 购买了这块板,但它们也在 Amazon 和 eBay 上以各种形式出售。我拥有这块板一段时间后,才想出了要用它做什么。在与我的另一半交谈后,我发现了这个板的一个绝佳应用。她给我讲了一个故事,讲的是她小时候和儿子一起玩的一个游戏,当时天气不好,他们不能在外面玩。(当时,她驻扎在德国。)
游戏是她给儿子一个指令,让他去做,比如:举起你的左臂,伸出你的右脚等。它之所以被称为“接下来是什么?”,是因为每次他完成一项任务时,他都会说:“接下来是什么?”
所以,这让我想到,如果我能编程一个处理器来以随机顺序选择预录制的指令,并使用语音播放模块,那么连接一个按钮来启动程序,同时播种一个随机数生成器,这将很容易实现,从而使指令真正随机。
如前所述,该板可以配置为在几种不同的模式下运行,对于这个应用,我选择了集成模式 0,因为它允许我进行选择,并且它会播放语音文件直到结束。从图 2 可以看出,使用集成模式 0 允许我最多使用 255 个文件,请密切注意 I/O 集成模式 0 部分图像中所示的文件名。请注意,没有 00000.mp3 文件,而是从 00001.mp3 开始。这有点误导,因为您也可以播放 WAV 文件,这也是我在此应用中主要使用的文件,尽管我确实有几个 MP3 文件,其中包含一首儿童歌曲,可以让播放器跳舞。
该板有一个 8 引脚并行端口用于选择要播放的文件,根据手册,一旦释放输入,文件将一直播放直到完成,并且在播放过程中,BUSY 引脚会保持高电平。这似乎是一个简单的过程,但我花了一段时间才弄清楚,因为结果不一致。在尝试了各种解决方案但没有成功之后,我拿出了我的 Saleae 逻辑分析仪并观看了结果。(我不能高度推荐这个设备和 Saleae 的人,他们是一群很棒的人)我遇到问题的原因是我要么误解了文档,要么就是文档错了。我没有给选择线脉冲,而是通过设置适当的引脚为低电平,等待选择完成,BUSY 引脚在播放期间为高电平,完成后变为低电平,然后将选择引脚设置回高电平来完成选择。这个过程工作得很一致!根据手册,这似乎是集成模式 1 应该做的事情?
void Play(uint8_t val)
{
// Set the appropriate bits LOW
// Wait for the BUSY line to go LOW
// Reset the select bits
GPIOA->ODR &= ~val;
if (GPIOB->IDR & BUSY_PIN)
while (GPIOB->IDR & BUSY_PIN);
GPIOA->ODR |= val;
}
硬件
图 3 显示了工作的硬件配置,唯一缺少的是电源。我使用了一个三输出电源进行调试,但如果我获得足够的兴趣,我会将所有东西放在一个漂亮的盒子中,并使用单个电源。NUCLEO 板能够通过使用 VIN 引脚和设置跳线 JP5(5V 电源选择跳线)来接受 7V-12V 电压。DY-HV20T 也使用 6V-35V 的供电电压,因此 9V 的 Walwart 可能是理想的选择,因为两者都不会消耗太多电流。
下表是 NUCLEO 板连接到 DY-HV20T 板的接线引脚分配。这是一个相当简单的设置,STM32C031C6 有足够的引脚用于并行通信,但如果使用引脚较少的处理器,则可以使用 74HC595 移位寄存器将所需引脚减少到 5 个。
描述 | NUCLEO-C031C6 | 方向 |
I/O 0 | CN7 引脚 28 (GPIOA Pin0) | 输出 |
I/O 1 | CN7 引脚 30 (GPIOA Pin1) | 输出 |
I/O 2 | CN7 引脚 37 (GPIOA Pin2) | 输出 |
I/O 3 | CN10 引脚 10 (GPIOA Pin3) | 输出 |
I/O 4 | CN7 引脚 32 (GPIOA Pin4) | 输出 |
I/O 5 | CN10 引脚 11 (GPIOA Pin5) | 输出 |
I/O 6 | CN10 引脚 13 (GPIOA Pin6) | 输出 |
I/O 7 | CN10 引脚 15 (GPIOA Pin7) | 输出 |
到 NUCLEO 的按钮输入 | CN10 引脚 17 (GPIOB Pin0) | 输入 |
BUSY | CN7 引脚 34 (GPIOB Pin1) | 输入 |
图 4 所示的按钮去抖动电路是我从 Jack Ganslle 一篇精彩的两页文章中获得的常见电路,去抖动指南 - 第一部分,或,如何用两页轻松去抖动触点。我基本上是一个软件工程师,对电子的了解刚好够危险。
软件
声音剪辑以随机方式播放,我使用 SysTick
定时器为随机数生成器提供种子。我从 CodeProject 上的一篇精彩文章中获得了 RNG,请参阅参考文献。我不得不稍微修改它,以便将其与 STM32C031C6 一起使用,原始版本使用了 C# 的 TimeDate
函数来播种生成器。我想,如果我启动 Systick 定时器并使用一个邀请片段让用户在准备好玩游戏时按下一个按钮,那么这个时间就足以播种生成器了,而且在实践中,效果相当好。列表 2 是我用来初始化 SysTick
定时器的代码。
void InitSysTick(uint ticks)
{
// Disable SysTick
SysTick->CTRL = 0;
// Set reload register
SysTick->LOAD = ticks - 1;
// Set counter to 0
SysTick->VAL = 0;
// Enable SysTick
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}
列表 3 是初始化**播放**按钮的引脚更改中断的代码。中断逻辑检测 PORTB Pin0 上的上升沿,并在代码段的后半部分触发中断例程。
void ConfigurePinChangeInterrupt()
{
__disable_irq();
// Enable GPIOB
RCC->IOPENR |= RCC_IOPENR_GPIOBEN;
// Enable SYSCFGEN
RCC->APBENR2 |= RCC_APBENR2_SYSCFGEN;
// Configure PB0 as output with pull down
GPIOB->MODER &= ~(3 << 0);
GPIOB->PUPDR |= 2;
// External interrupt select PB0
EXTI->EXTICR[0] &= ~0x00ff;
EXTI->EXTICR[0] |= EXTI_EXTICR1_EXTI0_0;
// wakeup with interrupt unmasked PB0
EXTI->IMR1 |= EXTI_IMR1_IM0;
// Rising edge select
EXTI->RTSR1 |= EXTI_RTSR1_RT0_Msk;
NVIC_EnableIRQ(EXTI0_1_IRQn);
__enable_irq();
}
// Button press interrupt handler for PB0 pin change on
// rising edge.
volatile bool g_flg = false;
void EXTI0_1_IRQHandler (void)
{
g_flg = true;
EXTI->RPR1 |= 1;
}
主例程(见列表 4)在 while
循环中等待 g_flg
被设置,然后获取一个随机数并播放相应的声音剪辑。这是一个相当简单的逻辑,我可能会考虑在等待时让处理器进入睡眠状态,但这足以用来逗乐我的孙子。
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;
}
}
}
结论
这是一个有趣的但有些令人沮丧的项目。首先,我开始使用 ATMega4809 处理器,但花了太长时间才让它工作起来,计时器运行时声音剪辑无法播放,至今仍未弄清楚原因。其次,手册非常令人困惑,我不明白为什么中国人不找一个能编写出易懂文档的人,而是使用某种翻译器,结果大多数时候都是垃圾。在这种情况下,我要么没有理解他们的意思,要么就是他们的意思错了。
参考文献
历史
- 2023 年 4 月 3 日:初始版本