65.9K
CodeProject 正在变化。 阅读更多。
Home

Simon Says,一款儿童游戏

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2023 年 4 月 3 日

CPOL

7分钟阅读

viewsIcon

6845

downloadIcon

61

类似“Simon Says” 的项目

引言

我正在做一个类似于很多年前孩子们玩过的旧版 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 文件的各种模式。

图 1:DY-HV20T 语音播放板

我从 AliExpress 购买了这块板,但它们也在 Amazon 和 eBay 上以各种形式出售。我拥有这块板一段时间后,才想出了要用它做什么。在与我的另一半交谈后,我发现了这个板的一个绝佳应用。她给我讲了一个故事,讲的是她小时候和儿子一起玩的一个游戏,当时天气不好,他们不能在外面玩。(当时,她驻扎在德国。)

游戏是她给儿子一个指令,让他去做,比如:举起你的左臂,伸出你的右脚等。它之所以被称为“接下来是什么?”,是因为每次他完成一项任务时,他都会说:“接下来是什么?”

所以,这让我想到,如果我能编程一个处理器来以随机顺序选择预录制的指令,并使用语音播放模块,那么连接一个按钮来启动程序,同时播种一个随机数生成器,这将很容易实现,从而使指令真正随机。

如前所述,该板可以配置为在几种不同的模式下运行,对于这个应用,我选择了集成模式 0,因为它允许我进行选择,并且它会播放语音文件直到结束。从图 2 可以看出,使用集成模式 0 允许我最多使用 255 个文件,请密切注意 I/O 集成模式 0 部分图像中所示的文件名。请注意,没有 00000.mp3 文件,而是从 00001.mp3 开始。这有点误导,因为您也可以播放 WAV 文件,这也是我在此应用中主要使用的文件,尽管我确实有几个 MP3 文件,其中包含一首儿童歌曲,可以让播放器跳舞。

图 2:DY-HV20T 工作模式配置

该板有一个 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;
}    
列表 1. 播放当前选择的代码

硬件

图 3 显示了工作的硬件配置,唯一缺少的是电源。我使用了一个三输出电源进行调试,但如果我获得足够的兴趣,我会将所有东西放在一个漂亮的盒子中,并使用单个电源。NUCLEO 板能够通过使用 VIN 引脚和设置跳线 JP5(5V 电源选择跳线)来接受 7V-12V 电压。DY-HV20T 也使用 6V-35V 的供电电压,因此 9V 的 Walwart 可能是理想的选择,因为两者都不会消耗太多电流。

图 3:完整电路

下表是 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 一篇精彩的两页文章中获得的常见电路,去抖动指南 - 第一部分,或,如何用两页轻松去抖动触点。我基本上是一个软件工程师,对电子的了解刚好够危险。

图 4:瞬时开关去抖动电路

软件

声音剪辑以随机方式播放,我使用 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;
} 
列表 2. 初始化 SysTick 定时器

列表 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;
}    
列表 3. 初始化播放按钮的引脚更改中断

主例程(见列表 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;
		}
	}
} 
列表 4. 主例程

结论

这是一个有趣的但有些令人沮丧的项目。首先,我开始使用 ATMega4809 处理器,但花了太长时间才让它工作起来,计时器运行时声音剪辑无法播放,至今仍未弄清楚原因。其次,手册非常令人困惑,我不明白为什么中国人不找一个能编写出易懂文档的人,而是使用某种翻译器,结果大多数时候都是垃圾。在这种情况下,我要么没有理解他们的意思,要么就是他们的意思错了。

参考文献

历史

  • 2023 年 4 月 3 日:初始版本
© . All rights reserved.