Страницы

среда, 13 июня 2018 г.

Сенсорные кнопки с переносом заряда на STM8L


Сенсорные кнопки с переносом заряда на STM8L


Сенсорные кнопки с переносом заряда имеют хорошую помехозащищённость и чувствительность, но более сложны в реализации. На универсальных микроконтроллерах (на примере STM8S серии) вполне возможно получить такие кнопки. Такая реализация описана в этой статье. Для каждой кнопки нужно использовать два вывода микроконтроллера и один накопительный конденсатор. Эту схему успешно повторили на микроконтроллерах ATTiny, ATMega. В линейке продуктов фирмы STM есть большое количество контроллеров, поддерживающих сенсорные кнопки как на аппаратном, так и на программном уровне. Однако, для их реализации нужен накопительный конденсатор на каждую группу выводов (от 1-й до 3-х сенсорных кнопок), и размер библиотеки совершенно не оптимальный для мелких контроллеров. Используя систему внутренней коммутации Routing Interface (RI) и программную реализацию, я построил сенсорные кнопки с одним накопительным конденсатором на контроллер. Это позволило освободить выводы для других целей в небольшом двадцатиногом корпусе.
Мой проект построен на микроконтроллере STM8L051F3 в корпусе TSSOP-20, но может быть реализован на любом контроллере этой серии, в котором есть блок RI.
Схема соединений сенсорных кнопок:



Это кусочек схемы моей сенсорной лампы, для примера. C4 как раз и есть накопительный конденсатор, а S1-S3сенсорные кнопки. Резисторы R11-R13 служат для защиты выводов МК от статического электричества. Внутренние соединения выделены на следующем рисунке:




Здесь использован рисунок 25 из документа RM0031 (reference manual for STM8L series).
Основные определения:
 Как видите, используя внутренние ключи и управление выводами, можно заряжать сенсорные датчики и передавать их заряд накопительному конденсатору. Алгоритм работы следующий:
1)    Разрядить накопительный конденсатор,
2)    Зарядить сенсорный датчик, подав на него напряжение питания через вывод микроконтроллера,
3)    Соединить сенсорный датчик с конденсатором и дать заряду датчика перетечь в накопительный конденсатор,
4)    Проверить напряжение на конденсаторе. Если достигло порогового уровня – закончить измерение, если нет – повторить с пункта 1.
5)    Сменить канал измерения и повторить с пункта 1.
6)    После измерения ёмкости всех датчиков подождать несколько миллисекунд.
Ёмкость сенсорного датчика обратно пропорциональна количеству циклов зарядки. Я использую количество циклов напрямую, хотя можно вычислять и ёмкость датчиков в пикофарадах.
Программа измерения ёмкости состоит из двух частей:
a)    Инициализации
b)    Программы конечного автомата, вызываемой из бесконечного основного цикла.
В программе также использована подсистема времени на основе миллисекундного таймера.

Основные определения:

typedef     void  (*mess_func) (void);

typedef     u8    (*pos_func) (void);

typedef     bool  (*cap_func) (void);

typedef     void  (*config_func) (void);



// объявления структур --------------------------------------------------------



typedef enum _CH_states

{

      CH_INIT = 0,

      CH_UNPRESS,

      CH_FOLLOW,

      CH_FOLLOW_LONG

} CH_states;      // определение состояния клавиш





typedef enum _cap_status

{

      C_INIT = 0,

      C_UNPRESS,

      C_FOLLOW,

      C_FOLLOW_LONG

} cap_status;



typedef enum touch_modes

{

  T_CONFIG = 0,

  T_COUNT,

  T_DISCHARGE,

  T_WAIT

} touch_modes;



typedef struct _t_button

