Artery AT32F403A. Работа с USB
Итак, продолжаю Вас знакомить с микроконтроллером (МК) AT32F403A. Первая статья была посвящена знакомству с таймерами и миганием светодиодов. Теперь пора продолжить изучение интерфейсов данного МК.
Пожалуй не ошибусь, если скажу, что работа с USB является пожалуй одной из основной. Без этого практически никуда.
Дополнительно нам понадобится приложение терминал для Windows. Я использую COM port Terminal v.1.5 Sviridov. Скачать можно по ссылке.
Немного отвлекусь, и скажу с чем мне пришлось столкнуться при разборе примеров работы. Гуру программирования МК посмеются, но я в этом деле новичок, мне можно.
Итак, я запустил пример работы с USB и всё работает. Запустил пример работы с CAN и всё работает. Копирую код с примера CAN в USB — CAN не работает. Копирую код с примера USB в CAN — USB не работает. Чудеса (для меня). Так же по прошлой работе с STM я помнил про настройку тактирования (поправьте если терминология неверная). Пока с ней не столкнулся. Примеры же работают.
Начал пошагово смотреть все строчки в обоих примерах. И нашёл функцию void system_clock_config(void).
CAN работает при частоте: crm_pll_config(CRM_PLL_SOURCE_HEXT_DIV, CRM_PLL_MULT_60, CRM_PLL_OUTPUT_RANGE_GT72MHZ);
USB работает при частоте: crm_pll_config(CRM_PLL_SOURCE_HEXT_DIV, CRM_PLL_MULT_48, CRM_PLL_OUTPUT_RANGE_GT72MHZ);
Значит пора искать, где это у китайцев настраивается. Есть отдельное приложение AT32_New_Clock_Configuration_V3.0.05. Оно есть на сайте artery, есть в архиве первого поста.
Запускаем приложение. Выбираем Project — New — AT32F403A.
Из документации китайцев я прочитал, что частота, на которой работает USB, должна быть отделена от общей. И работа USB должна быть на частоте 48Мгц. Это, кстати, следует из названия функции в примере. Чуть ниже увидите.
Нажимаем Generate Code, нам предлагают выбрать папку, куда сохранить файлы. Создаём папку CLK AT32F403A и нажимаем сохранить. В папке появляются две подпапки src и inc. Всё, можно приступать к изучению.
Находим пример работы с USB. Это папка AT32F403A_407_Firmware_Library_V2.1.4\project\at_start_f403a\examples\usb_device\virtual_comport
Копируем полученные ранее файлы: из папки CLK AT32F403A\src - только один файл at32f403a_407_clock в папку virtual_comport\src с заменой. Из папки CLK AT32F403A\inc - оба файла, at32f403a_407_clock.h и at32f403a_407_conf.h в папку virtual_comport\inc с заменой.
Запускаем пример из папки virtual_comport\mdk_v5. Компилируем проект F7. Открываем main.c. И не пугаемся, код уже намного больше, чем в прошлом примере. Находим main функцию. Всю функцию приводить не буду, только код инициализации.
/* config nvic priority group */
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
system_clock_config();
// at32_board_init(); - убираем
/* usart gpio config */
// usart_gpio_config(); - нам не требуется, убираем
/* hardware usart config: usart2 */
// usb_usart_config(linecoding); - нам не требуется, убираем
/* select usb 48m clcok source */ - Как я говорил ранее, 48Мгц частота работы USB
// usb_clock48m_select(USB_CLK_HEXT); - нам не требуется, убираем, мы уже настроили выше частоту
/* enable usb clock */
crm_periph_clock_enable(CRM_USB_PERIPH_CLOCK, TRUE);
/* enable usb interrupt */
nvic_irq_enable(USBFS_L_CAN1_RX0_IRQn, 0, 0);
/* usb core init */
usbd_core_init(&usb_core_dev, USB, &cdc_class_handler, &cdc_desc_handler, 0);
/* enable usb pull-up */
usbd_connect(&usb_core_dev);
USB у нас подцеплен на PA11 и PA12.
Вроде всё, компилируем F7 и запускаем режим Debug и нажимаем F5. (Без режима Debug? простым F8 не заработало. Может у меня что-то не так). Если всё удачно, с компьютера услышите звук подключенного usb устройства. Заходим в терминал и нажимаем Поиск.
Добавим таймеры и LED в наш проект, из первой статьи. И делаем два таймера, на 500 мс и 1 секунду (1999 и 3999. Эти числа рассчитаны на основе системной частоты, которую мы поменяли выше, поэтому отличается от первого поста).
// таймеры
/* enable tmr1 tmr2 clock */
crm_periph_clock_enable(CRM_TMR1_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_TMR2_PERIPH_CLOCK, TRUE);
/* tmr1 tmr2 configuration */
/* time base configuration */
/* systemclock/24000/10000 = 1hz */
tmr_base_init(TMR1, 1999, (crm_clocks_freq_struct.ahb_freq / 10000) - 1);
tmr_cnt_dir_set(TMR1, TMR_COUNT_UP);
tmr_base_init(TMR2, 3999, (crm_clocks_freq_struct.ahb_freq / 10000) - 1);
tmr_cnt_dir_set(TMR2, TMR_COUNT_UP);
/* overflow interrupt enable */
tmr_interrupt_enable(TMR1, TMR_OVF_INT, TRUE);
tmr_interrupt_enable(TMR2, TMR_OVF_INT, TRUE);
/* tmr1 overflow interrupt nvic init */
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(TMR1_OVF_TMR10_IRQn, 0, 0);
/* enable tmr1 tmr2 */
tmr_counter_enable(TMR1, TRUE);
tmr_counter_enable(TMR2, TRUE);
// LED
init_led();
at32_led_off(LED3); // гасим зелёный
at32_led_on(LED2); // зажигаем красный, типа устройство включено
Сделаем индикацию USB соединения. Объявляем переменную uint8_t usb_ready = 0. Меняем код прерывания USB:
void USBFS_L_CAN1_RX0_IRQHandler(void)
{
usbd_irq_handler(&usb_core_dev);
usb_ready = 1;
}
В коде таймера пишем:
if(tmr_flag_get(TMR1, TMR_OVF_FLAG) != RESET) {
if (usb_ready == 1) {
at32_led_on(LED2);
} else {
at32_led_toggle(LED2);
}
usb_ready = 0;
tmr_flag_clear(TMR1, TMR_OVF_FLAG);
}
На выходе получаем: есть связь, красный диод горит, выдёргиваем usb из разъёма, красный диод начинает мигать с частотой 500 мс.
Теперь самое интересное, обмен информацией. Сделаем отправку в терминал серийного номера процессора AT32.
Для этого добавим переменные:
uint32_t cortex_id, cortex_id_2, cortex_id_3;
uint8_t ButtonTx_Buffer_usb[10] = {0x0A, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
Так же добавим для новых функций строку в начале кода #include <string.h>.
В коде таймера пишем:
if(tmr_flag_get(TMR2, TMR_OVF_FLAG) != RESET) {
cortex_id = *(uint32_t *)0x1FFFF7E8; // получаем 1 часть серийного номера МК
cortex_id_2 = *(uint32_t *)0x1FFFF7EC; // получаем 2 часть серийного номера МК
cortex_id_3 = *(uint32_t *)0x1FFFF7F0; // получаем 3 часть серийного номера МК
memcpy(&ButtonTx_Buffer_usb[2], (uint32_t*)&cortex_id_3, 4);
memcpy(&ButtonTx_Buffer_usb[6], (uint32_t*)&cortex_id_2, 4);
usb_vcp_send_data(&usb_core_dev, ButtonTx_Buffer_usb, 0x000A); // эта функция отвечает за отправку данных в usb
// 0x000A - это длина пакета, в моём случае 10 байт
at32_led_toggle(LED3); // весело мигаем зелёным диодом
tmr_flag_clear(TMR2, TMR_OVF_FLAG);
}
Обратите внимание, я беру только 2 и 3 часть серийного номера. Я проверял, у МК меняется только 3 часть номера. То есть особо смысла использовать 1 и 2 часть номера нет.
Отлично. Теперь научимся принимать данные с терминала. Открываем функцию main и смотрим что у нас в теле while. Всё оттуда удаляем, и оставляем только следующий код:
while(1) {
data_len = usb_vcp_get_rxdata(&usb_core_dev, usb_buffer);
if(data_len > 0) {
work_with_mmc();
}
}
Здесь просто, если пришёл пакет, data_len становится отличной от 0 и мы переходим в функцию work_with_mmc(). Напишем теперь эту функцию.
Обявляем переменные:
uint8_t USB_CRC = 0;
uint8_t receivedUSBData[13] = {0};
// Для примера я шлю из терминала строку HEX $AA$E0$07$08$03$19$02$AF$00$00$00$00$99
void work_with_mmc(void) {
uint8_t i2;
memcpy(receivedUSBData, usb_buffer, data_len); // копируем принятый пакет из usb_buffer в receivedUSBData в количестве data_len
if (data_len == 13) { // проверяем пакет на длину
if (receivedUSBData[0] == 0xAA) { // если нулевой байт равен AA, то продолжаем
USB_CRC = 0;
for (i2 = 0; i2 < 12; i2++) {
USB_CRC = USB_CRC + receivedUSBData[i2];
}
if (0xFF-USB_CRC == receivedUSBData[12]) { // проверяем CRC
// отправляем полученный пакет обратно в USB
memcpy(&ButtonTx_Buffer_usb[0], (uint8_t*)&receivedUSBData[3], 10);
usb_vcp_send_data(&usb_core_dev, ButtonTx_Buffer_usb, 0x000A);
}
}
}
}
Как видите, всё достаточно просто. Меняя нулевой байт пакета, мы можем сделать много управляющих пакетов для работы МК. Я в частности меняю из магнитолы режимы работы прошивки, под разные настройки.
В общем-то и всё. В заключение я хочу ещё поделиться наблюдением. Когда в режиме Debug просматривал переменные, некоторые значения пишутся не слева направо, а справа налево. Хотя дальнейшие вычисления с ними верны. Например при анализе кода STM32 значение переменной показывает 8B08, то при этом же коде в AT будет отображаться 088B. С чем это связано, не знаю.
Так же я не смог разобраться, как убрать из терминала эхо TX. Если кто знает, подскажите. В целом работе не мешает.
Полный код main.c
#include "at32f403a_407_board.h"
#include "at32f403a_407_clock.h"
#include "usbd_core.h"
#include "cdc_class.h"
#include "cdc_desc.h"
#include "usbd_int.h"
#include <string.h>
/** @addtogroup AT32F403A_periph_examples
* @{
*/
/** @addtogroup 403A_USB_device_vcp_loopback USB_device_vcp_loopback
* @{
*/
uint8_t USB_CRC = 0;
uint8_t receivedUSBData[13] = {0};
uint16_t data_len;
uint32_t timeout;
uint8_t send_zero_packet = 0;
uint32_t cortex_id, cortex_id_2, cortex_id_3;
uint8_t ButtonTx_Buffer_usb[10] = {0x0A, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
crm_clocks_freq_type crm_clocks_freq_struct = {0};
usbd_core_type usb_core_dev;
uint8_t usb_buffer[256];
uint8_t usb_ready = 0;
/* usart global struct define */
extern linecoding_type linecoding;
void usb_usart_config(linecoding_type linecoding);
void usart_gpio_config(void);
#define usart_buffer_size 2048
uint8_t usart_rx_buffer[usart_buffer_size];
uint16_t hw_usart_rx_index = 0;
uint16_t hw_usart_read_index = 0;
uint16_t usart_rx_data_len = 0;
uint16_t ov_cnt = 0;
void usart_send_data(uint8_t *send_data, uint16_t len);
uint16_t usart_receive_data(void);
/**
* @brief usb 48M clock select
* @param clk_s:USB_CLK_HICK, USB_CLK_HEXT
* @retval none
*/
void TMR1_OVF_TMR10_IRQHandler(void) {
if(tmr_flag_get(TMR1, TMR_OVF_FLAG) != RESET) {
if (usb_ready == 1) {
at32_led_on(LED2);
} else {
at32_led_toggle(LED2);
}
usb_ready = 0;
tmr_flag_clear(TMR1, TMR_OVF_FLAG);
}
if(tmr_flag_get(TMR2, TMR_OVF_FLAG) != RESET) {
cortex_id = *(uint32_t *)0x1FFFF7E8;
cortex_id_2 = *(uint32_t *)0x1FFFF7EC;
cortex_id_3 = *(uint32_t *)0x1FFFF7F0;
memcpy(&ButtonTx_Buffer_usb[2], (uint32_t*)&cortex_id_3, 4);
memcpy(&ButtonTx_Buffer_usb[6], (uint32_t*)&cortex_id_2, 4);
usb_vcp_send_data(&usb_core_dev, ButtonTx_Buffer_usb, 0x000A);
/* add user code... */
at32_led_toggle(LED3);
tmr_flag_clear(TMR2, TMR_OVF_FLAG);
}
}
void init_led(void) {
gpio_init_type GPIO_Init;
crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE); // - очень важно не пропустить
GPIO_Init.gpio_mode = GPIO_MODE_OUTPUT;
GPIO_Init.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
GPIO_Init.gpio_pull = GPIO_PULL_NONE;
GPIO_Init.gpio_pins = GPIO_PINS_1;
GPIO_Init.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init(GPIOC, &GPIO_Init);
GPIO_Init.gpio_mode = GPIO_MODE_OUTPUT;
GPIO_Init.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
GPIO_Init.gpio_pull = GPIO_PULL_NONE;
GPIO_Init.gpio_pins = GPIO_PINS_2;
GPIO_Init.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init(GPIOC, &GPIO_Init);
}
void work_with_mmc(void) {
uint8_t i2;
memcpy(receivedUSBData, usb_buffer, data_len); // input data
if (data_len == 13) {
if (receivedUSBData[0] == 0xAA) {
USB_CRC = 0;
for (i2 = 0; i2 < 12; i2++) {
USB_CRC = USB_CRC + receivedUSBData[i2];
}
if (0xFF-USB_CRC == receivedUSBData[12]) {
memcpy(&ButtonTx_Buffer_usb[0], (uint8_t*)&receivedUSBData[3], 10);
usb_vcp_send_data(&usb_core_dev, ButtonTx_Buffer_usb, 0x000A);
}
}
}
}
/**
* @brief main function.
* @param none
* @retval none
*/
int main(void) {
/* config nvic priority group */
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
system_clock_config();
/* enable usb clock */
crm_periph_clock_enable(CRM_USB_PERIPH_CLOCK, TRUE);
/* enable usb interrupt */
nvic_irq_enable(USBFS_L_CAN1_RX0_IRQn, 0, 0);
/* usb core init */
usbd_core_init(&usb_core_dev, USB, &cdc_class_handler, &cdc_desc_handler, 0);
/* enable usb pull-up */
usbd_connect(&usb_core_dev);
// таймеры
/* enable tmr1 tmr2 clock */
crm_periph_clock_enable(CRM_TMR1_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_TMR2_PERIPH_CLOCK, TRUE);
/* tmr1 tmr2 configuration */
/* time base configuration */
/* systemclock/24000/10000 = 1hz */
tmr_base_init(TMR1, 1999, (crm_clocks_freq_struct.ahb_freq / 10000) - 1);
tmr_cnt_dir_set(TMR1, TMR_COUNT_UP);
tmr_base_init(TMR2, 3999, (crm_clocks_freq_struct.ahb_freq / 10000) - 1);
tmr_cnt_dir_set(TMR2, TMR_COUNT_UP);
/* overflow interrupt enable */
tmr_interrupt_enable(TMR1, TMR_OVF_INT, TRUE);
tmr_interrupt_enable(TMR2, TMR_OVF_INT, TRUE);
/* tmr1 overflow interrupt nvic init */
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(TMR1_OVF_TMR10_IRQn, 0, 0);
/* enable tmr1 tmr2 */
tmr_counter_enable(TMR1, TRUE);
tmr_counter_enable(TMR2, TRUE);
// LED
init_led();
at32_led_off(LED3);
at32_led_on(LED2);
while(1) {
data_len = usb_vcp_get_rxdata(&usb_core_dev, usb_buffer);
if(data_len > 0) {
work_with_mmc();
}
}
}
/**
* @brief this function handles usb interrupt.
* @param none
* @retval none
*/
void USBFS_L_CAN1_RX0_IRQHandler(void) {
usbd_irq_handler(&usb_core_dev);
usb_ready = 1;
}
/**
* @brief usb delay millisecond function.
* @param ms: number of millisecond delay
* @retval none
*/
void usb_delay_ms(uint32_t ms) {
/* user can define self delay function */
delay_ms(ms);
}
/**
* @brief usb delay microsecond function.
* @param us: number of microsecond delay
* @retval none
*/
void usb_delay_us(uint32_t us) {
delay_us(us);
}