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

четверг, 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.

воскресенье, 30 сентября 2012 г.

AVR32: simple C pitfall

Often programmers like to convert pointer to one type to pointer to another. It's a usually (and INCORRECT!) "method" to pick-up some integer from byte array. From C standard we know that pointer to one type IS NOT EQUAL to pointer to another. But programmers ignores this fact on x86 :)
They will not ignore this on AVR/AVR32 architecture :)

понедельник, 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).

понедельник, 10 октября 2011 г.

AVR timers: calculate prescalers

Functions below are only for Atmega128, see "bitness" of timers, prescalers values...

When you use AVR timers, you should setup it's prescalers' values. Often, programmers calculates prescaler on the paper and then hardcodes it into the code. And this is done every time for new delay value. This task can be done automatically by next algorithm. Next is the Python prototype of the algorithm.
class Timer:
    freq = 0 # freq. of timer
    bits = 0 # "bitsness" of timer

# We have for example 4 timers
_timers = [Timer(), Timer(), Timer(), Timer()]

def init_timers():
    # setup bitness of the timers
    global _timers
    _timers[0].bits = 256
    _timers[1].bits = 65536
    _timers[2].bits = 256
    _timers[3].bits = 65536

# for Atmega128
def _alarm_setup(timer, D):
    prescales0 = (1, 8, 32, 64, 128, 256, 1024,)
    prescales = (1, 8, 64, 256, 1024,)
    # base freq of timer, the same for all but is possible to specify differents
    freq = 8000000 # Freq. of MCU

    if timer == 0:
        psc = prescales0
    else:
        psc = prescales

    _timers[timer].freq = freq

    T = 1000000 * psc[-1] * _timers[timer].bits / freq
    cycles = D/T
    if cycles:
        D -= cycles*T

    for _psc in psc:
        # T - timer period (step in usec)
        T = 1000000 * _psc * _timers[timer].bits / freq
        # D_T - how many T (timer periods) are in D (delay)
        D_T = float(D)/T
        if D_T <= .5:
            # can not be rounded even to 1, too short delay time!
            return None
        elif D_T > _timers[timer].bits:
            # delay is too long for this prescale value
            continue
        else:
            # bingo!
            cn = _timers[timer].bits - int(D_T) + 1
            prescale = _psc
            return (cn, prescale, cycles)
    return None

# test
init_timers()
print _alarm_setup(0, 10000000)
print _alarm_setup(0, 256)

Function _alarm_setup() returns triplet (start value of timer counter, prescaler value, total cycles of the timer counter). These 3 values defines initial parameters of counter which should count to delay D microseconds. It's possible to define any values of delay time in this manner.
This Python prototype example is only to show idea of algorithm. But next is the real implementation on C for WinAVR:
/* Calculates prescale (divisor of timer: 1,8,...) and start
 * counter value and total cycles number. Returns 0 on success, -1 on faulire.
 * D is the delay in microseconds.  timer is number of timer.
 * Out values are saved in corresponding timerc_t.
 * Atmega128 implementation!!!
 */
int
_alarm_setup(int timer,
             unsigned long D)
{
        int i;
        int max_psc; // max. value of prescaler of timer
        int *psc; // prescalers of timer
        unsigned long T; // period of tick
        unsigned long Tclk; // period of clock
        unsigned long Tall; // period of timer cycle (from 0 to bits)

        psc = _timers[timer].prescalers;
        max_psc = psc[_timers[timer].nprescalers-1];

        /* Base frequency is F_CPU. Real frequency is F_CPU/prescaler.
           One tick occupes 1/freq seconds (freq in MHz). N ticks occupes
           N/freq. N is max ticks number in counter = bits of this counter =
           _timers[timer].bits. So, total time to count all ticks of counter is
           N/freq = N/(F_CPU/prescaler) = N*prescaler/F_CPU. Freq is in MHz, to
           use microseconds instead of seconds, * 1000000.

           T is the total time of count all the counter with slowest freq (with
           last prescaler - max_psc). cycles is how many counter cycles are in
           D with slowest prescaler on this timer-counter.

           Then D set to D without cycles time.
           */

        // 1000000 * max_psc > F_CPU, so T/F_CPU != 0
        T = (1000000 * max_psc / F_CPU) * _timers[timer].bits;
        _timers[timer].cycles = D/T;

        if (_timers[timer].cycles > 0) {
                D -= (_timers[timer].cycles) * T;
                _timers[timer].cycles_prescaler = max_psc;
        }
        else {
                // If does not need cycles, cycles_prescaler will be 0
                // (may be used as flag of this)
                _timers[timer].cycles_prescaler = 0;
        }

        D *= 1000; // in nanoseconds
        Tclk = 1000000000 / F_CPU; // in nanoseconds
        for (i=0; i<_timers[timer].nprescalers; i++) {
                T = Tclk * psc[i]; // time of one tick
                Tall = T * _timers[timer].bits; // time to count all ticks of counter
                if (D > Tall) {
                        // delay D is too long for this Tall
                        continue;
                }
                else {
                        _timers[timer].prescaler = psc[i];
                        // D/T how many ticks in D, cn is ticks from 0
                        _timers[timer].cn = _timers[timer].bits - D/T;
                        return (0);
                }
        }
        return (-1);
}

This function uses global array of timers: _timers. This is the array of the timerc_t structs:
typedef struct timerc_t {
        volatile timer_handler_t alarm_handler;
        volatile unsigned long nover; // number of overflow
        unsigned long bits; // number of bits
        volatile unsigned long cn; // start from this count value
        volatile unsigned long cycles; // cycles number
        volatile int prescaler; // prescaler (1, 8, 32... - depends on arch. and timer)
        volatile int cycles_prescaler; // prescaler (usually 1024) for total counter cycles
        int *prescalers; // array of timer-counter prescalers
        int nprescalers; // length of prescalers' array
} timerc_t; 
Full implementation of timers support for AVR (Atmega128 only at the moment) are here.

четверг, 23 июня 2011 г.

Usage of UART for debug messages on AVR

If you need to print out a debug messages to UART port of AVR chip, you can use a simple way for it: switch stdout stream to UART. This is the code of the solution:
#include <avr/io.h>
#include <stdio.h>

#ifndef NDEBUG

#  if !defined(F_DEBUG_UART) && !defined(F_CPU)
#    error F_DEBUG_UART frequency or F_CPU frequency should be defined!
#  endif

#  ifndef F_DEBUG_UART
#    define F_DEBUG_UART F_CPU
#  endif

#  ifndef DEBUG_NUART
#    error DEBUG_NUART should be defined!
#  endif

#  define __CONC3(X, Y, Z) X ## Y ## Z
#  define _CONC3(X, Y, Z) __CONC3(X, Y, Z)
// _CONC3(UDRE, DEBUG_NUART,) does not works
#  define _UDRE (_CONC3(UDR, E, DEBUG_NUART))
#  define _UDR (_CONC3(UDR, DEBUG_NUART,))
#  define _UBRRH (_CONC3(UBRR, DEBUG_NUART, H))
#  define _UBRRL (_CONC3(UBRR, DEBUG_NUART, L))
#  define _UCSRA (_CONC3(UCSR, DEBUG_NUART, A))
#  define _UCSRB (_CONC3(UCSR, DEBUG_NUART, B))
#  define _UCSRC (_CONC3(UCSR, DEBUG_NUART, C))
#  define _RXEN (_CONC3(RXEN, DEBUG_NUART,))
#  define _TXEN (_CONC3(TXEN, DEBUG_NUART,))
#  define _UCSZ0 (_CONC3(UCSZ, DEBUG_NUART, 0))
#  define _U2X (_CONC3(U2X, DEBUG_NUART,))
#  define DEBUG_UART_SUPPORT() \
static int debug_uart_putchar(char c, FILE *stream); \
static FILE mystdout = FDEV_SETUP_STREAM(debug_uart_putchar, NULL, _FDEV_SETUP_WRITE); \
static int debug_uart_putchar(char c, FILE *stream) \
{ \
        while (0 == (_UCSRA & (1<<_UDRE))) ; \
        _UDR = c; \
        return (0); \
}

/* 8 bits data, 1 stop bit, 9600 kbps, async */
#  define INIT_DEBUG_UART() do { \
        stdout = &mystdout; \
        _UCSRC = 0x06; \
        _UCSRA = 0x00; \
        _UCSRB = 0x18; /* receive, transmit, no interrupts */ \
        uint16_t ubrr = (F_DEBUG_UART/16/9600-1); \
        _UBRRL = (unsigned char)ubrr; \
        _UBRRH = (unsigned char)(ubrr>>8); \
} while (0)

#  define DEBUG_UART_PRN(...) printf(__VA_ARGS__)

#else
#  define DEBUG_UART_SUPPORT()
#  define INIT_DEBUG_UART()
#  define DEBUG_UART_PRN(...)
#endif
Save it into debug_uart.h, for example.
What you need to use it, it's only defining F_DEBUG_UART or F_CPU, DEBUG_NUART and calling 3 macros: DEBUG_UART_SUPPORT() on module level (it generates code, so call it only once!), INIT_DEBUG_UART() to init UART and DEBUG_UART_PRN(...) where you need message printing. The example is:
#define F_DEBUG_UART 7372800
#define DEBUG_NUART 0
#include "debug_uart.h"
DEBUG_UART_SUPPORT();
int
main(void)
{
        INIT_DEBUG_UART();
        DEBUG_UART_PRN("Hello world, %d\n", 99);
        ... so on ...
F_DEBUG_UART is the exact amount of the system oscillator frequency to calculate UART baud rate. Default value is equals to F_CPU, but it's possible, that you have value of fosc = 7.3728MHz (see AVR Datasheet about "Examples of UBRR settings") and you use in the Makefile F_CPU value as 8MHz - if you use 8MHz for UBRR calculation too, you will get errors in transmission! So use for F_DEBUG_UART exact values only!
DEBUG_NUART defined number of UART. If you define NDEBUG, all macros will be empty - you don't need to delete it's calls. For more information see help on WinAVR.

вторник, 21 июня 2011 г.

AVR timers support

These modules implements for AVR8, AVR128, AVR168P chips next functions:
void init_timers(void);
int alarm_action(int timer, timer_handler_t alarm_handler);
int _alarm(int timer, unsigned long usec);
int alarm(int timer, unsigned long usec);
unsigned long tclock(int timer);
unsigned long millis(void);
unsigned long micros(void);
alarm() is like standard C function (but for AVR), it's wrapped by cli()/sei() _alarm(). int timer is the number of the used timer. tclock() is like C's clock() but used timer id. clock() is the tclock(0) - standard C function, implemented on timer 0. millis()/micros() returns milliseconds/microseconds from last call. Before use, call init_timer() first.

Before to use, define F_CPU. CLOCKS_PER_SEC should not be defined. If is, this means that some timer for clock() is already in use. Tested with WinAVR-20100110, but may be needs more testing!

You can download source code from here on Google Project page.

пятница, 10 июня 2011 г.

Software SPI implementation for WinAVR

Sometimes you need software SPI MASTER (in my case it was needed to test one chip) implementation. It supports all SPI modes (0, 1, 2, 3), and clock dividers (but so it's software "SPI", that timing is not exact!), but sends only in MSBFIRST bit order (it's easy to add LSBFIRST support).
See the code.
To use it in your project, you need to define some macro-variables (with #define-s) before include the file. They are:
  • SWSPI_MOSI_PORT - port for MOSI "wire"
  • SWSPI_MOSI_PIN - pin for MOSI "wire"
  • SWSPI_MISO_PORT - like above but for MISO (only port)
  • SWSPI_MISO_PIN - line above, but pin for MISO
  • SWSPI_SCLK_PORT - port for SCLK "wire"
  • SWSPI_SCLK_PIN - pin for SCLK "wire"
  • SWSPI_MODE - mode of SPI communication
  • SWSPI_DIV - if not defined, 1 will be used

The example:

#define SWSPI_MOSI_PORT PORTB
#define SWSPI_MOSI_PIN PB3
#define SWSPI_MISO_PORT PINB
#define SWSPI_MISO_PIN PB4
#define SWSPI_SCLK_PORT PORTB
#define SWSPI_SCLK_PIN PB5
#define SWSPI_MODE SPI_MODE0
#define SWSPI_DIV SPI_CLOCK_DIV2

In my real application I included .c file to local file in my project, it's not convenient way for C, but... seems like this:

common/   |  myprj/
swspi.h   |    conf.h (#include <common/swspi.h> and defines macro-vars)
swspi.h   |    spi.c (#include "conf.h" then #include <common/swspi.c>

You can use another scheme, it's trivial.
Here is the code. The .h file:

#ifndef _COMMON_SWSPI_H
#define _COMMON_SWSPI_H

#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

#define SPI_CLOCK_DIV4 4
#define SPI_CLOCK_DIV16 16
#define SPI_CLOCK_DIV64 64
#define SPI_CLOCK_DIV128 128
#define SPI_CLOCK_DIV2 2
#define SPI_CLOCK_DIV8 8
#define SPI_CLOCK_DIV32 32
#define SPI_CLOCK_DIV64_2 64

#define SPI_MODE0 0
#define SPI_MODE1 1
#define SPI_MODE2 2
#define SPI_MODE3 3

uint8_t spi_transfer(uint8_t data);
uint8_t spi_ntransfer(const uint8_t *req, int reqlen, uint8_t *resp, int resplen);
#define spi_interrupt_on()
#define spi_interrupt_off()

// for nCS (nSEL) select of device before sending
#define BEGIN_SPI_SENDING(PORT, PIN) do { \
        (PORT) &= ~_BV(PIN); \
        _delay_loop_1(4); \
} while (0)

// for nCS (nSEL) unselect of device after sending
#define END_SPI_SENDING(PORT, PIN) do { \
        _delay_loop_1(4); \
        (PORT) |= _BV(PIN); \
} while (0)

#endif /* !_COMMON_SWSPI_H*/

and the .c file (swspi.c):

#include "swspi.h"
#include <util/delay_basic.h>

#if !defined(SWSPI_MOSI_PORT) || !defined(SWSPI_MOSI_PIN) || \
    !defined(SWSPI_MISO_PORT) || !defined(SWSPI_MISO_PIN) || \
    !defined(SWSPI_SCLK_PORT) || !defined(SWSPI_SCLK_PIN) || \
    !defined(SWSPI_MODE)
#  error Software SPI not configured! SWSPI_* should be configured!
#endif

#define setbit(P, B) ((P) |= (_BV(B)))
#define clibit(P, B) ((P) &= ~(_BV(B)))
#define getbit(P, B) (((P) & (_BV(B)))? 1:0)

#ifdef SWSPI_DIV
#  define SPIDELAYTIME ((SWSPI_DIV)/2)
#else
#  define SPIDELAYTIME 1
#endif
#define SPIHALFDELAY() _delay_loop_1(SPIDELAYTIME) // half of tact (bit transfer) - min 1 CPU tact

#define SETMOSI() setbit(SWSPI_MOSI_PORT, SWSPI_MOSI_PIN)
#define CLIMOSI() clibit(SWSPI_MOSI_PORT, SWSPI_MOSI_PIN)
#define NOMOSI() setbit(SWSPI_MOSI_PORT, SWSPI_MOSI_PIN)

// instead of PORTX may be PINX?
#define READMISO() getbit(SWSPI_MISO_PORT, SWSPI_MISO_PIN)

#if (2 & SWSPI_MODE)
#  define ONSCLK() clibit(SWSPI_SCLK_PORT, SWSPI_SCLK_PIN)
#  define OFFSCLK() setbit(SWSPI_SCLK_PORT, SWSPI_SCLK_PIN)
#else
#  define ONSCLK() setbit(SWSPI_SCLK_PORT, SWSPI_SCLK_PIN)
#  define OFFSCLK() clibit(SWSPI_SCLK_PORT, SWSPI_SCLK_PIN)
#endif

#if (1 & SWSPI_MODE)
#  define SHIFTBIT(outbyte, inbyte) do { \
        (outbyte) & 0x80 ? (SETMOSI()) : (CLIMOSI()); \
        (outbyte) <<= 1; \
        ONSCLK(); \
        SPIHALFDELAY(); \
        (inbyte) <<=1; \
        (inbyte) |= READMISO(); \
        OFFSCLK(); \
        SPIHALFDELAY(); \
} while (0)
#else
#  define SHIFTBIT(outbyte, inbyte) do { \
        (outbyte) & 0x80 ? (SETMOSI()) : (CLIMOSI()); \
        (outbyte) <<= 1; \
        SPIHALFDELAY(); \
        ONSCLK(); \
        SPIHALFDELAY(); \
        (inbyte) <<=1; \
        (inbyte) |= READMISO(); \
        OFFSCLK(); \
} while (0)
#endif

