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

93а. nRF24L01: радио 2,4 ГГц на SPI — адреса, каналы, Enhanced ShockBurst, подтверждения и повторы

В разделе 93 радио LoRa: далеко, медленно, с расчётом бюджета линии. Здесь — его противоположность: самый дешёвый модуль на 2,4 ГГц, который бьёт на десятки метров, зато быстро, и сам подтверждает и повторяет пакеты. Микроконтроллеру остаётся SPI, два вывода и прерывание. Разбираем, что летит в эфире, как подключить модуль к F103, какие команды и регистры нужны, как работают подтверждение, повторы и шесть труб приёма и где на этом модуле обычно теряют вечер.

Опоры: RM0008 (DocID13902 Rev 17): таблица режимов выводов, с. 160; AFIO_EXTICR1, с. 190; EXTI_FTSR, с. 211; делитель BR в SPI_CR1, с. 743; CPOL и CPHA, с. 744. DocID13587 Rev 17 (STM32F103x8/xB): PA4–PA7 — SPI1_NSS, SPI1_SCK, SPI1_MISO, SPI1_MOSI, с. 29. Документа на nRF24L01+ в проекте нет: команды, регистры, значения после сброса, формат пакета и CRC взяты из общеизвестной спецификации и заложены в модель. Поэтому всё ниже проверяет согласие драйвера с моделью, а не с микросхемой. Все числа — прогон драйвера _verify/nrf93b/nrf93b.c на компьютере (gcc 12.2.0) со своей моделью модулей и эфира на Python 3.11; прошивка собрана armclang 6.24 под Cortex-M3.

Как это называется в документации

Оригинал Перевод
Enhanced ShockBurst (ESB) протокол пакетов модуля с подтверждением и повтором
PTX / PRX передатчик / приёмник
data pipe труба приёма: свой адрес, до шести на приёмник
auto acknowledgement автоподтверждение
auto retransmit delay (ARD) / count (ARC) ожидание подтверждения / число повторов
packet control field (PCF) поле управления пакета: длина, PID, NO_ACK
packet identity (PID) номер пакета, 2 бита: узнать повтор
dynamic payload length (DPL) переменная длина пакета
payload полезные данные
chip enable (CE) вывод включения приёма и передачи
flush очистить FIFO
GFSK частотная манипуляция с гауссовым фильтром

Вверху — пакет Enhanced ShockBurst: преамбула 1 байт, адрес 5 байт старшим байтом вперёд, поле PCF 9 бит — длина 6, PID 2, NO_ACK 1 — данные 0–32 байта и CRC 1–2 байта; CRC считается по адресу, PCF и данным; 32 байта данных с адресом 5 байт и CRC 2 байта — 329 бит, на 1 Мбит/с это 329 мкс. Посередине — подключение к STM32F103: PA4 — CSN, PA5 — SCK, PA6 — MISO, PA7 — MOSI на SPI1 в режиме 0 на 9 МГц, PB0 — CE, PB1 — IRQ; модуль на канале 2476 МГц, питание 3,3 В и электролит у самого модуля; IRQ в нуле, пока поднят RX_DR, TX_DS или MAX_RT. Внизу — обмен: импульс CE, через 130 мкс пакет 329 мкс, через 130 мкс подтверждение ACK 73 мкс, TX_DS через 662 мкс от CE; нет подтверждения за ARD — повтор с тем же PID, после ARC повторов — MAX_RT; на 250 кбит/с ACK длится 292 мкс, и ARD 250 мкс мал


93а.1 Что за модуль

nRF24L01+ — приёмопередатчик на 2,4 ГГц с SPI. Модуляция — GFSK, скорости 250 кбит/с, 1 и 2 Мбит/с, канал задаёт регистр RF_CH: частота 2400 + RF_CH МГц, каналы 0–125 с шагом 1 МГц. Эти числа — из общеизвестной спецификации, документа на модуль в проекте нет. Драйвер этого раздела ставит канал 76: модель пересчитала его в 2476 МГц.

Главное отличие от «просто радио» — Enhanced ShockBurst: пакеты собирает, подтверждает и повторяет сам модуль. Передатчик кладёт данные в FIFO и дёргает CE; модуль отправляет пакет, переходит на приём и ждёт подтверждение. Нет подтверждения — повторяет сам, до ARC раз. Приёмник тоже без участия программы проверяет CRC, отвечает подтверждением и откладывает пакет в FIFO на три пакета. Программе приходит один флаг: передано, не передано или принято.

Отсюда и раскладка раздела: SPI и регистры — 93а.3, пакет — 93а.4, подтверждения и повторы — 93а.5, приём и трубы — 93а.6.


93а.2 Подключение к F103

Подключение то же, что у SX1276 в разделе 93.5, — SPI1 и два вывода:

Вывод модуля STM32 Назначение
CSN PA4 выбор, активный ноль; начало и конец каждой команды
SCK PA5 такт SPI1
MISO PA6 данные от модуля
MOSI PA7 данные в модуль
CE PB0 «1» — слушать (приёмник), импульс — передать
IRQ PB1 ноль, пока поднят флаг; EXTI1 по спаду
VCC, GND — только 3,3 В

PA4–PA7 — выводы SPI1 (DocID13587, с. 29). CSN здесь обычный выход: он должен держать ноль всю команду, от первого байта до последнего. SPI — режим 0 (CPOL = 0, CPHA = 0, с. 744), старший бит первым. APB2 на 72 МГц, делитель 8 (BR = 010, с. 743) — 9 МГц; предел SPI модуля по общеизвестной спецификации — 10 МГц. PB1 — вход с подтяжкой (код 1000 в таблице режимов, с. 160), на EXTI1 его выбирает AFIO_EXTICR1 = 0001 (с. 190), спад включает EXTI_FTSR (с. 211):

