Архитектура AVR в примерах/Калькулятор на асинхронном порту

В этой работе мы рассмотрим использование встроенного в МК ATmega8 универсального асинхронного приемопередатчика (УАПП; англ. UART) на примере программы, реализующей простейший целочисленный калькулятор с постфиксной записью.

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

править

Для данной работы нам потребуется наличие связи между простейшим устройством и адаптером «USB—асинхронный порт» — которая, впрочем, уже существует, поскольку необходима для взаимодействия с загрузчиком Optiboot.[1]

Рассматриваемый код полагается на библиотеку УАПП за авторством Peter Fleury (uartlibrary.zip), которая является свободным программным обеспечением и доступна на условиях GNU General Public License.[2]

Со стороны системы разработчика, для связи с устройством можно применить такие программы, как cu (англ. call up или call Unix) или PuTTY.

Все прочие требования к среде разработки и устройству также сохраняются.

Сборка кода

править
/*** uartc.c — A simplistic calculator on UART  -*- 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 <stdlib.h>             /* for ltoa () */
#include <util/setbaud.h>       /* for UBRRH_VALUE, UBRRL_VALUE */

#include "uart.h"

static volatile uint8_t seen_interrupt_p  = 0;
ISR (TIMER0_OVF_vect) {
  seen_interrupt_p   = 1;
}

int
main ()
{
  /* expecting a reset about every 10 ms */
  wdt_enable (WDTO_1S);

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

  /* initialize timer 0 to ensure we are interrupted once in a
     while; CS0 = 101 _2: Divide F_CPU by 1024 */
  TCCR0    = ((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 */

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

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

  /* main loop */
  uint8_t number_p;
  long    x, y;
  for (x = y = 0, number_p = 1; ; ) {
    unsigned int c  = uart_getc ();
    if ((c & (~ 0xff)) == 0) {
      uint8_t
        echo_p  = 1,
        new_number_p  = 0;
      /* ignore all errors, including UART_NO_DATA */
      switch (tolower (c)) {
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
        if (! number_p) {
          x = y;
          y = 0;
        }
        y *= 10;
        y += (c - '0');
        new_number_p  = 1;
        break;
      case '+': x += y; y = x;  break;
      case '*': x *= y; y = x;  break;
      case '/': x /= y; y = x;  break;
      case '-': x -= y; y = x;  break;
      case 'p': case 'n':
        /* inhibit echo later, do it now */
        echo_p  = 0;
        uart_putc (c);
        /* NB: it takes some 3.32 bits per a decimal digit */
        char s[4 + (sizeof (y) * 8 - 1) / 3]
          = "\r\n";
        ltoa (y, 2 + s, 10);
        uart_puts (s);
        uart_putc ('\r');
        uart_putc ('\n');
        break;
      case ' ': case '\t':
        /* ignore whitespace (but still echo it) */
        break;
      case '\r':  case '\n':
        /* ensure \r\n */
        echo_p  = 0;
        uart_putc ('\r');
        uart_putc ('\n');
        break;
      default:
        echo_p  = 0;
        uart_puts_P ("\r\nHuh?\r\n");
        break;
      }
      if (echo_p) {
        uart_putc (c);
      }
      number_p  = new_number_p;
    }

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

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

  /* not reached */
  /* . */
  return 0;
}
/*** uartc.c ends here */
  1. Внесем в созданный ранее Makefile следующие изменения.

    • Добавим новую переменную BAUD, определив ее так:

      BAUD    = 115200
      
    • Дополним определение переменной CPPFLAGS параметром -DBAUD=$(BAUD).

    • Добавим следующие цели:

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

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

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

    $ make uartc.hex 
    avr-gcc -O2 -Wall -std=gnu11 -mmcu=atmega8 -DF_CPU=7372800 -DBAUD=115200   uartc.c   -o uartc
    avr-objcopy -O ihex uartc uartc.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 uartc.hex 
    
  5. Удостоверимся в отсутствии ошибок сборки и в появлении файла uartc.hex, содержащего результирующий машинный код в формате Intel hex.

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

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

  2. Проверим работоспособность кода и устройства установив соединение и вычислив (5 + 4) × 3 ∕ 2 − 1 (в целых числах):

    $ cu -l /dev/ttyUSB1 -s 115200 
    Connected.
    
    READY
    5 4 + 3 * 2 / 1 - n
    12
    ~.
    Disconnected.
    $ 
    
    NB

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

    $ stty -crtscts -F /dev/ttyUSB1 
    $ 
    

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

править
  1. Попробуйте реализовать в коде обработку вводимых кодов BS (8 или '\b'; C-h) и DEL (127; C-8) — один из этих кодов, как правило, закрепляется за клавишей Backspace — для отмены ввода последнего символа, если такой символ — цифра.

  2. Код выше не предусматривает возможность непосредственного ввода отрицательных чисел — ввод - немедленно приводит к выполнению операции вычитания. Попробуйте дополнить код буфером введенной операции так, чтобы:

    • коды BS и DEL приводили к отмене ввода также и последнего символа операции;
    • операция вычитания выполнялась только при вводе после - символа, отличного от цифры;
    • ввод - непосредственно перед цифрой воспринимался как знак вводимого числа.
  3. Наконец, можно попробовать реализовать в коде полноценный буфер введенной строки, с возможностью удаления любого символа кодами BS и DEL, или всей строки в целом кодом 21 (C-u).

Примечания

править
  1. optiboot — An optimised bootloader for Arduino platforms. Проверено 15 марта 2014.
  2. 2,0 2,1 Peter Fleury AVR-GCC libraries. Проверено 15 марта 2014.