Pull to refresh

STM32, CMSIS, CAN, Часть 1 — передача

Reading time11 min
Views35K

Введение

Привет, сегодня мы будем настраивать отправку данных с помощью CAN (Controller Area Network). В интернете много информации о том, как настроить CAN использую HAL библиотеку, а в случае использования CMSIS информация обрывочна, по этой причине решил рассказать о своем опыте работы.

Принцип работы CAN-сети в этой статье разбирать не будем т.к. на просторах интернета существует большой объем материла (особенно мне нравится как написано тут) на эту тему, но по ходу повествования будем останавливаться на некоторых нюансах.

Ставим себе задачу: заставить контроллер периодически отправлять кадры в CAN-сеть с скоростью передачи 250 кБит/с, со стандартной длиной идентификатора (11 бит) с полем данных размеров 8 байт.

Оборудование и ПО

Использовать будем микроконтроллер STM32F103C8T6 на отладочной плате, называемой в народе "Blue Pill" (рис. 1а). Также нам понадобится два приемопередачика (ПП) (по англицки "transceiver") для CAN-шины. Я использую 2 готовые платы с SN65HVD230 на борту (рис. 1б). На рис. 1в представлена схема этой платы . Для написания прошивки я буду использовать Keil uVision v5. Отладку и демонстрацию работы будем производить с помощью осциллографа и логического анализатора.

Рис. 1 – а) Отладочная плата Blue Pill; б) Плата ПП; в) Эл. схема платы ПП.
Рис. 1 – а) Отладочная плата Blue Pill; б) Плата ПП; в) Эл. схема платы ПП.

Соберем, не побоюсь этого слова, испытательный стенд. Соединяем выводы ПП с выводами Blue Pill:

CAN TX -> PA12

СAN RX -> PA11

Соединяем выводы ПП CAN_H и CAN_L между собой. Далее соединяем линии питания. В итоге должно получиться что-то подобное схеме на рис. 2. Желтый провод идет к входу логического анализатора.

Рис. 2 – Схема соединения блоков и фотография стенда
Рис. 2 – Схема соединения блоков и фотография стенда

Встраиваемое ПО

Напишем встраиваемое программное обеспечение для МК. Открываем Keil (или другую удобную для вас среду разработки: IAR, Eclipse/CubeIDE и др., главное, чтобы был установлен CMSIS), создаем проект и настраиваем для работы с нашей «Blue Pill». Если у Вас это вызывает затруднения, то в помощь статья.

Рис. 3 – Периферия, соединенная с шиной APB1
Рис. 3 – Периферия, соединенная с шиной APB1

Настроем систему тактирования. Смотрим в тех. спецификацию (datasheet) (рис. 3). Будем использовать высокоскоростной внутренний тактовый генератор (HSI). Причины почему именно его нет, да и это не является темой статьи. Главное, настроить так чтобы на шине APB1 была частота 36 МГц. Если все же интересно как настраивать систему тактирования, то можно ввести в поисковике «stm32 cmsis rcc», в интернете информации огромное количество, или почитать Reference Manual (бред, конечно, но вдруг поможет 😊). Здесь я лишь приведу текст функции настройки тактирования с комментариями, она не идеальна, но на данном этапе с задачей справляется:

uint8_t rcc_init(void){
    /* Using the default HSI sorce - 8 MHz */
    /* Checking that the HSI is working */
    for (uint8_t i=0; ; i++){
        if(RCC->CR & (1<<RCC_CR_HSIRDY_Pos))
          break;
        if(i == 255)
          return 1;          
    }    
    /* RCC_CFGR Reset value: 0x0000 0000 */
    /* PLLSRC: PLLSRC: PLL entry clock source - Reset Value - 0 -> HSI oscillator clock / 2 selected as PLL input clock */
    /* HSI = 8 MHz */
    RCC->CFGR |= RCC_CFGR_PLLMULL9;     /* 0x000C0000 - PLL input clock*9 */
    RCC->CFGR |= RCC_CFGR_SW_1;         /* 0x00000002 - PLL selected as system clock */
    /* SYSCLK = 36 MHz */
    /*Also you can change another parameters:*/
    /* HPRE: AHB prescaler - Reset Value - 0 -> SYSCLK not divided */
    /* HCLK = 36 MHz (72 MHz MAX) */
    /* PPRE1: APB low-speed prescaler (APB1) - Reset Value - 0 -> 0xx: HCLK not divided */
    /* PPRE2: APB low-speed prescaler (APB2) - Reset Value - 0 -> 0xx: HCLK not divided */
    /* APB1 = APB2 = 36 MHz */
    /* ADCPRE: ADC prescaler - Reset Value - 0 -> PCLK2 divided by 2 */
    /* PLLXTPRE: HSE divider for PLL entry - ResVal - 0 -> HSE clock not divided */
    /* USBPRE: USB prescaler - ResVal - 0: PLL clock is divided by 1.5 */
    RCC->CR |=RCC_CR_PLLON;             /* 0x01000000 - PLL enable */
    for (uint8_t i=0; ; i++){
        if(RCC->CR & (1U<<RCC_CR_PLLRDY_Pos))
            break;
        if(i==255){
            RCC->CR &= ~(1U<<RCC_CR_PLLON_Pos);
            return 2;
        }
    }      
    return 0;
}

Помещаем прототип функции в начало, само тело прячем в подвал, чтобы не мешалось. В функции main вызываем rcc_init().

Создадим функцию и uint8_t can_init(void). Далее текст в теле функции. Включим тактирование CAN:

RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;

CAN RX и TX по умолчанию находятся на выводах PA11 и PA12, соответственно (см. 31 стр. тех. спецификации). Включаем тактирование порта A:

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

Настраиваем выводы на альтернативную функцию. Настроим сразу и прием (CAN Rx) , но в этой части он на не понадобится:

/* PA11 - CAN_RX */
GPIOA->CRH	&= ~GPIO_CRH_CNF11;   /* CNF11 = 00 */ 
GPIOA->CRH	|= GPIO_CRH_CNF11_1;  /* CNF11 = 10 -> AF Out | Push-pull (CAN_RX) */
GPIOA->CRH 	|= GPIO_CRH_MODE11;   /* MODE8 = 11 -> Maximum output speed 50 MHz */
/* PA12 - CAN_TX */
GPIOA->CRH	&= ~GPIO_CRH_CNF12;	  /* CNF12 = 00 */
GPIOA->CRH	|= GPIO_CRH_CNF12_1;	/* CNF12 = 10 -> AF Out | Push-pull (CAN_TX) */
GPIOA->CRH 	|= GPIO_CRH_MODE12;   /* MODE8 = 11 -> Maximum output speed 50 MHz */

Теперь нам нужен master control register (MCR) Переводим CAN в режим инициализации:

CAN1->MCR |= CAN_MCR_INRQ;              /* Initialization Request */

Отключим режим автоматической ретрансляции. Ретрансляция сообщения происходит при отсутствии подтверждения сообщения, а т.к. в нашей стенде нам принимать сообщение нечем, следовательно, подтвердить прием тоже некому. Кстати, забегая вперед, когда не приходит подтверждение получения сообщения то в CAN error status register (CAN_ESR) в битах 4…6 LEC[2:0]: (Last error code) возникает Acknowledgment Error. При этом поднимаются 4 и 5 бит (0b011).

CAN1->MCR |= CAN_MCR_NART;

Настроим чтобы CAN автоматически выходил из спящего режима (Automatic Wakeup Mode):

CAN1->MCR |= CAN_MCR_AWUM;

Настроим скорость передачи данных. Помним, мы поставили себе задачу, что скорость составляет 250 кБит/с, т.е длительность одного бита: T_bit = 1/(250*1000) = 4 мкс при этом частота шины APB1 f_APB1 = 36 МГц. Далее я приведу рисунок (рис. 4, взял тут), который пояснит некоторые термины и понятия которые будем использовать далее

Рис. 4 – Период импульсов для CAN
Рис. 4 – Период импульсов для CAN

