Artery AT32F403A. Знакомство новичка

от автора

Добрый день.

Пишу первый раз для аудитории, поэтому представлюсь. Зовут меня Артём, я разработчик приложения VestaXR для автомобилей LADA. Это приложение является бортовым компьютером для машин с магнитолами (ГУ) на базе Андроид, которое взаимодействует с машиной через электронную плату (канбас), и отправляет сигналы с каншины в ГУ и обратно, позволяя получать все данные и управлять некоторыми настройками автомобиля. Изначально канбас строился на базе STM32F405. Но настали времена, когда этот микроконтроллер стал дорогой и попал под санкции.

И вот волею судеб мне пришлось на время переквалифицироваться из программиста приложения для Андроид в программиста микроконтроллера (МК). Работать пришлось с МК, информации о котором очень мало, можно сказать даже что её толком нет. Всё что у меня было, это сайт производителя и один пример с led на github. Речь идёт о МК Artery AT32F403A.

По спецификации это очень навороченный МК

Опыт работы с STM32F405 у меня уже был, я правил уже готовую прошивку. Поэтому знаком с оболочкой Keil и принципами работы. Но я понимал, что здесь всё другое. Кроме конечно языка программирования.

Итак, две недели мозгового штурма, поиска информации и тупого копирования кода, принесли свои результаты. Я получил действующий канбас со всеми функциями, которые у него должны быть. работа с USB, CAN и USART. И я хочу с вами поделиться своим опытом, надеюсь кому-то это сэкономит время. Всё по порядку.

Моя первая статья будет о самом простом. Таймеры и LED. Так сказать «hello world»

Знакомимся с документами, выложенных на сайте производителя https://www.arterytek.com/en/product/AT32F403A.jsp

Для работы нам понадобится скачать следующие файлы (архив скачанных файлов в конце статьи):

  • AT32F403A_407_Firmware_Library_V2.1.4 — примеры и основные шаблоны

  • Keil5_AT32MCU_AddOn_V2.1.9 — файл данных для Keil и других средств разработки

  • AT_Link_20221221 — драйвера для программатора

Далее тестовая плата (у меня готовый канбас, с перепаянным МК. Стоял STM32F405, поставили AT32F403A. Разводку платы не пришлось менять. Жирный плюс!)

CanbusXR

CanbusXR

И наконец программатор AT-Link+

Итак, запускаем Keil. Нажимаем Pack installer. Далее Import Packs, находим расположение папки Keil5_AT32MCU_AddOn_V2.1.9. Выбираем наш МК и нажимаем «Открыть». итоге получаем такую картинку:

Когда справа у вас появится ваш МК, всё готово

Когда справа у вас появится ваш МК, всё готово

Открываем папку с примерами AT32F403A_407_Firmware_Library_V2.1.4\project\at_start_f403a\examples\tmr (это примеры таймеров). Находим папку timer_base. Как следует из названия, это базовый пример работы таймера. Открываем проект из папки mdk_v5. Всё стандартно и знакомо, не правда ли?

Надо сказать спасибо, все примеры лежат по полочкам. Но осторожно, эти примеры для тестовой доски от Artery. В моём случае у меня уже есть плата.

Только-только открыли проект

Только-только открыли проект

Что же у нас есть в файле main.c:

  • void clkout_config — функция настройки внешнего генератора частоты

  • void TMR1_OVF_TMR10_IRQHandler — функция обработки таймера

  • main — главное тело программы, которое состоит:

  1. system_clock_config() — настройка тактирования всей платы. Не трогаем! К этому мы вернемся ещё в следующих постах

  2. at32_board_init(); — настройка отладочной платы. Нам она не нужна, поэтому закомментируем

  3. /* get system clock */
    crm_clocks_freq_get(&crm_clocks_freq_struct); — как следует из комментариев, настройка системных часов

  4. /* turn led2/led3/led4 on */
    at32_led_on(LED2);
    at32_led_on(LED3);
    at32_led_on(LED4); — включение led на плате. У нас их нет, закомментируем всё

  5. /* enable tmr1 clock */
    crm_periph_clock_enable(CRM_TMR1_PERIPH_CLOCK, TRUE);

    /* tmr1 configuration / / time base configuration / / systemclock/24000/10000 = 1hz */
    tmr_base_init(TMR1, 9999, (crm_clocks_freq_struct.ahb_freq / 10000) — 1);
    tmr_cnt_dir_set(TMR1, TMR_COUNT_UP);

    /* overflow interrupt enable */
    tmr_interrupt_enable(TMR1, 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 */
    tmr_counter_enable(TMR1, TRUE); — здесь собственно и объявляется работа таймера

  6. clkout_config() — настройка внешнего генератора частоты

Находим строчку tmr_base_init(TMR1, 9999, (crm_clocks_freq_struct.ahb_freq / 10000) — 1). Здесь мы задаём время работы таймера.

Формула такая: время_сек * 10000 — 1. То есть 9999 это 1 секунда, 999 это 100 мс, 29999 — 3 секунды.

Обращаем внимание на надпись TMR1. Нажимаем F7, проект компилируется и нам теперь доступны переходы по телу проекта. Переходим по TMR1 и видим, что у нас можно сделать 14 таймеров

Прекрасно, не правда ли?

Прекрасно, не правда ли?

Итак, добавляем в теле главной функции main строчки с TMR2.

  /* 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, 9999, (crm_clocks_freq_struct.ahb_freq / 10000) - 1);   tmr_cnt_dir_set(TMR1, TMR_COUNT_UP);   tmr_base_init(TMR2, 29999, (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);

Строчка 19 — это объявление главной функции работы с таймерами. Она не меняется. Ищем эту функцию и тоже исправляем для TMR2:

void TMR1_OVF_TMR10_IRQHandler(void) {   if(tmr_flag_get(TMR1, TMR_OVF_FLAG) != RESET)   {     /* add user code... */     //at32_led_toggle(LED3);     tmr_flag_clear(TMR1, TMR_OVF_FLAG);   }   if(tmr_flag_get(TMR2, TMR_OVF_FLAG) != RESET)   {     /* add user code... */     //at32_led_toggle(LED3);     tmr_flag_clear(TMR2, TMR_OVF_FLAG);   } }

У нас получается что при срабатывании таймера флаг переходит в !reset, с выполняется код в скобках для каждого таймера отдельно.

Теперь что касается внешнего генератора, а так же объявления в коде пинов МК.

У Artery объявление пинов обозначается gpio_init_struct.gpio_pins = GPIO_PINS_8, без указания A, B, C, D, F, E. (зависит от количество ножек МК). Они указываются в двух строчках:

/* enable gpio port clock */   crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE); // - это его регистр, А    /* set default parameter */   gpio_default_para_init(&gpio_init_struct);    /* clkout gpio init */   gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;   gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;   gpio_init_struct.gpio_mode = GPIO_MODE_MUX;   gpio_init_struct.gpio_pins = GPIO_PINS_14; // - это пин МК   gpio_init_struct.gpio_pull = GPIO_PULL_NONE;   gpio_init(GPIOA, &gpio_init_struct); // - это его регистр, А

То есть у меня на плате внешний генератор подцеплен на PA14. Если у вас на другой ноге, то и меняете в трёх местах: GPIOA, GPIOB, GPIOC и так далее.

С кодом разобрались, теперь надо настроить программатор для режима Debug.

переходим в Options for Target, и выбираем CMSIS-DAP. Нажимаем далее Settings. Выбираем программатор.

Нажимаем F7, компилируем, и нажимаем меню Debug — start/stop debug session, и через 5 секунд снова нажимаем F5 для запуска.

На плате тишина. Давайте проверим что таймеры работают. Поставим breakpoint на строчку первого таймера

Как мы видим, выполнение остановилось, значит и таймеры работают. Отлично! Нажмите Breakpoint на TMR2 и увидите, что он сработает через 3 секунды

Как мы видим, выполнение остановилось, значит и таймеры работают. Отлично! Нажмите Breakpoint на TMR2 и увидите, что он сработает через 3 секунды

Выйдем из режима debug и добавим функцию инициализации led и впишем её запуск в main

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); }

