
Опрос датчика температуры по 1-wire контроллером AVR на Си с avr-libc
Недавно мне по работе нужно было реализовать на AVR опрос датчика температуры Dallas DS18B20 для управления частотным приводом насоса системы охлаждения. В интернете нашел много статей как всё это сделать на ассемблере, но по некоторым обстоятельствам мне нужно было на Си.
В начале добавим заголовочные файлы:
#include <avr/io.h> #include <util/crc16.h> #include <util/delay.h> #include <avr/interrupt.h>
После этого я обычно объявляю макросы для работы с отдельными выводами портов ввода/вывода. На моей схеме датчик был подкулючен через согласующий транзистор к разным выводам. У меня получился такой код:
#define RX() bit_is_set(PINC,0) #define TX1() PORTC &= ~_BV(1) //выходной сигнал инвертирован #define TX0() PORTC |= _BV(1)
Из datasheet на датчики температуры фирмы Dallas ясно, что все команды общения с датчиком посылаются после инициализация. Она нужна для того чтобы датчик понял, что ему сейчас будет послана команда. И написал такую функцию:
/*Сброс и ожидание начала presence pulse*/ uint8_t oneWireInit() { cli(); TX0(); _delay_us(500); TX1(); _delay_us(60); sei(); return RX() ; //если на выводе 1 - ошибка }
Далее напишем фунцию отправки массива данных. т.к. вся работа с датчиком происходит в пределах одного пространтва имен я объявил глобальный массив:
uint8_t frame[9] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
как разделяемые данные для разных функций. И так функция отправки(как аргумент функция получает байт который необходимо отправить):
/* Передача 1 байта. Передача осуществляется * младшим битом вперед */ void oneWireTans(uint8_t data) { cli(); for (uint8_t i = 0; i<8;i++) { TX0(); //импульс сброса (1-15мкс) - начало интервала передачи _delay_us(5); if (data & 0x01) { TX1(); } else { TX0(); } _delay_us(60);//пауза для считывания данных таблеткой (60-120мкс) TX1();//импульс сброса "1"(1мкс) _delay_us(10); data >>= 1; } sei(); }
Прием данных осуществляется в массив frame в качестве аргумента функция получает колличество байт, оно зависит от типа команды которую мы послали датчику:
/*Чтение length байт данных младшим байтом вперед*/ void oneWireRecv(uint8_t length) { cli(); _delay_us(60); for (uint8_t i = 0;i<length;i++) { frame[i] = 0x00; //Очистка буфера приема /*Цикл для приема очередного байта*/ for (uint8_t j = 0;j<8;j++) { frame[i] >>= 1; TX0(); //импульс сброса "0"(1-15мкс) - начало интервала приема _delay_us(10); TX1(); //импульс сброса "1" _delay_us(9); frame[i] |= RX() ? 0x80:0; _delay_us(50); } } sei(); }
В нутри каждой функции есть макросы cli() и sei() для запрета прерываний на время общения с датчиком.
Все базовые функции написаны. Теперь напишем функцию которая мопожет нам обстрагироваться от всех внутренних команд датчика и просто возвратит температуру. Я назавал функцию getT() --> get Temperature вот её содержимое:
uint16_t getT() { /*Чение адреса термо датчика*/ if (oneWireInit()) return 255; _delay_us(110); //Ожидание конца presence pulse oneWireTans(0x33); //Чтение ROM подчиненного устройства oneWireRecv(8); //Читаем ROM /*Расчет CRC*/ uint8_t crcResult = 0; for (uint8_t i = 0; i<7;i++) { crcResult = _crc_ibutton_update(crcResult,frame[i]); } if (crcResult != frame[7]) return 254; /*даем команду конвертации температуры нужному устройсту*/ if (oneWireInit()) return 253; _delay_us(110); //Ожидание конца presence pulse oneWireTans(0x55); for (uint8_t i = 0; i<8; i++) { oneWireTans(frame[i]); } oneWireTans(0x44); //Команда конвертации и записи температуры в регистр /*Чтение температуры*/ if (oneWireInit()) return 252; _delay_us(110); //Ожидание конца presence pulse oneWireTans(0x55); for (uint8_t i = 0; i<8; i++) { oneWireTans(frame[i]); } oneWireTans(0xBE); //Комманда чтения регистра с температурой oneWireRecv(9); crcResult = 0; for (uint8_t i =0; i<8;i++) { crcResult = _crc_ibutton_update(crcResult,frame[i]); } if(crcResult != frame[8]) return 251; //Store integer uint16_t digit=frame[0]>>4; digit|=(frame[1]&0xf)<<4; //Store decimal digits uint8_t decimal=frame[0]&0xf; decimal= (decimal*5)/8; /*Возвращается только цело значение*/ return ((uint16_t)digit); }
При проектировании я знал, что теперература возвращаемая датчиком не может быть больше 125С. И все значаения выше 200 я возвращал как сигнал о той или иной ошибке. Используем функцию так. Считываем значение в переменную. Если значение больше 200 значит произошла та или иная ошибка чтения. Если меньше значит все Ок и это искомая температура. Если есть предложения по улучшению кода, пишите в комментариях к статье.
Дополнительная информация: