19н. CAN-контроллер MCP2515 по SPI: когда в микроконтроллере нет bxCAN, битовое время на 8 МГц, буферы, фильтры и прерывания
Раздел 19 разобрал шину CAN на встроенном блоке bxCAN: кадр, арбитраж, битовое время, фильтры, ошибки. Разделы 19а–19м строили поверх него протоколы и устройства. Здесь — тот же CAN, но контроллер шины стоит снаружи: микросхема MCP2515 на дешёвом модуле вместе с трансивером TJA1050, а микроконтроллер говорит с ней по SPI. Так делают, когда своего CAN нет, когда он занят или когда нужен второй канал. Устройство кадра, арбитраж и правила счётчиков ошибок здесь не повторяются — они в 19.3, 19.4 и 19.7; здесь — как всё это выглядит, когда до контроллера шины — четыре провода SPI и вывод прерывания.
Опоры: RM0008 (DocID13902 Rev 17): USB и CAN делят выделенную память
512 байт и одновременно не работают, с. 622 и с. 655; APB1 — не выше
36 МГц, с. 48; бит SPI2EN, с. 115; биты IOPBEN и AFIOEN, с. 113;
выводы SPI, таблица 25, с. 166; AFIO_EXTICR3, поле EXTI11[3:0], код
0001 — порт B, с. 191; вектор EXTI15_10 — позиция 40, таблица 63,
с. 204; EXTI_FTSR, с. 211; EXTI_PR снимается записью единицы, с. 212;
SPI_CR1, BR = 010 — fPCLK / 8, с. 743. DocID13587 Rev 17
(STM32F103x8/xB): PB10–PB15 — с допуском 5 В, SPI2 на PB12–PB15, с. 30;
CANRX и CANTX — на тех же PA11 и PA12, что USBDM и USBDP, с. 31; PA4–PA7
и PB0 — без допуска 5 В, с. 29. Документа на MCP2515 в проекте нет. Коды
команд SPI, адреса регистров CANSTAT…RXB1CTRL, биты CANINTF и EFLG,
раскладку полей CNF1–CNF3 и то, что флаг приёма снимается при чтении
буфера, подтверждает драйвер Zephyr 4.4.2
(github.com/zephyrproject-rtos/zephyr,
коммит dccb095, drivers/can/can_mcp2515.h и can_mcp2515.c, лицензия
Apache-2.0); пределы квантов и выбор сегментов — программа
can-calc-bit-timing из can-utils
(github.com/linux-can/can-utils,
коммит cbbad5e). Команда RX STATUS, адреса фильтров и масок, биты
TXBnCTRL, RXBnCTRL и CANCTRL сверх режима, правило приоритета буферов и
сравнение байтов данных маской стандартного кадра взяты по документации
производителя и не проверены. Все числа ниже — прогон драйвера
_verify/mcp19n/mcp19n.c на компьютере (gcc 12.2.0) со своей моделью
MCP2515, шины и второго узла на Python 3.11; прошивка собрана armclang 6.24
под Cortex-M3.
Как это называется в документации
Оригинал Перевод stand-alone CAN controller отдельный контроллер CAN transmit buffer, TXBn буфер передачи receive buffer, RXBn буфер приёма acceptance mask / filter, RXMn / RXFn маска / фильтр приёма rollover, BUKT перенос в следующий буфер request to send, RTS запрос передачи time quantum, TQ квант времени propagation / phase segment сегмент распространения / фазовый сегмент one-shot mode, OSM одна попытка передачи message lost arbitration, MLOA проиграл арбитраж error passive / bus-off пассивный по ошибкам / отключён от шины
19н.1 Зачем внешний контроллер и что на модуле
Когда bxCAN нет или он занят. У STM32F103 блоки USB и CAN делят выделенную память на 512 байт для буферов и одновременно работать не могут (RM0008, с. 622 и с. 655) — это 19.8. Мало того, на BluePill их выводы совпадают: CANRX и CANTX сидят на PA11 и PA12, где и USBDM и USBDP (DocID13587, с. 31). Нужен прибор, который отдаёт данные в компьютер по USB и при этом слушает CAN, — CAN выносят наружу. Вторая причина — в микроконтроллере CAN нет совсем. Третья — нужен второй канал, а встроенный один. MCP2515 закрывает все три случая: это отдельный контроллер CAN 2.0B — со своими буферами, фильтрами, счётчиками ошибок и битовым временем, — которому от микроконтроллера нужны только SPI и вывод прерывания.
Что на модуле. Типичный модуль за копейки — MCP2515, кварц, трансивер TJA1050, клеммы CANH и CANL и перемычка терминатора 120 Ом. Кварц на многих таких модулях — 8 МГц; на корпусе он подписан, и это первое, что надо прочитать: от него считается битовое время (19н.3), и половина историй «модуль не видит шину» — это код, написанный под 16 МГц.
Питание и уровни — главные грабли модуля. TJA1050 — трансивер на 5 В (таблица в 19.2), и на модуле обычно один вывод питания 5 В на обе микросхемы. Тогда MCP2515 тоже живёт от 5 В, и её выходы SO и INT выдают 5 В. Если подключить их к выводам F103 без допуска 5 В — к PA6 (MISO у SPI1) или к PB0, как RC522 в 18г, — вход можно повредить. Поэтому здесь взят SPI2: PB12–PB15, и INT заведён на PB11 — все эти выводы с допуском 5 В (DocID13587, с. 30). Обратная сторона: входы MCP2515 при питании 5 В ждут свои пороги, а F103 выдаёт 3,3 В. Гарантировано ли, что этого хватит, — по документации производителя, её здесь нет, не проверено. Надёжный вариант — разделить питание: MCP2515 от 3,3 В (она так умеет — по документации производителя, не проверено), TJA1050 от 5 В.
Схема. SPI2 висит на APB1, а та — не выше 36 МГц (RM0008, с. 48);
делитель 8 (BR = 010, с. 743) даёт 4,5 МГц: байт — 1,778 мкс. Режим 0,
CS — обычный выход PB12, как в разделе 18.4. INT — выход с активным
низким уровнем: он внизу, пока в CANINTF есть разрешённый флаг. На F103 его
ловит EXTI11 по спаду: AFIO_EXTICR3, поле EXTI11[3:0] = 0001 — порт B
(с. 191), EXTI_FTSR (с. 211), вектор EXTI15_10 (позиция 40, с. 204);
как сигнал доходит до обработчика — раздел 7.5.
void mcp_spi(const uint8_t *tx, uint8_t *rx, uint32_t n)
{
uint32_t pm = __get_PRIMASK();
__disable_irq(); /* обработчик INT не влезет посреди транзакции */
GPIOB->BRR = 1u << 12; /* CS вниз */
for (uint32_t i = 0; i < n; i++) {
while (!(SPI2->SR & SPI_SR_TXE)) {
}
SPI2->DR = tx[i];
while (!(SPI2->SR & SPI_SR_RXNE)) {
}
rx[i] = (uint8_t)SPI2->DR;
}
while (SPI2->SR & SPI_SR_BSY) {
}
GPIOB->BSRR = 1u << 12; /* CS вверх: команда выполнена */
__set_PRIMASK(pm);
}
Запрет прерываний на время транзакции — не украшение: обработчик INT сам ходит по SPI, и если он ворвётся между байтами транзакции из основного цикла, микросхема получит смесь двух команд.
19н.2 Команды SPI и регистры
У MCP2515 нет адресного байта, как у MFRC522 в 18г: первый байт транзакции — код команды. Девять команд:
| Команда | Код | Байты транзакции | Что делает |
|---|---|---|---|
| RESET | C0 |
1 | сброс, режим конфигурации |
| READ | 03 |
2 + n | адрес и n регистров подряд |
| WRITE | 02 |
2 + n | адрес и n значений подряд |
| BIT MODIFY | 05 |
4 | адрес, маска, значение: меняет только биты маски |
| READ STATUS | A0 |
2 | флаги приёма и передачи одним байтом |
| RX STATUS | B0 |
2 | где лежат кадры, их тип, какой фильтр сработал |
| LOAD TX BUFFER | 40, 42, 44 |
1 + 5 + n | заголовок и данные в TXB0, TXB1, TXB2 без адреса |
| RTS | 81, 82, 84 |
1 | запросить передачу буферов |
| READ RX BUFFER | 90, 94 |
1 + 13 | RXB0 или RXB1 целиком; флаг приёма снимается сам |
Коды, кроме RX STATUS, совпадают с драйвером Zephyr. Регистры, которые нужны драйверу:
| Регистр | Адрес | Зачем |
|---|---|---|
| CANSTAT / CANCTRL | 0E / 0F |
текущий и запрошенный режим (биты 7:5), OSM — бит 3 |
| TEC / REC | 1C / 1D |
счётчики ошибок передачи и приёма |
| CNF3, CNF2, CNF1 | 28, 29, 2A |
битовое время |
| CANINTE / CANINTF | 2B / 2C |
разрешения и флаги прерываний |
| EFLG | 2D |
предупреждение, пассивный, отключён, переполнения |
| TXB0CTRL…TXB2CTRL | 30, 40, 50 |
запрос TXREQ, приоритет TXP, MLOA, TXERR |
| RXB0CTRL / RXB1CTRL | 60 / 70 |
режим фильтров RXM, ролловер BUKT, FILHIT |
| RXF0–RXF2, RXF3–RXF5 | 00…0B, 10…1B |
фильтры по 4 байта |
| RXM0 / RXM1 | 20 / 24 |
маски по 4 байта |
Режимы. После RESET микросхема в режиме конфигурации: CANSTAT =
0x80, CANCTRL = 0x87. Только в нём пишутся CNF1–CNF3, фильтры и маски;
в остальных режимах запись молча пропадает. В модели запись фильтра RXF0
в обычном режиме — 4 пропущенные записи, и RXF0 остался 00 00 00 00.
Режим запрашивают битами 7:5 CANCTRL (0 — обычный, 2 — loopback, 4 —
конфигурация), а смотрят в CANSTAT: смена не мгновенная, и драйвер ждёт,
пока CANSTAT покажет то, что просили, — как Zephyr.
Настройка. mcp_init даёт RESET, ждёт 128 мкс (Zephyr ждёт столько
же: 128 тактов генератора при самой медленной частоте 1 МГц), проверяет
CANSTAT — 0x00 или 0xFF значит «нет связи по SPI» — и одной командой
WRITE с адреса 0x28 пишет CNF3, CNF2, CNF1 и CANINTE: адреса идут
подряд. Затем RXB0CTRL и RXB1CTRL и контрольное чтение CNF. В модели —
7 транзакций, 26 байт и 174,2 мкс, из них 128 — ожидание после сброса.
Переход в обычный режим — 2 транзакции и 7 байт: BIT MODIFY и одно чтение
CANSTAT.
BIT MODIFY меняет только биты маски — без чтения регистра и без гонки с микросхемой, которая сама ставит флаги в том же регистре. Им драйвер снимает флаги CANINTF и EFLG, ставит режим и TXP.
19н.3 Битовое время на 8 МГц
Формулы — те же, что в 19.4, но со своими именами и одной особенностью.
Квант TQ = 2 · BRP / Fosc: перед делителем BRP всегда стоит деление на 2
(в Zephyr частота ядра CAN — Fosc / 2, в can-calc-bit-timing для
mcp251x — «половина частоты генератора»). Бит — 1 + PRSEG + PHSEG1 + PHSEG2
квантов; выборка — после PHSEG1. Пределы — из can-calc-bit-timing:
PRSEG + PHSEG1 — от 3 до 16, PHSEG2 — от 2 до 8, SJW — до 4, BRP — от 1
до 64. Значит, в бите от 6 до 25 квантов. Поля CNF пишутся «минус один»,
как у bxCAN: CNF1 = SJW и BRP, CNF2 = бит BTLMODE, PHSEG1 и PRSEG, CNF3 =
PHSEG2 — раскладка как в Zephyr.
int mcp_calc_timing(uint32_t fosc, uint32_t bitrate, mcp_timing_t *t)
{
uint32_t fcan = fosc / 2u; /* делитель на 2 стоит перед BRP всегда */
for (uint32_t brp = 1; brp <= 64u; brp++) {
if (fcan % (brp * bitrate))
continue; /* только точная скорость */
uint32_t ntq = fcan / (brp * bitrate);
if (ntq > 25u)
continue; /* больше 1 + 16 + 8 квантов не набрать */
if (ntq < 6u)
return -1; /* меньше 1 + 3 + 2: скорость недостижима */
uint32_t ps2 = (ntq * 125u + 999u) / 1000u; /* точка выборки не позже 87,5 % */
if (ps2 < 2u)
ps2 = 2u; /* PHSEG2 не короче 2 квантов */
uint32_t tseg1 = ntq - 1u - ps2;
if (tseg1 > 16u) {
tseg1 = 16u;
ps2 = ntq - 17u;
}
t->brp = (uint8_t)brp;
t->prop = (uint8_t)(tseg1 / 2u);
t->ps1 = (uint8_t)(tseg1 - t->prop);
t->ps2 = (uint8_t)ps2;
t->sjw = 1;
t->ntq = (uint8_t)ntq;
t->sp_permille = (uint16_t)((1000u * (ntq - ps2) + ntq / 2u) / ntq);
t->cnf1 = (uint8_t)(((t->sjw - 1u) << 6) | (brp - 1u));
t->cnf2 = (uint8_t)(0x80u | ((t->ps1 - 1u) << 3) | (t->prop - 1u)); /* BTLMODE: PHSEG2 из CNF3 */
t->cnf3 = (uint8_t)(t->ps2 - 1u);
return 0;
}
return -1;
}
Функция берёт самый мелкий делитель — то есть больше всего квантов на бит — и целится в выборку не позже 87,5 %, как советует 19.4. Прогон для кварца 8 МГц:
| Скорость | BRP | Квант | Квантов | 1 + Prop + PS1 + PS2 | Выборка | CNF1 CNF2 CNF3 |
|---|---|---|---|---|---|---|
| 1 Мбит/с | — | — | 4 | не набрать | — | — |
| 500 кбит/с | 1 | 250 нс | 8 | 1 + 2 + 3 + 2 | 75 % | 00 91 01 |
| 250 кбит/с | 1 | 250 нс | 16 | 1 + 6 + 7 + 2 | 87,5 % | 00 B5 01 |
| 125 кбит/с | 2 | 500 нс | 16 | 1 + 6 + 7 + 2 | 87,5 % | 01 B5 01 |
can-calc-bit-timing с опорной частотой 4 МГц дал те же сегменты и те же
CNF для всех трёх скоростей, а на 1 Мбит/с — «bitrate not possible».
Почему 500 кбит/с — только 75 %. От 8 МГц квант не бывает короче 2 / 8 МГц = 250 нс, а бит 500 кбит/с — 2 мкс, ровно 8 квантов. Для 87,5 % понадобился бы PHSEG2 в 1 квант, а у MCP2515 он не короче 2. Остаётся выборка на 6 / 8 = 75 %. С кварцем 16 МГц квантов было бы 16 и 87,5 %. Работать с 75 % можно, но у остальных узлов шины выборка обычно около 87,5 %, и на длинной линии запас по задержке у этого узла меньше.
Допуск частоты — формулы из 19.4: меньшее из min(PS1, PS2) / (2 · (13 · N − PS2)) и SJW / (20 · N). Для 500 кбит/с (N = 8, PS1 = 3, PS2 = 2, SJW = 1): 0,98 % и 0,625 % — узлу хватает 0,625 %. Для 250 и 125 кбит/с (N = 16, PS1 = 7, PS2 = 2): 0,485 % и 0,312 % — допуск 0,312 %. Кварц модуля держит частоту намного точнее, так что запас есть; но генератор RC вместо кварца не годится, как и у bxCAN.
Кварц 16 МГц в коде, 8 МГц на плате. Та же функция для «16 МГц и
500 кбит/с» даёт 16 квантов и CNF 00 B5 01 — ровно те же байты, что для
8 МГц и 250 кбит/с. Записанные в модуль с кварцем 8 МГц, они дают
250 000 бит/с: узел работает на половине скорости шины (опыт — в 19н.6).
19н.4 Передача и приём: три буфера, два буфера, INT
Передача. Свободный буфер виден по READ STATUS: биты 2, 4 и 6 — TXREQ буферов 0, 1 и 2. Дальше LOAD TX BUFFER кладёт заголовок (SIDH, SIDL, EID8, EID0, DLC) и данные без адреса, а RTS просит передать.
int mcp_send(const mcp_frame_t *f, uint8_t txp)
{
uint8_t st = mcp_read_status(); /* TXREQ буферов 0, 1, 2 — биты 2, 4, 6 */
for (uint8_t b = 0; b < 3u; b++) {
if (!(st & (0x04u << (2u * b)))) {
if (mcp_load(b, f, txp))
return -1;
mcp_rts((uint8_t)(1u << b));
return b;
}
}
return -1; /* все три буфера ждут шину */
}
Кадр с 8 байтами — 4 транзакции и 21 байт по SPI, 37,3 мкс: READ STATUS
(2), BIT MODIFY для TXP (4), LOAD TX BUFFER (14), RTS (1). В модели кадр
100 с данными 11 22 … 88 занял на шине 114 бит с 3 вставленными
битами и CRC 72D1 — 228 мкс на 500 кбит/с.
Порядок трёх буферов. Если шина занята, буферы ждут, и первым уйдёт
буфер с большим TXP (3 — старший), а при равных — с большим номером. Про
идентификаторы микросхема при этом не думает. Опыт: пока узел B передаёт
свой кадр, драйвер ставит в очередь 100, 200, 300 — они ложатся в
TXB0, TXB1, TXB2. При одинаковом TXP на шину ушло 300, 200, 100 —
наоборот относительно приоритета CAN. С TXP 3, 2, 1 по идентификатору —
100, 200, 300. Поэтому драйвер Zephyr пользуется одним буфером
передачи: в его заголовке так и написано — чтобы избежать инверсии
приоритета. Арбитраж на шине работает как в 19.3: кадр 100 из
TXB0 и кадр 0F0 узла B оба ждут, пока узел B закончит предыдущий кадр, —
выиграл 0F0, в TXB0CTRL
поднялся MLOA (0x20), и 100 ушёл сразу после.
Приём. Принятый кадр ложится в RXB0 или RXB1, поднимается RX0IF или RX1IF, и INT уходит вниз. READ RX BUFFER читает 13 байт буфера одной транзакцией и на подъёме CS сам снимает флаг — отдельный BIT MODIFY не нужен (так и в Zephyr). Обычный READ того же буфера с отдельным снятием флага — 2 + 13 и ещё 4 байта, 19 вместо 14.
Обработчик крутится, пока CANINTF не пуст. INT — уровень, а EXTI ловит фронт. Пока хоть один разрешённый флаг стоит, INT внизу, и новый кадр нового спада не даёт. Если обработчик снял не всё, следующее прерывание не придёт никогда.
int mcp_isr(mcp_t *m)
{
for (m->isr_loops = 0; m->isr_loops < 8u; m->isr_loops++) {
uint8_t f = mcp_read1(MCP_CANINTF) & m->inte;
if (!f)
break; /* флагов нет — INT поднялся, следующий спад даст EXTI */
if (f & MCP_RX0IF)
rx_buffer(m, 0);
if (f & MCP_RX1IF)
rx_buffer(m, 1);
m->tx_done += ((f >> 2) & 1u) + ((f >> 3) & 1u) + ((f >> 4) & 1u);
if (f & MCP_ERRIF) {
uint8_t c[2];
m->err_irqs++;
m->eflg = mcp_read1(MCP_EFLG);
mcp_read(MCP_TEC, c, 2); /* TEC и REC подряд */
m->tec = c[0];
m->rec = c[1];
if (m->eflg & (MCP_RX0OVR | MCP_RX1OVR)) {
m->ovr++;
mcp_bitmod(MCP_EFLG, m->eflg & (MCP_RX0OVR | MCP_RX1OVR), 0);
}
}
uint8_t clr = (uint8_t)(f & ~(MCP_RX0IF | MCP_RX1IF));
if (clr)
mcp_bitmod(MCP_CANINTF, clr, 0);
}
return (int)m->isr_loops;
}
В прошивке обработчик EXTI сначала снимает свой бит EXTI_PR (записью
единицы, с. 212), а потом зовёт mcp_isr: если новый спад придёт, пока
обработчик работает, бит встанет снова и прерывание повторится.
void EXTI15_10_IRQHandler(void)
{
if (EXTI->PR & (1u << 11)) {
EXTI->PR = 1u << 11; /* снять до обработки: новый спад во время неё не пропадёт */
mcp_isr(&M); /* читает CANINTF, пока там есть флаги */
}
}
Один принятый кадр стоит обработчику 20 байт по SPI — чтение CANINTF (3), READ RX BUFFER (14), повторное чтение CANINTF (3) — и 35,6 мкс. Поток из 20 кадров по 8 байт подряд на 500 кбит/с: между началами кадров — не меньше 226 мкс, приняты все 20, SPI занят 15,0 % времени. Этот расчёт не учитывает паузы между байтами и время процессора — их на железе не мерили.
Два буфера приёма и ролловер. Без фильтров (RXM = 11) каждый кадр сначала просится в RXB0. Если тот занят, а бит BUKT в RXB0CTRL снят, кадр теряется — с флагом RX0OVR, даже если RXB1 пуст. С BUKT = 1 он уходит в RXB1. Опыт: прерывания закрыты на 1000 мкс (скажем, другой обработчик), за это время узел B шлёт три кадра подряд. BUKT = 0: принят 1, потеряно 2. BUKT = 1: принято 2, потерян 1. Два буфера — это запас на одну задержку обработчика, не больше: при 500 кбит/с кадр с 8 байтами длится 226–254 мкс.
Обработчик «одно сообщение за прерывание». Частый образец: прочитать
RXB0, а если там пусто — RXB1, и выйти. С ролловером оба буфера заняты,
обработчик забрал один — INT остался внизу. Опыт: два кадра подряд при
закрытых прерываниях, потом ещё четыре через 1 мс. Такой обработчик принял
1 кадр из 6, 3 потеряны, а INT так и висит внизу с CANINTF = 0x23:
непрочитанные RX0IF и RX1IF и ERRIF от переполнений. Один вызов полного
mcp_isr забрал два застрявших кадра и поднял INT. Тот же опыт с
mcp_isr с самого начала — приняты все 6.
19н.5 Маски и фильтры
Фильтр сравнивает идентификатор не целиком, а только в битах, где у маски единица. У RXB0 одна маска RXM0 и фильтры RXF0, RXF1; у RXB1 — маска RXM1 и RXF2–RXF5. Кадр, прошедший фильтр RXB0, идёт в RXB0 (или в RXB1 при ролловере); иначе проверяются фильтры RXB1. Маски и фильтры — по 4 регистра; для расширенного идентификатора все 29 бит раскладываются по SIDH, SIDL, EID8 и EID0, в SIDL у фильтра ещё бит EXIDE: фильтр ловит либо стандартные кадры, либо расширенные.
static void id_bytes(uint32_t id, uint8_t ext, uint8_t b[4])
{
if (ext) {
b[0] = (uint8_t)(id >> 21);
b[1] = (uint8_t)((((id >> 18) & 7u) << 5) | 0x08u | ((id >> 16) & 3u)); /* 0x08 — EXIDE */
b[2] = (uint8_t)(id >> 8);
b[3] = (uint8_t)id;
} else {
b[0] = (uint8_t)(id >> 3);
b[1] = (uint8_t)((id & 7u) << 5);
b[2] = 0; /* у стандартного кадра здесь — байты данных 0 и 1 */
b[3] = 0;
}
}
Раскладка заголовка совпадает с той, что Zephyr пишет в буфер передачи.
Опыт с настройкой: RXM0 = 7FF, RXF0 = 100, RXF1 = 101 —
стандартные; RXM1 = 1FFFFF00 — маска расширенного вида, RXF2 =
18DAF100 (расширенный), RXF3 = 7E0 (стандартный), RXF4 и RXF5 —
копии RXF2, чтобы пустые фильтры не ловили лишнего. Узел B шлёт кадры, и
вот куда они попали:
| Кадр | Данные | Куда | FILHIT | RX STATUS |
|---|---|---|---|---|
100 |
01 02 |
RXB0 | RXF0 | 40 |
101 |
03 |
RXB0 | RXF1 | 41 |
102 |
04 |
не принят | — | — |
18DAF110, расширенный |
02 10 |
RXB1 | RXF2 | 92 |
18DAF210, расширенный |
02 10 |
не принят | — | — |
100, расширенный |
05 |
не принят | — | — |
7E0 |
00 11 |
RXB1 | RXF3 | 83 |
7E0 |
02 11 |
не принят | — | — |
Расширенный 100 не попал в RXF0, хотя номер тот же: у RXF0 EXIDE = 0.
18DAF210 не прошёл: маска 1FFFFF00 сравнивает всё, кроме младшего
байта, а там, где у фильтра F1, у кадра F2. Самое
неожиданное — два кадра 7E0. У стандартного кадра биты EID маски и
фильтра сравниваются с байтами данных 0 и 1: маска 1FFFFF00 в EID8 даёт
FF, в EID0 — 00, а у RXF3 там нули. Кадр с первым байтом 00 прошёл,
с 02 — нет. Удобно, если нужен фильтр по первому байту данных, и
загадочно, если маска для расширенных кадров задела стандартные (это
поведение — по документации производителя, не проверено; в модели оно
реализовано так). RX STATUS в модели устроен так: биты 7:6 — в каком
буфере кадр, бит 4 — расширенный, биты 2:0 — номер фильтра; 92 — RXB1,
расширенный, RXF2.
Фильтры пишутся только в режиме конфигурации (19н.2): драйвер заходит в него, пишет маски и фильтры, ставит RXM = 00 в обоих RXBnCTRL и возвращается в обычный режим.
19н.6 Ошибки, отключение от шины и самопроверка
Счётчики и правила — те же, что в 19.7: TEC растёт на 8 за ошибку передачи и падает на 1 за удачную; при 128 узел пассивный; при TEC больше 255 — отключён; REC растёт за ошибки приёма. Здесь их видно в TEC, REC и EFLG, а изменение состояния поднимает ERRIF — обработчик читает EFLG и оба счётчика.
Одинокий узел. Узел B отключён, драйвер шлёт один кадр. Подтвердить
некому: ошибка ACK, TEC + 8, повтор. После 16 попыток TEC = 128, узел
пассивный — через 4037,3 мкс после постановки кадра. Дальше ошибка ACK у
пассивного счётчик не растит: за 10 мс было 39 попыток, TEC так и стоит
на 128, EFLG = 0x15 (TXEP, TXWAR, EWARN), в TXB0CTRL — TXREQ и TXERR
(0x18). Узел B вернулся — кадр ушёл, TEC = 127, EFLG = 0x05:
пассивность снята, предупреждение осталось.
Одна попытка. Бит OSM в CANCTRL: кадр уходит один раз. Тот же
одинокий узел: 1 попытка, TEC = 8, TXERR поднят, TXREQ снят (TXB0CTRL =
0x10). Так делают, когда устаревший кадр повторять бессмысленно.
Отключение от шины. CANH и CANL замкнуты: доминантный уровень на шину
не выходит, контроллер видит не то, что передал, — ошибка бита, + 8 при
каждой попытке, и у пассивного тоже. Пассивный — после 16 попыток,
613,3 мкс; отключён — после 32 попыток, 1445,3 мкс, EFLG = 0x35 (TXBO в
придачу). Замыкание убрали — узел вернулся сам через 2816 мкс: 128 × 11 =
1408 рецессивных бит по 2 мкс (возврат без участия программы — по
документации производителя, не проверено), TEC = 0, и ждавший в TXB0
кадр ушёл.
Кварц не тот. Код считал под 16 МГц, на модуле 8 МГц: узел работает
на 250 000 бит/с, шина — на 500 000. Пять кадров узла B — для MCP2515
мусор, REC = 5. Свой кадр — ошибка за ошибкой: 32 попытки, и узел
отключён, EFLG = 0x35.
Самопроверка в loopback. В режиме loopback (2) кадр не выходит на
шину: микросхема передаёт его себе, подтверждения не ждёт и кладёт в
буфер приёма через те же фильтры. Трансивер и вторая плата не нужны.
mcp_selftest переключает режим, шлёт кадр 5A5 с 8 байтами, ждёт его в
очереди приёма, сверяет и возвращает обычный режим: 28 транзакций, 107
байт, 350,2 мкс; кадр — 118 бит с 7 вставками, узел B не увидел ничего.
Прошивка делает это при старте: проверяет SPI, режимы, буферы и фильтры
до того, как выйти на шину. Чего loopback не проверяет — трансивер,
провода, терминаторы и скорость: в опыте с неверным кварцем самопроверка
тоже прошла, за 580,2 мкс, — внутри микросхемы скорость сама с собой
всегда совпадает.
Кадр по битам. Модель собирает кадры сама: SOF, идентификатор, RTR
или SRR и IDE, DLC, данные, CRC-15 (многочлен 0x4599), вставка бита после
пяти одинаковых, затем разделитель CRC, ACK, EOF и межкадровый промежуток.
Длина кадров прогона и 1000 случайных кадров совпала с canframelen.c из
can-utils — во всех 1000. Кадр 100 с 8 нулевыми байтами — 126 бит,
из них 15 вставок; кадр 0AA с 8 байтами AA — 112 бит и 1 вставка.
19н.7 Грабли
Модуль молчит: CANSTAT читается 0x00 или 0xFF. Нет связи по SPI:
перепутаны SO и SI, CS не опускается, нет питания. Драйвер дальше RESET
не идёт.
Код под 16 МГц, кварц 8 МГц. CNF 00 B5 01 для «500 кбит/с» на
кварце 8 МГц — это 250 000 бит/с. Узел копит ошибки и уходит с шины, а
самопроверка в loopback при этом проходит. Прочитать надпись на кварце.
5 В на выводе без допуска. На модуле с одним питанием 5 В выходы SO и INT — тоже 5 В. PA6 и PB0 у F103 без допуска 5 В (с. 29); PB11–PB15 — с допуском (с. 30).
1 Мбит/с не настраивается. От 8 МГц — 4 кванта на бит, а нужно не меньше 6. 500 кбит/с — с выборкой 75 %, не 87,5 %.
Прерывание пришло один раз и больше никогда. Обработчик снял не все флаги: INT остался внизу, спада больше нет. Крутить цикл, пока CANINTF не пуст (19н.4).
Кадры теряются при двух пустых буферах. BUKT снят: при занятом RXB0 кадр пропадает с RX0OVR, RXB1 пустует. Включить ролловер.
Кадры уходят не в том порядке. Три буфера с одинаковым TXP идут от старшего номера, а не от меньшего идентификатора. TXP по идентификатору или один буфер передачи.
Фильтр стандартных кадров пропускает не всё. Биты EID маски у стандартного кадра сравнивают байты данных 0 и 1. Для стандартных кадров — маска с нулями в EID8 и EID0.
Фильтры «не записываются». CNF, маски и фильтры пишутся только в режиме конфигурации; в обычном запись пропадает без ошибки.
Обработчик врывается в транзакцию. Основной цикл и прерывание делят один SPI: транзакции основного цикла — с закрытыми прерываниями.
19н.8 Итоги
- MCP2515 — отдельный контроллер CAN 2.0B на SPI: выручает, когда на F103 занят USB (общая память 512 байт и общие выводы PA11, PA12), когда CAN нет или нужен второй канал.
- Подключение: SPI2 на PB12–PB15, 36 МГц / 8 = 4,5 МГц, режим 0; INT — PB11, EXTI11 по спаду; выводы с допуском 5 В.
- Девять команд: RESET
C0, READ03, WRITE02, BIT MODIFY05, READ STATUSA0, RX STATUSB0, LOAD TX BUFFER40, RTS80, READ RX BUFFER90. - Кварц 8 МГц: квант от 250 нс; 500 кбит/с —
00 91 01, выборка 75 %; 250 кбит/с —00 B5 01и 125 кбит/с —01 B5 01, выборка 87,5 %; 1 Мбит/с — нельзя. - Кадр на отправку — 21 байт по SPI, 37,3 мкс; приём кадра в обработчике — 20 байт, 35,6 мкс.
- Три буфера передачи идут по TXP и номеру, не по идентификатору.
- Обработчик INT — цикл до пустого CANINTF; BUKT = 1 спасает один кадр.
- Маска стандартного кадра в битах EID сравнивает байты данных 0 и 1.
- Одинокий узел: 16 попыток до TEC = 128 и дальше без конца; замыкание линии: 32 попытки до отключения, возврат через 1408 бит.
- Loopback проверяет микросхему и драйвер, но не трансивер и не скорость.
Полные исходники
mcp19n.h — команды, регистры и биты MCP2515, интерфейс драйвера.
/* mcp19n.h — раздел 19н: драйвер CAN-контроллера MCP2515 по SPI без регистров STM32.
* Драйвер говорит с микросхемой через две функции платформы: mcp_spi и mcp_delay_us.
* В прошивке fw19n.c их даёт SPI2 STM32F103, в стенде host19n.c — модель model19n.py.
* Коды команд SPI, адреса CANSTAT…RXB1CTRL, биты CANINTF и EFLG, поля CNF1–CNF3 совпадают с драйвером
* Zephyr (drivers/can/can_mcp2515.h); команда RX STATUS, адреса фильтров и масок, биты TXBnCTRL,
* RXBnCTRL и CANCTRL сверх режима — по документации производителя (в проекте её нет, не проверено). */
#ifndef MCP19N_H
#define MCP19N_H
#include <stdint.h>
/* ── команды SPI ──────────────────────────────────────────────────────── */
#define MCP_RESET 0xC0u
#define MCP_READ 0x03u
#define MCP_WRITE 0x02u
#define MCP_BITMOD 0x05u /* адрес, маска, значение */
#define MCP_READ_STATUS 0xA0u
#define MCP_RX_STATUS 0xB0u
#define MCP_LOAD_TX 0x40u /* | 0, 2, 4 — с TXBnSIDH буфера 0, 1, 2 */
#define MCP_RTS 0x80u /* | биты 0–2 — буферы TXB0–TXB2 */
#define MCP_READ_RX 0x90u /* | 0 — RXB0SIDH, | 4 — RXB1SIDH; флаг RXnIF снимается сам */
/* ── регистры ─────────────────────────────────────────────────────────── */
#define MCP_RXF0 0x00u /* фильтры RXF0–RXF2: 0x00, 0x04, 0x08 */
#define MCP_RXF3 0x10u /* фильтры RXF3–RXF5: 0x10, 0x14, 0x18 */
#define MCP_CANSTAT 0x0Eu
#define MCP_CANCTRL 0x0Fu
#define MCP_TEC 0x1Cu
#define MCP_REC 0x1Du
#define MCP_RXM0 0x20u /* маски RXM0 и RXM1: 0x20, 0x24 */
#define MCP_CNF3 0x28u
#define MCP_CNF2 0x29u
#define MCP_CNF1 0x2Au
#define MCP_CANINTE 0x2Bu
#define MCP_CANINTF 0x2Cu
#define MCP_EFLG 0x2Du
#define MCP_TXB0CTRL 0x30u /* TXB1CTRL 0x40, TXB2CTRL 0x50 */
#define MCP_RXB0CTRL 0x60u
#define MCP_RXB1CTRL 0x70u
/* режимы: CANCTRL.REQOP и CANSTAT.OPMOD, биты 7:5 */
#define MCP_MODE_NORMAL 0u
#define MCP_MODE_LOOPBACK 2u
#define MCP_MODE_LISTEN 3u
#define MCP_MODE_CONFIG 4u
#define MCP_CANCTRL_OSM 0x08u /* одна попытка передачи */
/* CANINTE / CANINTF */
#define MCP_RX0IF 0x01u
#define MCP_RX1IF 0x02u
#define MCP_TX0IF 0x04u
#define MCP_TX1IF 0x08u
#define MCP_TX2IF 0x10u
#define MCP_ERRIF 0x20u
#define MCP_MERRF 0x80u
/* EFLG */
#define MCP_EWARN 0x01u
#define MCP_TXEP 0x10u
#define MCP_TXBO 0x20u
#define MCP_RX0OVR 0x40u
#define MCP_RX1OVR 0x80u
/* TXBnCTRL и RXBnCTRL */
#define MCP_TXREQ 0x08u
#define MCP_TXERR 0x10u
#define MCP_MLOA 0x20u
#define MCP_RXM_ANY 0x60u /* RXM = 11: фильтры выключены, принимать всё */
#define MCP_BUKT 0x04u /* RXB0 полон — кадр уходит в RXB1 */
typedef struct {
uint32_t id;
uint8_t ext, rtr, dlc, buf; /* buf — в каком буфере приёма лежал кадр */
uint8_t data[8];
} mcp_frame_t;
typedef struct {
uint8_t brp, prop, ps1, ps2, sjw; /* в квантах; brp — делитель 1…64 */
uint8_t ntq; /* квантов на бит: 1 + prop + ps1 + ps2 */
uint16_t sp_permille; /* точка выборки, ‰ */
uint8_t cnf1, cnf2, cnf3;
} mcp_timing_t;
#define MCP_RXQ 16u
typedef struct {
mcp_timing_t tm;
uint8_t inte; /* разрешённые прерывания CANINTE */
uint8_t canstat0, canctrl0; /* CANSTAT и CANCTRL сразу после RESET */
uint8_t eflg, tec, rec; /* последнее, что видел обработчик ошибок */
uint32_t mode_polls; /* чтений CANSTAT в последней смене режима */
uint32_t isr_loops; /* проходов цикла в последнем mcp_isr */
uint32_t rx_count, rx_qlost, tx_done, ovr, err_irqs;
mcp_frame_t rxq[MCP_RXQ];
uint8_t rx_head, rx_tail;
} mcp_t;
enum { MCP_OK, MCP_ENOCHIP, MCP_ETIMING, MCP_EMODE, MCP_EBUSY, MCP_EVERIFY, MCP_ESELF };
/* ── платформа: даёт прошивка или стенд ───────────────────────────────── */
void mcp_spi(const uint8_t *tx, uint8_t *rx, uint32_t n); /* CS вниз, n байт, CS вверх */
void mcp_delay_us(uint32_t us);
/* ── драйвер ──────────────────────────────────────────────────────────── */
void mcp_read(uint8_t reg, uint8_t *d, uint32_t n);
uint8_t mcp_read1(uint8_t reg);
void mcp_write(uint8_t reg, const uint8_t *d, uint32_t n);
void mcp_bitmod(uint8_t reg, uint8_t mask, uint8_t v);
uint8_t mcp_read_status(void);
uint8_t mcp_rx_status(void);
int mcp_calc_timing(uint32_t fosc, uint32_t bitrate, mcp_timing_t *t);
int mcp_init(mcp_t *m, uint32_t fosc, uint32_t bitrate, uint8_t bukt);
int mcp_set_mode(mcp_t *m, uint8_t mode);
void mcp_set_filter(uint8_t n, uint32_t id, uint8_t ext); /* только в режиме конфигурации */
void mcp_set_mask(uint8_t n, uint32_t mask, uint8_t ext);
void mcp_use_filters(uint8_t on, uint8_t bukt);
int mcp_load(uint8_t buf, const mcp_frame_t *f, uint8_t txp);
void mcp_rts(uint8_t bufs);
int mcp_send(const mcp_frame_t *f, uint8_t txp); /* свободный буфер, загрузка, RTS */
int mcp_isr(mcp_t *m); /* по INT: пока есть флаги */
int mcp_pop(mcp_t *m, mcp_frame_t *f);
int mcp_selftest(mcp_t *m); /* кадр сам себе в режиме loopback */
#endif
mcp19n.c — драйвер без регистров STM32: общая часть прошивки и стенда.
/* mcp19n.c — раздел 19н: драйвер MCP2515 — команды SPI, битовое время, режимы, фильтры и маски,
* три буфера передачи, приём по INT с циклом до пустого CANINTF, ошибки, самопроверка в loopback.
* Без регистров STM32: общая часть прошивки fw19n.c и стенда host19n.c. */
#include <string.h>
#include "mcp19n.h"
/* ── команды SPI ──────────────────────────────────────────────────────── */
void mcp_read(uint8_t reg, uint8_t *d, uint32_t n)
{
uint8_t tx[16] = {MCP_READ, reg}, rx[16];
mcp_spi(tx, rx, n + 2u); /* адрес растёт сам: n регистров подряд */
memcpy(d, rx + 2, n);
}
uint8_t mcp_read1(uint8_t reg)
{
uint8_t v;
mcp_read(reg, &v, 1);
return v;
}
void mcp_write(uint8_t reg, const uint8_t *d, uint32_t n)
{
uint8_t tx[16] = {MCP_WRITE, reg}, rx[16];
memcpy(tx + 2, d, n);
mcp_spi(tx, rx, n + 2u);
}
void mcp_bitmod(uint8_t reg, uint8_t mask, uint8_t v)
{
uint8_t tx[4] = {MCP_BITMOD, reg, mask, v}, rx[4];
mcp_spi(tx, rx, 4); /* меняются только биты маски — без чтения регистра */
}
uint8_t mcp_read_status(void)
{
uint8_t tx[2] = {MCP_READ_STATUS, 0}, rx[2];
mcp_spi(tx, rx, 2);
return rx[1]; /* RX0IF, RX1IF, TXREQ и TXnIF трёх буферов в одном байте */
}
uint8_t mcp_rx_status(void)
{
uint8_t tx[2] = {MCP_RX_STATUS, 0}, rx[2];
mcp_spi(tx, rx, 2);
return rx[1]; /* 7:6 — где кадры, 4:3 — тип, 2:0 — какой фильтр */
}
/* ── битовое время: TQ = 2 · BRP / Fosc, бит = 1 + PRSEG + PHSEG1 + PHSEG2 квантов ── */
int mcp_calc_timing(uint32_t fosc, uint32_t bitrate, mcp_timing_t *t)
{
uint32_t fcan = fosc / 2u; /* делитель на 2 стоит перед BRP всегда */
for (uint32_t brp = 1; brp <= 64u; brp++) {
if (fcan % (brp * bitrate))
continue; /* только точная скорость */
uint32_t ntq = fcan / (brp * bitrate);
if (ntq > 25u)
continue; /* больше 1 + 16 + 8 квантов не набрать */
if (ntq < 6u)
return -1; /* меньше 1 + 3 + 2: скорость недостижима */
uint32_t ps2 = (ntq * 125u + 999u) / 1000u; /* точка выборки не позже 87,5 % */
if (ps2 < 2u)
ps2 = 2u; /* PHSEG2 не короче 2 квантов */
uint32_t tseg1 = ntq - 1u - ps2;
if (tseg1 > 16u) {
tseg1 = 16u;
ps2 = ntq - 17u;
}
t->brp = (uint8_t)brp;
t->prop = (uint8_t)(tseg1 / 2u);
t->ps1 = (uint8_t)(tseg1 - t->prop);
t->ps2 = (uint8_t)ps2;
t->sjw = 1;
t->ntq = (uint8_t)ntq;
t->sp_permille = (uint16_t)((1000u * (ntq - ps2) + ntq / 2u) / ntq);
t->cnf1 = (uint8_t)(((t->sjw - 1u) << 6) | (brp - 1u));
t->cnf2 = (uint8_t)(0x80u | ((t->ps1 - 1u) << 3) | (t->prop - 1u)); /* BTLMODE: PHSEG2 из CNF3 */
t->cnf3 = (uint8_t)(t->ps2 - 1u);
return 0;
}
return -1;
}
/* ── сброс, настройка, режимы ─────────────────────────────────────────── */
int mcp_set_mode(mcp_t *m, uint8_t mode)
{
mcp_bitmod(MCP_CANCTRL, 0xE0u, (uint8_t)(mode << 5));
for (m->mode_polls = 1; m->mode_polls <= 100u; m->mode_polls++) {
if ((mcp_read1(MCP_CANSTAT) >> 5) == mode)
return MCP_OK; /* режим меняется не сразу: ждём OPMOD */
mcp_delay_us(10);
}
return MCP_EMODE;
}
void mcp_use_filters(uint8_t on, uint8_t bukt)
{
mcp_bitmod(MCP_RXB0CTRL, MCP_RXM_ANY | MCP_BUKT, (uint8_t)((on ? 0u : MCP_RXM_ANY) | (bukt ? MCP_BUKT : 0u)));
mcp_bitmod(MCP_RXB1CTRL, MCP_RXM_ANY, on ? 0u : MCP_RXM_ANY);
}
int mcp_init(mcp_t *m, uint32_t fosc, uint32_t bitrate, uint8_t bukt)
{
uint8_t cmd = MCP_RESET, rx, cnf[4], back[3];
memset(m, 0, sizeof *m);
mcp_spi(&cmd, &rx, 1);
mcp_delay_us(128); /* запуск генератора: 128 тактов при 1 МГц, как в Zephyr */
m->canstat0 = mcp_read1(MCP_CANSTAT);
m->canctrl0 = mcp_read1(MCP_CANCTRL);
if ((m->canstat0 >> 5) != MCP_MODE_CONFIG)
return MCP_ENOCHIP; /* 0x00 или 0xFF — нет связи по SPI */
if (mcp_calc_timing(fosc, bitrate, &m->tm))
return MCP_ETIMING;
m->inte = MCP_RX0IF | MCP_RX1IF | MCP_TX0IF | MCP_TX1IF | MCP_TX2IF | MCP_ERRIF;
cnf[0] = m->tm.cnf3; /* CNF3, CNF2, CNF1, CANINTE — подряд одной записью */
cnf[1] = m->tm.cnf2;
cnf[2] = m->tm.cnf1;
cnf[3] = m->inte;
mcp_write(MCP_CNF3, cnf, 4);
mcp_use_filters(0, bukt);
mcp_read(MCP_CNF3, back, 3);
if (memcmp(back, cnf, 3))
return MCP_EVERIFY; /* CNF пишутся только в режиме конфигурации */
return MCP_OK;
}
/* ── фильтры и маски: SIDH, SIDL, EID8, EID0 ──────────────────────────── */
static void id_bytes(uint32_t id, uint8_t ext, uint8_t b[4])
{
if (ext) {
b[0] = (uint8_t)(id >> 21);
b[1] = (uint8_t)((((id >> 18) & 7u) << 5) | 0x08u | ((id >> 16) & 3u)); /* 0x08 — EXIDE */
b[2] = (uint8_t)(id >> 8);
b[3] = (uint8_t)id;
} else {
b[0] = (uint8_t)(id >> 3);
b[1] = (uint8_t)((id & 7u) << 5);
b[2] = 0; /* у стандартного кадра здесь — байты данных 0 и 1 */
b[3] = 0;
}
}
void mcp_set_filter(uint8_t n, uint32_t id, uint8_t ext)
{
uint8_t b[4];
id_bytes(id, ext, b);
mcp_write((uint8_t)(n < 3u ? 4u * n : MCP_RXF3 + 4u * (n - 3u)), b, 4);
}
void mcp_set_mask(uint8_t n, uint32_t mask, uint8_t ext)
{
uint8_t b[4];
id_bytes(mask, ext, b);
b[1] &= (uint8_t)~0x08u; /* у маски бита EXIDE нет */
mcp_write((uint8_t)(MCP_RXM0 + 4u * n), b, 4);
}
/* ── передача ─────────────────────────────────────────────────────────── */
int mcp_load(uint8_t buf, const mcp_frame_t *f, uint8_t txp)
{
uint8_t tx[14], rx[14];
if (buf > 2u || f->dlc > 8u)
return -1;
mcp_bitmod((uint8_t)(MCP_TXB0CTRL + 0x10u * buf), 0x03u, txp); /* TXP: 3 — старший */
tx[0] = (uint8_t)(MCP_LOAD_TX | (2u * buf));
id_bytes(f->id, f->ext, tx + 1);
tx[5] = (uint8_t)(f->dlc | (f->rtr ? 0x40u : 0u));
memcpy(tx + 6, f->data, f->dlc);
mcp_spi(tx, rx, 6u + f->dlc); /* без адреса: команда сама указывает на TXBnSIDH */
return 0;
}
void mcp_rts(uint8_t bufs)
{
uint8_t tx = (uint8_t)(MCP_RTS | (bufs & 7u)), rx;
mcp_spi(&tx, &rx, 1);
}
int mcp_send(const mcp_frame_t *f, uint8_t txp)
{
uint8_t st = mcp_read_status(); /* TXREQ буферов 0, 1, 2 — биты 2, 4, 6 */
for (uint8_t b = 0; b < 3u; b++) {
if (!(st & (0x04u << (2u * b)))) {
if (mcp_load(b, f, txp))
return -1;
mcp_rts((uint8_t)(1u << b));
return b;
}
}
return -1; /* все три буфера ждут шину */
}
/* ── приём и прерывание ───────────────────────────────────────────────── */
static void rx_buffer(mcp_t *m, uint8_t n)
{
uint8_t tx[14] = {(uint8_t)(MCP_READ_RX | (n << 2))}, rx[14];
const uint8_t *h = rx + 1;
mcp_spi(tx, rx, 14); /* 13 байт буфера; на подъёме CS снимается RXnIF */
mcp_frame_t f;
memset(&f, 0, sizeof f);
if (h[1] & 0x08u) {
f.ext = 1;
f.id = ((uint32_t)h[0] << 21) | ((uint32_t)(h[1] >> 5) << 18) | ((uint32_t)(h[1] & 3u) << 16)
| ((uint32_t)h[2] << 8) | h[3];
f.rtr = (uint8_t)((h[4] >> 6) & 1u);
} else {
f.id = ((uint32_t)h[0] << 3) | (h[1] >> 5);
f.rtr = (uint8_t)((h[1] >> 4) & 1u); /* SRR в SIDL — запрос у стандартного кадра */
}
f.dlc = (uint8_t)(h[4] & 0x0Fu);
if (f.dlc > 8u)
f.dlc = 8;
f.buf = n;
memcpy(f.data, h + 5, 8);
m->rx_count++;
uint8_t next = (uint8_t)((m->rx_head + 1u) % MCP_RXQ);
if (next == m->rx_tail) {
m->rx_qlost++; /* программная очередь полна */
return;
}
m->rxq[m->rx_head] = f;
m->rx_head = next;
}
int mcp_isr(mcp_t *m)
{
for (m->isr_loops = 0; m->isr_loops < 8u; m->isr_loops++) {
uint8_t f = mcp_read1(MCP_CANINTF) & m->inte;
if (!f)
break; /* флагов нет — INT поднялся, следующий спад даст EXTI */
if (f & MCP_RX0IF)
rx_buffer(m, 0);
if (f & MCP_RX1IF)
rx_buffer(m, 1);
m->tx_done += ((f >> 2) & 1u) + ((f >> 3) & 1u) + ((f >> 4) & 1u);
if (f & MCP_ERRIF) {
uint8_t c[2];
m->err_irqs++;
m->eflg = mcp_read1(MCP_EFLG);
mcp_read(MCP_TEC, c, 2); /* TEC и REC подряд */
m->tec = c[0];
m->rec = c[1];
if (m->eflg & (MCP_RX0OVR | MCP_RX1OVR)) {
m->ovr++;
mcp_bitmod(MCP_EFLG, m->eflg & (MCP_RX0OVR | MCP_RX1OVR), 0);
}
}
uint8_t clr = (uint8_t)(f & ~(MCP_RX0IF | MCP_RX1IF));
if (clr)
mcp_bitmod(MCP_CANINTF, clr, 0);
}
return (int)m->isr_loops;
}
int mcp_pop(mcp_t *m, mcp_frame_t *f)
{
if (m->rx_tail == m->rx_head)
return 0;
*f = m->rxq[m->rx_tail];
m->rx_tail = (uint8_t)((m->rx_tail + 1u) % MCP_RXQ);
return 1;
}
/* ── самопроверка: кадр уходит и возвращается внутри микросхемы, шина не нужна ── */
int mcp_selftest(mcp_t *m)
{
static const mcp_frame_t t = {0x5A5u, 0, 0, 8, 0, {0x55, 0xAA, 0x00, 0xFF, 0x01, 0x02, 0x04, 0x08}};
mcp_frame_t r;
int got = 0;
if (mcp_set_mode(m, MCP_MODE_LOOPBACK) != MCP_OK)
return MCP_EMODE;
while (mcp_pop(m, &r)) {
}
if (mcp_send(&t, 0) < 0)
return MCP_EBUSY;
for (uint32_t i = 0; i < 1000u && !got; i++) {
mcp_isr(m);
got = mcp_pop(m, &r);
if (!got)
mcp_delay_us(10);
}
int ok = got && r.id == t.id && !r.ext && r.dlc == t.dlc && !memcmp(r.data, t.data, 8);
if (mcp_set_mode(m, MCP_MODE_NORMAL) != MCP_OK)
return MCP_EMODE;
return ok ? MCP_OK : MCP_ESELF;
}
fw19n.c — прошивка STM32F103: SPI2, EXTI11, вывод в USART1.
/* fw19n.c — раздел 19н: модуль MCP2515 + TJA1050 на STM32F103 (BluePill), 72 МГц; USB (PA11, PA12) свободен.
* SPI2: PB12 — CS (ведём сами), PB13 — SCK, PB14 — MISO, PB15 — MOSI; PB11 — INT модуля, EXTI11 по спаду.
* Все пять выводов — с допуском 5 В (FT). SPI — режим 0, старшим битом вперёд, APB1 36 МГц / 8 = 4,5 МГц.
* CAN 500 кбит/с от кварца модуля 8 МГц, ролловер RXB0 → RXB1. Кадры и ошибки — в USART1 (PA9, 115200).
* Тактирование до 72 МГц — как в разделе 6 (здесь не повторяется); APB1 = 36 МГц. */
#include "stm32f10x.h"
#include "mcp19n.h"
#define CPU_HZ 72000000u
#define MCP_FOSC 8000000u
#define SPI_BR SPI_CR1_BR_1 /* BR = 010: fPCLK / 8 */
_Static_assert(SPI_BR == 0x10u, "BR[2:0] = 010 — биты 5:3 SPI_CR1");
_Static_assert(36000000u / 8u == 4500000u, "SPI2: 36 МГц / 8 = 4,5 МГц");
_Static_assert(CPU_HZ / 115200u == 625u, "USART1: BRR = 625 — 115 200 бод");
static mcp_t M;
/* ── платформа драйвера ───────────────────────────────────────────────── */
void mcp_spi(const uint8_t *tx, uint8_t *rx, uint32_t n)
{
uint32_t pm = __get_PRIMASK();
__disable_irq(); /* обработчик INT не влезет посреди транзакции */
GPIOB->BRR = 1u << 12; /* CS вниз */
for (uint32_t i = 0; i < n; i++) {
while (!(SPI2->SR & SPI_SR_TXE)) {
}
SPI2->DR = tx[i];
while (!(SPI2->SR & SPI_SR_RXNE)) {
}
rx[i] = (uint8_t)SPI2->DR;
}
while (SPI2->SR & SPI_SR_BSY) {
}
GPIOB->BSRR = 1u << 12; /* CS вверх: команда выполнена */
__set_PRIMASK(pm);
}
void mcp_delay_us(uint32_t us)
{
uint32_t t0 = DWT->CYCCNT;
while (DWT->CYCCNT - t0 < us * (CPU_HZ / 1000000u)) {
}
}
void EXTI15_10_IRQHandler(void)
{
if (EXTI->PR & (1u << 11)) {
EXTI->PR = 1u << 11; /* снять до обработки: новый спад во время неё не пропадёт */
mcp_isr(&M); /* читает CANINTF, пока там есть флаги */
}
}
/* ── вывод в USART1 ───────────────────────────────────────────────────── */
static void put(char c)
{
while (!(USART1->SR & USART_SR_TXE)) {
}
USART1->DR = (uint8_t)c;
}
static void puts_(const char *s)
{
while (*s)
put(*s++);
}
static void hex(uint32_t v, int digits)
{
static const char h[] = "0123456789ABCDEF";
while (digits--)
put(h[(v >> (4 * digits)) & 15u]);
}
static void board_init(void)
{
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN
| RCC_APB2ENR_USART1EN;
RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
GPIOB->BSRR = (1u << 12) | (1u << 11); /* CS вверх; подтяжка INT вверх */
GPIOB->CRH = (GPIOB->CRH & 0x00000FFFu)
| 0x8u << 12 /* PB11 — вход с подтяжкой: INT */
| 0x3u << 16 /* PB12 — выход 50 МГц: CS */
| 0xBu << 20 /* PB13 — SCK, альтернативная функция */
| 0x4u << 24 /* PB14 — MISO, вход без подтяжки */
| 0xBu << 28; /* PB15 — MOSI, альтернативная функция */
GPIOA->CRH = (GPIOA->CRH & ~(0xFu << 4)) | 0xBu << 4; /* PA9 — USART1_TX */
GPIOC->CRH = (GPIOC->CRH & ~(0xFu << 20)) | 0x2u << 20; /* PC13 — светодиод платы */
SPI2->CR1 = SPI_CR1_MSTR | SPI_BR | SPI_CR1_SSM | SPI_CR1_SSI; /* ведущий, режим 0, NSS программный */
SPI2->CR1 |= SPI_CR1_SPE;
USART1->BRR = CPU_HZ / 115200u;
USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
}
static void exti_init(void)
{
AFIO->EXTICR[2] = (AFIO->EXTICR[2] & ~(0xFu << 12)) | 0x1u << 12; /* EXTI11 ← PB11 */
EXTI->FTSR |= 1u << 11; /* INT активен низким: прерывание по спаду */
EXTI->IMR |= 1u << 11;
EXTI->PR = 1u << 11;
NVIC_EnableIRQ(EXTI15_10_IRQn);
}
static void fail(const char *what, int st)
{
puts_(what);
hex((uint32_t)st, 2);
puts_("\r\n");
for (;;) {
GPIOC->ODR ^= 1u << 13; /* частое мигание: модуль не отвечает */
mcp_delay_us(100000);
}
}
int main(void)
{
mcp_frame_t f = {0x100u, 0, 0, 8, 0, {0}}, r;
uint32_t n = 0;
uint8_t eflg_seen = 0;
board_init();
int st = mcp_init(&M, MCP_FOSC, 500000u, 1);
if (st != MCP_OK)
fail("MCP2515 init st=", st);
st = mcp_selftest(&M); /* loopback: SPI, буферы и фильтры без шины */
if (st != MCP_OK)
fail("MCP2515 selftest st=", st);
puts_("MCP2515 ok, CNF ");
hex(M.tm.cnf1, 2);
hex(M.tm.cnf2, 2);
hex(M.tm.cnf3, 2);
puts_("\r\n");
exti_init();
for (;;) {
f.data[0] = (uint8_t)n;
f.data[1] = (uint8_t)(n >> 8);
if (mcp_send(&f, 0) >= 0)
n++;
for (uint32_t k = 0; k < 100u; k++) { /* 100 мс: разбирать очередь приёма */
while (mcp_pop(&M, &r)) {
puts_(r.ext ? "RX x" : "RX ");
hex(r.id, r.ext ? 8 : 3);
put(' ');
for (uint32_t i = 0; i < r.dlc; i++)
hex(r.data[i], 2);
puts_("\r\n");
}
if (M.eflg != eflg_seen) { /* состояние ошибок поменялось */
eflg_seen = M.eflg;
puts_("EFLG ");
hex(eflg_seen, 2);
puts_(" TEC ");
hex(M.tec, 2);
puts_("\r\n");
}
mcp_delay_us(1000);
}
GPIOC->ODR ^= 1u << 13;
}
}
Дальше: раздел 20 «USB».