Stm32 hal can ошибки

Один из немаловажных аспектов работы CAN шины является обработка прерываний bxCan. Их не так уж и много, но при правильной их настройке и обработке мы сможем обеспечить максимальную работоспособность и высокую отказоустойчивость нашего устройства. Поэтому следует обратить наше внимание на то, как это сделать правильно — создать необходимый минимум.

Теория

Начнем опять с теории и обратимся к Reference Maanual от ST Microelectronics.

Для начала мы попытаемся разобраться с механизмом прерываний микроконтроллера STM32F103C6, затем пройдемся по регистрам контроллера и, затем, выстроим некий шаблон, который мы сможем использовать в своих проектах.

Прерывания

Для bxCan может быть назначено четыре прерывания. Каждый из источников прерываний может быть включен или выключен независимо друг от друга в регистре CAN_IER (CAN Interrupt Enable Register).

Вот схема отработки прерываний приведенная в мануале:

Рис. 1. Флаги событий и формирование прерываний

Как видим на рисунке 1, прерывания сгруппированы в четыре группы:

Transmit interrupt  (Прерывание при передаче сообщения) — может быть вызвано следующими событиями:

— Выполнена передача и освобожден mailbox 0. Бит RQCP0 в регистре CAN_TSR установлен;
— Выполнена передача и освобожден mailbox 1. Бит RQCP1 в регистре CAN_TSR установлен;
— Выполнена передача и освобожден mailbox 2. Бит RQCP2 в регистре CAN_TSR установлен.

FIFO0 interrupt (Прерывание связанное с входящим буфером FIFO0) — вызывается по следующим событиям:

— Прием нового сообщения, биты FMP0 в регистре CAN_RF0R не равны «00». В принципе значение этого регистра говорит нем о том, сколько сообщений в буфере FIFO еще не обработано программой;
— Буфер FIFO0 заполнен. Бит FULL0  в регистре CAN_RF0R установлен — сообщает нам о том, что в буфере FIFO0 больше нет свободного места;
— Переполнение буфера FIFO0. Бит FOVR0 в регистре CAN_RF0R установлен — возникает в случае когда буфер FIFO0 заполнен и по шине принято еще одно сообщение. Что с этим сообщением произойдет, мы указываем в настройках инициализации CAN (параметр CAN_RFLM).

FIFO1 interrupt (Прерывание связанное с входящим буфером FIFO1) — вызывается по следующим событиям:

— Прием нового сообщения, биты FMP1 в регистре CAN_RF1R не равны «00». В принципе значение этого регистра говорит нем о том, сколько сообщений в буфере FIFO еще не обработано программой;
— Буфер FIFO1 заполнен. Бит FULL1  в регистре CAN_RF0R установлен — сообщает нам о том, что в буфере FIFO1 больше нет свободного места;
— Переполнение буфера FIFO1. Бит FOVR1 в регистре CAN_RF0R установлен — возникает в случае когда буфер FIFO1 заполнен и по шине принято еще одно сообщение. Что с этим сообщением произойдет, мы указываем в настройках инициализации CAN (параметр CAN_RFLM).

 • Error and Status change Interrupt (Прерывание по возникновению ошибок и изменению состояния bxCAN) — вызывается по следующим событиям:

— Возникновение ошибки. Информация об ошибке хранится в регистре CAN Error (CAN_ESR);
— «Просыпание» контроллера — выход из режима сна, когда на Rx появился сигнал CAN шины;
— Переход в спящий режим.

Четвертая группа отвечает за прерывания не только ошибок, но, как видно из названия, и за изменения статуса (режима) bxCan. 

Регистры

Полное описание регистров bxCan я приведу в отдельной статье, здесь же мы коснемся лишь некоторых из них, которые непосредственно рассматриваются в рамках данной статьи.

Для того, чтобы мы смогли программно обработать прерывания bxCan, необходимо изучить регистры микроконтроллера, которые непосредственно связаны с этими прерываниями. Разработчики ST Microelectronix постарались для нас и большинство функционала для работы CAN шины возложили на аппаратную часть микроконтроллера, но все же нам придется выполнить некоторые действия самостоятельно.

За обработку ошибок, включение/выключение прерываний CAN шины, а также за информацию о текущем статусе шины и ошибок в bxCan отвечают шесть регистров, которые мы сейчас и изучим:

CAN master status register (CAN_MSR)

Один из основных регистров bxCAN. Он отображает текущее состояние CAN устройства и позволяет программному обеспечению контролировать более детально работу bxCan. 
В большинстве случаев нет необходимости досконально контролировать аппаратную часть, сама bxCan превосходно с этим справляется, но в некоторых случаях будет полезно понимать предоставленные нам возможности и инструменты.

CAN Master Status Register предоставляет нам информацию о том, в каком состоянии находится bxCan и сообщает нам о прерываниях, если они установлены.

Рис. 2. CAN master status register.

Address offset: 0x04
Reset value: 0x0000 0C02

Биты Название Описание
 31:12 Зарезервировано   
11 RX — CAN Rx signal CAN Rx сигнал.
Контролирует фактическое состояние пина CAN_Rx
10  SAMP — Last sample point Последнее принятое значение.
Значение RX на последней точки выборки (фактически значение последнего принятого бита).
RXM — Receive mode Режим передачи.
Сообщает, что bxCan находится в режиме передачи сообщения.
TXM — Transmit mode Режим приема.
Сообщает, что bxCan находится в режиме приема сообщения.
7:5  Зарезервировано   
SLAKI — Sleep acknowledge interrupt Бит прерывания при переходе в спящий режим.
Когда SLKI = 1, то этот бит устанавливается аппаратно и сигнализирует о том, что bxCan вошел в режим «спячки». После установки этого бита генерируется прерывание по переходу в спящий режим (если установлен бит SLKIE в регистре CAN_IER).
SLAKI может сбрасываться программно или аппаратно, когда сбрасывается бит SLAK.
Примечание: когда бит SLKIE = 0, то нельзя выполнить опрос бита SLAKI. В этом случае необходимо читать значение бита SLAK.
WKUI — Wakeup interrupt Бит прерывания при возврате из «спящего» режима.
Этот бит аппаратно устанавливает сигнал о том, что бит SOF был обнаружен, в то время, как bxCan находился в спящем режиме.
Установка этого бита генерирует изменение статуса прерывания, если бит WKUIE регистра CAN_IER был установлен.
Сбрасывается этот бит с помощью программного обеспечения.
ERRI — Error interrupt Бит прерывание по ошибке.
Этот бит устанавливается аппаратно, когда бит в регистре CAN_ESR был установлен на обнаружение ошибок и при этом включено соответствующее прерывание в регистре CAN_IER.
Установка этого бита генерирует прерывание, если установлен бит ERRIE в регистре CAN_IER.
Очищается с помощью программного обеспечения.
SLAK — Sleep acknowledge Режим сна.
Этот бит устанавливается аппаратно и указывает на то, что bxCan находится в режиме сна. Этот бит подтверждает запрос о переходе в «спящий» режим из программного обеспечения (установка бита Sleep в регистре CAN_MCR).
Сбрасывается аппаратно, когда bxCan переходит в спящий режим (для синхронизации по CAN — шине). Для синхронизации устройств на шине необходимо контролировать последовательность 11-ти рецессивных бит подряд на сигнале CAN_RX.
Процесс выхода из сна запускается, когда сбрасывается бит SLEEP в регистре CAN_MCR.
Автоматическое пробуждение из режима сна происходит при установке бита AWUM регистра CAN_MCR.
INAK — Initialization acknowledge Режим инициализации.
Этот бит устанавливается аппаратно и указывает программному обеспечению на то, что bxCan находится в режиме инициализации. Этот бит подтверждает запрос инициализации из программного обеспечения (установлен бит INRQ в регистре CAN_MCR).
Бит INAK сбрасывается автоматически, когда bxCan выходит из режима инициализации.
Для того чтобы синхронизировать устройства с шиной, необходимо контролировать последовательность 11 рецессивных бит подряд на CAN_RX.

CAN transmit status register (CAN_TSR)

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

Рис. 3. CAN transmit status register.

Address offset: 0x08
Reset value: 0x1C00 0000

Биты Название Описание
31 LOW2 — Lowest priority flag for mailbox 2 Наименьший приоритет для почтового ящика №2.
Этот бит устанавливается аппаратно, когда более чем один почтовый ящик находится в обработке, а сообщение в почтовом ящике №2 имеет наименьший приоритет.
30 LOW1 — Lowest priority flag for mailbox 1 Наименьший приоритет для почтового ящика №1.
Этот бит устанавливается аппаратно, когда более чем один почтовый ящик находится в обработке, а сообщение в почтовом ящике №1 имеет наименьший приоритет.
29 LOW0 — Lowest priority flag for mailbox 0 Наименьший приоритет для почтового ящика №0.
Этот бит устанавливается аппаратно, когда более чем один почтовый ящик находится в обработке, а сообщение в почтовом ящике №0 имеет наименьший приоритет.

Примечание: Биты LOW[2:0] устанавливаются в ноль,  когда только один почтовый ящик находится на обработке.

28 TME2 — Transmit mailbox 2 empty Почтовый ящик №2 пуст.
Этот бит устанавливается аппаратно, когда нет запроса на обработку почтового ящика №2.
27 TME1 — Transmit mailbox 1 empty Почтовый ящик №1 пуст.
Этот бит устанавливается аппаратно, когда нет запроса на обработку почтового ящика №1.
26 TME0 — Transmit mailbox 0 empty Почтовый ящик №0 пуст.
Этот бит устанавливается аппаратно, когда нет запроса на обработку почтового ящика №0.
25:24 CODE[1:0] — Mailbox code Код почтового ящика.
В случае, когда по меньшей мере освобождается один почтовый ящик, значение CODE содержит номер следующего почтового ящика в очереди с наименьшим приоритетом.
23 ABRQ2 — Abort request for mailbox 2 Прервать запрос на обработку почтового ящика №2. 
Устанавливается программно с целью прервать передачу из почтового ящика №2. Сбрасывается автоматически после того, как bxCan очищает почтовый ящик. Установка этого бита не имеет никакого значения, если почтовый ящик не задерживается для передачи.
22:20 Зарезервировано   
19 TERR2 — Transmission error of mailbox 2 Ошибка передачи для почтового ящика №2.
Устанавливается, когда произошла ошибка при передаче сообщения из этого почтового ящика.
18 ALST2 — Arbitration lost for mailbox 2 Потеря арбитража для почтового ящика №2.
Бит устанавливается, если при передачи сообщения устройство проиграла арбитраж.
17 TXOK2 — Transmission OK of mailbox 2 Завершение передачи для почтового ящика №2.
bxCan обновляет этот бит после каждой попытки передачи из почтового ящика и устанавливает следующие значения:
0 — передача не удалась;
1 — передача была успешной.
Этот бит устанавливается аппаратно, когда успешно завершен запрос передачи для почтового ящика №2.
16 RQCP2 — Request completed mailbox2 Завершен запрос на передачу для почтового ящика №2.
Устанавливается аппаратно, когда был выполнен последний запрос (передан или прерван).
Очищается программно путем установки бита в «1» или аппаратно по факту завершения передачи (установка бита TXRQ2 в регистре CAN_TI2R).
Очистка этого бита сбрасывает все виды состояния для почтового ящика №2 (TXOK2, ALST2 и TERR2).
15 ABRQ1 — Abort request for mailbox 1 Прервать запрос на обработку почтового ящика №1.
Устанавливается программно с целью прервать передачу из почтового ящика №1. Сбрасывается автоматически после того, как bxCan очищает почтовый ящик.
Установка этого бита не имеет никакого значения, если почтовый ящик не задерживается для передачи.
14:12 Зарезервировано   
11 TERR1 — Transmission error of mailbox1 Ошибка передачи для почтового ящика №2.
Устанавливается, когда произошла ошибка при передаче сообщения из этого почтового ящика.
10 ALST1 — Arbitration lost for mailbox1 Потеря арбитража для почтового ящика №1.
Бит устанавливается, если при передачи сообщения устройство проиграла арбитраж.
9 TXOK1 — Transmission OK of mailbox1 Завершение передачи для почтового ящика №1.
bxCan обновляет этот бит после каждой попытки передачи из почтового ящика и устанавливает следующие значения:
0 — передача не удалась;
1 — передача была успешной.
Этот бит устанавливается аппаратно, когда успешно завершен запрос передачи для почтового ящика №1.
8 RQCP1 — Request completed mailbox1 Завершен запрос на передачу для почтового ящика №1.
Устанавливается аппаратно, когда был выполнен последний запрос (передан или прерван).
Очищается программно путем установки бита в «1» или аппаратно по факту завершения передачи (установка бита TXRQ1 в регистре CAN_TI1R).
Очистка этого бита сбрасывает все виды состояния для почтового ящика №1 (TXOK1, ALST1 и TERR1).
7 ABRQ0 — Abort request for mailbox0 Прервать запрос на обработку почтового ящика №0.
Устанавливается программно с целью прервать передачу из почтового ящика №0. Сбрасывается программно после того, как bxCan очищает почтовый ящик.
Установка этого бита не имеет никакого значения, если почтовый ящик не задерживается для передачи.
6:4 Зарезервировано   
3 TERR0 — Transmission error of mailbox0 Ошибка передачи для почтового ящика №0.
Устанавливается, когда произошла ошибка при передаче сообщения из этого почтового ящика.
2 ALST0 — Arbitration lost for mailbox0 Потеря арбитража для почтового ящика №0.
Бит устанавливается, если при передачи сообщения устройство проиграла арбитраж.
1 TXOK0 — Transmission OK of mailbox0 Завершение передачи для почтового ящика №0.
bxCan обновляет этот бит после каждой попытки передачи из почтового ящика и устанавливает следующие значения:
0 — передача не удалась;
1 — передача была успешной.
Этот бит устанавливается аппаратно, когда успешно завершен запрос передачи для почтового ящика №0.
0 RQCP0 — Request completed mailbox0 Завершен запрос на передачу для почтового ящика №0.
Устанавливается аппаратно, когда был выполнен последний запрос (передан или прерван).
Очищается программно путем установки бита в «1» или аппаратно по факту завершения передачи (установка бита TXRQ0 в регистре CAN_TI0R).
Очистка этого бита сбрасывает все виды состояния для почтового ящика №0 (TXOK0, ALST0 и TERR0).

Как правило необходимости напрямую обращаться к этому регистру у нас не будет — можно полностью доверится bxCan. Я вижу потребность в его чтении только в очень сложных проектах, где часть функционала ложится не только на аппаратную часть, но и на программную. Также может потребоваться в случаях, когда необходимо использовать парсер CAN-шины — необходимо более детальное изучение ошибок передачи данных, логирование всего и вся.

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

CAN receive FIFO 0 register (CAN_RF0R)

Первый из двух регистров, отвечающих за буфер входящих сообщений FIFO 0.

Из этого регистра мы можем узнать количество почтовых сообщений, а также текущее состояние буфера FIFO 0.

 

Рис. 4. CAN receive FIFO 0 register.

Address offset: 0x0C
Reset value: 0x0000 0000

Биты Название Описание
31:6 Зарезервировано  
5 RFOM0 — Release FIFO 0 output mailbox Буфер FIFO0 освобожден.
Устанавливается программно, чтобы освободить (очистить) почтовые ящики буфера FIFO0. Выходной почтовый ящик буфера может быть освобожден только в том случае, если на обработке FIFO0 имеется хотя бы одно сообщение. Устанавливать этот бит, когда FIFO0 пуст — не имеет никакого смысла.
Очищается автоматически с помощью аппаратных средств, когда обработаны все сообщения, находящиеся в почтовых ящиках буфера FIFO0.
4 FOVR0 — FIFO 0 overrun Буфер FIFO0 переполнен.
Этот бит устанавливается аппаратными средствами, когда получено новое сообщение, но буфер FIFO0 уже заполнен.
Этот бит необходимо сбрасывать программно.
3 FULL0 — FIFO 0 full Буфер FIFO0 заполнен.
Устанавливается аппаратно, когда заполнены все три почтовых ящика буфера FIFO0
Этот бит необходимо сбрасывать программно.
2 Зарезервировано  
1:0 FMP0[1:0] — FIFO 0 message pending Количество сообщений в буфере FIFO0.
Эти биты указывают, сколько сообщений находится на обработке в буфере FIFO0. FMP увеличивается каждый раз, когда поступает новое сообщение и уменьшается, после обработки каждого сообщения буфера.

Обработка прерываний для буфера FIFO 0 происходит в функции USB_LP_CAN1_RX0_IRQHandler(). В ней необходимо обработать получение почтовых сообщений, а также проверить состояние ошибок (заполнение или переполнение буфера FIFO 0). Естественно, необходимо включить эти прерывания при настройке bxCan.

Необходимо помнить, что биты FOVR0 и FULL0 сбрасываются вручную. 

CAN receive FIFO 1 register (CAN_RF1R)

А это второй регистр, предназначенный для чтения состояния буфера входящих сообщений, но уже для FIFO 1.

Из него мы также можем почерпнуть информацию о  том, сколько сообщений у нас хранится и текущий статус самого буфера FIFO 1.

Рис. 5. CAN receive FIFO 1 register.

Address offset: 0x10
Reset value: 0x0000 0000

Биты Название Описание
31:6 Зарезервировано  
5 RFOM1 — Release FIFO 1 output mailbox Буфер FIFO1 освобожден.
Устанавливается программно, чтобы освободить (очистить) почтовые ящики буфера FIFO1. Выходной почтовый ящик буфера может быть освобожден только в том случае, если на обработке FIFO1 имеется хотя бы одно сообщение. Устанавливать этот бит, когда FIFO1 пуст — не имеет никакого смысла.
Очищается автоматически с помощью аппаратных средств, когда обработаны все сообщения, находящиеся в почтовых ящиках буфера FIFO1.
4 FOVR1 — FIFO 1 overrun Буфер FIFO1 переполнен.
Этот бит устанавливается аппаратными средствами, когда получено новое сообщение, но буфер FIFO1 уже заполнен.
Этот бит необходимо сбрасывать программно.
3 FULL1 — FIFO 1 full Буфер FIFO1 заполнен.
Устанавливается аппаратно, когда заполнены все три почтовых ящика буфера FIFO1
Этот бит необходимо сбрасывать программно.
2 Зарезервировано  
1:0 FMP1[1:0] — FIFO 1 message pending Количество сообщений в буфере FIFO1.
Эти биты указывают, сколько сообщений находится на обработке в буфере FIFO1.
FMP увеличивается каждый раз, когда поступает новое сообщение и уменьшается, после обработки каждого сообщения буфера.

Для обработки прерываний для буфера FIFO 1 необходимо включить обработку этих прерываний при настройке bxCan и вставить в модуль функцию CAN1_RX1_IRQHandler(). Также как и с буфером FIFO 0, мы можем в обработчике прерываний выполнить обработку получения почтового сообщения в буфер FIFO 1, а также проверить состояние ошибок буфера и сбросить их после обработки.

Также напомню о необходимости сбрасывать биты FOVR1 и FULL1 вручную.

CAN interrupt enable register (CAN_IER)

Мы добрались до регистра, который непосредственно отвечает за включение прерываний bxCAN. Установив необходимые нам биты, мы сможем выполнить обработку прерываний. Напомню, что включение прерываний само по себе недостаточно, необходимо еще и включить сами прерывания и добавить их обработчики в тело программы, иначе наша программа при возникновении прерывания перейдет в прерывание по умолчанию и просто «зависнет». Но об этом поговорим чуть ниже, где я приведу несколько примеров.

Итак, нам необходимо включить прерывания. Сделать это мы можем установив соответствующие биты в регистре CAN interrupt enable register (CAN_IER).

Рис. 6. CAN interrupt enable register.

Address offset: 0x14
Reset value: 0x0000 0000

Биты Название Описание
31:18 Зарезервировано  
17 SLKIE — Sleep interrupt enable Прерывание при переходе в спящий режим.
Вызывается, когда bxCan переходит в «спящий» режим при установленном бите SLAKI регистра CAN_MSR.
0: Прерывание не генерируется
1: Прерывание генерируется
16 WKUIE — Wakeup interrupt enable Прерывание при выходе из спящего режима.
Вызывается, когда bxCan выходит из спящего режима при установленном бите WKUI регистра CAN_MSR.
0: Прерывание не генерируется
1: Прерывание генерируется
15 ERRIE — Error interrupt enable Прерывание при возникновении ошибки.
0: Прерывание не генерируется
1: Генерируется прерывание когда есть описание ошибки в регистре CAN_ESR.
14:12 Зарезервировано  
11 LECIE — Last error code interrupt enable Прерывание при возникновении ошибки приема-передачи.
Вызывается, когда установлены биты LEC[2:0] (регистр CAN_ESR) аппаратной частью bxCan.
0: Бит ERRI не будет установлен
1: Бит ERRI будет установлен при обнаружении ошибки на шине.
10 BOFIE — Bus-off interrupt enable Прерывание при переходе в режим Bus-Off.
Вызывается при переходе bxCan в режим Bus-Off при установленном бите BOFF регистра CAN_ESR.
0: Бит ERRI не будет установлен
1: Бит ERRI будет установлен
9 EPVIE — Error passive interrupt enable Прерывание при достижении пассивного уровня ошибок.
Вызывается когда счетчики ошибок приема или передачи превышают значение 127 при установленном бите EPVF регистра CAN_ESR.
0: Бит ERRI не будет установлен
1: Бит ERRI будет установлен
8 EWGIE — Error warning interrupt enable Прерывание при достижении предупреждающего уровня ошибок.
Вызывается когда счетчики ошибок приема или передачи превышают либо равны значению 96 при установленном бите EWGF.
0: Бит ERRI не будет установлен
1: Бит ERRI будет установлен
7 Зарезервировано  
6 FOVIE1 — FIFO overrun interrupt enable Прерывание при переполнении буфера FIFO1.
Вызывается, когда буфер FIFO1 заполнен и получено еще один пакет данных при установленном бите FOVR регистра CAN_RF1R.
0: Прерывание не генерируется
1: Генерируется прерывание
5 FFIE1 — FIFO full interrupt enable Прерывание при заполнении буфера FIFO1.
Вызывается когда в буфере FIFO1 заполнены все три почтовых ящика при установленном бите FULL регистра CAN_RF1R.
0: Прерывание не генерируется
1: Генерируется прерывание
4 FMPIE1 — FIFO message pending interrupt enable Прерывание при получении пакета из шины.
Вызывается когда в буфер FIFO1 получено очередное сообщение (при значении бита FMP[1:0] регистра CAN_RF1R не равном 00b).
0: Прерывание не генерируется
1: Генерируется прерывание
3 FOVIE0 — FIFO overrun interrupt enable Прерывание при переполнении буфера FIFO0.
Вызывается, когда буфер FIFO0 заполнен и получено еще один пакет данных при установленном бите FOVR регистра CAN_RF0R.
0: Прерывание не генерируется
1: Генерируется прерывание
2 FFIE0 — FIFO full interrupt enable Прерывание при заполнении буфера FIFO0.
Вызывается когда в буфере FIFO0 заполнены все три почтовых ящика при установленном бите FULL регистра CAN_RF0R.
0: Прерывание не генерируется
1: Генерируется прерывание
1 FMPIE0 — FIFO message pending interrupt enable Прерывание при получении пакета из шины.
Вызывается когда в буфер FIFO0 получено очередное сообщение (при значении бита FMP[1:0] регистра CAN_RF0R не равном 00b).
0: Прерывание не генерируется
1: Генерируется прерывание
0 TMEIE — Transmit mailbox empty interrupt enable Прерывание при освобождении исходящего почтового ящика.
Вызывается при окончании передачи сообщения при установленном бите RQCPx регистра CAN_TSR.
0: Прерывание не генерируется
1: Генерируется прерывание

Если Вы новичок и только начинаете изучать протокол CAN  и его использование на микроконтроллерах семейства STM32, то можно ограничиться одним прерыванием FMPIE0 (FIFO 0 message pending interrupt) и TMEIE (Transmit mailbox empty interrupt), которые отвечает за обработку получения входящего пакета в буфер FIFO 0, а также за обработку окончания отправки пакета в шину соответственно. Для первоначального изучения и тестирования этого вполне хватит, а дальше уже требуется более глубокое понимание физики процессов и специфики работы CAN шины.

CAN error status register (CAN_ESR)

Регистр отвечает за состояние ошибок при работе с bxCan.

Управление ошибками, как описано в протоколе CAN, обрабатывается полностью аппаратными средствами с помощью счетчиков ошибок передачи (TEC — Transmit error counter) и счетчиков ошибок приема сообщений (REC — Receive error counter), которые увеличивают или уменьшают свое значение в соответствии с состоянием ошибки.

Оба счетчика могут быть прочитаны с помощью программного обеспечения, чтобы определить стабильность сети.

Кроме того, аппаратное обеспечение может предоставлять более подробную информацию о текущем состоянии ошибок (LEC — Last error code).

Рис. 7. CAN error status register.

Address offset: 0x18
Reset value: 0x0000 0000

Биты Название Описание
31:24 REC[7:0] — Receive error counter Счетчик ошибок приема пакетов.
Исполняющая часть механизма контроля состояния протокола CAN. В случае возникновения ошибки во время приема пакета, этот счетчик увеличивается на 1 или на 8 в зависимости от состояния ошибки (по определению стандарта CAN).
После каждого успешного приема счетчик уменьшается на единицу или сбрасывается до 120, если его значение было выше, чем 128.
Если значение счетчика превышает 127, то контроллер bxCan переходит в пассивное состояние ошибки (устанавливается бит EPVF).
23:16 TEC[7:0] — Transmit error counter Счетчик ошибок передачи пакетов.
Аналогично REC, только для ошибок передачи.
15:17 Зарезервировано  
6:4 LEC[2:0] — Last error code Код последней ошибки.
Это поле устанавливается аппаратно и содержит значение, которое указывает на вид последней ошибки, обнаруженной на CAN шине.
Если сообщение было передано или получено без ошибок, то значение этих битов будет сброшено в ноль.
Также программно можно установить эти биты в значение 0b111, что указывает, что ошибка установлена с помощью программного обеспечения.
Коды ошибок:
000 — Нет ошибок
001 — Stuff error
010 — Form error
011 — Acknowledgment Error
100 — Bit recessive Error
101 — Bit dominant Error
110 — CRC Error
111 — Set by software

Описание ошибок приведено ниже в таблице №4.

3 Зарезервировано  
2 BOFF — Bus-off flag Bus-off флаг.
Этот бит устанавливается, когда bxCan переходит в режим Bus-off. Режим Bus-off вводится, когда счетчик ошибок передачи (TEC) становится больше чем 255.
1 EPVF — Error passive flag Флаг пассивной ошибки.
Этот бит устанавливается аппаратно, когда достигнут пассивный предел счетчиков ошибок (Счетчик приема и/или передачи больше 127).
0 EWGF — Error warning flag Флаг предупреждения об ошибках.
Бит устанавливается аппаратно, когда достигнут предел предупреждения (Счетчик ошибок приема и/или передачи ≥ 96).

Согласно описанию протокола CAN принято увеличивать счетчик ошибок REC (Receive error counter) на одну единицу при каждой обнаруженной ошибке приема на шине, а счетчик ошибок TEC (Transmit error counter) увеличивать на 8 при каждой ошибке передачи пакетов. Это связано стем, что существует предположение о том, что с наибольшей вероятностью источником ошибок на шине является передающий узел. 

Уменьшение счетчика ошибок происходит автоматически на единицу при каждом успешном приеме или передаче сообщений по шине для счетчиков REC и TEC соответственно.

Восстановление BUS-OFF

Состояние шины Bus-Off устанавливается, когда счетчик ошибок передачи превышает 255, при этом устанавливается бит BOFF регистра CAN_ESR. В этом режиме bxCan фактически перестает принимать и передавать пакеты по шине.

При настройке bxCAN можно установить бит ABOM, который отвечает за то, что если шина перейдет в режим Bus-off, то bxCan автоматически начнет проверять сигнал CAN_RX для восстановления шины. Если бит ABOM не установлен, то разработчику необходимо контролировать этот процесс самостоятельно и в случае возникновения ошибки и перехода в режим Bus-off необходимо заново проинициализировать bxCan.

Обратите внимание, что bxCan слушает порт CAN_RX только в нормальном режиме работы. Если bxCan находится в режиме инициализации, то автоматического восстановления шины не произойдет.

Долгое время не мог понять, что это за 11 рецессивных бит 128 раз подряд и где их необходимо взять и куда подать. Путем гугления и раскурки мануалов понял, что это указывается время, через которое контроллер CAN шины автоматически выйдет из режима Bus-Off и оно равно времени, которое потребуется для передачи 11 рецессивных бит 128 раз подряд.

Другими словами, контроллер автоматически выйдет из режима Bus-Off, когда на CAN шине будет «тишина» в течении времени, равному времени передачи 11 бит * 128 раз. Естественно, если в настройках контроллера мы ему указали, что он может автоматически выходить из этого режима (установлен бит ABOM регистра CAN_MCR).

Практика

Вроде все моменты, которые касаются обработки прерываний bxCan мы рассмотрели. Текста очень много, но как это применить на практике?

Давайте разбираться.

Для начала нам необходимо определится с тем, какие прерывания bxCan мы будем использовать. Конечно можно ограничиться прерыванием на получение сообщения в буфер FIFO, но мы же не ищем легких путей, поэтому проинициализируем сразу все.

За включение/отключение прерываний bxCan отвечает функция CAN_ITConfig, эти действия выполняются в модуле инициализации can шины совместно с настройкой прерываний NVIC:

Листинг №1. Включение прерываний bxCan
	// CAN Transmit mailbox empty Interrupt enable
	// Обрабатывается в прерывании USB_HP_CAN1_TX_IRQHandler
	CAN_ITConfig(CAN1, CAN_IT_TME, ENABLE);         // Прерывание при освобождении исходящего почтового ящика
	// CAN Receive Interrupt enable
	// Обрабатывается в прерывании USB_LP_CAN1_RX0_IRQHandler
	CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);        // Прерывание получения пакета в буфер FIFO 0
	CAN_ITConfig(CAN1, CAN_IT_FF0, ENABLE);         // Прерывание при заполнении буфера FIFO 0
	CAN_ITConfig(CAN1, CAN_IT_FOV0, ENABLE);        // Прерывание при переполнении буфера FIFO 0
 
	// Обрабатывается в прерывании CAN1_RX1_IRQHandler
	CAN_ITConfig(CAN1, CAN_IT_FMP1, ENABLE);        // Прерывание получения пакета в буфер FIFO 1
	CAN_ITConfig(CAN1, CAN_IT_FF1, ENABLE);         // Прерывание при заполнении буфера FIFO 1
	CAN_ITConfig(CAN1, CAN_IT_FOV1, ENABLE);        // Прерывание при переполнении буфера FIFO 1
 
	// CAN Operating Mode Interrupt enable
	// Обрабатывается в прерывании CAN1_SCE_IRQHandler
	CAN_ITConfig(CAN1, CAN_IT_WKU, ENABLE);         // Прерывание при "пробуждении" - выход из "спящего" режима
	CAN_ITConfig(CAN1, CAN_IT_SLK, ENABLE);         // Прерывание при переходе в "спящий" режим
 
	// CAN Error Interrupts
	// Обрабатывается в прерывании CAN1_SCE_IRQHandler
	CAN_ITConfig(CAN1, CAN_IT_EWG, ENABLE);         // Error warning Interrupt (error counter >= 96)
	CAN_ITConfig(CAN1, CAN_IT_EPV, ENABLE);         // Error passive Interrupt (error counter > 127)
	CAN_ITConfig(CAN1, CAN_IT_BOF, ENABLE);         // Bus-off Interrupt (error counter > 255)
	CAN_ITConfig(CAN1, CAN_IT_LEC, ENABLE);         // Last error code - при возникновении ошибок приема-передачи
	CAN_ITConfig(CAN1, CAN_IT_ERR, ENABLE);         // Прерывание при возникновении ошибок bxCan


	// NVIC Configuration
	NVIC_InitTypeDef NVIC_InitStructure;

	// Enable CAN1 TX0 interrupt IRQ channel
	NVIC_InitStructure.NVIC_IRQChannel = USB_HP_CAN1_TX_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	// Enable CAN1 RX0 interrupt IRQ channel
	NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	// Enable CAN1 RX1 interrupt IRQ channel
	NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	// Enable CAN1 SCE (Status Change Error) interrupt IRQ channel
	NVIC_InitStructure.NVIC_IRQChannel = CAN1_SCE_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

После того, как с помощью функции CAN_ITConfig() мы проинициализировали необходимые нам прерывания, необходимо включить обработчики этих прерываний. Делается это через NVIC. Если мы забудем хотя бы одно из них включить, то микроконтроллер при возникновении прерывания, которое мы забыли обработать, выкинет нас в стандартный обработчик прерываний и просто зависнет. Поможет только перезагрузка процессора, так как он будет сидеть в «вечном» цикле и ни на что больше реагировать не будет.

А теперь, чтобы этого не произошло, нам необходимо вставить в наш программный код функции обработки прерываний. Всего их четыре: прерывание при освобождении исходящего почтового ящика, два прерывания для буферов FIFO 1 и 2, а также прерывание по обработке ошибок и входа/выхода в «режим сна».

Обработка прерываний при освобождении исходящего почтового ящика

За обработку прерывания при освобождении исходящего почтового ящика отвечает функция USB_HP_CAN1_TX_IRQHandler(). Нам необходимо проверить флаг прерывания и, если он установлен, сбросить его и выполнить код обработки прерывания:

Листинг №2. Обработка прерываний bxCan для исходящего почтового ящика
void USB_HP_CAN1_TX_IRQHandler(void)
{
	// CAN Transmit mailbox empty Interrupt enable
	// Обработаем прерывания при освобождении исходящего почтового ящика
	if (CAN_GetITStatus(CAN1, CAN_IT_TME)==SET) {       // Прерывание при освобождении исходящего почтового ящика
		CAN_ClearITPendingBit(CAN1, CAN_IT_TME);
   
		// Вставляем свой код по обработке прерывания
	}
}

Функция CAN_GetITStatus() возвращает текущее состояние флага прерывания, значение может быть равным SET или RESET («Установлен» или «сброшен» соответственно). Значение SET говорит нам о том, что бит установлен и нам необходимо обработать это прерывание и не забыть сбросить его флаг.

Следует помнить о том, что необходимо всегда производить сброс флага прерывания, иначе оно будет вызываться постоянно. Исключение составляют только флаги прерываний ошибок, они почти все сбрасываются аппаратно, подробнее можно посмотреть в описании функции CAN_ClearITPendingBit().

Проверять освобождение исходящего почтового ящика имеет смысл, если Вы пересылаете большой объем данных по Can-шине: при обработке отправки сообщения выставляется флаг отправки сообщения, а в прерывании проверяется была выполнена отправка данных или нет. Если отправка была завершена без ошибок, то устанавливаем в флаг значение без ошибок и отправляем следующий пакет данных, иначе устанавливаем в флаг код ошибки и обрабатываем ее в модуле программы.

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

Проверить статус отправки сообщения можно не только с помощью прерываний, но и в момент отправки сообщения (это наверное самый оптимальный вариант):

Листинг №3. Отправка сообщения с проверкой статуса отправки
	uint32_t i = 0;
	uint8_t TransmitMailbox = 0;
	
	...
	
	TransmitMailbox = CAN_Transmit(CAN1, &TxMessage);
	i = 0;
	while ((CAN_TransmitStatus(CAN1, TransmitMailbox) != CANTXOK) && (i != 0xFF)) 	{
		i++;
	}

При передаче сообщения через функцию CAN_Transmit, она нам возвращает номер исходящего почтового ящика, в который помещено сообщение. Затем мы в цикле проверяем, отправил ли bxCan наше сообщение или нет. Если отправка прошла успешно, то статус отправки сообщения из почтового ящика будет равен CANTXOK.

По окончанию цикла while() проверяем значение переменной «i». Если оно у нас равно 0xFF, значит отправка не удалась и тут мы уже думаем что сделать: то ли сбросить отправку сообщения, то ли обработать ошибку.

Вообщем все на усмотрение разработчика.

Обработка прерываний входящего буфера сообщений FIFO 0

За прерывания для входящего буфера сообщений FIFO 0 отвечают флаги CAN_IT_FMP0, CAN_IT_FF0 и CAN_IT_FOV0.

Наименование Описание
CAN_IT_FMP0 Прерывание срабатывает при получении очередного сообщения в буфер FIFO 0.
CAN_IT_FF0  Прерывание возникает при заполнении всех трех почтовых ящиков буфера FIFO 0.
CAN_IT_FOV0 А это прерывание возникает если у нас все три почтовых ящика буфера FIFO 0 заполнены и мы получаем четвертое сообщение по шине. У нас происходит переполнение буфера.
Таб. 1. Прерывания  буфера FIFO 0

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

Фильтры помогут на аппаратном уровне отсеивать пакеты с ненужными нам данными, чтобы не тратить время и ресурсы процессора на обработку ненужной информации. Подробнее работу с фильтрами я описал в статье STM32. Почтовые ящики. Фильтры пакетов CAN.

Действия по обработке данных прерываний возлагаются на разработчика и описываются в функции USB_LP_CAN1_RX0_IRQHandler().

Листинг №4. Обработка прерываний bxCan для буфера FIFO 0
void USB_LP_CAN1_RX0_IRQHandler(void)
{
	CanRxMsg RxMessage;
	
	// CAN Receive Interrupt enable FIFO 0
	// Обработаем прерывания приемного буфера FIFO 0
	if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) == SET) {                   // Прерывание получения пакета в буфер FIFO 0
		// Флаг сбрасывается автоматически после прочтения последнего сообщения
   
		// Обнулим данные пакета
		RxMessage.DLC =     0x00;
		RxMessage.ExtId =   0x00;
		RxMessage.FMI =     0x00;
		RxMessage.IDE =     0x00;
		RxMessage.RTR =     0x00;
		RxMessage.StdId =   0x00;
		RxMessage.Data [0] = 0x00;
		RxMessage.Data [1] = 0x00;
		RxMessage.Data [2] = 0x00;
		RxMessage.Data [3] = 0x00;
		RxMessage.Data [4] = 0x00;
		RxMessage.Data [5] = 0x00;
		RxMessage.Data [6] = 0x00;
		RxMessage.Data [7] = 0x00;
		
		CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);                   // Получим сообщение
   
		// Вставляем любой свой код обработки входящего пакета
   
		}
   
	if (CAN_GetITStatus(CAN1, CAN_IT_FF0)==SET) {                       // Прерывание при заполнении буфера FIFO 0
		CAN_ClearITPendingBit(CAN1, CAN_IT_FF0);
		
		// Вставляем свой код по обработке прерывания
 		
		// Не забываем после обработки сбросить флаг ошибки
		CAN_ClearFlag(CAN1, CAN_FLAG_FF0);
	}
	
	if (CAN_GetITStatus(CAN1, CAN_IT_FOV0)==SET) {                      // Прерывание при переполнении буфера FIFO 0
		CAN_ClearITPendingBit(CAN1, CAN_IT_FOV0);
		
		// Вставляем свой код по обработке прерывания
   
		// Не забываем после обработки сбросить флаг ошибки
		CAN_ClearFlag(CAN1, CAN_FLAG_FOV0);
	}
}

Следует обратить внимание на то, что функция USB_LP_CAN1_RX0_IRQHandler() предназначена как для обработки прерываний bxCan так и для обработки прерываний USB. Если Вы будете использовать USB в своем проекте, то нужно внимательно подойти к этому моменту, чтобы правильно обрабатывать сообщения для каждого устройства.

Обратите внимание, что в обработчике прерываний мы сбрасываем не только флаг прерывания, но также сбрасываем флаг ошибки bxCan. Это немного (точнее очень много) разные вещи: флаг прерывания отвечает за то, что мы попадем в обработчик прерывания при его установке и если его не сбросить, то мы можем сидеть вечно в этом обработчике. А флаг ошибки сигнализирует нам о том, что есть ошибка и его нужно снять после того, как мы обработали эту ошибку. Соответственно, если по какой-либо причине мы ее не обработали — этот флаг снимать не следует.

Напомню, что часть флагов прерываний (и ошибок) снимаются автоматически, здесь нужно внимательно изучать руководство.

Обработка прерываний входящего буфера сообщений FIFO 1

За прерывания для входящего буфера сообщений FIFO 1 отвечают флаги CAN_IT_FMP1, CAN_IT_FF1 и CAN_IT_FOV1. Описание и действие аналогично с обработкой прерывания для буфера FIFO 0.

Наименование Описание
CAN_IT_FMP1 Прерывание срабатывает при получении очередного сообщения в буфер FIFO 1.
CAN_IT_FF1 Прерывание возникает при заполнении всех трех почтовых ящиков буфера FIFO 1.
CAN_IT_FOV1 А это прерывание возникает если у нас все три почтовых ящика буфера FIFO 1 заполнены и мы получаем четвертое сообщение по шине. У нас происходит переполнение буфера.
Таб. 2. Прерывания  буфера FIFO 1

В отличии от буфера FIFO 0, обработка этих прерываний происходит в функции CAN1_RX1_IRQHandler().

Листинг №5. Обработка прерываний bxCan для буфера FIFO 1
void CAN1_RX1_IRQHandler(void)
{
	CanRxMsg RxMessage;
   
	// CAN Receive Interrupt enable FIFO 1
	// Обработаем прерывания приеного буфера FIFO 1
	if (CAN_GetITStatus(CAN1, CAN_IT_FMP1) == SET) {              // Прерывание получения пакета в буфер FIFO 1
		// Флаг сбрасывается автоматически после прочтения последнего сообщения
   
		// Обнулим данные пакета
		RxMessage.DLC =     0x00;
		RxMessage.ExtId =   0x00;
		RxMessage.FMI =     0x00;
		RxMessage.IDE =     0x00;
		RxMessage.RTR =     0x00;
		RxMessage.StdId =   0x00;
		RxMessage.Data [0] = 0x00;
		RxMessage.Data [1] = 0x00;
		RxMessage.Data [2] = 0x00;
		RxMessage.Data [3] = 0x00;
		RxMessage.Data [4] = 0x00;
		RxMessage.Data [5] = 0x00;
		RxMessage.Data [6] = 0x00;
		RxMessage.Data [7] = 0x00;
   
		CAN_Receive(CAN1, CAN_FIFO1, &RxMessage);               // Получим сообщение
   
		// Вставляем любой свой код обработки входящего пакета
   
	}
   
	if (CAN_GetITStatus(CAN1, CAN_IT_FF1)==SET) {                   // Прерывание при заполнении буфера FIFO 1
		CAN_ClearITPendingBit(CAN1, CAN_IT_FF1);
   
		// Вставляем свой код по обработке прерывания
   
		// Не забываем после обработки сбросить флаг ошибки
		CAN_ClearFlag(CAN1, CAN_FLAG_FF1);
	}

	if (CAN_GetITStatus(CAN1, CAN_IT_FOV1)==SET) {                  // Прерывание при переполнении буфера FIFO 1
		CAN_ClearITPendingBit(CAN1, CAN_IT_FOV1);

		/ Вставляем свой код по обработке прерывания
   
		// Не забываем после обработки сбросить флаг ошибки
		CAN_ClearFlag(CAN1, CAN_FLAG_FF1);
	}
}

Обработчик для буфера FIFO 1 ничем не отличается от обработчика для буфера FIFO 0, разница есть только в наименовании функции обработчика и флагов, которые мы проверяем. Наполнение буфера FIFO 1 (как и FIFO 0) зависит исключительно от настроек фильтрации пакетов и в них же указывается какие сообщения в какой буфер будут попадать.

Подробно я о фильтрах рассказывал в статье STM32. Почтовые ящики. Фильтры пакетов CAN.

В своем проекте управления умным домом я с помощью фильтрации разделяю управляющие пакеты и пакеты с данными по разным буферам: сообщения данных попадают исключительно в буфер FIFO 0, а системные и приоритетные сообщения я помещаю в буфер FIFO 1.

Если у Вас нет задачи такой фильтрации сообщений, то можно вполне обойтись одним буфером FIFO 0  и в принципе не использовать буфер FIFO 1 (или наоборот, роли никакой не играет). Для большинства задач это будет вполне достаточно и сэкономит Вам несколько сотен байт прошивки.

Обработка прерываний по ошибкам и «спящему» режиму

Обработчик прерываний ошибок bxCan позволяет нам определить насколько стабильно работает наше устройство с протоколом CAN и вовремя принять меры для достижения максимально стабильного режима работы.

За обработку ошибок отвечают следующие флаги:

Наименование Описание
CAN_IT_ERR Error Interrupt — устанавливается при возникновении любой ошибки
CAN_IT_EWG Error warning Interrupt — предупреждение о том, что один из счетчиков ошибок достиг 96 или более ошибок
CAN_IT_EPV Error passive Interrupt — предупреждение о том, что один из счетчиков ошибок достиг более 127 ошибок
CAN_IT_BOF Bus-off Interrupt — Возникает при переходе шины в режим Bus-Off, когда любой из счетчиков ошибок превысил значение 255
CAN_IT_LEC Last error code Interrupt — активируется при возникновении ошибок приема передачи
CAN_IT_WKU Wake-up Interrupt — возникает при «пробуждении» bxCan, когда шина выходит из спящего режима.
CAN_IT_SLK Sleep acknowledge Interrupt — возникает при уходе шины в «спящий» режим.
Таб. 3. Прерывания ошибок и режима шины

Прерывание по флагам CAN_IT_WKU и CAN_IT_SLK срабатывают при входе или выходе в/из спящего режима. При этом флаг CAN_IT_ERR не устанавливается. А если же срабатывает прерывание по флагам CAN_IT_EWG, CAN_IT_EPV, CAN_IT_BOF или CAN_IT_LEC, то одновременно с ними устанавливается и флаг CAN_IT_ERR.

Флаги CAN_IT_EWG, CAN_IT_EPV и CAN_IT_BOF предназначены для контроля количества ошибок приема передачи по шине. Механизм bxxCan не только увеличивает счетчик ошибок, например когда на линии возникает помеха и счетчик ошибок увеличивается, но и уменьшает его, когда работа шины нормализуется. Флаги CAN_IT_EWG и CAN_IT_EPV являются предупреждающими о том, что идет рост ошибок и необходимо выявить причину их возникновения, а вот флаг CAN_IT_BOF нам сообщает, что счетчик ошибок достиг своего максимума и шина перешла в режим Bus-off. Выход из этого режима может произойти автоматически (при получении 128 раз 11 рецессивных бит подряд по шине) или принудительно — заново проинициализировав bxCan.

Напомню, что за автоматический выход из режима Bus-Off отвечает бит ABOM в регистре CAN_MCR. Рекомендую его устанавливать в Ваших проектах, тогда bxCan этот функционал возьмет на себя.

Флаг CAN_IT_LEC срабатывает, когда происходит ошибка приема или передачи пакета, при его обработке мы можем узнать последнюю ошибку шины или устройства.

С тем что обрабатывать — мы определились, теперь необходимо вставить в программный модуль и саму функцию обработки прерываний ошибок CAN1_SCE_IRQHandler().

В ней мы сформируем шаблон обработки исключений, возникающих при работе bxCan:

Листинг №6. Обработка прерываний засыпания/пробуждения и ошибок bxCan
void CAN1_SCE_IRQHandler(void)
{
	uint8_t errorcode = 0;

	if (CAN_GetITStatus(CAN1, CAN_IT_ERR)==SET)    {                   // Прерывание при возникновении ошибки
		CAN_ClearITPendingBit(CAN1, CAN_IT_ERR);

		// CAN Error Interrupts
		// Обработка прерываний по ошибке
		if (CAN_GetITStatus(CAN1, CAN_IT_EWG)==SET) {              // Error warning Interrupt (счетчик ошибок >= 96)
			CAN_ClearITPendingBit(CAN1, CAN_IT_EWG);

			// Вставляем свой код по обработке прерывания
		}
   
		if (CAN_GetITStatus(CAN1, CAN_IT_EPV)==SET) {              // Error passive Interrupt  (счетчик ошибок > 127)
			CAN_ClearITPendingBit(CAN1, CAN_IT_EPV);

			// Вставляем свой код по обработке прерывания
		}
   
		if (CAN_GetITStatus(CAN1, CAN_IT_BOF)==SET) {              // Bus-off. Прерывание при переполнении счетчика ошибок (>255)
			CAN_ClearITPendingBit(CAN1, CAN_IT_BOF);           // bxCan уходит в режим Bus-OFF

			// Вставляем свой код по обработке прерывания
		}
   
		if (CAN_GetITStatus(CAN1, CAN_IT_LEC)==SET) {              // Прерывание при ошибке приема передачи сообщения
			CAN_ClearITPendingBit(CAN1, CAN_IT_LEC);
			errorcode = CAN_GetLastErrorCode(CAN1);            // Получим код ошибки
   
			// Вставляем свой код по обработке прерывания

			// Не забываем после обработки сбросить флаг ошибки
			CAN_ClearFlag(CAN1, CAN_FLAG_LEC);
		}

	} else {

		// CAN Operating Mode Interrupt
		// Обработка прерываний по режимам сна/пробуждения
		if (CAN_GetITStatus(CAN1, CAN_IT_WKU)==SET) {             // Прерывание при "пробуждении" - выход из "спящего" режима
			CAN_ClearITPendingBit(CAN1, CAN_IT_WKU);

			// Вставляем свой код по обработке прерывания
   
			// Не забываем после обработки сбросить флаг ошибки
			CAN_ClearFlag(CAN1, CAN_FLAG_WKU);
		}

		if (CAN_GetITStatus(CAN1, CAN_IT_SLK)==SET) {             // Прерывание при переходе в "спящий" режим
			CAN_ClearITPendingBit(CAN1, CAN_IT_SLK);

			// Вставляем свой код по обработке прерывания
   
			// Не забываем после обработки сбросить флаг ошибки
			CAN_ClearFlag(CAN1, CAN_FLAG_SLAK);
		}
	}
}

Также как мы сбрасываем флаги прерываний, нам необходимо сбрасывать и флаги ошибок после их обработки. Но если обратить внимание на описание регистров, то мы видим, что часть флагов ошибок сбрасываются автоматически аппаратными средствами bxCan, а часть мы должны очищать вручную. Вот и сейчас при обработке ошибок мы можем сбросить только флаги CAN_IT_LEC, CAN_IT_WKU и CAN_IT_SLK, а остальные флаги ошибок в данном обработчике прерываний сбрасываются автоматически.

Не стоит забывать и о том, что перед сбросом флага ошибки мы должны ее (ошибку) обработать и уже потом менять флаг.

Еще стоит обратить внимание на обработку прерывания CAN_IT_LEC (Last Error Code), которая появляется при возникновении ошибок ввода-вывода.

В обработчике  с помощью функции CAN_GetLastErrorCode() мы заполняем переменную errorcode данными о последней ошибке. Она может принимать следующие значения:

Вид Определение Описание
0x00 CAN_ErrorCode_NoErr Нет ошибок
0x10 CAN_ErrorCode_StuffErr Когда узел передает последовательно в шину 5 бит с одинаковым значением, то он добавляет шестой бит с противоположным значением. Принимающие узлы этот дополнительный бит удаляют. Если узел обнаруживает на шине больше 5 последовательных бит с одинаковым значением, то он генерирует ошибку Stuff Error.
0x20 CAN_ErrorCode_FormErr Некоторые части CAN-сообщения имеют одинаковое значение во всех типах сообщений. Т.е. протокол CAN точно определяет какие уровни напряжения и когда должны появляться на шине. Если формат сообщений нарушается, то узлы генерируют ошибку Form Error.
0x30 CAN_ErrorCode_ACKErr Каждый узел получив правильное сообщение по сети посылает в сеть доминантный (0) бит. Если же этого не происходит, то передающий узел регистрирует ошибку Acknowledgement Error.
0x40 CAN_ErrorCode_BitRecessiveErr Ошибка установки рецессивного бита.
Каждый узел во время передачи битов в сеть сравнивает значение передаваемого им бита со значением бита которое появляется на шине. Если эти значения не совпадают, то узел генерирует ошибку Bit Error. Естественно, что во время арбитража на шине (передача поля арбитража в шину) этот механизм проверки ошибок отключается.
0x50 CAN_ErrorCode_BitDominantErr Ошибка установки доминантного бита.
Каждый узел во время передачи битов в сеть сравнивает значение передаваемого им бита со значением бита которое появляется на шине. Если эти значения не совпадают, то узел генерирует ошибку Bit Error. Естественно, что во время арбитража на шине (передача поля арбитража в шину) этот механизм проверки ошибок отключается.
0x60 CAN_ErrorCode_CRCErr Каждое сообщение CAN содержит CRC сумму, и каждый принимающий узел подсчитывает значение CRC для каждого полученного сообщения. Если подсчитанное значение CRC суммы, не совпадает со значением CRC в теле сообщения, принимающий узел генерирует ошибку CRC Error.
0x70 CAN_ErrorCode_SoftwareSetErr Установлено программно. Можно заполнить единицами данные битов LEC[2:0] регистра CAN_ESR. 
 Таб. 4. Виды ошибок, возвращаемые при вызове функции CAN_GetLastErrorCode()

Таким образом, обрабатывая флаг CAN_IT_LEC и изучая ошибки, которые происходят при работе с CAN, мы можем заблаговременно выявить причину и предпринять некоторые действия для того, что бы предотвратить рост ошибок и сваливание CAN контроллера в режим Bus-Off.

Заключение

В этой статье я постарался подробно описать механизмы обработки прерываний bxCan, а также механизмы связанные с обработкой ошибок и методы по их сокращению. Напомню, что все тестирование кода производилось на базе микроконтроллера STM32F103C6.

Вполне вероятно, что я что-то упустил, где то наоборот ошибся и был не точен. Поэтому если есть какие-либо замечания и/или предложения к статье — добро пожаловать в комменты и я исправлюсь.

Как обычно, во вложениях весь исходный код, который рассматривается в рамках данной статьи. Но в отличие от предыдущих постов, исходный код для этой статьи представляет собой некий шаблон, который предполагается использовать в разных проектах.

З.Ы. Простите за много букафф )))

Здравствуйте.

У большинства читателей шина CAN ассоциируется с автомобилем. Однако если вы не автолюбитель не спешите закрывать статью так как использовать CAN можно где угодно, например в постройке «умного дома», или ещё где-то, где необходима надёжная передача данных.

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

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

Единственный неудобный момент, это необходимость в специальном CAN-трансивере (маленькая восьминогая микруха). Чтобы ничего не паять, можно купить на Али вот такие штуки…

Этот трансивер (SN65HVD230, маркируется как VP230) питается от 3.3V, что несомненно является удобством при работе с stm32. Так же можно использовать другие популярные трансиверы — MCP2551 и TJA1050 (он же A1050). У TJA1050 есть небольшой недостаток — в даташите написано что он не поддерживает скорости ниже 60Кбит/с, правда у меня работал даже на меньших скоростях, но не всегда стабильно. Впрочем это не страшно так как вряд ли вы будете использовать столь низкую скорость в своих проектах.

Контакты CTX и CRX подключаются к stm32, а CANH и CANL к витой паре. «Земли» соединять не нужно, это дифференциальный сигнал. К каждой stm’ки подключается свой трансивер.

Топология сети такова…

На шине могут находиться два и более устройств (можно любое количество, однако практически оно ограничивается нагрузочной способностью передатчиков и колеблется от 100 до 200). На двух крайних устройствах должны быть установлены терминирующие резисторы (120 Ом). На платках, про которые я писал выше они уже есть. Если на шине присутствуют промежуточные устройства, то на них резисторы не устанавливаются (на платках выше, их нужно выпаять).

Не рекомендуется делать длинные ответвления для промежуточных устройств, по хорошему ответвлений вообще не должно быть, должно быть так…


Сеть похожа на RS485, но в CAN-шине не нужно программно выбирать направление передачи, нет мастера и слейвов, и нет идентификаторов устройств как у I2C. Каждый шлёт свои данные когда хочет, получают их все присутствующие на шине устройства, а разруливается всё это аппаратно самим CAN-интерфейсом. Подробнее об это ниже.

Пока вы не приобрели трансиверы, можно будет поэкспериментировать без них, для этого есть специальный режим когда одна stm’ка, может сама себе посылать пакеты на одном интерфейсе (и нет, для этого не нужно замыкать между собой ножки как это иногда делается с UART’ом, так работать не будет).

Максимальная скорость передачи 1Мбит/с. Сейчас делаются устройства с большей скоростью (CAN FD), но это нас не интересует.

Длина линии зависит от скорости передачи:

1000 Кбит/с — 40 метров.
500 Кбит/с — 100 метров.
250 Кбит/с — 200 метров.
125 Кбит/с — 500 метров.
10 Кбит/с — 6 километров.

Это наиболее распространённые скорости в промышленности, но встречаются и другие — 800 Кбит/с, 400 Кбит/с, 83.333 Кбит/с, 50 Кбит/с, 20 Кбит/с. Да и вообще можно настроить любую скорость в своих проектах, правда смысла в этом особого нет.


Теперь перейдём к более подробному рассмотрению CAN-шины.

Далее я буду называть устройства подключённые к шине узлами.

Данные в CAN-шине передаются небольшими пакетами, которые называются кадрами (frame). Существуют четыре типа кадров:

Data Frame — основной кадр, в котором передаются различные полезные данные.

Remote Frame — это кадр не содержит полезных данных, и служит для того, чтобы «попросить» какой-либо узел послать кадр. То есть, какой-то узел посылает этот кадр в сеть, а другой узел (который понимает что обращаются к нему) отправляет кадр с полезными данными. Такой способ подходит для опроса каких-то датчиков и немного снижает нагрузку на сеть. О том, как узел понимает что обращаются к нему написано ниже. А вот тут написано что не рекомендуется использовать Remote Frame.

Error Frame — кадр ошибки. Когда один из узлов обнаруживает ошибку формата кадра, он посылает в сеть Error Frame (имеет наивысший приоритет). Другие узлы получая Error Frame понимают что в сети произошла ошибка и последнее сообщение надо считать некорректным.

Overload Frame — этот кадр посылается узлом, который сильно перегружен и не успел переварить входящие сообщения. Послав в сеть кадр Overload Frame он как-бы просит другие узлы подождать немножко и повторно прислать сообщения. Это тоже происходит аппаратно. Сейчас этот кадр практически не используется так как CAN-контроллеры достаточно мощные чтоб успевать всё обработать.

Кадры Error Frame и Overload Frame передаются узлами аппаратно, без нашего участия.

Начнём с детального разбора Data Frame и потихоньку коснёмся всего.

Если мы подключимся к шине CAN-снифером работающим с программой Can Hacker и пульнём в шину кадр, то увидим в программе следующее…


Так выглядит кадр в CAN-шине глазами обывателя. Здесь есть идентификатор кадра (ID), количество полезных байт (DLC), и сами полезные байты (Data), восемь штук. Для анализа CAN-шины этого достаточно.

Все данные в CAN записываются в HEX формате. То есть в нашем случае идентификатор — это 0x0280.

К недостаткам CAN-шины можно отнести ограниченное количество передаваемых полезных байт в одном кадре, их может быть не больше восьми. Меньше можно.

А теперь посмотрим на кадр глазами программиста…

Здесь мы видим:

SOF (Start of Frame) — стартовый бит сообщающий о начале кадра. Позже мы к нему вернёмся так как он выполняет важную роль в работе всей сети.

Identifier — идентификатор кадра. Подчеркну,

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

. То есть, при проектировании сети вы прописываете в каждом узле какие идентификаторы ему нужно принять и обработать, а какие игнорировать. При этом вам не надо проверять идентификаторы «вручную» в своей программе. Для этого у stm32 есть аппаратная фильтрация кадров, которая с помощью настраиваемых фильтров сама решает что сделать с кадром имеющим тот или иной идентификатор. То есть, например, вы настроили фильтр так, чтоб принимался только кадр с идентификатором 280, тогда кадры с другими идентификаторами будут отбрасываться аппаратно, не создавая нагрузки на ЦПУ. Фильтры можно настраивать на приём нескольких идентификаторов или группы, или нескольких групп. В общем достаточно гибкая система. Благодаря этой аппаратной фильтрации вам не нужно беспокоится о том, переварят ли вашы узлы весь трафик, даже если он очень плотный.

Идентификаторы бывают двух видов. Изначально, когда проектировалась шина CAN было решено сделать идентификатор 11-ти битным, то есть количество идентификаторов было от 0х00 до 0x07FF (0 — 2047), то есть всего 2048. Спустя некоторое время пришли к выводу что это слишком мало и придумали 29-ти битный идентификатор — от 0х00 до 1FFFFFFF (0 — 536870911). Однако для совместимости с прежней системой решили не увеличивать поле для идентификатора у старого кадра, а сделали два вида кадров. Первый остался с 11-ти битным идентификатором и его назвали

стандартным

. Второй получил 29-ти битный идентификатор и его назвали

расширенным

. Таким образом у CAN-шины появилось два вида Data Frame, стандартный и расширенный.

расширенный кадр

Расширенный кадр выглядит немного иначе…

После 11 бит идентификатора идёт бит SSR-бит (Substitute Remote Request — «заменяющий RTR-бит»), потом идёт бит IDE (см. ниже) с записанной в него единицей сообщающей что это расширенный кадр, после чего идут ещё 18 бит идентификатора, а далее всё как у стандартного кадра.

Поле

Identifier

на картинке обведено фигурной скобкой подписанной как

Arbitration

. Это говорит о том, идентификатор выполняет ещё и арбитраж на шине — задаёт приоритет кадра.

Чем меньше числовое значение идентификатора кадра, тем выше у него приоритет над другими кадрами

(см. ниже). Помимо этого, у расширенного кадра приоритет выше чем у стандартного.

RTR — один бит. Если этот бит равен нулю, значит передаётся

Data Frame

, если равен единице, значит передаётся

Remote Frame

.

IDE — один бит. Если этот бит равен нулю, значит передаётся стандартный кадр, если равен единице, значит передаётся расширенный кадр.

R — зарезервированный на будущее бит.

DLC — четыре бита определяющие количество полезных байт (DATA) в кадре.

DATA — полезные данные.

CRC — контрольная сумма кадра. Аппаратно вычисляется на основе передаваемых битов (от SOF до поля DATA включительно) и полинома генератора G(x), определенного в ISO 11898-1.

DEL — разделительный бит.

ACK — это бит подтверждения корректной отправки кадра. Перед отправкой кадра передатчик записывает в этот бит единицу… А вот чтоб объяснить что происходит дальше придётся кратко описать работу протокола CAN

(чуть ниже)

.

EOF (End of Frame) — конец кадра. Семь бит высокого уровня.

ITM — три бита высокого уровня для отделения переданного кадра от следующего.

В спецификации CAN высокий уровень

(логическая единица)

называется рецессивным, а низкий

(логический ноль)

доминантным. Доминантный бит имеет приоритет над рецессивным. Короче говоря, ноль важнее единицы (ниже будет понятно к чему это сказано).

Когда никто ничего не передаёт, на линии устанавливается напряжение ~2.5 вольта, это значит что шина свободна.

дополнительно

Напряжения логических уровней на шине могут быть разные, но суть остаётся прежняя.

Если у вас один узел работает с 3-х вольтовым трансивером, а другой с 5-ти вольтовым, то в этом нет ничего страшного.

Когда узел хочет передать кадр, он «щупает» шину и если она свободна начинает передачу. Если же шина окажется занята, тогда узел не станет ничего передавать, а будет постоянно «прощупывать» шину в ожидании когда она освободится. Как только она освободится узел тут же начнёт передачу. Такой механизм позволяет узлам не мешать друг другу.

Однако не всё так просто. Поскольку в CAN-шине происходит весьма интенсивный обмен данными, часто происходит так, что два или несколько узлов видят что шина свободна и одномоментно начинают передачу. Вот тут в дело вступает побитовый арбитраж шины основывающийся на идентификаторах кадров.

Арбитраж работает очень просто. Итак, у нас два (или более) узла начали одновременно передавать кадры в шину…


Первый узел (Device A) передаёт кадр с идентификатором 0х0647, а второй (Device В) с идентификатором 0х06C7.

Поскольку узлы ничего не знают друг о друге, то при передаче каждого бита они постоянно мониторят («прощупывают») линию. Если узел передающий в данный момент единицу обнаружит что кто-то в этот же момент передаёт ноль, тогда он тут же прекратит передачу (так как ноль имеет более высокий приоритет чем единица) и будет ждать освобождения шины. Это проиллюстрировано на картинке выше.

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

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

Исходя из прочитанного про арбитраж, становиться ясно что функционирование всей шины, её стабильность и правильная работа, завязана на том, что все узлы должны быть чётко синхронизированы между собой. Синхронизация узлов происходит каждый раз когда кто-то начинает передачу, то есть когда на линии появляется SOF — стартовый бит. Как только он появился все узлы тут же начинают отсчёт времени. Ещё существует ресинхронизация во время передачи кадра, но об этом позже.

И наконец про ACK-бит проверки доставки кадра. Как уже говорилось выше, когда узел передаёт кадр он записывает в этот бит единицу и постоянно «щупает» линию. Когда какой-либо из узлов примет этот кадр, он проверит его на валидность (совпала CRC) и если всё нормально, тогда он изменит этот бит с единицы на ноль. Передающий узел тут же «увидит» что бит изменился, и поймёт что кадр успешно доставлен. При этом никто не знает какой именно узел перевернул бит, может тот кому «нужен» этот кадр, а может тот кто не пропустит его через фильтр и отбросит. Таким образом единственное в чём может быть уверен передающий узел, это то, что хотя бы один из узлов в сети правильно принял его кадр. По идее если линия исправна, то все узлы должны были принять этот кадр.

Если никто из узлов не «ответил», тогда в зависимости от настроек, кадр будет отправлен повторно, либо не будет (см. ниже «Automatic Retransmision»).

Далее займёмся непосредственно настройкой и продолжим изучение.


Традиционно я буду делать описание для BluePill, однако оно так же справедливо и для других камней. Настройка двух CANов на одном МК тоже будет описана.

Если у вас камень с двумя CANами, тогда настраивайте первый, второй не трогайте.

Итак, первым делом нужно посмотреть в мануале на какой шине у вас висит интерфейс CAN. У F103, F105, F205, F303, F407 и F446 это шина

APB1


Желательно чтоб частота этой шины была либо 16 МГц, либо 32 МГц. Это нужно для оптимальной настройки.

Теперь активируем CAN и видим различные настройки (у меня уже настроено для скорости 500Кб)

Первый раздел (Bit Timings Parameters) самый сложный, он отвечает за настройку таймингов и скорости передачи.

Выше я писал что CAN-интерфейс тщательно мониторит линию. Так вот, мониторится не просто логические уровни, а каждый бит (не байт, а именно бит) «раскладывается» на сегменты которые должны длиться определённое количество квантов времени.

В данном примере один квант времени равен 125.0 наносекунд (Time Quantum).

Вот так выглядит один бит разложенный на сегменты SYNC_SEG, PROP_SEG, PHASE_SEG1 и PHASE_SEG2…

Теперь разберёмся что же мы будем настраивать и как оно работает…

System Clock

это частота шины APB1. Предделителем (Prescaler) мы делим её на какое-то число и получаем время одного кванта (tQ). То есть один квант будет равен одному «тику»

CAN System Clock

.

Согласно спецификации CAN, сегмент SYNC_SEG всегда длится ровно один квант. Остальные три сегмента могут длится от 1 до 8 квантов. Их количество нужно настраивать. Время одного кванта и количество этих квантов в одном бите определяет скорость шины (см. ниже).

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

Sample Point

— это точка в которой происходит «захват» бита.

Количество квантов в сегментах PROP_SEG, PHASE_SEG1 и PHASE_SEG2 настраивается так, чтобы Sample Point находилась в районе 87.5% длительности бита (см. ниже).

Все эти сегменты работают аппаратно.

Это очень поверхностное описание, сделанное чтоб вы понимали за что отвечают настройки в Кубе. Все подробности и нюансы можно почитать в этом мануале.

Итак, вернёмся в Куб и теперь уже более осмысленно посмотрим что у меня там настроено…

Указываем

Prescaler (for Time Quantum)

4 и получаем время одного кванта

Time Quantum

125 нс. Частота APB1 у нас 32МГц, делим на 4, получаем 8МГц, 1 / 8000000 = 0.000000125.

Time Quanta in Bit Segment 1

— это количество квантов из которых состоят два сегмента PROP_SEG и PHASE_SEG1 (они объединены).

Time Quanta in Bit Segment 2

— это количество квантов из которых состоит последний сегмент (PHASE_SEG2).

В результате получается что один бит у нас состоит из 16 квантов — SYNC_SEG (один квант) + Bit Segment 1 + Bit Segment 2, общей длительностью 2000 нс (Time for one Bit). Соответственно скорость передачи данных (Baud Rate) получилась 500 Кбит/с.

ReSynchronization Jamp Width

— это значение от 1 до 4 квантов, на которое может аппаратно увеличиваться или уменьшаться длительность сегментов PHASE_SEG1 и PHASE_SEG2 для более точной коррекции. Цель коррекции — поместить точку «захвата» бита в более удачный момент и ресинхронизировать узлы на шине. Короче говоря, на сколько я понимаю, если линия очень длинная или вокруг неё много помех, из-за чего может проявляться неустойчивая работа, тогда это значение можно увеличить для улучшения стабильности. Однако увеличивая это значение вы снижаете скорость передачи.

Ну и на конец разберёмся откуда взялись цифры 13 (Bit Segment 1) и 2 (Bit Segment 2).

Откройте онлайн калькулятор…


В выпадающем списке выберите

ST Microelectronics bxCAN

и в

Clock Rate

укажите частоту APB1 шины, в нашем случае это 32.

Как видите здесь написано что 87.5% это предпочтительное значение для захвата бита (выше я про это говорил). Больше нам ничего не нужно. Теперь нажмите кнопку

Request Table

и получите такую картинку…

В первой колонке показаны скорости которые вы можете настроить. Смотрим значения для 500 Кбит/с (жёлтым выделены оптимальные значения). Всё как в нашем примере — предделитель 4, общее количество квантов 16, 13 квантов для Bit Segment 1, и 2 кванта для Bit Segment 2. Sample Point получился 87.5%.

Если например захотите настроить скорость 250 Кбит/с, тогда достаточно изменить только предделитель, а если 800 Кбит/с, тогда только количество квантов для сегментов. Думаю всё понятно, и как и обещал всё просто. Вы сможете

Почему я писал что желательно чтоб частота APB1 была 32 или 16 МГц. Если указать частоту например 36 МГц, тогда мы получим вот такой результат…


Видно что Sample Point немного «уплывает», но это не критично, можно работать.


Далее нам нужно разобраться со следующим блоком настроек —

Basic Parameters

.

Time Triggered Communication Mode

— в этом документе, описывающим работу этого пункта, написано что это сложный в понимании (прям так и написано) механизм синхронизации узлов. Суть его примерно следующая: в обычном режиме у нас синхронизация происходит по стартовому биту, а если включить этот пункт тогда узел превращается в Time Master и с определённым интервалом (есть внутренний счётчик) начинает посылать в сеть сообщения (кадры), по которым другие узлы синхронизируются. Плюс к этому, вроде как устанавливаются временные рамки для передачи сообщений узлами. Точно я не могу сказать ибо сам не стал глубоко вникать в этот вопрос. Так же мне не понятно поддерживается ли это любыми CAN-сетями и устройствами. В общем я это не включаю.

Вот ещё пара документа на эту тему, кому какой лучше «зайдёт» )))

www.can-cia.org/fileadmin/resources/documents/proceedings/2006_fredriksson.pdf

www.cs.put.poznan.pl/wswitala/download/pdf/CiA2000Paper_1.pdf

Да, далее я буду называть кадры сообщениями, и наоборот.

Automatic Bus-Off Management — это важный и полезный пункт. У модуля CAN есть два счётчика ошибок —

Transmit Error Counter

(счетчик ошибок передачи) и

Receive Error Counter

(счетчик ошибок приема). При возникновении ошибки приёма Receive Error Counter увеличивается на единицу, при ошибке передачи Transmit Error Counter увеличивается сразу на 8

(видимо это объясняется тем, что отправка считается более важным действием)

. При успешном приёме или передаче соответствующий счётчик уменьшается на единицу. Если по причинам неисправности сети какой либо из счётчиков достигнет значения 255, модуль CAN перейдёт в состояние Bus-Off — ничего не передаёт и не отправляет в шину.

Если Automatic Bus-Off Management включён, тогда модуль CAN, будет автоматически восстанавливаться. Восстановление заключается в том, что модуль ждёт когда на шине будет «тишина» (рецессивное состояние) в течении времени, равное времени передачи 11 бит 128 раз подряд, и если всё окей, тогда включается в работу.

При скорости 500Kbit/s один бит передаётся за 2мкс. Значит чтобы шина восстановилась нужно чтоб линия находилась в рецессивном состоянии в течении 2.8 мс (128 * 11 * 2мкс = 2816мкс).

Если Automatic Bus-Off Management отключён, тогда надо «вручную» отлавливать момент перехода узла в состояние Bus-Off и переинициализировать его. В общем желательно включить этот пункт.

Automatic Wake-Up Mode — здесь всё понятно, если включено, то активность на шине разбудит спящий узел без дополнительных программностей (слово новое придумал)

Automatic Retransmission — если этот пункт включён, тогда узел будет повторять попытки отправить сообщение если не получает подтверждения приёма

(как вы помните, это инвертированный ACK-бит)

.

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

Вне зависимости от того, включён

Automatic Retransmission

или выключен, если передатчик не получает подтверждение приёма, выкидывается ошибка HAL_CAN_ERROR_ACK (не получил подтверждение приёма).

Если используется режим

Time Triggered Communication

(см. выше), тогда пункт

Automatic Retransmission

должен быть отключён.

Тут стоит сказать ещё пару слов: представим простую схему из двух узлов — один передаёт несколько разных кадров, другой их принимает. Если мы разорвём один любой провод шины (между трансиверами), тогда передатчик будет продолжать передавать, а приёмник будет принимать. То есть сигнал будет идти по

одному

проводу. Такая вот супер устойчивость CAN шины. Однако нюанс в том, что передатчик будет постоянно сыпать ошибкой HAL_CAN_ERROR_ACK. Это я к тому, что если повредите один провод, а система продолжит работу, то не удивляйтесь. При этом, если

Automatic Retransmission

включён, тогда передатчик будет долбить одним и тем же кадром, пытаясь повторить отправку.

Я провел этот эксперимент на линии длиной около полуметра. Как будут обстоять дела на длинных дистанциях не знаю.

В общем работа CAN шины весьма не проста.

Вот тут описана интересная, но невероятная, ситуация с Bus-Off и Automatic Retransmission.

Receive Fifo Locked Mode — у приёмника есть два независимых буфера (RX_FIFO_0 и RX_FIFO_1), можно пользоваться одним буфером или обоими. Какие сообщения будут попадать в нулевой или в первый буфер зависит от настроек фильтров (см. ниже). Каждый из буферов разделён на три ячейки называющиеся почтовыми ящиками (см. рис. ниже). Каждый почтовый ящик может хранить одно сообщение.

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

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

Включить или отключить этот режим, решать вам, исходя из того что важнее.

Transmit Fifo Priority — у передатчика тоже есть буфер (один) разделённый на три почтовых ящика. Отправляемые сообщения помещаются в эти почтовые ящики, а оттуда уже улетают в сеть. Если слишком часто отправлять сообщения, то они будут накапливаться в этих ящиках.

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

Если режим отключён, тогда первыми улетают сообщения с более высоким приоритетом (как вы помните приоритет задаётся идентификатором). Таким образом, при очень интенсивной отправке сообщений может получится так, что сообщение с низким приоритетом никогда не будет отправлено, и так и будет валяться в своём ящике всё время.

Схема почтовых ящиков…


Сверху мы видим три почтовых ящика для отправки, которые управляются блоком Transmission Scheduler, и шесть почтовых ящиков для приёма. Active Core это грубо говоря сам интерфейс, Memory Access Controller разруливает трафик, а Acceptance Filters это аппаратные фильтры, которые пропускают или отбрасывают сообщения в зависимости от настроек этих самых фильтров, давая или не давая им попасть во входящие почтовые ящики.

Вся работа с почтовыми ящиками (кроме вычитывания входящих сообщений и отправки) происходит на аппаратном уровне.

И на конец последний пункт настройки —

Operating Mode

— режим работы или способ подключения.

Normal


К плате подключён трансивер и она работает к полноценный узел в сети из двух и более устройств. То есть обычная работа.

Loopback


Плата с трансивером будет передавать данные в шину и слушать себя же одновременно. Данные из шины получать не будет.

Silent


Плата с трансивером получает данные из шины, но сама ничего не передаёт. Этот вариант подойдёт когда вам нужно почитать CAN-шину в автомобиле, и при этом ничего туда случайно не отправить.

Loopback combined with Silent


Плата без трансивера. Все данные крутятся внутри МК. Этот режим подойдёт для тестирования программы при отсутствие трансивера и другого узла.

Теперь пришло время попрограммировать.


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

Loopback combined with Silent

. Укажите его в настройках, остальное сделайте как на картинках и включите прерывания…


Прерывания для буфера RX_FIFO_0 (CAN RX0) будет вызываться когда прилетит кадр и пройдя через фильтры будет помещён в один из трёх почтовых ящиков. В какой именно ящик для нас не имеет значения, важно сразу же его прочитать, прямо в прерывании. Буфером RX_FIFO_1 мы не будем пользоваться, хватит и одного, позже мы вернёмся к этому вопросу.

Обратите внимание что у F103 (и у некоторых других камней) это прерывание пересекается с прерыванием от USB. Если вы планируете пользоваться USB, тогда включите прерывание для буфера RX_FIFO_1, буфером RX_FIFO_0 вы не будете пользоваться. Работе USB он не помешает.

Прерывание CAN SCE вызывается при возникновении ошибок.

Прерывание CAN TX вызывается когда кадр отправлен — оно нам не сильно интересно поэтому не включаем.

Объявляем глобально две структуры, два массива и переменную…

/* USER CODE BEGIN PV */
CAN_TxHeaderTypeDef TxHeader;
CAN_RxHeaderTypeDef RxHeader;
uint8_t TxData[8] = {0,};
uint8_t RxData[8] = {0,};
uint32_t TxMailbox = 0;

Структура TxHeader отвечает за отправку кадров, ниже мы её заполним.
Структура RxHeader для принятого кадра, из неё мы будем читать при приёме.
В массив TxData мы будем заносить полезные данные которые хотим передать.
В массиве RxData будут лежать полезные данные из принятого кадра.
С переменной TxMailbox ничего делать не нужно.

Добавляем колбек для приёма данных (для буфера RX_FIFO_0)

/* USER CODE BEGIN 0 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);	 
    }
}

При приёме любого кадра мы тут же забираем его из почтового ящика с помощью функции

HAL_CAN_GetRxMessage(…)

и мигаем светиком.

Если у вас настроено прерывание для буфера RX_FIFO_1, тогда колбек такой…

void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO1, &RxHeader, RxData) == HAL_OK)
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);	 
    }
}

void HAL_CAN_RxFifo1… и CAN_RX_FIFO1.

И колбек для ошибок…

void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan)
{
    uint32_t er = HAL_CAN_GetError(hcan);
    sprintf(trans_str,"ER CAN %lu %08lX", er, er);
    HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 100);
}

Перед бесконечным циклом заполняем структуру отвечающую за отправку кадров…

/* USER CODE BEGIN 2 */
TxHeader.StdId = 0x0378;
TxHeader.ExtId = 0;
TxHeader.RTR = CAN_RTR_DATA; //CAN_RTR_REMOTE
TxHeader.IDE = CAN_ID_STD;   // CAN_ID_EXT
TxHeader.DLC = 8;
TxHeader.TransmitGlobalTime = 0;

StdId

— это идентификатор стандартного кадра.

ExtId

— это идентификатор расширенного кадра. Мы будем отправлять стандартный поэтому сюда пишем 0.

RTR = CAN_RTR_DATA

— это говорит о том, что мы отправляем кадр с данными (Data Frame). Если указать CAN_RTR_REMOTE, тогда это будет Remote Frame.

IDE = CAN_ID_STD

— это говорит о том, что мы отправляем стандартный кадр. Если указать CAN_ID_EXT, тогда это будет расширенный кадр. В StdId нужно будет указать 0, а в ExtId записать расширенный идентификатор.

DLC = 8

— количество полезных байт передаваемых в кадре (от 1 до 8) .

TransmitGlobalTime

— относится к Time Triggered Communication Mode, мы это не используем поэтому пишем 0.

Заполняем массив для отправки полезных данных каким-нибудь хламом…

for(uint8_t i = 0; i < 8; i++)
{
    TxData[i] = (i + 10);
}

Запускаем CAN…

HAL_CAN_Start(&hcan);

Если CANов несколько, тогда запускаем первый…

HAL_CAN_Start(&hcan1);

И активируем события которые будут вызывать прерывания…

HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_ERROR | CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE);

CAN_IT_RX_FIFO0_MSG_PENDING

— вызовет прерывание при получении сообщения в буфер CAN_RX_FIFO0. Колбек для этого мы прописали. Если используется буфер CAN_RX_FIFO1, тогда макрос такой — CAN_IT_RX_FIFO1_MSG_PENDING.

Если используются оба буфера, тогда так…

HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING | CAN_IT_ERROR | CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE);

Остальное это прерывания из-за различных ошибок. Колбек для них мы тоже прописали. По большому счёту ошибки будут происходить только если что-то не в порядке с линией. Можно добавить в колбек мигалку красным светиком.

Вот все возможные события, которые можно добавить в функцию…


stm32f1xx_hal_can.h

И различные колбеки…


stm32f1xx_hal_can.c

Думаю что тут всё понятно из названий. Да и опять же, можно спокойно обходится без этих прерываний.

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

static void MX_CAN_Init(void)

.

Добавляем в неё структуру для конфигурации фильтров…

/* USER CODE BEGIN CAN_Init 0 */
CAN_FilterTypeDef  sFilterConfig;
/* USER CODE END CAN_Init 0 */

И настройку фильтра…

/* USER CODE BEGIN CAN_Init 2 */
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; 
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
//sFilterConfig.SlaveStartFilterBank = 14;

if(HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE END CAN_Init 2 */

Подробно про фильтры будет рассказано в следующей части, а здесь только краткое пояснение.

У каждого модуля CAN есть 14 фильтров. У F103 один модуль CAN соответственно у него 14 фильтров с 0 по 13. Камни в которых есть два модуля CAN имеют в наличии 28 фильтров, с 0 по 13 для CAN_1, и с 14 по 28 для CAN_2. Каждый фильтр называется «банком» и имеет порядковый номер. В данном случае мы используем банк-фильтр номер 0 (первый элемент структуры).

Пропускаем несколько элементов

(в них записываются идентификаторы кадров, которые нужно принять)

и видим что в элемент FilterFIFOAssignment мы записали макрос CAN_RX_FIFO0. Это значит что фильтр будет работать с буфером RX_FIFO_0. Поскольку мы не настроили никаких фильтров для буфера RX_FIFO_1, он не будет принимать участия в работе, все сообщения будут приходить только в буфер RX_FIFO_0. Если вы настраивали прерывание для буфера RX_FIFO_1, тогда в FilterFIFOAssignment нужно записать CAN_RX_FIFO1.

Чтобы задействовать оба буфера нужно настроить хотя бы два фильтра для разных идентификаторов и тогда можно будет в один буфер пропускать одни сообщения, а в другой другие. Тогда будет смысл в использовании обоих буферов.

В конце вызывается функция конфигурации фильтра.

В результате функция инициализации CAN будет выглядеть так…

static void MX_CAN_Init(void)
{
  /* USER CODE BEGIN CAN_Init 0 */
  CAN_FilterTypeDef  sFilterConfig;
  /* USER CODE END CAN_Init 0 */

  /* USER CODE BEGIN CAN_Init 1 */

  /* USER CODE END CAN_Init 1 */
  hcan.Instance = CAN1;
  hcan.Init.Prescaler = 4;
  hcan.Init.Mode = CAN_MODE_NORMAL;
  hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
  hcan.Init.TimeSeg1 = CAN_BS1_13TQ;
  hcan.Init.TimeSeg2 = CAN_BS2_2TQ;
  hcan.Init.TimeTriggeredMode = DISABLE;
  hcan.Init.AutoBusOff = ENABLE;
  hcan.Init.AutoWakeUp = DISABLE;
  hcan.Init.AutoRetransmission = ENABLE;
  hcan.Init.ReceiveFifoLocked = DISABLE;
  hcan.Init.TransmitFifoPriority = ENABLE;
  if (HAL_CAN_Init(&hcan) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CAN_Init 2 */
  sFilterConfig.FilterBank = 0;
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; 
  sFilterConfig.FilterIdHigh = 0x0000;
  sFilterConfig.FilterIdLow = 0x0000;
  sFilterConfig.FilterMaskIdHigh = 0x0000;
  sFilterConfig.FilterMaskIdLow = 0x0000;
  sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
  sFilterConfig.FilterActivation = ENABLE;
  //sFilterConfig.SlaveStartFilterBank = 14;

  if(HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE END CAN_Init 2 */

}

И на конец последнее что нужно сделать, это добавить в бесконечный цикл отправку сообщений…

/* USER CODE BEGIN WHILE */
while (1)
{
	while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0);

	if(HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox) != HAL_OK)
	{
		HAL_UART_Transmit(&huart1, (uint8_t*)"ER SEND\n", 8, 100);
	}
	
	HAL_Delay(500);
}

Функция

HAL_CAN_GetTxMailboxesFreeLevel(…)

возвращает количество свободных ящиков для отправки (как вы помните у нас их три), то есть она должна вернуть 1, 2 или 3. Если все ящики заняты (сообщения не успели улететь) тогда будет возвращаться 0. Соответственно мы тормозим программу пока не освободится хотя бы один ящик.

Функция

HAL_CAN_AddTxMessage(…)

отправляет сообщение в почтовый ящик (оттуда оно улетает автоматически). Аргументами мы передаём структуру в которой у нас записаны настройки сообщения (идентификатор и т.д.), и массив с полезными данными. Последний аргумент нам не интересен.

Собственно это всё, можно прошивать. Плата будет сама себе отправлять сообщения, а светик будет мигать в колбеке приёма.

Чтобы было немного интереснее, будем передавать сообщения с разными идентификаторами и менять нулевой элемент в массиве с полезными данными…

/* USER CODE BEGIN WHILE */
while (1)
{
	TxHeader.StdId = 0x0378;
	TxData[0] = 90;

	while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0);

	if(HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox) != HAL_OK)
	{
		HAL_UART_Transmit(&huart1, (uint8_t*)"ER SEND\n", 8, 100);
	}

	HAL_Delay(500);


	TxHeader.StdId = 0x0126;
	TxData[0] = 100;

	while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0);

	if(HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox) != HAL_OK)
	{
		HAL_UART_Transmit(&huart1, (uint8_t*)"ER SEND\n", 8, 100);
	}

	HAL_Delay(500);

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}

В колбеке будем проверять идентификаторы и выводить инфу в USART…

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);

        if(RxHeader.StdId == 0x0378)
        {
        	snprintf(trans_str, 128, "ID %04lX %d\n", RxHeader.StdId, RxData[0]);
        	HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 100);
        }
        else if(RxHeader.StdId == 0x0126)
        {
        	snprintf(trans_str, 128, "ID %04lX %d\n", RxHeader.StdId, RxData[0]);
        	HAL_UART_Transmit(&huart1, (uint8_t*)trans_str, strlen(trans_str), 100);
        }
    }
}

Если у вас есть две платы с трансиверами, тогда можете настроить режим Normal…


И залить этот проект в обе платы — они начнут обмениваться данными между собой. Только пропишите разные идентификаторы для отправки для разных плат, в бесконечном цикле.


Статья получилась большая поэтому описание настройки фильтров и использование двух CAN модулей на одном камне в следующей части.

Проект на Github.

Это всё, всем спасибо

Телеграм-чат istarik

Телеграм-чат STM32

Setup

I am using an STM32F103C8T6 (aka Blue Pill).
With the STM32Cube I set CAN_RX to PB8 and CAN_TX9 to PB9 (these are defaults/nonchangeable).

Circuit

schematic

simulate this circuit – Schematic created using CircuitLab

Components in above circuit:

  • STM #1: STM32F103C8T6
  • STM #2: STM32F103C8T6
  • Transceiver #1: TJA1050 based transceiver (see TJA 1050)
  • Transceiver #2: TJA1050 based transceiver (see TJA 1050)

I found out the TJA1050 works on 5V and the output VCCs from STM32 are 3.3V, so I used a breadboard power supply to give 5V to Transceiver 1 and 2 VCC. I assume the USB GNDs are coupled to the GNDs of the STM32s (probably internally since I didn’t do any specific wiring), same as the USB +5V is coupled to the VCC of the STMs.

The transceivers already contain 120 ohm resistors so I assume I don’t need any additional.
The current distance between CANL and CANH of transceiver #1 and #2 is about 10 cm / 4″ (simple wire). In my real application it will be about 2 meters.

Also I assume that the CAN TX needs to be connected to the Tranceiver’s TX (and RX to RX).

Can Settings

The generated CAN settings are below. This executes ok.

/* CAN init function */
static void MX_CAN_Init(void)
{

  static CanRxMsgTypeDef CanRX;
  static CanTxMsgTypeDef CanTX;
  CAN_FilterConfTypeDef sFilterConfig;

  hcan.Instance = CAN1;

  hcan.pRxMsg = &CanRX;
  hcan.pTxMsg = &CanTX;

  hcan.Init.Prescaler = 128;
  hcan.Init.Mode = CAN_MODE_NORMAL;
  hcan.Init.SJW = CAN_SJW_1TQ;
  hcan.Init.BS1 = CAN_BS1_12TQ;
  hcan.Init.BS2 = CAN_BS2_5TQ;
  hcan.Init.TTCM = DISABLE;
  hcan.Init.ABOM = DISABLE;
  hcan.Init.AWUM = DISABLE;
  hcan.Init.NART = DISABLE;
  hcan.Init.RFLM = DISABLE;
  hcan.Init.TXFP = DISABLE;
  if (HAL_CAN_Init(&hcan) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  sFilterConfig.FilterNumber = 0;
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
  sFilterConfig.FilterIdHigh = 0x0000;
  sFilterConfig.FilterIdLow = 0x0000;
  sFilterConfig.FilterMaskIdHigh = 0x0000;
  sFilterConfig.FilterMaskIdLow = 0x0000;
  sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0;
  sFilterConfig.FilterActivation = ENABLE;
  sFilterConfig.BankNumber = 14;

  if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
  {
      Error_Handler();
  }
}

Program

(removed STM generated comments blocks)

Transmitter:

int main(void)
{
  ..
  /* USER CODE BEGIN 2 */
  hcan.pTxMsg->StdId = 0x100;
  hcan.pTxMsg->ExtId = 0x01;
  hcan.pTxMsg->RTR   = CAN_RTR_DATA;
  hcan.pTxMsg->IDE   = CAN_ID_STD;
  hcan.pTxMsg->DLC   = 2;

  while (1)
  {
  hcan.pTxMsg->Data[0] = 0x10;
  hcan.pTxMsg->Data[1] = 0x1;

  if (HAL_CAN_Transmit(&hcan, CAN_FIFO0) != HAL_OK)
  {
      Error_Handler();
  }
  HAL_Delay(1000);
  }
}

Receiver (interrupt code is never called):

void RxIntEnable(CAN_HandleTypeDef *CanHandle)

{
    if (CanHandle->State == HAL_CAN_STATE_BUSY_TX)
    {
        CanHandle->State = HAL_CAN_STATE_BUSY_TX_RX0;
    }
    else
    {
        CanHandle->ErrorCode = HAL_CAN_ERROR_NONE;
        __HAL_CAN_ENABLE_IT(CanHandle, CAN_IT_EWG); // Error warning interrupt
        __HAL_CAN_ENABLE_IT(CanHandle, CAN_IT_EPV); // Error passive interrupt
        __HAL_CAN_ENABLE_IT(CanHandle, CAN_IT_BOF); // Bus-off interrupt
        __HAL_CAN_ENABLE_IT(CanHandle, CAN_IT_LEC); // Last error code interrupt
        __HAL_CAN_ENABLE_IT(CanHandle, CAN_IT_ERR); // Error interrupt
    }

    __HAL_CAN_ENABLE_IT(CanHandle, CAN_IT_FMP0); // FIFO0 message pending interrupt
}

void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* CanHandle)
{
    if ((CanHandle->pRxMsg->StdId == 0x100) &&
        (CanHandle->pRxMsg->IDE   == CAN_ID_STD) &&
        (CanHandle->pRxMsg->DLC   == 2))
    {
        printf("1");
    }

    RxIntEnable(CanHandle);
}

within main:

if (HAL_CAN_Receive_IT(&hcan, CAN_FIFO0) != HAL_OK)
{
    Error_Handler();
}

Loopback mode

When I use loopback mode:

hcan.Init.Mode = CAN_MODE_LOOPBACK 

instead of Normal mode, I can transmit and receive messages (and the hcan shows the correct data in the received message).

Problem

However, in Normal mode (as shown in the code fragment above) I always get a timeout in the next command:

 if (HAL_CAN_Transmit(&hcan, 10) != HAL_OK)

The function returns: HAL_CAN_STATE_TIMEOUT within this fragment (default HAL code):

/* Check End of transmission flag */
while(!(__HAL_CAN_TRANSMIT_STATUS(hcan, transmitmailbox)))
{
  /* Check for the Timeout */
  if(Timeout != HAL_MAX_DELAY)
  {
    if((Timeout == 0U) || ((HAL_GetTick()-tickstart) > Timeout))
    {
      hcan->State = HAL_CAN_STATE_TIMEOUT;

      /* Cancel transmission */
      __HAL_CAN_CANCEL_TRANSMIT(hcan, transmitmailbox);

      /* Process unlocked */
      __HAL_UNLOCK(hcan);
      return HAL_TIMEOUT;
    }
  }
}

All initialization seems to be ok (all functions return HAL_OK).

Analysis

What I tried/checked was:

  • Using another STM32: no difference
  • Using another transceiver: no difference
  • Played a bit with the SJW/BS1/BS2 time quantities: no difference
  • Making sure the time quantities were equal
  • Playing with different data values and filters: no difference
  • Checked the output of PB9 (CAN transmit): it seems not to change at all (so this is a problem): no difference
  • Removing the wire from GPIO PB9 (CAN Tx) to the TX of my CAN transceiver : no difference.
  • Checking the Transmit is cancelled (which is needed and was an old bug, but has already been fixed by the HAL library I’m using).
  • Checking the resistance between CANL and CANH which moves between 63 and 70 ohms.
  • Checking the voltage between CANL and CANH (while not sending after the error). 0 Voltage; I wouldn’t expect this.

Questions

  1. Why do I get a timeout? I’m having a hard time trying to find more to check what I already did.

Related question:

Update

This is an old question, but trying again to get CAN working with same bad result (timeout). I changed some settings and updated the information above accordingly.

Newt

Добрый день!

Не могу запустить CAN на STM32F4, причем проблема возникает еще до попыток отправки и приема — во время инициализации. HAL_CAN_Init() завершается с ошибкой, вот моя инициализация:

/**
  * @brief CAN2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_CAN2_Init(void)
{

  /* USER CODE BEGIN CAN2_Init 0 */

  /* USER CODE END CAN2_Init 0 */

  /* USER CODE BEGIN CAN2_Init 1 */

  /* USER CODE END CAN2_Init 1 */
  hcan2.Instance = CAN2;
  hcan2.Init.Prescaler = 10;
  hcan2.Init.Mode = CAN_MODE_NORMAL;
  hcan2.Init.SyncJumpWidth = CAN_SJW_1TQ;
  hcan2.Init.TimeSeg1 = CAN_BS1_5TQ;
  hcan2.Init.TimeSeg2 = CAN_BS2_5TQ;
  hcan2.Init.TimeTriggeredMode = DISABLE;
  hcan2.Init.AutoBusOff = DISABLE;
  hcan2.Init.AutoWakeUp = DISABLE;
  hcan2.Init.AutoRetransmission = DISABLE;
  hcan2.Init.ReceiveFifoLocked = DISABLE;
  hcan2.Init.TransmitFifoPriority = DISABLE;
  if (HAL_CAN_Init(&hcan2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CAN2_Init 2 */

  /* USER CODE END CAN2_Init 2 */

}

Подскажите, в какую сторону копнуть, у меня пока идей нет… Может баг Cube?

Цитата

Topic starter
Размещено : 21.12.2021 12:42

Aveal

Приветствую) А какой код ошибки? Для CAN2 он здесь:

hcan2.ErrorCode

ОтветитьЦитата

Размещено : 21.12.2021 13:31

Newt

@aveal спасибо за ответ! Посмотрел под отладчиком,

hcan2.ErrorCode = 0x00020000

ОтветитьЦитата

Topic starter
Размещено : 21.12.2021 14:47

Aveal

@newt судя по коду — HAL_CAN_ERROR_TIMEOUT. А что на шине подключено помимо контроллера?

ОтветитьЦитата

Размещено : 21.12.2021 18:26

Newt

@aveal Пока ничего не подключал, хочу сначала просто осциллографом посмотреть сигналы на выходах.

ОтветитьЦитата

Topic starter
Размещено : 21.12.2021 21:24

Aveal

@newt С CAN так не получится, нужно полноценную шину организовать, STM — трансивер — другое устройство.

ОтветитьЦитата

Размещено : 22.12.2021 12:38

AI

Либо loopback-режим можно использовать для тестирования отправки/приема.

ОтветитьЦитата

Размещено : 22.12.2021 13:39

Newt

Добрый вечер! Да, Вы правы, подключил трансивер и еще несколько устройств на шину и ошибка пропала. Прием-передача работают на ура 🙂 

ОтветитьЦитата

Topic starter
Размещено : 23.12.2021 18:57

preface

After studying stm32 for a period of time, I have summarized some common problems. This article mainly writes about the problems encountered on the can line.

Specific manifestations of can line problem:

1. The program can run, but cannot communicate, and cannot receive the information of the connected equipment. Reason: the problem of hardware. Observe whether the power line and can line are normal; Software problem: speed problem.

2. The program cannot run. It is stuck in the initialization of the can line. Reason: the pins are not configured properly.

3. The program can run but cannot communicate and cannot receive information (similar to the first one, but here it mainly refers to can1 can communicate and can2 cannot): Reason: whether the buffers used by can1 and can2 are different. If they are different, the interrupt enable may not be set properly.

Software problems:

1. Speed

stm32 must understand the can line communication rate of a device to communicate with it. Take the general products of robomaster as an example, for example, the can line communication rate of G6020 and 3508 is 1Mbps, so it is necessary to set the can line rate of the main control board to 1Mbps, otherwise it is impossible to communicate normally,

void MX_CAN1_Init(void)
{

  hcan1.Instance = CAN1;
  hcan1.Init.Prescaler = 3;
  hcan1.Init.Mode = CAN_MODE_NORMAL;
  hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
  hcan1.Init.TimeSeg1 = CAN_BS1_10TQ;
  hcan1.Init.TimeSeg2 = CAN_BS2_3TQ;
  hcan1.Init.TimeTriggeredMode = DISABLE;
  hcan1.Init.AutoBusOff = ENABLE;
  hcan1.Init.AutoWakeUp = ENABLE;
  hcan1.Init.AutoRetransmission = DISABLE;
  hcan1.Init.ReceiveFifoLocked = DISABLE;
  hcan1.Init.TransmitFifoPriority = ENABLE;
  if (HAL_CAN_Init(&hcan1) != HAL_OK)
  {
    Error_Handler();
  }

}

The rate configuration is performed during the initialization of can1 and can2, specifically hcan1 Init. Prescaler,hcan1.Init.TimeSeg1,hcan1.Init.TimeSeg2 is determined by three variables. For example, in the above code, the set values of these three variables are 3, 10 and 3 respectively. At this time, the rate of stm32f427 chip used is set to 168mhz and the rate of apb1 is set to 42Mhz, so the configured rate is 42 / (3 * (10 + 3 + 1)) to 1Mhz (refer to other articles for rate calculation here, which is common), which meets the requirements.

The common problem of rate configuration is that if the rate is directly configured in the cube, the infinite circular decimal is not allowed in the new cube. If the main board is 168Mhz, it cannot be configured to 1Mhz in the cube, as shown in the figure below. Therefore, you can only find the void MX after generating the code_ CAN1_ Init (void) initializes the function to modify the rate.

2. Pin

The pins of can line include TX pin and RX pin, which must be configured properly, otherwise the initialization of can line cannot pass normally.

The method here is to find the schematic diagram of the board you use and find out which pins of the can line are. Here we still need to mention the cube. Most cubes automatically generate two pins, but please note that the pins here are not completely correct, so it’s best to find the pins for configuration.

3. Interrupt callback

The communication of can line is also through the interrupt callback function. The code generated by cube can be in stm32fxxx_it.c file, for example, Tx interrupt callback of can1 and Rx0 interrupt callback,

void CAN1_TX_IRQHandler(void)
{
  /* USER CODE BEGIN CAN1_TX_IRQn 0 */

  /* USER CODE END CAN1_TX_IRQn 0 */
  HAL_CAN_IRQHandler(&hcan1);
  /* USER CODE BEGIN CAN1_TX_IRQn 1 */

  /* USER CODE END CAN1_TX_IRQn 1 */
}

/**
  * @brief This function handles CAN1 RX0 interrupts.
  */
void CAN1_RX0_IRQHandler(void)
{
  /* USER CODE BEGIN CAN1_RX0_IRQn 0 */
	
  /* USER CODE END CAN1_RX0_IRQn 0 */
  HAL_CAN_IRQHandler(&hcan1);
  /* USER CODE BEGIN CAN1_RX0_IRQn 1 */

  /* USER CODE END CAN1_RX0_IRQn 1 */
}

The function calling can line output is

HAL_CAN_AddTxMessage(hcanx, &TxMessage, TxData, &pTxMailbox);

The function received by can line is

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan);

void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan);

The difference between the two receiving functions here is the different fifo buffers used,

Generally speaking, we let can1 use FIFO0 and can2 use FIFO1. There is a problem here. There is only one TX in the interrupt callback, but there are two rxs, one RX0 and the other RX1. The difference between 0 and 1 here is caused by the different use of buffers,

Therefore, for normal use, use FIFO0 to enable RX0 interrupt and FIFO1 to enable RX1 interrupt.

 

summary

This paper mainly summarizes the can line problems I encountered in the development of stm32, and looks forward to supplement and progress together.

Понравилась статья? Поделить с друзьями:
  • Stm32 dma ошибка
  • Steam код ошибки 106
  • Stk500 ошибка ардуино
  • Steering ошибка субару что означает
  • Steam код ошибки 105