uint8_t
spi_transfer(uint8_t data) {
        int nbit;
        uint8_t res = 0;
        for (nbit=0; nbit<8; nbit++) {
                SHIFTBIT(data, res);
        }
        NOMOSI();
        return (res);
}

/* resp - responce from slave device; resplen - max. expected resp. len (buf. size);
 * if resp is NULL, not saved. Returns always the last byte of the response.
 */
uint8_t
spi_ntransfer(const uint8_t *req, int reqlen, uint8_t *resp, int resplen) {
        int nbit;
        int nbyte;
        register uint8_t outbyte;
        uint8_t inbyte = 0;
        for (nbyte=0; nbyte<reqlen; nbyte++) {
                outbyte = req[nbyte];
                inbyte = 0;
                for (nbit=0; nbit<8; nbit++) {
                        SHIFTBIT(outbyte, inbyte);
                }
                if (resp && nbyte < resplen) {
                        resp[nbyte] = inbyte;
                }
        }
        NOMOSI();
        return (inbyte);
}

#undef ONSCLK
#undef OFFSCLK
#undef SETMOSI
#undef CLIMOSI
#undef NOMOSI
#undef READMISO
#undef SPIHALFDELAY
#undef SPIDELAYTIME
#undef SHIFTBIT
#undef SHIFT0
To send and receive response spi_transfer() and spi_ntransfer() functions are needed. First sends one byte and returns responce's byte (from MISO), seconde - sends several bytes and saves them into 'resp' buffer (contrained by 'resplen') and if resp i NULL, returns the last byte only.
Before any transfer (one or several bytes in the one request) to some chip you should call macro BEGIN_SPI_SENDING(CHIP_NSEL_PORT, CHIP_NSEL_PIN), where CHIP_NSEL_PORT and CHIP_NSEL_PIN defines nCS wire for this concrete chip. After sending you should call END_SPI_SENDING(CHIP_NSEL_PORT, CHIP_NSEL_PIN) to release the wire by nCS.