static void spi1_init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN
                  | RCC_APB2ENR_SPI1EN;
    /* PA7, PA5 — альтернативная функция 50 МГц (1011), PA6 — вход (0100), PA4 — выход 50 МГц (0011) */
    GPIOA->CRL = (GPIOA->CRL & 0x0000FFFFu) | 0xB4B30000u;
    GPIOA->BSRR = 1u << 4;                              /* CSN = 1 до первой команды */
    /* PB0 — выход (0011), PB1 — вход с подтяжкой (1000) */
    GPIOB->CRL = (GPIOB->CRL & 0xFFFFFF00u) | 0x00000083u;
    GPIOB->BSRR = 1u << 1;                              /* подтяжка PB1 вверх; CE = 0 */
    GPIOB->BSRR = 1u << 16;
    /* PB10 — вход с подтяжкой: перемычка на землю — приёмник */
    GPIOB->CRH = (GPIOB->CRH & ~(0xFu << 8)) | 0x8u << 8;
    GPIOB->BSRR = 1u << 10;
    GPIOC->CRH = (GPIOC->CRH & ~(0xFu << 20)) | 0x2u << 20;    /* PC13 — светодиод */
    /* APB2 72 МГц / 8 = 9 МГц, режим 0, старший бит первым */
    SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_1 | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_SPE;
}

Питание. Модуль питается от 3,3 В. На передаче он берёт ток рывками, а провода и слабый стабилизатор BluePill дают просадку — у самого модуля ставят электролит, обычно 10–100 мкФ (практика, не документ). При просадке модуль перезапускается со значениями после сброса, и связь молча пропадает (93а.7).


93а.3 Команды SPI и регистры

Каждая команда — один обмен при CSN = 0: первый байт — код команды, дальше данные. В ответ на первый байт модуль всегда отдаёт STATUS — поэтому флаги можно читать даром, командой NOP (0xFF). В модели после сброса первый обмен «прочитать CONFIG» — байты 00 FF — вернул 0E 08: STATUS = 0x0E и CONFIG = 0x08.

Команда Код Что делает
R_REGISTER 000A AAAA прочитать регистр A
W_REGISTER 001A AAAA записать регистр A
R_RX_PAYLOAD 0x61 забрать пакет из FIFO приёма
W_TX_PAYLOAD 0xA0 положить пакет в FIFO передачи
FLUSH_TX / FLUSH_RX 0xE1 / 0xE2 очистить FIFO
R_RX_PL_WID 0x60 длина первого пакета при переменной длине
NOP 0xFF ничего — только STATUS

Регистры, которые нужны всегда:

Регистр Адрес После сброса Что в нём
CONFIG 0x00 0x08 EN_CRC, CRCO (CRC 2 байта), PWR_UP, PRIM_RX
EN_AA 0x01 0x3F автоподтверждение по трубам
EN_RXADDR 0x02 0x03 включённые трубы
SETUP_RETR 0x04 0x03 ARD (7:4) — ждать (ARD + 1) × 250 мкс, ARC (3:0) — повторов
RF_CH 0x05 0x02 канал
RF_SETUP 0x06 0x0E скорость (RF_DR_LOW, RF_DR_HIGH), мощность
STATUS 0x07 0x0E RX_DR, TX_DS, MAX_RT, номер трубы, TX_FULL
RX_ADDR_P0, TX_ADDR 0x0A, 0x10 E7E7E7E7E7 адрес трубы 0 и адрес передачи
RX_PW_P0…P5 0x11–0x16 0 длина пакета трубы; 0 — труба не работает
DYNPD, FEATURE 0x1C, 0x1D 0 переменная длина

Флаги STATUS снимаются записью единицы в свой бит. Адреса пишутся младшим байтом первым. nrf_init пишет всё нужное подряд; на передатчике это 20 обменов, на приёмнике вместе с открытием трубы и nrf_listen — 19. В модели после настройки: CONFIG = 0x0E у передатчика и 0x0F у приёмника (PRIM_RX), SETUP_RETR = 0x15 — ждать 500 мкс, 5 повторов, RF_CH = 0x4C, RF_SETUP = 0x06 — 1 Мбит/с и наибольшая мощность.

Первое, что делает nrf_init, — пишет ширину адреса и читает её обратно. Это проверка, что модуль вообще есть (93а.7):

int nrf_init(nrf_t *d, uint8_t id, const nrf_cfg_t *c, int rx)
{
    static const uint8_t rate[] = {0, NRF_RF_DR_HIGH, NRF_RF_DR_LOW};
    *d = (nrf_t){0};
    d->id = id;
    d->len = c->len;
    d->dyn = c->dyn;
    nrf_port_ce(id, 0);
    nrf_write(d, NRF_SETUP_AW, 3u);                     /* адрес 5 байт */
    if (nrf_read(d, NRF_SETUP_AW) != 3u)
        return NRF_E_NOCHIP;                            /* нет питания или провода: читаются нули или единицы */
    nrf_write(d, NRF_RF_CH, c->ch & 0x7Fu);
    nrf_write(d, NRF_RF_SETUP, (uint8_t)(rate[c->rate] | NRF_RF_PWR_0DBM));
    nrf_write(d, NRF_SETUP_RETR, (uint8_t)(c->ard << 4 | (c->arc & 0x0Fu)));
    nrf_write(d, NRF_FEATURE, c->dyn ? NRF_EN_DPL : 0u);
    nrf_write(d, NRF_DYNPD, 0);
    nrf_write(d, NRF_EN_AA, 0);
    nrf_write(d, NRF_EN_RXADDR, 0);
    nrf_cmd(d, NRF_FLUSH_TX);
    nrf_cmd(d, NRF_FLUSH_RX);
    nrf_write(d, NRF_STATUS, NRF_RX_DR | NRF_TX_DS | NRF_MAX_RT);
    if (!rx) {
        nrf_write_addr(d, NRF_TX_ADDR, c->addr);
        nrf_open_rx(d, 0, c->addr);                     /* подтверждение приходит на адрес трубы 0 */
    }
    d->config = (uint8_t)(NRF_EN_CRC | (c->crc2 ? NRF_CRCO : 0u) | NRF_PWR_UP | (rx ? NRF_PRIM_RX : 0u));
    nrf_write(d, NRF_CONFIG, d->config);
    nrf_port_delay_us(id, NRF_T_PWR_US);
    return NRF_OK;
}

93а.4 Пакет Enhanced ShockBurst

В эфир идёт: преамбула 1 байт, адрес 3–5 байт, поле PCF 9 бит (длина 6 бит, PID 2 бита, NO_ACK 1 бит), данные 0–32 байта и CRC 1 или 2 байта. Преамбула — 10101010 или 01010101, по первому биту адреса. Адрес уходит старшим байтом вперёд — в регистр он пишется наоборот. PCF в 9 бит сдвигает всё дальше с границы байта, поэтому CRC считается по битам:

