Итак, продолжаю Вас знакомить с микроконтроллером (МК) 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); }