{

  u8   cap;   // текущие данные ёмкости кнопки

  u16  acc;  // Аккумулятор для фильтра НЧ

  u8   flt;  // отфильтрованные данные ёмкости пустой кнопки

  u8   calib; // максимальные значения ёмкости датчиков для калибровки уровней

  u8   level; // уровень нажатия кнопки 0-255  

  config_func config; // функция инициализации ёмкостного датчика

  cap_func    func;   // функция обслуживания ёмкостного датчика

} t_button;



typedef struct _t_slider_3_button

{

  t_button    s[3];           // сенсорные датчики

  u8          s_count;        // счётчик окончания измерения ёмкостных датчиков

  cap_status  st;             // состояние измерителя слайдера

  touch_modes mode;           // режим работы слайдера

  u16         timer_decrease; // таймер адаптации к помехам

  u16         debounce;       // таймер антидребезга

  u16         timeout;        // таймер антизалипания

} t_slider_3_button;



static t_slider_3_button            T;

           
Инициализация выводов:


/******************************************************************************

* инициализация обработчика сенсорных кнопок

******************************************************************************/

void Touch::init_touch (void)

{    

  T.st = C_INIT; 

  T.press = m_press;

  T.unpress = m_unpress;

  T.s[0].cap = 0;

  T.s[1].cap = 0;

  T.s[2].cap = 0;

  T.s[0].config = config_s0;  // соответствие пинов и программ обработки

  T.s[1].config = config_s2;

  T.s[2].config = config_s1;

  T.s[0].func = touch_s0; // соответствие реальных пинов программным тач левый

  T.s[1].func = touch_s2; // тач правый

  T.s[2].func = touch_s1; // тач с краёв

  T.s[0].calib = SLIDER_CALIB_0;  //

  T.s[1].calib = SLIDER_CALIB_1;

  T.s[2].calib = SLIDER_CALIB_2;

      T.s_count = 0;

  T.mode = T_WAIT;

 

  // init io pins

  GPIO_ConfigOutput  (S1_PORT1, S1_PIN1, PortHiSpeed, PortPushPull);    // всё управление пинами через RI регистры

  GPIO_ConfigOutput  (S2_PORT1, S2_PIN1, PortHiSpeed, PortPushPull);

  GPIO_ConfigOutput  (S3_PORT1, S3_PIN1, PortHiSpeed, PortPushPull);

  GPIO_ConfigOutput  (C1_PORT1, C1_PIN1, PortHiSpeed, PortPushPull);

  GPIO_ConfigInput  (C2_PORT1, C2_PIN1, PinFloating, PinIrqOff);    // ошибочно подключённый пин

  S1_PORT = 0;

  S2_PORT = 0;

  S3_PORT = 0;

  C1_PORT = 0;

 

  RI->IOCMR1 = RI_IOCMR1_CH1M  * 0  /*!< Channel 1 I/O control mode */

             | RI_IOCMR1_CH4M  * 0  /*!< Channel 4 I/O control mode */

             | RI_IOCMR1_CH7M  * 0  /*!< Channel 7 I/O control mode */

             | RI_IOCMR1_CH10M * 0  /*!< Channel 10 I/O control mode */

             | RI_IOCMR1_CH13M * 0  /*!< Channel 13 I/O control mode */

             | RI_IOCMR1_CH16M * 1  /*!< Channel 16 I/O control mode */

             | RI_IOCMR1_CH19M * 1  /*!< Channel 19 I/O control mode */

             | RI_IOCMR1_CH22M * 0; /*!< Channel 22 I/O control mode */;

  RI->IOCMR2 = RI_IOCMR2_CH2M  * 0  /*!< Channel 2 I/O control mode */

             | RI_IOCMR2_CH5M  * 0  /*!< Channel 5 I/O control mode */

             | RI_IOCMR2_CH8M  * 0  /*!< Channel 8 I/O control mode */

             | RI_IOCMR2_CH11M * 0  /*!< Channel 11 I/O control mode */

             | RI_IOCMR2_CH14M * 0  /*!< Channel 14 I/O control mode */

             | RI_IOCMR2_CH17M * 0  /*!< Channel 17 I/O control mode */

             | RI_IOCMR2_CH20M * 0  /*!< Channel 20 I/O control mode */

             | RI_IOCMR2_CH23M * 1; /*!< Channel 23 I/O control mode */

  RI->IOCMR3 = RI_IOCMR3_CH3M  * 0  /*!< Channel 3 I/O control mode */

             | RI_IOCMR3_CH6M  * 0  /*!< Channel 6 I/O control mode */

             | RI_IOCMR3_CH9M  * 0  /*!< Channel 9 I/O control mode */

             | RI_IOCMR3_CH12M * 0  /*!< Channel 12 I/O control mode */

             | RI_IOCMR3_CH15M * 0  /*!< Channel 15 I/O control mode */

             | RI_IOCMR3_CH18M * 1  /*!< Channel 18 I/O control mode */

             | RI_IOCMR3_CH21M * 0  /*!< Channel 21 I/O control mode */

             | RI_IOCMR3_CH24M * 0; /*!< Channel 24 I/O control mode */

  RI->IOSR1 = RI_IOSR1_CH1E    * 0  /*!< Channel 1 I/O switch control */

             | RI_IOSR1_CH4E   * 0  /*!< Channel 4 I/O switch control */

             | RI_IOSR1_CH7E   * 0  /*!< Channel 7 I/O switch control */

             | RI_IOSR1_CH10E  * 0  /*!< Channel 10 I/O switch control */

             | RI_IOSR1_CH13E  * 0  /*!< Channel 13 I/O switch control */

             | RI_IOSR1_CH16E  * 1  /*!< Channel 16 I/O switch control */

             | RI_IOSR1_CH19E  * 1  /*!< Channel 19 I/O switch control */

             | RI_IOSR1_CH22E  * 0; /*!< Channel 22 I/O switch control */

  RI->IOSR2 = RI_IOSR2_CH2E    * 0  /*!< Channel 2 I/O switch control */

             | RI_IOSR2_CH5E   * 0  /*!< Channel 5 I/O switch control */

             | RI_IOSR2_CH8E   * 0  /*!< Channel 8 I/O switch control */

             | RI_IOSR2_CH11E  * 0  /*!< Channel 11 I/O switch control */

             | RI_IOSR2_CH14E  * 0  /*!< Channel 14 I/O switch control */

             | RI_IOSR2_CH17E  * 0  /*!< Channel 17 I/O switch control */

             | RI_IOSR2_CH20E  * 0  /*!< Channel 20 I/O switch control */

             | RI_IOSR2_CH23E  * 1; /*!< Channel 23 I/O switch control */

  RI->IOSR3 = RI_IOSR3_CH3E    * 0  /*!< Channel 3 I/O switch control */

             | RI_IOSR3_CH6E   * 0  /*!< Channel 6 I/O switch control */

             | RI_IOSR3_CH9E   * 0  /*!< Channel 9 I/O switch control */

             | RI_IOSR3_CH12E  * 0  /*!< Channel 12 I/O switch control */

             | RI_IOSR3_CH15E  * 0  /*!< Channel 15 I/O switch control */

             | RI_IOSR3_CH18E  * 1  /*!< Channel 18 I/O switch control */

             | RI_IOSR3_CH21E  * 0  /*!< Channel 21 I/O switch control */

             | RI_IOSR3_CH24E  * 0; /*!< Channel 24 I/O switch control */



  RI->ASCR1 =  RI_ASCR1_AS0  * 0  /*!< Analog switch AS0 control */

             | RI_ASCR1_AS1  * 0  /*!< Analog switch AS1 control */

             | RI_ASCR1_AS2  * 0  /*!< Analog switch AS2 control */

             | RI_ASCR1_AS3  * 0  /*!< Analog switch AS3 control */

             | RI_ASCR1_AS4  * 0  /*!< Analog switch AS4 control */

             | RI_ASCR1_AS5  * 1  /*!< Analog switch AS5 control */

             | RI_ASCR1_AS6  * 1  /*!< Analog switch AS6 control */

             | RI_ASCR1_AS7  * 1; /*!< Analog switch AS7 control */

  RI->ASCR2 =  RI_ASCR2_AS8  * 0  /*!< Analog switch AS8 control */

             | RI_ASCR2_AS9  * 0  /*!< Analog switch AS9 control */

             | RI_ASCR2_AS10 * 0  /*!< Analog switch AS10 control */

             | RI_ASCR2_AS11 * 0  /*!< Analog switch AS11 control */

             | RI_ASCR2_AS14 * 0; /*!< Analog switch AS14 control */

}


