Управляйте сервоприводом с ПК с помощью Atmel SAM4S
Узнайте, как установить связь между ПК и SAM4S с помощью USB-модуля Atmel Software Framework и простых команд ASCII.
Вспомогательная информация
- Введение в разработку проекта с помощью Atmel SAM4S Xplained Pro
- Широтно-импульсная модуляция
- Превратите свою ШИМ в ЦАП
- Низкочастотный фильтр ШИМ-сигнал в аналоговое напряжение
- Модуляция с широтно-импульсной модуляцией с помощью SAM4S Xplained Pro
Требуемое оборудование / программное обеспечение
- Комплект оценки SAM4S Xplained Pro
- PROTO1 Xplained Pro (не обязательно, но очень полезно)
- Студия Atmel
- Servo TowerPro SG92R (или любой эквивалентный серво)
- Настенный трансформатор переменного / постоянного тока, 5 В (или любой другой источник питания 5 В с достаточной пропускной способностью)
- 2 провода-перемычки
- 1 перемычка для подключения проводов
- Realterm (необязательно, но рекомендуется)
Предыдущая статья
Сервоуправление через USB с помощью SAM4S Xplained Pro, часть 1
Стек
В предыдущей статье мы установили наше оборудование и внедрили базовую структуру для управления сервоприводом с использованием сигнала с широтно-импульсной модуляцией. Теперь пришло время сделать этот проект немного более интересным, включив USB-связь между ПК и SAM4S вместе с простым интерфейсом команд ASCII. Первое, что нам нужно сделать, это правильно настроить и инициализировать USB-накопитель. На всякий случай, если вы не знакомы с этой терминологией, «стек» в этом контексте относится к программной структуре, которая упрощает (или, по крайней мере, упрощает) для обычного пользователя выполнение конкретной задачи программирования. Таким образом, USB-накопитель Atmel представляет собой набор программного обеспечения, которое 1) заботится о многочисленных сложных деталях, связанных с USB-связью, и 2) предоставляет различные функции высокого уровня, которые позволяют пользователю реализовать функциональность USB, ничего не зная о конечных точках, типах пакетов, перечисление и т. д.
Atmel Software Framework (ASF) охватывает несколько вариантов USB. Как обсуждалось в предыдущей статье, мы будем использовать модуль CDC (класс устройства связи):

Этот модуль делает удивительно безболезненным создание виртуального соединения COM-порта с ПК. Как только у нас будет виртуальное соединение с COM-портом, мы можем отправлять и получать сообщения USB с помощью не более чем терминальной программы. Я буду использовать Realterm, который является бесплатным, надежным и загруженным полезными функциями. (Если вы видите странное сообщение об ошибке при открытии Realterm, попробуйте использовать «Запуск от имени администратора» в меню правой кнопки мыши.) Если вы предпочитаете другие способы общения с COM-портом, обязательно используйте его. Если у вас нет других средств, но вы не хотите загружать новую программу, вы можете использовать панель управления последовательным портом, включенную в Atmel Studio 7.0 (Tools → Data Visualizer, затем нажмите «Конфигурация» слева и под « Модули "выберите Внешнее соединение → Последовательный порт).
Конфигурация USB
Нам нужно изменить файл conf_usb.h, прежде чем мы сможем инициализировать и использовать стек. В этом файле происходит совсем немного, и я не буду пытаться освещать все. Вместо этого я просто дам вам код и добавлю комментарии Atmel с полезной информацией:
#ifndef _CONF_USB_H_ #define _CONF_USB_H_ #include "compiler.h" //#warning You must refill the following definitions with a correct values /** * USB Device Configuration *) // (USB_CONFIG_ATTR_REMOTE_WAKEUP //@} //! The includes of classes and other headers must be done at the end of this file to avoid compile error #include "udi_cdc_conf.h" #endif // _CONF_USB_H_
Обратите внимание, что я удалил некоторые комментарии и код с комментариями; если вы хотите четко видеть все изменения, вы можете использовать инструмент diff (например, Diffchecker) для сравнения моего файла с тем, который первоначально был создан Atmel Studio при добавлении модуля ASF. На мой взгляд, самая запутанная часть заключается в следующем:
//! Interface callback definition #define UDI_CDC_TX_EMPTY_NOTIFY(port) #define UDI_CDC_SET_CODING_EXT(port, cfg) #define UDI_CDC_SET_DTR_EXT(port, set) #define UDI_CDC_SET_RTS_EXT(port, set) #define UDI_CDC_ENABLE_EXT(port) my_callback_cdc_enable() extern bool my_callback_cdc_enable(void); #define UDI_CDC_DISABLE_EXT(port) my_callback_cdc_disable() extern void my_callback_cdc_disable(void); #define UDI_CDC_RX_NOTIFY(port) my_callback_rx_notify(port) extern void my_callback_rx_notify(uint8_t port); // #define UDI_CDC_TX_EMPTY_NOTIFY(port) my_callback_tx_empty_notify(port) // extern void my_callback_tx_empty_notify(uint8_t port); // #define UDI_CDC_SET_CODING_EXT(port, cfg) my_callback_config(port, cfg) // extern void my_callback_config(uint8_t port, usb_cdc_line_coding_t * cfg); // #define UDI_CDC_SET_DTR_EXT(port, set) my_callback_cdc_set_dtr(port, set) // extern void my_callback_cdc_set_dtr(uint8_t port, bool b_enable); // #define UDI_CDC_SET_RTS_EXT(port, set) my_callback_cdc_set_rts(port, set) // extern void my_callback_cdc_set_rts(uint8_t port, bool b_enable);
Здесь вы видите различные события, связанные с USB, к которым вы можете захотеть подключить функцию обратного вызова. Функция обратного вызова похожа на процедуру обслуживания прерывания, поскольку она вызывается в ответ на значимое событие. Разница заключается в том, что подпрограмма обслуживания прерываний выполняется как прямой результат аппаратного прерывания, тогда как функция обратного вызова вызывается стеком как способ разрешить основному приложению обрабатывать различные события, которые обрабатываются стеком. Как вы можете видеть, мы используем только три из семи событий, перечисленных в приведенном выше отрывке кода. В этом конкретном приложении мне не нужно получать уведомления, когда буфер данных передается на ПК, поэтому я не раскомментировал следующие строки:
// #define UDI_CDC_TX_EMPTY_NOTIFY(port) my_callback_tx_empty_notify(port) // extern void my_callback_tx_empty_notify(uint8_t port);
Однако я хочу получать уведомление 1) когда интерфейс CDC готов к передаче данных, 2) когда интерфейс CDC потерял возможность выполнять передачу данных и 3) когда байты, полученные с ПК, готовы к обработке, Таким образом, я раскомментировал следующие определения препроцессора и прототипы функций:
#define UDI_CDC_ENABLE_EXT(port) my_callback_cdc_enable() extern bool my_callback_cdc_enable(void); #define UDI_CDC_DISABLE_EXT(port) my_callback_cdc_disable() extern void my_callback_cdc_disable(void); #define UDI_CDC_RX_NOTIFY(port) my_callback_rx_notify(port) extern void my_callback_rx_notify(uint8_t port)
Команды ASCII: если не разбиты, не исправляйте
Этот проект дает нам возможность использовать простой, надежный, эффективный (хотя и не совсем сложный) командный интерфейс, который простирается обратно в старые добрые времена RS-232. Мы передадим все данные в виде кодов ASCII-букв и цифр вместе с символами возврата пробела и каретки. Каждая команда имеет три компонента: двухбуквенную строку, которая указывает тип команды, один или несколько аргументов и возврат каретки, чтобы однозначно идентифицировать конец команды. Я говорю «однозначно», потому что все данные, включая числовые данные, кодируются как символы ASCII, и, следовательно, мы знаем, что возврат каретки (соответствующий номеру 13) никогда не появится нигде, кроме как в конце сообщения, даже если мы имеем для отправки номера 13 это будет закодировано как 0x31 (ASCII '1'), за которым следует 0x33 (ASCII '3'). То же самое относится к любым другим символам пробела, которые мы, возможно, захотим использовать, например, пробел, линия или вкладка.
Для этого проекта мы реализуем только два типа команд: «установить позицию» и «переместиться в позицию»; соответствующие командные строки - это SP и MP. Команда SP используется для перемещения сервомотора как можно быстрее в заданное угловое положение; он принимает один аргумент. Команда MP перемещает сервоуправление в заданное угловое положение за определенное количество времени; он принимает два аргумента. Символ пробела используется между командной строкой и первым аргументом и, если применимо, между первым аргументом и вторым аргументом. Последний символ всегда является возвратом каретки. В следующей таблице приведены сведения для каждой команды:
команда | Командная строка | Аргумент 1 | Аргумент 2 |
заданное положение | SP | «L» для крайнего левого, «C» для центра, «R» для крайнего правого или 0-180 для углового положения в градусах | н / |
переместиться в положение | член парламента | 0-180 для углового положения в градусах | 1-9 для времени в секундах |
Например, предположим, что мы хотим сделать следующее: переместите вал на крайнее левое положение как можно быстрее, а затем на 45 ° как можно быстрее, затем на 135 ° как можно быстрее, затем на 0 ° за 2 секунды, затем до 180 ° за 9 секунд. Ниже приведена соответствующая последовательность команд ( r представляет собой байта возврата каретки):
- SP L \ r
- SP 45 \ r
- SP 135 \ r
- MP 0 2 \ r
- MP 180 9 \ r
Прошивка
Вы можете использовать следующую ссылку для загрузки всех исходных файлов и файлов проекта:
Файлы источника и проекта
Здесь очень много кода, поэтому я не могу охватить каждую деталь; вы должны понимать большинство из них с помощью комментариев и описательных идентификаторов. В основном я хочу обсудить USB-код, так как связь с USB является в центре внимания этой статьи.
После того, как вы правильно изменили конфигурационный файл USB («conf_usb.h»), вам нужно три вызова функций, чтобы интерфейс USB был готов к действию:
/*Initialize interrupt vectors and enable interrupts globally. These statements are necessary because the USB stack uses interrupts.*/ irq_initialize_vectors(); cpu_irq_enable(); //make the USB stack active udc_start();
После выполнения любых других задач инициализации и конфигурации вам нужно подождать, пока USB-интерфейс не будет готов к отправке и получению данных. Это можно сделать следующим образом:
/*When a connection is established between the host and the device, the USB stack calls my_callback_cdc_enable(). In this callback, CDC_TRANSFERS_AUTHORIZED is set to "true".*/ while(CDC_TRANSFERS_AUTHORIZED == false);
Важное примечание: флаг CDC_TRANSFERS_AUTHORIZED должен быть объявлен как «изменчивый»:
//must be volatile volatile bool CDC_TRANSFERS_AUTHORIZED = false;
Ключевое слово volatile сообщает компилятору, что эта переменная может быть изменена «неожиданным» способом, то есть в любое время и без какого-либо видимого подключения к соседнему коду. (В этом случае «неожиданная» модификация происходит в функции my_callback_cdc_enable ().) Если вы не сообщите компилятору, что эта переменная является volatile, код сборки, который он генерирует для оператора while, будет терпеть неудачу, потому что он будет основан на неправильном предположении, а именно, что нет возможности для CDC_TRANSFERS_AUTHORIZED изменить, как только выполнение программы входит в оператор while.
Как упоминалось выше, мы используем три обратных вызова USB:
/*This function is called by the stack when the CDC interface is ready for data transfers.*/ bool my_callback_cdc_enable(void) { CDC_TRANSFERS_AUTHORIZED = true; return true; } /*This function is called by the stack when data can no longer be transferred because the USB device was unplugged or reset by the USB host.*/ void my_callback_cdc_disable(void) { CDC_TRANSFERS_AUTHORIZED = false; } /*This function is called by the stack when received data is available.*/ void my_callback_rx_notify(uint8_t port) { iram_size_t Number_of_Rcvd_Bytes; Number_of_Rcvd_Bytes = udi_cdc_get_nb_received_data(); udi_cdc_read_buf(USB_Rcv_Buffer, Number_of_Rcvd_Bytes); //if the packet ends with a carriage return, we assume it is valid if (USB_Rcv_Buffer(Number_of_Rcvd_Bytes - 1) == ASCII_CR) { PROCESS_RCVD_PACKET = true; } }
На самом деле нам действительно не нужен my_callback_cdc_disable (), потому что это приложение отвечает только командам хоста, и можно с уверенностью предположить, что интерфейс USB активен, если мы только что получили сообщение через USB. Если вы разрабатываете проект USB, в котором микроконтроллер отправляет сообщения USB без получения чего-либо с хоста, вы можете подтвердить, что соединение USB по-прежнему активно, установив флаг CDC_TRANSFERS_AUTHORIZED.
После выполнения действия, указанного командой, микропрограмма отвечает следующим образом:
//send the response indicating "command accepted" udi_cdc_putc('1'); udi_cdc_putc(ASCII_CR);
Вот важная деталь. Как вы можете видеть, мы отправляем двухбайтовое сообщение, совершая два вызова функции udi_cdc_putc (). Интерфейс ASF CDC включает функцию отправки нескольких байтов, а именно: udi_cdc_write_buf () - зачем использовать udi_cdc_putc ()? Просто: Насколько я могу судить, udi_cdc_write_buf () не работает. Фактически, это вызвало сбой моей прошивки, и это даже помешало программе терминала, запущенной на ПК. Поиск в Google показывает, что может быть известная ошибка с udi_cdc_write_buf (), хотя, вероятно, проблема ограничена конкретными ситуациями (иначе Atmel бы исправил ее сейчас, мне кажется). Поэтому, если вы попробуете udi_cdc_write_buf () и заметите странное поведение, перейдите на страницу udi_cdc_putc ().
Результаты
Вы можете проверить Диспетчер устройств, чтобы определить номер COM-порта, который будет использоваться при подключении к SAM4S:

Следующий скриншот дает вам пример использования Realterm для отправки серво-команд:

И вот видео, в котором показано действие сервопривода, генерируемое приведенной выше примерной последовательностью команд, повторяется здесь для вашего удобства:
- SP L \ r
- SP 45 \ r
- SP 135 \ r
- MP 0 2 \ r
- MP 180 9 \ r
Попробуйте этот проект сами! Получить спецификацию.