Архитектура AVR в примерах/Осциллографическая приставка

В этой работе мы рассмотрим использование встроенного в МК ATmega8 аналого-цифрового преобразователя[1] как основы для «осциллографической приставки», непрерывно передающей 10-битные результаты преобразования через асинхронный порт в виде двусимвольных ASCII-последовательностей.

Перед началом

править

Для данной работы, простейшее устройство следует дополнить следующими цепями:

  • AVcc — в данную цепь можно включить LC-фильтр,[2] однако при макетировании, вполне допустимо просто соединить выводы AVcc (20) и Vcc (7);
  • между ARef (21) и «общим» можно включить конденсатор емкостью порядка 0.1 µF;
  • к выводу ADC0 (23) подключим проводник, который будет выполнять роль источника сигнала — «антенны» — на этапе проверки работоспособности.

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

Осциллографическая приставка

править

/*** uartscop.c — UART-based oscilloscope adapter  -*- C -*- */
#include <avr/interrupt.h>      /* for sei (), ISR () */
#include <avr/io.h>             /* for DDRB, PORTB, etc. */
#include <avr/pgmspace.h>       /* FIXME: should be in uart.h */
#include <avr/sleep.h>          /* for sleep_enable (), etc. */
#include <avr/wdt.h>            /* for wdt_enable (), etc. */
#include <ctype.h>              /* for tolower () */
#include <stdbool.h>            /* for bool */
#include <stdint.h>             /* for int16_t, etc. */
#include <util/setbaud.h>       /* for UBRRH_VALUE, UBRRL_VALUE */

#include "uart.h"

/* ATmega8 compatibility */
#if (defined (TIMSK) && ! defined (TIMSK0))
#define TIMSK0  TIMSK
#endif
#if (defined (TCCR0) && ! defined (TCCR0B))
#define TCCR0B  TCCR0
#endif

/* each sample is two bytes; each byte is 10 bits (including
   start and stop bits, and assuming no parity bit) */
#define UART_BITS_PER_SAMPLE    (2 * 10L)
/* maximum sample rate for given baud rate */
#define UART_RATE_MAX           (BAUD / UART_BITS_PER_SAMPLE)

static volatile struct {
  bool  adc_p   : 1;
  bool  timer_p : 1;
} seen_interrupts = {
  .adc_p    = 0,
  /* assume a timer interrupt has happened */
  .timer_p  = 1
};

ISR (ADC_vect) {
  seen_interrupts.adc_p = 1;
}

ISR (TIMER0_OVF_vect) {
  seen_interrupts.timer_p = 1;
}

static void
adc_initiate ()
{
  ADCSRA  |= ((1    << ADSC));

  /* . */
}

static void
uart_puts_nl ()
{
  uart_putc ('\r');
  uart_putc ('\n');

  /* . */
}

static void
handle_control (unsigned char c)
{
  unsigned char cl  = tolower (c);

  if ((c      >= '0' && c   <= '9')
      || (cl  >= 'a' && cl  <= 'f')) {
    /* NB: assuming contiguous 0–9 and a–f ranges  */
    uint8_t chan
      = ((c >= '0' && c <= '9') ? c - '0'
         : cl - 'a' + 10);
    /* NB: assuming MUX3–MUX0 are consequent bits */
    if (((ADMUX >> MUX0) & 017) != chan) {
      ADMUX = ((ADMUX
                & (~ (017 << MUX0)))
               | (chan    << MUX0));
      /* FIXME: newline should go /after/ the next pair */
      uart_puts_nl ();
    }

    /* . */
    return;
  }

  switch (cl) {
  case 'g':
#if UART_RATE_MAX > (F_CPU / 256 / 256)
  case 'h':
#if UART_RATE_MAX > (F_CPU /  64 / 256)
  case 'i':
#if UART_RATE_MAX > (F_CPU /   8 / 256)
  case 'j':
#else
#warning "BAUD too low; j (/ 8) control disabled"
#endif
#else
#warning "BAUD too low; i, j (/ 64, 8) controls disabled"
#endif
#else
#warning "BAUD too low; h, i, j (/ 256, 64, 8) controls disabled"
#endif
    {
      /* NB: assuming contiguous g–j range  */
      /* CS0 = 101₂ to 010₂: Divide F_CPU by 1024, 256, 64, 8 */
      uint8_t cs  = (5 - (c - 'g'));
      /* NB: assuming CS02–CS00 are consequent bits */
      if (((TCCR0B >> CS00) & 007) != cs) {
        TCCR0B  = ((TCCR0B
                    & (~ (007 << CS00)))
                   | (cs      << CS00));
        uart_puts_P ("\r\nCS:");
        /* NB: assuming contiguous 0–5 range */
        uart_putc ('0' + cs);
        uart_puts_nl ();
      }
    }
    break;
  }

  /* . */
}

int
main ()
{
  /* expecting a reset every 36 ms at the least */
  wdt_enable (WDTO_1S);

  /* initialize the UART library */
  uart_init ((UBRR_VALUE
              | (USE_2X ? 0x8000 : 0)));

  /* initialize timer 0 to ensure we are interrupted on regular
     basis; CS0 = 101₂: Divide F_CPU by 1024 */
  TCCR0B   = ((1    << CS02)
              | (1  << CS00));
  /* Timer 0 overflow frequency is thus F_CPU / 1024 / 256,
     or 28 Hz to 76 Hz for F_CPU of 7.3728 MHz to 20 MHz;
     but handle_control () can raise it to F_CPU / 8 / 256,
     or 3.6 kHz to 9.8 kHz for the same F_CPU range */
#if UART_RATE_MAX < (F_CPU / 256 / 1024)
#warning "BAUD too low; proper operation not warranted"
#endif

  /* initialize ADC */
  ADMUX    = (0
              /* MUX  =  0000₂: ADC0 for the input */
              /* REFS   =  11₂: Internal 2.56 V reference */
              | (1  << REFS1)
              | (1  << REFS0));
  ADCSRA   = ((1    << ADEN)
              | (1  << ADIE)
              /* ADPS   = 111₂: Divide clock by 128 */
              | (1  << ADPS2)
              | (1  << ADPS1)
              | (1  << ADPS0));
  /* ADC clock is thus F_CPU / 128,
     or 57.6 kHz to 156 kHz for F_CPU of 7.3728 MHz to 20 MHz;
     sampling rate is at most F_CPU / 128 / 15,
     or 3.84 kHz to 10.4 kHz for the same F_CPU range */

  /* enable interrupts */
  TIMSK0  |= (1     << TOIE0);  /* timer 0 overflow */
  sei ();

  /* NB: ignoring the datasheet recommendation! */
  sleep_enable ();

  /* show the message */
  uart_puts_P ("\r\nREADY\r\n");

  /* initiate ADC conversion */
  adc_initiate ();

  /* main loop */
  uint8_t pairs;
  for (pairs = 0; ; ) {
    uint16_t c  = uart_getc ();
    if ((c & (~ 0xff)) == 0) {
      handle_control (c);
    }

    if (! seen_interrupts.timer_p) {
      /* do nothing */
    } else if (! seen_interrupts.adc_p) {
      uart_puts_P ("--");
      pairs++;
    } else {
      /* both ADC and timer 0 overflow interrupts seen */
      /* NB: we assume ASCII here */
      uint8_t
        lo  = ADCL; 
      const char
        h = (0x60 | (lo >> 5) | (ADCH << 3)),
        l = (0x40 | (lo & 037));
      /* clear the flags */
      seen_interrupts.timer_p
        = seen_interrupts.adc_p
        = 0;
      /* we have read the results – initiate another conversion
       */
      adc_initiate ();
      /* replace 0177 (ASCII DEL) with an ASCII printable */
      uart_putc ((h == 0177 ? '+' : h));
      uart_putc (l);
      pairs++;
    }
    if (pairs >= 32) {
      /* ensure a newline every 32 pairs (64 characters) */
      uart_puts_nl ();
      pairs -= 32;
    }

    /* reset the watchdog timer */
    wdt_reset ();

    /* sleep until the next event */
    sleep_cpu ();
  }

  /* not reached */
  /* . */
  return 0;
}
/*** uartscop.c ends here */

Сборка

править
  1. Внесем в Makefile, ранее измененный для сборки кода, использующего асинхронный порт, следующие зависимости:

    default: uartscop.hex uartscop
    
    uartscop: uartscop.c uart.c
    
  2. Создадим файл uartscop.c приведенного выше содержания.

  3. Скопируем файлы uart.c и uart.h используемой библиотеки УАПП (uartlibrary.zip) в директорию с исходным кодом.[3]

  4. Соберем рассматриваемый пример выполнив команду make:

    $ make uartscop.hex 
    avr-gcc -O2 -Wall -std=gnu11 -mmcu=atmega8 -DF_CPU=7372800 -DBAUD=115200   uartscop.c   -o uartscop
    avr-objcopy -O ihex uartscop uartscop.hex
    $ 
    
    NB

    При использовании микроконтроллера, отличного от ATmega8, кварцевого резонатора на частоту, отличную от 7.3728 MHz, или же желаемой скорости передачи данных, отличной от 115200 bit⁄s, следует явно указать параметры сборки MCU, F_CPU, или BAUD, соответственно

    Так, для платы Arduino Uno R3, поставляемой с МК ATmega328P и кварцевым резонатором на 20 MHz, команда сборки может быть следующей:

    $ make MCU=atmega328p F_CPU=20000000 uartscop.hex 
    
  5. Удостоверимся в отсутствии ошибок сборки и в появлении файла uartscop.hex, содержащего результирующий машинный код в формате Intel hex.

Загрузка и проверка работоспособности

править
  1. Загрузку кода в МК выполним аналогично ранее рассмотренному примеру.

  2. Проверим работоспособность кода и устройства установив соединение и сбросив МК:

    $ cu -l /dev/ttyUSB1 -s 115200 
    Connected.
    Здесь МК следует сбросить, если это не было выполнено при установлении соединения.
    READY
    --`A`D`A`A`@`C`E`@`@`@`C`@`@`@`D`D`A`@`A`D`B`@`@`C`D`A`@`C`D`C`B
    `@`C`B`B`@`A`E`B`@`@`B`D`B`@`D`D`B`@`@`C`C`A`@`B`F`A`@`@`D`E`A`@
    
    NB

    Программа cu настаивает на использовании сигналов RTS и CTS порта RS-232. Поскольку сигнал CTS не формируется МК, передача данных устройству не будет выполняться. Отключить ожидание можно выполнив после запуска cu (но перед вводом каких-либо данных!) команду, подобную:

    $ stty -crtscts -F /dev/ttyUSB1 
    $ 
    
  3. Поднося ладонь к «антенне» и отдаляя ее, пронаблюдаем изменение амплитуды принимаемого сигнала:

    `A`B`A`@`@`E`E`@`@`D`F`C`@`@`E`F`B`@`E`I`C`@`@`L`G`A`@`H`K`F`@`A
    `N`L`A`@`I`Q`J`@`B`T`N`A`@`M`V`J`@`F`W`R`A`@`P`]`L`@`F`^`W`B`@`R
    `^`L`@`H`]`U`B`@`Ta@`L`@`JaA`X`A`@`Va@`O`@`GaA`W`B`@`ZaC`N`@`Ha@
    `Y`A`@`U`^`M`@`Ka@`W`A`@`UaB`M`@`G`^`V`A`@`T`_`K`@`K`^`X`B`@`V`_
    `K`@`G`^`W`C`@`TaA`L`@`I`_`X`C`@`W`]`K`@`J`_`V`A`@`SaA`M`@`HaB`Z
    `A`@`UaB`M`@`HaD`[`A`@`WaC`N`@`GaD`\`B`@`WaB`O`@`Ja@`Y`D`@`YaE`N
    `@`IaE`[`C`@`ZaF`O`@`JaE`\`C`@`XaF`N`@`JaF`]`C`@`YaF`P`@`JaE`\`B
    `@`YaF`O`@`HaF`[`A`@`XaE`P`@`JaD`[`B`@`VaE`M`@`JaB`[`A`@`WaC`O`@
    `GaD`\`C`@`YaF`N`@`LaC`\`B`@`YaD`O`@`HaH`\`B`@`ZaF`P`@`KaDaA`B`@
    `VaD`N`@`JaD`[`B`@`ZaE`O`@`HaC`\`B`@`YaF`P`@`KaC`]`B`@`XaG`M`@`K
    aE`]`D`@`ZaD`M`@`IaA`[`B`@`VaB`M`@`IaB`W`B`@`TaA`L`@`H`T`Q`B`@`I
    `M`C`@`@`G`D`@`@`A`G`B`@`@`E`D`A`@`@`D`B`@`@`D`C`A`@`A`C`B`@`@`C
    `A`A`@`A`D`B`@`@`C`C`B`@`A`D`A`A`@`B`@`@`A`A`D`C`@`@`B`C`A`@`@`C
    
  4. Пронаблюдаем изменение частоты поступающих данных при вводе символов g, h, i, j, задающих коэффициенты деления тактовой частоты 262144 (по-умолчанию), 65536, 16384, 2048, соответственно.

Исследование

править
  1. Зная тактовую частоту МК и используемый коэффициент деления, измерьте период и частоту принимаемого сигнала.

  2. Изучите возможности выбора источника сигнала (управляющий регистр ADMUX), реализованные в предлагаемом коде.

  3. Обратите внимание, что пожертвовав частотой дискретизации, можно реализовать попеременное измерение напряжений на двух (или более) различных входах мультиплексора АЦП. Попробуйте реализовать «двулучевой осциллограф» и измерить с его помощью вольт-амперные характеристики для некоторых двувыводных элементов (резистор, различные виды полупроводниковых диодов.)

Примечания

править
  1. Analog-to-Digital Converter. ATmega8, ATmega8L: 8-bit Atmel with 8 KBytes In-System Programmable Flash. Проверено 29 апреля 2013.
  2. Analog Noise Canceling Techniques. ATmega8, ATmega8L: 8-bit Atmel with 8 KBytes In-System Programmable Flash. Проверено 29 апреля 2013.
  3. Peter Fleury AVR-GCC libraries. Проверено 15 марта 2014.