ATtiny13A: режимы энергосбережения, фьюзы и программный UART

разделы: AVR , ATtiny13A , дата: 10 апреля 2016г.

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

Первым делом мой взгляд упал на ATtiny13A. Небольшая микросхема, если логически подумать, должна потреблять минимум энергии. На практике однако, это зависит от используемого техпроцесса при изготовлении этой микросхемы.

Справедливости ради стоит заметить, что ATtiny13 с пониженным энергопотреблением имеют индекс V. А чипы с технологией picoPower, снабжаются индексом P. Что же до ATtiny13A, согласно документации, его потребление в обычном режиме, при питании 1.8V и частоте 1МГц - 160 микро ампер. В Idle режиме при тех же условиях - 24 микро ампер. Питание 1.8В я не собираюсь использовать, меня больше интересует потребление при 3.3В.

Мой мультиметр фирмы Mastech, модель: M830BZ. Это не весть что, но если измерять потребление различных чипов одним прибором, выводы сделать можно будет.

Для работы с режимами энергосбережения в avr-gcc есть библиотека avr/sleep.h. Переводной мануал по данной библиотеке можно почитать здесь. Я же пойду по традиционному пути ковыряния регистров.

В качестве исходного примера возьмем blink из старого поста:

// blink.c for AVR ATtiny 13/25/45/85

#include <avr/io.h>
#define F_CPU 1000000UL // частота резонатора 1МГц
#include <util/delay.h>
int main(void) {
 // макрос _BV(число) заменяет конструкцию (1 << число)
 DDRB |= _BV(PB0); // аналог pinMode(PB0,OUTPUT); в Wiring
 for (;;) {
  PORTB ^= _BV(PB0); // инвертируем состояние порта PB0
  _delay_ms(1000); // ждем 1 секунду
 }
 return 0;
}

Фьюзы пусть будут установлены по умолчанию: hfuse = 0xFF; lfuse = 0x6A; Батарея, CR2032, у меня не первой свежести выдает 3.05В без нагрузки. Чип тактируется от внутреннего генератора частотой 1MHz

Итого потребление в активном режиме: 525 мкА

Заглянув на 30-ю страницу официального руководства, раздел семь: "Power Management and Sleep Modes" можно найти описание регистра: " Power Reduction Register" который позволяет отключать часть периферии:

т.е.

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
	PRR=(1<<PRTIM0)|(1<<PRADC);

	DDRB |= (1<<PB0);
	for (;;) {
  		PORTB ^= (1<<PB0);
  		_delay_ms(3000);
 	}
 	return 0;
}

Ok энергопотребление снизилось до 512 мА. Хм, как-то не впечатляет.

Теперь попробуем отпустить микроконтроллер в Idle режим, когда CPU и флеш память засыпают, а прерывания продолжают работать.

Режимы энергосбережения управляются через регистр MCUCR:

Порядок работы такой: сначала задается режим энергосбережения, и только потом он включается установкой флага SE. Включение энергосбережения происходит ассемблерной командой sleep. С учетом всего вышесказанного, перепишем blink на таймере:

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t i;
ISR(TIM0_OVF_vect)
{
	if (!(++i%16)) //period 4s
		PORTB ^= (1<<PB0);
}

int main(void) {


//	PRR = (1<<PRTIM0) | (1<<PRADC); // shut down Timer0 & ADC
	PRR = (1<<PRADC); // shut down ADC

        TIMSK0 = (1<<TOIE0);  // timer0 overflow interrupt enable
        TCCR0B = (1<<CS02) | (1<<CS00); // prescaler 1/1024

	DDRB |= (1<<PB0);
	i=0;

//	set_sleep_mode (SLEEP_MODE_IDLE);
//	sleep_enable();
	MCUCR &= ~(1<<SM1); // idle mode
	MCUCR &= ~(1<<SM0); // idle mode
	MCUCR |= (1<<SE);

	sei();

 	while(1) {
//		sleep_cpu();
		asm("sleep");
	}

 	return 0;
}

Энергопотребление упало до 180 мкА, недурно! А теперь попробуем взглянуть на максимум того, на что можно расчитывать, и пошлем микроконтроллер в power-down режим:

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t i;
ISR(TIM0_OVF_vect)
{
	if (!(++i%16)) //period 4s
		PORTB ^= (1<<PB0);
}

