понедельник, 24 июня 2013 г.

Работа с беспроводным трансивером nRF24L01

Когда то много недель назад получил я заказанные много месяцев назад по сходной цене из китая беспроводные трансиверы (приемопередатчики) на 2.4ГГц nRF24L01. Только спустя много времени наконец дошли руки их поковырять, а заодно задокументировать процесс в статье а-ля "пример работы с nRF24L01".

Начали всплывать проблемы:
 - на этот раз в Proteus не оказалось такой микрухи, так что лафа кончилась, паять и отлаживать придется в железе, но благо чип уже сам на плате и со всеми нужными выведенными контактами
 - отлаживать придется 2 шт одновременно, одна передает, другая принимает, вероятность неработоспособности связки равна произведению вероятностей неработоспособности каждой из двух микросхем
 - на 2 передатчика понадобится и 2 контроллера
 - питается передатчик напряжением до 3.3В, придется мутить стабилизатор и конфигурировать фьюзы МК, чтоб работали на пониженном напряжении, впрочем не такая уж это большая проблема



Распиновка nRF24L01

Для начала познакомимся с выводами nRF:
1-2. Питание, от 1.9 до 3.6В, не более (рекомендуется 3В)! однако на другие пины можно подавать до 5.25В (in datasheet we trust).
3. CE: Chip Enable. Зависит от режима работы. Если чип сконфигурен как приемник, то высокий (HIGH) уровень на CE позволяет чипу мониторить среду и получать пакеты. Низкий (LOW) уровень переводит чип в Standby-I и такая возможность становится уже недоступна. Если чип настроен на передачу, CE всегда держится на низком уровне. В этом случае для передачи данных нужно положить их в очередь FIFO и дернуть CE минимум на 10мкс (LOW->HIGH, 10мкс, HIGH->LOW).
4. CSN. Chip Select Not. Not, потому что активный уровень - низкий. Пин всегда держится на высоком уровне, переводим на низкий уровень для начала общения между чипом и МК по SPI. Когда пообщались - снова возвращаем на высокий уровень.
5. SCK. Стробирующий сигнал SPI. Дежурный уровень - LOW. Переход L->H говорит чипу что можно читать бит с MOSI и писать на MISO. nRF24 - SLAVE устройство. Оно никогда не инициирует связь с МК само, строб-сигнал SCK генерирует именно МК.
6-7. MOSI+MISO. Собственно линии дуплексной передачи данных от чипа к МК и обратно. Когда МК вывел новый бит на линию MOSI и/или считал бит с MISO, он может дергать SCK, чтобы дать понять чипу, что он может читать/писать следующий бит.
8. IRQ. Interrupt Pin. Дежурный уровень - высокий. Пин полезно мониторить со стороны МК, дабы понять, не случилось ли чего интересного, например новый пакет пришел (прерывания настраиваются в чипе по SPI). Активный уровень - низкий. Когда случается прерывание - читаем статусный регистр и смотрим, что случилось. Всего прерываний три.

Как общаться с nRF24L01

Для начала можно пообщаться с самим чипом по SPI и например почитать его внутренние регистры. Для этого достаточно при инициализации МК не забыть задрать линию CSN к питанию, это дежурный режим; чтобы начать сеанс - прижимаем CSN к земле, чип понимает это как начало передачи данных по SPI и готовится выдать на MISO свой статус-регистр. Чип делает это всегда при начале общения по SPI. Если мы хотим что то писать в регистры чипа или читать из них, для начала мы должны дать соответствующую инструкцию (1 байт), и пока мы ее пишем на линию MOSI, чип выдает на MISO свой статус-регистр, тоже 1 байт.
Пожалуй отладку можно начать с чтения и записи регистров nRF и выдачи этих данных например на терминал.
Байт инструкции передается начиная со старшего бита. 
Далее, не отпуская линии CSN, дергаем SCK и читаем ответ чипа. Сколько байт читать зависит от того, какой регистр читаем, это нужно задавать в коде программы. Никакого признака "окончания передачи" тут нет. Регистры в основном имеют длину 1 байт, исключение составляют регистры TX_ADDR и RX_ADDR_P*. Как видно из иллюстрации, байты ответа также приходят "вперед ногами", т.е. сначала старший бит (7).
К примеру, чтение регистра EN_RXADDR (адрес 02h) будет проходить так:
1. CSN - к земле, начинаем передавать байт 02h (0b 0000 0010) с наложенной маской инструкции R_REGISTER (00h)
2. вывели бит 7, дернули SCK вверх-вниз, сдвинули байт логически влево, и так 8 раз для передачи всего байта инструкции
3. далее еще 8 раз дергаем SCK, читая уровни на линии MISO в байт ответа
4. поднимаем линию CSN, обмен завершен

