Архитектура 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 */
Сборка
[править]Внесем в
Makefile
, ранее измененный для сборки кода, использующего асинхронный порт, следующие зависимости:default: uartscop.hex uartscop uartscop: uartscop.c uart.c
Создадим файл
uartscop.c
приведенного выше содержания.Скопируем файлы
uart.c
иuart.h
используемой библиотеки УАПП (uartlibrary.zip
) в директорию с исходным кодом.[3]Соберем рассматриваемый пример выполнив команду
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
Удостоверимся в отсутствии ошибок сборки и в появлении файла
uartscop.hex
, содержащего результирующий машинный код в формате Intel hex.
Загрузка и проверка работоспособности
[править]Загрузку кода в МК выполним аналогично ранее рассмотренному примеру.
Проверим работоспособность кода и устройства установив соединение и сбросив МК:
$ 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 $
Поднося ладонь к «антенне» и отдаляя ее, пронаблюдаем изменение амплитуды принимаемого сигнала:
`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
Пронаблюдаем изменение частоты поступающих данных при вводе символов g, h, i, j, задающих коэффициенты деления тактовой частоты 262144 (по-умолчанию), 65536, 16384, 2048, соответственно.
Исследование
[править]Зная тактовую частоту МК и используемый коэффициент деления, измерьте период и частоту принимаемого сигнала.
Изучите возможности выбора источника сигнала (управляющий регистр ADMUX), реализованные в предлагаемом коде.
Обратите внимание, что пожертвовав частотой дискретизации, можно реализовать попеременное измерение напряжений на двух (или более) различных входах мультиплексора АЦП. Попробуйте реализовать «двулучевой осциллограф» и измерить с его помощью вольт-амперные характеристики для некоторых двувыводных элементов (резистор, различные виды полупроводниковых диодов.)
Примечания
[править]- ↑ Analog-to-Digital Converter. ATmega8, ATmega8L: 8-bit Atmel with 8 KBytes In-System Programmable Flash. Проверено 29 апреля 2013.
- ↑ Analog Noise Canceling Techniques. ATmega8, ATmega8L: 8-bit Atmel with 8 KBytes In-System Programmable Flash. Проверено 29 апреля 2013.
- ↑ Peter Fleury AVR-GCC libraries. Проверено 15 марта 2014.