View Categories

Различные трюки

Урок применим к 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
}
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх