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.

Настройки по умолчанию. pll_mult - множитель общей частоты

Из документации китайцев я прочитал, что частота, на которой работает 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); }


ссылка на оригинал статьи https://habr.com/ru/articles/727572/