Оглавление
Урок применим к 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() {
}
Измерение напряжения питания #
/*
Скетч для калибровки точного вольтметра и его использование
КАЛИБРОВКА:
0) Ставим vol_calibration 1, прошиваем
1) Запускаем, открываем монитор. Будет выведено реальное значение Vсс в милливольтах
из расчёта по стандартной константе 1.1
2) Измеряем вольтметром напряжение на пинах 5V и GND, полученное значение отправляем в порт
В МИЛЛИВОЛЬТАХ (то есть если у нас 4.54В то отправляем 4540). Новая константа будет рассчитана
автоматически и запишется во внутреннюю память
3) Ставим vol_calibration 0, прошиваем
4) Наслаждайтесь точными измерениями!
ИСПОЛЬЗОВАНИЕ:
0) Функция readVCC возвращает ТОЧНОЕ опорное напряжение В МИЛЛИВОЛЬТАХ. В расчётах используем
не 5 Вольт, а readVсс!
1) При использовании analogRead() для перевода в вольты пишем:
float voltage = analogRead(pin) * (readVсс() / 1023.0); - это точный вольтаж В МИЛЛИВОЛЬТАХ
*/
#define vol_calibration 1 // калибровка вольтметра (если работа от АКБ) 1 - включить, 0 - выключить
float my_vcc_const = 1.1; // начальное значение константы должно быть 1.1
#include "EEPROMex.h" // библиотека для работы со внутренней памятью ардуино
void setup() {
Serial.begin(9600);
if (vol_calibration) calibration(); // калибровка, если разрешена
my_vcc_const = EEPROM.readFloat(1000); // считать константу из памяти
}
void loop() {
/*
// отображение заряда в процентах по ёмкости! Интерполировано
вручную по графику разряда ЛИТИЕВОГО аккумулятора
int volts = readVcc();
int capacity;
if (volts > 3870)
capacity = map(volts, 4200, 3870, 100, 77);
else if ((volts <= 3870) && (volts > 3750) )
capacity = map(volts, 3870, 3750, 77, 54);
else if ((volts <= 3750) && (volts > 3680) )
capacity = map(volts, 3750, 3680, 54, 31);
else if ((volts <= 3680) && (volts > 3400) )
capacity = map(volts, 3680, 3400, 31, 8);
else if (volts <= 3400)
capacity = map(volts, 3400, 2600, 8, 0);
Serial.println(capacity);
*/
}
void calibration() {
my_vcc_const = 1.1; // начальаня константа калибровки
Serial.print("Real VCC is: "); Serial.println(readVcc()); // общаемся с пользователем
Serial.println("Write your VCC (in millivolts)");
while (Serial.available() == 0); int Vcc = Serial.parseInt(); // напряжение от пользователя
float real_const = (float)1.1 * Vcc / readVcc(); // расчёт константы
Serial.print("New voltage constant: "); Serial.println(real_const, 3);
Serial.println("Set vol_calibration 0, flash and enjoy!");
EEPROM.writeFloat(1000, real_const); // запись в EEPROM
while (1); // уйти в бесконечный цикл
}
// функция чтения внутреннего опорного напряжения, универсальная (для всех ардуин)
long readVcc() {
#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(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA, ADSC)); // measuring
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
long result = (high << 8) | low;
result = my_vcc_const * 1023 * 1000 / result; // расчёт реального VCC
return result; // возвращает VCC
}