int main(void) {


//	PRR = (1<<PRTIM0) | (1<<PRADC); // shut down Timer0 & ADC
	PRR = (1<<PRADC); // shut down ADC

        TIMSK0 = (1<<TOIE0);  // timer0 overflow interrupt enable
        TCCR0B = (1<<CS02) | (1<<CS00); // prescaler 1/1024

	DDRB |= (1<<PB0);
	i=0;

//	set_sleep_mode (SLEEP_MODE_PWR_DOWN);
//	sleep_enable();
	MCUCR |= (1<<SM1); // power-down mode
	MCUCR &= ~(1<<SM0); // power-down mode
	MCUCR |= (1<<SE);

	sei();

 	while(1) {
//		sleep_cpu();
		asm("sleep");
	}

 	return 0;
}

Иииииии.... питание упало до запредельных 0.1 мкА или ~100 нА. Здорово! Но и наш светодиод перестал мигать. Так зачем же такой режим если из него нельзя ничего делать?

На самом деле, делать из него можно много чего. Но сперва призываю обратить внимание на фьюзы. Они описаны в разделе 17.2 официального руководства.

Здесь ноль означает, что соответствующий бит установлен, единица - сброшен. т.е. если старший байт, hfuse, имеет значение 0хFF - это означает, что все его флаги сброшены.

Вообще, я изначально думал, что через фьюз биты устанавливается частота тактирования от внутреннего генератора. Отчасти это так, но не все так просто. Через фьюзы можно сменить частоту внутреннего генератора:

Однако делитель для этого генератора задается в регистре CLKPR:

Интересен этот параграф:

"The CKDIV8 Fuse determines the initial value of the CLKPS bits. If CKDIV8 is unprogrammed, the CLKPS bits will be reset to “0000”. If CKDIV8 is programmed, CLKPS bits are reset to “0011”, giving a division factor of eight at start up."

Т.е. если фьюз CKDIV8 установлен, тогда в регистру CLKPR при старте присваивается значение 0011 т.е. делитель восемь.

Теперь смотрим дефолтное значение младшего фьюз байта:
0х6А = 0110 1010

Установлены биты: SPIEN(мой любимый способ залочивания чипов), CLDIV8, SUT0, CKSEL0

Из этого видим, что внутренний генератор работает на частоте 9.6МГц с делителем равным восьми, т.е. примерно 1МГц.

Интересный способ залочивания чипов заключается в изменении фьюз битов CKSEL1/CKSEL0:

Можно тактировать микроконтроллер от низкочастотного таймера watchdog'а что должно дать на первый взгляд(время выполнения кода увеличится пропорционально) существенную экономию знергопотребления. Однако после этого, перед тем как прошить микроконтроллер еще раз, придется пройти квест: "найди на программаторе джампер slowSCL".

Ok, с тактированием разобрались, возвращаемся к энергопотреблению. Для батарейного устройства вроде бы неплохо будет монитор питания BOD. Он настраивается через фьюз биты BODLEVEL:

Для трехвольтового источника питания соответствует вторая строчка, т.е. hfuse будет равен: 11111011 = 0xFB

$ avrdude -p t13 -c usbasp -U hfuse:w:0xfb:m

avrdude: warning: cannot set sck period. please check for usbasp firmware update.
avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e9007
avrdude: reading input file "0xfb"
avrdude: writing hfuse (1 bytes):

Writing | ################################################## | 100% 0.01s

avrdude: 1 bytes of hfuse written
avrdude: verifying hfuse memory against 0xfb:
avrdude: load data hfuse data from input file 0xfb:
avrdude: input file 0xfb contains 1 bytes
avrdude: reading on-chip hfuse data:

Reading | ################################################## | 100% 0.00s

avrdude: verifying ...
avrdude: 1 bytes of hfuse verified

avrdude: safemode: Fuses OK (E:FF, H:FB, L:6A)

avrdude done.  Thank you.

Но если сейчас замерить энергопотребление обнаружится неприятное: потребление в power-down режиме выросло с 0.1 мкА до 104мкА.

Программно отключить BOD можно через BODCR регистр:

Я же лучше сброшу фьюз на исходное значение.

В заключение хочу уберечь от попыток изменить RSTDISBL и SPIEN фьюзы. Они отключат возможность работы программатора.

После того как замерили потребление BOD, заодно разобравшись с фьюзами, осталось разобраться с тем как заставить микроконтроллер работать в power-down режиме. Ответ кроется в использовании watchdog'а.

Про эту штуку часто можно услышать из сообщений NASA когда бортовой компьютер КА в очередной раз сломали инопланетяне завис и он был перезапущен watchdog'ом. На ихнем сайте по ключевому слову watchdog timer, вообще много чего интересного выпадает.

Суть в том, что watchdog это отдельный энергоэффективный таймер, и пока основная программа работает, она этот таймер периодически сбрасывает. Если же главный рабочий цикл подвис, то watchdog дождавшись переполнения счетчика просто сделает то для чего предназначен - софтовый reset.

Причем в AVR есть регистр MCUSR по флагу WDRF которого можно определить, что микроконтроллер был сброшен именно watchdog'ом а не BOD - монитором наример:

Хороший пример программы на прерывании WDT можно найти здесь: AVR - Power management или как правильно спать Пример настолько замечательный что его следует сразу скомпилировать и прошить в микроконтроллер:

#include <avr/io.h>
#include <avr/wdt.h> // здесь организована работа с ватчдогом
#include <avr/sleep.h> // здесь описаны режимы сна
#include <avr/interrupt.h> // работа с прерываниями

volatile uint8_t i;
ISR (WDT_vect) {
 if ((++i%4) == 0)
         PORTB |= (1<<PB0); // включаем светодиод
 else
         PORTB &= ~(1<<PB0); // выключаем светодиод

         WDTCR |= (1<<WDTIE); // разрешаем прерывания по ватчдогу. Иначе будет резет.
}

int main() {

        DDRB = (1<<PB0); // на этом пине висит светодиод
        i=0;

        //инициализация ватчдога
        wdt_reset(); // сбрасываем
        wdt_enable(WDTO_1S); // разрешаем ватчдог 1 сек
        WDTCR |= (1<<WDTIE); // разрешаем прерывания по ватчдогу. Иначе будет резет.
        sei(); // разрешаем прерывания

        set_sleep_mode(SLEEP_MODE_PWR_DOWN); // если спать - то на полную
        while(1) {
                sleep_enable(); // разрешаем сон
                sleep_cpu(); // спать!
        }
}

Т.о. теперь, потребление тока составляет- 4.7мкА. Т.е. заряда обычной батареи типа "таблетка" на 200мА хватит на 42553 часов, или 5 лет.

Нужно заметить, что значение таймера watchdog не абсолютное, а зависит от напряжения батареи.

========== UART ============================

Касаемо ATiny13, хотелось бы затронуть еще такой вопрос как программный UART. Трудно писать программу вслепую, и если, например, делаешь температурный регулятор оборотов вентилятора, то хочется знать что термодатчик показывает и скорость с какой какой крутиться вентилятор.

По правде говоря программный UART для ATtiny уже давно написан и описан здесь:

UART программный на Atiny13A

Программа изложенная там хорошая, она работает. Правда там ничего не сказано про частоту чипа, на которой он работал у автора, но методом перебора, мне удалось установить свазь для 1МГц чипа на 1200 бод.

Кроме того упомянутая программа не оптимизирована по размеру. Попытка оптимизации была предпринята здесь: UART в ATtiny13 или Как вывести данные из МК за 52р

однако автору не хватило смелости на более кардинальную оптимизацию. Покрутив какое-то время программу в отладчике, я составил свою версию:

#define F_CPU 1000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

volatile uint8_t c;

ISR(TIM0_COMPA_vect)
{
        c=1; TCNT0=0;
}

static int UART_trans(uint8_t data)
{
        uint8_t f,data2;
        data=~data;
        TCCR0B=(1<<CS01);
	OCR0A=115;
        TIMSK0=(1<<OCIE0A);
        for(f=0;f<10;f++)
	{
        	while(c==0);
		c=0;
	        if (f>8)
        	{
                	PORTB|=(1<<PB4);
	        } else if (f==0) {
                	PORTB&=~(1<<PB4);
		} else
		{
                        data2 = data & (1<<(f-1));
                        if (data2)
                                PORTB&=~(1<<PB4);
                        else
                                PORTB|=(1<<PB4);
         	}
        }

	c=0; TIMSK0=0;
	return 0;
}

int main(void)
{
        DDRB |= (1<<PB0) | (1<<PB4);
	sei();
        for(;;)
	{
		PORTB ^= (1<<PB0);
           	_delay_ms(1000);  // Пауза для наглядности
           	UART_trans('L');  // Отправляем обратно
        }

	return 0;
}

После компиляции прошивка весит 202 байта, работает только на передачу, частота чипа 1МГц, битрейт 1200 бод. Для увеличения битрейта используйте пропорциональный делитель для регистра OCR0A. Для подключения USB-TTL преобразователя к работающему чипу, нужно соединить "землю" преобразователя и чипа, а также подключить RX линию преобразователя к TX линии чипа. Питание преобразователя подключать не надо!

Результат работы:

поделиться: