diff --git a/include/display.h b/include/display.h index 814c9d8..b37d620 100644 --- a/include/display.h +++ b/include/display.h @@ -1,8 +1,14 @@ #include +enum TaskOpt { + DISPLAY_CLEAR, + DISPLAY_WRITE +}; + typedef struct { - char* text; + enum TaskOpt taskType; + char text[17]; int page; } displayQueue_t; diff --git a/include/main.h b/include/main.h new file mode 100644 index 0000000..dfbbe80 --- /dev/null +++ b/include/main.h @@ -0,0 +1,20 @@ +#define sda_pin 21 +#define scl_pin 20 +#define frequency 400000 + +void setup_i2c(); +void start_game(); +void cycle_choice(); +void confirm_choice(); + +enum GameState { + STATE_MENU, + STATE_PLAYING, + STATE_OVER_LOST, + STATE_OVER_WIN, +}; + +enum ActionType { + Action_Select, + Action_Confirm, +}; \ No newline at end of file diff --git a/include/sound.h b/include/sound.h index ea49d3f..380be86 100644 --- a/include/sound.h +++ b/include/sound.h @@ -2,12 +2,22 @@ #define SAMPLE_RATE 8000 +enum soundOpts { + SOUND_MAIN_MENU, + SOUND_DRAW, + SOUND_LOST, + SOUND_WIN, + SOUND_SELECT +}; + void init_sound(); -void sample_start(); -void sample_stop(); - -void play_main_menu_sound(); -void play_draw_sound(); -void play_lost_sound(); -void play_win_sound(); -void play_select_sound(); \ No newline at end of file + +// void play_main_menu_sound(); +// void play_draw_sound(); +// void play_lost_sound(); +// void play_win_sound(); +// void play_select_sound(); + + +void play_sound(enum soundOpts soundType); + diff --git a/platformio.ini b/platformio.ini index bffbd0f..2244389 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,3 +14,5 @@ board = esp32-c6-devkitc-1 framework = espidf monitor_speed = 115200 board_upload.flash_size=8MB +monitor_filters = esp32_exception_decoder +build_type = release diff --git a/sdkconfig.esp32-c6-devkitc-1.old b/sdkconfig.esp32-c6-devkitc-1.old index 6325a86..c84a508 100644 --- a/sdkconfig.esp32-c6-devkitc-1.old +++ b/sdkconfig.esp32-c6-devkitc-1.old @@ -1253,9 +1253,9 @@ CONFIG_ESP_SYSTEM_PANIC_GDBSTUB=y CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE=y CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y -CONFIG_ESP_SYSTEM_NO_BACKTRACE=y +# CONFIG_ESP_SYSTEM_NO_BACKTRACE is not set # CONFIG_ESP_SYSTEM_USE_EH_FRAME is not set -# CONFIG_ESP_SYSTEM_USE_FRAME_POINTER is not set +CONFIG_ESP_SYSTEM_USE_FRAME_POINTER=y # # Memory protection @@ -1287,7 +1287,7 @@ CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 CONFIG_ESP_TASK_WDT_EN=y CONFIG_ESP_TASK_WDT_INIT=y # CONFIG_ESP_TASK_WDT_PANIC is not set -CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_TIMEOUT_S=10 CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y # CONFIG_ESP_PANIC_HANDLER_IRAM is not set # CONFIG_ESP_DEBUG_STUBS_ENABLE is not set diff --git a/src/btnctrl.c b/src/btnctrl.c index d544c50..fbdaf12 100644 --- a/src/btnctrl.c +++ b/src/btnctrl.c @@ -25,17 +25,17 @@ void btnctrl_init() { // Register button events for select and confirm actions void btnctrl_register_event(button_cb_t select_cb, button_cb_t confirm_cb) { - if (select_cb != NULL) { + if (select_cb != NULL) iot_button_register_cb(select_btn, BUTTON_SINGLE_CLICK, NULL, select_cb, NULL); - } - if (confirm_cb != NULL) { + if (confirm_cb != NULL) iot_button_register_cb(confirm_btn, BUTTON_SINGLE_CLICK, NULL, confirm_cb, NULL); - } } // Unregister button events (used when transitioning between screens to prevent unintended actions) void btnctrl_unregister_event() { - iot_button_unregister_cb(select_btn, BUTTON_SINGLE_CLICK, NULL); - iot_button_unregister_cb(confirm_btn, BUTTON_SINGLE_CLICK, NULL); + if(iot_button_count_event_cb(select_btn, BUTTON_SINGLE_CLICK) > 0) + iot_button_unregister_cb(select_btn, BUTTON_SINGLE_CLICK, NULL); + if(iot_button_count_event_cb(confirm_btn, BUTTON_SINGLE_CLICK) > 0) + iot_button_unregister_cb(confirm_btn, BUTTON_SINGLE_CLICK, NULL); } diff --git a/src/display.c b/src/display.c index 3038487..1c116c6 100644 --- a/src/display.c +++ b/src/display.c @@ -21,25 +21,22 @@ void display_write_queue(void *pvParameters); void display_init() { // Init stuff - writePageQueue = xQueueCreate(32, sizeof(displayQueue_t)); + writePageQueue = xQueueCreate(32, sizeof(displayQueue_t*)); // Addr Stuff dev._address = 0x3C; dev._flip = false; ssd1306_init(&dev, 128, 64); display_clear(); - xTaskCreate(display_write_queue, "display_write_queue", configMINIMAL_STACK_SIZE*1.5, NULL, 5, NULL); + xTaskCreate(display_write_queue, "display_write_queue", 4096, NULL, 5, NULL); } // Clean the display void display_clear() { - ssd1306_clear_screen(&dev, false); - // for(int i=0;i<5;i++) { - // displayQueue_t data; - // data.text = malloc(16); - // memset(data.text, 0x0, 16); - // data.page = i; - // xQueueSend(writePageQueue, &data, 0); - // } + //ssd1306_clear_screen(&dev, false); + displayQueue_t* data = calloc(1, sizeof(displayQueue_t)); + data->taskType = DISPLAY_CLEAR; + if(xQueueSend(writePageQueue, &data, 0) != pdPASS) + free(data); } // Render text on the display @@ -50,30 +47,27 @@ void display_text(char* text) { // Write text to a specific line on the display (isCenter is used to center the text on the line) void display_write_page(const char* text, int page, int isCenter) { + displayQueue_t* data = calloc(1, sizeof(displayQueue_t)); + data->taskType = DISPLAY_WRITE; + data->page = page; + // Get the length and allocate the space size_t text_len = strlen(text); // Each line only supports 16 characters - char* strArr = (char*)malloc(sizeof(char)*16); text_len = text_len > 16 ? 16 : text_len; // Check if the text needs to be centered if(isCenter == 1) { int padLen = ceil((16 - text_len) / 2); for(int i = 0; i < padLen; i++) - strArr[i] = ' '; + data->text[i] = ' '; for(int i = padLen; i < padLen+text_len; i++) - strArr[i] = text[i - padLen]; + data->text[i] = text[i - padLen]; text_len+=padLen; - - // Fill in the rest of the string with byte 0 - for(int i = text_len; i < 16; i++) - strArr[i] = '\0'; - } else strncpy(strArr, text, 16); + } else strncpy(data->text, text, 16); // Add it to the queue - displayQueue_t data; - data.text = strArr; - data.page = page; - xQueueSend(writePageQueue, &data, 0); + if(xQueueSend(writePageQueue, &data, 0) != pdPASS) + free(data); } /* @@ -82,15 +76,27 @@ Executing ssd1306 display command simultaneously causes the display to glitch ou */ void display_write_queue(void *pvParameters) { while(1) { - displayQueue_t data; + displayQueue_t* data; if(xQueueReceive(writePageQueue, &data, portMAX_DELAY) == pdTRUE) { - /* Handler Stuff Here */ + // Dedicated Clear Task + if(data->taskType == DISPLAY_CLEAR) { + ssd1306_clear_screen(&dev, false); + free(data); + continue; + } + + /* Write Task */ + if(data->taskType != DISPLAY_WRITE) { + free(data); + continue; // Might be bad memory data? + } + char zeros[16] = {0}; // The reason to write 0s is because the display sometime persist the text from previous queue and 0s somehow stops this issue - ssd1306_display_text(&dev, data.page, zeros, 16, false); + ssd1306_display_text(&dev, data->page, zeros, 16, false); vTaskDelay(pdMS_TO_TICKS(10)); - ssd1306_display_text(&dev, data.page, data.text, 16, false); - free(data.text); + ssd1306_display_text(&dev, data->page, data->text, 16, false); + free(data); } else vTaskDelay(pdMS_TO_TICKS(5)); diff --git a/src/main.c b/src/main.c index fffcb33..d50e54b 100644 --- a/src/main.c +++ b/src/main.c @@ -1,3 +1,4 @@ +#include "main.h" #include #include #include @@ -8,19 +9,50 @@ #include #include -#define sda_pin 21 -#define scl_pin 20 -#define frequency 400000 - -void setup_i2c(); -void start_game(); -void cycle_choice(); -void confirm_choice(); - rps_choice playerChoice = ROCK; // Default choice is rock, player can cycle through choices +enum GameState currentState = STATE_MENU; // Game flow: main menu -> game screen -> result screen -> back to game screen (if not win) +QueueHandle_t actionQueue; + +void queueHandle() { + while(1) { + enum ActionType actionOpt; + if(xQueueReceive(actionQueue, &actionOpt, portMAX_DELAY) == pdTRUE) { + if(actionOpt == Action_Confirm) { + switch(currentState) { + case STATE_MENU: + start_game(); + break; + case STATE_PLAYING: + confirm_choice(); + break; + case STATE_OVER_LOST: + start_game(); + break; + case STATE_OVER_WIN: + break; + } + } + if(actionOpt == Action_Select && currentState == STATE_PLAYING) + cycle_choice(); + } + else + vTaskDelay(pdMS_TO_TICKS(5)); + } +} + +void selectHandle() { + enum ActionType curAction = Action_Select; + xQueueSend(actionQueue, &curAction, 0); +} + +void confirmHandle() { + enum ActionType curAction = Action_Confirm; + xQueueSend(actionQueue, &curAction, 0); +} + void app_main() { // Initialize all components ESP_LOGI("main", "Starting application"); @@ -29,13 +61,17 @@ void app_main() { display_init(); btnctrl_init(); init_sound(); + + // Queue Managers + actionQueue = xQueueCreate(8, sizeof(enum ActionType)); + xTaskCreate(queueHandle, "main_control_handle", 4096, NULL, 5, NULL); set_led(1); // Turn on LED to indicate device is ready // Initiate main menu init_main_menu(); - play_main_menu_sound(); - btnctrl_register_event(NULL, start_game); + play_sound(SOUND_MAIN_MENU); + btnctrl_register_event(selectHandle, confirmHandle); } void setup_i2c() { @@ -56,26 +92,21 @@ void setup_i2c() { // Initialize game screen and register button events for cycling and confirming choice void start_game() { - btnctrl_unregister_event(); // Clear main menu event - init_game_screen(playerChoice); - play_select_sound(); - - btnctrl_register_event(cycle_choice, confirm_choice); + play_sound(SOUND_SELECT); + currentState = STATE_PLAYING; } // Update game screen with current player choice when cycle button is pressed void cycle_choice() { playerChoice = (playerChoice + 1) % 3; // Cycle through ROCK, PAPER, SCISSORS update_game_screen(playerChoice); - play_select_sound(); + play_sound(SOUND_SELECT); } // Determine and display the game outcome void confirm_choice() { - btnctrl_unregister_event(); // Clear start_game events - rps_choice cpuChoice = (rps_choice)(esp_random() % 3); // Generate random CPU choice rps_outcome outcome = determine_rps_outcome(playerChoice, cpuChoice); // Determine game outcome init_result_screen(outcome, playerChoice, cpuChoice); // Update screen with outcome and choices @@ -83,15 +114,16 @@ void confirm_choice() { // Play sound based on outcome and register event to start new game if not a win switch (outcome) { case WIN: - play_win_sound(); + play_sound(SOUND_WIN); + currentState = STATE_OVER_WIN; break; case LOSE: - play_lost_sound(); - btnctrl_register_event(NULL, start_game); + play_sound(SOUND_LOST); + currentState = STATE_OVER_LOST; break; case DRAW: - play_draw_sound(); - btnctrl_register_event(NULL, start_game); + play_sound(SOUND_DRAW); + currentState = STATE_OVER_LOST; break; } diff --git a/src/sound.c b/src/sound.c index ccff8e4..6db238d 100644 --- a/src/sound.c +++ b/src/sound.c @@ -5,16 +5,48 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include #define GPIO 7 #define SAMPLE_PERIOD_US (1000000 / SAMPLE_RATE) +QueueHandle_t soundQueue; -void pcm_playback_cb(); -struct playback_data { - uint8_t * buffer; - size_t buffer_size; - uint32_t curIndex; -}; +void play_main_menu_sound(); +void play_draw_sound(); +void play_lost_sound(); +void play_win_sound(); +void play_select_sound(); + +void play_sound(enum soundOpts soundType) { + xQueueSend(soundQueue, &soundType, 0); +} + +void sound_queue_process() { + while(1) { + enum soundOpts soundOpt; + if(xQueueReceive(soundQueue, &soundOpt, portMAX_DELAY) == pdTRUE) { + switch(soundOpt) { + case SOUND_MAIN_MENU: + play_main_menu_sound(); + break; + case SOUND_DRAW: + play_draw_sound(); + break; + case SOUND_LOST: + play_lost_sound(); + break; + case SOUND_WIN: + play_win_sound(); + break; + case SOUND_SELECT: + play_select_sound(); + break; + } + } + else + vTaskDelay(pdMS_TO_TICKS(5)); + } +} void init_sound() { ledc_timer_config_t timerconf = { @@ -35,6 +67,10 @@ void init_sound() { }; ledc_timer_config(&timerconf); ledc_channel_config(&channel); + + // Audio Queue Handles + soundQueue = xQueueCreate(8, sizeof(enum soundOpts)); + xTaskCreate(sound_queue_process, "sound_queue_process", 2048, NULL, 5, NULL); } static void play_tone(int freq, int duration_ms)