Добрый день, я продолжаю небольшой курс по ознакомлению с микроконтроллером (МК) Artery AT32F403A. В прошлый раз мы изучили:

  1. Знакомство с таймерами и LED

  2. Работа с USB

Сегодня я познакомлю Вас с работой МК с CAN шиной. В автомобиле, да и не только, без неё никуда. Пример мы сделаем на основе прошлого примера работы с USB. То есть соединим пример работы с USB и с CAN. Дополнительно нам понадобится канхакер, и приложение для работы с ним, например CarBUSAnalyzer.

Собрал стенд

Открываем в Keil наш прошлый проект с USB: AT32F403A_407_Firmware_Library_V2.1.4\project\at_start_f403a\examples\usb_device\virtual_comport\mdk_v5

Параллельно открываем проект AT32F403A_407_Firmware_Library_V2.1.4\project\at_start_f403a\examples\can\communication_mode\mdk_v5. Из него мы будем копировать нужные нам участки кода. Копируем из этого примера следующие функции целиком в пример virtual_comport:

  • static void can_gpio_config(void)

  • static void can_configuration(void)

  • static void can_transmit_data(void)

  • void CAN1_SE_IRQHandler(void)

Но это не все. Из тела main копируем две строчки в main примера virtual_comport, в конец инициализации, перед while(1)

  can_gpio_config();
  can_configuration();

Внимание. Функция void USBFS_L_CAN1_RX0_IRQHandler (void). Она есть в обоих примерах. Поэтому нам нужно тело функции скопировать и соединить с нашим первым примером. Должно получится вот так:

void USBFS_L_CAN1_RX0_IRQHandler(void) {
  can_rx_message_type rx_message_struct;
  if(can_flag_get(CAN1,CAN_RF0MN_FLAG) != RESET)
  {
    can_message_receive(CAN1, CAN_RX_FIFO0, &rx_message_struct);
    if(rx_message_struct.standard_id == 0x400)
      at32_led_toggle(LED2);
    else
      at32_led_toggle(LED3);
  }

  usbd_irq_handler(&usb_core_dev);
  usb_ready = 1;
}

Всё, с копированием закончили. Частоты работы мы с вами выставили на прошлом примере работы с USB. Теперь нам нужно настроить в коде CAN интерфейс. Нам понадобится приложение Artery_CAN_BitRate_Configuration_V1.0.0. Оно есть в архиве первой статьи или на сайте artery. Запускаем, нажимаем кнопку Calculate и всё. Частота у нас 120 МГц.

В раскрывающемся списке мы можем посмотреть на каки частотах работает то или иной интерфейс

Копируем текст с правого окна в приложении

/**
  * @brief  set the baudrate of the can peripheral
  * @param  can_x: select the can peripheral.
  *         this parameter can be one of the following values:
  *         CAN1,CAN2.
  * @param  baudrate_div: baudrate division.
  * @param  rsaw_size: resynchronization adjust width.
  * @param  bts1_size: bit time segment 1.
  * @param  bts2_size: bit time segment 2.
  * @note   baudrate calculate method is:
  *         baudrate = fpclk/(baudrate_div *(1 + bts1_size + bts2_size))
  */
  can_baudrate_type can_baudrate_struct;

  can_baudrate_default_para_init(&can_baudrate_struct);
  can_baudrate_struct.baudrate_div = 30;
  can_baudrate_struct.rsaw_size = CAN_RSAW_1TQ;
  can_baudrate_struct.bts1_size = CAN_BTS1_6TQ;
  can_baudrate_struct.bts2_size = CAN_BTS2_1TQ;
  can_baudrate_set(CANx, &can_baudrate_struct);

Находим в нашем примере функцию can_configuration(void), в ней похожие строки, и заменяем их все. Комментарии можем убрать, вместо CANx пишем CAN1. В функции can_gpio_config(void) проверяём пины, куда подключен CAN. В моём случае ничего не меняем. У меня PB8 и PB9.

Лайфхак. Нажав F12 на CAN_MODE_COMMUNICATE, вы попадете в описание настроек. Нажав на ttc_enable вы так же попадете на описание настроек. Так можно исследовать очень много кода из примеров, везде есть описание. Думаю с переводом у вас проблем не будет.

В код второго таймера (1 секунда) пишем строчку

    can_transmit_data();

Остальное всё проверяем, и нажимаем F7. Переходим в режим Debug и.... устройство не опознано. И ничего не работает. Первый раз я сидел очень долго над этим. Сейчас чуть быстрее. Проблема у нас в одной строчке функции void can_transmit_data(void): while(can_transmit_status_get(CAN1, (can_tx_mailbox_num_type)transmit_mailbox) != CAN_TX_STATUS_SUCCESSFUL);

