CAN od zera #2: Jak połączyć dwa mikrokontrolery i przesłać pierwszą ramkę?
Na rozgrzewkę:
W poprzednim wpisie przedstawiłem komunikację po magistrali CAN bez transceivera z wykorzystaniem jednego mikrokontrolera. Tym razem zbudujemy pełne połączenie dwóch mikrokontrolerów STM32 poprzez transceivery CAN i prześlemy pierwszą ramkę danych. Projekt realizowany jest bez systemu operacyjnego (bare metal) z użyciem przerwań i FIFO CAN.
Schemat podłączenia dwóch mikrokontrolerów:
Dwa mikrokontrolery są połączone poprzez transceivery protokołem CAN, zgodnie z poniższym schematem blokowym:
Fizyczne połączenie:
Rys. 1. przedstawia zdjęcie fizycznego połączenia dwóch mikrokontrolerów poprzez transceivery protokołem CAN za pomocą skręconych przewodów. Dodano także rezystor terminujący, który jest niezbędny do poprawnego działania. Normalnie powinienem dodać dwa rezystory 120Ω na początku i końcu magistrali, lecz w obecnym układzie magistrala jest krótka oraz jest niska prędkość transmisji (250 kbps). W przypadku dłuższej magistrali lub wyższej prędkości transmisji należy zastosować dwa rezystory 120 Ω na końcach magistrali, pomiędzy CAN_H i CAN_L.
Konfiguracja mikrokontrolerów w środowisku CubeIDE:
W obu mikrokontrolerach:
- użyto CAN1,
- włączono przerwanie od CAN1,
- ustawiono bitrate 250 kbps.
Program mikrokontrolera nr 1:
Poniżej znajduje sie fragment funkcji obsługi przerwania od CAN:
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
// Sprawdzanie flagi nowej wiadomości
if(RxFifo0ITs && FDCAN_IT_RX_FIFO0_NEW_MESSAGE != RESET)
{
if(HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, RxData)!= HAL_OK)
{
Error_Handler();
}
if (HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0))
{
Error_Handler();
}
}
// Sprawdzanie długości danych
if(RxHeader.DataLength==2)
{
// Flaga poprawnego odbioru
datacheck=1;
}
}
Nagłówek CAN:
//Header CAN1
TxHeader.Identifier = 0x11;
TxHeader.IdType = FDCAN_STANDARD_ID;
TxHeader.TxFrameType = FDCAN_DATA_FRAME;
TxHeader.DataLength = FDCAN_DLC_BYTES_2;
TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
TxHeader.BitRateSwitch = FDCAN_BRS_OFF;
TxHeader.FDFormat = FDCAN_CLASSIC_CAN;
TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
TxHeader.MessageMarker = 0;
Pętla główna, w której po naciśnięciu przycisku wysyłane są dane po CAN oraz następuje miganie diodą po odebraniu danych po CAN:
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)
{
TxData[0] = 200;
TxData[1] = 20;
if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, TxData) != HAL_OK)
{
Error_Handler();
}
HAL_Delay(1000);
}
if (datacheck == 1)
{
for (int i = 0; i < RxData[1]; i++)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(RxData[0]);
}
datacheck = 0;
}
Ustawienia filtrów CAN:
FDCAN_FilterTypeDef fdcan1filterconfig;
fdcan1filterconfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
fdcan1filterconfig.FilterID1 = 0x12;
fdcan1filterconfig.FilterID2 = 0x12;
fdcan1filterconfig.FilterIndex = 0;
fdcan1filterconfig.FilterType = FDCAN_FILTER_MASK;
fdcan1filterconfig.IdType = FDCAN_STANDARD_ID;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &fdcan1filterconfig) != HAL_OK)
{
Error_Handler();
}
Program mikrokontrolera nr 2:
Kod mikrokontrolera nr 2 jest analogiczny – różni się jedynie ID komunikacyjnym oraz wartościami przesyłanych danych.
Nagłówek CAN:
//Header CAN1
TxHeader.Identifier = 0x12;
TxHeader.IdType = FDCAN_STANDARD_ID;
TxHeader.TxFrameType = FDCAN_DATA_FRAME;
TxHeader.DataLength = FDCAN_DLC_BYTES_2;
TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
TxHeader.BitRateSwitch = FDCAN_BRS_OFF;
TxHeader.FDFormat = FDCAN_CLASSIC_CAN;
TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
TxHeader.MessageMarker = 0;
Pętla główna, w której po naciśnięciu przycisku wysyłane są dane po CAN oraz następuje miganie diodą po otrzymaniu danych po CAN:
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)
{
TxData[0] = 100;
TxData[1] = 30;
if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, TxData) != HAL_OK)
{
Error_Handler();
}
HAL_Delay(1000);
}
if (datacheck == 1)
{
for (int i = 0; i < RxData[1]; i++)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(RxData[0]);
}
datacheck = 0;
}
Ustawienia filtrów CAN:
FDCAN_FilterTypeDef fdcan1filterconfig;
fdcan1filterconfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
fdcan1filterconfig.FilterID1 = 0x11;
fdcan1filterconfig.FilterID2 = 0x11;
fdcan1filterconfig.FilterIndex = 0;
fdcan1filterconfig.FilterType = FDCAN_FILTER_MASK;
fdcan1filterconfig.IdType = FDCAN_STANDARD_ID;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &fdcan1filterconfig) != HAL_OK)
{
Error_Handler();
}
Przedstawienie działania układu:
Mikrokontroler nr 1 oraz mikrokontroler nr 2 czytają stan przycisku, po naciśnięciu przycisku wysyłają informację po CAN do siebie o częstotliwości i ilości mignięć diodą. Częstotliwość i ilość mignięć jest inna wysyłana przez każdy z mikrokontrolerów.
Przedstawienie przesyłanych danych za pomocą konwertera USB-CAN:
Do odczytu danych wysyłanych po CAN użyłem transceivera USB-CAN od firmy Waveshare. Mikrokontroler nr 1 ma ID 0x11, zaś mikrokontroler nr 2 0x12.
Podsumowanie:
We wpisie przedstawiłem prostą implementację CAN pomiędzy dwoma mikrokontrolerami wysyłając dane dotyczące ilości oraz częstotliwości mignięć diodą. W kolejnym wpisie zajmę się implementacją CAN z wykorzystaniem RTOS oraz opisaniem mechanizmów potrzebnych do użycia CAN w urządzeniach, które chcemy aby działały bezawaryjnie przez wiele lat.