Собственно, программа измерения ёмкости кнопок:




/******************************************************************************

* процедура измерения ёмкости кнопки.

* Вход: сообщение (M_BUTTON_START)

* Выход - функция оценки кнопки t_button ()

*     Данные о ёмкости в структуре C

******************************************************************************/

void Touch::touch_func (void)

{

      static u16  touch_timer = 0;

      static u16  disch_timer = 0;



      switch (T.mode)

      {

  case T_CONFIG:

    T.s[T.s_count].config ();

    T.mode = T_COUNT;

    break;

   

      case T_COUNT:

    if (!T.s[T.s_count].func ())  // перенос заряда

    {

      T.s[T.s_count].cap++;       // если не зарядились - продолжать измерение

    }

    else

    {

      T.s_count++;                // иначе - следующий сенсор

      T.mode = T_DISCHARGE;       // и разрядить накопительный конденсатор

      timer_reset (disch_timer);

      RI->IOGCR = RI_IOGCR_IOM1_FIO

                | RI_IOGCR_IOM2_FIO

                | RI_IOGCR_IOM3_O0    // группа 3 (PB1) = 0

                | RI_IOGCR_IOM4_FIO;

    }

            break;

   

  case T_DISCHARGE:     // разрядка конденсатора

    if (timer_end (disch_timer, DISCHARGE_TIME))

    {

      RI->IOGCR = RI_IOGCR_IOM1_FIO

                | RI_IOGCR_IOM2_FIO

                | RI_IOGCR_IOM3_FIO

                | RI_IOGCR_IOM4_FIO;

      if (T.s_count <= 2)

      {

        T.mode = T_CONFIG;

      }

      else         // когда все сенсоры измерились

      {

        T.s_count = 0;

        t_slider (&T);  // функция оценки кнопок

        T.mode = T_WAIT;

      }

    }

    break;

           

      case T_WAIT:

            if (timer_end (touch_timer, TOUCH_INTERVAL))    // ждём интервал 30 мс

            {

      timer_reset (touch_timer);

      T.s[0].cap = 0;

      T.s[1].cap = 0;

      T.s[2].cap = 0;

      T.mode = T_CONFIG;

            }

            break;



      default:

            T.mode = T_CONFIG;

            break;

      }

}


