Artery AT32F403A. Работа с CAN
Добрый день, я продолжаю небольшой курс по ознакомлению с микроконтроллером (МК) Artery AT32F403A. В прошлый раз мы изучили:
Знакомство с таймерами и LED
Работа с 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 закончен. В последней статье я расскажу про состояние портов ввода/вывода и может что-то ещё интересное.
Полный код примера
#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
/** @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);
}