Этот двухразрядный светодиодный термометр автор изготовил в качестве подарка на день рождения сыну друга. Ему всего два года, и цифры он уже читает, а буквы — нет. Теперь он может узнавать температуру за окном самостоятельно. Датчиком в термометре служит микросхема DS18B20, работающая по протоколу 1-Wire, а микроконтроллер применён типа ATtiny84. Плата — квадратная со стороной в 25 мм, по размерам она сравнима с монетой в 50 пенсов. Автор планирует поместить плату во влагозащищённый корпус и разместить за окном. Индикация включается кратковременно раз в 24 секунды, и батарейки CR2032 хватает примерно на год.
Термометр работает в диапазоне от -19 до +99 °C. При необходимости в старшем разряде одновременно отображаются минус и единица. При выходе за пределы диапазона отображаются буквы Lo или Hi. Можно «научить» устройство отображать температуры ниже -19 °C, задействовав в качестве минуса сегмент с точкой.
По такой схеме устройство было предварительно собрано на макетке:
Задействованы все выводы микроконтроллера, использован встроенный тактовый генератор на 8 МГц. Протитип получился таким:
В прототипе применены DS18B20 в корпусе TO-92, ATtiny84 в корпусе PDIP и 3,6-дюймовый индикатор 3621AS. Затем автор разработал плату в Eagle и заказал её в PCBway. Здесь микроконтроллер уже в корпусе SOIC, датчик — в корпусе µSOP, а резисторы, конденсаторы и дисплей — типоразмера 0805. Всё, кроме дисплея, впаяно феном Youyue 858D+ при температуре в 250°C.
Как на прототипе, так и на печатной плате применены индикаторы с общим анодом. Устройство изготовлено в двух вариантах, с индикаторами красного и жёлтого цветов. Красный — на КДПВ, жёлтый — вот:
С обратной стороны впаян держатель для 20-миллиметрового литиевого элемента (любого с обозначением, начинающимся на 20, т.е., 2016, 2025 или 2032):
Прошивка написана таким образом, чтобы микроконтроллер большую часть времени находился в спящем режиме и просыпался по прерыванию от сторожевого таймера. В реализации интерфейса 1-Wire задействована эта наработка того же автора. Времязадающим является 16-битный таймер-счётчик микроконтроллера, работающий на частоте в 1 МГц:
void OneWireSetup () {
TCCR1A = 0<<WGM10; // Normal mode
TCCR1B = 0<<WGM12 | 2<<CS10; // Normal mode, divide clock by 8
}
Подпрограмма DelayMicros() обеспечивает задержку в заданное число микросекунд, опираясь на регистр сравнения выхода OCR0A:
void DelayMicros (unsigned int micro) {
TCNT1 = 0; TIFR1 = 1<<OCF1A;
OCR1A = micro;
while ((TIFR1 & 1<<OCF1A) == 0);
}
Подпрограмма DisplayTemperature() считывает значение температуры из датчика и отображает его. Поскольку датчик на шине всего один, на серийный номер можно не обращать внимание, и просто подать команду Skip ROM, после чего все последующие команды поступают на любое устройство:
void DisplayTemperature () {
cli(); // No interrupts
if (OneWireReset() != 0) {
sei();
DisplayError(0); // Device not found
} else {
OneWireWrite(SkipROM);
OneWireWrite(ConvertT);
while (OneWireRead() != 0xFF);
OneWireReset();
OneWireWrite(SkipROM);
OneWireWrite(ReadScratchpad);
OneWireReadBytes(9);
sei(); // Interrupts
if (OneWireCRC(9) == 0) {
int temp = DataWords[0];
Display((temp+8)>>4); // Round to nearest degree
} else DisplayError(1); // CRC error
}
}
В ответ на запрос датчик возвращает значение температуры в виде 16-битного целого числа со знаком в единицах, равных 1/16 градуса. Число округляется до ближайшего целого градуса и отображается вызовом подпрограммы Display().
Подпрограмма DisplayError() отображает ошибки взаимодействия микроконтроллера с датчиком по шине 1-Wire:
void DisplayError (int no) {
Buffer[0] = Error;
Buffer[1] = no;
}
E0 — датчик не обнаружен, E1 — ошибка CRC.
Данные для динамической индикации берутся из массива Buffer[]. Например, чтобы отобразить число 20, надо выполнить:
Buffer[0]=2; Buffer[1]=0;
Таймер-счётчик 0 генерирует прерывания на частоте в 125 Гц, чего достаточно для устранения мерцания. Вначале таймер сконфигурирован в setup()"
TCCR0A = 2<<WGM00; // CTC mode; count up to OCR0A
TCCR0B = 0<<WGM02 | 4<<CS00; // Divide by 256
OCR0A = 250-1; // Compare match at 125Hz
TIMSK0 = 0; // Interrupts initially off
Процедура обработки прерывания совпадения при сравнении вызывает подпрограмму DisplayNextDigit() и затем считает в обратном направлении:
ISR(TIM0_COMPA_vect) {
DisplayNextDigit();
Ticks--;
}
Подпрограмма DisplayNextDigit() считывает данные из соответствующей ячейки массива Buffer[] и включает нужные сегменты в соответствующем разряде дисплея. Программа использует #define для выбора между индикатором с общим катодом или анодом. Если при подаче питания светятся сразу все сегменты, значит, тип дисплея не соответствует заданному в прошивке. Для общего катода подпрограмму надо заменить на такую:
void DisplayNextDigit () {
PORTB = PORTB | 1<<digit; // Turn old digit off
digit = digit ^ 1; // Toggle between 0 and 1
char segs = charArray[Buffer[digit]];
PORTA = segs; // Lit segments high
PORTB = PORTB & ~(1<<digit); // Turn new digit on
}
Наконец, подпрограмма Display() вырабатывает двухзначное число для записи в массив Buffer[]:
void Display (int n) {
int units = n % 10;
int tens = n / 10;
int temp0 = tens;
int temp1 = abs(units);
if (tens < -1) {temp0 = Lo; temp1 = Lo+1; }
else if (tens > 9) {temp0 = Hi; temp1 = Hi+1; }
else if (tens == -1) temp0 = Minus1;
else if ((tens == 0) && (units >= 0)) temp0 = Blank;
else if ((tens == 0) && (units < 0)) temp0 = Minus;
Buffer[0] = temp0;
Buffer[1] = temp1;
}
В ней же учтены случаи отображения минуса вместе с единицей в старшем разряде, а также сообщений о выходе температуры за пределы диапазона.
Для максимально возможного энергосбережения отключены АЦП, тактовые генераторы интерфейса USI и АЦП, и разрешён спящий режим PWR_DOWN:
ADCSRA &= ~(1<<ADEN); // Disable ADC to save power
PRR = 1<<PRUSI | 1<<PRADC; // Turn off clocks to USI & ADC to save power
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
Основная программа отображает температуру в течение десятых долей секунды, затем включает спящий режим. Оказалось, что это минимальная продолжительность индикации, удобная для считывания. За две секунды до отображения температуры кратковременно мигает точка:
void loop () {
Buffer[0] = DP; Buffer[1] = Blank;
DisplayOn(12);
WDDelay(6); // Sleep for 1 second
Buffer[0] = Blank; Buffer[1] = DP;
DisplayOn(12);
WDDelay(6); // Sleep for 1 second
DisplayTemperature();
DisplayOn(12);
WDDelay(9); // Sleep for 8 seconds
WDDelay(9); // Sleep for 16 seconds
WDDelay(9); // Sleep for 24 seconds
}
Дисплей остаётся выключенным на 24 секунды за счёт трёх вызовов сторожевого таймера по 8 секунд каждый. При работающем индикаторе потребляемый ток составляет 6,6 мА, в спящем режиме — 4,7 мкА, средний потребляемый ток равен 1/240 * 6,6 мА. Типичная ёмкость элемента CR2032 равна 225 мАч, поэтому хватит его на (225/6.6) x 240 / 24 = 340 дней — чуть меньше года.
Температурные диапазоны компонентов следующие: микроконтроллера и индикатора — от -40 до +85°C, резисторов и конденсатора — от -55 до +125 °C, батарейки — от -20 до +70 °C. Элемент с расширенным температурным диапазоном BR2032 будет работать в диапазоне от -30 до +85 °C.
Микроконтроллер сделан Arduino-совместимым при помощи этой разработки Spence Konde. В IDE надо выбрать пункт ATtiny24/44/84 в разделе ATTinyCore меню Board. Затем надо выставить следующие опции, не обращая внимания на остальные:
Chip: "ATtiny84"
Clock: "8 MHz (internal)"
B.O.D: "B.O.D. Disabled"
Pin Mapping: "Clockwise (like damellis core)"
Программа залита при помощи приспособления Pomona test clip, размещённого поверх микроконтроллера и подключённого к программатору SparkFun Tiny AVR Programmer. Вначале надо выбрать Burn Bootloader, затем — Upload.
Ссылки: полный текст программы, плата и программа на GitHub, плата на OSHpark.