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