/*
Simon Game for ESP32-C3 with Score display
Ported to Rust by Maverick -> https://wokwi.com/makers/maverick
*/
#![no_std]
#![no_main]
use esp32c3_hal::{
clock::ClockControl,
gpio::*,
peripherals::Peripherals,
prelude::*,
timer::TimerGroup,
Delay,
Rtc,
Rng,
};
use esp_println::println;
use esp_backtrace as _;
const MAX_GAME_LENGTH: usize = 100;
const TONES: [usize; 4] = [ 196, 262, 330, 784 ];
/* Define pin numbers for LEDs, buttons and speaker: */
/* If these are changed, the gpio used in fn main() also need to be changed. */
const DATA: u8 = 19;
const CLOCK: u8 = 9;
const LATCH: u8 = 18;
const BUZZER: u8 = 10;
const LED_0: u8 = 6;
const LED_1: u8 = 5;
const LED_2: u8 = 7;
const LED_3: u8 = 8;
const BUTTON_0: u8 = 2;
const BUTTON_1: u8 = 3;
const BUTTON_2: u8 = 1;
const BUTTON_3: u8 = 0;
/*
SIM_SPEED set for 85% simulation speed. Adjust accordingly.
40 works well for 25% simulation speed.
200 works well for 100% simulation speed.
*/
const SIM_SPEED: u64 = 160;
/* Digit table for the 7-segment display */
const DIGITS: [usize; 10] = [
0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110,
0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111
];
const DASH: usize = 0b01000000;
fn display_score(
value: usize,
game_over: bool,
delay: &mut Delay,
data: &mut GpioPin<Output<PushPull>, DATA>,
clock: &mut GpioPin<Output<PushPull>, CLOCK>,
latch: &mut GpioPin<Output<PushPull>, LATCH>
){
let right_index: usize = (value % 10).into();
let left_index: usize = (value / 10).into();
let mut left_digit: usize = DIGITS[left_index];
let mut right_digit: usize = DIGITS[right_index];
if left_index == 0
{
left_digit = 0b00000000;
}
if game_over
{
left_digit = DASH;
right_digit = DASH;
}
let mut j = 7;
for _i in 0..8
{
if (right_digit >> j) & 1 == 0
{
data.set_high().unwrap();
}
else
{
data.set_low().unwrap();
}
clock.set_high().unwrap();
delay.delay_ms(10u32);
clock.set_low().unwrap();
j -= 1;
}
j = 7;
for _i in 0..8
{
if (left_digit >> j) & 1 == 0
{
data.set_high().unwrap();
}
else
{
data.set_low().unwrap();
}
clock.set_high().unwrap();
delay.delay_ms(10u32);
clock.set_low().unwrap();
j -= 1;
}
latch.set_high().unwrap();
delay.delay_ms(10u32);
latch.set_low().unwrap();
}
/*
Plays the current sequence of notes that the user has to repeat
*/
fn play_sequence(
game_index: usize,
rtc: &mut Rtc,
delay: &mut Delay,
sequence: &mut [usize; MAX_GAME_LENGTH],
led_0: &mut GpioPin<Output<PushPull>, LED_0>,
led_1: &mut GpioPin<Output<PushPull>, LED_1>,
led_2: &mut GpioPin<Output<PushPull>, LED_2>,
led_3: &mut GpioPin<Output<PushPull>, LED_3>,
buzzer: &mut GpioPin<Output<PushPull>, BUZZER>
){
let mut j: usize = 0;
for _i in 0..game_index
{
let current_led: usize = sequence[j];
match current_led
{
0=>led_0.set_high().unwrap(),
1=>led_1.set_high().unwrap(),
2=>led_2.set_high().unwrap(),
_=>led_3.set_high().unwrap()
}
tone(TONES[current_led], SIM_SPEED, rtc, delay, buzzer);
led_0.set_low().unwrap();
led_1.set_low().unwrap();
led_2.set_low().unwrap();
led_3.set_low().unwrap();
delay.delay_ms(50u32);
j += 1;
}
}
/*
Waits until the user pressed one of the buttons,
and returns the index of that button
*/
fn read_buttons(
delay: &mut Delay,
btn_0: &mut GpioPin<Input<PullUp>, BUTTON_0>,
btn_1: &mut GpioPin<Input<PullUp>, BUTTON_1>,
btn_2: &mut GpioPin<Input<PullUp>, BUTTON_2>,
btn_3: &mut GpioPin<Input<PullUp>, BUTTON_3>
) -> usize{
loop
{
if btn_0.is_low().unwrap()
{
return 0;
}
else if btn_1.is_low().unwrap()
{
return 1;
}
else if btn_2.is_low().unwrap()
{
return 2;
}
else if btn_3.is_low().unwrap()
{
return 3;
}
delay.delay_ms(10u32);
}
}
/*
Get the user's input and compare it with the expected sequence.
*/
fn check_sequence(
game_index: usize,
rtc: &mut Rtc,
delay: &mut Delay,
sequence: &mut [usize; MAX_GAME_LENGTH],
led_0: &mut GpioPin<Output<PushPull>, LED_0>,
led_1: &mut GpioPin<Output<PushPull>, LED_1>,
led_2: &mut GpioPin<Output<PushPull>, LED_2>,
led_3: &mut GpioPin<Output<PushPull>, LED_3>,
btn_0: &mut GpioPin<Input<PullUp>, BUTTON_0>,
btn_1: &mut GpioPin<Input<PullUp>, BUTTON_1>,
btn_2: &mut GpioPin<Input<PullUp>, BUTTON_2>,
btn_3: &mut GpioPin<Input<PullUp>, BUTTON_3>,
buzzer: &mut GpioPin<Output<PushPull>, BUZZER>
) -> bool {
for i in 0..game_index
{
let expected_button = sequence[i];
let actual_button = read_buttons(delay, btn_0, btn_1, btn_2, btn_3);
match actual_button
{
0=>led_0.set_high().unwrap(),
1=>led_1.set_high().unwrap(),
2=>led_2.set_high().unwrap(),
_=>led_3.set_high().unwrap()
}
tone(TONES[actual_button], SIM_SPEED, rtc, delay, buzzer);
led_0.set_low().unwrap();
led_1.set_low().unwrap();
led_2.set_low().unwrap();
led_3.set_low().unwrap();
delay.delay_ms(50u32);
if expected_button != actual_button
{
return false;
}
}
return true;
}
/*
Play the game over sequence, and report the game score
*/
fn game_over(
game_index: usize,
rtc: &mut Rtc,
delay: &mut Delay,
buzzer: &mut GpioPin<Output<PushPull>, 10>
){
println!("Game over! your score: {}", game_index - 1);
delay.delay_ms(200u32);
// Play a Wah-Wah-Wah-Wah sound
tone(622, SIM_SPEED * 2, rtc, delay, buzzer);
tone(587, SIM_SPEED * 2, rtc, delay, buzzer);
tone(554, SIM_SPEED * 2, rtc, delay, buzzer);
for _i in 0..10
{
let mut j: i32 = -10;
for _k in 0..20
{
tone(
(523 + j).try_into().unwrap(),
SIM_SPEED / 20,
rtc,
delay,
buzzer
);
j += 1;
}
}
}
/* Plays a hooray sound whenever the user finishes a level */
fn play_level_up_sound(
rtc: &mut Rtc,
delay: &mut Delay,
buzzer: &mut GpioPin<Output<PushPull>, BUZZER>
){
tone(330, SIM_SPEED, rtc, delay, buzzer);
tone(392, SIM_SPEED, rtc, delay, buzzer);
tone(659, SIM_SPEED, rtc, delay, buzzer);
tone(523, SIM_SPEED, rtc, delay, buzzer);
tone(587, SIM_SPEED, rtc, delay, buzzer);
tone(784, SIM_SPEED, rtc, delay, buzzer);
}
/* Replacement for Arduino tone() function. */
fn tone(
note: usize,
duration: u64,
rtc: &mut Rtc,
delay: &mut Delay,
buzzer: &mut GpioPin<Output<PushPull>, 10>
){
let frequency: u32 = (1000000 / note).try_into().unwrap();
let duty = frequency / 2;
let start_time = rtc.get_time_ms();
while rtc.get_time_ms() - start_time < duration
{
buzzer.set_high().unwrap();
delay.delay_us(duty);
buzzer.set_low().unwrap();
delay.delay_us(frequency - duty);
}
}
/* Generates a random color to add to the sequence. */
fn random_color( rng: &mut Rng ) -> usize {
let rand = rng.random();
if rand < u32::MAX / 4
{
return 0;
}
else if rand > u32::MAX / 4 && rand < u32::MAX / 2
{
return 1;
}
else if rand > u32::MAX / 2 && rand < (u32::MAX / 2) + (u32::MAX / 4)
{
return 2;
}
else
{
return 3;
}
}
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let mut system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
/* Disable the watchdog timers. */
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(
peripherals.TIMG0,
&clocks,
&mut system.peripheral_clock_control,
);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(
peripherals.TIMG1,
&clocks,
&mut system.peripheral_clock_control,
);
let mut wdt1 = timer_group1.wdt;
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
/*----------------------------------------------------*/
/* Define pin numbers for LEDs, buttons and speaker: */
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let mut data = io.pins.gpio19.into_push_pull_output();
let mut clock = io.pins.gpio9.into_push_pull_output();
let mut latch = io.pins.gpio18.into_push_pull_output();
let mut buzzer = io.pins.gpio10.into_push_pull_output();
let mut led_0 = io.pins.gpio6.into_push_pull_output();
let mut led_1 = io.pins.gpio5.into_push_pull_output();
let mut led_2 = io.pins.gpio7.into_push_pull_output();
let mut led_3 = io.pins.gpio8.into_push_pull_output();
let mut btn_0 = io.pins.gpio2.into_pull_up_input();
let mut btn_1 = io.pins.gpio3.into_pull_up_input();
let mut btn_2 = io.pins.gpio1.into_pull_up_input();
let mut btn_3 = io.pins.gpio0.into_pull_up_input();
/*----------------------------------------------------*/
/* Setup RNG and delay capability */
let mut rng = Rng::new(peripherals.RNG);
let mut delay = Delay::new(&clocks);
/* Global variables - store the game state */
let mut game_sequence: [usize; MAX_GAME_LENGTH] = [0; MAX_GAME_LENGTH];
let mut game_index: usize = 0;
let mut initialized = false;
/* The main game loop */
loop
{
if !initialized
{
let delay_time: u32 = (SIM_SPEED * 10).try_into().unwrap();
delay.delay_ms(delay_time);
initialized = true;
}
display_score(
game_index,
false,
&mut delay,
&mut data,
&mut clock,
&mut latch
);
/* Add a random color to the end of the sequence */
game_sequence[game_index] = random_color(&mut rng);
game_index += 1;
if game_index >= MAX_GAME_LENGTH
{
game_index = MAX_GAME_LENGTH - 1;
}
play_sequence(
game_index,
&mut rtc,
&mut delay,
&mut game_sequence,
&mut led_0,
&mut led_1,
&mut led_2,
&mut led_3,
&mut buzzer
);
if !check_sequence(
game_index,
&mut rtc,
&mut delay,
&mut game_sequence,
&mut led_0,
&mut led_1,
&mut led_2,
&mut led_3,
&mut btn_0,
&mut btn_1,
&mut btn_2,
&mut btn_3,
&mut buzzer
){
game_over(
game_index,
&mut rtc,
&mut delay,
&mut buzzer
);
display_score(game_index,
true,
&mut delay,
&mut data,
&mut clock,
&mut latch
);
let delay_time: u32 = (SIM_SPEED * 10).try_into().unwrap();
delay.delay_ms(delay_time);
game_index = 0;
}
let delay_time: u32 = (SIM_SPEED * 2).try_into().unwrap();
delay.delay_ms(delay_time);
if game_index > 0
{
play_level_up_sound(&mut rtc, &mut delay, &mut buzzer);
let delay_time: u32 = (SIM_SPEED * 2).try_into().unwrap();
delay.delay_ms(delay_time);
}
let delay_time: u32 = SIM_SPEED.try_into().unwrap();
delay.delay_ms(delay_time);
}
}
Loading
esp32-c3-devkitm-1
esp32-c3-devkitm-1