/** * @copyright * Copyright (C) 2011 Pavel Semerad * * @file * $RCSfile: $ * * @purpose * gt3b alternative firmware - read keys and ADC values * * @cfg_management * $Source: $ * $Author: $ * $Date: $ * * @comment * 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 3 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "input.h" #include "menu.h" #include "config.h" #include "calc.h" #include "lcd.h" #include "timer.h" // autorepeat/long press times in 5ms steps #define BTN_AUTOREPEAT_DELAY (500 / 5) #define BTN_AUTOREPEAT_RATE (70 / 5) #define ENCODER_FAST_THRESHOLD 10 // INPUT task, called every 5ms TASK(INPUT, 40); static void input_loop(void); void input_init(void) { // ADC inputs IO_IF(B, 0); // steering ADC IO_IF(B, 1); // throttle ADC IO_IF(B, 2); // CH3 button IO_IF(B, 3); // battery voltage ADC // ADC init BSET(CLK_PCKENR2, 3); // enable clock to ADC ADC_TDRL = 0b00001111; // schmitt-trigger disable 0-3 ADC_CSR = 0b00000011; // end channel 3, no interrupts ADC_CR3 = 0b00000000; // no-DBUF ADC_CR2 = 0b00001010; // right align, SCAN ADC_CR1 = 0b01110001; // clock/18, no-CONT, enable // buttons - rows are inputs with pullups and are switched to output // only when reading that row IO_IP(B, 4); // button row B4 IO_IP(B, 5); // button row B5 IO_IP(C, 4); // button row C4 IO_IP(D, 3); // button row D3 IO_IP(C, 5); // button col C5 IO_IP(C, 6); // button col C6 IO_IP(C, 7); // button col C7 // and set default row values to 1 BSET(PB_ODR, 4); BSET(PB_ODR, 5); BSET(PC_ODR, 4); BSET(PD_ODR, 3); // rotate encoder - connected to Timer1, using encoder mode IO_IF(C, 1); // pin TIM1_CH1 IO_IF(C, 2); // pin TIM1_CH2 BSET(CLK_PCKENR1, 7); // enable master clock to TMR1 TIM1_SMCR = 0x01; // encoder mode 1, count on TI2 edges TIM1_CCMR1 = 0x01; // CC1 is input TIM1_CCMR2 = 0x01; // CC2 is input TIM1_ARRH = 0; // max value TIM1_ARRL = 0xff; // max value TIM1_CNTRH = 0; // start value TIM1_CNTRL = 0; // start value TIM1_IER = 0; // no interrupts TIM1_CR2 = 0; TIM1_CR1 = 0x01; // only counter enable // init task build(INPUT); activate(INPUT, input_loop); } // read keys, change value only when last 3 reads are the same // internal state variables @near static u16 buttons1, buttons2, buttons3; // last 3 reads u16 buttons_state; // actual combined last buttons state static u8 buttons_autorepeat; // autorepeat enable for TRIMs and D/R @near static u8 buttons_timer[12]; // autorepeat/long press buttons timers static u8 encoder_timer; // for rotate encoder slow/fast // variables representing pressed buttons u16 buttons; u16 buttons_long; // >1s press // variables for ADC values u16 adc_all_last[3], adc_battery_last; @near u16 adc_battery; @near u32 adc_battery_filt; // reset pressed button void button_reset(u16 btn) { buttons &= ~btn; buttons_long &= ~btn; } void button_reset_nolong(u16 btn) { buttons &= ~btn; } /** @brief set autorepeat @detailed FIXME @param[in] btn FIXME */ void button_autorepeat(u8 btn) { buttons_autorepeat = btn; } // read key matrix (11 keys) #define button_row(port, bit, c5, c6, c7) \ BSET(P ## port ## _DDR, bit); \ BSET(P ## port ## _CR2, bit); \ BRES(P ## port ## _ODR, bit); \ if (!BCHK(PC_IDR, 5)) btn |= c5; \ if (!BCHK(PC_IDR, 6)) btn |= c6; \ if (c7 && !BCHK(PC_IDR, 7)) btn |= c7; \ BSET(P ## port ## _ODR, bit); \ BRES(P ## port ## _CR2, bit); \ BRES(P ## port ## _DDR, bit) /** @brief * FIXME * @detailed * FIXME * @returns * @retval * FIXME */ static u16 read_key_matrix(void) { u16 btn = 0; button_row(B, 4, BTN_TRIM_LEFT, BTN_TRIM_CH3_L, BTN_END); button_row(B, 5, BTN_TRIM_RIGHT, BTN_TRIM_CH3_R, BTN_BACK); button_row(D, 3, BTN_TRIM_FWD, BTN_DR_R, 0); button_row(C, 4, BTN_TRIM_BCK, BTN_DR_L, BTN_ENTER); return btn; } // read all keys static void read_keys(void) { u16 buttons_state_last = buttons_state; u16 buttons_last = buttons; u16 bit; u8 i, lbs, bs; // rotate last buttons buttons3 = buttons2; buttons2 = buttons1; // read actual keys status buttons1 = read_key_matrix(); // add CH3 button, middle state will be only in buttons_state, // not in buttons // do only when CH3 is button, not potentiometer if (!cg.ch3_pot) { if (adc_ch3_last <= BTN_CH3_LOW) buttons1 |= BTN_CH3; else if (adc_ch3_last < BTN_CH3_HIGH) buttons1 |= BTN_CH3_MID; } // combine last 3 readed buttons buttons_state |= buttons1 & buttons2 & buttons3; buttons_state &= buttons1 | buttons2 | buttons3; // do autorepeat/long_press only when some keys were pressed if (buttons_state_last || buttons_state) { // key pressed or released, activate backlight backlight_on(); // handle autorepeat for first 8 keys (TRIMs and D/R) if (buttons_autorepeat) { for (i = 0, bit = 1; i < 8; i++, bit <<= 1) { if (!(bit & buttons_autorepeat)) continue; // not autorepeated lbs = (u8)(buttons_state_last & bit ? 1 : 0); bs = (u8)(buttons_state & bit ? 1 : 0); if (!lbs) { // last not pressed if (bs) { // now pressed, set it pressed and set autorepeat delay buttons |= bit; buttons_timer[i] = BTN_AUTOREPEAT_DELAY; } // do nothing for now not pressed } else { // last was pressed if (bs) { // now pressed if (--buttons_timer[i]) continue; // not expired yet // timer expired, set it pressed and set autorepeat rate buttons |= bit; buttons_timer[i] = BTN_AUTOREPEAT_RATE; } // do nothing for now not pressed } } } // handle long presses for first 12 keys // exclude keys with autorepeat ON for (i = 0, bit = 1; i < 12; i++, bit <<= 1) { if (bit & buttons_autorepeat) continue; // handled in autorepeat lbs = (u8)(buttons_state_last & bit ? 1 : 0); bs = (u8)(buttons_state & bit ? 1 : 0); if (!lbs) { // last not pressed if (bs) { // now pressed, set long press delay buttons_timer[i] = cg.long_press_delay; } // do nothing for now not pressed } else { // last was pressed if (bs) { // now pressed, check long press delay // if already long or not long enought, skip if (!buttons_timer[i] || --buttons_timer[i]) continue; // set as pressed and long pressed buttons |= bit; buttons_long |= bit; } else { // now not pressed, set as pressed when no long press was applied if (!buttons_timer[i]) continue; // was long before buttons |= bit; } } } } // add rotate encoder if (encoder_timer) encoder_timer--; if (TIM1_CNTRL) { // encoder changed u16 btn = 0; if ((s8)TIM1_CNTRL > cg.encoder_2detents) { // left if (cg.rotate_reverse) btn = BTN_ROT_R; else btn = BTN_ROT_L; } else if ((s8)TIM1_CNTRL < -cg.encoder_2detents) { // right if (cg.rotate_reverse) btn = BTN_ROT_L; else btn = BTN_ROT_R; } if (btn) { // only when enought rotate was applied buttons |= btn; if (encoder_timer) buttons_long |= btn; // set it back to default value TIM1_CNTRL = 0; // init timer encoder_timer = ENCODER_FAST_THRESHOLD; backlight_on(); } } // if some of the keys changed, wakeup MENU task and reset inactivity timer if (buttons_last != buttons || buttons_state_last != buttons_state) { awake(MENU); reset_inactivity_timer(); } } // ADC buffers, last 4 values for each channel #define ADC_BUFFERS 4 @near u16 adc_buffer0[ADC_BUFFERS]; @near u16 adc_buffer1[ADC_BUFFERS]; @near u16 adc_buffer2[ADC_BUFFERS]; u16 adc_buffer_pos; // read ADC values static void read_ADC(void) { READ_ADC(); } // average battery voltage and check battery low static void update_battery(void) { // ignore very low, which means that it is supplied from SWIM connector adc_battery_filt = adc_battery_filt * (ADC_BAT_FILT - 1); // splitted - compiler hack adc_battery_filt = (adc_battery_filt + (ADC_BAT_FILT / 2)) / ADC_BAT_FILT + adc_battery_last; adc_battery = (u16)((adc_battery_filt + (ADC_BAT_FILT / 2)) / ADC_BAT_FILT); // start checking battery after 5s from power on if (time_sec >= 5) { // wakeup task only when something changed if (adc_battery > 50 && adc_battery < battery_low_raw) { // bat low if (!menu_battery_low) { menu_battery_low = 1; awake(MENU); } } else { // bat OK, but apply some hysteresis to not switch quickly ON/OFF if (menu_battery_low && adc_battery > battery_low_raw + 100) { menu_battery_low = 0; awake(MENU); } } } } // reset inactivity timer when some steering or throttle applied static @near u16 last_steering = 30000; // more that it can be static @near u16 last_throttle = 30000; // more that it can be #define INACTIVITY_DEAD 20 static void check_inactivity(void) { if (adc_steering_last < (last_steering - INACTIVITY_DEAD) || adc_steering_last > (last_steering + INACTIVITY_DEAD)) { reset_inactivity_timer(); last_steering = adc_steering_last; last_throttle = adc_throttle_last; } else if (adc_throttle_last < (last_throttle - INACTIVITY_DEAD) || adc_throttle_last > (last_throttle + INACTIVITY_DEAD)) { reset_inactivity_timer(); last_steering = adc_steering_last; last_throttle = adc_throttle_last; } } // check throttle trigger and start timer #define THROTTLE_TRIGGER_START 30 static void check_throttle_trigger(void) { u8 i, tbit, type; // nothing when throttle is not little forward if (ADC_OVS(throttle) >= (cg.calib_throttle_mid - THROTTLE_TRIGGER_START)) return; for (i =0, tbit = 1; i < TIMER_NUM; i++, tbit <<= 1) { type = TIMER_TYPE(i); // check if possible if (type == TIMER_OFF || type == TIMER_LAPCNT) continue; // not for this type if (!(menu_timer_throttle & tbit)) continue; // not set for this timer // start timer menu_timer_running |= tbit; menu_timer_throttle &= (u8)~tbit; // awake MENU to possibly display changing time awake(MENU); } } // read first ADC values #define ADC_BUFINIT(id) \ adc_buffer ## id ## [1] = adc_buffer ## id ## [2] = \ adc_buffer ## id ## [3] = adc_buffer ## id ## [0]; void input_read_first_values(void) { // read initial ADC values BSET(ADC_CR1, 0); // start conversion while (!BCHK(ADC_CSR, 7)); // wait for end of conversion read_ADC(); // put initial values to all buffers ADC_BUFINIT(0); ADC_BUFINIT(1); ADC_BUFINIT(2); adc_battery = adc_battery_last; adc_battery_filt = (u32)adc_battery * ADC_BAT_FILT; } // input task, awaked every 5ms static void input_loop(void) { while (1) { read_keys(); if (menu_timer_throttle) check_throttle_trigger(); check_inactivity(); update_battery(); stop(); } }