Embedded HandbookПриложения
Все разделы › III. Периферия
STM32F103 · уровень средний · 39 мин чтения
Открыть в интерактивном справочнике

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 пассивный по ошибкам / отключён от шины

Вверху — подключение модуля MCP2515 с трансивером TJA1050 к STM32F103 по SPI2 в режиме 0 на 4,5 МГц: PB12 — CS, PB13 — SCK, PB14 — MISO, он же вывод SO микросхемы, PB15 — MOSI, он же SI, PB11 — вывод INT, спад которого ловит EXTI11; у MCP2515 кварц 8 МГц, выводы TXCAN и RXCAN идут к TJA1050 с питанием 5 В, от него CANH и CANL — к шине с резисторами 120 Ом на концах и вторым узлом B; выводы PB11–PB15 с допуском 5 В, USB на PA11 и PA12 остаётся свободным. Посередине — внутреннее устройство: три буфера передачи TXB0–TXB2 уходят в протокольный блок со счётчиками TEC, REC и регистром EFLG в порядке приоритета TXP, а при равном — от старшего номера буфера; принятый кадр идёт в RXB0 через маску RXM0 и фильтры RXF0, RXF1, а если RXB0 занят и включён BUKT — в RXB1, иначе в RXB1 через маску RXM1 и фильтры RXF2–RXF5; занятый буфер — переполнение и потерянный кадр; флаги CANINTF держат INT внизу, пока их не снимут. Внизу — бит при кварце 8 МГц и кванте 250 нс: на 500 кбит/с восемь квантов — синхронизация 1, распространение 2, PS1 3, PS2 2, выборка на 75 %, CNF 00 91 01; на 250 кбит/с шестнадцать квантов — 1, 6, 7, 2, выборка на 87,5 %, CNF 00 B5 01; на 125 кбит/с — CNF 01 B5 01 с квантом 500 нс; 1 Мбит/с от 8 МГц не набрать — 4 кванта на бит; вне шины узел возвращается после 128 × 11 рецессивных бит


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 Итоги

Полные исходники

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».

Открыть в интерактивном справочнике