LED у меня подключены на PC1 и PC2.

Для их включения или выключения есть уже готовые функции

    at32_led_off(LED3);     at32_led_on(LED2);

Нажимаем F12 на LED3 и переходим в код настройки обозначений

У меня 2 led, поэтому LED_NUM = 2

У меня 2 led, поэтому LED_NUM = 2

Нажимаем F12 на at32_led_off в теле main и переходим на эту функцию, прокручиваем код вверх и меняем (создаём) текcт объявления переменных LED, убираем лишнее

Пишем вызов функций включения led в теле main:

    at32_led_on(LED3);     at32_led_on(LED2);

Нажимаем F7, далее Debug и у нас загораются оба led. УРА! Поздравляю.

Теперь модифицируем функцию обработки таймеров:

void TMR1_OVF_TMR10_IRQHandler(void) {     if(tmr_flag_get(TMR1, TMR_OVF_FLAG) != RESET) {         /* add user code... */         at32_led_toggle(LED3);         tmr_flag_clear(TMR1, TMR_OVF_FLAG);     }     if(tmr_flag_get(TMR2, TMR_OVF_FLAG) != RESET) {         /* add user code... */         at32_led_toggle(LED2);         tmr_flag_clear(TMR2, TMR_OVF_FLAG);     } }

И снова запускаем. Теперь led у нас перемигиваются. Один с частотой 1 секунда, второй с частотой 3 секунды.

Полный код main.с выглядит так:

#include "at32f403a_407_board.h" #include "at32f403a_407_clock.h"  /** @addtogroup AT32F403A_periph_examples   * @{   */  /** @addtogroup 403A_TMR_timer_base TMR_timer_base   * @{   */  crm_clocks_freq_type crm_clocks_freq_struct = {0};  void clkout_config(void) {     gpio_init_type gpio_init_struct;      /* enable gpio port clock */     crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);      /* set default parameter */     gpio_default_para_init(&gpio_init_struct);      /* clkout gpio init */     gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;     gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;     gpio_init_struct.gpio_mode = GPIO_MODE_MUX;     gpio_init_struct.gpio_pins = GPIO_PINS_14;     gpio_init_struct.gpio_pull = GPIO_PULL_NONE;     gpio_init(GPIOA, &gpio_init_struct);      /* config clkout division */     crm_clkout_div_set(CRM_CLKOUT_DIV_1);      /* config clkout clock */     crm_clock_out_set(CRM_CLKOUT_PLL_DIV_4); }  /**   * @brief  this function handles timer1 overflow handler.   * @param  none   * @retval none   */ void TMR1_OVF_TMR10_IRQHandler(void) {     if(tmr_flag_get(TMR1, TMR_OVF_FLAG) != RESET) {         /* add user code... */         at32_led_toggle(LED3);         tmr_flag_clear(TMR1, TMR_OVF_FLAG);     }     if(tmr_flag_get(TMR2, TMR_OVF_FLAG) != RESET) {         /* add user code... */         at32_led_toggle(LED2);         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); }  /**   * @brief  main function.   * @param  none   * @retval none   */ int main(void) {     system_clock_config();      //at32_board_init();      /* get system clock */     crm_clocks_freq_get(&crm_clocks_freq_struct);      /* turn led2/led3/led4 on */     //at32_led_on(LED2);     //at32_led_on(LED3);     //at32_led_on(LED4);      /* 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, 9999, (crm_clocks_freq_struct.ahb_freq / 10000) - 1);     tmr_cnt_dir_set(TMR1, TMR_COUNT_UP);     tmr_base_init(TMR2, 29999, (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);     clkout_config();      // enable led  init_led();     at32_led_on(LED3);     at32_led_on(LED2);       while(1) {     } }

Ещё один плюс, это очень много комментариев по всем функциям, что облегчает задачу.

В принципе на этом первоначальное знакомство можно считать законченным.

Если вам понравилось, и полезно, дайте обратную связь. И я продолжу статьи про USB, CAN и USART.

Скачать архив с нужными файлами можно по ссылке.


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


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *