ds18b20 и сс2530 - игнорирует команду конвертации температуры

Ответить
dtvims
Site Admin
Сообщения: 147
Зарегистрирован: Пн авг 02, 2010 2:43 pm

ds18b20 и сс2530 - игнорирует команду конвертации температуры

Сообщение dtvims »

Во многих проектах zigbee добавляют датчики температуры, надо или нет. Иногда это даже полезно. Вот и я хотел добавить популярный ds18b20.
Для сс2530 примеры скудные, по сути одна мелкая библиотека, кривая и не доделанная и все ее слегка под себя модифицируют, но лучше она не становится. По крайней мере чего-то дельного и рабочего я не нашел. Вернее не так: нашел, но работают они плохо.
Приехали ко мне пачка датчиков, я радостно подключил, а показание: 1. По коду 1 - это ошибка инициализации. Путем каких-то манипуляций я сумел получить иные значения, вроде 8500 (это тоже ошибка, что температура еще не сконвертирована). Чувствую, что что-то не так, да и библиотеки странные (не похожие на конечный работающий продукт).
Подключаю датчик к Ардуино и все работает, с первого раза, на любых библиотеках и скетчах. Вот прямо все работает!
Подключаю обратно к сс2530, получаю показание температуры, но всегда одно. Показания температуры не меняются!
Подключил анализатор сигналов/осциллограф. Питание у сс2530 какое-то не такое чистое как у Ардуино. Сигнал одинаковый, но тайминги другие. Полез в библиотеку, подрегулировал тайминги, чтобы было похоже на то что в ардуино, но это ничего не изменило. Проверял сигнал, конечно анализатором, который распознавал 1-wire корректно, как отправку команд, так и получение ответа.
Также я провел ряд тестов:
1. Если сперва отправить команду изменения разрядности датчика, то после этого датчик выдает новое значение корректно.
2. Если, после команды конвертации слишком рано запросить температуру, то будет ошибка, что она еще не сконвертирована.
3. Тайминги, уровень напряжения, фильтрация питания - погоды не делают, т.е. на работу ни как не влияют.

Вообще, тесты показывают, что должно работать, но именно на сс2530 не работает как надо.
Интересно, что в одной из версии библиотек, для сс2530, в функции чтения датчика сперва была команда на изменение разрядности ВСЕГДА! Так у меня и появился этот пункт выше.

ПОДДЕЛКИ!!!
Ну и конечно я нашел много статей, про поддельные чипы. Тут надо по внимательнее.
https://github.com/cpetrich/counterfeit_DS18B20/blob/master/discover-classify_fake_DS18B20.ino

Такой скетч для Ардуино был предложен одним энтузиастом (Спасибо тебе, кто бы ты ни был!!!), в котором проводятся все тесты на предмет определения подделок.
Не трудно догадаться, что рынок просто кишит подделками. Видимо покупка DALLAS компанией Maxim была положительной с точки зрения распространения именно оригинальных чипов и они еще часто попадались даже в китайских магазинах. А вот покупка Analog Device компании Maxim, уже напротив вывела на рынок подделки, т.к. стоимость свежих чипов от AD выше в несколько раз, чем Maxim. Может там есть отличия в качестве, но по описанию отличий не видно.
Запускаю скетч проверки и у меня отклонения от оригинала всего 2 - это первая проверка ROM не пройдена и при установке разрешения 12bit конвертирование происходит за 120микросекунд, хотя оригинал это должен делать за 750 (в максимуме).
Я также купил чип от Maxim, который прошел все проверки, у него время конвертирования 12bit составило 530мкс, что скетч уже не смутило.
С учетом всего этого, моя подделка весьма качественная получается, наверное. Она проходит почти все тесты.
На этом этапе я не пробовал оригинальную микросхему проверить на сс2530 с предложенными библиотеками для ds18b20, но начал писать свою.
Ну как свою... Основой общения с датчиком является протокол 1-wire. Сперва мне показалось, что на Ардуино он аппаратный, но нет, на мк ATmel ничем таким и не пахнет. Библиотека OneWire вполне себе программная. В основе ее лежит функция delay_us(). Вот с нее я и начал.
Берем Анализатор сигнала, начинаем моргать ножкой с задержкой функцией delay_us и смотрим, какие времена имеем в реальности, стараясь добиться максимального попадания.

Есть вот такой вариант:

Код: Выделить всё

static void _delay_us(uint16 microSecs) {
    MicroWait(microSecs);
}
Тут рука лицо по точности, далее объясню.

Код: Выделить всё

// Wait for specified microseconds
#define MicroWait(t) Onboard_wait(t)   
...
void Onboard_wait( uint16 timeout )
{
  while (timeout--)
  {
    asm("NOP");
    asm("NOP");
    asm("NOP");
  }
}
Ну вот тут мы приходим к более классическому варианту, с которого я и начал доработку.
В других вариантах было гораздо больше вызовов "asm("NOP");", от 5 до 8 - это из того, что мне встречались. Когда я начал свои тесты, то у меня их получилось штук 20, не без нюансов.
В тесте я проверял задержки разностью в 5мкс, от 5 до 50 и много раз подряд. Мне важно было получить и 5мкс и 50мкс, достаточно точно и чтобы была повторяемость. С повторяемостью тут конечно сложно совсем, но по наблюдениям компилятор что-то оптимизирует и совсем не в пользу точности. Когда повторяемости не было, не было похоже на вмешательство из вне, вроде прерываний, т.к. тогда неточность имела бы почти случайный характер, а тут разброс в показаниях был слишком стабильный.
5мкс не получался совсем, минимально на что-то рассчитывать можно было только на 8мкс при такой реализации. Это объясняется процедурой вызова функции, плюс мы используем тип данных 16 бит на 8-ми битном мк.
По моим прикидкам, вариант:

Код: Выделить всё

void delay_us( uint16 timeout )
{
  timeout-=4;
  while (timeout--)
  {
    asm("NOP");
    asm("NOP");
    asm("NOP");
	...
  }
}
дал уже более менее стабильный результат на диапазоне 5-50мкс. Как недостаток видно, что минимальное время задержки теперь 4мкс - это время входа и выхода в функцию. Именно это я и имел ввиду выше, при вызове одной из другой.
Далее подгонка до точности в 1мкс. А на 50мкс у меня уже или перебор на 2.5мкс или недобор в 2.5 мкс при различии всего в одну команду NOP. Половинку команды сделать нельзя - это уже минимальная единица. Делать доп команду каждые 10 мкс сработало бы, но проверка условия на каждую 10-ю команду - это тоже нагрузка, причем слишком большая, чтобы не нарушить вообще всё.
Причем, если задержка чуть меньше работает стабильнее, чем задержка чуть больше. Если сделать вариант чуть с большей задержкой, то чаше появляются расхождения, т.е. там где должно быть 10, может оказаться задержка 20, и так произвольно и часто, а разница всего в одной команде NOP. Не знаю с чем может быть связано такое поведение.

Уже вариант костыля "timeout-=4;" говорил сразу, что надо использовать макрос. Пишем все тоже самое, только вида:

Код: Выделить всё

#define DELAY_MS(t) for(uint16 i = 0; i < t; i++){asm("NOP");asm("NOP");asm("NOP");} 
Вот тут уже лучше. Костыль не требуется точность на 50мкс возросла до 1.5мкс, причем, если в меньшую сторону даже выше, а в большую сторону (на одну команду NOP больше), снова появляются посторонние вмешательства.
В общем остановился на варианте меньшей длительности, но большей точности. Тут всегда будут доп вмешательства, которые только увеличат задержки, а значит они скорее исправят ситуацию, чем сделают еще хуже.

2-ой ЭТАП!
Пишем функцию "uint8 ds18b20_send_bit(void)".
Не обращаем внимание на название, она всегда одна, название чуть отличается, тут главное суть, что отправляем по одному БИТУ, а не байту. Такая реализация везде.

Код: Выделить всё

// Sends one bit to bus
static void ds18b20_send(uint8 bit) {
    TSENS_SBIT = 1;
    TSENS_DIR |= TSENS_BV; // output
    TSENS_SBIT = 0;
    if (bit != 0)
        _delay_us(8);
    else
        _delay_us(80);
    TSENS_SBIT = 1;
    if (bit != 0)
        _delay_us(80);
    else
        _delay_us(2);
}
Странные тайминги, хотя анализатор распознает протокол 1-wire, а вот чип нет. Вернее чип все распознает, но работает как-то не так, а надо сделать чтобы было так.
Рабочей является библиотека OneWire для Arduino, вот на нее и будем оглядываться.

Код: Выделить всё

void CRIT_TIMING OneWire::write_bit(uint8_t v)
{
	IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
	__attribute__((unused)) volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;

	if (v & 1) {
		noInterrupts();
		DIRECT_WRITE_LOW(reg, mask);
		DIRECT_MODE_OUTPUT(reg, mask);	// drive output low
		delayMicroseconds(10);
		DIRECT_WRITE_HIGH(reg, mask);	// drive output high
		interrupts();
		delayMicroseconds(55);
	} else {
		noInterrupts();
		DIRECT_WRITE_LOW(reg, mask);
		DIRECT_MODE_OUTPUT(reg, mask);	// drive output low
		delayMicroseconds(65);
		DIRECT_WRITE_HIGH(reg, mask);	// drive output high
		interrupts();
		delayMicroseconds(5);
	}
}
Смотрите и тайминги другие, да и подход другой. отдельно отправляем бит 1 и отдельно 0, а в либе для сс2530 есть оптимизация. Данная оптимизация дает лишние такты, а значит увеличивает задержки - убираем ее и делаем как у Ардуино.

Код: Выделить всё

static void ds18b20_send_bit(uint8 bit) {
    TSENS_SBIT = 1;
    TSENS_DIR |= TSENS_BV; // output
    if (bit != 0){
		TSENS_SBIT = 0;
        DELAY_MS(10);
		TSENS_SBIT = 1;
		DELAY_MS(55);
    }else{
		TSENS_SBIT = 0;
        DELAY_MS(65);
		TSENS_SBIT = 1;
		DELAY_MS(5);
    }
}
функция не усложнилась, а при сравнении с оригинальной даже проще, т.к. пока не отключаем прерывания (Спойлер: и не будем).

Есть еще одна важная функция reset() для 1-wire.
Было:

Код: Выделить всё

// Sends reset pulse
static uint8 ds18b20_Reset(void) {
    TSENS_SBIT = 0;
    TSENS_DIR |= TSENS_BV; // output
    _delay_us(600);
    TSENS_DIR &= ~TSENS_BV; // input
    _delay_us(70);
    uint8 i = TSENS_SBIT;
    _delay_us(200);
    TSENS_SBIT = 1;
    TSENS_DIR |= TSENS_BV; // output
    _delay_us(600);
    return i;
}
Стало:

Код: Выделить всё

// Sends reset pulse
static uint8 ds18b20_Reset(void) {
	TSENS_DIR |= TSENS_BV; // output
    TSENS_SBIT = 0;    
    DELAY_MS(480);
    TSENS_DIR &= ~TSENS_BV; // input
    DELAY_MS(70);
    uint8 i = TSENS_SBIT;
    DELAY_MS(410);
    return i;
}
В оригинальной библиотеке от ардуино есть еще функционал: Молчать всем, тут я пришел! Ну или что-то в этом роде. Согласно протоколу у нас один ведущий и он делает reset, предположим, что этого достаточно, ну хотя бы пока.
Теперь пробуем функцию readTemperature() из библиотеки для сс2530, но с новыми функциями.
УРА!!! Заработало! Весьма странно, конечно, но факт. Я сравнивал анализатором и сигналы от Ардуино и сс2530 были одинаковыми, но на сс2530 все время выдавалось старое значение, а как поменял функции на то как сделано в Ардуино, данные стали обновляться по запросу. Есть вероятность, что я рано радуюсь и что-то просто не углядел, но предварительно все заработало как надо.

В одной из статей про подделки/клоны было сказано, что чипу при команде на конвертацию не хватает питания, т.к. в этот момент он начинает потреблять существенно больше тока, в то время как оригинал очень экономичен. Токи я не измерял, потому верность этой версии не могу подтвердить применительно к моему клону. Согласно этой версии, измеренная температура сохраняется во внутреннюю память, чип перезагружается и выдает значение из памяти, что объясняет именно старые показания. С учетом, что память не вечна, то время жизни такого чипа будет коротким, если каждый раз все записывать в память. Функция для установки разрешения датчика по сути делает ему принудительный сброс и поэтому происходит принудительное измерение температуры и датчик выдает свежее значение. Судя по одному из вариантов либы для сс2530, кто-то уже с этим сталкивался, но решать проблему глобально не стал, просто вставил в функцию readTemperature первым вызовом установку разрешающей способности, что решило проблему и на этом все.
Удивительно как для сс2530 все проблемы решаются костылями. И в очередной раз восхищаюсь проектом Ардуино, для которого базовые классы, хоть и громоздкие, но отлажены до стабильной работы. Мне кажется Ардуино не заслуженно принижают, что это для новичков и неумех. Довольно проработанная среда. Причем почему-то модные движки для той же java так не критикуются, хотя они очень жирные и делают всю работу за разработчика, работа которого сводится только верхнеуровневому описанию задачи.

Буду далее работать в этом направлении. Необходимо доделать библиотеку, чтобы она умела в нормальную работу с датчиками, плюс надо добавить те возможности, которые используются в скетче для проверки оригинальности, т.к. там есть неплохие методы. Я, наверное, их использовать не буду, но тут уже вопрос принципа, чтобы что-то доделать до хоть немного полного результата, который в последствии приложу.
dtvims
Site Admin
Сообщения: 147
Зарегистрирован: Пн авг 02, 2010 2:43 pm

версия библиотеки

Сообщение dtvims »

Во вложении что получилось. Предварительный вариант с тестовым примером функции readTemperature().
Функция использует функцию поиска датчика и если находит его, то обращается к нему по адресу, отправляя команду на конвертацию. Выжидает пока датчик выдаст статус готовности и делает запрос температуры.
За основу взят код из библиотеки OneWire для Arduino с минимальными изменениями до 8051.

Подразумевается, что в проекте заданы константы, например:

Код: Выделить всё

#define DS18B20_PORT 0
#define TSENS_SBIT P0_7
#define TSENS_BV BV(7)
#define TSENS_DIR P0DIR
Методы LREP* как и библиотеку debug можно удалить - оставлены для отладки.
Вложения
ds18b20.zip
(3.15 КБ) 3 скачивания
Ответить