PIC24 CTMU (Сенсорные кнопки)

Сейчас рассмотрим только сенсорные кнопки, т.к. измерение времени и генерация сигналов, а уж темболее с помощью термо-нестабильных конденсаторов на мой взгляд мало полезна.
Сам модуль измерения заряда по сути представляет собой регулируемый источник тока и кучку тригеров. Всё это связано с АЦП. Общий алгоритм измерения емкости:
1. Калибровка источника тока
2. Разряд измеряемой емкости
3. Заряд постоянным током, длительностью N тактов
4. Измерение напряжения, до которого зарядилась емкость
5. Вычисления
Математическая основа идеи это Q=C*U, Q=I*t
где Q — заряд; C — емкость; U — напряжение на емкости; I — ток; t — время.
Калибровка источника тока.
Из-за температурных эффектов, широкого диапазона рабочих напряжений ток источника тока может плавать. А так как обратной связи в нем судя по всему никакой не предусмотрено, то приходится его калибровать или просто измерять и запоминать значение(что мы и будем делать).Сам источник тока имеет три предела: 0.ХХ, Х.Х и ХХ.0 мкА. Где ХХ — число, номинальное 55. Но и это число настраивается на +-60%.
Источником тока управляет регистр CTMUICON:
биты 15-10 ITRIM<5:0>: Коррекция тока
011111 = Максимальное увеличение
011110
…
000001 = Минимальное увеличение
000000 = Номинальное значение
111111 = Минимальное уменьшение
…
100010
100001 = Максимальное уменьшение
биты 9-8 IRNG<1:0>: Выбор пределов источника
11 = 100x
10 = 10x
01 = Номинальное значение (0.55 мкА)
00 = Источник отключен
биты 7-0 не используются
Тоесть с помощью IRNG мы выбираем предел тока, а с помошью ITRIM можно корректировать значение. Стоит отметить что ITRIM это в принципе знаковое число в дополнительном коде. тоесть прибавляя 0x0400 мы будем увеличивать на 1 а прибавляя 0xFC00 вычитать 1.
Для калибровки к одному из аналоговых входов подключается резистор, такой чтобы напряжение на нем при выбранном пределе тока было 50-70% от напряжения питания. Microchip рекомендуют 4,2 МОм для 0,55мкА и 42 кОм для 55мкА при питании 3.3В
Настройки CTMU
Все настройки собраны в регистре CTMUCON:
бит 15 CTMUEN: Разрешение работы CTMU
1 = Включен
0 = Выключен
бит 14 не используется
бит 13 CTMUSIDL: Работа в Idle:
1 = Останавливать работу модуля
0 = Продолжать работу
бит 12 TGEN: Разрешение генерации задержек
1 = Разрешено
0 = Запрещено
Используется чтобы с помошью конденсатора и компаратора сформировать импульс, длинной, зависящей от емкости, тока заряда и уровня на другом входе компаратора.
бит 11 EDGEN: Разрешить прием инпульсов с выводов
1 = Разрешить
0 = Запретить
Запретить если надо управлять изнутри, разрешить если надо измерять какие-то времнные значения.
бит 10 EDGSEQEN: Последовательность фронтов
1 = Необходимо чтобы EDGE1 пришло раньше чем EDGE2
0 = Последовательность фронтов не важна
бит 9 IDISSEN: Источник тока подключен:
1 = к земле(отключен)
0 = к выводу(но нужно еще включить его)
бит 8 CTTRIG: Триггер запуска АЦП
1 = включен, АЦП запускается сам, по приходу второго фронта
0 = триггер отключен, ацп надо запускать вручную
бит 7 EDG2POL: полярность фронта EDGE2
1 = Положительный фронт(0 — 1)
0 = Отрицательный (1 — 0)
биты 6-5 EDG2SEL<1:0>: Источник импульсов EDGE2
11 = вход CTED1
10 = вход CTED2
01 = выход блока компараторов OC1
00 = от Таймера1
бит 4 EDG1POL: полярность фронта Edge1
1 = Положительный фронт(0 — 1)
0 = Отрицательный (1 — 0)
биты 3-2 EDG1SEL<1:0>: Источник импульсов EDGE1
11 = вход CTED1
10 = вход CTED2
01 = выход блока компараторов OC1
00 = от Таймера1
бит 1 EDG2STAT: статус Edge2. Фронт
1 = Edge 2 пришел
0 = Edge 2 нет
бит 0 EDG1STAT: статус Edge1. Фронт
1 = Edge 1 пришел
0 = Edge 1 нет
А теперь подробно: что делать для измерения:
допустим у нас все включено так что никакой автоматики. все биты ставим вручную. тогда:
Заземляем источник тока
CTMUCONbits.IDISSEN=1;
Начинаем измерение на АЦП(захват)
AD1CON1bits.SAMP = 1;
Запускаем преобразование
CTMUCONbits.EDG1STAT = 1;
ждем некоторое время
отключаем источник тока от земли
CTMUCONbits.IDISSEN=1;
ждем определенное время. это время надо знать для пересчета емкости
начинаем преобразование на АЦП
AD1CON1bits.SAMP = 0;
ждем завершения преобразования на АЦП
while(!AD1CON1bits.DONE);
(не забываем предварительно сбросить этот бит)завершаем измерение
CTMUCONbits.EDG1STAT = 0;
Если соответствующим образом настроить АЦП и CTMU то нам потребуется только управлять только заземлением источника и стартовым флагом. Для этого нам надо: выбрать для CTMU в качестве источника флагов для AЦП(в ADC1CON1 биты 7 и 6 установить, бит 5 сбросить), и разрешить генерацию флагов установкой бита CTTRIG в CTMUCON.
Пример
А вот и пример. Все просто: один таймер срабатывает относительно редко и измеряет емкости на всех каналах. Другой часто, генерит ШИМ.
Когда делал плату для эксперимента сфейлил. Кнопки и потенциометр надо было устроить иначе. Все должно быть максимально симметрично, кнопки должны состоять из 2х пластин(чтобы недалеко была земля) а потерциометр должен быть из 2х треугольных площадок(типа как на плате) с землей между ними. И считая разность между показаниями находить положение. У меня же 1 треугольник — земля. что не правильно А надо так:

Пруф
В процедуре калибровки использовалось ручное управление, при замере емкости — автоматическое.
#include "p24FJ64GB002.h"
_CONFIG4(0xFF1F)
_CONFIG3(0xFCFF)
_CONFIG2(0x9FDF)
_CONFIG1(0x377F)
#define COUNT 5
#define ETIME 0.0000625*COUNT //время заряда
#define ADSCALE 1023
#define ADREF 3.25 //напряжение питания
#define RCAL 12000.0 //сопротивления калибровочного резистора. больше с точностью менее процента не нашел
#define RCALPin PORTBbits.RB2 //AN4, калибровочный резистор
#define POTlPin PORTAbits.RA1 //AN1, левый потенциометр
#define POTrPin PORTAbits.RA0 //AN0, правый потенциометр
#define BUTlPin PORTBbits.RB15 //AN9, левая кнопка
#define BUTrPin PORTBbits.RB14 //AN10, правая кнопка
//соответствующие предыдущим выводам номера аналоговых каналов
#define RCALAn 4
#define POTlAn 1
#define POTrAn 0
#define BUTlAn 9
#define BUTrAn 10
//выводы со светодиодами
#define LED1 LATBbits.LATB8
#define LED2 LATBbits.LATB7
#pragma udata
int i,j; //переменные для разных циклов
float Icap; //Значение номинальное значение тока при множителе 100.
float butl0,butr0,potl0,potr0; //начальные емкости, т.е. не нажатые
float butl,butr,potl,potr; //текущие значения
float Kpress; //если емкость возросла во столько раз считаем кнопку нажатой
unsigned char pos,ll,lr; //переменные для ШИМа
float capmeasure(unsigned char pin); //определение функции измерения емкости на аналоговом входе.
#pragma code
//самодельная функция преобразования float в unsigned char, т.к. в библиотеках не нашел.
unsigned char ftuc(float p)
{
union {
float Val;
unsigned char byte[4];
} f;
unsigned char exponent, mantiss;
unsigned char result;
result=0;
if (p>=1) {
f.Val=p;
exponent=(f.byte[3]<<1)|((f.byte[2]>>7));
mantiss=f.byte[2]|0x80;
exponent=exponent-127;
if (exponent<7) {result=mantiss>>(7-exponent);} else
if (exponent>7) {result=mantiss<<(exponent-7);} else
{result=mantiss;}
if (p>=255.0) {result=255;}
}
return result;
}
//в этом прерывании измеряются емкости на выводах
void _ISR _T2Interrupt(void)
{
_T2IF=0;
potl=capmeasure(POTlAn);
butl=capmeasure(BUTlAn);
potr=capmeasure(POTrAn);
butr=capmeasure(BUTrAn);
lr=ftuc(100*(potr/potr0-1)); //коэфициент 100 подобран экспериментально, чтобы было заметно влияние емкости на яркость светодиода
ll=ftuc(100*(potl/potl0-1));
if (butl/butl0>Kpress) {ll=255;}
if (butr/butr0>Kpress) {lr=255;}
}
//шим
void __attribute__( (interrupt,no_auto_psv) ) _T1Interrupt(void)
{
_T1IF=0;
TMR1=0xFF00;
pos++;
if (ll<=pos)
{LED2=0;} else
{LED2=1;}
if (lr<=pos)
{LED1=0;} else
{LED1=1;}
}
void initm()
{
//настраиваем порты ввода-вывода
TRISA=0x0003;
LATA=0x0000;
TRISB=0xC004;
LATB=0x0000;
//включаем таймер 1 и разрешаем прерывания
T1CON=0x8000;
_T1IF=0;
_T1IP=5;
//включаем таймер2 с делителем на 8
T2CON=0x8010;
_T2IP=4;
_T2IF=0;
//для таймеров установлены приоритеты, у таймера 1 он выше, поэтому он может прерывать выполнение прерывания таймера 2
Kpress=2.0; //если отношение текущей емкости к начальной превышает это число - кнопка нажата
}
//калибровка источника тока
void callibrate()
{
AD1PCFG=(0xFFFF)^(0x0001<<RCALAn); //настраиваем вход как аналовый
AD1CON3=0x8F0F; //настроили тактирование АЦП
AD1CON1=0x8000; //включили АЦП
AD1CHS=RCALAn; //выбрали для АЦП канал с резистором
CTMUICON=0x0300;//55мкА
//CTMU_NOMINAL_CURRENT+CTMU_CURR_RANGE_100_BASE_CURR
CTMUCON=0x8090; //EDGE1 и EDGE2 полярность: фронт
//CTMU_ENABLE+CTMU_EDGE1_POLARITY_POS+CTMU_EDGE2_POLARITY_POS
Vread=0;
for (i=0; i<10;i++) {
AD1CON1bits.DONE = 0;
CTMUCONbits.IDISSEN=1; //заземлили источник тока
CTMUCONbits.EDG1STAT = 1; //если не установить этот бит то вход АЦП не будет подключен к CTMU
TMR3=0;
_T3IF=0;
T3CON=0xFF00;
while (_T3IF==0); //задержка чтобы завершились всякие переходные процессы разряда паразитных емкостей
CTMUCONbits.IDISSEN=0;
AD1CON1bits.SAMP = 1; //начинаем заряд емкости АЦП
TMR3=0;
_T3IF=0;
T3CON=0xFF00;
while (_T3IF==0); //задержка чтобы завершились всякие переходные процессы заряда паразитных емкостей
AD1CON1bits.SAMP = 0; //начинаем преобразование
while(!AD1CON1bits.DONE);
CTMUCONbits.EDG1STAT = 0;
Vread = Vread+ ADC1BUF0;
}
Icap=(float) Vread*(float)ADREF/(10*RCAL*ADSCALE);
}
float capmeasure(unsigned char pin)
{
int Vread;
AD1PCFG=(0xFFFF)^(0x0001<<pin); //настраиваем вывод как аналоговый вход
AD1CON3=0x8808; //настройка тактирования
AD1CON1=0x80C4; //включили АЦП, выбрали источник запускающих флагов
AD1CHS=0x0000+pin; //выбрали аналоговый вход
CTMUICON=0x0200; //5,5мкА
//CTMU_NOMINAL_CURRENT+CTMU_CURR_RANGE_10_BASE_CURR
CTMUCON=0x8190; //разрешили генерацию флагов для АЦП, включили СTMU
//CTMU_ENABLE+CTMU_TRIG_OUTPUT_ENABLE+CTMU_EDGE1_POLARITY_POS+CTMU_EDGE2_POLARITY_POS
do {
ADC1BUF0=0;
AD1CON1bits.DONE = 0;
CTMUCONbits.IDISSEN=1; //заземляем вход чтобы разрядить емкость
CTMUCONbits.EDG1STAT = 1; //подключаем CTMU к выводу
TMR3=0xFF00;
_T3IF=0;
T3CON=0x8000;
while (_T3IF==0); //ждем некоторое время пока разрядится емкость
_T3IF=0;
DISICNT=COUNT+5; //устанавливаем запрет прерываний на COUNT+5 инструкций
_DISI=1;
TMR3=0xFFFF-(COUNT+1); //настраиваем таймер, чтобы переполнение произошло через COUNT инструкций после отключения источника от земли
CTMUCONbits.IDISSEN=0; //отключаем источник тока от земли. старт заряда
while (_T3IF==0); //ждем таймер
CTMUCONbits.EDG1STAT = 0; //завершаем преобразование
while(!AD1CON1bits.DONE); //ждем пока завершится преобразование на АЦП
} while (ADC1BUF0==0);
//эту проверку пришлось ввести т.к. иногда по непонятным причинам измереное значение оказывается равным 0. отловить причину не удалось
//в идеале эти значения надо фильтровать, т.к. разные наводки так же влияют на напряжение
return (1*ETIME*Icap/10)/(float)ADC1BUF0; //пересчитываем напряжение в емкость
}
int main(void)
{
initm(); //инициализируемся
callibrate(); //измеряем ток
potl0=capmeasure(POTlAn); //измеряем начальные значения
butl0=capmeasure(BUTlAn);
potr0=capmeasure(POTrAn);
butr0=capmeasure(BUTrAn);
//включаем прерывания от таймеров
_T2IE=1;
_T1IE=1;
while (1);
}
Так же модуль CTMU можно использовать для измерения температуры способом, который использовал dcoder. В AN01375 описано что можно делать на CTMU, а тут конкретно по измерению температуры. Приведены даже экспериментальные зависимости напряжения от температуры.
А вот работа примера:
Да, как раз слабая чувствительность и плохая линейнось из-за не правильной конфигурации кнопок.
ЗЫ статья уже кучу времени валяется в черновиках, хотел что-то доделать, но что уже и не помню.
- +2
- 11 января 2013, 12:07
- kest
Комментарии (1)
RSS свернуть / развернуть