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

PIC
Давно я собирался разобраться с работой модуля измерения заряда. Ну вот момент пришел.

Сейчас рассмотрим только сенсорные кнопки, т.к. измерение времени и генерация сигналов, а уж темболее с помощью термо-нестабильных конденсаторов на мой взгляд мало полезна.

Сам модуль измерения заряда по сути представляет собой регулируемый источник тока и кучку тригеров. Всё это связано с АЦП. Общий алгоритм измерения емкости:

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 свернуть / развернуть
Не хватает блоксхемы модуля CTMU, а то непонятно, что и как работает.
+1
  • avatar
  • Vga
  • 11 января 2013, 17:10
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.