В нашем случае: System Clock = f_APB1 = 36 МГц. CAN System Clock это частота после пред делителя. Теперь посмотрим на CAN Bit Period. Видим, что один БИТ состоит из 4 частей. Первая часть всегда длительностью 1 квант. Вторая и третья часть объединяются в первый сегмент. Далее идет точка «захвата» бита (Sample Point) и второй сегмент бита. Количество квантов в сегментах выбирается так чтобы точка «захвата» находилась в районе 87,5% длительности бита для протоколов CANopen и DeviceNet и 75% для ARINC 825. Наш вариант 87,5 %. Теперь у нас два пути: воспользоваться специальным калькулятором. либо составить и систему из двух простых уравнений и решить её. Пойдем по первому пути.

Вводим данные и нажимаем «Request Table». Ах да, еще есть величина ширина скачка синхронизации (SJW - Synchronization Jump Width) регулирует битовую синхронизацию по мере необходимости. На расчет она не влияет.

В результате получим таблицу:

Выбираем 2-ю строку исходя из положения точки «захвата» и получаем, что 1 сегменту у нас длиной 13 квантов, а 2-ой – 2 кванта времени, t_Q = 1/(16*250000) = 250 нс, а пред делитель равен 9. Более того, нам даже подсказывают, что нужно записать в регистр CAN_BTR, мы его рассмотрим чуть более детально. Кстати, можно было и не указывать желаемую скорость передачи, тогда бы нам калькулятор выдал значения для наиболее часто используемых скоростей.

Второй путь расчёта

Решаем задачку как в школе:

Дано:
Скорость передачи, bps = 250 кБит/с
Точка захвата на 87,5 % длины бита, sp = 0,875
Частота шины APB1, f_APB1 = 36 МГц

Решение:
Знаем, что длительность бита равна:
(x+y+1)*t_Q = 1/bps (1)
t_Q - длительность кванта времени
Считаем, что пред делитель равен 1 (PreSc = 1), тогда:
t_Q = PreSc/f_APB1 = 27,78 нс

Найти
Длительность 1 и 2 сегментов (x и y, соответственно)

\frac{x+1}{x+y+1}=sp, (2)

Решаем систему ур-ий (1) и (2), получаем:

x = 125, y = 18, но если посмотреть в ref. manual, то можно заметить, что x (длительность 1-ого сегмента) может принимать значение от 1 до 16. В цикле увеличиваем значение пред делителя на 1 по порядку и решаем систему уравнений для каждого случая. Выбираем наиболее подходящий для нас вариант.

Ответ: PreSc = 9, x = 13, y = 2

Настроим скорость передачи, для этого пойдем в CAN bit timing register (CAN_BTR). Сбросим биты раздела Baud rate prescaler (BRP[9:0]) и установим: BRP[9:0] = PreSc - 1 = 8:

CAN1->BTR &= ~CAN_BTR_BRP;
CAN1->BTR |= 8U << CAN_BTR_BRP_Pos;

Сбросим биты, отвечающие за 1 временной сегмент и установим: TS1[3:0] = 13 – 1 = 12:

CAN1->BTR &= ~(0xFU << CAN_BTR_TS1_Pos);
CAN1->BTR |= 12U << CAN_BTR_TS1_Pos;

Сбросим биты, отвечающие за 2 временной сегмент и установим: TS2[2:0] = 2 – 1 = 1:

CAN1->BTR &= ~(7U << CAN_BTR_TS2_Pos);
CAN1->BTR |=   1U << CAN_BTR_TS2_Pos;

Биты 24…25 SJW[1:0] не трогаем их состояние т. о. получим что ширина скачка синхронизации t_SJW = 2t_Q = 500 нс.

Также в этом регистре можно настроить CAN в режим отладки (Loop back mode, Silent mode), но это не наш случай.

Настроим почтовый ящик номер 0 предназначенный для отправки (transmit mailbox 0). Чтобы добраться до него в структуре типа CAN_TypeDef есть вложенная структура sTxMailBox типа CAN_TxMailBox_TypeDef.  Сообщения, которые будем отправлять из этого ящика содержат данные (data frame), а не запрос (remote frame), сообщим об этом контроллеру:

CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_RTR;

Будем использовать стандартный идентификатор для кадра:

CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_IDE;

Отчистим идентификатор и поставим какой душе вздумается (в диапазоне от 0 до 3777):

CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID;
CAN1->sTxMailBox[0].TIR |= (0x556U << CAN_TI0R_STID_Pos);

Теперь сообщим сколько байт полезной информации будет передавать в одном кадре. CAN позволяет передать от 1 до 8. Эх, гулять так гулять, будем передавать 8. Определим это значение в начале файла, т.к. это значение нам еще пригодится в функции отправки сообщения:

#define DATA_LENGTH_CODE 8

и передадим это значение в регистр:

CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC;
CAN1->sTxMailBox[0].TDTR |= (DATA_LENGTH_CODE << CAN_TDT0R_DLC_Pos);

Все, с настройкой закончили. Естественно, дальше нужно будет настроить и прием, функцию будем допиливать, но это отдельная история. Нас пока интересует передача. Переходим из режима инициализации в «нормальный» режим (normal mode). Завершаем функцию. ссылка на полный код в конце статьи.

CAN1->MCR &= ~CAN_MCR_INRQ;
return 0;
}

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

uint8_t can1_send(uint8_t * pData, uint8_t dataLength);

Нам понадобятся 2 счетчика i и j. Первый используем для перебора байтов, второй в случае, если размер кадра будет меньше количество передаваемых байт.

uint16_t i = 0;
uint8_t j = 0;

Проверим есть ли запросы на передачу для 0 почтового ящика, эта информация хранится в CAN transmit status register (CAN_TSR) а именно в 26 бите (TME0). Если есть, ждем, и если ждать придется слишком долго, то функция вернет 1.

while (!(CAN1->TSR & CAN_TSR_TME0)){
    i++;
    if (i>0xEFFF) return 1;
}

Обнуляем данные, которые лежат в регистрах с данными чтобы не отправить случайно какой-нибудь мусор:

CAN1->sTxMailBox[mailboxNum].TDLR = 0;
CAN1->sTxMailBox[mailboxNum].TDHR = 0;

Далее пишем цикл, который постепенно заполняет каждый байт в регистрах данных TDLR и TDHR и делит данные на кадры если нужно передать больше байт чем настроено в DATA_LENGTH_CODE (мы настроили на 8 байт):

while (i<dataLength){
    if (i>(DATA_LENGTH_CODE-1)){
		    CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ; /* Transmit Mailbox Request */
		    dataLength -= i;
		    j++;
		    while (!(CAN1->TSR & CAN_TSR_TME0)){      /* Transmit mailbox 0 empty? */
			      i++;
				if (i>0xEFFF) return 1;
		    }
		    if (CAN1->TSR & CAN_TSR_TXOK0){}          /* Tx OK? */
		    //else return ((CAN1->ESR & CAN_ESR_LEC)>>CAN_ESR_LEC_Pos); /* return Last error code */
		    i = 0;
		    CAN1->sTxMailBox[0].TDLR = 0;
		    CAN1->sTxMailBox[0].TDHR = 0;
    }
		if (i<4){
		    CAN1->sTxMailBox[0].TDLR |= (pData[i+j*DATA_LENGTH_CODE]*1U << (i*8));
		}
		else{
		    CAN1->sTxMailBox[0].TDHR |= (pData[i+j*DATA_LENGTH_CODE]*1U << (i*8-32));
		}
		i++;
}

Далее добавляем запрос на передачу оставшихся данных и проверяем, что данные отправились без ошибок или возвращаем код ошибки (мы про этот регистр упоминали ранее):

CAN1->sTxMailBox[mailboxNum].TIR |= CAN_TI0R_TXRQ; /* Transmit Mailbox Request */
if (CAN1->TSR & CAN_TSR_TXOK0) return 0;
else return ((CAN1->ESR & CAN_ESR_LEC)>>CAN_ESR_LEC_Pos);

Теперь в main до бесконечного цикла вызываем функцию настройки can_init(), инициализируем тестовую строку и переменную для счетчика, который будет использоваться для задержки, а то поднимать таймеры в этой статье не будем, дабы не раздувать объем).

uint16_t counter = 0;
uint8_t * data = “ABCDEFGHIJ9”;

В цикле вызываем функцию и создаем простенькую задержку:

can1_send(data, sizeof(data));
while(counter<0xFFFF)
    counter++;
counter = 0;

Все, наша программа готова.

Полный текст программы
#include "main.h"

#define DATA_LENGTH_CODE 8