И служебные программы:


void config_s0 (void)

{

  RI->IOSR1 = RI_IOSR1_CH16E  * 0  /*!< Channel 16 I/O switch control */

            | RI_IOSR1_CH19E  * 0;  /*!< Channel 19 I/O switch control */

  RI->IOSR2 = RI_IOSR2_CH23E  * 1; /*!< Channel 23 I/O switch control */

}

/******************************************************************************

* процедура переноса заряда на накопительный конденсатор

* Вход - указатель на структуру данных кнопки

* Выход - уровень заряда конденсатора

******************************************************************************/

bool touch_s0 (void)

{

  bool reg;

      // фаза 1 - накачка конденсатора сенсорной панели

  RI->IOGCR = RI_IOGCR_IOM1_FIO

            | RI_IOGCR_IOM2_O1

            | RI_IOGCR_IOM3_FIO

            | RI_IOGCR_IOM4_FIO;  // пин s0 в 1

  delay_charge ();

      // фаза 3 - перенос заряда на накопительный конденсатор

  RI->IOGCR = RI_IOGCR_IOM1_FIO

            | RI_IOGCR_IOM2_FIC

            | RI_IOGCR_IOM3_FIC

            | RI_IOGCR_IOM4_FIO; // пин s0 и конденсатор подключён

  RI->ASCR1 = RI_ASCR1_AS5 | RI_ASCR1_AS7;  // замкнут ключ

  delay_transfer ();

      // фаза 4 - проверим уровень напряжения на входе измерительной ножки

  reg = RI->IOIR3 & RI_IOIR3_CH18I;

  // фаза 5 - пауза, все входы высокоомные

  RI->IOGCR = RI_IOGCR_IOM1_FIO

            | RI_IOGCR_IOM2_FIO

            | RI_IOGCR_IOM3_FIO

            | RI_IOGCR_IOM4_FIO;

  RI->ASCR1 = 0;

  return reg;

}



void config_s1 (void)

{

  RI->IOSR1 = RI_IOSR1_CH16E  * 0  /*!< Channel 16 I/O switch control */

            | RI_IOSR1_CH19E  * 1;  /*!< Channel 19 I/O switch control */

  RI->IOSR2 = RI_IOSR2_CH23E  * 0; /*!< Channel 23 I/O switch control */

}

/******************************************************************************

* процедура переноса заряда на накопительный конденсатор

* Вход: нет

* Выход - уровень заряда конденсатора

******************************************************************************/

bool touch_s1 (void)

{

  bool reg;

      // фаза 1 - накачка конденсатора сенсорной панели

  RI->IOGCR = RI_IOGCR_IOM1_O1

            | RI_IOGCR_IOM2_FIO

            | RI_IOGCR_IOM3_FIO

            | RI_IOGCR_IOM4_FIO;  // пин s1 в 1

  delay_charge ();

      // фаза 3 - перенос заряда на накопительный конденсатор

  RI->IOGCR = RI_IOGCR_IOM1_FIC

            | RI_IOGCR_IOM2_FIO

            | RI_IOGCR_IOM3_FIC

            | RI_IOGCR_IOM4_FIO; // пин s1 и конденсатор подключён

  RI->ASCR1 = RI_ASCR1_AS5 | RI_ASCR1_AS6;

  delay_transfer ();

      // фаза 4 - проверим уровень напряжения на входе измерительной ножки

  reg = RI->IOIR3 & RI_IOIR3_CH18I;

  // фаза 5 - пауза, все входы высокоомные

  RI->IOGCR = RI_IOGCR_IOM1_FIO

            | RI_IOGCR_IOM2_FIO

            | RI_IOGCR_IOM3_FIO

            | RI_IOGCR_IOM4_FIO;

  RI->ASCR1 = 0;

  return reg;

}



void config_s2 (void)

{

  RI->IOSR1 = RI_IOSR1_CH16E  * 1  /*!< Channel 16 I/O switch control */

            | RI_IOSR1_CH19E  * 0;  /*!< Channel 19 I/O switch control */

  RI->IOSR2 = RI_IOSR2_CH23E  * 0; /*!< Channel 23 I/O switch control */

}

/******************************************************************************

* процедура переноса заряда на накопительный конденсатор

* Вход: нет

* Выход - уровень заряда конденсатора

******************************************************************************/

bool touch_s2 (void)

{

  bool reg;

      // фаза 1 - накачка конденсатора сенсорной панели

  RI->IOGCR = RI_IOGCR_IOM1_O1

            | RI_IOGCR_IOM2_FIO

            | RI_IOGCR_IOM3_FIO

            | RI_IOGCR_IOM4_FIO;  // пин s3 в 1

  delay_charge ();

      // фаза 3 - перенос заряда на накопительный конденсатор

  RI->IOGCR = RI_IOGCR_IOM1_FIC

            | RI_IOGCR_IOM2_FIO

            | RI_IOGCR_IOM3_FIC

            | RI_IOGCR_IOM4_FIO; // пин s3 и конденсатор подключён

  delay_transfer ();

      // фаза 4 - проверим уровень напряжения на входе измерительной ножки

  reg = RI->IOIR3 & RI_IOIR3_CH18I;

  // фаза 5 - пауза, все входы высокоомные

  RI->IOGCR = RI_IOGCR_IOM1_FIO

            | RI_IOGCR_IOM2_FIO

            | RI_IOGCR_IOM3_FIO

            | RI_IOGCR_IOM4_FIO;

  return reg;

}


А эти задержки я честно спёр у ST:


/*******************************************************************************

* Delay in NOPs to apply during charging time.

*******************************************************************************/

inline void delay_charge (void)

{

#define DELAY_CHARGE 5

 

#if DELAY_CHARGE > 0

  nop();

#endif

#if DELAY_CHARGE > 1

  nop();

#endif

#if DELAY_CHARGE > 2

  nop();

#endif

#if DELAY_CHARGE > 3

  nop();

#endif

#if DELAY_CHARGE > 4

  nop();

#endif

#if DELAY_CHARGE > 5

  nop();

#endif

#if DELAY_CHARGE > 6

  nop();

#endif

#if DELAY_CHARGE > 7

  nop();

#endif

#if DELAY_CHARGE > 8

  nop();

#endif

#if DELAY_CHARGE > 9

  nop();

#endif

#if DELAY_CHARGE > 10

  nop();

#endif

#if DELAY_CHARGE > 11

  nop();

#endif

#if DELAY_CHARGE > 12

  nop();

#endif

}



/*******************************************************************************

* Delay in NOPs to apply during transfer charge time.

*******************************************************************************/

inline void delay_transfer (void)

{

#define DELAY_TRANSFER 5

 

#if DELAY_TRANSFER > 0

  nop();

#endif

#if DELAY_TRANSFER > 1

  nop();

#endif

#if DELAY_TRANSFER > 2

  nop();

#endif

#if DELAY_TRANSFER > 3

  nop();

#endif

#if DELAY_TRANSFER > 4

  nop();

#endif

#if DELAY_TRANSFER > 5

  nop();

#endif

#if DELAY_TRANSFER > 6

  nop();

#endif

#if DELAY_TRANSFER > 7

  nop();

#endif

#if DELAY_TRANSFER > 8

  nop();

#endif

#if DELAY_TRANSFER > 9

  nop();

#endif

#if DELAY_TRANSFER > 10

  nop();

#endif

#if DELAY_TRANSFER > 11

  nop();

#endif

#if DELAY_TRANSFER > 12

  nop();

#endif

}



Особенности национальной рыбалки настройки:
Поскольку работа сенсорного датчика напрямую зависит от его конструкции, то в каждом вновь разрабатываемом устройстве потребуется его калибровка. Начинается она с выбора ёмкости накопительного конденсатора. Количество циклов заряда не должно быть больше 255, чтобы не переполнилась переменная счётчика циклов, и меньше 10-20, чтобы была хорошая разрешающая способность. Просто запускаем программу в режиме отладки и смотрим значение T.s[n].flt не притрагиваясь к датчикам. Желательна величина 50-150. Если больше – уменьшаем ёмкость накопительного конденсатора, меньше – увеличиваем.
Затем нужно откалибровать чувствительность каждого датчика. Для этого устанавливаем значение T.s[n].calib в максимальную чувствительность (примерно 10), и не спеша касаемся пальцем всех датчиков с максимальной площадью контакта. Калибровочные коэффициенты можно принять чуть меньше получившихся значений в T.s[n].calib.
Вот пока и всё. Программу интерпретации получившихся значений ёмкости можно взять из предыдущей публикации или написать самому. Программу слайдера я ещё проверяю и оптимизирую.



Комментариев нет:

Отправить комментарий