То есть пока отправка пакета не пройдет удачно, ничего не делать. Уберем проверку (как показывает работа моего устройства, это не приводит к каким-то последствиям для его работы). Строчка теперь будет выглядеть так:

  can_transmit_status_get(CAN1, (can_tx_mailbox_num_type)transmit_mailbox);

Запускаем снова, ура! Всё запустилось. В терминале каждую секунду идет серийный номер МК, а в CarBUSAnalyzer:

Обратите внимание, ID и полезные байты прописаны в функции void can_transmit_data(void). Давайте сделаем так, что в каншину будет посылаться серийный номер МК. Для этого вводим новую переменную

uint8_t transmit_mailbox;

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

void can_transmit_data(can_tx_message_type tx_message_struct) {
    tx_message_struct.extended_id = 0;
    tx_message_struct.id_type = CAN_ID_STANDARD;
    tx_message_struct.frame_type = CAN_TFT_DATA;
    tx_message_struct.dlc = 8;
    transmit_mailbox = can_message_transmit(CAN1, &tx_message_struct);
    can_transmit_status_get(CAN1, (can_tx_mailbox_num_type)transmit_mailbox);
}

Изменим немного тело функции второго таймера (1 секунда):

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

        tx_message_struct_2.standard_id = 0x2F2; // - ID
        memcpy(tx_message_struct_2.data, &ButtonTx_Buffer_usb[2], 8); // копируем серийный номер
        can_transmit_data(tx_message_struct_2); // отправляем

        /* add user code... */
        at32_led_toggle(LED3);
        tmr_flag_clear(TMR2, TMR_OVF_FLAG);
    }
Наш результат

С частотой немного что-то у меня не так, хочется ровно 1000 мс. С ходу разобраться не получилось, но вопрос интересный, буду разбираться.

Итак, в CAN шину отправлять научились. Теперь давайте сделаем отправку в CAN шину из терминала. Как мы помним, данные из терминала перехватываются в функции work_with_mmc(). Туда и заглянем, а заодно выделим управляющую посылку с нулевым байтом AA, и сигнал в каншину с нулевым байтом FF без проверки на CRC.

Добавим немного кода в функцию work_with_mmc()

void work_with_mmc(void) {
    uint8_t i2;
    can_tx_message_type tx_message_struct_2;

  	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);
            }
        }
        if (receivedUSBData[0] == 0xFF) {
            tx_message_struct_2.standard_id = receivedUSBData[2]<<8 | receivedUSBData[1]; // выделяем ID из пакета
            memcpy(tx_message_struct_2.data, &receivedUSBData[4], 8); // копируем полезные 8 байт
            can_transmit_data(tx_message_struct_2); // отправляем
        }
    }
}
Вроде хорошо получилось, согласны?

Осталось нам посмотреть, как получить данные с CAN шины и отправить их в терминал через USB. Данные с CAN приходят в функцию USBFS_L_CAN1_RX0_IRQHandler(void). Поменяем немного код функции

void USBFS_L_CAN1_RX0_IRQHandler(void) {
    can_rx_message_type rx_message_struct;
	uint8_t CDC_Tx_Buffer[10] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    
	if(can_flag_get(CAN1,CAN_RF0MN_FLAG) != RESET) {
        can_message_receive(CAN1, CAN_RX_FIFO0, &rx_message_struct);
        
	    memcpy(&CDC_Tx_Buffer[0], &rx_message_struct.standard_id, 2);
        memcpy(&CDC_Tx_Buffer[2], &rx_message_struct.data, 8);
        usb_vcp_send_data(&usb_core_dev, CDC_Tx_Buffer, 0x000A);
    }

    usbd_irq_handler(&usb_core_dev);
    usb_ready = 1;
}

Всё, смотрим на результат

По-моему неплохо получилось

На этом пример работы с CAN закончен.

Небольшая поправка, питания платы с программатора не хватает для работы с CAN. У меня по крайней мере так. Поэтому необходимо было подключить питание по USB.

В последней статье я расскажу про состояние портов ввода/вывода и может что-то ещё интересное.

Полный код примера
#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 transmit_mailbox;

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
  */

static void can_gpio_config(void) {
    gpio_init_type gpio_init_struct;

    crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);
    crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE);
    gpio_pin_remap_config(CAN1_GMUX_0010,TRUE);

    gpio_default_para_init(&gpio_init_struct);
    /* can tx pin */
    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_9;
    gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
    gpio_init(GPIOB, &gpio_init_struct);
    /* can rx pin */
    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
    gpio_init_struct.gpio_pins = GPIO_PINS_8;
    gpio_init_struct.gpio_pull = GPIO_PULL_UP;
    gpio_init(GPIOB, &gpio_init_struct);
}

/**
  *  @brief  can configiguration.
  *  @param  none
  *  @retval none
  */
static void can_configuration(void) {
    can_base_type can_base_struct;
    can_baudrate_type can_baudrate_struct;
    can_filter_init_type can_filter_init_struct;

    crm_periph_clock_enable(CRM_CAN1_PERIPH_CLOCK, TRUE);
    /* can base init */
    can_default_para_init(&can_base_struct);
    can_base_struct.mode_selection = CAN_MODE_COMMUNICATE;
    can_base_struct.ttc_enable = FALSE;
    can_base_struct.aebo_enable = TRUE;
    can_base_struct.aed_enable = TRUE;
    can_base_struct.prsf_enable = FALSE;
    can_base_struct.mdrsel_selection = CAN_DISCARDING_FIRST_RECEIVED;
    can_base_struct.mmssr_selection = CAN_SENDING_BY_ID;
    can_base_init(CAN1, &can_base_struct);

    can_baudrate_default_para_init(&can_baudrate_struct);
    can_baudrate_struct.baudrate_div = 30;
    can_baudrate_struct.rsaw_size = CAN_RSAW_1TQ;
    can_baudrate_struct.bts1_size = CAN_BTS1_6TQ;
    can_baudrate_struct.bts2_size = CAN_BTS2_1TQ;
    can_baudrate_set(CAN1, &can_baudrate_struct);

    /* can filter init */
    can_filter_init_struct.filter_activate_enable = TRUE;
    can_filter_init_struct.filter_mode = CAN_FILTER_MODE_ID_MASK;
    can_filter_init_struct.filter_fifo = CAN_FILTER_FIFO0;
    can_filter_init_struct.filter_number = 0;
    can_filter_init_struct.filter_bit = CAN_FILTER_32BIT;
    can_filter_init_struct.filter_id_high = 0;
    can_filter_init_struct.filter_id_low = 0;
    can_filter_init_struct.filter_mask_high = 0;
    can_filter_init_struct.filter_mask_low = 0;
    can_filter_init(CAN1, &can_filter_init_struct);

    /* can interrupt config */
    nvic_irq_enable(CAN1_SE_IRQn, 0x00, 0x00);
    nvic_irq_enable(USBFS_L_CAN1_RX0_IRQn, 0x00, 0x00);
    can_interrupt_enable(CAN1, CAN_RF0MIEN_INT, TRUE);

    /* error interrupt enable */
    can_interrupt_enable(CAN1, CAN_ETRIEN_INT, TRUE);
    can_interrupt_enable(CAN1, CAN_EOIEN_INT, TRUE);
}

void can_transmit_data(can_tx_message_type tx_message_struct) {
    tx_message_struct.extended_id = 0;
    tx_message_struct.id_type = CAN_ID_STANDARD;
    tx_message_struct.frame_type = CAN_TFT_DATA;
    tx_message_struct.dlc = 8;
    transmit_mailbox = can_message_transmit(CAN1, &tx_message_struct);
    can_transmit_status_get(CAN1, (can_tx_mailbox_num_type)transmit_mailbox);
}

void CAN1_SE_IRQHandler(void) {
    __IO uint32_t err_index = 0;
    if(can_flag_get(CAN1,CAN_ETR_FLAG) != RESET) {
        err_index = CAN1->ests & 0x70;
        can_flag_clear(CAN1, CAN_ETR_FLAG);
        /* error type is stuff error */
        if(err_index == 0x00000010) {
            /* when stuff error occur: in order to ensure communication normally,
            user must restart can or send a frame of highest priority message here */
        }
    }
}

void TMR1_OVF_TMR10_IRQHandler(void) {
    can_tx_message_type tx_message_struct_2;
    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;
        /* add user code... */
        //at32_led_toggle(LED3);
        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);

        tx_message_struct_2.standard_id = 0x2F2;
        memcpy(tx_message_struct_2.data, &ButtonTx_Buffer_usb[2], 8);
        can_transmit_data(tx_message_struct_2);

        /* 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;
    can_tx_message_type tx_message_struct_2;

  	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);
            }
        }
        if (receivedUSBData[0] == 0xFF) {
            tx_message_struct_2.standard_id = receivedUSBData[2]<<8 | receivedUSBData[1];
            memcpy(tx_message_struct_2.data, &receivedUSBData[4], 8);
            can_transmit_data(tx_message_struct_2);
        }
    }
}

/**
  * @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);

    // can
    can_gpio_config();
    can_configuration();

    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) {
    can_rx_message_type rx_message_struct;
	uint8_t CDC_Tx_Buffer[10] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    
	if(can_flag_get(CAN1,CAN_RF0MN_FLAG) != RESET) {
        can_message_receive(CAN1, CAN_RX_FIFO0, &rx_message_struct);
        
 	    memcpy(&CDC_Tx_Buffer[0], &rx_message_struct.standard_id, 2);
        memcpy(&CDC_Tx_Buffer[2], &rx_message_struct.data, 8);
        usb_vcp_send_data(&usb_core_dev, CDC_Tx_Buffer, 0x000A);
    }

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