Показаны сообщения с ярлыком ASF. Показать все сообщения
Показаны сообщения с ярлыком ASF. Показать все сообщения

четверг, 4 апреля 2013 г.

Qtouch sensors input processing (example for ASF)

There are several tasks when we try to process Qtouch input (Qtouch 60168): 1) fix random (false) signals (splashes or BOUNCE) 2) detecting PRESS state 3) detecting long "pressing" (touching) - sticking of key.

1) means: really signal from Qt is not 000..0 or 111..1 but something like 000001000000... - with error '1' in sequence of '0' (and vise verse) because of signal bouncing, so it should be 'filtered' 2) means: we should detect when touch sensor is "pressed" 3) means: when user touches screen for long time we should detect this state as 'hold key' (like you can see in old mobile phones or key sticking in Windows) and call some special routine (callback) for processing such event.

Next code processes all of this:
/* Input from qTouch (qt60168) with 4 buttons and one wheel
 * - There is some feature: sticking of key (long pressing detection) will occurs
 *   after usual key event: first key is pressed - and application open some
 *   window, only after this may occurs long-time-detection.
 * - test_event() test of PRESSED state, not released.
 * - simulation of repeatition simulate seq: pressed, released, no IDLE!
 */ 

#include "input.h"
#include "myutl.h"

#if !defined(FCPU)
# error Define FCPU to support refresh screen!
#endif

input_t _input = INPUT_INITIALIZER();

static void _qt60168_io_init(void);
static inline void _input_scan_keys(input_t* self);
static inline void _input_update_keys_state(input_t* self);
static inline void input_process_sticking(input_t* self, input_keys_t key, input_state_t new_state);
inline static bool input_fltbounce_filter(input_fltbounce_t* self, input_state_t new_state, int msdelay);
inline static void input_stick_fsm(input_stick_t* self, input_t* input, input_stick_fsm_event_t ev, input_keys_t key);

#define _INPUT_STICK_STOP(S) do { \
  cpu_stop_timeout(&(S)->long_to); \
  cpu_stop_timeout(&(S)->short_to); \
} while (0)

#define _INPUT_GET_SENSOR(INP, I) (INP)->sensors[I]

/// set wheel to left
#define _INPUT_SET_WHEEL_LEFT(INP) do { \
  if (INPUT_RIGHT == (INP)->keys[INPUT_WHEEL]) { \
    (INP)->wheel = 0; \
  } \
  (INP)->keys[INPUT_WHEEL] = INPUT_LEFT; \
  (INP)->wheel++; \
} while (0)
/// set wheel to right
#define _INPUT_SET_WHEEL_RIGHT(INP) do { \
  if (INPUT_LEFT == (INP)->keys[INPUT_WHEEL]) { \
    (INP)->wheel = 0; \
  } \
  (INP)->keys[INPUT_WHEEL] = INPUT_RIGHT; \
  (INP)->wheel++; \
} while (0)

#define _INPUT_SET_SENSOR(INP, I, ST) (INP)->sensors[I] = (ST)
#define _INPUT_SET_KEY(INP, I, ST) (INP)->keys[I] = (ST)

// apply state of bounce filter FSM
#define _INPUT_FLTBOUNCE_APPLY_STATE(F, NEW_STATE, MSDELAY) do { \
  (F)->state = NEW_STATE; \
  cpu_set_timeout(cpu_ms_2_cy(MSDELAY, FCPU), &(F)->to); \
} while (0)

// is timeout in bounce filter?
#define _INPUT_FLTBOUNCE_IS_TO(F) cpu_is_timeout(&(F)->to)

static void
_qt60168_io_init(void) {
  static const gpio_map_t QT60168_SPI_GPIO_MAP =
  {
    {QT60168_SPI_SCK_PIN, QT60168_SPI_SCK_FUNCTION},// SPI Clock.
    {QT60168_SPI_MISO_PIN, QT60168_SPI_MISO_FUNCTION},// MISO.
    {QT60168_SPI_MOSI_PIN, QT60168_SPI_MOSI_FUNCTION},// MOSI.
    {QT60168_SPI_NPCS0_PIN, QT60168_SPI_NPCS0_FUNCTION}// Chip Select NPCS.
  };

  // SPI options.
  spi_options_t spiOptions =
  {
    .reg    = QT60168_SPI_NCPS,
    .baudrate   = QT60168_SPI_MASTER_SPEED, // Defined in conf_qt60168.h.
    .bits   = QT60168_SPI_BITS,   // Defined in conf_qt60168.h.
    .spck_delay   = 0,
    .trans_delay  = 0,
    .stay_act   = 0,
    .spi_mode   = 3,
    .modfdis  = 1
  };

  // Assign I/Os to SPI.
  gpio_enable_module(QT60168_SPI_GPIO_MAP,
      sizeof(QT60168_SPI_GPIO_MAP) / sizeof(QT60168_SPI_GPIO_MAP[0]));
  // Initialize as master.
  spi_initMaster(QT60168_SPI, &spiOptions);
  // Set selection mode: variable_ps, pcs_decode, delay.
  spi_selectionMode(QT60168_SPI, 0, 0, 0);
  // Enable SPI.
  spi_enable(QT60168_SPI);
  // Initialize QT60168 with SPI clock Osc0 (=FCPU).
  spi_setupChipReg(QT60168_SPI, &spiOptions, FCPU);
}

/// call after INPUT(...) initializer calling
void
input_init(input_t* self) {
  // Initialize QT60168 resources: GPIO, SPI and QT60168.
  _qt60168_io_init();
  // Initialize QT60168 component.
  qt60168_init(FCPU);
  // TODO : need?
  for (int i=0; i<INPUT_NUMBER_OF_KEYS; i++) {
    _INPUT_STICK_STOP(&self->stick_keys[i]);
  }
}

// for input task!
static inline void
_input_scan_keys(input_t* self) {

  int all_keys = qt60168_report_all_key();
  int mask = 1;
  for (int i=0; i<QT60168_TOUCH_NUMBER_OF_SENSORS; i++ ) {
    if (all_keys & (mask<<i)) {
      // if pressed - set pressed
      if (input_fltbounce_filter(self->fltsensors+i, INPUT_PRESSED, INPUT_SENS_TIME)) {
        _INPUT_SET_SENSOR(self, i, INPUT_PRESSED);
      }
    }
    else if (INPUT_PRESSED == _INPUT_GET_SENSOR(self, i)) {
      // if WAS pressed but now is not - set released
      if (input_fltbounce_filter(self->fltsensors+i, INPUT_RELEASED, INPUT_SENS_TIME)) {
        _INPUT_SET_SENSOR(self, i, INPUT_RELEASED);
      }
      //if (i==QT60168_TOUCH_SENSOR_BUTTON_3) { gui_screen_prn(SCREEN, RED, WHITE, "BTN3 RELEASED"); }
    }
    else {
      // else (not pressed now and WASN'T pressed early) - set idle
      if (input_fltbounce_filter(self->fltsensors+i, INPUT_IDLE, INPUT_SENS_TIME)) {
        _INPUT_SET_SENSOR(self, i, INPUT_IDLE);
      }
      //if (i==QT60168_TOUCH_SENSOR_BUTTON_3) { gui_screen_prn(SCREEN, RED, WHITE, "BTN3 IDLE"); }
    }
  }
}

static inline void
_input_update_keys_state(input_t* self) {

  // reset 'ready' flag. If some will be pressed, set to true. If
  // released - not
  self->ready = false;

#define _UPDATE_BTKKEY(BTN, KEY) do { \
  if (INPUT_PRESSED == _INPUT_GET_SENSOR(self, BTN)) { \
    _INPUT_SET_KEY(self, KEY, INPUT_PRESSED); \
    input_process_sticking(self, KEY, INPUT_PRESSED); \
    self->ready = true; \
  } \
  else if (INPUT_RELEASED == _INPUT_GET_SENSOR(self, BTN)) { \
    _INPUT_SET_KEY(self, KEY, INPUT_RELEASED); \
    input_process_sticking(self, KEY, INPUT_RELEASED); \
  } \
} while (0)

  _UPDATE_BTKKEY(QT60168_TOUCH_SENSOR_BUTTON_0, INPUT_F1);
  _UPDATE_BTKKEY(QT60168_TOUCH_SENSOR_BUTTON_1, INPUT_F2);
  _UPDATE_BTKKEY(QT60168_TOUCH_SENSOR_BUTTON_2, INPUT_F3);
  _UPDATE_BTKKEY(QT60168_TOUCH_SENSOR_BUTTON_3, INPUT_F4);

#define _UPDATE_WHKEY(SENS0, SENS1, ACT) do { \
  if (_INPUT_GET_SENSOR(self, SENS0) == INPUT_RELEASED && \
      _INPUT_GET_SENSOR(self, SENS1) == INPUT_PRESSED) { \
    ACT(self); \
    self->ready = true; \
  } \
} while (0)

  // scroll to right
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_0,
      QT60168_TOUCH_SENSOR_WHEEL_1, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_1,
      QT60168_TOUCH_SENSOR_WHEEL_2, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_2,
      QT60168_TOUCH_SENSOR_WHEEL_3, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_3,
      QT60168_TOUCH_SENSOR_WHEEL_4, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_4,
      QT60168_TOUCH_SENSOR_WHEEL_5, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_5,
      QT60168_TOUCH_SENSOR_WHEEL_6, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_6,
      QT60168_TOUCH_SENSOR_WHEEL_7, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_7,
      QT60168_TOUCH_SENSOR_WHEEL_8, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_8,
      QT60168_TOUCH_SENSOR_WHEEL_9, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_9,
      QT60168_TOUCH_SENSOR_WHEEL_10, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_10,
      QT60168_TOUCH_SENSOR_WHEEL_11, _INPUT_SET_WHEEL_RIGHT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_11,
      QT60168_TOUCH_SENSOR_WHEEL_0, _INPUT_SET_WHEEL_RIGHT);

  // scroll to left
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_0,
      QT60168_TOUCH_SENSOR_WHEEL_11, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_11,
      QT60168_TOUCH_SENSOR_WHEEL_10, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_10,
      QT60168_TOUCH_SENSOR_WHEEL_9, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_9,
      QT60168_TOUCH_SENSOR_WHEEL_8, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_8,
      QT60168_TOUCH_SENSOR_WHEEL_7, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_7,
      QT60168_TOUCH_SENSOR_WHEEL_6, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_6,
      QT60168_TOUCH_SENSOR_WHEEL_5, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_5,
      QT60168_TOUCH_SENSOR_WHEEL_4, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_4,
      QT60168_TOUCH_SENSOR_WHEEL_3, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_3,
      QT60168_TOUCH_SENSOR_WHEEL_2, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_2,
      QT60168_TOUCH_SENSOR_WHEEL_1, _INPUT_SET_WHEEL_LEFT);
  _UPDATE_WHKEY(QT60168_TOUCH_SENSOR_WHEEL_1,
      QT60168_TOUCH_SENSOR_WHEEL_0, _INPUT_SET_WHEEL_LEFT);

#undef _UPDATE_BTKKEY
#undef _UPDATE_WHKEY
}

void
input_task(input_t* self) {
  _input_scan_keys(self);
  _input_update_keys_state(self);
  if (self->ready) {
    MCALL(&self->gui_input, onready);
    input_stick_fsm(self->stick_keys+INPUT_F1, self, INPUT_STICK_FSM_RESET_EV, INPUT_F1);
    input_stick_fsm(self->stick_keys+INPUT_F2, self, INPUT_STICK_FSM_RESET_EV, INPUT_F2);
    input_stick_fsm(self->stick_keys+INPUT_F3, self, INPUT_STICK_FSM_RESET_EV, INPUT_F3);
    input_stick_fsm(self->stick_keys+INPUT_F4, self, INPUT_STICK_FSM_RESET_EV, INPUT_F4);
  }
}

int
input_test_event(input_t* self, gui_events_t event) {

#define _KEY_STATE(KEY) \
  (self->stick_keys[KEY].fsm.state==INPUT_STICK_FSM_NO_EMUL_ST? \
   self->keys[KEY] : self->stick_keys[KEY].fsm.state)

// FIXED: moved all FSM switching to input_task() to prevent problem with 2
// and more test_event() calling in ONE input cycle and getting different results!
  bool ret;
  switch (event) {
    case GUI_ESCAPE_EVENT:
      ret = (_KEY_STATE(INPUT_F1) == INPUT_PRESSED);
      //input_stick_fsm(self->stick_keys+INPUT_F1, self, INPUT_STICK_FSM_RESET_EV, INPUT_F1);
      break;
    case GUI_UP_EVENT:
      ret = (_KEY_STATE(INPUT_F2) == INPUT_PRESSED);
      //input_stick_fsm(self->stick_keys+INPUT_F2, self, INPUT_STICK_FSM_RESET_EV, INPUT_F2);
      break;
    case GUI_DOWN_EVENT:
      ret = (_KEY_STATE(INPUT_F3) == INPUT_PRESSED);
      //input_stick_fsm(self->stick_keys+INPUT_F3, self, INPUT_STICK_FSM_RESET_EV, INPUT_F3);
      break;
    case GUI_ENTER_EVENT:
      ret = (_KEY_STATE(INPUT_F4) == INPUT_PRESSED);
      //input_stick_fsm(self->stick_keys+INPUT_F4, self, INPUT_STICK_FSM_RESET_EV, INPUT_F4);
      break;
    default:
      ret = 0;
      break;
  }
  return (ret);
#undef _KEY_STATE
}

void
input_reset_event(input_t* self, gui_events_t event) {

  switch (event) {
    case GUI_ESCAPE_EVENT:
      _INPUT_SET_KEY(self, INPUT_F1, INPUT_IDLE);
      break;
    case GUI_UP_EVENT:
      _INPUT_SET_KEY(self, INPUT_F2, INPUT_IDLE);
      break;
    case GUI_DOWN_EVENT:
      _INPUT_SET_KEY(self, INPUT_F3, INPUT_IDLE);
      break;
    case GUI_ENTER_EVENT:
      _INPUT_SET_KEY(self, INPUT_F4, INPUT_IDLE);
      break;
  }
#undef _MAKE_STICK_PAUSE
}

inline static void
input_process_sticking(input_t* self, input_keys_t key, input_state_t new_state) {
  input_stick_fsm_event_t fsm_ev;
  // generate events for sticking fsm
  if (cpu_is_timeout(&self->stick_keys[key].long_to)) {
    fsm_ev = INPUT_STICK_FSM_RAISED_LTO_EV;
  } else if (cpu_is_timeout(&self->stick_keys[key].short_to)) {
    fsm_ev = INPUT_STICK_FSM_RAISED_STO_EV;
  } else if (new_state != INPUT_PRESSED) {
    fsm_ev = INPUT_STICK_FSM_CHG_KEYST_EV;
  } else if (new_state == INPUT_PRESSED) {
    fsm_ev = INPUT_STICK_FSM_SAME_KEYST_EV;
  } else
    return;

  input_stick_fsm(&self->stick_keys[key], self, fsm_ev, key);
}

/// filter new state: returns true - allowed, false - bounce (rejected)
inline static bool
input_fltbounce_filter(input_fltbounce_t* self, input_state_t new_state, int msdelay) {
  if (new_state == self->state) {
    if (_INPUT_FLTBOUNCE_IS_TO(self)) {
      return (true);
    }
  } else {
    _INPUT_FLTBOUNCE_APPLY_STATE(self, new_state, msdelay);
  }
  return (false);
}

inline static void
input_stick_fsm(input_stick_t* self, input_t* input, input_stick_fsm_event_t ev, input_keys_t key) {

  switch (self->fsm.state) {
    case INPUT_STICK_FSM_NO_EMUL_ST:
        if (ev == INPUT_STICK_FSM_RAISED_LTO_EV) {
          DBG_ASSERT(0);
        } else if (ev == INPUT_STICK_FSM_RAISED_STO_EV) {
          DBG_ASSERT(0);
        } else if (ev == INPUT_STICK_FSM_CHG_KEYST_EV) {
          ;
        } else if (ev == INPUT_STICK_FSM_SAME_KEYST_EV) {
          cpu_set_timeout(cpu_ms_2_cy(INPUT_KEY_STICK_TIME, FCPU), &self->long_to);
          self->fsm.state = INPUT_STICK_FSM_EMUL_PRESSED_ST;
        } else if (ev == INPUT_STICK_FSM_RESET_EV) {
          ;
        }
      break;


    case INPUT_STICK_FSM_EMUL_PRESSED_ST:
        if (ev == INPUT_STICK_FSM_RAISED_LTO_EV) {
          _INPUT_STICK_STOP(self);
          MCALL(input, onstickkey, key);
          self->fsm.state = INPUT_STICK_FSM_NO_EMUL_ST;
        } else if (ev == INPUT_STICK_FSM_RAISED_STO_EV) {
          DBG_ASSERT(0);
        } else if (ev == INPUT_STICK_FSM_CHG_KEYST_EV) {
          _INPUT_STICK_STOP(self);
          self->fsm.state = INPUT_STICK_FSM_NO_EMUL_ST;
        } else if (ev == INPUT_STICK_FSM_SAME_KEYST_EV) {
          ;
        } else if (ev == INPUT_STICK_FSM_RESET_EV) {
          cpu_set_timeout(cpu_ms_2_cy(INPUT_REPEAT_TIME, FCPU), &self->short_to);
          self->fsm.state = INPUT_STICK_FSM_EMUL_RELEASED_ST;
        }
      break;


    case INPUT_STICK_FSM_EMUL_RELEASED_ST:
        if (ev == INPUT_STICK_FSM_RAISED_LTO_EV) {
          _INPUT_STICK_STOP(self);
          MCALL(input, onstickkey, key);
          self->fsm.state = INPUT_STICK_FSM_NO_EMUL_ST;
        } else if (ev == INPUT_STICK_FSM_RAISED_STO_EV) {
          cpu_stop_timeout(&self->short_to);
          self->fsm.state = INPUT_STICK_FSM_EMUL_PRESSED_ST;
        } else if (ev == INPUT_STICK_FSM_CHG_KEYST_EV) {
          _INPUT_STICK_STOP(self);
          self->fsm.state = INPUT_STICK_FSM_NO_EMUL_ST;
        } else if (ev == INPUT_STICK_FSM_SAME_KEYST_EV) {
          ;
        } else if (ev == INPUT_STICK_FSM_RESET_EV) {
          ;
        }
      break;
  }
}
H file is:
#ifndef INPUT_H_
#define INPUT_H_

#include <stdbool.h>
#include <board.h>
#include <gpio.h>
#include <spi.h>
#include <qt60168.h>
#include <conf_qt60168.h>
#include <cycle_counter.h>
#include "config/conf_app.h"
#include "gui.h"

#if !defined(FCPU)
# error Define FCPU to support sticking of keys!
#endif

// config {{{
#ifndef INPUT_KEY_STICK_TIME
# define INPUT_KEY_STICK_TIME 2500
# warning Default INPUT_KEY_STICK_TIME is used: 2500 ms
#endif
#ifndef INPUT_SENS_TIME
# define INPUT_SENS_TIME 10
# warning Default INPUT_SENS_TIME is used: 10 ms
#endif
#ifndef INPUT_REPEAT_TIME
# define INPUT_REPEAT_TIME 300
# warning Default INPUT_REPEAT_TIME is used: 300 ms
#endif
// }}}

struct input_t;

/// state of key/sensor
typedef enum input_state_t {
  INPUT_IDLE,
  INPUT_PRESSED,
  INPUT_RELEASED,
  INPUT_LEFT,
  INPUT_RIGHT
} input_state_t;

/// keys
typedef enum input_keys_t {
  INPUT_F1, /* 0 */
  INPUT_F2,
  INPUT_F3,
  INPUT_F4,
  INPUT_WHEEL,
  INPUT_NUMBER_OF_KEYS ///< total number, should be last
} input_keys_t;

/// for filter key bounce (there is, yes!)
typedef struct input_fltbounce_t {
  t_cpu_time to;
  input_state_t state;
} input_fltbounce_t;
#define INPUT_FLTBOUNCE_INITIALIZER(...) { \
  .to = {0}, \
  .state = INPUT_IDLE, \
  __VA_ARGS__ }

/// for key sticking, repeat (long pressing)
typedef enum input_stick_fsm_state_t {
  INPUT_STICK_FSM_NO_EMUL_ST = INPUT_IDLE,
  INPUT_STICK_FSM_EMUL_PRESSED_ST = INPUT_PRESSED,
  INPUT_STICK_FSM_EMUL_RELEASED_ST = INPUT_RELEASED
} input_stick_fsm_state_t;

typedef enum input_stick_fsm_event_t {
  INPUT_STICK_FSM_RAISED_LTO_EV,   // 0
  INPUT_STICK_FSM_RAISED_STO_EV,   // 1
  INPUT_STICK_FSM_CHG_KEYST_EV,  // 2
  INPUT_STICK_FSM_SAME_KEYST_EV,   // 3
  INPUT_STICK_FSM_RESET_EV   // 4
} input_stick_fsm_event_t;

typedef struct input_stick_t {
  t_cpu_time long_to, ///< long timeout
       short_to; ///< short (for emulating)
  struct {
    input_stick_fsm_state_t state; ///< the same values as input_state_t!
  } fsm;
} input_stick_t;

#define INPUT_STICK_INITIALIZER(...) { \
  .long_to = {.delay_start_cycle=0, .delay_end_cycle=0, .timer_state=CPU_TIMER_STATE_STOPPED}, \
  .short_to = {.delay_start_cycle=0, .delay_end_cycle=0, .timer_state=CPU_TIMER_STATE_STOPPED}, \
  .fsm = { .state=INPUT_STICK_FSM_NO_EMUL_ST }, \
  __VA_ARGS__ }

/// input class {{{
typedef void (*input_onstickkey_t)(struct input_t* self, enum input_keys_t key);
typedef struct input_t {
  gui_input_if_t gui_input;
  input_state_t sensors[QT60168_TOUCH_NUMBER_OF_SENSORS]; ///< states of sensors
  input_fltbounce_t fltsensors[QT60168_TOUCH_NUMBER_OF_SENSORS]; ///< filtered states
  input_state_t keys[INPUT_NUMBER_OF_KEYS]; ///< states of keys
  input_stick_t stick_keys[INPUT_NUMBER_OF_KEYS]; ///< sticking of keys
  int wheel; ///< wheel amount (counter)
  bool ready; ///< there is some input
  input_onstickkey_t onstickkey;
} input_t;

#define INPUT_INITIALIZER(...) { \
  .gui_input = GUI_INPUT_IF_INITIALIZER( \
      .test_event = (gui_input_if_test_event_t)input_test_event, \
      .reset_event = (gui_input_if_reset_event_t)input_reset_event \
      ), \
  .sensors = {INPUT_IDLE}, \
  .fltsensors = {INPUT_FLTBOUNCE_INITIALIZER()}, \
  .keys = {INPUT_IDLE}, \
  .stick_keys = {INPUT_STICK_INITIALIZER()}, \
  .wheel = 0, \
  .ready = false, \
  .onstickkey = NULL, \
  __VA_ARGS__ }

// }}}

/// Global instance (use *INPUT* pointer)
extern struct input_t _input;
#define INPUT (&_input)

void input_init(input_t* self);
void input_task(input_t* self);
/// Check if event occurs (key pressing)
int input_test_event(input_t* self, enum gui_events_t event);
//int input_get_event(input_t* self, gui_event_t *event);
void input_reset_event(input_t* self, enum gui_events_t event);

#endif /* INPUT_H_ */
Example of usage:
...
void onstickkey(input_t* self, input_keys_t key) {
 // do something...
}

void main(void) {
  ...
  input_init(INPUT);
  INPUT->onstickkey = onstickkey;
  ...
  while (1) { // main loop with "tasks"
    ...
    input_task(INPUT);
    ...
  }
}
onstickkey (callback on long pressing) will call automatically when user touch Qtouch sensor for long time.
Somewhere in the program you can test pressed key:
if (input->test_event(input, GUI_UP_EVENT)) {
  // event UP
  // do something...
  input->reset_event(input, GUI_UP_EVENT);
}
This apptoach of processing Qtouch sensors has only one limitation: it treats 'pressing' as touch ("down"), not as sensor release, IMHO it's more natural way when user has deal with touch sensors instead of real keys.

пятница, 22 марта 2013 г.

ASF/AVR32: how to read/write to FLASH

First, open example ASF project on FLASHC read/write. Get code from and paste into your project. Also add *.lds file from example (...asf/avr32/drivers/flashc/flash_example//.lds) into your project. Then add to Linker options next:
--rodata-writable -Wl,--direct-data -T../src/asf/avr32/drivers/flashc/flash_example/at32uc3a3256_evk1104/link_uc3a3256.lds
(first in options line on tab "Miscellaneous" and correct path).
Now build and no any linker errors about regions...

четверг, 14 марта 2013 г.

AVR32/ASF: ANSI terminal on UART

It's good known goal - to run terminal (with some kind of shell) of one (or all) of the UARTs. This snippet shows how to process input byte from UART and to emulate ANSI terminal behavior:

/**
 * Handle input byte via USART; process it to emulate edit line behaviour.
 * After processing, sets self->response array to 4 possible responses (until
 * one is NULL). sh_task() will send these responses via UART...
 * Each response can be usual ASCIIZ but also can be ANSI ESC codes, so user
 * terminal should be in ANSI TERM mode. Supported:
 * - BS/DEL - delete prev. char
 * - ^U - delete entire line
 * - ^L - clear screen but keep edited line
 * - TAB - changed to SPACE
 * On error sends BEL ('\7')
 */
void handle_input_byte(sh_t *self, uint8_t ch)
{
    switch (ch) {
        case '\r':
            ch = '\n';
        case '\n':
            self->inpos = 0;
            self->inbuf[0] = 0;
            _SETRESP(self, "\r\n", self->promptbuf, NULL, NULL);
        break;

        /* backspace, del */
        case '\b':
        case '\x7f':
            if (self->inpos > 0) {
                self->inpos--;
                self->inbuf[self->inpos] = 0;
                _SETRESP(self, "\b \b", NULL, NULL, NULL);
            } else {
                _SETRESP(self, "\a", NULL, NULL, NULL);
            }
        break;

        /* ^U - del line */
        case 'u' & 0x1f:
            self->inpos = 0;
            self->inbuf[self->inpos] = 0;
            _SETRESP(self, "\x1B[2K\r", self->promptbuf, NULL, NULL);
        break;

        /* ^L - clear screen */
        case 'l' & 0x1f:
            _SETRESP(self, "\x1B[2J\x1B[H", self->promptbuf, self->inbuf, NULL);
        break;

        /* all other chars */
        case '\t':
            ch = ' ';
        default:
            if ((ch >= (uint8_t)' ' && ch <= (uint8_t)'\x7e')
                            || ch >= (uint8_t)'\xa0') {
                // printable
                if (self->inpos < sizeofarray(self->inbuf) - 1) {
                    // in bounds (-2 is for ch, -1 is for '\0')
                    self->inbuf[self->inpos++] = ch;
                    self->inbuf[self->inpos] = 0;
                    _SETRESP(self, self->inbuf + (self->inpos-1), NULL, NULL, NULL);
                } else {
                    _SETRESP(self, "\a", NULL, NULL, NULL);
                }
            } else {
                // non-printable
                _SETRESP(self, "\a", NULL, NULL, NULL);
            }
        break;
    }
}
Nothing special: no support of editing in the middle of the line, only at the end, clearing of line, screen, deleting last char. It generates ANSI ESC sequencies. self is the pointer to special struct with inbuf char buffer and inpos - current position of added bytes, so entered line will be in the inbuf. _SETRESP() is the macros - it sets 4 char pointers to be sent in sh_task() function (sends all 4 or until one of them is NULL) - it's looks similar like yielding of responces in WSGI Python Web application, but limited with 0 to 4 parts only :)
sh_task() is call in main loop and sends these responces, handle_input_byte() is called in UART ISR routine to save input and prepare responces. Using of struct, wrapped all needed resources helps us to avoid global and single shell only, so we can have several shells on several UARTs.
sizeofarray() is trivial macros, returns array items number. Short example how it looks:

ANSI terminal on AVR chip from bapcyk on Vimeo.

понедельник, 17 декабря 2012 г.

Tags generation for ASF project

If you use Atmel Software Framework you can generate tags (with ctags|GNU Global) for your Vim/Emacs environment in such way (in Makefile):
ctags = c:/Program Files/ctags.exe
gtags = c:/global/bin/gtags.exe
sed = c:/MinGW/msys/1.0/bin/sed.exe
find = c:/MinGW/msys/1.0/bin/find.exe

GTAGS_FILES = GTAGS GPATH GRTAGS
# Global can not parse out of c:/prj/PRJ1/src
TAGS_DIRS = 'c:/Program Files/Atmel/Atmel Studio 6.0/extensions/Atmel/AVRGCC/3.3.2.31/AVRToolchain/avr32/include' \
   c:/prj/PRJ1/src/asf-3.0.1 \
   c:/prj/PRJ1/src/PRJ1

tags.files: force
 $(find) $(TAGS_DIRS) -type f -name *.S -or -name *.h -or -name *.c|$(sed) 's/c:\/prj\/PRJ1\/src/./g' >tags.files

$(GTAGS_FILES): tags.files
 $(gtags) -f tags.files

.PHONY: global_tags
global_tags: $(GTAGS_FILES)

tags: tags.files
 $(ctags) -L tags.files

.PHONY: all_tags
all_tags: tags global_tags

.PHONY: clean_tags
clean_tags:
 rm -f $(GTAGS_FILES) tags
I suppose in this example you use Windows, have MinGW (with find, sed utils). Project is located in c:\prj and has name PRJ1. To generate use targets: tags, global_tags or all_tags. tags file will be about 100 MB, Global tags files will be about 70 MB, but Global can not parse files out of source tree, so avr32/include will be missed - use ctags instead of.

Calculating timer/clock parameters (prescalers) for 'tick' generation with AVR32

Task similar to this (AVR) but for AVR32 with usage of ASF. If we need to generate ticks, we can use one of wave generation mode of AVR32 TCs. For this we need to define value of RC register and to select clock source (internal line). We use PBA line divided by 2, 8, 32, 128 dividers (so sources are TC_CLOCK_SOURCE2..5 in ASF 'terminology' - macroses :).
Here is the source of calculator of RC, divider:
#include <stdint.h>
#include <limits.h>

// max value of RC register with type uint16_t (unsigned short of AVR32 GCC)
#define MAX_RC (1<<(sizeof(uint16_t)*CHAR_BIT))
int
calc_tc_params(long *freq, long *err, uint16_t *presc, uint16_t *rc)
{
        static const long divs[] = {2, 8, 32, 128};
        static const int ndivs = sizeof divs/sizeof divs[0];
        long _err = 0, _merr = *err;
        uint16_t _rc = 0, _mrc = 0, _idiv = 0, _midiv = 0;

        for (_idiv = 0; _idiv < ndivs; _idiv++) {
                _rc = (FPBA / divs[_idiv]) / (*freq);
                _rc = (_rc==0? 1 : _rc>MAX_RC? MAX_RC : _rc);
                _err = abs((*freq) - (FPBA / divs[_idiv]) / (long)_rc);
                if (_err <= _merr) {
                        _merr = _err;
                        _midiv = _idiv;
                        _mrc = _rc;
                }
                if (!_err) break;
        }

        if (_mrc) {
                *rc = _mrc;
                *err = _merr;
                *presc = divs[_midiv];
                *freq = (FPBA / divs[_midiv]) / _mrc;
                return (1);
        } else
                return (0);
}
NOTE: FPBA is the frequency of PBA line. Inputs are:
  • freq - frequency in Hz
  • err - absolute error in Hz
Outputs are:
  • freq - real frequency for calculated params
  • err - real absolute error
  • presc - divider value (2..128)
  • rc - value for RC register to load
Timer source can be found in this way:
#define PRESC2TCSRC(PRESC) ((PRESC)==2? TC_CLOCK_SOURCE_TC2 \
                            :(PRESC)==8? TC_CLOCK_SOURCE_TC3 \
                            :(PRESC)==32? TC_CLOCK_SOURCE_TC4 \
                            :(PRESC)==128? TC_CLOCK_SOURCE_TC5 \
                            :-1)
and it's result should be used as tcclks member of tc_waveform_opt_t struct.
On success, returns 1, 0 otherwise.
For more information how to start 'ticks' generation, see ASF example TC_EXAMPLE3 of TC module.

понедельник, 17 сентября 2012 г.

ASF: simple utility for font editing

Used fonts in Atmel Software Framework are simple: each glyph is described with array of bytes - each set bit is pixel, no bit - no pixel :)
Next is the very-very simple Tcl utility to convert glyph bytes into console image of symbol and vice-verse.

# parsing and loading font
#------------------------------------------------------------------------------
proc parsesymb str {
    # Parse one symbol in notation like:
    # "0x06,0x08,0x08,0x00,0x00,0x00,0x00,0x00"

    set bytes [regexp -all -inline {0[xX][0-9a-fA-F]{2}} $str]
    return $bytes
}

proc putsymbline byte {
    # put on screen one line of a symbol

    set mask 0b10000000
    for {set i 0} {$i<8 data-blogger-escaped-bit="bit" data-blogger-escaped-byte="byte" data-blogger-escaped-expr="expr" data-blogger-escaped-i="i" data-blogger-escaped-incr="incr" data-blogger-escaped-mask="mask" data-blogger-escaped-set="set">>1}]
        if $bit {
            puts -nonewline "#"
        } else {
            puts -nonewline " "
        }
    }
}

proc putsymb symb_bytes {
    # put on screen all symbol (all it's lines)

    puts "+--------+"
    foreach sb $symb_bytes {
        puts -nonewline "|"
        putsymbline $sb
        puts "|"
    }
    puts "+--------+"
}

