Оглавление
Урок применим к ATMega328 и некоторым другим из этого поколения
analogPrescaler #
Установка делителя АЦП. Доступны 2, 4, 8, 16, 32, 64, 128:
void analogPrescaler(uint8_t prescaler) {
switch (prescaler) {
case 2: ADCSRA = (ADCSRA & 0xF8) | 0x01;
break;
case 4: ADCSRA = (ADCSRA & 0xF8) | 0x02;
break;
case 8: ADCSRA = (ADCSRA & 0xF8) | 0x03;
break;
case 16: ADCSRA = (ADCSRA & 0xF8) | 0x04;
break;
case 32: ADCSRA = (ADCSRA & 0xF8) | 0x05;
break;
case 64: ADCSRA = (ADCSRA & 0xF8) | 0x06;
break;
case 128: ADCSRA = (ADCSRA & 0xF8) | 0x07;
break;
}
}
Pin change interrupts (PCINT) #
Пример использования PCINT
- прерывания на любом пине. Прерывание вызывается при переключении состояния любого пина из группы:
// обработчики прерываний
ISR(PCINT0_vect) { // пины 8-13
}
ISR(PCINT1_vect) { // пины A0-A5
}
ISR(PCINT2_vect) { // пины 0-7
}
// функция для настройки PCINT. Вернёт номер группы пинов
uint8_t attachPCINT(uint8_t pin) {
if (pin < 8) { // D0-D7 (PCINT2)
PCICR |= (1 << PCIE2);
PCMSK2 |= (1 << pin);
return 2;
} else if (pin > 13) { // A0-A5 (PCINT1)
PCICR |= (1 << PCIE1);
PCMSK1 |= (1 << pin - 14);
return 1;
} else { // D8-D13 (PCINT0)
PCICR |= (1 << PCIE0);
PCMSK0 |= (1 << pin - 8);
return 0;
}
}
Микро Serial (UART) #
// UART_begin(бод) - запустить
// UART_write(byte) - отправить байт
// UART_available() - проверка на входящий
// UART_read() - прочитать байт
// UART_end() - выключить
void UART_begin(uint32_t baudrate) {
uint16_t speed = (F_CPU / (8L * baudrate)) - 1;
UBRR0H = highByte(speed);
UBRR0L = lowByte(speed);
UCSR0A = (1 << U2X0);
UCSR0B = (1 << TXEN0) | (1 << RXEN0);
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
void UART_write(uint8_t data) {
while (!(UCSR0A & (1 << UDRE0)));
UDR0 = data;
}
bool UART_available() {
return (UCSR0A & (1 << RXC0));
}
uint8_t UART_read() {
uint8_t data = UDR0;
return data;
}
void UART_end() {
UCSR0B = 0;
}
// пример работы
void setup() {
UART_begin(9600);
UART_write(40); // отправить байт 40
UART_write(40);
}
void loop() {
if (UART_available()) { // если есть что на приём
byte data = UART_read(); // прочитать
UART_write(data); // отправить обратно
}
}
Сон и пробуждение по INT #
// ВНИМАНИЕ! Это просто сон, без отключения АЦП и прочих блоков
// для полноценного сна используйте GyverPower
void setup() {
pinMode(2, 2); // внешняя подтяжка лучше длоя энергосбережения!
Serial.begin(9600);
Serial.println("hello!");
delay(500);
Serial.println("go to sleep");
delay(500);
attachInterrupt(0, wakeUp, LOW); // вкл прерывание пробуждения
goToSleep(); // отправка в сон
delay(1000); // после сна
Serial.println("im back in business"); // продолжили работу
}
void wakeUp() {
detachInterrupt(0); // откл прерывание пробуждения
SMCR &= ~ (1 << SE); // запретили сон
Serial.println("five more minutes,pls"); // сказали что проснулись
}
void goToSleep() {
SMCR |= (1 << SM1); // настроили сон как powerDown
SMCR |= (1 << SE); // разрешили сон
asm volatile ("sleep"); // инструкция сна
}
void loop() {
}
Поднимаем millis() на 0 таймере #
// полные аналоги стандартным функциям времени
// пригодится, если работать без Arduino.h
// доступные функции
// необходимо вызвать uptime0Init() при запуске, чтобы всё завелось
void uptime0Init();
unsigned long millis0();
unsigned long micros0();
void delay0(unsigned long ms);
void delayMicroseconds0(unsigned int us);
// ==================== РЕАЛИЗАЦИЯ ==================
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)
#define MICROS_MULT (64 / clockCyclesPerMicrosecond())
volatile unsigned long timer0_overflow_count = 0;
volatile unsigned long timer0_millis = 0;
static unsigned char timer0_fract = 0;
void uptime0Init() {
sei();
TCCR0A = (1 << WGM01) | (1 << WGM00);
TCCR0B = (1 << CS01) | (1 << CS00);
TIMSK0 |= (1 << TOIE0);
} ISR(TIMER0_OVF_vect) {
timer0_millis += MILLIS_INC;
timer0_fract += FRACT_INC;
if (timer0_fract >= FRACT_MAX) {
timer0_fract -= FRACT_MAX;
timer0_millis++;
}
timer0_overflow_count++;
}
unsigned long millis0() {
uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания
cli(); // выключаем прерывания
unsigned long m = timer0_millis; // перехватить значение
SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот
return m; // вернуть миллисекунды
}
unsigned long micros0() {
uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания
cli(); // выключаем прерывания
unsigned long m = timer0_overflow_count; // счет переполнений
uint8_t t = TCNT0; // считать содержимое счетного регистра
if ((TIFR0 & _BV(TOV0)) && (t < 255)) // инкремент по переполнению
m++;
SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот
return (long)(((m << 8) + t) * MICROS_MULT); // вернуть микросекунды
}
void delay0(unsigned long ms) {
uint32_t start = micros0();
while (ms > 0) { // ведем отсчет
while ( ms > 0 && (micros0() - start) >= 1000) {
ms--;
start += 1000;
}
}
}
void delayMicroseconds0(unsigned int us) {
#if F_CPU >= 24000000L
us *= 6; // x6 us, = 7 cycles
us -= 5; //=2 cycles
#elif F_CPU >= 20000000L
__asm__ __volatile__ (
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop"); //just waiting 4 cycles
if (us <= 1) return; // = 3 cycles, (4 when true)
us = (us << 2) + us; // x5 us, = 7 cycles us -= 7; // 2 cycles #elif F_CPU >= 16000000L
if (us <= 1) return; // = 3 cycles, (4 when true)
us <<= 2; // x4 us, = 4 cycles us -= 5; // = 2 cycles, #elif F_CPU >= 12000000L
if (us <= 1) return; // = 3 cycles, (4 when true)
us = (us << 1) + us; // x3 us, = 5 cycles us -= 5; //2 cycles #elif F_CPU >= 8000000L
if (us <= 2) return; // = 3 cycles, (4 when true)
us <<= 1; //x2 us, = 2 cycles
us -= 4; // = 2 cycles
#else
if (us <= 16) return; //= 3 cycles, (4 when true)
if (us <= 25) return; //= 3 cycles, (4 when true), (must be at least 25 if we want to substract 22) us -= 22; // = 2 cycles us >>= 2; // us div 4, = 4 cycles
#endif
__asm__ __volatile__ (
"1: sbiw %0,1" "\n\t" // 2 cycles
"brne 1b" : "=w" (us) : "0" (us) // 2 cycles
);
}
Поднимаем millis() на 2 таймере #
// полные аналоги стандартным функциям времени
// пригодится, если работать без Arduino.h
// доступные функции
// необходимо вызвать uptime2Init() при запуске, чтобы всё завелось
void uptime2Init();
unsigned long millis2();
unsigned long micros2();
void delay2(unsigned long ms);
void delayMicroseconds2(unsigned int us);
// =================== РЕАЛИЗАЦИЯ ==================
#define MICROSECONDS_PER_TIMER2_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
#define MILLIS2_INC (MICROSECONDS_PER_TIMER2_OVERFLOW / 1000)
#define FRACT2_INC ((MICROSECONDS_PER_TIMER2_OVERFLOW % 1000) >> 3)
#define FRACT2_MAX (1000 >> 3)
#define MICROS2_MULT (64 / clockCyclesPerMicrosecond())
volatile unsigned long timer2_overflow_count = 0;
volatile unsigned long timer2_millis = 0;
static unsigned char timer2_fract = 0;
void uptime2Init() {
sei();
TCCR2A = (1 << WGM20) | (1 << WGM21);
TCCR2B = 1 << CS22;
TIMSK2 = 1 << TOIE2; } ISR(TIMER2_OVF_vect) { timer2_millis += MILLIS2_INC; timer2_fract += FRACT2_INC; if (timer2_fract >= FRACT2_MAX) {
timer2_fract -= FRACT2_MAX;
timer2_millis++;
}
timer2_overflow_count++;
}
unsigned long millis2() {
uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания
cli(); // выключаем прерывания
unsigned long m = timer2_millis; // перехватить значение
SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот
return m; // вернуть миллисекунды
}
unsigned long micros2() {
uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания
cli(); // выключаем прерывания
unsigned long m = timer2_overflow_count; // счет переполнений
uint8_t t = TCNT2; // считать содержимое счетного регистра
if ((TIFR2 & _BV(TOV2)) && (t < 255)) // инкремент по переполнению
m++;
SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот
return (long)(((m << 8) + t) * MICROS2_MULT); // вернуть микросекунды } void delay2(unsigned long ms) { uint32_t start = micros2(); while (ms > 0) { // ведем отсчет
while ( ms > 0 && (micros2() - start) >= 1000) {
ms--;
start += 1000;
}
}
}
void delayMicroseconds2(unsigned int us) {
#if F_CPU >= 24000000L
us *= 6; // x6 us, = 7 cycles
us -= 5; //=2 cycles
#elif F_CPU >= 20000000L
__asm__ __volatile__ (
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop"); //just waiting 4 cycles
if (us <= 1) return; // = 3 cycles, (4 when true)
us = (us << 2) + us; // x5 us, = 7 cycles us -= 7; // 2 cycles #elif F_CPU >= 16000000L
if (us <= 1) return; // = 3 cycles, (4 when true)
us <<= 2; // x4 us, = 4 cycles us -= 5; // = 2 cycles, #elif F_CPU >= 12000000L
if (us <= 1) return; // = 3 cycles, (4 when true)
us = (us << 1) + us; // x3 us, = 5 cycles us -= 5; //2 cycles #elif F_CPU >= 8000000L
if (us <= 2) return; // = 3 cycles, (4 when true)
us <<= 1; //x2 us, = 2 cycles
us -= 4; // = 2 cycles
#else
if (us <= 16) return; //= 3 cycles, (4 when true)
if (us <= 25) return; //= 3 cycles, (4 when true), (must be at least 25 if we want to substract 22) us -= 22; // = 2 cycles us >>= 2; // us div 4, = 4 cycles
#endif
__asm__ __volatile__ (
"1: sbiw %0,1" "\n\t" // 2 cycles
"brne 1b" : "=w" (us) : "0" (us) // 2 cycles
);
}
Чтение фьюзов #
uint8_t fuse_get(uint16_t address) {
uint8_t data;
asm volatile
(
"sts %[spmreg], %[spmcfg] \n\t"
"lpm %[data], Z \n\t"
: [data] "=r" (data)
: [spmreg]"i" (_SFR_MEM_ADDR(SPMCSR)),
[spmcfg] "r" ((1 << SPMEN) |(1 << BLBSET)),
"z" (address)
);
return data;
}
// пример чтения установленных фьюз-байтов
#define LOW_FUSE (0x0000)
#define LOCK (0x0001)
#define EXTENDED_FUSE (0x0002)
#define HIGH_FUSE (0x0003)
void setup() {
Serial.begin(115200);
Serial.print("Low: 0x");
Serial.println(fuse_get(LOW_FUSE_BYTE), HEX);
Serial.print("High: 0x");
Serial.println(fuse_get(HIGH_FUSE_BYTE), HEX);
Serial.print("Extended: 0x");
Serial.println(fuse_get(EXTENDED_FUSE_BYTE), HEX);
Serial.print("Lock: 0x");
Serial.println(fuse_get(LOCK_BYTE), HEX);
}
void loop() {
}
Измерение напряжения питания #
long readVcc(long ref1v1) {
uint8_t muxt = ADMUX;
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
delay(1);
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA, ADSC));
long vcc = ref1v1 * 1023 / ADC;
ADMUX = muxt;
return vcc;
}
Функция возвращает напряжение питания МК в милливольтах, а как аргумент принимает точное значение опорного напряжения 1.1V в милливольтах. Для определения точного опорного можно "откалибровать" его по факту через пропорцию, считая базовое значение равным 1100
. Либо на ATMega328p его можно измерить, для чего нужно загрузить код:
void setup() {
analogReference(INTERNAL);
analogRead(0);
}
void loop() {}
И замерить напряжение между GND и AREF - это и будет реальное значение опорного. Например получилось 1.103V. Далее используем предложенную функцию:
Serial.println(readVcc(1103));

Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
- Поддержать автора за работу над уроками