Машинка на радиоуправлении на базе ATtiny2313

Машинка на радиоуправлении на базе ATtiny2313В данной статье пойдёт речь о том как сделать машинку на радиоуправлении своими руками на базе микроконтроллера ATtiny2313. Другими словами — изобретаем велосипед, ибо по интернетам есть тонны примеров на Arduino и без Arduino. Несмотря на это, я тоже решил внести свою лепту.

Я не очень люблю работать с Arduino, так как не чувствуется сама идея работы с микроконтроллерами, всё спрятано в библиотеках и, если что-то нужно, просто скачиваешь нужную либу, устанавливаешь её и используешь, а как и что там в большинстве случаев остаётся тайной.

Для изготовления машинки, нам понадобится

  1. микроконтроллер ATtiny2313;
  2. готовое шасси вместе с двигателями (танк или двухколёсный кит);
  3. HC-11, HC-12, TB387 или любые доступные USART радио-модули — две штуки;
  4. L298 — модуль драйвера двигателей;
  5. Аккумуляторы 18650 Li-ion — две штуки;
  6. Коробка (бокс) под аккумуляторы 18650 (на 2 аккумулятора);
  7. Преобразователь USB-UART на базе CH340G или PL2303HX;
  8. USBasp v2.0 ISP программатор

Ссылки на Aliexpress на всё это добро вы найдёте в конце статьи.

Почему микроконтроллер ATtiny2313

Микроконтроллер ATTiny2313 улучшенный вариант старого микроконтроллера AT90S2313. Внутри у него 120 инструкций оптимизированных для программирования на языках высокого уровня, 32 регистра общего назначения, 2 килобайта флеш-памяти для программ, 128 байт EEPROM (энергонезависимая память) и 128 байт SRAM (оперативная память). Из периферии: один 8 битный таймер/счетчик, один 16 битный таймер/счетчик, четыре ШИМ канала, 2 из которых будут использованы для управления колёсами, аналоговый компаратор, Watchdog таймер, USI универсальный последовательный интерфейс и, самое главное для данного проекта, USART. Если фьюзы выставлены на работу от внешнего кварца, кварц должен быть установлен на частоту, не превышающей максимальную по даташиту, это 20МГц.

Все вышеперечисленные характеристики более чем достаточно для наших задач. К тому же ATTiny2313 не дорогой и доступный микроконтроллер, в отличии от Arduino.
ATTiny2313

Принцип работы программы на МК ATtiny2313

Сама программа состоит из 3-х классов (USART, Queue, CmdExecutor) и основного файла main.cpp, который содержит функцию main(). Класс USART отвечает за инициализацию протокола и получения данных, в нашем случае данные — это команды. После получения, команда добавляется, push(cmd), в очередь Queue. Класс Queue, он же очередь, имеет два метода push(cmd) и pop(cmd). С помощью первого метода, как мы уже сказали, добавляем команды в очередь, а вторым, соответственно берём первую команду из очереди. В функции main() и проверяется если в очереди есть команды. Если команда нашлась main() берёт её и передаёт классу CmdExecutor, он же исполнитель команд, выполняет её — execute(cmd).
Машинка на радиоуправлении на базе ATtiny2313 (Диаграмма последовательности)

Для чего нужна была очередь команд, нельзя было просто выполнять команды сразу после получения, а не тратить время и ресурсы не очень-то и мощного ATtiny2313? Да, можно было, можно было вообще сделать этот пример из двух функций: main() и ISR(USART_RX_vect), и гуляй Вася. Однако не так, во первых, если одна команда выполняется очень много времени, а другая уже на подходе, то как тут быть? Во вторых, если микроконтроллер помимо команд выполняет ещё и другую работу, тоже очень важную, а мы эту работу будем остановить очень часто, тогда может выйти так, что результат будет не тот, да и команды не правильно могут выполнятся, особенно тогда, когда и команда и работа используют те же ресурсы.

Схема подключения компонентов

Машинка на радиоуправлении на базе ATtiny2313 (Схема)P1 (COMPIM) — COM порт, на реальной машине его нужно заменить на USART радио модуля, к примеру: HC-11, HC-12, TB387 или на любого доступного.
U1 (ATTINY2313) — микроконтроллер
U2 (L298) — модуль драйвера двигателей

Программа для управления

Программа для управления

Управлять «бэтмобиль» можно было и с помощью пульта. Однако написать программу на Джаве намного легче, чем взять паяльник в руки и пилить пульт, да и программу можно сделать с большим функционалом, что-то добавить, что-то отображать, другое сделать конфигурируемым и вообще — возможности почти безграничны.

В моём случае эта программа всего лишь отправляет команды по USART, т.е. делает необходимый минимум задач.

Как установить Rx Tx в Java смотрим здесь, а пример приложения здесь.

Список команд

  • private static final byte PWM1 = '1'; — 1-я скорость;
  • private static final byte PWM2 = '2'; — 2-я скорость;
  • private static final byte PWM3 = '3'; — 3-я скорость;
  • private static final byte PWM4 = '4'; — 4-я скорость;
  • private static final byte PWM5 = '5'; — максимальная скорость;
  • private static final byte STOP = 'a'; — стоп машина;
  • private static final byte START = 'b'; — старт машина, включаются периферия и ШИМ, команда выполняется при включении машины;
  • private static final byte RIGHT_FORWARD = 'c'; — правая гусеница движется вперёд;
  • private static final byte LEFT_FORWARD = 'd'; — левая гусеница движется вперёд;
  • private static final byte RIGHT_BACK = 'e'; — правая гусеница движется назад;
  • private static final byte LEFT_BACK = 'f'; — левая гусеница движется назад;
  • private static final byte ALL_FORWARD = 'g'; — обе гусеницы движутся вперёд;
  • private static final byte ALL_BACK = 'h'; — обе гусеницы движутся назад;
  • private static final byte LEFT_STOP = 'i'; — левая гусеница остановлена;
  • private static final byte RIGHT_STOP = 'j'; — правая гусеница остановлена;
  • private static final byte STOP_ALL = 'k'; — обе гусеницы остановлены;

Управление

  1. 5 скоростных режимов (кнопки от 1 до 5);
  2. Движение вперёд — обе гусеницы движутся вперёд (↑↑), нажата стрелка вверх (↑);
  3. Движение назад — обе гусеницы движутся назад (↓↓), нажата стрелка вниз (↓);
  4. Движение вперёд и направо — правая гусеница остановлена, левая движется вперёд (↑■), нажаты стрелки вверх и направо(↑→);
  5. Движение вперёд и налево — левая гусеница остановлена, правая движется вперёд (■↑), нажаты стрелки вверх и налево (←↑);
  6. Движение назад и направо — правая гусеница остановлена, левая движется назад (↓■), нажаты стрелки вниз и направо (↓→);
  7. Движение назад и налево — левая гусеница остановлена, правая движется назад (■↓), нажаты стрелки вниз и налево (←↓);
  8. Движение по кругу по часовой — левая гусеница движется назад, правая движется вперёд (↓↑), нажата стрелка налево (←);
  9. Движение по кругу против часовой — правая гусеница движется назад, левая движется вперёд (↑↓), нажата стрелка направо (→);

Вывод на консоль

После нажатия стрелок в консоли появятся знаки, указывающие движение машины/танка:

↑↑
■■
↓↑
■■
↑↓
■■
↓↓
■■
↑↑
■↑
↑↑
↑■
↑↑
■■
↓↓
■↓
↓↓
↓■
↓↓
■■

Настройки программы

Выход из программы: USART->Exit или Alt-F4;

Программа для управления

Подключение: USART->Connect или Ctrl+Alt-C и выбираем COM порт;Программа для управления

Настройка скорости: USART->Baud или Ctrl+Alt-B и выбираем скорость передачи данных, по умолчанию 9600, такаяже установлена и в прошивке. Настроить следует перед тем, как подключиться;Программа для управленияНастроить можно число стоп битов и число битов данных, но в нашем случае их лучше оставить 1 и 8 соответственно.

Код программы для ATtiny2313

Программа для ATtiny2313 написана на C++, а проект сделал в Eclipse C++. Как настроить Eclipse C/C++ для программирования AVR микроконтроллеров смотрите здесь.

main.cpp

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#include "USART.h"
#include "CmdExecutor.h"
#include "Queue.h"

void pushData(uint8_t data) {
  cmdQueue.push(data);
}

int main() {
  usart.init(9600);
  usart.setOnReceiveFunction(pushData);
  sei();
  cmdExecutor.cmdStart();

  uint8_t cmd;
  while (1) {
    if (cmdQueue.pop(cmd)) {
      cmdExecutor.execute(cmd);
    }
    _delay_ms(1);
  }
  return 1;
}

Queue.h

#ifndef QUEUE_H_
#define QUEUE_H_

#define QUEUE_SIZE      4

class Queue {
private:
  uint8_t queueSize;
  uint8_t data[QUEUE_SIZE];
public:
  Queue();
  void push(uint8_t);
  uint8_t pop(uint8_t&);
};

extern Queue cmdQueue;

#endif /* QUEUE_H_ */

Queue.cpp

#include <avr/io.h>
#include <stdlib.h>
#include "Queue.h"

Queue cmdQueue;

Queue::Queue() :
    queueSize(0) {
}

void Queue::push(uint8_t cmd) {
  if (this->queueSize < QUEUE_SIZE) {
    this->data[this->queueSize] = cmd;
    this->queueSize++;
  }
}

uint8_t Queue::pop(uint8_t&cmd) {
  if (this->queueSize > 0) {
    this->queueSize--;
    cmd = this->data[0];
    for (uint8_t i = 0; i < this->queueSize; i++) {
      this->data[i] = this->data[i + 1];
    }
    return 1;
  }
  return 0;
}

CmdExecutor.h

#ifndef CMDEXECUTOR_H_
#define CMDEXECUTOR_H_

#define LEFT_PWM_DDR         DDRB
#define RIGTH_PWM_DDR        DDRB
#define LEFT_PWM_PIN         PINB3
#define RIGTH_PWM_PIN        PINB4

#define LEFT_DDR_FORWARD     DDRD
#define RIGTH_DDR_FORWARD    DDRD
#define LEFT_DDR_BACK        DDRD
#define RIGTH_DDR_BACK       DDRD
#define LEFT_PORT_FORWARD    PORTD
#define RIGTH_PORT_FORWARD   PORTD
#define LEFT_PORT_BACK       PORTD
#define RIGTH_PORT_BACK      PORTD
#define LEFT_PIN_FORWARD     PIND2
#define RIGTH_PIN_FORWARD    PIND3
#define LEFT_PIN_BACK        PIND4
#define RIGTH_PIN_BACK       PIND5

#define  PWM1                '1'
#define  PWM2                '2'
#define  PWM3                '3'
#define  PWM4                '4'
#define  PWM5                '5'
#define  STOP                'a'
#define  START               'b'
#define  RIGHT_FORWARD       'c'
#define  LEFT_FORWARD        'd'
#define  RIGHT_BACK          'e'
#define  LEFT_BACK           'f'
#define  ALL_FORWARD         'g'
#define  ALL_BACK            'h'
#define  LEFT_STOP           'i'
#define  RIGHT_STOP          'j'
#define  STOP_ALL            'k'

class CmdExecutor {
public:
  CmdExecutor();
  void execute(uint8_t);
  void cmdStart();
  void cmdStop();
  void cmdRightForward();
  void cmdLeftForward();
  void cmdRightBack();
  void cmdLeftBack();
  void cmdAllForward();
  void cmdAllBack();
  void cmdStopAll();
  void cmdStopLeft();
  void cmdStopRight();
  void cmdPwm1();
  void cmdPwm2();
  void cmdPwm3();
  void cmdPwm4();
  void cmdPwm5();
};

extern CmdExecutor cmdExecutor;

#endif /* CMDEXECUTOR_H_ */

CmdExecutor.cpp

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include "USART.h"
#include "CmdExecutor.h"

CmdExecutor cmdExecutor;

CmdExecutor::CmdExecutor() {
}

void CmdExecutor::execute(uint8_t cmd) {
  switch (cmd) {
  case STOP:
    cmdStop();
    break;

  case START:
    cmdStart();
    break;

  case RIGHT_FORWARD:
    cmdRightForward();
    break;

  case LEFT_FORWARD:
    cmdLeftForward();
    break;

  case RIGHT_BACK:
    cmdRightBack();
    break;

  case LEFT_BACK:
    cmdLeftBack();
    break;

  case ALL_FORWARD:
    cmdAllForward();
    break;

  case ALL_BACK:
    cmdAllBack();
    break;

  case LEFT_STOP:
    cmdStopLeft();
    break;

  case RIGHT_STOP:
    cmdStopRight();
    break;

  case PWM1:
    cmdPwm1();
    break;

  case PWM2:
    cmdPwm2();
    break;

  case PWM3:
    cmdPwm3();
    break;

  case PWM4:
    cmdPwm4();
    break;

  case PWM5:
    cmdPwm5();
    break;

  case STOP_ALL:
    cmdStopAll();
    break;

  default:
    break;
  }
}

void CmdExecutor::cmdStart() {
  LEFT_DDR_FORWARD |= 1 << LEFT_PIN_FORWARD;
  RIGTH_DDR_FORWARD |= 1 << RIGTH_PIN_FORWARD;
  LEFT_DDR_BACK |= 1 << LEFT_PIN_BACK;
  RIGTH_DDR_BACK |= 1 << RIGTH_PIN_BACK;
  LEFT_PWM_DDR |= 1 << LEFT_PWM_PIN;
  RIGTH_PWM_DDR |= 1 << RIGTH_PWM_PIN;
  cmdStopAll();
  TCCR1A |= 1 << COM1A1 | 1 << COM1B1 | 1 << WGM11 | 1 << WGM10;
  TCCR1B |= 1 << WGM12 | 1 << CS11 | 1 << CS10;
  cmdPwm3();
  cmdPwm3();
}

void CmdExecutor::cmdStop() {
  cmdStopAll();
  LEFT_PWM_DDR &= ~(1 << LEFT_PWM_PIN);
  RIGTH_PWM_DDR &= ~(1 << RIGTH_PWM_PIN);
  TCCR1A &= ~(1 << COM1A1 | 1 << COM1B1 | 1 << WGM11 | 1 << WGM10);
  TCCR1B &= ~(1 << WGM12 | 1 << CS11 | 1 << CS10);
}

void CmdExecutor::cmdRightForward() {
  cmdStopRight();
  RIGTH_PORT_FORWARD |= (1 << RIGTH_PIN_FORWARD);
}

void CmdExecutor::cmdLeftForward() {
  cmdStopLeft();
  LEFT_PORT_FORWARD |= (1 << LEFT_PIN_FORWARD);
}

void CmdExecutor::cmdRightBack() {
  cmdStopRight();
  RIGTH_PORT_BACK |= (1 << RIGTH_PIN_BACK);
}

void CmdExecutor::cmdLeftBack() {
  cmdStopLeft();
  LEFT_PORT_BACK |= (1 << LEFT_PIN_BACK);
}

void CmdExecutor::cmdAllForward() {
  cmdStopAll();
  LEFT_PORT_FORWARD |= (1 << LEFT_PIN_FORWARD);
  RIGTH_PORT_FORWARD |= (1 << RIGTH_PIN_FORWARD);
}

void CmdExecutor::cmdAllBack() {
  cmdStopAll();
  LEFT_PORT_BACK |= (1 << LEFT_PIN_BACK);
  RIGTH_PORT_BACK |= (1 << RIGTH_PIN_BACK);
}

void CmdExecutor::cmdStopAll() {
  LEFT_PORT_FORWARD &= ~(1 << LEFT_PIN_FORWARD);
  RIGTH_PORT_FORWARD &= ~(1 << RIGTH_PIN_FORWARD);
  LEFT_PORT_BACK &= ~(1 << LEFT_PIN_BACK);
  RIGTH_PORT_BACK &= ~(1 << RIGTH_PIN_BACK);
}

void CmdExecutor::cmdPwm1() {
  OCR1A = 204;
  OCR1B = 204;
}

void CmdExecutor::cmdPwm2() {
  OCR1A = 408;
  OCR1B = 408;
}

void CmdExecutor::cmdPwm3() {
  OCR1A = 612;
  OCR1B = 612;
}

void CmdExecutor::cmdPwm4() {
  OCR1A = 816;
  OCR1B = 816;
}

void CmdExecutor::cmdStopLeft() {
  LEFT_PORT_FORWARD &= ~(1 << LEFT_PIN_FORWARD);
  LEFT_PORT_BACK &= ~(1 << LEFT_PIN_BACK);
}

void CmdExecutor::cmdStopRight() {
  RIGTH_PORT_FORWARD &= ~(1 << RIGTH_PIN_FORWARD);
  RIGTH_PORT_BACK &= ~(1 << RIGTH_PIN_BACK);
}

void CmdExecutor::cmdPwm5() {
  OCR1A = 1023;
  OCR1B = 1023;
}

USART.h

#ifndef USART_H_
#define USART_H_

class USART {
private:
  typedef void (*OnReceiveFunction)(uint8_t);

public:
  OnReceiveFunction onReceiveFunction;
  USART();
  void init(uint16_t);
  void setOnReceiveFunction(OnReceiveFunction);

  //  Отправка байта
  void transmitChar(char);

  //  Отправка строки
  void transmitString(char*);

  //  Отправка строки
  void transmitStringLn(char*);

  //  Получение байта
  char receiveChar();
};

extern USART usart;

#endif /* USART_H_ */

USART.cpp

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include "USART.h"

USART usart;

ISR(USART_RX_vect) {
  if (usart.onReceiveFunction) {
    usart.onReceiveFunction(UDR);
  }
}

USART::USART() :
    onReceiveFunction(0) {
}

void USART::init(uint16_t baud) {
  uint16_t ubrr = F_CPU / 16 / baud - 1;
  UBRRH = (unsigned char) (ubrr >> 8);
  UBRRL = (unsigned char) (ubrr);

  //  RXC         - завершение приёма
  //  |TXC        - завершение передачи
  //  ||UDRE      - отсутствие данных для отправки
  //  |||FE       - ошибка кадра
  //  ||||DOR     - ошибка переполнение буфера
  //  |||||PE     - ошибка чётности
  //  ||||||U2X   - Двойная скорость
  //  |||||||MPCM - Многопроцессорный режим
  //  ||||||||
  //  76543210
  UCSRA = 0;

  //  RXCIE       - прерывание при приёме данных
  //  |TXCIE      - прерывание при завершение передачи
  //  ||UDRIE     - прерывание отсутствие данных для отправки
  //  |||RXEN     - разрешение приёма
  //  ||||TXEN    - разрешение передачи
  //  |||||UCSZ2  - UCSZ0:2 размер кадра данных
  //  ||||||RXB8  - 9 бит принятых данных
  //  |||||||TXB8 - 9 бит переданных данных
  //  ||||||||
  //  76543210

  //  разрешен приём и передача данных, прерывание при приёме данных
  UCSRB = 1 << RXEN | 1 << TXEN | 1 << RXCIE;

  //  URSEL        - всегда 1
  //  |UMSEL       - режим: 1-синхронный 0-асинхронный
  //  ||UPM1       - UPM0:  1 чётность
  //  |||UPM0      - UPM0:  1 чётность
  //  ||||USBS     - стоп биты: 0-1, 1-2
  //  |||||UCSZ1   - UCSZ0: 2 размер кадра данных
  //  ||||||UCSZ0  - UCSZ0: 2 размер кадра данных
  //  |||||||UCPOL - в синхронном режиме - тактирование
  //  ||||||||
  //  76543210
  //  8-битовая посылка, 2 стоп бита
  UCSRC = 1 << USBS | 1 << UCSZ0 | 1 << UCSZ1;
}

void USART::setOnReceiveFunction(OnReceiveFunction onReceiveFunction) {
  this->onReceiveFunction = onReceiveFunction;
}

//  Отправка байта
void USART::transmitChar(char c) {
  //  Устанавливается, когда регистр свободен
  while (!( UCSRA & (1 << UDRE))) {
  }
  UDR = c;
}

//  Отправка строки
void USART::transmitString(char str[]) {
  while (*str) {
    transmitChar(*str++);
  }
}

//  Отправка строки
void USART::transmitStringLn(char str[]) {
  transmitString(str);
  transmitChar((char) 13);
  transmitChar((char) 10);
}

//  Получение байта
char USART::receiveChar(void) {
  //  Устанавливается, когда регистр свободен
  while (!(UCSRA & (1 << RXC))) {
  }
  return UDR;
}

Исходники и Java приложение

Проект на C++: ATtiny2313_Car — C++.zip
Проект на Java : ATtiny2313_Car — Java.zip
Java приложение: ATiny2313_Car-1.0.0.jar.zip

Маленькое видео


Купить компоненты на AliExpress

Похожие записи

Комментарии 32

Добавить комментарий для Саша Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *