/* * Funcgen firmware * * Copyright (C) 2007-2009 Michael Buesch * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "main.h" #include "lcd.h" #include "calibration.h" #include "timer.h" #include "util.h" #include #include #include #include #include "tables.c" static void update_lcd(void); /* Pin for external "power" signal. */ #define POWERSIG_PORT PORTC #define POWERSIG_DDR DDRC #define POWERSIG_BIT (1 << 5) /* User keys */ #define KEYS_PORT PORTC #define KEYS_PIN PINC #define KEYS_DDR DDRC #define KEY_PROFCHG (1 << 0) #define KEY_SELECT (1 << 1) #define KEY_FAST (1 << 2) #define KEY_DOWN (1 << 3) #define KEY_UP (1 << 4) #define KEYS_MASK (KEY_PROFCHG | KEY_SELECT | KEY_FAST | KEY_UP | KEY_DOWN) /* Operation modes */ enum { MODE_MAIN = 0, /* Main mode */ MODE_PROFILES, /* Profile selection */ MODE_RAW, /* Raw mode (for calibration probing) */ MODE_CALIB, /* Calibration mode */ NR_MODES, /* Number of modes */ }; /* Selections in MAIN mode. */ enum { SEL_MAIN_WAV = 0, /* Waveform */ SEL_MAIN_FREQ, /* Frequency */ SEL_MAIN_PRE, /* Prescaler */ NR_SEL_MAIN, /* Number of selections in MAIN mode */ }; /* Selections in RAW mode. */ enum { SEL_RAW_WAV = 0, /* Generator waveform */ SEL_RAW_OCR, /* Generator OCR */ SEL_RAW_STEP, /* Generator step */ SEL_RAW_PS, /* Generator prescaler */ NR_SEL_RAW, /* Number of selections in RAW mode */ }; /* Context information for the RAW mode */ struct raw_mode_context { uint8_t waveform; uint8_t step; uint16_t ocr; uint16_t prescaler; }; struct calibration_data { uint16_t min_ocr_sine; /* Min OCR value for Sine waveform */ uint16_t min_ocr_square; /* Min OCR value for Square waveform */ uint16_t min_ocr_triang; /* Min OCR value for Triangle waveform */ uint16_t min_ocr_sawt; /* Min OCR value for Sawtooth waveform */ }; /* A user frequency profile */ struct profile { /* User-selected waveform */ uint8_t waveform; /* User-selected freq (in Hz) */ uint16_t user_freq; /* Signal timer prescaler setting */ uint16_t prescaler; }; #define NR_PROFILES 4 static struct profile profiles[NR_PROFILES]; static uint8_t active_profile; /* The preselected profile. This is only valid for the PROFILES menu */ static uint8_t presel_profile; /* Context for RAW mode, only */ static struct raw_mode_context raw; /* Calibration data */ static struct calibration_data calibration; /* Is the FAST-key pressed? */ static bool fast_enabled; /* The currently used operation mode. */ static uint8_t opmode; /* The current selection. */ static uint8_t selection; /* The error rate of the generator (in Hz) */ static int16_t generator_error; /*** EEPROM storage ***/ #define EE_PROF_INIT { \ .waveform = WAV_SINE, \ .user_freq = 1000, /* Hz */ \ .prescaler = PRESCALER_1, \ } static struct profile EEMEM ee_profiles[] = { EE_PROF_INIT, EE_PROF_INIT, EE_PROF_INIT, EE_PROF_INIT, }; static uint8_t EEMEM ee_active_profile = 0; static struct calibration_data EEMEM ee_calibration = {//FIXME store .min_ocr_sine = 0x32, .min_ocr_square = 0x2A, .min_ocr_triang = 0x32, .min_ocr_sawt = 0x26, }; static bool configuration_changed; static uint16_t config_change_time; /* Load the configuration from eeprom. */ static void eeprom_load_config(void) { BUILD_BUG_ON(ARRAY_SIZE(ee_profiles) != NR_PROFILES); eeprom_busy_wait(); eeprom_read_block(&profiles, &ee_profiles, sizeof(ee_profiles)); active_profile = eeprom_read_byte(&ee_active_profile); eeprom_read_block(&calibration, &ee_calibration, sizeof(ee_calibration)); eeprom_busy_wait(); } /* Store the configuration to eeprom. */ static void eeprom_store_config(void) { eeprom_busy_wait(); eeprom_write_block(&profiles, &ee_profiles, sizeof(ee_profiles)); eeprom_write_byte(&ee_active_profile, active_profile); eeprom_write_block(&calibration, &ee_calibration, sizeof(ee_calibration)); eeprom_busy_wait(); } static uint16_t jiffies_hi; /* High 16-bit of jiffies. */ static void jiffies_init(void) { /* The 8-bit timer 2 is used as hardware jiffies counter. * As the hardware only provides 8-bit, we use a carry detection * mechanism from the mainloop to extend it to 24-bit. * Only the high 16-bit (so only the software part) are actually * used. */ /* prescaler 1024. So timer will operate at * 15.625 kHz on a 16MHz CPU. */ TCCR2 = (1 << CS20) | (1 << CS21) | (1 << CS22); } /* Check for hardware jiffies counter overflow. */ static void jiffies_carry_check(void) { uint8_t count; static uint8_t last_count; count = TCNT2; if (count < last_count) { /* Carry detected */ jiffies_hi++; } last_count = count; } /* Get the current jiffies value. */ static inline uint16_t jiffies_get(void) { mb(); return jiffies_hi; } static void set_config_changed(void) { if (opmode == MODE_RAW) return; configuration_changed = 1; config_change_time = jiffies_get(); update_lcd(); } /* Calculate the frequency for a given OCR and DACstep value. */ static uint16_t calc_freq(uint8_t dac, uint16_t ocr) { struct profile *prof = &(profiles[active_profile]); uint32_t tmp; uint16_t freq; tmp = F_CPU; tmp /= prof->prescaler; tmp /= ocr; tmp /= ((uint16_t)256 / dac); BUG_ON(tmp & 0xFFFF0000); freq = (uint16_t)tmp; return freq; } /* Binary search for an OCR setting for a given frequency (@freq_hz). * Based on the @dac_stepping, a binary search is performed to find the * OCR setting that matches the @freq_hz with the lowest @error. The @error * and the OCR setting are returned. */ static uint16_t binary_freq_search(uint8_t dac_stepping, uint16_t freq_hz, uint16_t ocr_left, uint16_t ocr_right, int16_t *error) { uint16_t center; uint16_t cur_freq; int32_t cur_freq_error; uint16_t match_ocr = 0; int32_t match_freq_error = INT32_MAX; while (1) { center = ((ocr_right - ocr_left) / 2) + ocr_left; /* Calc the frequency for this OCR setting */ cur_freq = calc_freq(dac_stepping, center); cur_freq_error = (int32_t)cur_freq - (int32_t)freq_hz; if (abs(cur_freq_error) < abs(match_freq_error)) { /* Found a better freq */ match_freq_error = cur_freq_error; match_ocr = center; } if (cur_freq_error == 0) break; if (cur_freq_error < 0) { ocr_right = center - 1; if (ocr_left > ocr_right) break; } else { ocr_left = center + 1; if (ocr_right < ocr_left) break; } /* This calculation takes quite some time. * Make sure jiffies are updated correctly. */ jiffies_carry_check(); } BUG_ON(match_freq_error > INT16_MAX || match_freq_error < INT16_MIN); *error = match_freq_error; return match_ocr; } /* Search for an OCR and DAC-STEP setting for a given frequency (@freq_hz). * Returns the DAC-STEP in @ret_dac_stepping and the OCR value as return value. * The error rate in Hz is returned in @error. * Note that the search is biased by the current waveform. */ static uint16_t find_ocr_and_stepping_for_freq(uint16_t freq_hz, uint8_t *ret_dac_stepping, int16_t *error) { const uint16_t max_ocr = 0xFFFF; uint16_t min_ocr; struct profile *prof = &(profiles[active_profile]); uint8_t max_dac_stepping; uint8_t dac_stepping; int16_t cur_error; uint16_t ocr; int16_t lowest_error = INT16_MAX; uint16_t best_ocr = 0; int8_t best_dac_stepping = 0; switch (prof->waveform) { case WAV_SQUARE: min_ocr = calibration.min_ocr_square; max_dac_stepping = (1 << 7); break; case WAV_SINE: min_ocr = calibration.min_ocr_sine; max_dac_stepping = (1 << 4); break; case WAV_TRIANG: min_ocr = calibration.min_ocr_triang; max_dac_stepping = (1 << 4); break; case WAV_SAWT: min_ocr = calibration.min_ocr_sawt; max_dac_stepping = (1 << 4); break; default: BUG_ON(1); } dac_stepping = (1 << 0); while (1) { ocr = binary_freq_search(dac_stepping, freq_hz, min_ocr, max_ocr, &cur_error); if (abs(cur_error) < abs(lowest_error)) { lowest_error = cur_error; best_ocr = ocr; best_dac_stepping = dac_stepping; } if (cur_error == 0) break; if (dac_stepping == max_dac_stepping) break; dac_stepping <<= 1; } BUG_ON(best_dac_stepping == 0); *error = lowest_error; *ret_dac_stepping = best_dac_stepping; return best_ocr; } /* Recalculate the generator settings, based on the user-supplied frequency */ static void recalculate_generator_settings(void) { struct profile *prof = &(profiles[active_profile]); uint16_t ocr; uint8_t flags; uint8_t step; uint8_t waveform; int16_t error; uint16_t prescaler; if (opmode == MODE_CALIB) { /* Do nothing, in calib mode */ return; } else if (opmode == MODE_RAW) { /* In raw mode the user supplies the lowlevel generator settings. */ waveform = raw.waveform; step = raw.step; ocr = raw.ocr; prescaler = raw.prescaler; error = 0; } else { /* In all other modes, we have to calculate the lowlevel settings. */ ocr = find_ocr_and_stepping_for_freq(prof->user_freq, &step, &error); waveform = prof->waveform; prescaler = prof->prescaler; } flags = irq_disable_save(); generator_error = error; generator_reconfigure(waveform, ocr, prescaler, step); irq_restore(flags); set_config_changed(); } static void print_waveform_char(uint8_t waveform_id) { uint8_t wave_char; switch (waveform_id) { case WAV_SINE: wave_char = ASCII_SINE; break; case WAV_SQUARE: wave_char = ASCII_SQUARE; break; case WAV_TRIANG: wave_char = ASCII_TRI; break; case WAV_SAWT: wave_char = ASCII_SAW; break; default: BUG_ON(1); } lcd_put_char(wave_char); } static void print_prescaler_str(uint16_t prescaler_id) { switch (prescaler_id) { case PRESCALER_1: lcd_put_str("ps0"); break; case PRESCALER_8: lcd_put_str("ps1"); break; case PRESCALER_64: lcd_put_str("ps2"); break; case PRESCALER_256: lcd_put_str("ps3"); break; case PRESCALER_1024: lcd_put_str("ps4"); break; default: BUG_ON(1); } } static void update_lcd_main(void) { struct profile *prof = &(profiles[active_profile]); /* Print waveform symbol */ lcd_cursor(0, 0); print_waveform_char(prof->waveform); /* Config indicator */ if (configuration_changed) { lcd_cursor(1, 11); lcd_put_char('*'); } /* Print prescaler info */ lcd_cursor(1, 13); print_prescaler_str(prof->prescaler); /* User frequency */ lcd_cursor(0, 2); lcd_printf("%u", prof->user_freq); lcd_put_str(" Hz"); /* Generator error */ lcd_cursor(1, 0); lcd_put_str("err="); lcd_printf("%d", generator_error); /* Finally move the cursor to the selection position. */ switch (selection) { case SEL_MAIN_WAV: lcd_cursor(0, 0); break; case SEL_MAIN_FREQ: lcd_cursor(0, 2); break; case SEL_MAIN_PRE: lcd_cursor(1, 13); break; default: BUG_ON(1); } } static void update_lcd_profiles(void) { uint8_t i; /* Print header line */ lcd_cursor(0, 0); lcd_put_str("Select profile:"); /* Print the profile numbers */ lcd_cursor(1, 0); for (i = 0; i < NR_PROFILES; i++) { if (i == presel_profile) { lcd_put_char('['); lcd_printf("%u", i + 1); lcd_put_char(']'); } else { lcd_put_char('<'); lcd_printf("%u", i + 1); lcd_put_char('>'); } if (i != NR_PROFILES - 1) lcd_put_char(' '); } /* Finally move the cursor to the selected profile */ lcd_cursor(1, 1 + (presel_profile * 4)); } static void update_lcd_rawmode(void) { lcd_cursor(0, 0); lcd_put_str("RAW"); lcd_cursor(0, 4); print_waveform_char(raw.waveform); lcd_cursor(0, 6); lcd_put_str("ocr=0x"); lcd_printf("%02X%02X", (unsigned int)(uint8_t)(raw.ocr >> 8), (unsigned int)(uint8_t)raw.ocr); lcd_cursor(1, 0); lcd_put_str("step=0x"); lcd_printf("%02X", raw.step); lcd_cursor(1, 13); print_prescaler_str(raw.prescaler); /* Finally move the cursor to the selection position. */ switch (selection) { case SEL_RAW_WAV: lcd_cursor(0, 4); break; case SEL_RAW_OCR: lcd_cursor(0, 12); break; case SEL_RAW_STEP: lcd_cursor(1, 7); break; case SEL_RAW_PS: lcd_cursor(1, 15); break; default: BUG_ON(1); } } static void update_lcd_calibmode(void) { lcd_cursor(0, 0); lcd_put_str("CAL"); //TODO } static void update_lcd(void) { lcd_clear_buffer(); switch (opmode) { case MODE_MAIN: update_lcd_main(); break; case MODE_PROFILES: update_lcd_profiles(); break; case MODE_RAW: update_lcd_rawmode(); break; case MODE_CALIB: update_lcd_calibmode(); break; default: BUG_ON(1); } lcd_commit(); } static inline bool key_is_pressed(uint8_t key_mask) { return !(KEYS_PIN & key_mask); } static void key_debounce(uint8_t key_mask, uint8_t delay) { uint8_t maxwait; mdelay(1); do { if (fast_enabled) maxwait = 25; else maxwait = 35; for ( ; maxwait; maxwait--) { if (!key_is_pressed(key_mask)) { /* Released */ break; } mdelay(5); } } while (delay--); } void emergency_shutdown(void) { irq_disable(); DAC_PORT = 0; } static void kp_select_main(void) { if (++selection >= NR_SEL_MAIN) selection = 0; } static void kp_select_raw(void) { if (++selection >= NR_SEL_RAW) selection = 0; } static void handle_keypress_select(void) { key_debounce(KEY_SELECT, 6); switch (opmode) { case MODE_MAIN: kp_select_main(); break; case MODE_PROFILES: break; case MODE_RAW: kp_select_raw(); break; default: BUG_ON(1); } update_lcd(); } static void kp_wav_up(void) { struct profile *prof = &(profiles[active_profile]); if (++(prof->waveform) >= NR_WAV) prof->waveform = 0; recalculate_generator_settings(); } static uint16_t prescaler_up(uint16_t ps) { switch (ps) { case PRESCALER_1: ps = PRESCALER_8; break; case PRESCALER_8: ps = PRESCALER_64; break; case PRESCALER_64: ps = PRESCALER_256; break; case PRESCALER_256: ps = PRESCALER_1024; break; case PRESCALER_1024: ps = PRESCALER_1; break; default: BUG_ON(1); } return ps; } static void kp_freq_up(void) { struct profile *prof = &(profiles[active_profile]); const uint16_t max_freq = 30000; const uint8_t big_step = 50; const uint8_t small_step = 1; if (fast_enabled) { if (prof->user_freq > max_freq - big_step) return; prof->user_freq += big_step; } else { if (prof->user_freq > max_freq - small_step) return; prof->user_freq += small_step; } recalculate_generator_settings(); } static void kp_up_main(void) { struct profile *prof = &(profiles[active_profile]); switch (selection) { case SEL_MAIN_WAV: kp_wav_up(); break; case SEL_MAIN_FREQ: kp_freq_up(); break; case SEL_MAIN_PRE: prof->prescaler = prescaler_up(prof->prescaler); recalculate_generator_settings(); break; default: BUG_ON(1); } } static void kp_up_raw(void) { switch (selection) { case SEL_RAW_WAV: if (++(raw.waveform) >= NR_WAV) raw.waveform = 0; recalculate_generator_settings(); break; case SEL_RAW_OCR: if (fast_enabled) raw.ocr += 0x40; else raw.ocr++; recalculate_generator_settings(); break; case SEL_RAW_STEP: if (raw.step < 0x80) raw.step <<= 1; recalculate_generator_settings(); break; case SEL_RAW_PS: raw.prescaler = prescaler_up(raw.prescaler); recalculate_generator_settings(); break; default: BUG_ON(1); } } static void handle_keypress_up(void) { key_debounce(KEY_UP, fast_enabled ? 0 : 1); switch (opmode) { case MODE_MAIN: kp_up_main(); break; case MODE_PROFILES: if (presel_profile == NR_PROFILES - 1) presel_profile = 0; else presel_profile++; break; case MODE_RAW: kp_up_raw(); break; default: BUG_ON(1); } update_lcd(); } static void kp_wav_down(void) { struct profile *prof = &(profiles[active_profile]); if (prof->waveform == 0) prof->waveform = NR_WAV - 1; else prof->waveform--; recalculate_generator_settings(); } static uint16_t prescaler_down(uint16_t ps) { switch (ps) { case PRESCALER_1: ps = PRESCALER_1024; break; case PRESCALER_8: ps = PRESCALER_1; break; case PRESCALER_64: ps = PRESCALER_8; break; case PRESCALER_256: ps = PRESCALER_64; break; case PRESCALER_1024: ps = PRESCALER_256; break; default: BUG_ON(1); } return ps; } static void kp_freq_down(void) { struct profile *prof = &(profiles[active_profile]); const uint16_t min_freq = 1; const uint8_t big_step = 50; const uint8_t small_step = 1; if (fast_enabled) { if (prof->user_freq < min_freq + big_step) return; prof->user_freq -= big_step; } else { if (prof->user_freq < min_freq + small_step) return; prof->user_freq -= small_step; } recalculate_generator_settings(); } static void kp_down_main(void) { struct profile *prof = &(profiles[active_profile]); switch (selection) { case SEL_MAIN_WAV: kp_wav_down(); break; case SEL_MAIN_FREQ: kp_freq_down(); break; case SEL_MAIN_PRE: prof->prescaler = prescaler_down(prof->prescaler); recalculate_generator_settings(); break; default: BUG_ON(1); } } static void kp_down_raw(void) { switch (selection) { case SEL_RAW_WAV: if (raw.waveform == 0) raw.waveform = NR_WAV - 1; else raw.waveform--; recalculate_generator_settings(); break; case SEL_RAW_OCR: if (fast_enabled) raw.ocr -= 0x40; else raw.ocr--; recalculate_generator_settings(); break; case SEL_RAW_STEP: if (raw.step > 0x01) raw.step >>= 1; recalculate_generator_settings(); break; case SEL_RAW_PS: raw.prescaler = prescaler_down(raw.prescaler); recalculate_generator_settings(); break; default: BUG_ON(1); } } static void handle_keypress_down(void) { key_debounce(KEY_DOWN, fast_enabled ? 0 : 1); switch (opmode) { case MODE_MAIN: kp_down_main(); break; case MODE_PROFILES: if (presel_profile == 0) presel_profile = NR_PROFILES - 1; else presel_profile--; break; case MODE_RAW: kp_down_raw(); break; default: BUG_ON(1); } update_lcd(); } static void handle_profile_key(void) { if (opmode == MODE_RAW || opmode == MODE_CALIB) { if (!key_is_pressed(KEY_PROFCHG)) return; key_debounce(KEY_PROFCHG, 1); if (opmode == MODE_RAW) opmode = MODE_CALIB; else opmode = MODE_RAW; selection = 0; recalculate_generator_settings(); } else { if (key_is_pressed(KEY_PROFCHG)) { if (opmode == MODE_PROFILES) return; key_debounce(KEY_PROFCHG, 1); if (!key_is_pressed(KEY_PROFCHG)) return; /* Switch to profile menu */ presel_profile = active_profile; opmode = MODE_PROFILES; } else { if (opmode == MODE_MAIN) return; /* Switch back to main menu */ opmode = MODE_MAIN; /* Update the generator statemachine */ if (active_profile != presel_profile) { active_profile = presel_profile; recalculate_generator_settings(); } } } update_lcd(); } static void upload_chars(void) { lcd_upload_char(ASCII_SINE, table_char_sine); lcd_upload_char(ASCII_SQUARE, table_char_square); lcd_upload_char(ASCII_TRI, table_char_tri); lcd_upload_char(ASCII_SAW, table_char_saw); } int main(void) { irq_disable(); POWERSIG_PORT &= ~POWERSIG_BIT; POWERSIG_DDR |= POWERSIG_BIT; /* Init the DAC port as Output */ DAC_DDR = 0xFF; DAC_PORT = 0; jiffies_init(); eeprom_load_config(); lcd_init(); upload_chars(); /* Tell the outside world that we are done with basic setup. */ POWERSIG_PORT |= POWERSIG_BIT; /* Initialize the signal generator timer. */ TCCR1B = (1 << WGM12); recalculate_generator_settings(); /* Enable timer compare IRQ */ TIMSK = (1 << OCIE1A); /* Initialize user input keys with pullups. */ KEYS_DDR &= ~KEYS_MASK; KEYS_PORT |= KEYS_MASK; mdelay(20); /* Wait for pullups. */ /* Select initial operation mode */ if (key_is_pressed(KEY_FAST)) { lcd_clear_buffer(); lcd_put_str("RAW mode enabled"); lcd_commit(); key_debounce(KEY_FAST, 20); lcd_clear_buffer(); lcd_commit(); opmode = MODE_RAW; raw.waveform = WAV_SINE; raw.step = 1; raw.ocr = 1000; raw.prescaler = PRESCALER_1; } else opmode = MODE_MAIN; configuration_changed = 0; update_lcd(); irq_enable(); while (1) { jiffies_carry_check(); if (configuration_changed) { /* Write config to eeprom, 10 seconds after the last change. */ if (time_before(config_change_time + JIFFIES_PER_SECOND * 10, jiffies_get())) { configuration_changed = 0; eeprom_store_config(); update_lcd(); } } if (key_is_pressed(KEY_FAST)) fast_enabled = 1; else fast_enabled = 0; /* Switch between main and profile mode */ handle_profile_key(); if (key_is_pressed(KEY_SELECT)) handle_keypress_select(); if (key_is_pressed(KEY_UP)) handle_keypress_up(); if (key_is_pressed(KEY_DOWN)) handle_keypress_down(); } }