Для записи байта в регистр EN_RXADDR последовательность такая же, только в п.1 маска будет W_REGISTER (20h) и байт инструкции получится уже 02h OR 20h = 22h. а в п.3 вместо чтения в байт из MISO напротив, пишем из байта на линию MOSI, опять же сначала бит 7, потом 6 и т.д.

Ближе к делу

Q. как настроить в рабочий режим и подготовить чип к передаче?
A. перед началом работы в обязательном порядке нужно выставить бит PWR_UP в регистре CONFIG. При подаче питания на чип этот бит по-умолчанию равен 0. Впрочем это не означает, что чип выключен и не подает признаков жизни. По SPI он общается всегда. Также выставляем бит PRIM_RX  того же регистра в 1 для режима приема и в 0 для режима передачи. По-умолчанию бит равен нулю, т.е. чип настроен на передачу. Также стоит отметить, что при старте включены все прерывания. Для приема данных обязательно надо настроить размер пакета, т.е. записать число больше нуля в RX_PW_P0.  На принимающей стороне настройка длины пакета должна быть такой, чтобы умещались передаваемые данные! 

Q. Как работать с прерываниями?
A. При генерации прерывания (низкий уровень на пин IRQ), читаем STATUS регистр, определяем событие. Например установлен бит RX_DR (Data Ready): пришел новый пакет. Читаем пакет, обязательно пишем 1 (не 0, а 1, ага) на место этого бита, чтобы сбросить флаг прерывания. Не забываем записать получившийся байт обратно в STATUS регистр.

Q. Ну и как уже что то передать в эфир?
A. Если дерево падает в глухом лесу, производит ли оно грохот? А если пакет передать в никуда? Попробуем.
На рисунке обозначено:
 - Antenna - интервалы активности в эфире, т.е. собственно отправка и прием пакетов
 - PTX и PRX Mode - режимы, в которых находятся передатчик (PTX) и приемник (PRX)

Из диаграммы становится ясно (и это описано в ДШ), что передача начинается нифига не по заднему фронту на линии CE, а через 130мкс (128+2 на рисунке) после переднего фронта. А значит с отпусканием CE можно не спешить. Также показано, что CE поднимается аккурат перед генерацией прерывания TX_DS, по какому событию - неизвестно, как неизвестно и зачем вообще ее поднимать, если мы в режиме передатчика.
Итак, перед процедурой передачи необходимо убедиться, что CE прижато к земле. Отправляем инструкцию W_TX_PAYLOAD (A0h) (это передача по SPI, соответственно про CSN тоже не забываем), а следом 3 байта данных, которые хотим передать пришельцам. Это будут 3 буквы. Я еще не решил какие. После того, как передали наши 3 буквы по SPI и подняли CSN, нужно дать понять чипу, что пора передавать данные в эфир. Это делается дерганием линии CE: ее нужно поднять как минимум на 10мкс, как показано на рисунке, затем снова придавить к земле на все время передачи. По окончании передачи будет сгенерировано прерывание: либо TX_DS (если данные переданы), либо MAX_RT (если не удалось передать в пределах заданного числа попыток). В любом случае линию CE пора поднимать (зачем), а бит прерывания очистить в регистре записью в него единицы. А вот зачем поднимать линию CE:
nRF24 goes into standby-I mode if CE is low. Otherwise next payload in TX FIFO is transmitted. If TX FIFO is empty and CE still high, nRF24 enters standby-II mode
Отсюда следует, что имеет смысл поднимать линию, если в очереди на передачу еще что-то есть, иначе можно оставлять линию у земли и девайс перейдет в режим энергосбережения.


Q. как переходить из режима RX в TX и обратно?
A. записью бита PRIM_RX = 1 в регистр CONFIG мы переводим чип в режим приемника. по-умолчанию при подаче питания чип в режиме передачи (PRIM_RX = 0). Разобраться как при этом управлять линией CE... А вот как: переводя бит PRIM_RX и 1 или 0 меняем режим чипа, но чтобы его собственно активировать - надо поднять линию CE, иначе чип будет как бы в режиме готовности, но неактивен. Для PRX это означает, что слушать эфир начнем, как только поднимем линию CE. Для PTX это значит что передавать данные из TX FIFO начнем как только поднимем линию CE. Опускание линии CE означает переход в режим энергосбережения.

Q. где настраивается сколько байт можно пихать в TX FIFO для передачи?
A. "The width of TX-payload is counted from number of bytes written to the TX FIFO from the MCU". По-русски говоря, чип имеет счетчик байт, и к моменту начала SPI обмена с целью положить в TX FIFO данные, этот счетчик сбрасывается и начинает считать, сколько байт мы ложим в TX FIFO. Положить можно вроде как от 1 до 32 байт. Сколько положили - все передаст. R_RX_PAYLOAD тоже выдает от 1 до 32 байт. 

Q. как происходит адресация и распределение пакетов по пайпам?
A. адреса пайпов задаются в регистрах RX_ADDR_P*. Нужно внимательно следить, чтобы не оказалось 2 одинаковых адресов в регистрах RX_* одного чипа. НО адреса отправителя и получателя должны совпадать! Т.е. регистры TX_ADDR и RX_ADDR_P0 обоих чипов должны быть равны между собой (итого 4 одинаковых значения). Чтобы получить пакет на одном nRF, нужно сначала его RX_ADDR_P0 сконфигурировать известным адресом, затем с другого nRF посылать по этому адресу пакеты (адрес вводится на передатчике в регистр TX_ADDR).
On the PTX the TX_ADDR must be the same as the RX_ADDR_P0 and as the pipe address for the designated pipe.
эта фигня гласит, что на передатчике (PTX) адрес для отправки должен совпадать с собственным RX_ADDR_P0 адресом (чтобы автоматом разруливать ACK), а также с RX_ADDR_P* пайпа приемника (PRX) (чтобы вообще пакет дошел до назначения).
ACK разруливаются сами только при включенном Enhanced ShockBurst mode.
Однако, по-умолчанию все чипы имеют один и тот же TX_ADDR, который к тому же равен RX_ADDR_P0 (чтобы работал авто-ACK), т.е. для quick-start конфигурировать адреса чипов необязательно! Все чипы будут рассылать на всех и принимать данные ото всех.

Q. Надо ли задавать RX_PW_P0 для работы авто-ACK.
A. На передатчике RX_PW_P0 задавать необязательно

Q. как просканировать передатчики в округе?
A. Регистр CD содержит 1, если мы в RX режиме видим несущую (кто-то передает). вот походу и все, promisc-mode? кажется нету.

Q. что будет если передать 10 байт, а приемник настроен принимать только 3?
A. не пробовал, ДШ читать уже лень, вероятно приемник нифига не получит, потому что в передаваемом пакете CRC будет далеко за пределами 3 байт. Вопрос пока открытый.
UPD: Если размер PAYLOAD не совпадает - передачи не будет. В принципе для передатчика нет такого понятия как размер пакета. Он передает столько байт, сколько по SPI получит. Но если это число байт отличается (хоть на 1 больше или меньше) от установленного на приемнике RX_PW_P0 - приемник не получит пакета.

А также:
- если скорости устройств (RF_SETUP, 0.25..2Mbps) не совпадают  или если каналы устройств (RF_CH) не совпадают - передачи не будет.
- если не хватает отведенных чипом 15-ти попыток передачи пакета, можно использовать команду REUSE_TX_PL и пытаться отсылать хоть до бесконечности. Суть в том, что в каждом пакете есть PID и приемник точно отличит повторный пакет среди потока. Может быть ситуация, что ACK не дошел до передатчика и тот отправил пакет снова, приемник поймет, что пакет повторный, и отправит ACK снова.
Не стоит мутить повторную отправку руками, через W_TX_PAYLOAD, это неверный путь, да и ненужный труд, все уже реализовано в железе.

Troubleshooting

1. не хочет отвечать по SPI
как оказалось перепутал линии IRQ и MISO, а еще думаю, чего оно мне на IRQ какие то 6 импульсов выдает, так то были 6 байт ответа при обмене по SPI. Осциллограф, великая вещь.
2. потери пакетов на PTX. Переключаемся в режим PRX, детектим несущую (CD), если есть - значит коллизия на канале (более одного передатчика одновременно), меняем канал, пробуем передавать снова.
3. не срабатывает ни одно из прерываний при отправке тестового пакета в пустоту (ожидалось MAX_RT). перепутал W_TX_PAYLOAD с R_RX_PAYLOAD. затем оказалось, что RX_PW_P0 сбрасывается после вызова процедуры отправки в эфир (W_TX_PAYLOAD). цикличный мониторинг регистра STATUS не давал результатов, биты прерываний всегда сброшены. далее стало ясно, что сбрасывается вообще все, и конфиг и RX_PW*. происходит это после 10us-строба на линии CE. Не хватало питания. Сброс чипа.
4. Подключил второй чип, настроил как приемник. Наблюдается необъяснимая мистика, PTX передает 11 пакетов без каких-либо прерываний, молча, затем 3 пакета с прерыванием TX_DS, остальные - с MAX_RT. На принимающей стороне такой же трансивер в режиме приемника, настроенным RX_PW_P0=3 (как и на передатчике). Мониторинг разнообразных регистров PTX и PRX выявил опять же сброс их значений вследствие нехватки питания (уже 2 трансивера на одном TL431), поставил электролит - все заработало как часы!