uint8_t rcc_init(void);
uint8_t can_init(void);
uint8_t can_send(uint8_t * pData, uint8_t dataLength);
int main(void){
    volatile uint16_t counter = 0;
    uint8_t data[] = "ABCDEFGHIJ9";
    rcc_init();
    can_init();
    while(1){
        can_send(data, sizeof(data));
        while(counter<0xFFFF)
            counter++;
		    counter = 0;
    }
}

uint8_t rcc_init(void){
    /* Using the default HSI sorce - 8 MHz */
    /* Checking that the HSI is working */
    for (uint8_t i=0; ; i++){
    if(RCC->CR & (1<<RCC_CR_HSIRDY_Pos))
        break;
    if(i == 255)
        return 1;
    }
    /* RCC_CFGR Reset value: 0x0000 0000 */
    /* PLLSRC: PLLSRC: PLL entry clock source - Reset Value - 0 -&gt; HSI oscillator clock / 2 selected as PLL input clock */
    /* HSI = 8 MHz */
    RCC->CFGR |= RCC_CFGR_PLLMULL9;     /* 0x000C0000 - PLL input clock*9 */
    RCC->CFGR |= RCC_CFGR_SW_1;         /* 0x00000002 - PLL selected as system clock */
    /* SYSCLK = 36 MHz */
    /*Also you can change another parameters:*/
    /* HPRE: AHB prescaler - Reset Value - 0 -> SYSCLK not divided */
    /* HCLK = 36 MHz (72 MHz MAX) */
    /* PPRE1: APB low-speed prescaler (APB1) - Reset Value - 0 ->; 0xx: HCLK not divided */
    /* PPRE2: APB low-speed prescaler (APB2) - Reset Value - 0 ->; 0xx: HCLK not divided */
    /* APB1 = APB2 = 36 MHz */
    /* ADCPRE: ADC prescaler - Reset Value - 0 -&gt; PCLK2 divided by 2 */
    /* PLLXTPRE: HSE divider for PLL entry - ResVal - 0 ->; HSE clock not divided */
    /* USBPRE: USB prescaler - ResVal - 0: PLL clock is divided by 1.5 */
    RCC->CR |=RCC_CR_PLLON;             /* 0x01000000 - PLL enable */
    for (uint8_t i=0; ; i++){
        if(RCC->CR & (1U<<RCC_CR_PLLRDY_Pos))
            break;
        if(i==255){
            RCC->CR &= ~(1U<<RCC_CR_PLLON_Pos);
            return 2;
        }
    }      
    return 0;
}

uint8_t can_init(void){
    RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; /* turn on clocking for CAN */
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; /* turn on clocking for GPIOA */
    /* PA11 - CAN_RX */
    GPIOA->CRH	&= ~GPIO_CRH_CNF11;     /* CNF11 = 00 */
    GPIOA->CRH	|= GPIO_CRH_CNF11_1;	  /* CNF11 = 10 -> AF Out | Push-pull (CAN_RX) */
    GPIOA->CRH 	|= GPIO_CRH_MODE11;     /* MODE11 = 11 -> Maximum output speed 50 MHz */
    /* PA12 - CAN_TX */
    GPIOA->CRH	&= ~GPIO_CRH_CNF12;	    /* CNF12 = 00 */
    GPIOA->CRH	|= GPIO_CRH_CNF12_1;	  /* CNF12 = 10 -> AF Out | Push-pull (CAN_TX) */
    GPIOA->CRH 	|= GPIO_CRH_MODE12;     /* MODE12 = 11 -> Maximum output speed 50 MHz */
    CAN1->MCR |= CAN_MCR_INRQ;          /* Initialization Request */
    CAN1->MCR |= CAN_MCR_NART;          /* Not autoretranslate transmission */
    CAN1->MCR |= CAN_MCR_AWUM;          /* Automatic Wakeup Mode */
    /* clean and set Prescaler = 9 */
    CAN1->BTR &= ~CAN_BTR_BRP;          
    CAN1->BTR |= 8U << CAN_BTR_BRP_Pos;
    /* clean and set T_1s = 13, T_2s = 2 */
    CAN1->BTR &= ~CAN_BTR_TS1;
    CAN1->BTR |= 12U << CAN_BTR_TS1_Pos;
    CAN1->BTR &= ~CAN_BTR_TS2;
    CAN1->BTR |= 1U << CAN_BTR_TS2_Pos;

    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_RTR;                    /* data frame */
    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_IDE;                    /* standart ID */ 
    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID;
    CAN1->sTxMailBox[0].TIR |= (0x556U << CAN_TI0R_STID_Pos);
    CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC;                  /* length of data in frame */
    CAN1->sTxMailBox[0].TDTR |= (DATA_LENGTH_CODE << CAN_TDT0R_DLC_Pos);
    CAN1->MCR &= ~CAN_MCR_INRQ;                                  /* go to normal mode */ 
    return 0;	
}

uint8_t can_send(uint8_t * pData, uint8_t dataLength){
    uint16_t i = 0;
    uint8_t j = 0;
    while (!(CAN1->TSR & CAN_TSR_TME0)){
        i++;
        if (i>0xEFFF) return 1;
    }
    i = 0;
    CAN1->sTxMailBox[0].TDLR = 0;
    CAN1->sTxMailBox[0].TDHR = 0;
    while (i<dataLength){
        if (i>(DATA_LENGTH_CODE-1)){
            CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ;                 /* Transmit Mailbox Request */
            dataLength -= i;
            j++;
            while (!(CAN1->TSR & CAN_TSR_TME0)){                      /* Transmit mailbox 0 empty? */
                i++;
                if (i>0xEFFF) return 1;
            }
        if (CAN1->TSR & CAN_TSR_TXOK0){}                          /* Tx OK? */
        //else return ((CAN1->ESR & CAN_ESR_LEC)>>CAN_ESR_LEC_Pos); / return Last error code /
        i = 0;
        CAN1->sTxMailBox[0].TDLR = 0;
        CAN1->sTxMailBox[0].TDHR = 0;
        }
        if (i<4)
	          CAN1->sTxMailBox[0].TDLR |= (pData[i+j*DATA_LENGTH_CODE]*1U << (i*8));
        else
	          CAN1->sTxMailBox[0].TDHR |= (pData[i+j*DATA_LENGTH_CODE]*1U << (i*8-32));
        i++;
    }
    CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ; /* Transmit Mailbox Request */
    if (CAN1->TSR & CAN_TSR_TXOK0) return 0;
    else return ((CAN1->ESR & CAN_ESR_LEC)>>CAN_ESR_LEC_Pos); /* return Last error code */
}

Испытания и результат

Cобираем проект, прошиваем, подключаем осциллограф и/или логический анализатор. Вот что показывает осциллограф:

Рис. 5 – Осциллограммы сигнала на CAN_H и CAN_L
Рис. 5 – Осциллограммы сигнала на CAN_H и CAN_L

На осциллограммах в первом случае отправляется 10 байт т.о. сообщение делится на 2 кадра. Во втором случае мы сообщение уменьшили до размера 8 байт – 1 кадр. И в третьем случаем мы длину кадра уменьшили до 4 байт, а длину сообщения вернули к 10 байтам – 3 кадра. Распознать вручную такую осциллограмму весьма затруднительно, для этого используем логически анализатор (я пользуюсь LWLA1034 в паре с ПО – PulseView) (рис. 6).

Рис. 6 – Сигналы, полученные логическим анализатором и интерпретированные PulseView.
Рис. 6 – Сигналы, полученные логическим анализатором и интерпретированные PulseView.

Видно, что данные отправляются как нужно (по таблице ASCII A = 0x41, B = 0x42 и т. д.), идентификатор какой записали (0x556), частота соответствует желаемой (250 кБит/с). Нет подтверждения, что отправленные данные получены, но ему и взяться неоткуда. Кстати посмотрим, что у нас записано в CAN error status register, а именно какой последний код ошибки. Зайдем в Keil в режиме отладки:

LEC = 3 – ошибка подтверждения как и ожидалось.

На этом всё! Задача выполнена, ура! Полный текст программы и проект в Keil можно скачать с github.

Если это будет кому-нибудь интересно, то напишу продолжение про настройку CAN через cmsis на прием.

Tags:
Hubs:
+15
Comments26

Articles

Change theme settings