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() {
}

Измерение напряжения питания #

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));

Полезные страницы #

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

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