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

Попробуем разобраться, как это происходит и вообще, что же такое почтовые ящики.

Немного теории

Периферия Basic Extended CAN (bxCAN) - это интерфейс сети CAN. Поддерживает протоколы CAN версии 2.0A и B.

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

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

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

Вот так выглядит схема работы bxCan:

Рис. 1. Схема работы bxCan

 

  

Отправка сообщений

При передаче сообщений используются три почтовых ящика. Планировщик передачи сам решает, какое сообщение должны быть передано первым. В зависимости он настроек bxCan это может быть организовано по формату FIFO (First Input First Output), либо, исходя из приоритета сообщений -  сообщение с более высоким приоритетом отправляется раньше, независимо от того, в каком почтовом ящике оно хранится.

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

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

После того, как сообщение будет передано, почтовый ящик освободится и будет готов для приема очередного сообщения из кода программы. bxCan в случае успешной передачи сообщения, установит биты RQCP и TXOK в регистре CAN_TSR.

Если передача не удалась, то причина указывается битом ALST в регистре CAN_TSR в случае проигрыша арбитража и/или битом TERR - в случае обнаружения ошибки передачи.

 

Прием сообщений

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

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

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

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

Начиная с момента, когда буфер сообщений пуст, при получении первого валидного сообщения оно сохраняется в FIFO, у которого устанавливается статус Pending-1. "Железо" сигнализирует об этом, устанавливая биты FMP[1:0]  регистра CAN_RFR в значение 01b. После этого сообщение в почтовом ящике FIFO доступно для программы.

Программное обеспечение считывает содержимое почтового ящика и освобождает его установкой бита RFOM в регистре CAN_RFR. FIFO снова становится пустым. Но если в это же время, пока программа считывает сообщение, поступает следующее, то оно будет сохранено в FIFO и ему (буферу) будет присвоен статус Pending_2 (установка битов FMP[1:0]  регистра CAN_RFR в значение 10b). А после получения программой первого сообщения для буфера будет установлен статус Pending_1.

Если приложение не освобождает почтовый ящик, то следующие валидные сообщения, после первого, будут сохранены в FIFO, которому будет присвоен статус Pending_2, а затем Pending_3 (установка битов FMP[1:0]  регистра CAN_RFR в значение 11b). 

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

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

bxCan сигнализирует о состоянии перегрузки получения  сообщений в буфер FIFO путем установки бита FOVR регистра CAN_RFR. Какое сообщение при этом теряется, зависит от конфигурации FIFO:

• Если функция блокировки FIFO отключена (установлен бит RFLM в регистре CAN_MCR), последнее принятое сообщение будет перезаписано новым поступившим, прошедшим проверку, сообщением;
• Если функция блокировки FIFO включена (сброшен бит RFLM в регистре CAN_MCR), вновь принимаемые сообщения будут игнорированы. Таким образом, программное обеспечение будет видеть в почтовом ящике самые старые сообщения, все последующие будут утеряны.

 

Прерывания по получению сообщений

Как же программа узнает о том, что пришло новое сообщение?

После того, как сообщение было сохранено в FIFO биты FMP[1:0] обновляются и генерируется запрос прерывания (если установлен бит FFIE в регистре CAN_IER). Когда заполняются все три почтовых ящика, устанавливается бит FULL в регистре CAN_IER.

Необходимо обратить внимание на то, что при перегрузке бит FOVR устанавливается и генерируется прерывание только в том случае, если установлен бит FOVIE регистра CAN_IER. В противном случае все новые сообщения при перегрузке будут полностью игнорироваться и не будет вызываться прерываний по их приему, пока не освободится хотя бы один почтовый ящик.

 

 

Фильтрация идентификаторов

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

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

Для того, чтобы выполнить это требование, контроллер bxCan обеспечивает 28 настраиваемых и масштабируемых банков фильтров для приложения (по 14 на каждое устройства CAN в микроконтроллере). Настройка этих фильтров в приложении позволяет отправлять в почтовый ящик только те сообщения, которые он может обрабатывать. Остальные сообщения будут отброшены. Эта аппаратная фильтрация экономит ресурсы процессора, которые в противном случае были бы необходимы для выполнения фильтрации с помощью программного обеспечения. Каждый банк фильтров состоит из 32-битных регистров CAN FxR0 и CAN FxR1.

 

Масштабируемость

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

• Один 32-битный фильтр для  битов STDID [10: 0], EXTID [17: 0], IDE и RTR;
• Два 16-битных фильтра для битов STDID [10: 0], RTR, IDE и EXTID [17:15].

Бит IDE (Identifier Extension Bit) означает, что фильтр предназначен для расширенного кадра сообщения, а бит RTR (Remote Transmission Request) - указывает на тип сообщения "Remote".

Кроме того, фильтры могут быть сконфигурированы в режиме маски или в режиме списка идентификаторов:

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

Немного понятнее станет, если мы обратим внимание на рисунок:

 

Рис. 3. Настройка фильтрации

 

Filter Math Index  - индексирование соответствий фильтров

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

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

Filter Match Index может быть использован двумя способами:

• Сравнение индекса фильтра со списком ожидаемых значений.
• Использовать Filter Index Match как индекс на массив, чтобы получить доступ к нужной ячейке памяти данных.

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

 

Правила назначения приоритета для фильтров

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

• 32-битный фильтр имеет приоритет над 16-ти битным фильтром;
• Для фильтров одинакового масштаба, приоритет режиму списка идентификаторов  дается выше, чем режим маски идентификатора;
• Для фильтров одинакового масштаба и режима, приоритет задается номером фильтра (чем меньше число, тем выше приоритет).

 

Рассмотрим механизм фильтрации на примере:

Пример для 3-х банков фильтров в режиме списка 32-х битных идентификаторов и банка фильтров в режиме 32-х битного идентификатора маски

Рис. 4.Пример аппаратной фильтрации сообщений bxCan

 

Приведенный выше пример показывает принцип фильтрации сообщений bxCan. При получении сообщения, идентификатор сначала сравнивается с фильтрами, настроенными в режиме списка идентификаторов. Если есть совпадение, то сообщение будет сохранено в соответствующем буфере FIFO и индекс фильтра соответствия будет сохранен в FMI (Filter Index Match). Как показано в примере, идентификатор совпадает с идентификатором №2, таким образом, содержимое сообщения и FMI 2 сохраняются в буфере FIFO.

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

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

 

 

Хранение сообщений

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

Передача сообщений

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

Таб. 1. Структура исходящего сообщения
Смещение относительно базового адреса сообщения
(в байтах)
Наименование регистра
0 CAN_TIxR
4 CAN_TDTxR
8 CAN_TDLxR
12 CAN_TDHxR

 

Получение сообщений

Когда сообщение получено, оно становится доступным  для программного обеспечения в выходном почтовом ящике FIFO. После того, как программа обработала сообщение, оно должно освободить выходной почтовый ящик FIFO с помощью бита RFOM в регистре CAN_RFR. Это необходимо для того, чтобы было доступно следующее почтовое сообщение и для того, чтобы освободить почтовый ящик для приема очередного сообщения шины.

FMI хранится в поле MFMI регистра CAN_RDTxR.  16 битное значение штампа времени хранятся в поле TIME[15:0] регистра CAN_RDTxR.

Таб. 2. Структура входящего сообщения
Смещение относительно базового адреса сообщения
(в байтах)
Наименование регистра
0 CAN_RIxR
4 CAN_RDTxR
8 CAN_RDLxR
12 CAN_RDHxR

 

 

Обработка ошибок

Управление ошибками, как описано в протоколе CAN, обрабатываются полностью аппаратными средствами с помощью счетчика ошибок передачи (Transmit Error Counter) (значение TEC регистра CAN_ESR) и счетчика ошибок приема (Receive Error Counter) (значение REC регистра CAN_ESR), в которых отражается уменьшение или увеличение количества ошибок с соответствии с текущим состоянием.

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

Кроме того, аппаратные средства могут предоставить подробную информацию о текущем состоянии в регистре CAN_ESR. С помощью регистра CAN_IER, программное обеспечение может настроить генерацию прерываний для оперативного обнаружения и исправления ошибок.

Сброс состояния Bus-Off

Устройство переходит в состояние Bus-Off, когда счетчик ошибок передачи (TEC) превышает значение 255 - это состояние обозначается битом BOFF в регистре CAN_ESR. В состоянии Bus-Off, bxCan больше не может как передавать сообщения, так и принимать их. В зависимости от бита ABOM в регистре CAN_MCR, bxCan самостоятельно избавится от режима Bus-Off, либо будет ждать, пока программное обеспечение само не сбросит это состояние. Но в обоих случаях bxCan должен ждать, по крайней мере, для последовательности восстановления, указанного в стандарте CAN (128 вхождений 11-ти последовательных рецессивных бит на входе RX контроллера).

 

Рис. 5. Схема обработки ошибок bxCan

 

Если бит ABOM установлен, то bxCan автоматически начнет восстановление последовательности после того, как он вошел в режим Bus-Off. 

Если бит ABOM сброшен, программное обеспечение должно самостоятельно инициировать восстановление, запрашивая bxCan для входа и выхода из режима инициализации. Другими словами - необходимо перезапустить и заново инициализировать bxCan "вручную".

Примечание: В режиме инициализации bxCAN не контролирует сигнал CANRX, поэтому он не может выполнить последовательность восстановления. Для восстановления bxCAN должен находиться в нормальном режиме работы.

 

 

Реализация

С  теорией покончено, теперь приступаем к реализации.

Начнем со стандартного, когда наша программа будет обрабатывать все сообщения:

Листинг №1. Фильтрация отключена - прием всех сообщений
	// CAN filter init
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;                        // Номер фильтра, доступны с 0 по 13
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;      // Режим работы фильтра
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;     // Разрядность (масштабирование)
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;                   // Старшая часть фильтра
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;                    // Младшая часть фильтра
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;               // Старшая часть маски
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;                // Младшая часть маски
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;        // Номер буфера FIFO (у нас их всего два)
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;               // Активность фильтра
	CAN_FilterInit(&CAN_FilterInitStructure);
 

 

При такой настройке все сообщения будут проходить через аппаратный фильтр и будут помещены во входящий буфер FIFO (в данном примере в буфер FIFO0).

 

Стандартный размер кадра сообщения CAN (11 бит)

А теперь рассмотрим пример, когда нам нужно отфильтровать сообщения для стандартного пакета с идентификаторами в диапазоне 0x07E0 - 0x07EF

Листинг №2. Фильтрация включена - прием стандартных сообщений в диапазоне 0x07E0 - 0x07EF
	// CAN filter init
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;                        // Номер фильтра, доступны с 0 по 13
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;      // Режим работы фильтра
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;     // Разрядность (масштабирование)
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x07E0<<5;                // Старшая часть фильтра
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;                    // Младшая часть фильтра
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x07E0<<5;            // Старшая часть маски
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;                // Младшая часть маски
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;        // Номер буфера FIFO (у нас их всего два)
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;               // Активность фильтра
	CAN_FilterInit(&CAN_FilterInitStructure);

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

Обратите внимание, что идентификатор для стандартного сообщения размещается в старшей части фильтра со смещением влево на 5 бит. (см. Рис.3)

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

А что делать, если нам необходима фильтрация нескольких диапазонов?

Листинг №3. Фильтрация включена - прием стандартных сообщений в диапазоне 0x00D0 - 0x00DF
	
	CAN_FilterInitStructure.CAN_FilterNumber = 1;                        // Номер фильтра, доступны с 0 по 13
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x00D0<<5;                // Старшая часть фильтра
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0F0<<5;             // Старшая часть маски
	CAN_FilterInit(&CAN_FilterInitStructure); 

Здесь видим несколько иной текст кода. В предыдущем примере мы активировали фильтр №0, а в этом примере мы уже активируем фильтр №1. Так как у нас часть значений не меняется, то мы можем их опустить, и указать только ключевые параметры для нового фильтра - номер фильтра и новый диапазон адресов. Теперь все сообщения с идентификаторами в диапазоне 0x00D0 - 0x00DF также будут проходить через фильтр и попадать в наш обработчик прерывания. Все остальные идентификаторы будут отсеиваться на аппаратном уровне.

Всего мы можем использовать 14 фильтров (номера с 0-го по 13-й). Каждый фильтр может иметь свою структуру и режим фильтрации (по маске или по идентификатору). Допускается, что сообщения могут пройти по двум разным фильтрам. Приоритет сообщения в этом случае будет выставлен согласно схеме, приведенной выше.

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

Листинг №4. Фильтрация включена - прием стандартных сообщений в диапазоне 0x07E0 - 0x07EF и в диапазоне 0x00D0 - 0x00DF
	// CAN filter init
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;                        // Номер фильтра, доступны с 0 по 13
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;      // Режим работы фильтра
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_16bit;     // Разрядность (масштабирование)
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x07E0<<5;                // Идентификатор фильтра №1
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x00D0<<5;                 // Идентификатор фильтра №2
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x07E0<<5;            // Маска фильтра №1
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0F0<<5;              // Маска фильтра №2
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;        // Номер буфера FIFO (у нас их всего два)
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;               // Активность фильтра
	CAN_FilterInit(&CAN_FilterInitStructure);

Здесь мы указали 16-ти битный формат фильтра и согласно формату, мы настроили два диапазона идентификаторов в одном фильтре, поместив второй диапазон и маску идентификаторов в младшие части фильтра и маски. 

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

Сначала в 32-х битном масштабе фильтра:

Листинг №5. Фильтрация включена - прием стандартных сообщений с идентификаторами: 0x07E0, 0x07E5
	// CAN filter init
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;                        // Номер фильтра, доступны с 0 по 13
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;      // Режим работы фильтра
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;     // Разрядность (масштабирование)
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x07E0<<5;                // Старшая часть идентификатора №1
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;                    // Младшая часть идентификатора №1
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x07E5<<5;            // Старшая часть идентификатора №2
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;                // Младшая часть идентификатора №2
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;        // Номер буфера FIFO (у нас их всего два)
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;               // Активность фильтра
	CAN_FilterInit(&CAN_FilterInitStructure);

В приведенном примере мы добавили в фильтр два идентификатора для стандартного формата сообщения. Теперь аппаратными средствами будут отфильтровываться все сообщения, идентификаторы которых не равны 0x07E0 и 0x07E5.

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

Листинг №6. Фильтрация включена - прием стандартных сообщений с идентификаторами: 0x0700, 0x07E0, 0x07E5 и 0x00D3
	// CAN filter init
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;                        // Номер фильтра, доступны с 0 по 13
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;      // Режим работы фильтра
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_16bit;     // Разрядность (масштабирование)
	CAN_FilterInitStructure.CAN_FilterIdHigh = 0x07E0<<5;                // Идентификатор №1
	CAN_FilterInitStructure.CAN_FilterIdLow = 0x07E5<<5;                 // Идентификатор №2
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0700<<5;            // Идентификатор №3
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x00D3<<5;             // Идентификатор №4
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;        // Номер буфера FIFO (у нас их всего два)
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;               // Активность фильтра
	CAN_FilterInit(&CAN_FilterInitStructure);

Таким образом, максимально нам доступен список из 56 идентификаторов (4 идентификатора на фильтр помноженные на 14 фильтров).

Установка других фильтров в банке настраивается аналогично приведенным выше примерам.

 

Расширенный размер кадра сообщения CAN (29 бит)

Теперь рассмотрим все тоже самое, но применительно к расширенному формату кадра, длиной в 29 бит.  Будем отфильтровывать все сообщения, идентификаторы которых в диапазоне 0x1fbf9000 - 0x1fbf9fff.

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

Листинг №7. Фильтрация включена - прием расширенных сообщений в диапазоне 0x1FBF9000 - 0x1FBF9FFF
	#define CAN_IDE_32            0b00000100            // Для 32-х битного масштаба
	
	...
	...
	
	// CAN filter init
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;                                         // Номер фильтра, доступны с 0 по 13
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;                       // Режим работы фильтра
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;                      // Разрядность (масштабирование)
	CAN_FilterInitStructure.CAN_FilterIdHigh = (uint16_t)(0x1fbf9000>>13);                // Старшая часть фильтра
	CAN_FilterInitStructure.CAN_FilterIdLow = (uint16_t)(0x1fbf9000<<3) | CAN_IDE_32;     // Младшая часть фильтра
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = (uint16_t)(0x1ffff000>>13);            // Старшая часть маски
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = (uint16_t)(0x1ffff000<<3) | CAN_IDE_32; // Младшая часть маски
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO1;                         // Номер буфера FIFO (у нас их всего два)
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;                                // Активность фильтра
	CAN_FilterInit(&CAN_FilterInitStructure);

В старшую часть фильтра мы помещаем биты идентификатора со смещением вправо на 13 бит, а в младшую - идентификатор со смещением влево на 3 бита (так как у нас формат идентификатора uint32_t, а используем всего 29 бит). Аналогично заполняем и маску.

Теперь небольшой пример, почему идентификатор равен 0x1fbf9000, а маска равна 0x1ffff000:

‭0x1fbf9000 - 0001 1111 1011 1111 1001 0000 0000 0000‬
0x1ffff000 - ‭0001 1111 1111 1111 1111 0000 0000 0000‬

Если 10-й, 18-19 биты оставить в маске равными идентификатору, то в список диапазонов попадут восемь! (2^3) диапазонов размером по 0xFFF, в которых будут варьироваться эти биты. Напомню, что ноль в маске означает то, что в полученном идентификаторе может быть любое значение, единица - точное соответствие фильтру.

Обратите внимание, что младшие части фильтра и маски заполняют бит IDE (Identifier Extension Bit). Это нужно для того, чтобы указать фильтру, что он используется для расширенного формата кадра.

Интересное наблюдение: если бит IDE не устанавливать, то в некоторых случаях фильтр отработает и для стандартного формата кадра и для расширенного, при условии совпадения первых n бит в стандартном и расширенных идентификаторах и "правильным" образом настроенной маски.

Подобно биту IDE, необходимо устанавливать бит RTR (Remote Transmission Request), если Вы фильтруете пакеты в режиме кадра Remote (см. Рис. 3).

 

Аналогично листингу №3, когда нам требуется установить несколько диапазонов идентификаторов, настраивается код и для расширенного идентификатора сообщения, с той лишь разницей, что надо заполнять и старшую и младшую части фильтра и маски:

Листинг №8. Фильтрация включена - прием расширенных сообщений в диапазоне 0x1AB03000 - 0x1AB03FFF
	CAN_FilterInitStructure.CAN_FilterNumber = 1;                                           // Номер фильтра, доступны с 0 по 13
	CAN_FilterInitStructure.CAN_FilterIdHigh = (uint16_t)(0x1AB03000>>13);                  // Старшая часть фильтра
	CAN_FilterInitStructure.CAN_FilterIdLow = (uint16_t)(0x1AB03000<<3) | CAN_IDE_32;       // Младшая часть фильтра
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = (uint16_t)(0x1FFFF000>>13);              // Старшая часть маски
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = (uint16_t)(0x1FFFF000<<3) | CAN_IDE_32;   // Младшая часть маски
	CAN_FilterInit(&CAN_FilterInitStructure);

В этом примере мы также изменяем номер фильтра и заполняем значения идентификатора сообщения и его маски. Все остальное - стандартно.

 

Если нам необходимо настроить фильтр по списку идентификаторов, то мы выставляем режим фильтра в "CAN_FilterMode_IdList" и заполняем поля фильтра и маски. В данном случае поля маски фильтра предназначены для ввода идентификатора №2, заполняются также, как и поля фильтра (Идентификатор №1).

Если нам не нужен идентификатор №2, то поля маски заполняем нулями.

Листинг №9. Фильтрация включена - прием расширенных сообщений  с идентификаторами: 0x1FBF9000 и 0x1AB03000 
	CAN_FilterInitTypeDef CAN_FilterInitStructure;
	CAN_FilterInitStructure.CAN_FilterNumber = 0;                                          // Номер фильтра, доступны с 0 по 13
	CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;                        // Режим работы фильтра
	CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;                       // Разрядность (масштабирование)
	CAN_FilterInitStructure.CAN_FilterIdHigh = (uint16_t)(0x1fbf9000>>13);                 // Старшая часть идентификатора №1
	CAN_FilterInitStructure.CAN_FilterIdLow = (uint16_t)(0x1fbf9000<<3) | CAN_IDE_32;      // Младшая часть идентификатора №1
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh = (uint16_t)(0x1AB03000>>13);             // Старшая часть идентификатора №2
	CAN_FilterInitStructure.CAN_FilterMaskIdLow = (uint16_t)(0x1AB03000<<3) | CAN_IDE_32;  // Младшая часть идентификатора №2
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO1;                          // Номер буфера FIFO (у нас их всего два)
	CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;                                 // Активность фильтра
	CAN_FilterInit(&CAN_FilterInitStructure);

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

Также, для расширенного формата кадра, я указал для поля "CAN_FilterFIFOAssignment" значение CAN_FIFO1. Это означает, что по фильтрам для расширенного формата все сообщения будут складываться в буфер FIFO1. Но для этого необходимо включить использование прерываний для буфера FIFO1, а также обрабатывать в прерывании получение сообщений из этого буфера.

Для STM32F10x необходимо дополнительно включить прерывание "CAN1_RX1_IRQn". При появлении сообщений в буфере FIFO1, будет вызываться именно это прерывание. (Для FIFO0 - сообщения отрабатываются по прерыванию "USB_LP_CAN1_RX0_IRQn")

Листинг №10. Настройка буферов FIFO0 и FIFO1
	// NVIC Configuration
	// Enable CAN1 RX0 interrupt IRQ channel
	NVIC_InitTypeDef NVIC_InitStructure;
	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);
	// CAN FIFO0 message pending interrupt enable
	CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);

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

	// CAN FIFO1 message pending interrupt enable
	CAN_ITConfig(CAN1, CAN_IT_FMP1, ENABLE);

 А это сам порядок обработки прерываний: 

Листинг №11. Обработка приема сообщений FIFO0 и FIFO1
// Получение сообщений из буфера FIFO0
void USB_LP_CAN1_RX0_IRQHandler(void)
{
	...

	if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) != RESET) {       // Проверим почтовый ящик FIFO0
		CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);        // Получим сообщения из FIFO0
		...
	}
	...
}
   
// Получение сообщений из буфера FIFO1
void CAN1_RX1_IRQHandler(void)
{
	...

	if (CAN_GetITStatus(CAN1, CAN_IT_FMP1) != RESET) {       // Проверим почтовый ящик FIFO1
		CAN_Receive(CAN1, CAN_FIFO1, &RxMessage);        // Получим сообщения из FIFO1
		... 
	}
	...
}

Небольшое замечание касаемо основной идеи проекта SmartMODE.info:
Мое мнение, что разделение по различным буферам лучше сделать по важности сообщений, к примеру, системные сообщения шины (синхронизация, режимы охраны, сообщения безопасности) отправлять в буфер FIFO1, а все остальные сообщения, как наименее важные, направлять в буфер FIFO0.

Как видим - ничего сложного.

На этом с фильтрами все. Готовый пример Вы можете скачать в конце статьи. Для отладки примера удобно использовать "режим Silent Loopback" с установленными брикпоинтами на отправке и получения сообщений в/из шины. Как обычно пример протестирован и готов к использованию на микроконтроллерах STM32 серии f10x. Написан в CooCox.

 

 

Заключение

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

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

За основу для статьи взяты материалы Programming Reference (перевод мой) для микроконтроллеров STM32 серии F10x с моими комментариями и пояснениями.

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

 

Вложения:
ФайлОписаниеРазмер файла:
Скачать этот файл (CAN_protocol_stm32f103_test_filters.zip)CAN_protocol_stm32f103_test_filters.zipТестовая прошивка218 Кб

Комментарии  

#26 OZN_ 20.02.2020 12:00
Цитирую shepard127:
второго фильтра:

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

HAL_CAN_ConfigFilter(p_can, &Filter2);

НЕПРАВИЛЬНО !!!! BankNumber Указывается только для слейва , это не имеет смысла если указать для мастера (CAN1), вот пример для f407 #if defined (CAN2)
#if defined (CAN2)
/* Select the start slave bank */
can_ip->FMR &= ~((uint32_t)CAN_FMR_CAN2SB);
can_ip->FMR |= (uint32_t)(sFilterConfig->BankNumber
Цитировать
#27 OZN_ 20.02.2020 12:03
//-----------Вот правильный вариант ----
//Для КАН 1
Filter1.FilterNumber = 0;
Filter1.FilterMode = CAN_FILTERMODE_IDMASK;
Filter1.FilterScale = CAN_FILTERSCALE_32BIT;
Filter1.FilterIdHigh = 0x0000;
Filter1.FilterIdLow = 0x0000;
Filter1.FilterMaskIdHigh = 0x0000;
Filter1.FilterMaskIdLow = 0x0000;
Filter1.FilterFIFOAssignment = 0;
Filter1.FilterActivation = ENABLE;
Filter1.BankNumber = 14;// можно не писать , не имеет значения , отсеится в HAL_CAN_ConfigFilter

HAL_CAN_ConfigFilter(&hcan1, &Filter1);
Цитировать
#28 OZN_ 20.02.2020 12:03
//-------------------CAN2------------- кану 2 принадлежат фильтры с 14-27
Filter2.FilterNumber = 14;
Filter2.FilterMode = CAN_FILTERMODE_IDMASK;
Filter2.FilterScale = CAN_FILTERSCALE_32BIT;
Filter2.FilterIdHigh = 0x0000;
Filter2.FilterIdLow = 0x0000;
Filter2.FilterMaskIdHigh = 0x0000;
Filter2.FilterMaskIdLow = 0x0000;
Filter2.FilterFIFOAssignment = 0;
Filter2.FilterActivation = ENABLE;
Filter2.BankNumber = 14;// вот тут обязательно

HAL_CAN_ConfigFilter(p_can, &Filter2);
в новых версия хала параметр BankNumber заменен на логичный ему SlaveStartFilterBank
Цитировать
#29 OZN_ 20.02.2020 12:09
Если для мастера указать BankNumber = 14 , а для слейва BankNumber = 0 , то в конечном итоге будет BankNumber = 0 и CAN 2 получит все 28 фильтров
Цитировать
#30 shepard127 30.03.2020 19:32
Цитирую OZN_:
Если для мастера указать BankNumber = 14 , а для слейва BankNumber = 0 , то в конечном итоге будет BankNumber = 0 и CAN 2 получит все 28 фильтров

Интересное замечание :-?
Понял, спасибо. Учту ваши слова при следующей конфигурации ;-)
Цитировать
#31 Андрей Гиль 13.04.2020 23:55
Не могу разобраться на новом Hal. Если мне надо что бы, проходили все id кроме конкретного например 245. Нужно сделать чтобы его устройство не получали, как настроить фильтр!?
Заранее спасибо!
Цитировать
#32 Админ 14.04.2020 07:55
Цитирую Андрей Гиль:
...как настроить фильтр!?

Насколько я знаю, исключающих фильтров в stm32 нет, поэтому для исключения одного адреса надо сделать два диапазона, в которые не попадает этот адрес. На тренировке это позволительно, а вот в рабочих проектах под вопросом, тем более если таких адресов несколько.
Логичнее было бы сделать программный фильтр уже после получения пакета.
Цитировать
#33 Puff 26.07.2021 02:23
А в листинге 2 в строке про маску точно ошибки нет?
Цитировать
#34 Yuriy 09.09.2021 10:49
Цитирую Puff:
А в листинге 2 в строке про маску точно ошибки нет?


Насколько я понимаю ошибки есть и в листинге 2 и в листинге 3, маски должны быть 0х7F0 и 0xFF0 соответственно ...
Цитировать