Итоги

Таки добился стабильной передачи. 
Настройка передатчика сводится к установке PWR_UP в регистре CONFIG и прижатию линии CE к земле.
Настройка приемника - установка PWR_UP и PRIM_RX в CONFIG, RX_PW_P0 > 0 и подтягивании CE к питанию.
Далее мониторим линии IRQ, читаем биты прерываний из регистра STATUS и не забываем их сбрасывать, читаем полученные данные из pipe 0.
Выкладываю архив сильно чернового проекта для ATMega8 и двух подключенных к ней nRF24L01 на ассемблере.
А вот видео с примером реального полезного устройства на nRF24 - http://www.youtube.com/watch?v=U58U8tzE4c8

UPD: у статьи появилось продолжение
UPD2: еще пример: простой ассемблерный код приемника и передатчика (включение/выключение светодиода), поделился Александр

Материалы



17 комментариев:

  1. Спасибо за статью - помогла систематизировать кашу в голове после прочтения даташита. Надеюсь на дальнейшее её развитие :)

    ОтветитьУдалить
  2. Спасибо за статью! Пытаюсь написать код для общения 2 железок. Делал, как Вы говорили, сначало пообщался по SPI, все заработало и стало ясно, потом решил толкнуть в эфир. Но тут беда. Уже дня 4 тра... У передатчика срабатывает прерывание IRQ = 0? проверяю статус, Сначало 1Е, потом по мере заполнения буфера 1F. Приемник сидит в ожидании IRQ. Что может быть?

    В асме не рулю, точнее очень слаб. Не могли бы вы на пальцах рассказать последовательность действий.
    Спасибо!

    ОтветитьУдалить
  3. Ну даташит да, а второй пока изучаю. Начал минут 15 назад:)

    ОтветитьУдалить
  4. Архив проекта не могу читать. Ссылка не работает

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

      Удалить
  5. Да ссылка заработала. Пишу на Proton IDE. Basic под PIC. Вот ссылка http://yadi.sk/d/Q82XMJ8Z8Q2ID

    ОтветитьУдалить
    Ответы
    1. ну так у меня проект под AVR. я по PIC никак не помогу

      Удалить
  6. Так я же код не прошу. Просто на человеческом языке можете объяснить что за чем следует.
    Я делаю так: Сначало поднимаю CSN и опускаю CE, потом опускаю CSN и закидываю адрес, потом регистр, поднимаю CSN, и так пока все адреса не пройду.

    ОтветитьУдалить
    Ответы
    1. я адреса вообще не трогал. по дефолтным все отсылалось.
      на передатчике сначала записывается регистр W_TX_PAYLOAD, с общением по SPI вопросов не возникает, как я понял.
      во время записи регистра CSN опущено, CE тоже.
      по окончании записи регистра CSN поднимается.
      далее, чтобы пошла отправка дергаем CE на 10мкс в состояние единицы.

      все, на приемнике должно быть прерывание

      Удалить
  7. Ага должно быть! Но нет прерывания. Уже пробовал менять местами модули.

    ОтветитьУдалить
  8. Могу посоветовать читать ещё состаяние FLUSH и выводить на экран (я на период отладки выводин на терминал через USART (атмега правда)) каждый ответ модуля (регистр STATUS) и глядя на то как меняются биты этого регистра добивался одекватной работы (10 часов заняло у меня это удовольствие)
    P.S. долго не мог понять почему после наладки передатчика до приёмника ни чего не доходтило. Причина была в том что забыл линию CE задрать на верх. Теперь она задрана постоянно на приёмнике.

    ОтветитьУдалить
  9. Да, и не забывайте после каждого рестарта МК перезагружать и радиомодуль.

    ОтветитьУдалить
  10. Всем привет. Сделал машинный перевод описания всех регистров. Могу скинуть,что бы кто нить перевел до понятного русского.

    ОтветитьУдалить
  11. Анонимный16 мая 2014 г., 21:51

    Еще замечания:
    - обязательно нужно поставить конденсатор на питание модуля, я поставил 4,7 мкФ (достаточно, наверное, и 1 мкФ, не электролит)
    - выход IRQ не должен висеть в воздухе, если не нужен, нужно подключить через резистор в 150-200 кОм к плюсу или минусу питания.

    ОтветитьУдалить
  12. Плохо, что нет наглядной демонстрации на C++

    ОтветитьУдалить