/** * \brief Show explanation screen * * This function draws an explanation screen, and is run if the applications are * started without the jumpers described in the documentation attached. */ static void show_explain_splash(void) { struct keyboard_event input; /* Clear screen */ gfx_mono_draw_filled_rect(0, 0, 128, 32, GFX_PIXEL_CLR); gfx_mono_draw_string("Class B Demonstration", 1, 4, &sysfont); gfx_mono_draw_string("See Application note", 4, 12, &sysfont); gfx_mono_draw_string("AVR1610", 40, 20, &sysfont); /* Any key will exit the loop */ while (true) { oven_wdt_periodic_reset(); keyboard_get_key_state(&input); if (input.type == KEYBOARD_RELEASE) { break; } } /* Clear screen */ gfx_mono_draw_filled_rect(0, 0, 128, 32, GFX_PIXEL_CLR); gfx_mono_draw_string("Attach jumpers betwe-", 0, 4, &sysfont); gfx_mono_draw_string("en ADC2-ADC4 on J2,", 0, 12, &sysfont); gfx_mono_draw_string("and RXD-SS on J1", 0, 20, &sysfont); /* Any key will exit the loop */ while (true) { oven_wdt_periodic_reset(); } }
/** * \brief Show button names on display * * This function shows the user what the different on-board buttons do. When * the user presses a button, this function exits and the application can * continue. */ static void show_button_splash(void) { struct keyboard_event input; /* Clear screen */ gfx_mono_draw_filled_rect(0, 0, 128, 32, GFX_PIXEL_CLR); gfx_mono_draw_filled_circle(6, 6, 3, GFX_PIXEL_SET, GFX_WHOLE); gfx_mono_draw_filled_circle(6, 24, 3, GFX_PIXEL_SET, GFX_WHOLE); gfx_mono_draw_filled_circle(122, 6, 3, GFX_PIXEL_SET, GFX_WHOLE); gfx_mono_draw_filled_circle(122, 24, 3, GFX_PIXEL_SET, GFX_WHOLE); gfx_mono_draw_string("Oven power/", 50, 0, &sysfont); gfx_mono_draw_string("Up", 94, 8, &sysfont); gfx_mono_draw_string("Down", 90, 22, &sysfont); gfx_mono_draw_string("Menu/", 12, 0, &sysfont); gfx_mono_draw_string("Back", 12, 8, &sysfont); gfx_mono_draw_string("Pot/enter", 12, 22, &sysfont); /* Any key will exit the loop */ while (true) { oven_wdt_periodic_reset(); keyboard_get_key_state(&input); if (input.type == KEYBOARD_RELEASE) { break; } } }
/** * \brief Simulate corruption of a part of Flash * * This function simulates corruption of the Flash by modifying one of the * strings in the error insertion menu. More specifically it replaces the * "-- Error insertion --" menu title with "Out of cheese!". If called again, * the original string is reverted. */ static void oven_classb_flash_corrupter(void) { /* Flash page base address of the menu title string */ flash_addr_t page_addr = ((uintptr_t)error_menu_title / FLASH_PAGE_SIZE) * FLASH_PAGE_SIZE; /* Address within the page */ uintptr_t string_addr = (uintptr_t)error_menu_title % FLASH_PAGE_SIZE; /* New string that we will replace it with */ const char newstring[] = " Out of cheese! "; const char oldstring[] = "-- Error insertion --"; /* Load page with strings from flash into our buffer */ nvm_flash_flush_buffer(); for (uint16_t addr = 0; addr < FLASH_PAGE_SIZE / 2; addr++) { flash_scramble_buf[addr] = nvm_flash_read_word((addr * 2) + page_addr); oven_wdt_periodic_reset(); } /* Check which string is the current and change it accordingly */ if (strcmp((char *)flash_scramble_buf + string_addr, oldstring) == 0) { strcpy((char *)flash_scramble_buf + string_addr, newstring); } else { strcpy((char *)flash_scramble_buf + string_addr, oldstring); } oven_wdt_periodic_reset(); /* Fill page buffer with our buffered data, then write it to flash */ for (uint16_t addr = 0; addr < FLASH_PAGE_SIZE / 2; addr++) { nvm_flash_load_word_to_buffer(addr * 2, flash_scramble_buf[addr]); oven_wdt_periodic_reset(); } nvm_flash_atomic_write_app_page(page_addr); }
/** * \brief Show menu for error insertion and handle user's selection. * * This function changes device configurations and does some hacks to make * the device behave incorrectly. * * The menu entries are * - Change clock frequency: Changes the peripheral clock divider, simulating * that the clock system has malfunctioned. This should be detected by the * Class B frequency consistency test. * * - Mess with test timer: Changes how often periodic tests are performed, * simulating an error with an interrupt timer. This should be detected by * the Class B interrupt monitor. * * - Change a Flash section: Changes the string for the menu title stored in * program memory to "Out of cheese", simulating Flash memory corruption. * This can be changed back by selecting the menu item again. This should be * detected by the Class B Flash CRC test. * * - Scramble SRAM section: Starts a continuous DMA transfer in the background * to a memory location, simulating transient SRAM corruption. This should * be detected by the periodic and power-on Class B SRAM test. * * - Enter infinite loop: Simulates a runaway program counter by looping * forever. This should be detected by the watchdog timer system which is * tested on device power-up. * * - Change ADC reference: Enables a callback function for the ADC, which will * change the voltage reference after the next completed conversion. This * will cause the analog IO test to fail when user turns up the power to the * plate. */ void oven_classb_error_insertion(void) { uint8_t menu_status; struct keyboard_event input; struct adc_channel_config adcch_conf; /* Initialize menu system */ gfx_mono_menu_init(&error_menu); /* Wait for user to select something in the menu system */ do { do { keyboard_get_key_state(&input); oven_wdt_periodic_reset(); /* Wait for key release */ } while (input.type != KEYBOARD_RELEASE); /* Send key to menu system */ menu_status = gfx_mono_menu_process_key(&error_menu, input.keycode); oven_wdt_periodic_reset(); } while (menu_status == GFX_MONO_MENU_EVENT_IDLE); /* Handle the user's selection */ switch (menu_status) { case 0: /* Change cpu frequency by modifying the prescalers */ sysclk_set_prescalers(CLK_PSADIV_4_gc, CLK_PSBCDIV_1_1_gc); break; case 1: /* Change timing of the periodic temperature tests */ OVEN_PERIODIC_TEMPTEST_TC.CTRLA = TC_CLKSEL_DIV256_gc; break; case 2: /* Change flash section. */ oven_classb_flash_corrupter(); break; case 3: /* Disrupt SRAM by setting up the DMA to write to a location on * the heap, triggered by the class B frequency monitor timer */ PR.PRGEN &= ~PR_DMA_bm; DMA.CTRL |= DMA_ENABLE_bm; DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_TCD1_CCA_gc; /* Address of Timer D1 CNT base is 0x0960. */ DMA.CH0.SRCADDR0 = 0x60; DMA.CH0.SRCADDR1 = 0x09; DMA.CH0.SRCADDR2 = 0x00; DMA.CH0.DESTADDR0 = ((uint16_t)&variable_for_sram_error) & 0xFF; DMA.CH0.DESTADDR1 = (((uint16_t)&variable_for_sram_error) >> 8) & 0xFF; DMA.CH0.DESTADDR2 = 0x00; DMA.CH0.CTRLA = DMA_CH_BURSTLEN_2BYTE_gc | DMA_CH_REPEAT_bm | DMA_CH_ENABLE_bm; break; case 4: /* Enter infinite loop */ while (1) { } break; case 5: /* Set up ADC channel interrupt */ adc_set_callback(&ADCA, adc_foul_callback); adcch_read_configuration(&ADCA, ADC_CH0, &adcch_conf); adcch_enable_interrupt(&adcch_conf); adcch_write_configuration(&ADCA, ADC_CH0, &adcch_conf); adcch_read_configuration(&ADCA, ADC_CH2, &adcch_conf); adcch_enable_interrupt(&adcch_conf); adcch_write_configuration(&ADCA, ADC_CH2, &adcch_conf); break; case 6: /* Back */ break; case GFX_MONO_MENU_EVENT_EXIT: /* Fall through to default */ default: /* Nothing, go back. */ break; } }
/** * \brief Main function. * * Initializes the board, displays splash screens, then launches application. * * If the application exits (fails), the error is written to display and an * infinite loop with watchdog resets is entered. */ int main(void) { /* Holds state of the QTouch button */ enum pot_t potstate = POT_OFF; /* Last state of the QTouch button for change detection */ enum pot_t last_potstate = POT_OFF; /* Holds user-selected power-setting */ uint8_t wattage = 0; /* Last power-setting for change detection */ uint8_t last_wattage = 0; /* Whether the plate element is/should be actuated */ bool power = false; /* Last power setting for change detection */ bool last_power = false; /* Last time a simulation step was executed */ uint32_t last_sim_step; /* Last time a control step was executed */ uint32_t last_ctl_step; /* The WDT was just reset by the WDT functional test*/ classb_last_wdt_reset = rtc_get_time(); last_sim_step = classb_last_wdt_reset; last_ctl_step = classb_last_wdt_reset; sysclk_init(); board_init(); pmic_init(); gfx_mono_init(); touch_init(); main_init_rtc32(); cpu_irq_enable(); /* Enable display backlight */ ioport_set_pin_level(LCD_BACKLIGHT_ENABLE_PIN, LCD_BACKLIGHT_ENABLE_LEVEL); /* If an error was detected, skip directly to displaying it */ if (classb_error) { goto display_error; } /* Check if required jumpers are mounted, show explanation if not */ if (!main_check_jumpers()) { show_explain_splash(); } /* Display a splash screen showing button functions */ show_button_splash(); /* Initialize the ADC, DAC and Timer/Counter modules that are used to * emulate real world objects. */ main_init_adc_dac(); main_init_tc(); /* Initialize subsystems used for Class B testing */ oven_classb_init_tests(); /* Reset the simulation states */ oven_plant_init(); /* Clear screen */ gfx_mono_draw_filled_rect(0, 0, 128, 32, GFX_PIXEL_CLR); /* Draw the initial axis system */ oven_ui_draw_axis(); /* Draw the user interface */ oven_ui_update_graphics(potstate, wattage, power); /* Run simulation as long as no error is detected in class B tests */ while (!classb_error) { uint32_t current_time; oven_wdt_periodic_reset(); current_time = rtc_get_time(); /* Add power on SW1 press, wrap at 20 */ if (oven_ui_power_button_is_pressed()) { wattage = (wattage + 5) % 20; } /* Check QTouch sensor and map this to `potstate`. This is * needed both for simulation and for the control routine. */ potstate = (!touch_key_is_pressed()) ? POT_ON : POT_OFF; /* Execute control routine periodically */ if (current_time > (last_ctl_step + OVEN_CTL_STEP_TIME)) { ovenctl_execute_control_step(current_time, &wattage, &power, potstate); last_ctl_step = current_time; } /* Execute simulation step periodically */ if (current_time > (last_sim_step + OVEN_SIM_STEP_TIME)) { main_execute_simulation_step(current_time, potstate); last_sim_step = current_time; } /* Update graphics on wattage, power or pot on/off change */ if ((potstate != last_potstate) || (power != last_power) || (wattage != last_wattage)) { oven_ui_update_graphics(potstate, wattage, power); } /* Update variable states for change detection on next loop * iteration. */ last_power = power; last_wattage = wattage; last_potstate = potstate; /* If back/menu button is pressed, pause simulation and test * timers, and show menu. */ if (oven_ui_back_button_is_pressed()) { /* Disable interrupt monitoring, if enabled */ classb_intmon_set_state(TEMP_SANITY_TEST, M_DISABLE); classb_intmon_set_state(PER_CLASSB_TESTS, M_DISABLE); OVEN_PERIODIC_TEMPTEST_TC.CTRLA &= ~TC1_CLKSEL_gm; OVEN_PERIODIC_CLASSB_TC.CTRLA &= ~TC0_CLKSEL_gm; /* Show the error insertion menu */ oven_classb_error_insertion(); /* Re-enable timers upon return */ OVEN_PERIODIC_CLASSB_TC.CTRLA |= TC_CLKSEL_DIV1024_gc; /* If user did not induce an error in the temperature * test timing, re-enable it correctly. */ if ((OVEN_PERIODIC_TEMPTEST_TC.CTRLA & TC1_CLKSEL_gm) == TC_CLKSEL_OFF_gc) { OVEN_PERIODIC_TEMPTEST_TC.CTRLA |= TC_CLKSEL_DIV1024_gc; } /* Re-enable interrupt monitoring if the oven is * supposed to be on, i.e., they were enabled before. */ if (wattage > 0) { classb_intmon_set_state(TEMP_SANITY_TEST, M_ENABLE); classb_intmon_set_state(PER_CLASSB_TESTS, M_ENABLE); } /* Reset UI */ gfx_mono_draw_filled_rect(0, 0, 128, 32, GFX_PIXEL_CLR); oven_ui_draw_axis(); oven_ui_update_graphics(potstate, wattage, power); } } display_error: /* Show red status LED and write the error on display */ oven_ui_set_status_leds(S_RED); oven_classb_display_error(); /* Enter infinite loop of watchdog resets so user can read display */ while (true) { oven_wdt_periodic_reset(); } }