# Compile glyphs to font in C format
#-------------------------------------------------------------------------------
proc compsymbline line {
    # Compile line like "# # # # " to 0xAA

    set bits [string map { " " 0 # 1} $line]
    return [format 0x%02X "0b$bits"]
}

proc compsymb symb_lines {
    # Compile lines of symbol (what out 'putsymb')

    set read 0
    set res {}
    foreach line $symb_lines {
        if {0 == $read} {
            if {$line eq "+--------+"} {
                set read 1
            }
        } else {
            if {$line eq "+--------+"} {
                break
            } else {
                set line [string trim $line "|"]
                lappend res [compsymbline $line]
            }
        }
    }
    return [join $res ","]
}

proc getlines {} {
    set lines {}
    while {[gets stdin line] >= 0} {
        lappend lines $line
    }
    return $lines
}

proc main args {
    global argc argv
    if {$argc} {
        set argv0 [lindex $argv 0]
        if {$argv0 eq "comp"} {
            set lines [getlines]
            puts [compsymb $lines]
            return
        } elseif {$argv0 eq "put"} {
            set line [gets stdin]
            puts [putsymb [parsesymb $line]]
            return
        }
    }
    puts {\
Syntax on Linux:
    1. cat file|PRG comp
    2. cat file|PRG put
Syntax on Windows:
    1. more file|PRG comp
    2. more file|PRG put

This means:
    1. Compile glyph image into C hex array
    2. Put glyph hex array on the screen as glyph image

Glyph images and its' hex arrays are inverse}
}

main
}
Example of usage:
$echo 0x20,0x78,0xA0,0x70,0x28,0xF0,0x20,0x00|tclkitsh font.tcl put
+--------+
|  #     |
| ####   |
|# #     |
| ###    |
|  # #   |
|####    |
|  #     |
|        |
+--------+
$
This is the "$" symbol. It's bytes I get from et024006dhu.c (see const unsigned char FONT6x8[97][8]) From another hand, you can save this glyph (with ASCII-box!) in some file and then call:
$cat somefile.txt|tclkitsh font.tcl comp
0x20,0x78,0xA0,0x70,0x28,0xF0,0x20,0x00
$
to get bytes of the glyph.

четверг, 26 июля 2012 г.

ASF: RGB macros

Famouse RGB macros for R5G6B5 for ET0240006 (analogue of et024006_Color(R, G, B) in Atmel Software Framework):
#define GUI_RGB(R, G, B) ((((R) & 0xF8)<<8) | (((G) & 0xFC)<<3) | ((B)>>3))
may be used anywhere - where you need constant, not function call (initializer of structs members and so on)

понедельник, 16 июля 2012 г.

ASF, AVR32, ET024006: et024006_PutPixmap() and bitmap format

How to use bitmap on ET024006 with ASF (Atmel Software Framework)? Format of bitmap file, used in et024006_PutPixmap(), is the R5G6B5, you can create image in the GIMP, then export to 'C source file' (select 'save as RGB 565 16 bits', and uncheck 'Use Glib types') and get something like this:
/* GIMP RGBA C-Source image dump (appicon.c) */
#define GIMP_IMAGE_WIDTH (22)
#define GIMP_IMAGE_HEIGHT (22)
#define GIMP_IMAGE_BYTES_PER_PIXEL (4) /* 3:RGB, 4:RGBA */
#define GIMP_IMAGE_PIXEL_DATA ((unsigned char*) GIMP_IMAGE_pixel_data)
static const unsigned char GIMP_IMAGE_pixel_data[22 * 22 * 4 + 1] =
("\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
...
);
I change it to:
const unsigned char appmenu_icon[] =
 ("\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
...
);
And then call:
et024006_PutPixmap((et024006_color_t*)appmenu_icon,
     appmenu_icon_width,
     0, 0, x, y,
     appmenu_icon_width,
     appmenu_icon_height);
appmenu_icon_width and appmenu_icon_height may be #define's

пятница, 6 апреля 2012 г.

AVR32: development without IDE - only command line tools

How-To install environment for AVR32 development on Windows.


I downloaded avr-toolchain-installer-3.2.3.579-win32.win32.x86.exe and install it. Then downloaded asf-standalone-archive-3.0.1.zip (Atmel Software Framework)and unpacked it into c:\Program Files\Atmel\AVR Tools\. Then downloaded uname.exe (see bug with uname.exe, returns "windows32" on Windows instead of something like "MINGW32_NT-6.0") and replace with it installed c:\Program Files\Atmel\AVR Tools\AVR Toolchain\bin\uname.exe.

Testing of compiling ASF application: cd into c:\Program Files\Atmel\AVR Tools\asf-3.0.1\avr32\applications\uc3-dsplib-demo\at32uc3a3256_evk1104\gcc\ and run make:
"c:\Program Files\Atmel\AVR Tools\AVR Toolchain"\bin\make
after it you'll get .hex (and .elf and many other) files there!

Another way is to install AVR Studio and to use it's console but AVR Studio is so big :)

Programming a device


There are different ways to do it. For example, with avrdude, avr32program or atprogram. avrdude is free GNU program for Linux, Windows, Mac, it needs libusb to be installed. If you use Windows, then get it from http://sourceforge.net/projects/libusb-win32/, attach device, install (with inf-wizard.exe) driver (select your device from list, generate .inf and install), then run something like this:
avrdude.exe -P usb -c %PROGRAMMATOR% -p %MCU%
. Last (today:) version of avrdude for Windows is here: http://download.savannah.gnu.org/releases/avrdude/avrdude-5.11-Patch7610-win32.zip.
If you use AVR Dragon and Windows Vista, you can have problems with this!
For AVRDragon install last AVR Studio and use it's programmer tool: Jungo WinDriver. It's possible to install Jungo driver without studio, but you need also atprogram.exe or avr32program.exe tools. They needs XML files (database of MCUs and looks for themin installed Studio path).
Jungo driver will conflict with libusb drivers. So, remove libusb drivers first. For Windows: click WinKey+Pause, open Devices' manager and remove (deinstall) libusb drivers when device is attached. Then go to C:\Windows\System32 and delete file libusb0.dll. Then go to C:\Windows\System32\Drivers and delete libusb0.sys. Now detach device. Install Jungo drivers (automatically when install Studio or manually), attach device (AVRDragon I'm meaning) and if it ask you about driver files point to C:\Program Files\Atmel\Atmel USB (I thing Windows automatically can find it...). After it you should see in Deviceses' Manager this:
COMPUTER
  |
  +--Jungo
  |    |
  .    +--AVR Dragon
  .    +--WinDriver
and no any libusb32!!! AVRDragon and board should be on power (attached to USB). If Windows found "Unknown devices" (board, attached to USB for power) you can delete it from device list. Now if you use Studio 6 run in Atmel Studio Command Prompt (see Programs menu) something like this:
atprogram -t avrdragon -i jtag -d at32uc3b0512 read -fs -s 4
and you will see data bytes in console.
Better is to run Studio and try to open programming dialog. Studio will verify version of AVR Dragon firmware and upgrade it (if needed).