uint16_t esb_crc(const uint8_t *bits, uint32_t from, uint32_t nbits, int crc2)
{
    uint16_t crc = crc2 ? 0xFFFFu : 0xFFu;
    uint16_t top = crc2 ? 0x8000u : 0x80u, poly = crc2 ? 0x1021u : 0x07u, mask = crc2 ? 0xFFFFu : 0xFFu;
    for (uint32_t i = from; i < from + nbits; i++) {
        int fb = ((crc & top) != 0) ^ bit(bits, i);
        crc = (uint16_t)((crc << 1) & mask);
        if (fb)
            crc ^= poly;
    }
    return crc;
}

CRC 1 байт — многочлен x^8 + x^2 + x + 1 с начальным 0xFF, 2 байта — x^16 + x^12 + x^5 + 1 с начальным 0xFFFF (общеизвестная спецификация). Проверка кода — три независимых счёта по строке «123456789»: esb_crc на стенде, своя функция модели и binascii.crc_hqx из Python дали одно и то же — 0x29B1; CRC 1 байт — 0xFB. Пакет «STM32» с PID 1 esb_pack собрал в 113 бит — AAC35AA093011529AA26999944BD00, CRC 0x897A, с CRC 1 байт — 105 бит; сборка модели совпала бит в бит.

Сколько пакет висит в эфире, считается из длины. С адресом 5 байт и CRC 2 байта:

Пакет Бит 2 Мбит/с 1 Мбит/с 250 кбит/с
32 байта данных 329 164,5 мкс 329 мкс 1316 мкс
подтверждение без данных 73 36,5 мкс 73 мкс 292 мкс

Что ловит CRC. Модель портила пакеты из 32 байт — переворачивала k случайных бит — и спрашивала стенд, сойдётся ли CRC. По 4000 пакетов на каждое k:

Перевёрнуто бит 1 2 3 4 5 8
пропустил CRC 2 байта 0 0 0 0 0 1
пропустил CRC 1 байт 0 16 0 40 0 38

Нечётное число ошибок CRC 1 байт ловит всегда: его многочлен делится на x + 1. Чётное пропускает около 1 %. CRC 2 байта на 24 000 испорченных пакетов пропустил один. Её и стоит ставить: CRCO = 1. Во всех 48 000 счётах esb_crc совпал с моделью.


93а.5 Передача: подтверждение и повторы

Передача — пакет в FIFO, импульс CE не короче 10 мкс, ждать IRQ, по STATUS понять, чем кончилось:

int nrf_send(nrf_t *d, const uint8_t *p, uint8_t n)
{
    uint8_t tx[33] = {NRF_W_TX_PAYLOAD}, rx[33];
    uint8_t len = d->dyn ? n : d->len;                  /* постоянная длина: хвост — нули */
    for (uint8_t i = 0; i < len; i++)
        tx[1 + i] = i < n ? p[i] : 0u;
    xfer(d, tx, rx, 1u + len);
    nrf_port_ce(d->id, 1);                              /* импульс CE — один пакет со всеми повторами */
    nrf_port_delay_us(d->id, NRF_T_CE_US);
    nrf_port_ce(d->id, 0);
    if (!nrf_port_wait_irq(d->id, NRF_T_SEND_US)) {
        nrf_cmd(d, NRF_FLUSH_TX);
        d->timeouts++;
        return NRF_E_TIMEOUT;                           /* IRQ не пришёл: модуль сброшен или не включён */
    }
    uint8_t st = nrf_cmd(d, NRF_NOP);
    d->retries += nrf_read(d, NRF_OBSERVE_TX) & 0x0Fu;  /* ARC_CNT: сколько повторов понадобилось */
    nrf_write(d, NRF_STATUS, st & (NRF_TX_DS | NRF_MAX_RT));
    if (st & NRF_TX_DS) {
        d->sent++;
        return NRF_OK;
    }
    nrf_cmd(d, NRF_FLUSH_TX);                           /* после MAX_RT пакет остаётся в FIFO */
    d->failed++;
    return NRF_E_MAXRT;
}

В модели на 1 Мбит/с с 32 байтами: загрузка пакета по SPI — 29,3 мкс; от фронта CE пакет пошёл через 130 мкс и кончился через 459 мкс, подтверждение кончилось через 662 мкс — тогда и поднялся TX_DS, а nrf_send вернулась через 666,4 мкс. 130 мкс на перестройку, готовность через 1,5 мс после PWR_UP — допущения модели по общеизвестной спецификации. 1000 пакетов подряд с приёмом на другой стороне:

Скорость мкс на пакет данных, кбит/с
2 Мбит/с 529,4 483,5
1 Мбит/с 730,4 350,5
250 кбит/с 1936,4 132,2

Все 1000 приняты без искажений на каждой скорости. Половину времени на 2 Мбит/с съедают две перестройки по 130 мкс, а не эфир.

ARD должен вместить подтверждение. Модуль ждёт его ARD после конца пакета. Подтверждению нужно 130 мкс и его длина: 166,5 мкс на 2 Мбит/с, 203 мкс на 1 Мбит/с и 422 мкс на 250 кбит/с. Самое короткое ожидание — 250 мкс — на 250 кбит/с мало, и это видно в прогоне: 100 пакетов приняты все, а передатчик о каждом сообщил MAX_RT, приёмник получил ещё 300 повторов и выбросил их как копии. С ARD = 1 (500 мкс) — все 100 подтверждены.

Потери. Модель переворачивала один бит в пакете и, отдельно, в подтверждении с вероятностью p — CRC приёмника такой пакет отбрасывает. 1000 пакетов на 1 Мбит/с с ожиданием 500 мкс:

p ARC TX_DS MAX_RT дошло дошло, но MAX_RT копий выброшено
0,1 0 809 191 899 90 0
0,1 3 997 3 1000 3 111
0,1 15 1000 0 1000 0 131
0,3 0 485 515 707 222 0
0,3 3 942 58 997 55 335
0,3 15 1000 0 1000 0 432
0,5 0 248 752 506 258 0
0,5 3 697 303 940 243 461
0,5 15 993 7 1000 7 881

Попытка удаётся, когда дошли и пакет, и подтверждение: (1 − p)². Тогда TX_DS бывает с вероятностью 1 − (1 − (1 − p)²)^(ARC + 1), а пакет доходит с вероятностью 1 − p^(ARC + 1); прогон сходится с этим в пределах разброса (проверка считает обе формулы). Два вывода. Повторы вытягивают даже плохой канал: при p = 0,5 с 15 повторами дошли все 1000. И MAX_RT не значит «не дошло»: при p = 0,5 и 3 повторах 243 пакета из 303 «потерянных» приёмник получил — пропало подтверждение. Если важно не задвоить команду, нумеруйте пакеты в данных.


93а.6 Приём по IRQ, трубы 0–5

Приёмник — PRIM_RX = 1 и CE = 1, через 130 мкс он слушает. Пакет с верной CRC модуль подтверждает, кладёт в FIFO и поднимает RX_DR — IRQ падает в ноль, EXTI1 вызывает прерывание. Номер трубы — в битах 3:1 STATUS, 7 — FIFO пуст:

int nrf_recv(nrf_t *d, uint8_t *p, uint8_t *pipe)
{
    uint8_t tx[33] = {NRF_R_RX_PAYLOAD}, rx[33];
    uint8_t st = nrf_cmd(d, NRF_NOP);
    if (NRF_RX_P_NO(st) > 5u) {
        nrf_write(d, NRF_STATUS, NRF_RX_DR);
        return 0;                                       /* FIFO приёма пуст */
    }
    uint8_t n = d->len;
    if (d->dyn) {
        uint8_t w[2] = {NRF_R_RX_PL_WID, NRF_NOP};
        xfer(d, w, rx, 2);
        n = rx[1];
        if (n > 32u) {
            nrf_cmd(d, NRF_FLUSH_RX);
            return 0;
        }
    }
    for (uint8_t i = 1; i <= n; i++)
        tx[i] = NRF_NOP;
    xfer(d, tx, rx, 1u + n);
    for (uint8_t i = 0; i < n; i++)
        p[i] = rx[1 + i];
    *pipe = (uint8_t)NRF_RX_P_NO(st);
    nrf_write(d, NRF_STATUS, NRF_RX_DR);                /* снять флаг; в FIFO может быть ещё пакет */
    return n;
}

В FIFO может лежать до трёх пакетов, поэтому прерывание забирает всё, а флаг снимает после каждого пакета:

void EXTI1_IRQHandler(void)
{
    uint8_t p[32], pipe;
    EXTI->PR = 1u << 1;
    while (nrf_recv(&radio, p, &pipe) > 0) {            /* забрать всё: в FIFO до трёх пакетов */
        got++;
        GPIOC->ODR ^= 1u << 13;
    }
}

Повторы приёмник отсеивает сам. Если подтверждение пропало, передатчик шлёт тот же пакет с тем же PID; приёмник сравнивает PID и CRC с прошлым пакетом трубы, снова подтверждает, но в FIFO не кладёт. В таблице 93а.5 это колонка «копий выброшено» — ни одна копия до программы не дошла.

Трубы. Приёмник слушает до шести адресов сразу. Трубы 0 и 1 имеют полные адреса по 5 байт, у труб 2–5 свой только младший байт, остальные четыре — общие с трубой 1. В модели приёмник открыл все шесть (EN_RXADDR = 0x3F, EN_AA = 0x3F): труба 0 — E011223344, труба 1 — 1093A05AC3, трубы 2–5 — младшие байты 11, 12, 13, 14. Шесть передатчиков отправили по 10 пакетов, каждый на свой адрес: приёмник получил по 10 в каждую трубу, все 60 — с верным номером трубы и подтверждены.

Переменная длина. С EN_DPL в FEATURE и битом трубы в DYNPD длина едет в PCF, а приёмник читает её командой R_RX_PL_WID. Пакеты длиной от 1 до 32 байт — все 32 подтверждены, длины на приёме совпали.


93а.7 Грабли

Всё по нулям. Модуль без питания или без провода: по MISO приходят одни нули (или единицы — смотря как висит линия). STATUS = 0x00 или 0xFF, запись «проходит», а чтение возвращает то же. В модели в обоих случаях nrf_init прочитала не ту ширину адреса и вернула NRF_E_NOCHIP. Без такой проверки программа «настраивает» пустоту и ждёт пакеты вечно.

Пакеты доходят, а передатчик пишет MAX_RT. Подтверждение приходит на адрес трубы 0 передатчика, и RX_ADDR_P0 обязан совпадать с TX_ADDR. В модели с RX_ADDR_P0, оставшимся E7E7E7E7E7: приёмник получил все 100 пакетов и отправил 400 подтверждений, передатчик не принял ни одного — 100 MAX_RT и 300 лишних повторов.

Ни одного пакета. У пары должны совпадать канал, скорость, длина CRC, адрес, длина пакета трубы, и приёмник должен держать CE = 1. В модели по 100 пакетов на каждую ошибку:

Отличие Принято MAX_RT Отброшено по CRC
нет — всё совпадает 100 0 0
канал 77 вместо 76 0 100 0
скорость 2 Мбит/с вместо 1 0 100 0
CRC 1 байт вместо 2 0 100 400
RX_PW_P1 = 16, а пакет 32 байта 0 100 400
RX_PW_P1 = 0 0 100 0
CE приёмника = 0 0 100 0
другой младший байт адреса 0 100 0

Ошибки CRC и длины видны: приёмник слышит пакет и отбрасывает его. Канал, скорость, адрес и CE не оставляют следов вообще.

Прерывание пришло один раз. EXTI ловит спад, а IRQ держит ноль, пока поднят хоть один флаг. Обработчик забрал пакет, но не снял RX_DR — IRQ так и остался в нуле, нового спада нет. В модели из 10 пакетов программа получила 1. Модуль принял ещё три и заполнил FIFO, а дальше перестал подтверждать: последние 6 передач — MAX_RT, 24 попытки упали в полный FIFO. С обработчиком из 93а.6 — 10 из 10.

Связь пропала после рывка тока. Модуль перезапустился от просадки питания: CONFIG = 0x08, PWR_UP = 0, импульс CE ничего не делает, IRQ не приходит. В модели передатчик сбросили после 100-го пакета: дальше все 100 вызовов nrf_send кончились по времени. Если на NRF_E_TIMEOUT проверять nrf_alive и заново звать nrf_init, дошли все 200, перенастройка понадобилась одна. Электролит у модуля лечит причину, проверка — следствие.

Регистры «не пишутся». По общеизвестной спецификации W_REGISTER работает в режимах выключения и ожидания: настройку меняют при CE = 0. Модель этого правила не воспроизводит — не проверено.


93а.8 Итоги

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

nrf93b.h — команды, регистры и биты nRF24L01+, настройка, порт и функции драйвера.

/* nrf93b.h — раздел 93а: драйвер nRF24L01+ без регистров микроконтроллера.
 * Драйвер говорит с модулем только через четыре функции порта (nrf_port_*): в прошивке fw93b.c их
 * делают SPI1 и выводы STM32F103, в стенде host93b.c — модель двух и более модулей model93b.py.
 * Карта регистров и команды — по общеизвестной спецификации nRF24L01+; документа в проекте нет. */
#ifndef NRF93B_H
#define NRF93B_H
#include <stdint.h>

/* ── команды SPI: первый байт обмена; в ответ на него модуль всегда отдаёт STATUS ── */
#define NRF_R_REGISTER    0x00u         /* 000A AAAA: прочитать регистр A */
#define NRF_W_REGISTER    0x20u         /* 001A AAAA: записать регистр A */
#define NRF_R_RX_PL_WID   0x60u         /* длина первого пакета в FIFO приёма */
#define NRF_R_RX_PAYLOAD  0x61u         /* забрать пакет из FIFO приёма */
#define NRF_W_TX_PAYLOAD  0xA0u         /* положить пакет в FIFO передачи */
#define NRF_FLUSH_TX      0xE1u
#define NRF_FLUSH_RX      0xE2u
#define NRF_NOP           0xFFu         /* ничего не делать — только получить STATUS */

/* ── регистры ─────────────────────────────────────────────────────────── */
#define NRF_CONFIG        0x00u
#define NRF_EN_AA         0x01u         /* автоподтверждение по трубам */
#define NRF_EN_RXADDR     0x02u         /* включённые трубы приёма */
#define NRF_SETUP_AW      0x03u         /* ширина адреса: 1 — 3, 2 — 4, 3 — 5 байт */
#define NRF_SETUP_RETR    0x04u         /* ARD (7:4) и ARC (3:0) */
#define NRF_RF_CH         0x05u         /* канал: 2400 + RF_CH МГц */
#define NRF_RF_SETUP      0x06u
#define NRF_STATUS        0x07u
#define NRF_OBSERVE_TX    0x08u         /* PLOS_CNT (7:4), ARC_CNT (3:0) */
#define NRF_RX_ADDR_P0    0x0Au         /* 0x0A…0x0F — адреса труб 0…5 */
#define NRF_TX_ADDR       0x10u
#define NRF_RX_PW_P0      0x11u         /* 0x11…0x16 — длина пакета труб 0…5; 0 — труба не работает */
#define NRF_FIFO_STATUS   0x17u
#define NRF_DYNPD         0x1Cu         /* переменная длина по трубам */
#define NRF_FEATURE       0x1Du

/* CONFIG */
#define NRF_EN_CRC        0x08u
#define NRF_CRCO          0x04u         /* 1 — CRC 2 байта */
#define NRF_PWR_UP        0x02u
#define NRF_PRIM_RX       0x01u         /* 1 — приёмник, 0 — передатчик */
/* STATUS: флаги снимаются записью единицы */
#define NRF_RX_DR         0x40u         /* пакет принят */
#define NRF_TX_DS         0x20u         /* пакет передан и подтверждён */
#define NRF_MAX_RT        0x10u         /* повторы кончились, подтверждения нет */
#define NRF_RX_P_NO(s)    (((s) >> 1) & 7u)     /* труба первого пакета в FIFO; 7 — FIFO пуст */
/* RF_SETUP */
#define NRF_RF_DR_LOW     0x20u
#define NRF_RF_DR_HIGH    0x08u
#define NRF_RF_PWR_0DBM   0x06u
/* FEATURE */
#define NRF_EN_DPL        0x04u

/* ── времена, мкс ─────────────────────────────────────────────────────── */
#define NRF_T_PWR_US      5000u         /* после PWR_UP — до готовности, с запасом */
#define NRF_T_CE_US       15u           /* импульс CE для передачи: не короче 10 мкс */
#define NRF_T_RX_US       130u          /* после CE = 1 приёмник слушает через 130 мкс */
#define NRF_T_SEND_US     100000u       /* дольше любой передачи со всеми повторами */

enum { NRF_1MBPS, NRF_2MBPS, NRF_250KBPS };
enum { NRF_OK = 0, NRF_E_NOCHIP = -1, NRF_E_MAXRT = -2, NRF_E_TIMEOUT = -3 };

typedef struct {
    uint8_t ch;                         /* 0…125 */
    uint8_t rate;                       /* NRF_1MBPS, NRF_2MBPS, NRF_250KBPS */
    uint8_t ard;                        /* 0…15: ждать подтверждение (ard + 1) × 250 мкс */
    uint8_t arc;                        /* 0…15 повторов */
    uint8_t crc2;                       /* 1 — CRC 2 байта, 0 — 1 байт */
    uint8_t len;                        /* длина пакета 1…32 при постоянной длине */
    uint8_t dyn;                        /* 1 — переменная длина (FEATURE, DYNPD) */
    uint8_t addr[5];                    /* адрес, младший байт первым — так он пишется в регистр */
} nrf_cfg_t;

typedef struct {
    uint8_t id;                         /* номер модуля для порта: в прошивке один, в стенде несколько */
    uint8_t status;                     /* STATUS — первый байт ответа последнего обмена */
    uint8_t config;                     /* что драйвер записал в CONFIG — для nrf_alive */
    uint8_t len, dyn;
    uint32_t sent, failed, timeouts, retries;
} nrf_t;

/* порт: делает прошивка или стенд */
void nrf_port_xfer(uint8_t id, const uint8_t *tx, uint8_t *rx, uint32_t n);  /* CSN = 0, n байт, CSN = 1 */
void nrf_port_ce(uint8_t id, int level);
void nrf_port_delay_us(uint8_t id, uint32_t us);
int nrf_port_wait_irq(uint8_t id, uint32_t timeout_us);                      /* 1 — IRQ = 0, 0 — время вышло */

uint8_t nrf_cmd(nrf_t *d, uint8_t cmd);                       /* однобайтная команда, вернуть STATUS */
uint8_t nrf_read(nrf_t *d, uint8_t reg);
void nrf_write(nrf_t *d, uint8_t reg, uint8_t v);
void nrf_write_addr(nrf_t *d, uint8_t reg, const uint8_t a[5]);

int nrf_init(nrf_t *d, uint8_t id, const nrf_cfg_t *c, int rx);
void nrf_open_rx(nrf_t *d, uint8_t pipe, const uint8_t a[5]);  /* трубы 2…5: только младший байт a[0] */
void nrf_listen(nrf_t *d);                                     /* CE = 1: приёмник слушает */
int nrf_send(nrf_t *d, const uint8_t *p, uint8_t n);           /* NRF_OK, NRF_E_MAXRT, NRF_E_TIMEOUT */
int nrf_recv(nrf_t *d, uint8_t *p, uint8_t *pipe);             /* длина пакета; 0 — FIFO приёма пуст */
int nrf_alive(nrf_t *d);                                       /* CONFIG тот, что записан: модуль не сбрасывался */

/* ── пакет Enhanced ShockBurst, как его собирает модуль: для проверки на стенде ── */
#define ESB_MAX_BYTES 42u                                      /* 1 + 5 + (9 + 256 + 16 бит) / 8, с округлением */
uint16_t esb_crc(const uint8_t *bits, uint32_t from, uint32_t nbits, int crc2);
uint32_t esb_pack(const uint8_t a[5], uint8_t aw, const uint8_t *p, uint8_t n, uint8_t pid, uint8_t no_ack,
                  int crc2, uint8_t out[ESB_MAX_BYTES]);       /* вернуть число бит */

#endif

nrf93b.c — драйвер и сборка пакета Enhanced ShockBurst без регистров МК: общая часть прошивки и стенда.

/* nrf93b.c — раздел 93а: драйвер nRF24L01+ и сборка пакета Enhanced ShockBurst (без регистров МК). */
#include "nrf93b.h"

static uint8_t xfer(nrf_t *d, const uint8_t *tx, uint8_t *rx, uint32_t n)
{
    nrf_port_xfer(d->id, tx, rx, n);
    d->status = rx[0];                                  /* в ответ на первый байт — всегда STATUS */
    return rx[0];
}

uint8_t nrf_cmd(nrf_t *d, uint8_t cmd)
{
    uint8_t rx[1];
    return xfer(d, &cmd, rx, 1);
}

uint8_t nrf_read(nrf_t *d, uint8_t reg)
{
    uint8_t tx[2] = {(uint8_t)(NRF_R_REGISTER | reg), NRF_NOP}, rx[2];
    xfer(d, tx, rx, 2);
    return rx[1];
}

void nrf_write(nrf_t *d, uint8_t reg, uint8_t v)
{
    uint8_t tx[2] = {(uint8_t)(NRF_W_REGISTER | reg), v}, rx[2];
    xfer(d, tx, rx, 2);
}

void nrf_write_addr(nrf_t *d, uint8_t reg, const uint8_t a[5])
{
    uint8_t tx[6] = {(uint8_t)(NRF_W_REGISTER | reg), a[0], a[1], a[2], a[3], a[4]}, rx[6];
    xfer(d, tx, rx, 6);                                 /* младший байт адреса — первым */
}

int nrf_init(nrf_t *d, uint8_t id, const nrf_cfg_t *c, int rx)
{
    static const uint8_t rate[] = {0, NRF_RF_DR_HIGH, NRF_RF_DR_LOW};
    *d = (nrf_t){0};
    d->id = id;
    d->len = c->len;
    d->dyn = c->dyn;
    nrf_port_ce(id, 0);
    nrf_write(d, NRF_SETUP_AW, 3u);                     /* адрес 5 байт */
    if (nrf_read(d, NRF_SETUP_AW) != 3u)
        return NRF_E_NOCHIP;                            /* нет питания или провода: читаются нули или единицы */
    nrf_write(d, NRF_RF_CH, c->ch & 0x7Fu);
    nrf_write(d, NRF_RF_SETUP, (uint8_t)(rate[c->rate] | NRF_RF_PWR_0DBM));
    nrf_write(d, NRF_SETUP_RETR, (uint8_t)(c->ard << 4 | (c->arc & 0x0Fu)));
    nrf_write(d, NRF_FEATURE, c->dyn ? NRF_EN_DPL : 0u);
    nrf_write(d, NRF_DYNPD, 0);
    nrf_write(d, NRF_EN_AA, 0);
    nrf_write(d, NRF_EN_RXADDR, 0);
    nrf_cmd(d, NRF_FLUSH_TX);
    nrf_cmd(d, NRF_FLUSH_RX);
    nrf_write(d, NRF_STATUS, NRF_RX_DR | NRF_TX_DS | NRF_MAX_RT);
    if (!rx) {
        nrf_write_addr(d, NRF_TX_ADDR, c->addr);
        nrf_open_rx(d, 0, c->addr);                     /* подтверждение приходит на адрес трубы 0 */
    }
    d->config = (uint8_t)(NRF_EN_CRC | (c->crc2 ? NRF_CRCO : 0u) | NRF_PWR_UP | (rx ? NRF_PRIM_RX : 0u));
    nrf_write(d, NRF_CONFIG, d->config);
    nrf_port_delay_us(id, NRF_T_PWR_US);
    return NRF_OK;
}

void nrf_open_rx(nrf_t *d, uint8_t pipe, const uint8_t a[5])
{
    if (pipe < 2u)
        nrf_write_addr(d, (uint8_t)(NRF_RX_ADDR_P0 + pipe), a);
    else
        nrf_write(d, (uint8_t)(NRF_RX_ADDR_P0 + pipe), a[0]);  /* старшие 4 байта — общие с трубой 1 */
    nrf_write(d, NRF_EN_RXADDR, (uint8_t)(nrf_read(d, NRF_EN_RXADDR) | 1u << pipe));
    nrf_write(d, NRF_EN_AA, (uint8_t)(nrf_read(d, NRF_EN_AA) | 1u << pipe));
    nrf_write(d, (uint8_t)(NRF_RX_PW_P0 + pipe), d->len);
    if (d->dyn)
        nrf_write(d, NRF_DYNPD, (uint8_t)(nrf_read(d, NRF_DYNPD) | 1u << pipe));
}

void nrf_listen(nrf_t *d)
{
    nrf_port_ce(d->id, 1);
    nrf_port_delay_us(d->id, NRF_T_RX_US);
}

int nrf_send(nrf_t *d, const uint8_t *p, uint8_t n)
{
    uint8_t tx[33] = {NRF_W_TX_PAYLOAD}, rx[33];
    uint8_t len = d->dyn ? n : d->len;                  /* постоянная длина: хвост — нули */
    for (uint8_t i = 0; i < len; i++)
        tx[1 + i] = i < n ? p[i] : 0u;
    xfer(d, tx, rx, 1u + len);
    nrf_port_ce(d->id, 1);                              /* импульс CE — один пакет со всеми повторами */
    nrf_port_delay_us(d->id, NRF_T_CE_US);
    nrf_port_ce(d->id, 0);
    if (!nrf_port_wait_irq(d->id, NRF_T_SEND_US)) {
        nrf_cmd(d, NRF_FLUSH_TX);
        d->timeouts++;
        return NRF_E_TIMEOUT;                           /* IRQ не пришёл: модуль сброшен или не включён */
    }
    uint8_t st = nrf_cmd(d, NRF_NOP);
    d->retries += nrf_read(d, NRF_OBSERVE_TX) & 0x0Fu;  /* ARC_CNT: сколько повторов понадобилось */
    nrf_write(d, NRF_STATUS, st & (NRF_TX_DS | NRF_MAX_RT));
    if (st & NRF_TX_DS) {
        d->sent++;
        return NRF_OK;
    }
    nrf_cmd(d, NRF_FLUSH_TX);                           /* после MAX_RT пакет остаётся в FIFO */
    d->failed++;
    return NRF_E_MAXRT;
}

int nrf_recv(nrf_t *d, uint8_t *p, uint8_t *pipe)
{
    uint8_t tx[33] = {NRF_R_RX_PAYLOAD}, rx[33];
    uint8_t st = nrf_cmd(d, NRF_NOP);
    if (NRF_RX_P_NO(st) > 5u) {
        nrf_write(d, NRF_STATUS, NRF_RX_DR);
        return 0;                                       /* FIFO приёма пуст */
    }
    uint8_t n = d->len;
    if (d->dyn) {
        uint8_t w[2] = {NRF_R_RX_PL_WID, NRF_NOP};
        xfer(d, w, rx, 2);
        n = rx[1];
        if (n > 32u) {
            nrf_cmd(d, NRF_FLUSH_RX);
            return 0;
        }
    }
    for (uint8_t i = 1; i <= n; i++)
        tx[i] = NRF_NOP;
    xfer(d, tx, rx, 1u + n);
    for (uint8_t i = 0; i < n; i++)
        p[i] = rx[1 + i];
    *pipe = (uint8_t)NRF_RX_P_NO(st);
    nrf_write(d, NRF_STATUS, NRF_RX_DR);                /* снять флаг; в FIFO может быть ещё пакет */
    return n;
}

int nrf_alive(nrf_t *d)
{
    return nrf_read(d, NRF_CONFIG) == d->config;        /* после сброса CONFIG = 0x08: PWR_UP = 0 */
}

/* ── пакет Enhanced ShockBurst ────────────────────────────────────────── */
static int bit(const uint8_t *b, uint32_t i)
{
    return b[i >> 3] >> (7u - (i & 7u)) & 1u;
}

static void put(uint8_t *b, uint32_t *at, uint32_t v, uint32_t nbits)
{
    while (nbits--) {
        if (v >> nbits & 1u)
            b[*at >> 3] |= (uint8_t)(0x80u >> (*at & 7u));
        (*at)++;
    }
}

/* CRC по битам, старший первым: 1 байт — x^8 + x^2 + x + 1, начальное 0xFF;
 * 2 байта — x^16 + x^12 + x^5 + 1, начальное 0xFFFF. Считается по адресу, PCF и данным. */
uint16_t esb_crc(const uint8_t *bits, uint32_t from, uint32_t nbits, int crc2)
{
    uint16_t crc = crc2 ? 0xFFFFu : 0xFFu;
    uint16_t top = crc2 ? 0x8000u : 0x80u, poly = crc2 ? 0x1021u : 0x07u, mask = crc2 ? 0xFFFFu : 0xFFu;
    for (uint32_t i = from; i < from + nbits; i++) {
        int fb = ((crc & top) != 0) ^ bit(bits, i);
        crc = (uint16_t)((crc << 1) & mask);
        if (fb)
            crc ^= poly;
    }
    return crc;
}

uint32_t esb_pack(const uint8_t a[5], uint8_t aw, const uint8_t *p, uint8_t n, uint8_t pid, uint8_t no_ack,
                  int crc2, uint8_t out[ESB_MAX_BYTES])
{
    uint32_t at = 0;
    for (uint32_t i = 0; i < ESB_MAX_BYTES; i++)
        out[i] = 0;
    put(out, &at, (a[aw - 1u] & 0x80u) ? 0xAAu : 0x55u, 8);   /* преамбула: первый бит — как у адреса */
    for (uint32_t i = aw; i-- > 0;)
        put(out, &at, a[i], 8);                         /* в эфир адрес идёт старшим байтом вперёд */
    put(out, &at, (uint32_t)n << 3 | (uint32_t)(pid & 3u) << 1 | (no_ack & 1u), 9);  /* PCF: длина, PID, NO_ACK */
    for (uint32_t i = 0; i < n; i++)
        put(out, &at, p[i], 8);
    uint16_t crc = esb_crc(out, 8, at - 8u, crc2);
    put(out, &at, crc, crc2 ? 16u : 8u);
    return at;
}

fw93b.c — прошивка STM32F103: порт на SPI1, CE и IRQ, передатчик или приёмник по перемычке.

/* fw93b.c — раздел 93а: nRF24L01+ на STM32F103 (BluePill), 72 МГц.
 * SPI1: PA5 — SCK, PA6 — MISO, PA7 — MOSI, PA4 — CSN (обычный выход); PB0 — CE; PB1 — IRQ (EXTI1, спад).
 * Перемычка PB10 на землю — приёмник, без неё — передатчик. Передатчик раз в 100 мс шлёт номер пакета;
 * приёмник в прерывании EXTI1 забирает из FIFO все пакеты. Светодиод PC13 мигает на каждом успехе.
 * Тактирование до 72 МГц — как в разделе 6 (здесь не повторяется): APB2 = 72 МГц, SPI1 / 8 = 9 МГц. */
#include "stm32f10x.h"
#include "nrf93b.h"

#define CPU_HZ 72000000u

static const nrf_cfg_t cfg = {
    .ch = 76, .rate = NRF_1MBPS, .ard = 1, .arc = 5, .crc2 = 1, .len = 8, .dyn = 0,
    .addr = {0x01, 0x93, 0xA0, 0x5A, 0xC3},
};
static nrf_t radio;
static volatile uint32_t got, lost;

/* ── порт драйвера ────────────────────────────────────────────────────── */
void nrf_port_xfer(uint8_t id, const uint8_t *tx, uint8_t *rx, uint32_t n)
{
    (void)id;
    GPIOA->BSRR = 1u << (4 + 16);                       /* CSN = 0: начало команды */
    for (uint32_t i = 0; i < n; i++) {
        while (!(SPI1->SR & SPI_SR_TXE)) { }
        SPI1->DR = tx[i];
        while (!(SPI1->SR & SPI_SR_RXNE)) { }
        rx[i] = (uint8_t)SPI1->DR;                      /* первый байт ответа — STATUS */
    }
    while (SPI1->SR & SPI_SR_BSY) { }
    GPIOA->BSRR = 1u << 4;                              /* CSN = 1: команда закончена */
}

void nrf_port_ce(uint8_t id, int level)
{
    (void)id;
    GPIOB->BSRR = level ? 1u : 1u << 16;                /* PB0 */
}

void nrf_port_delay_us(uint8_t id, uint32_t us)
{
    (void)id;
    uint32_t t0 = DWT->CYCCNT;
    while (DWT->CYCCNT - t0 < us * (CPU_HZ / 1000000u)) { }
}

int nrf_port_wait_irq(uint8_t id, uint32_t timeout_us)
{
    (void)id;
    uint32_t t0 = DWT->CYCCNT;
    while (GPIOB->IDR & (1u << 1)) {                    /* IRQ активен нулём */
        if (DWT->CYCCNT - t0 >= timeout_us * (CPU_HZ / 1000000u))
            return 0;
    }
    return 1;
}

/* ── приём: прерывание по спаду IRQ ───────────────────────────────────── */
void EXTI1_IRQHandler(void)
{
    uint8_t p[32], pipe;
    EXTI->PR = 1u << 1;
    while (nrf_recv(&radio, p, &pipe) > 0) {            /* забрать всё: в FIFO до трёх пакетов */
        got++;
        GPIOC->ODR ^= 1u << 13;
    }
}

static void spi1_init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN
                  | RCC_APB2ENR_SPI1EN;
    /* PA7, PA5 — альтернативная функция 50 МГц (1011), PA6 — вход (0100), PA4 — выход 50 МГц (0011) */
    GPIOA->CRL = (GPIOA->CRL & 0x0000FFFFu) | 0xB4B30000u;
    GPIOA->BSRR = 1u << 4;                              /* CSN = 1 до первой команды */
    /* PB0 — выход (0011), PB1 — вход с подтяжкой (1000) */
    GPIOB->CRL = (GPIOB->CRL & 0xFFFFFF00u) | 0x00000083u;
    GPIOB->BSRR = 1u << 1;                              /* подтяжка PB1 вверх; CE = 0 */
    GPIOB->BSRR = 1u << 16;
    /* PB10 — вход с подтяжкой: перемычка на землю — приёмник */
    GPIOB->CRH = (GPIOB->CRH & ~(0xFu << 8)) | 0x8u << 8;
    GPIOB->BSRR = 1u << 10;
    GPIOC->CRH = (GPIOC->CRH & ~(0xFu << 20)) | 0x2u << 20;    /* PC13 — светодиод */
    /* APB2 72 МГц / 8 = 9 МГц, режим 0, старший бит первым */
    SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_1 | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_SPE;
}

static void irq_init(void)
{
    AFIO->EXTICR[0] = (AFIO->EXTICR[0] & ~0xF0u) | 0x10u;      /* EXTI1 ← PB1 */
    EXTI->FTSR |= 1u << 1;                              /* спад: модуль поднял флаг */
    EXTI->IMR |= 1u << 1;
    NVIC_EnableIRQ(EXTI1_IRQn);
}

int main(void)
{
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    DWT->CYCCNT = 0;
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
    spi1_init();
    int rx = !(GPIOB->IDR & (1u << 10));
    while (nrf_init(&radio, 0, &cfg, rx) != NRF_OK)
        nrf_port_delay_us(0, 100000u);                  /* модуля нет: нули или единицы по MISO */
    if (rx) {
        nrf_open_rx(&radio, 1, cfg.addr);
        irq_init();
        nrf_listen(&radio);
        for (;;)
            __WFI();
    }
    for (uint32_t n = 0;; n++) {
        uint8_t p[8] = {(uint8_t)n, (uint8_t)(n >> 8), (uint8_t)(n >> 16), (uint8_t)(n >> 24)};
        int rc = nrf_send(&radio, p, sizeof p);
        if (rc == NRF_OK)
            GPIOC->ODR ^= 1u << 13;
        else
            lost++;
        if (rc == NRF_E_TIMEOUT && !nrf_alive(&radio))
            nrf_init(&radio, 0, &cfg, 0);               /* модуль сбросился от просадки питания */
        nrf_port_delay_us(0, 100000u);
    }
}

Дальше: раздел 94 «LoRaWAN: сеть, ключи, классы».

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