Модули E73-2G4M04S на базе nRF52832

a5021
Offline
Зарегистрирован: 07.07.2013

Вот здесь было небольшое обсуждение беспроводных модулей  E73-2G4M04S на базе чипа Nordic Semi nRF52832, к которому я бы хотел вернуться, только уже здесь. Получил я один такой модуль  с али и идея подпайки проводков отпала сразу же -- шаг контактных площадок мелковат. Проще показалось заЛУТить платку-адаптер, что и было сделано. Получилось в форме гаечного ключа, т.к. захотелось, чтобы штырьевые контакты по ширине помещались на беспаечную макетку и оставалось по одному ряду с каждой стороны для втыкания проводков.

HASL по-домашнему:

Запайка самого модуля:

Ну и стенд для первичных испытаний:

Оценка работоспособности производилась при помощи китайского клона ST-LINK v2 и среды разработки Keil uVision V5. Из списка примеров, которые идут с самим Keil-ом, был извлечен  Blink, который сходу, тем не менее, загружаться не захотел. Гугление навело на рецепт поменять  настройки в разделе программатора:

После чего стало возможным тереть, шить и отлаживать программу через программатор ST-LINK V2. Ну и блинк в итоге заработал.

О ходе дальнейших изысканий планирую дописывать тут по мере накопления материала.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Круто! И плата красивая!

-----

Не в тему, но большая просьба, гляньте, пожалуйста.

b707
Offline
Зарегистрирован: 26.05.2017

a5021. приветствую.

По вашей наводке заказал 2 таких модуля и тоже получил в эти выходные. Правда у меня пока ST-Linka нет, чтобы попробовать.

Плата отличная. В чем разводили?

a5021
Offline
Зарегистрирован: 07.07.2013

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

Без ST-Link V2 (или J-Link) к этой плате подступиться будет сложно. Теоретически, Arduino Primo Core тоже состоит главным образом из одного только чипа nRF52832 и для него существует бутлоадер, но простым способом сей бутлоадер можно зашить только с помощью Arduino Primo, который используется, как программатор. Так что даже если удастся подсунуть E73, как Arduino Primo Core, нужен еще и просто Primo, чтобы зашить. Такой путь лично мне не видится совсем простым.

Проще оказалось пойти на https://github.com/sandeepmistry/arduino-nRF5 и по инструкции оттуда добавить в Arduino IDE поддержку "Nordic Semiconductor nRF5 based boards". Этот пакет при установке затянул еще и поддержку программаторов ST-LINK, J-LINK, Black Magic Probe и CMSIS-DAP, т.ч. с вопросом "Чем зашиться?" ситуация упростилась.

Упростилась бы она и в случае отсутствия у меня ST-LINK, т.к. в вышеприведенном списке программаторов присутствует еще и Black Magic Probe, который на коленке легко делается из небезызвестной китайской платки BluePill (на базе STM32F103C8)

по методике изложенной здесь.

Сам я продолжаю пользоваться китайским клоном ST-LINK-а и здесь могу попутно заметить, что идея перешить его в J-LINK тут не прокатывает. Я попробовал, он без проблем перешивается, но из-за политики SEGGER-а, такой новообращенный J-LINK ни с чем, кроме процессоров ST Micro работать не желает. Поверхностное гугление способов преодоления данного затруднения не предлагает.

Не разобрался пока еще почему, но Arduino IDE никак не желает зашивать скетчи, если в качестве целевой платформы выбрать Generic NRF52, хотя это вроде бы самый короткий путь в моем случае. Ладно, фиг сним, выбрал платформу "RED BEAR BLEND 2". Эту зашивает с вот таким журналом сообщений:

Цитата:

Скетч использует 2 604 байт (0%) памяти устройства. Всего доступно 524 288 байт.
C:\Users\0123456\AppData\Local\Arduino15\packages\sandeepmistry\tools\openocd\0.10.0-dev.nrf5/bin/openocd.exe -d2 -f interface/stlink-v2.cfg -c transport select hla_swd; set WORKAREASIZE 0x4000; -f target/nrf52.cfg -c program {{C:\Users\0123456\AppData\Local\Temp\arduino_build_153375/BlinkWithoutDelayZerro.ino.hex}} verify reset; shutdown;
Open On-Chip Debugger 0.10.0-dev-00254-g696fc0a (2016-04-10-10:13)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
debug_level: 2
0x4000
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 10000 kHz
Info : Unable to match requested speed 10000 kHz, using 4000 kHz
Info : Unable to match requested speed 10000 kHz, using 4000 kHz
Info : clock speed 4000 kHz
Info : STLINK v2 JTAG v24 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.520420
Info : nrf52.cpu: hardware has 6 breakpoints, 4 watchpoints
nrf52.cpu: target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x00000534 msp: 0x20010000
** Programming Started **
auto erase enabled
Info : nRF51822-QFN48(build code: B00) 512kB Flash
Warn : using fast async flash loader. This is currently supported
Warn : only with ST-Link and CMSIS-DAP. If you have issues, add
Warn : "set WORKAREASIZE 0" before sourcing nrf51.cfg to disable it
nrf52.cpu: target state: halted
target halted due to breakpoint, current mode: Thread
xPSR: 0x61000000 pc: 0x2000001e msp: 0x20010000
wrote 4096 bytes from file C:\Users\0123456\AppData\Local\Temp\arduino_build_153375/BlinkWithoutDelayZerro.ino.hex in 0.896679s (4.461 KiB/s)
** Programming Finished **
** Verify Started **
nrf52.cpu: target state: halted
target halted due to breakpoint, current mode: Thread
xPSR: 0x61000000 pc: 0x2000002e msp: 0x20010000
verified 2724 bytes in 0.588028s (4.524 KiB/s)
** Verified OK **
** Resetting Target **
shutdown command invoked

Не очень понятно о чем вот эта строка:

Info : nRF51822-QFN48(build code: B00) 512kB Flash

Чип, по идее, должен отображаться, как nRF52832.

Cоответствие ног в скетчах с ногами на E73 надо будет выяснять и составлять таблицу, т.к. попытки подрыгать ногами с последовательными номерами иногда приводят к тому, что дрыгаются ноги, которые  находятся далеко друг от друга.

Пока попробовал компилировать самое простое:

/* Blink without Delay -- музыка Arduino, слова народные  */

#define LED_PIN      13
#define BLINK_SPEED  10

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, (millis() >> BLINK_SPEED) & 1);
}

Пин 13 оказался пином 5 на E73, но блинкает исправно.  Посмотрел, какой код варится для того же millis(). По сравнению с атмеговским выглядит забавно:

uint32_t millis( void )
{
 576:	b530      	push	{r4, r5, lr}
  uint64_t ticks = (uint64_t)((uint64_t)overflows << (uint64_t)24) | (uint64_t)(NRF_RTC1->COUNTER);
 578:	f8d3 0504 	ldr.w	r0, [r3, #1284]	; 0x504

  return (ticks * 1000) / 32768;
 57c:	060a      	lsls	r2, r1, #24
 57e:	0a0b      	lsrs	r3, r1, #8
 580:	4302      	orrs	r2, r0
 582:	f44f 717a 	mov.w	r1, #1000	; 0x3e8
 586:	fba2 4501 	umull	r4, r5, r2, r1
 58a:	fb01 5503 	mla	r5, r1, r3, r5
 58e:	0be0      	lsrs	r0, r4, #15
}
 590:	ea40 4045 	orr.w	r0, r0, r5, lsl #17
 594:	bd30      	pop	{r4, r5, pc}

Глаз цепляется за 64-битную арифметику. Ассемблерная команда umull перемножает два беззнаковых 32-битных числа с получением 64-битного результата. Время выполнения -- один такт. Внушает.

Раз уж тут засветился реалтаймовый счетчик  NRF_RTC1->COUNTER, который увеличивается на 1 со скоростью 32768 раз в секунду, то подумалось, что можно блинк непосредственно по нему вычислять, не вызывая миллис:

void loop() {
  digitalWrite(LED_PIN, (NRF_RTC1->COUNTER / 32768 ) & 1);
}

Так тоже мигает. Опять интересно глянуть, во что компилятор превращает  (NRF_RTC1->COUNTER / 32768 ) & 1:

  digitalWrite(LED_PIN, (NRF_RTC1->COUNTER / 32768 ) & 1);
 520:	4b03      	ldr	r3, [pc, #12]	; (530 <loop+0x10>)
 522:	f8d3 1504 	ldr.w	r1, [r3, #1284]	; 0x504
 526:	200d      	movs	r0, #13
 528:	f3c1 31c0 	ubfx	r1, r1, #15, #1
 52c:	f000 b842 	b.w	5b4 <digitalWrite>

По сути, вся арифметика выполняется единственной командой: ubfx r1, r1, #15, #1 -- взять из регистра R1 один бит начиная с 15-го, дополнить нулями слева до 32-х разрядов и поместить обратно в регистр R1. Красиво.

a5021
Offline
Зарегистрирован: 07.07.2013

Соответствие пинов ардуино-совместимой платы RedBear Blend 2 пинам модуля E73-2G4M04S:

Таким образом, если хочется помигать светодиодом, который у Ардуино УНО висит на 13 пине, к E73-2G4M04S его надо цеплять на 5-ый контакт. В скетч стандартного блинка никаких изменений вносить не надо.

xDriver
xDriver аватар
Offline
Зарегистрирован: 14.08.2015

скажите пожалуйста, а чем Вы переносите тонер на текстолит ? ужбольно платка красивая получилась.

я остановился на глянцевом ломонде, но между мелких дорожек выковыривать его остатки замучался.

Valera19701
Valera19701 аватар
Offline
Зарегистрирован: 18.10.2015

xDriver пишет:

скажите пожалуйста, а чем Вы переносите тонер на текстолит ? ужбольно платка красивая получилась.

я остановился на глянцевом ломонде, но между мелких дорожек выковыривать его остатки замучался.

я такой пользуюсь

https://ru.aliexpress.com/item/10pcs-A4-Heat-Toner-Transfer-Paper-For-DIY-PCB-Electronic-Prototype-Mak/32638704954.html

a5021
Offline
Зарегистрирован: 07.07.2013

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

Чтобы было понятно, как фольга попадает в принтер, вот фотка того, что использовалось для изготовления данной платы.

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

xDriver
xDriver аватар
Offline
Зарегистрирован: 14.08.2015

да, спасибо, встречал это способ на просторах инета, надо будет попробывать.. 

и еще, будьте любезны, а чем травите это дело ?

a5021
Offline
Зарегистрирован: 07.07.2013

 Лучший состав из мной испробованных -- соляная кислота + перекись водорода. В принципе, он же и в промышленности в основном используется. Мне он глянулся тем, что обеспечивает минимальный боковой подтрав. Если бы я взял состав на основе той же лимонной кислоты, скажем, то ровных дорожек, как на фотках, точно бы не получилось. Лимонка грызет края дорог, как рашпиль. Многократно в этом убеждался, когда экспериментировал.

xDriver
xDriver аватар
Offline
Зарегистрирован: 14.08.2015

хех, ну с "лимонкой" понятно, а где брать соляную кислоту и какой состав для травления ?

не гуглится ничего по этому поводу...

a5021
Offline
Зарегистрирован: 07.07.2013

Я добывал соляную кислоту в промышленности и насколько знаю, частным лицам ее не продают.

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

/* NRF52 INFO  */

void setup() {
  Serial.begin(115200);
}

void loop() {
  Serial.println("=======================================");
  Serial.print("Chip\t\t: nRF");
  Serial.print(NRF_FICR->INFO.PART, HEX);
  switch (NRF_FICR->INFO.VARIANT) {
    case 0x41414141: Serial.println(" AAAA"); break;
    case 0x41414142: Serial.println(" AAAB"); break;
    case 0x41414241: Serial.println(" AABA"); break;
    case 0x41414242: Serial.println(" AABB"); break;
    case 0x41414230: Serial.println(" AAB0"); break;
    case 0x41414530: Serial.println(" AAE0"); break;
  }

  Serial.print("Package\t\t: ");
  switch (NRF_FICR->INFO.PACKAGE) {
    case 0x2000: Serial.println("QFxx - 48-pin QFN"); break;
    case 0x2001: Serial.println("CHxx - 7x8 WLCSP 56 balls"); break;
    case 0x2002: Serial.println("CIxx - 7x8 WLCSP 56 balls"); break;
    case 0x2005: Serial.println("CKxx - 7x8 WLCSP 56 balls with backside coating"); break;
  }
  
  Serial.print("RAM size\t: ");
  switch (NRF_FICR->INFO.RAM) {
    case 0x10: Serial.println("16 kByte"); break;
    case 0x20: Serial.println("32 kByte"); break;
    case 0x40: Serial.println("64 kByte"); break;
  }
  Serial.print("Flash size\t: ");
  switch (NRF_FICR->INFO.FLASH) {
    case 0x80:  Serial.println("128 kByte"); break;
    case 0x100: Serial.println("256 kByte"); break;
    case 0x200: Serial.println("512 kByte"); break;
  }
  while(1); // halt
}

У меня он показывает такое:

Не наврали, стало быть, китайцы. Установлена версия с 512к флеша и 64к ОЗУ.

a5021
Offline
Зарегистрирован: 07.07.2013

Где-то хорошо отрекламировали данные модули. У продавца на али 552 заказа на данный момент. С учетом, что девайс весьма специфический, можно лишь предположить, что встроили его куда-то в опен-соурс проект, который довольно популярен. Вот только куда?

b707
Offline
Зарегистрирован: 26.05.2017

a5021 пишет:

можно лишь предположить, что встроили его куда-то в опен-соурс проект, который довольно популярен. Вот только куда?

на MySensors.com огромная ветка про NRF52, в том числе и конкретно про эти модули.

spa-sam
Offline
Зарегистрирован: 14.12.2012

Я сделал иначе. На raspberry pi  установил openOCD (это система отладки для МК). Инструкции как установить и шить МК вот тут:

https://learn.adafruit.com/programming-microcontrollers-using-openocd-on...

https://gitlab.cba.mit.edu/pub/hello-world/nrf52/tree/master

https://github.com/ntfreak/openocd

Единственное, что при установке использовал не git clone git://git.code.sf.net/p/openocd/code openocd

а его зеркало git://repo.or.cz/openocd.git т.к. с вышеуказанного не качалось.

Далее установил настройки платы от adafruit https://github.com/adafruit/Adafruit_nRF52_Arduino для arduino IDE

Залил через raspberry pi загрузчик от adafruit. После этого можно шить наш МК через обычный переходник USB-UART предварительно законектив ножку DFU на GND.

У adafruit прицепом ставится куча примеров на все случаи. Пины соответсвуют.

spa-sam
Offline
Зарегистрирован: 14.12.2012

До этого быстро набросал для пробы макетку sim800c. Подключил её к nrf52... всё работает как часы.

spa-sam
Offline
Зарегистрирован: 14.12.2012

А кто-то пробовал принимать этим модулем с nrf24l01? Стандартными средствами(библиотеками работает), но приходит какая-то белеберда, вместо нужных цифр (

P/S Желтая бумага с али лучше всего переносит тонер, самый идеальный и простой вариант. https://ru.aliexpress.com/item/10PCS-A4-Sheets-Heat-Toner-Transfer-Paper-For-DIY-PCB-Electronic-Prototype-Mak/32571735391.html?spm=a2g0s.13010208.99999999.262.1nTfoS

a5021
Offline
Зарегистрирован: 07.07.2013

spa-sam пишет:

А кто-то пробовал принимать этим модулем с nrf24l01? Стандартными средствами(библиотеками работает), но приходит какая-то белеберда, вместо нужных цифр (

Насчет приема не в курсе, но в обратную сторону пишут, что работает. Сам я не проверял, т.к. в данный момент интереснее покопаться с самим процессором. Радио пока на потом оставил.

b707
Offline
Зарегистрирован: 26.05.2017

мужики, мне на фоне вас стыдно. Я лугить не умею, поэтому подошел к вопросу по "рабоче-крестьянски" :) 

Взял обычную одностороннюю макетку с шагом 2.54мм, напаял в нее два ряда по 7 стоек из проволоки 0.5мм на расстоянии 7 рядов друг от друга. Между этими рядами вставил модуль "брюшком" вверх - так паять удобнее, да и выводы подписаны. Стойки точно вошли в полукруглые выемки контактов. После этого обрезал проволоку по высоте модуля и запаял. Получилось надежно и вполне аккуратно(сорри за качество фото):

По краям поставил обычные гребенки контактов. Из нижнего ряда вывел только P0.06 P0.07 P0.08 для RX TX - три штуки, потому что не знал точно, какие надо - в разных источниках интернета указаны разные пины под Сериал. В итоге оказались 6 и 8 - как на картинке a5021, только при подключении UART на основе BMP почему-то RX TX оказываются наоборот.

Конечно, так я получил вывод только SWDIO, SWCLK. питания и RX|TX. а из GPIO - лишь каждого второго контакта - но для тестов мне пока хватит, а потом, может, и ЛУГ освою :)

Программатор сделал из платки STM32F103CBT6 "blue pill", как описано выше по ссылке, что давал a5021. Тоже не без проблем - позже может распишу - но в итоге получилось. Модуль прошивается, светодиодом мигает на 12 ноге (13-ю я не вывел, как можно видеть на фото).

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

 

a5021
Offline
Зарегистрирован: 07.07.2013

b707 пишет:
мужики, мне на фоне вас стыдно. Я лугить не умею,

А вот это, как раз, в данном случае не имеет никакого значения. Главное получить более-менее удобный доступ к функционалу. Преимущество вашего варината в том, что он простой и доступный. Я считаю, что вышло неплохо.

Цитата:
В итоге оказались 6 и 8 - как на картинке a5021, только при подключении UART на основе BMP почему-то RX TX оказываются наоборот.

Мож это на BMP наоборот? Распиновка ардуиновских плат зависит от содержимого массива g_ADigitalPinMap[], который определен в файле variant.cpp. Этот файл для каждой платы свой, но для той же ReadBear Blend2 TX и RX определены так:

  // RX, TX
  8,
  6,

Кстати сказать, назначение ног блокам периферии у nRF52832 весьма гибкое. Не знаю можно ли абсолютно любой периферии назначать абсолютно любые ноги, но для того же UART-а это практически так:

PSELRTS 0x508 Pin select for RTS
PSELTXD 0x50C Pin select for TXD
PSELCTS 0x510 Pin select for CTS
PSELRXD 0x514 Pin select for RXD

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

Цитата:
Программатор сделал из платки STM32F103CBT6 "blue pill", как описано выше по ссылке, что давал a5021. Тоже не без проблем - позже может распишу - но в итоге получилось.

Было бы интересно почитать.

Цитата:
Можно подробностей - что за протокол, какой девайс на другой стороне,  откуда брали примеры кода?

На нордиковском форуме есть сообщения, подтверждающие возможность передачи данных между nRF52832 и nRF24. Код там вроде самый незамысловатый, примеры есть в SDK (папка examples\proprietary_rf\). Все, что начинается с аббревиатуры esb (Enchanced ShockBurst), имеет отношение к передаче данных в форматах NRF24.  Подробнее на данный момент вряд ли могу рассказать, т.к. в этой части имею крайне поверхностные представления.

b707
Offline
Зарегистрирован: 26.05.2017

a5021 пишет:

 Распиновка ардуиновских плат зависит от содержимого массива g_ADigitalPinMap[], который определен в файле variant.cpp. Этот файл для каждой платы свой, но для той же ReadBear Blend2 TX и RX определены так:

  // RX, TX
  8,
  6,

Насколько я понял,  этот файл определяет только номера пинов в среде ардуино, соответвующие "ногам" конкретной платы. На функции ног он никак не влияет.

В этом смысле для нашего модуля лучше бы подощел variant.cpp от "Generic NRF52" - там в массиве просто перечислены числа от 0 до 31, что, кмк, означает, что номера пинов в Ардуино будут соответсвовать выходам P0.xx с этим же номером

a5021
Offline
Зарегистрирован: 07.07.2013

Generic NRF52 нам вообще не подходит и как раз по принчине того, что номера выводов перечислены сплошняком. С самого начала списка и не подходят, т.к. D0 и D1 не могут находиться на пинах P0.00 и P0.01 по той причине, что  на последних висит кварц.

a5021
Offline
Зарегистрирован: 07.07.2013

Продолжаю раскуривать потихоньку nRF5. На данный момент немного подразобрался с таким функционалом, как события и задания (EVENTS и TASKS). Фишка в том, что периферийные устройства в процессе функционирования генерируют разнообразные события (напр., изменение состояния пина, переполнение таймера, готовность результатов АЦП, опустошение буфера передачи и множество других). Данные события можно связывать с командами другим блокам периферии таким образом, что возникшее событие в одном периферийном блоке запусает задачу в другом блоке напрямую, т.е. без взаимодействия с центральным процессором.

Вот код, который моргает тремя светодиодами с частотой 1 герц, когда моргания каждого светодиода смещены по фазе на 120 градусов друг относительно друга:

/* NRF52 -- Tripple blink PPI */

void setup() {
  for (int i = 0; i < 3; i++) {
    NRF_GPIOTE->CONFIG[i] = (
      (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos)           |
      (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) |
      ((i + 22) << GPIOTE_CONFIG_PSEL_Pos)                          |
      (GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos)
    );

    NRF_PPI->CH[i].EEP = (uint32_t)&NRF_RTC2->EVENTS_COMPARE[i];
    NRF_PPI->CH[i].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[i];
    
    NRF_RTC2->CC[i] = 32768 * (i + 1) / 6;
  }

  NRF_PPI->FORK[2].TEP = (uint32_t)&NRF_RTC2->TASKS_CLEAR;
  NRF_PPI->CHEN = PPI_CHEN_CH0_Msk | PPI_CHEN_CH1_Msk | PPI_CHEN_CH2_Msk;

  NRF_RTC2->EVTENSET = RTC_EVTEN_COMPARE0_Msk | RTC_EVTEN_COMPARE1_Msk | RTC_EVTEN_COMPARE2_Msk;
  NRF_RTC2->TASKS_START = 1;
}

void loop() { /* NO ACTION */ }

Как можно видеть, процессор не выполняет никакого полезного кода в блоке loop(), тем не менее светики перемигиваются.

пины для подключения светиков -- P0.22, P0.23, P0.24

Работает это так: модуль часов реального времени RTC считает импульсы частотой 32768 герц. Три регистра сравнения этого модуля проинициализированы на 1/6 секунды, 1/3 и 1/2 соответственно. При совпадении значения счетчика с содержимым первого регистра сравнения, возникает событие, которое пинает первый пин, совпадение со вторым регистром пинает второй пин и с третьим разрядом все точно так же. Пины пинаются на переключение, т.е. был 0, стал 1 и наоборот. За одну секунду каждый пин включится и выключится по одному разу.

a5021
Offline
Зарегистрирован: 07.07.2013

Насчет связи между NRF24L01 и NRF52832, на MySensors.Org пишут следующее:

Good news. Thanks to the work of @d00616 on making the ESB transport available, I'm getting very good range using the nRF52832 as a receiver and a pro mini with an inexpensive power amplified nRF24 as the sender, all at 2mbps.

Код при этом выкладывают такой:

#define MY_CORE_ONLY

#ifndef ARDUINO_ARCH_NRF5
#define MY_NODE_ID (1)
#define SND_TO (2)
#else
#define MY_NODE_ID (2)
#define SND_TO (1)
#endif

// Enable debug
#define MY_DEBUG
//#define MY_DEBUG_VERBOSE_RF24
//#define MY_DEBUG_VERBOSE_NRF5_ESB


// RF24_250KBPS RF24_1MBPS RF24_2MBPS
#define MY_RF24_DATARATE (RF24_1MBPS)
// NRF5_250KBPS NRF5_1MBPS NRF5_2MBPS
#define MY_NRF5_ESB_MODE (NRF5_1MBPS)


// Enable and select radio type attached
#ifndef NRF5
#define MY_RADIO_NRF24
#else
#define MY_RADIO_NRF5_ESB
#include <nrf.h>
#endif

#include <MySensors.h>

void setup() {
  Serial.begin(115200);
  Serial.println("Starting....");
  Serial.print("MY_NODE_ID=");
  Serial.println(MY_NODE_ID);
  Serial.print("SND_TO=");
  Serial.println(SND_TO);
  Serial.flush();

  #ifdef ARDUINO_ARCH_NRF5
    NRF_CLOCK->TASKS_HFCLKSTART=1;  //activate the high frequency crystal oscillator
    while ((NRF_CLOCK->EVENTS_HFCLKSTARTED==0)) {};  //wait until high frequency clock start is confirmed
  #endif


  hwInit();
  transportInit();
  transportSetAddress(MY_NODE_ID);
}

uint32_t theTime=0;
uint32_t loopCounter=0;
void loop() {
  // Check for packages
  while ((millis()-theTime)<1000) {
    while (transportAvailable()) {
      uint8_t buffer[256];
      uint8_t num = transportReceive(&buffer);
      Serial.print(loopCounter);
      Serial.print(", RECEIVED=");
      for (int i=0;i<num;i++) {
        if (buffer[i]<0x10) Serial.print("0");
        Serial.print(buffer[i], HEX);
        Serial.print(" ");
      }
      Serial.println();
      Serial.flush();
    }
  }
  theTime=millis();

  //delay(1000);
  Serial.print(loopCounter++);
  Serial.println(", SENDING:  abcdefghijklmnopqrstuvwxyz01234");
  Serial.println();
  Serial.flush();

  // Send data
  transportSend(SND_TO, "abcdefghijklmnopqrstuvwxyz01234",32, false);
}

 

a5021
Offline
Зарегистрирован: 07.07.2013

Вроде бы простая процедура -- измерить температуру и считать ее значение в четвертях градуса из регистра  NRF_TEMP->TEMP. Но не тут то было. Из-за довольно объемной эрраты, правильное считывание температуры должно выглядеть так:

/* nRF52 -- temperature read  */

/*
* Workaround for PAN_028 rev2.0A anomalies 28, 29,30 and 31. PAN 43 is not covered.
*  - PAN_028 rev2.0A anomaly 28 - TEMP: Negative measured values are not represented correctly
*  - PAN_028 rev2.0A anomaly 29 - TEMP: Stop task clears the TEMP register.   
*  - PAN_028 rev2.0A anomaly 30 - TEMP: Temp module analog front end does not power down when DATARDY event occurs.
*  - PAN_028 rev2.0A anomaly 31 - TEMP: Temperature offset value has to be manually loaded to the TEMP module
*  - PAN_028 rev2.0A anomaly 43 - TEMP: Using PPI between DATARDY event and START task is not functional. 
*/

#define MASK_SIGN           (0x00000200UL)
#define MASK_SIGN_EXTENSION (0xFFFFFC00UL)

/**
 * @brief Function for reading temperature measurement.
 *
 * The function reads the 10 bit 2's complement value and transforms it to a 32 bit 2's complement value.
 */
static __INLINE int32_t nrf_temp_read(void)
{    
    /**@note Workaround for PAN_028 rev2.0A anomaly 28 - TEMP: Negative measured values are not represented correctly */
    return ((NRF_TEMP->TEMP & MASK_SIGN) != 0) ? (NRF_TEMP->TEMP | MASK_SIGN_EXTENSION) : (NRF_TEMP->TEMP);    
}

void setup() {
  Serial.begin(115200);

  /**@note Workaround for PAN_028 rev2.0A anomaly 31 - TEMP: Temperature offset value has to be manually loaded to the TEMP module */
  *(uint32_t *) 0x4000C504 = 0;
}

void loop() {
  
  NRF_TEMP->TASKS_START = 1; /** Start the temperature measurement. */

  /* Busy wait while temperature measurement is not finished*/
  while (NRF_TEMP->EVENTS_DATARDY == 0);
  NRF_TEMP->EVENTS_DATARDY = 0;

  /**@note Workaround for PAN_028 rev2.0A anomaly 29 - TEMP: Stop task clears the TEMP register. */
  double temp = nrf_temp_read() * 0.25;

  /**@note Workaround for PAN_028 rev2.0A anomaly 30 - TEMP: Temp module analog front end does not power down when DATARDY event occurs. */
  NRF_TEMP->TASKS_STOP = 1; /** Stop the temperature measurement. */
  
   Serial.print("Actual temperature: ");
   Serial.println(temp);

  delay(5000);
  
}

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

a5021
Offline
Зарегистрирован: 07.07.2013

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

Для запуска генератора всего и нужно -- проинициализировать регистр вот таким образом:

NRF_RNG->TASKS_START = 1;

После того, как генератор стартовал, в регистре результата NRF_RNG->VALUE в определенном темпе начинают появляться значения случайных чисел из диапазона 0..255 (8 бит). В принципе, генератору все равно, считываются эти значения или нет, новые значения замещают старые через определенные промежутки времени.

Момент, когда генерация нового случайного числа завершена, отображается записью единицы в NRF_RNG->EVENTS_VALRDY. Как только это произошло -- можно забирать случайное число из регистра результата. Для того, чтобы отслеживать появление следующего случайного числа, флажок имеет смысл каждый раз сбросывать обратно в ноль.

NRF_RNG->EVENTS_VALRDY = 0;

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

NRF_RNG->TASKS_STOP = 1;

В идеале, генератор случайных чисел должен обеспечивать дискретное равномерное распределение результатов. Так как идеального ничего нет, генератор случайных числе у nRF52832 имеет два режима -- быстрый и медленный. Быстрая генерация обеспечивает большее отклонение от равномерного распределения, медленная -- меньшее. Для большинства применений быстрой генерации должно быть достаточно. Если числа должны быть случайными "по максимуму", то есть смысл задействовать медленный режим. Пу умолчанию генератор включается в быстром режиме. Если нужно перевести его в медленный, значение регистра конфигурации надлежит сделать равным единице:

NRF_RNG->CONFIG = 1;

В быстром режиме генератор выдает результат в среднем за 30 микросекунд, в медленном за 120 микросекунд.

Вот скетч, демонстрирующий работу генератора случайных чисел:

/* nRF52 RNG -- random number generator    */

#define vector_size 8

typedef uint8_t  r8_arr_t[vector_size];
typedef uint16_t r16_arr_t[vector_size / 2];
typedef uint32_t r32_arr_t[vector_size / 4];

r8_arr_t r8_arr;
const r16_arr_t *r16 = (r16_arr_t *)&r8_arr;
const r32_arr_t *r32 = (r32_arr_t *)&r8_arr;

#define r16_arr (*r16)
#define r32_arr (*r32)

static uint32_t l_count;
char c_buf[120];

void setup() {
  Serial.begin(921600);
  NRF_RNG->CONFIG = 1;                         // Enable Bias correction
}

void loop() {
  NRF_RNG->TASKS_START = 1;                    // start generation
  for (uint16_t i = 0; i < vector_size; i++) { // for each element in vector
    while (NRF_RNG->EVENTS_VALRDY == 0);       // wait till data becomes ready
    NRF_RNG->EVENTS_VALRDY = 0;                // reset data-ready flag
    r8_arr[i] = NRF_RNG->VALUE;                // get new random number (byte)
  }
  NRF_RNG->TASKS_STOP = 1;                     // stop generation

  sprintf(
    c_buf, 
    "RND-%010u: %02x %02x %02x %02x %02x %02x %02x %02x;   %04x %04x %04x %04x;   %08x %08x\n\r", 
    ++l_count, 
    r8_arr[0], r8_arr[1], r8_arr[2], r8_arr[3], 
    r8_arr[4], r8_arr[5], r8_arr[6], r8_arr[7],
    r16_arr[0], r16_arr[1], r16_arr[2], r16_arr[3],
    r32_arr[0], r32_arr[1]
  );
  Serial.print(c_buf);
}

В принципе, как управляться с генератором показано в строках 25-31. В цикле из генератора извлекаются восемь случайных байт, которые сохраняются в массиве r8_arr. Почти весь остальной код служит цели визуализации результатов -- массив распечатывается сначала поэлементно, потом в виде четырех шестнадцатибитных значений и в конце в виде двух тридцатидвухбитных значений, типа такого:

===============================

RND-0000006691: 57 b8 bd bd ef a4 b2 6b;   b857 bdbd a4ef 6bb2;   bdbdb857 6bb2a4ef
RND-0000006692: c5 8e fc 45 79 91 56 c0;   8ec5 45fc 9179 c056;   45fc8ec5 c0569179
RND-0000006693: 58 2e eb f8 ce 59 88 1e;   2e58 f8eb 59ce 1e88;   f8eb2e58 1e8859ce
RND-0000006694: 0f ec b0 1a eb e1 24 80;   ec0f 1ab0 e1eb 8024;   1ab0ec0f 8024e1eb
RND-0000006695: bb 01 21 92 49 63 9d 21;   01bb 9221 6349 219d;   922101bb 219d6349
RND-0000006696: d5 46 ed ed bd a2 c0 aa;   46d5 eded a2bd aac0;   eded46d5 aac0a2bd
RND-0000006697: b2 97 92 39 15 6e 02 40;   97b2 3992 6e15 4002;   399297b2 40026e15
===============================

 

a5021
Offline
Зарегистрирован: 07.07.2013

Нашел на гитхабе простые, понятные и наглядные примеры использования периферии nRF5. В принципе, можно сразу затягивать в Arduino IDE с минимальными правками. Для разбирательств, как оно работает -- самое оно.

a5021
Offline
Зарегистрирован: 07.07.2013

В инстаграмме попалось:

a5021
Offline
Зарегистрирован: 07.07.2013

Дошла очередь до таймеров. Их у nRF52832 пять. Таймеры весьма специфичные, если их сравнивать с AVR/PIC/STM32/MSP430. Первое, что бросается в глаза -- это отсутствие прямого доступа к счетчику. Счетчик наличествует, но ни считать из него, ни записать в него ничего нельзя. Можно только сбросить в ноль или через захват скопировать текущее значение в регистр захвата, откуда уже, собственно, прочесть.

Чтобы запустить любой таймер в скетче, нужно выполнить операцию присваивания вида:

NRF_TIMERx->TASKS_START = 1;

где "x" может быть цифрой от 0 до 4, что соответствует номеру таймера.

После запуска таймера в конфигурации по умолчанию, счетчик начнет увеличивать свое значение на 1 через каждую микросекунду, пока не переполнится на значении 2^16=65536 (т.е. счетик 16-битный). После переполнения счетчик вновь начинает с нуля и цикл счета повторяется. Вызывать прерывание по переполнению счетчика таймеры не умеют, что опять сильно их отличает от таймеров других МК.

Осмысленно использовать таймер в таком режиме, на мой взгляд, можно только одним образом -- измерять время между событиями. Перед началом измерений следует сделать один захват счетчика, а после -- другой. Разница в значениях будет соответствовать измеренному времени в микросекундах. Нижеследующий пример измеряет, сколько длится вызов delay(10) на самом деле:

void setup() {
  Serial.begin(9600);
  NRF_TIMER0->TASKS_START = 1;
}

void loop() {

  NRF_TIMER0->TASKS_CAPTURE[0] = 1; 
  delay(10);
  NRF_TIMER0->TASKS_CAPTURE[1] = 1; 

  Serial.print("Elapsed time = ");
  Serial.print((uint16_t)(NRF_TIMER0->CC[1] - NRF_TIMER0->CC[0]));
  Serial.println("uS");
  
  delay(2000);
}

Инструкция в строке 08 инициирует захват счетчика в регистр захвата 0, а инструкция в строке 10 в регистр 1. Разница между значениями в регистрах захвата 1 и 0, как раз и равняется времени, затраченному на вызов функции в строке 09:

===

Elapsed time = 10042uS
Elapsed time = 10043uS
Elapsed time = 10043uS

===

Число регистров захвата у разных таймеров разное. TIMER0..TIMER2 имеют четыре регистра, а TIMER3 и TIMER4 шесть.

Измерения в вышеприведенном примере хорошо работают до тех пор, пока измеряемый промежуток не превышает 65536 микросекунд. Все, что дольше измеряться не будет. Как быть? Один из вариантов -- увеличить разрядность счетчика. В отличии от меги и прочих распространенных МК, у nRF52832 разрядность можно выбирать. 8, 16, 24 и 32 бит -- такую разрядность могут иметь регистры любого из пяти имеющихся таймеров.

За разрядность у нордика отвечает регистр NRF_TIMERx->BITMODE. Записывая туда значния 0, 1, 2 и 3, можно делать таймер 16-и, 8-и, 24-х и 32-х разрядным. Так, если к предыдущему скетчу дописать перед запуском таймера в функции setup() строчку

NRF_TIMER0->BITMODE = 3;

то таймер станет 32-разрядным и вышеописанным способом можно считать промежутки времени до 71,5 секунд с разрешением 1мкс. Правда, чтобы скетч работал правильно, нужно еще изменить приведение типа в строке 13 с uint16_t на uint32_t .

a5021
Offline
Зарегистрирован: 07.07.2013

Другим способом использования таймера является режим сравнения. Регистры NRF_TIMERx->СC[y] называются регистрами захвата/сравнения потому, что помимо захвата, рассмотренного в предыдущем примере, они могут использоваться и для сравнения значений в них записанных с содержимым счетчика. В принципе, логика тут простая: записываем в CC[x] некое число и как только счетчик таймера становится этому числу равен, генерируется событие. Так как число регистров сравнения четыре или шесть в зависимости от используемого таймера, то можно регистрировать до четырех или шести событий за один период счета.

Несколько раньше в этой теме я писал, что в функционале блоков периферии присутствуют такие понятия, как события и задания (EVENTS и TASKS). Есть и связывающий оба этих понятия функционал под названием "сочетания" (SHORTCUTS или SHORTS). Применительно к таймерам, "сочетания" могут быть использованы для сброса таймера или его останова по событию, скажем, совпадения. Например, если в регистр сравнения занести число 100, а в регистр "сочетаний" внести задание "очистка по совпадению", то счетчик будет считать до 100, сбрасываться и так по кругу. Если активировать "сочетание" "остановка по совпадению", то счетчик один раз досчитает до ста и остановится. Вот примеры, как можно использовать эти сочетания.

1) Blink на таймере. а)Значение регистра сравнения выбирается равным 500 тысяч. Этим обеспечивается выставления флага "совпадение" через 0.5 секунды после начала счета. б) Конфигуригурируется "сочетание" "очистка по свопадению", что обеспечивает выставление флага через каждые 0.5 секунды.


void setup() {
    // set 24-bit mode for TIMER0
  NRF_TIMER0->BITMODE = 2;
    // set 0.5s count period
  NRF_TIMER0->CC[0] = 500000;
    // reset counter to 0 on compare
  NRF_TIMER0->SHORTS =
    TIMER_SHORTS_COMPARE0_CLEAR_Enabled;
    // run timer
  NRF_TIMER0->TASKS_START = 1;
    // set LED pin to OUTPUT mode
  pinMode(LED_BUILTIN, OUTPUT);
}

static uint8_t led_state; // LED state

void loop() {
    // wait for compare flag
  while(NRF_TIMER0->EVENTS_COMPARE[0] == 0); 
    // reset flsg
  NRF_TIMER0->EVENTS_COMPARE[0] = 0;
    // switch led
  digitalWrite(LED_BUILTIN, (led_state == 0) ? HIGH : LOW);
    // toggle led-status variable
  led_state ^= 1;
}

2) delay() на таймере. Функция принимает значение задержки в миллисекундах. Это число умножается на 1000 и заносится в регистр сравнения таймера. Конфигурируются "сочетания" "остановка при совпадении" и "очистка при совпадении", т.е. одно событие запускает две задачи.  Работа функции иллюстрируется на все том же Blink-е.

void tim_delay(uint32_t ms) {
    // set period
  NRF_TIMER0->CC[0] = ms * 1000;
    // run timer
  NRF_TIMER0->TASKS_START = 1;
    // wait for counting period
  while(NRF_TIMER0->EVENTS_COMPARE[0] == 0);
    // reset compare flag
  NRF_TIMER0->EVENTS_COMPARE[0] = 0;
}

void setup() {
    // set 24-bit mode for TIMER0
  NRF_TIMER0->BITMODE = 2;
    // stop counter on compare & clear on compare
  NRF_TIMER0->SHORTS = (
    TIMER_SHORTS_COMPARE0_STOP_Enabled << TIMER_SHORTS_COMPARE0_STOP_Pos |
    TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos
  );
    // reset compare flag
  NRF_TIMER0->EVENTS_COMPARE[0] = 0;
    // set LED pin to OUTPUT mode
  pinMode(LED_BUILTIN, OUTPUT);
}

static uint8_t led_state; // LED state

void loop() {
  tim_delay(500);
    // switch led
  digitalWrite(LED_BUILTIN, (led_state == 0) ? HIGH : LOW);
    // toggle led-status variable
  led_state ^= 1;
}

 

a5021
Offline
Зарегистрирован: 07.07.2013

Твиттер принес ссылку на свежую статью, как хакнуть фитнесс-трекер X9. Это бы не было интересно, когда бы сей трекер ни был сделан на nRF52832, а хакали его с помощью Arduino IDE и библиотеки Sandeep Mistry's Nordic ArduinoCore.

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

b707
Offline
Зарегистрирован: 26.05.2017

a5021. сорри за молчание, неделю отсутсвовал. Спасибо, что делитесь опытом. Почитаю вечером

a5021
Offline
Зарегистрирован: 07.07.2013

Запустил я скетч, который упоминал в сообщении №30 и что касается блютуза в версии BLE то оно вполне себе работает. С телефоном общается.

Попробовал передавать данные через bleSerial.print() -- данные на телефон приходят.

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

 

a5021
Offline
Зарегистрирован: 07.07.2013

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

#include "BLESerial.h"

BLESerial bleSerial; //Bluetooth - create peripheral instance

void setup() {
  bleSerial.setLocalName("E73-2G4M04S"); /* set custom name */
  bleSerial.begin();                     /* Start Bluetooth UART */
}

static uint32_t rnd;

void loop() {     
  bleSerial.poll();
  delay(1500);

  bleSerial.print("E73 #");
  bleSerial.print(rnd++);
  bleSerial.print(" = 0x");
  bleSerial.print(random(0x1000, 0xFFFF), HEX);
}

Вот так на телефоне выглядит то, что он шлет через блютуз:

Вспомнил, что неожиданно много времени потратил на поиски SoftDevice v2.0.1, под которую заточена BLE Peripherial.  Более свежие вполне доступны, а с поисками именно этой пришлось повозиться. На всякий случай привожу ссылку на страницу загрузки.

b707
Offline
Зарегистрирован: 26.05.2017

a5021 пишет:

Вспомнил, что неожиданно много времени потратил на поиски SoftDevice v2.0.1, под которую заточена BLE Peripherial.  Более свежие вполне доступны, а с поисками именно этой пришлось повозиться. На всякий случай привожу ссылку на страницу загрузки.

a5021 - что-то я упустил, в какой момент и для чего Вам понадобилось SoftDevice ? - по моим наивным представлением - мы используем либо среду Ардуино, либо СофтДевайс, но не обе вместе...

a5021
Offline
Зарегистрирован: 07.07.2013

SoftDevice попал в поле моего зрения еще в ссобщении №30. На этом и возникло любопытство посмотреть, как оно работает. Вообще, возможность использования SoftDevice появляется после установки библиотеки sandeepmistry/arduino-nRF5.

Теперь, если мы хотим прошить SoftDevice в nRF52832, всего и надо -- выбрать "Записать Загрузчик" в данном меню.

Другое дело, что использовать каким-нибудь простым образом этот SoftDevice будет затруднительно, т.к. все манипуляции с ним придется самостоятельно описывать. Что бы не было так страшно, можно библиотеку BLEPeripherial установить. Там и примеры и возможность подглядеть, как оно все должно работать.

В общем и целом, Arduino и SoftDevice вполне себе совместимы, но есть ряд ограничений, которые придется учитывать при разработке приложений. Речь о совместном использовании ресурсов МК пользовательским приложением и самим SoftDevice. Я глубоко не копал, но уже налетел на некий конфликт в этой области. Попытка использовать аппаратный генератор случайных чисел без оглядки на SoftDevice привела к тому, что телефон перестал подключаться к E73, хотя в блютуз-окружении это устройство присутствует. Для выяснения подробностей, почему так, надо смотреть SoftDevice API и возможно у меня когда-то дойдет до этого дело, но на данный момент это не первоочередная цель.

 

b707
Offline
Зарегистрирован: 26.05.2017

a5021 пишет:

SoftDevice попал в поле моего зрения еще в ссобщении №30. На этом и возникло любопытство посмотреть, как оно работает. Вообще, возможность использования SoftDevice появляется после установки библиотеки sandeepmistry/arduino-nRF5.

Так вы с BLE (например скетч №33) работаете через SoftDevice? - просто из кода это никак не просматривается.

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

a5021
Offline
Зарегистрирован: 07.07.2013

Ардуиновская библиотека BLEPeripherial -- это лишь обертка над SoftDevice. Все протоколы BLE 4.2 реализуются внутри SoftDevice. Можно ли без SoftDevice ? Можно. Для этого, правда, придется написать свой стек протоколов согласно спецификации BLE 4.2. У нордиков это заняло порядка нескольких лет. Как по мне, перспектива не вдохновляет.

Насчет "погружения на много дней", тут больше зависит от того, что требуется. Чтобы начать передавать данные с E73 никаких "многих дней" вобщем-то и не нужно. Смотрим примеры, соображаем, как это употрибить в своих целях. Вместе с BLEPeripherial идет кучка примеров, откуда вполне возможно вытянуть простой функционал без заныривания в теории. Хочется чего-то большего -- есть SDK, в которой много всего и достаточно подробно, но придется погружаться. Способов, чтобы и "любой каприз" и без погружения, я не знаю.

b707
Offline
Зарегистрирован: 26.05.2017

a5021 пишет:

Способов, чтобы и "любой каприз" и без погружения, я не знаю.

Да это и понятно, что "без труда и рыбку съесть..." не выйдет. :) Буду разбираться.

Спасибо, что делитесь.

a5021
Offline
Зарегистрирован: 07.07.2013

Подбираюсь потихоньку к радио. Документация у нордиков, конечно, вещь в себе. Можно перечитать непонятное место несколько раз, но к пониманию не приблизиться ни на миллиметр. Это я о том, как пытался понять, о чем идет речь в "23.5 Data whitening" -- разделе манулала, касающегося нюансов функционирования радио. Нордики скупы на пояснения. По их словам, блок радио у nRF52832 умеет отбеливать данные и де-отбеливать (попытался представить на бытовом уровне, что может скрываться за понятием де-отбеливания, но не смог). Дальше даю точный перевод, описывающий сие "отбеливание" в мануале нордика:

Отбеливающее слово генерируется с помощью полинома  X^7 + X^4 + 1, которое затем складывается по модулю 2 ("исключающее ИЛИ") с пакетом данных, который должен быть отбелен или де-отбелен.

Кто-нибудь может вот так сходу пояснить, о чем бы это могло говориться?

Я, может, еще долго медитировал бы над этим "де-отбеливанием", пытаясь постигнуть его кармическую сущность, но решил сделать проще и почитать, что об этом пишут другие производители радио-чипов. И вот у SiliconLabs попалось совершенно доходчивое описание, что же сам процесс отбеливания означает и для чего он нужен.

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

Теперь о том, какой смысл это "отбеливание" может иметь в практическом применении. Насколько я в курсе, в BLE оно используется по требованию самого стандарта и тут выбора никакого нет. Модули NRF24L01, напротив, про "отбеливание" ничего не знают и аппаратно его не умеют. Таким образом, если возникает нужда связывать NRF24 и NRF52, отбеливание у последних нужно выключать в обязательном порядке. И наоборот, при организации связи на одних только NRF52 отбеливание лучше использовать, тем более, что оно полностью аппаратное и никаких доп. расходов не влечет.

Здесь попутно возникла мысль про то, что, отбеливание хорошо бы применять и в случае, когда используется связь вида NRF24L01 <-> NRF24L01 или NRF24L01 <-> NRF52832. Когда нет возможности использовать аппаратную фичу, можно и в софте изобразить то же самое. По идее, это должно улучшать качество связи.

a5021
Offline
Зарегистрирован: 07.07.2013

NRF52 может получать тактирование от внутреннего генератора или от внешнего кварца. В принципе, то же самое можно сказать и про мегу, но у этих МК имеется некоторая разница в подходах. У меги за источник тактового сигнала отвечают фьюзы и только с их помощью можно задать будет ли МК работать от кварца или внутреннего генератора. В случае с NRF52, МК всегда включается на внутреннем генераторе, а задействовать ли более стабильный внешний источник, решает программист, сочиняющий управляющую программу. Есть у NRF52 еще и генератор низкой частоты, от которого тактируются счетчики реального времени (RTC). Можно с их помощью отсчитывать время на глазок, если в качестве опорного используется внутренний генератор или более точно, когда задействован кварц. Казалось бы, ну что за ерунда и почему бы не использовать кварцованные генераторы всегда? Однако на деле выходит, что кроме плюсов у кварцованных генераторов есть и минусы. Основных минусов два -- повышенное энергопотребение и большее время выхода на режим стабильной генерации. Для устройств с микропотреблением -- это критично. Отсюда идея, оперативно выбирать источник тактового сигнала в зависимости от потребностей, выглядит вполне здравой. Надо -- включили кварцевую стабилизацию, не надо -- выключили. Теперь о том, как это может выглядеть в коде. Вот пример функции, вызов которой может переключить тактирование с внутренних на внешние генераторы:

__STATIC_INLINE void clock_switch(void) {
  if (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) {
    /* Start 32 MHz crystal oscillator */
    NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_HFCLKSTART    = 1;
     /* Wait for the external oscillator to start up */
    while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);
  }

  if (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) {
    /* Start low frequency crystal oscillator */
    NRF_CLOCK->LFCLKSRC            = (CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos);
    NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_LFCLKSTART    = 1;
     /* Wait for the external oscillator to start up */
    while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0);
  }
}

Ну а это скетч целиком с выводом в консоль результатов:


void print_clock_cfg(void) {
  Serial.print("High frequency clock is ");
  if ( (NRF_CLOCK->HFCLKSTAT & CLOCK_HFCLKSTAT_STATE_Msk) == 0) {
    Serial.println("not running.");
  } else {
    Serial.print("running. Clock source: 64 MHz ");
    if ((NRF_CLOCK->HFCLKSTAT & CLOCK_HFCLKSTAT_SRC_Msk) == 0) {
      Serial.print("internal (HFINT) ");
    } else {
      Serial.print("crystal (HFXO) ");
    }
    Serial.println("oscillator.");
  }

  Serial.print("Low frequency clock is ");
  if ((NRF_CLOCK->LFCLKSTAT & CLOCK_LFCLKSTAT_STATE_Msk) == 0) {
    Serial.println("not running.");
  } else {
    Serial.print("running. Clock source: 32.768 kHz ");
    if ((NRF_CLOCK->LFCLKSTAT & CLOCK_LFCLKSTAT_SRC_Msk) == CLOCK_LFCLKSTAT_SRC_RC) {
      Serial.println("RC oscillator.");
    } else if ((NRF_CLOCK->LFCLKSTAT & CLOCK_LFCLKSTAT_SRC_Msk) == CLOCK_LFCLKSTAT_SRC_Xtal) {
      Serial.println("crystal oscillator.");
    } else if ((NRF_CLOCK->LFCLKSTAT & CLOCK_LFCLKSTAT_SRC_Msk) == CLOCK_LFCLKSTAT_SRC_Synth) {
      Serial.println("synthesized from HFCLK.");
    }
  }
}

__STATIC_INLINE void clock_switch(void) {
  if (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) {
    /* Start 32 MHz crystal oscillator */
    NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_HFCLKSTART    = 1;
     /* Wait for the external oscillator to start up */
    while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);
  }

  if (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) {
    /* Start low frequency crystal oscillator */
    NRF_CLOCK->LFCLKSRC            = (CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos);
    NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_LFCLKSTART    = 1;
     /* Wait for the external oscillator to start up */
    while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0);
  }
}


void setup() {
  Serial.begin(9600);
}

void loop() {

  Serial.println("=== BEFORE:");

  print_clock_cfg();
 
  Serial.println("=== AFTER:");

  clock_switch();
  
  print_clock_cfg();
  while(1);
  
}

 

a5021
Offline
Зарегистрирован: 07.07.2013

Для проведения экспериментов по организации радиосвязи между nRF52832 и nRF24L01+, помимо имеющейся E73, по идее, необходимо еще некое устройство, куда бы можно было подключать nRF24L01+.  Например, ардуино уно/про-мини или что-то наподобие. С другой стороны, ничто не мешает подключить nRF24L01+ непосредственно к E73, благо последняя -- это тоже ардуино, только в своей ипостаси. В этом случае эксперименты при радиобмене будут проводиться между компонентами одного устройства -- встроенным радио и подключаемым. Так оно и компактнее и писанины меньше.

nRF24L01+ имеет интерфейс подключения SPI, стало быть, на E73 нужно настроить один из имеющихся там преиферийных блоков SPI (SPI0-SPI3). Весьма кстати, что почти весь нужный здесь функционал обеспечивают настройки по умолчанию и по большому счету, нужно лишь распределить на какие ноги, какие сигналы выводить. Чтобы не вносить лишней путаницы, практичнее испльзовать сложившуюся практику подлкючения SPI-устройств, для чего "совместить" линии интерфейсов таким образом:

SPI      E73           NRF24L01+
===============================
SS      10 (22) ---  CSN     4
MOSI  11 (23) ---  MOSI   6
MISO  12 (24) ---  MISO   7
SCK    13 (25) ---  SCK     5
===============================
            9 (20) ---  CE       3
            8 (19) ---  IRQ      8

в скобках у E73 приведена нумерация физических контактов. На всякий случай, распиновка nRF24L01+:

Теперь про инициализацию SPI у E73. Она незамысловата:

  

#define NRF24_PIN_CE     9
#define NRF24_PIN_CSN    SS

  /* SPI frequency = 8MHz */
  NRF_SPI0->FREQUENCY = SPI_FREQUENCY_FREQUENCY_M8;  

  /* Connect phisical pins to logical lines */
  NRF_SPI0->PSEL.MISO = g_ADigitalPinMap[MISO];
  NRF_SPI0->PSEL.MOSI = g_ADigitalPinMap[MOSI];
  NRF_SPI0->PSEL.SCK  = g_ADigitalPinMap[SCK];

  /* Enable SPI0  */
  NRF_SPI0->ENABLE = 1;

Дальше уже можно писать скетч опроса nRF24L01+ целиком:


#define NRF24_PIN_CE     9
#define NRF24_PIN_CSN    SS

uint8_t spi_putc(uint8_t c) {
  NRF_SPI0->TXD = c;
  while (NRF_SPI0->EVENTS_READY == 0);
  NRF_SPI0->EVENTS_READY = 0;
  return NRF_SPI0->RXD;
}

uint8_t nrf24_read_register(uint8_t r) {
  digitalWrite(NRF24_PIN_CSN, LOW);
  spi_putc(r);
  uint8_t res = spi_putc(0xFF);
  digitalWrite(NRF24_PIN_CSN, HIGH);
  return res;
}

void setup() {

  NRF_SPI0->FREQUENCY = SPI_FREQUENCY_FREQUENCY_M8;  /* SPI frequency = 8MHz  */

  NRF_SPI0->PSEL.MISO = g_ADigitalPinMap[MISO];
  NRF_SPI0->PSEL.MOSI = g_ADigitalPinMap[MOSI];
  NRF_SPI0->PSEL.SCK  = g_ADigitalPinMap[SCK];

  NRF_SPI0->ENABLE = 1;                              /* Enable SPI0           */

  digitalWrite(NRF24_PIN_CSN, HIGH);
  pinMode(NRF24_PIN_CSN, OUTPUT);

  digitalWrite(NRF24_PIN_CE, LOW);
  pinMode(NRF24_PIN_CE, OUTPUT);

  Serial.begin(9600);  /* Initialize serial console      */

}

void loop() {
  Serial.print("NRF24L01+ REGISTERS: ");
  for (uint8_t i = 0; i < 10; i++) {
    Serial.print(" 0x");
    Serial.print(nrf24_read_register(i), HEX);
  }
  Serial.println("");
  delay(5000);
}

Скетч считывает из nRF24L01+ первые десять регистров и выводит их содержимое в консоль. У меня получились вот такие значения:

===

NRF24L01+ REGISTERS:  0x8 0x3F 0x3 0x3 0x3 0x2 0xF 0xE 0x0 0x0

===

Если сравнить эти числа со значениями регистров по умолчанию из даташита, то они совпадают. Стало быть, SPI-интерфейс и висящий на нем внешний модуль работают нормально.

a5021
Offline
Зарегистрирован: 07.07.2013

Немного о том, как использовать радио nRF52832 в режиме совместимости с NRF24L01+. Нюансы там имеются, но можно начать с самого простого. Определяем самые основные параметры, без которых совсем никак.

Скорость приема или передачи:

NRF_RADIO->MODE = RADIO_MODE_MODE_Nrf_2Mbit; // 2 mbit, NRF modulation

Два регистра, задающих формат пакета:

  NRF_RADIO->PCNF0 = (              /* Packet configuration register 0           */
    (6 << RADIO_PCNF0_LFLEN_Pos)  | /* 6 bits for LENGTH field                   */
    (3 << RADIO_PCNF0_S1LEN_Pos)    /* 3 bits for PKT ID & NO_ACK field          */
  );

  NRF_RADIO->PCNF1 = (              /* Packet configuration register 1           */
    (RADIO_PCNF1_ENDIAN_Big << RADIO_PCNF1_ENDIAN_Pos) | /* Most significant bit on air first */
    (4 << RADIO_PCNF1_BALEN_Pos)  | /* Base address length in number of bytes    */
    (32 << RADIO_PCNF1_MAXLEN_Pos)  /* Max payload size in bytes                 */
  );

Адрес, используемый в радиообмене:

  NRF_RADIO->BASE0 = 0xE7E7E7E7;    /* Base address 0                            */
  NRF_RADIO->PREFIX0 = 0xE7;        /* Prefixes bytes for logical addresses 0    */
  NRF_RADIO->RXADDRESSES = 0x01;    /* Receive address select                    */

Параметры расчета контрольных сумм:

  NRF_RADIO->CRCCNF = RADIO_CRCCNF_LEN_Two;     /* CRC configuration: 16bit      */
  NRF_RADIO->CRCINIT = 0xFFFFUL;                /* CRC initial value             */
  NRF_RADIO->CRCPOLY = 0x11021UL;               /* CRC poly: x^16+x^12^x^5+1     */

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

a5021
Offline
Зарегистрирован: 07.07.2013

Следующее пояснение будет о том, как блок радио у nRF52 обращается с принятыми данными и данными для отправки. Все, что блок радио должен знать о данных -- это указатель на буфер (массив) в памяти. Указатель -- это регистр, в который должн быть записан 32-битный адрес буфера. Например, так:

NRF_RADIO->PACKETPTR = (uint32_t)&nrf52_payload; /* Set pointer to payload buffer */

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

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

typedef struct {
  uint8_t len;
  uint8_t id;
  uint8_t pl[33];
} esb_payload_t;

esb_payload_t nrf52_payload;

С точки зрения организации связи между nRF52 и nRF24, максимальный объем передаваемых данных за раз не может превышать 32 байта. Это ограничение проистекает из характеристик nRF24L01+ (в то время, как два модуля nRF52 вполне могли бы пересылать друг-другу пакеты и существенно большего размера).

Массив pl[] в данном примере, тем не менее, имеет размер на один байт больший. Такой размер выбран на тот случай, если пейлоад представляет собой ASCIIZ-строку. Такая ситуация может возникать, если в буфер передачи напрямую печатать, например, с помощью функции sprintf(). Вывели строку через sprintf(), дернули передачу и строка улетела в эфир. Удобно.

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

 

a5021
Offline
Зарегистрирован: 07.07.2013

Прежде, чем начинать передачу данных между nRF52832 и nRF24L01+, надлежит сторону nRF24L01+ так же сконфигурировать в совместимый режим. Это достигается записью одного регистра в случае работы на передачу и записью значений в два регистра, если модуль должен работать на прием. В обоих случаях надо разрешить переменную длину пакета передаваемых данных:

  /* Enable dynamic payload and W_TX_PAYLOAD_NOACK command       */
  /* 'FEATURE' register address = 0x1D  */
  nrf24_write_register(0x1D,
    1 * (1 << 2)     |     /* Dynamic Payload:     1=ON, 0=OFF   */
    0 * (1 << 1)     |     /* Payload with ACK:    1=ON, 0=OFF   */
    1 * (1 << 0)           /* W_TX_PAYLOAD_NOACK:  1=ON, 0=OFF   */
  );

Приемнику следует разрешить прием таких пакетов:

  /* 'DYNPD' register address = 0x1C                             */ 
  nrf24_write_register(0x1C, 0x01); /* enable DYNPD on RX0       */

Трансивер у nRF24L01+ включается установкой первого бита (Power Control) в регистре CONFIG:

  /* Power NRF24L01's transmitter up                             */
  /* 'CONFIG' register address = 0x00                            */
  nrf24_write_register(0x00,
    0 * (1 << 0)     |     /* RX/TX control:  1=PRX, 0=PTX       */
    1 * (1 << 1)     |     /* Power control:  1=UP, 0=DOWN       */
    1 * (1 << 2)     |     /* CRC encoding:   0=8bit, 1=16bit    */
    1 * (1 << 3)     |     /* CRC control:    0=OFF, 1=ON        */
    1 * (1 << 4)     |     /* MAX_RT IRQ:     0=ON,  1=OFF       */
    1 * (1 << 5)     |     /* TS_DS  IRQ:     0=ON,  1=OFF       */
    1 * (1 << 6)           /* RX_DR  IRQ:     0=ON,  1=OFF       */
  );

Нулевой бит в этом регистре отвечат за режим приема (бит установлен) или передачи (бит сброшен). На примере выше, трансивер включается в режиме передачи.

Не смотря на то, что трансивер включен, блок радио еще не работает. За включение радио отвечает вывод CE модуля. Переключение его из низкого положения в высокое приводит к тому, что модуль начинает передавать в эфир данные (если они были подготовлены заранее) или принимать их оттуда.

Оправку данных в модуль для передаче и извлечение данных из модуля при приеме имеет смысл оформить в виде отдельных процедур:

void nrf24_write_payload(uint8_t pl[]) {
  digitalWrite(NRF24_PIN_CSN, LOW);
  spi_putc(0xB0); /* 0xB0 = W_TX_PAYLOAD_NOACK */
  for (uint8_t i = 0; pl[i] != 0; i++) spi_putc(pl[i]);
  digitalWrite(NRF24_PIN_CSN, HIGH);
}

void nrf24_read_payload(uint8_t pl[], uint8_t len) {
  digitalWrite(NRF24_PIN_CSN, LOW);
  spi_putc(0x61); /* R_RX_PAYLOAD command code = 0x61 */
  for (uint8_t i = 0; i < len; i++) pl[i] = spi_putc(0xFF);
  digitalWrite(NRF24_PIN_CSN, HIGH);
}

При поднятом уровне CE данные в эфир выдаются сразу же после окончания работы функции nrf24_write_payload().

Извлекающая принятые данные из модуля функция nrf24_read_payload() нуждается в параметре len, указывающем сколько байт должно быть получено. Уточнить объем принятых данных можно вызовом следущей функции:

uint8_t nrf24_get_payload_size(void) {
  digitalWrite(NRF24_PIN_CSN, LOW);
  spi_putc(0x60); /* R_RX_PL_WID command code = 0x60    */
  uint8_t p_size = spi_putc(0xFF);
  digitalWrite(NRF24_PIN_CSN, HIGH);
  return p_size;
}

 

a5021
Offline
Зарегистрирован: 07.07.2013

Наконец два скетча, демонстрирующих работу модулей на прием и передачу. В первом nRF24L01+ передает, nRF52832 получает:


#include <stdio.h>
#include <string.h>

#define PL_SIZE 32

typedef struct {
  uint8_t len;
  uint8_t id;
  uint8_t pl[PL_SIZE + 1];
} esb_payload_t;

#define NRF24_PIN_CE     9
#define NRF24_PIN_CSN    SS

uint8_t spi_putc(uint8_t c) {
  NRF_SPI0->TXD = c;
  while (NRF_SPI0->EVENTS_READY == 0);
  NRF_SPI0->EVENTS_READY = 0;
  return NRF_SPI0->RXD;
}

void nrf24_write_register(uint8_t r, uint8_t d) {
  digitalWrite(NRF24_PIN_CSN, LOW);
  /* 'W_REGISTER' command code = 0x20     */
  /* 'REGISTER_MASK' = 0x1F               */
  spi_putc(0x20 | (0x1F & r));
  spi_putc(d);
  digitalWrite(NRF24_PIN_CSN, HIGH);
}

void nrf24_write_payload(uint8_t pl[]) {
  digitalWrite(NRF24_PIN_CSN, LOW);
  spi_putc(0xB0); /* 0xB0 = W_TX_PAYLOAD_NOACK */
  for (uint8_t i = 0; pl[i] != 0; i++) spi_putc(pl[i]);
  digitalWrite(NRF24_PIN_CSN, HIGH);
}

static esb_payload_t nrf52_payload;
static uint8_t nrf24_payload[PL_SIZE + 1];

void setup() {

  /************* START CRYSTALL OSCILLATORS *********************************************************/
  
  if (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) {
    /* Start 32 MHz crystal oscillator */
    NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_HFCLKSTART    = 1;
    
    while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);     /* Wait for the external oscillator to start up */
  }

  if (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) {
    /* Start low frequency crystal oscillator */
    NRF_CLOCK->LFCLKSRC            = (CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos);
    NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_LFCLKSTART    = 1;
  
    while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0);
  }

  /************* SET RADIO SHORTCUTS ****************************************************************/

  NRF_RADIO->SHORTS = (   /* Shortcut register                                                      */
    (RADIO_SHORTS_READY_START_Enabled << RADIO_SHORTS_READY_START_Pos)    |
    (RADIO_SHORTS_END_START_Enabled << RADIO_SHORTS_END_START_Pos)                                       
  );

  /************* SET RF PARAMETERS ******************************************************************/

  NRF_RADIO->MODE = RADIO_MODE_MODE_Nrf_2Mbit;    /* Data rate and modulation                       */

  /************* SET DATA PACKET PARAMETERS *********************************************************/

  NRF_RADIO->PCNF0 = (                            /* Packet configuration register 0                */
    (6 << RADIO_PCNF0_LFLEN_Pos)                     | /* 6 bits for LENGTH field                   */
    (3 << RADIO_PCNF0_S1LEN_Pos)                       /* 3 bits for PKT ID & NO_ACK field          */
  );

  NRF_RADIO->PCNF1 = (                            /* Packet configuration register 1                */
    (RADIO_PCNF1_ENDIAN_Big << RADIO_PCNF1_ENDIAN_Pos) | /* Most significant bit on air first       */
    (5 << RADIO_PCNF1_BALEN_Pos)                       | /* Base address length in number of bytes  */
    (32 << RADIO_PCNF1_MAXLEN_Pos)                       /* Max payload size in bytes               */
  );

  /************* SET ADDRESS ************************************************************************/

  NRF_RADIO->BASE0 = 0xE7E7E7E7;                /* Base address 0                                   */
  NRF_RADIO->PREFIX0 = 0xE7;                    /* Prefixes bytes for logical addresses 0           */
  NRF_RADIO->RXADDRESSES = 0x01;                /* Receive address select                           */

  /************* SET CRC PARAMETERS *****************************************************************/

  NRF_RADIO->CRCCNF = RADIO_CRCCNF_LEN_Two;     /* CRC configuration: 16bit                         */
  NRF_RADIO->CRCINIT = 0xFFFFUL;                /* CRC initial value                                */
  NRF_RADIO->CRCPOLY = 0x11021UL;               /* CRC poly: x^16+x^12^x^5+1                        */

  /************* SET DATA BUFFER ********************************************************************/

  NRF_RADIO->PACKETPTR = (uint32_t)&nrf52_payload; /* Set pointer to RX buffer                      */

  /************* START RADIO ************************************************************************/
  
  NRF_RADIO->TASKS_RXEN = 1;                    /* Enable RADIO in RX mode                          */

  /************* INITIALIZE GPIO ********************************************************************/

  digitalWrite(NRF24_PIN_CSN, HIGH);
  pinMode(NRF24_PIN_CSN, OUTPUT);

  digitalWrite(NRF24_PIN_CE, LOW);
  pinMode(NRF24_PIN_CE, OUTPUT);

 /************* INITIALIZE SPI *********************************************************************/

  NRF_SPI0->FREQUENCY = SPI_FREQUENCY_FREQUENCY_M8;  /* SPI frequency = 8MHz */

  NRF_SPI0->PSEL.MISO = g_ADigitalPinMap[MISO];
  NRF_SPI0->PSEL.MOSI = g_ADigitalPinMap[MOSI];
  NRF_SPI0->PSEL.SCK  = g_ADigitalPinMap[SCK];

  NRF_SPI0->ENABLE = 1;  /* Enable SPI0  */

  /************* CONFIGURE NRF24L01+ *****************************/

  /* Enable dynamic payload and W_TX_PAYLOAD_NOACK command       */
  /* 'FEATURE' register address = 0x1D  */
  nrf24_write_register(0x1D,
    1 * (1 << 2)     |     /* Enable Dynamic Payload             */
    0 * (1 << 1)     |     /* Disable Payload with ACK           */
    1 * (1 << 0)           /* Enable W_TX_PAYLOAD_NOACK          */
  );
  
  /* Power NRF24L01's transmitter up                             */
  /* 'CONFIG' register address = 0x00                            */
  nrf24_write_register(0x00,
    0 * (1 << 0)     |     /* RX/TX control:  1=PRX, 0=PTX       */
    1 * (1 << 1)     |     /* Power control:  1=UP, 0=DOWN       */
    1 * (1 << 2)     |     /* CRC encoding:   0=8bit, 1=16bit    */
    1 * (1 << 3)     |     /* CRC control:    0=OFF, 1=ON        */
    1 * (1 << 4)     |     /* MAX_RT IRQ:     0=ON,  1=OFF       */
    1 * (1 << 5)     |     /* TS_DS  IRQ:     0=ON,  1=OFF       */
    1 * (1 << 6)           /* RX_DR  IRQ:     0=ON,  1=OFF       */
  );
  
  /***** RISE CE PIN HIGH TO SWITCH NRF24 TO STANDBY-II MODE *****/
  
  digitalWrite(NRF24_PIN_CE, HIGH);

  Serial.begin(9600);  /* Initialize serial console              */
}

void loop() {

  /*********** CLEAR TX & RX BUFFERS *****************************/

  memset(&nrf24_payload, 0, sizeof(nrf24_payload));
  memset(&nrf52_payload, 0, sizeof(nrf52_payload));

  /*********** GENERATE PAYLOAD AND SEND IT TO TRANSMITTER *******/

  sprintf((char *)nrf24_payload, "Radio-TEST (%08X / %08X)", rand(), rand());
  nrf24_write_payload(nrf24_payload);

  /*********** WAIT FOR INCOMING PACKET **************************/
  uint32_t timeout_counter = 0;
  while(NRF_RADIO->EVENTS_CRCOK == 0) { /* wait for CRC OK flag  */
    if (++timeout_counter > 1000000) return;
  }
  NRF_RADIO->EVENTS_CRCOK = 0;          /* clear event flag      */

  /*********** PRINT DATA SENT ***********************************/

  Serial.print("TX: ");
  Serial.println((char*)&nrf24_payload);
  
  /*********** PRINT DATA RECEIVED *******************************/
  
  Serial.print("RX: ");
  Serial.println((char*)&nrf52_payload.pl);
  
}

Во втором передача ведется в обратном направлении -- nRF52832 передает, nRF24L01+ получает:

#define PL_SIZE 32

typedef struct {
  uint8_t len;
  uint8_t id;
  uint8_t pl[PL_SIZE + 1];
} esb_payload_t;

#define NRF24_PIN_CE     9
#define NRF24_PIN_CSN    SS

uint8_t spi_putc(uint8_t c) {
  NRF_SPI0->TXD = c;
  while (NRF_SPI0->EVENTS_READY == 0);
  NRF_SPI0->EVENTS_READY = 0;
  return NRF_SPI0->RXD;
}

uint8_t nrf24_read_register(uint8_t r) {
  digitalWrite(NRF24_PIN_CSN, LOW);
  /* 'R_REGISTER' command code = 0x00     */
  /* 'REGISTER_MASK' = 0x1F               */
  spi_putc(0x1F & r);
  uint res = spi_putc(0xFF);
  digitalWrite(NRF24_PIN_CSN, HIGH);
  return res;
}
 
void nrf24_write_register(uint8_t r, uint8_t d) {
  digitalWrite(NRF24_PIN_CSN, LOW);
  /* 'W_REGISTER' command code = 0x20     */
  /* 'REGISTER_MASK' = 0x1F               */
  spi_putc(0x20 | (0x1F & r));
  spi_putc(d);
  digitalWrite(NRF24_PIN_CSN, HIGH);
}

uint8_t nrf24_get_payload_size(void) {
  digitalWrite(NRF24_PIN_CSN, LOW);
  spi_putc(0x60); /* R_RX_PL_WID command code = 0x60    */
  uint8_t p_size = spi_putc(0xFF);
  digitalWrite(NRF24_PIN_CSN, HIGH);
  return p_size;
}

void nrf24_read_payload(uint8_t pl[], uint8_t len) {
  digitalWrite(NRF24_PIN_CSN, LOW);
  spi_putc(0x61); /* R_RX_PAYLOAD command code = 0x61 */
  for (uint8_t i = 0; i < len; i++) pl[i] = spi_putc(0xFF);
  digitalWrite(NRF24_PIN_CSN, HIGH);
}

static esb_payload_t nrf52_payload;
static uint8_t nrf24_payload[PL_SIZE + 1];

void setup() {

  /************* START CRYSTAL OSCILLATORS **********************************************************/
  
  if (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) {
    /* Start 32 MHz crystal oscillator */
    NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_HFCLKSTART    = 1;
    
    while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);     /* Wait for the external oscillator to start up */
  }

  if (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) {
    /* Start low frequency crystal oscillator */
    NRF_CLOCK->LFCLKSRC            = (CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos);
    NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_LFCLKSTART    = 1;
  
    while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0);
  }

  /************* SET RF PARAMETERS ******************************************************************/

  NRF_RADIO->MODE = RADIO_MODE_MODE_Nrf_2Mbit;    /* Data rate and modulation                       */

  /************* SET DATA PACKET PARAMETERS *********************************************************/

  NRF_RADIO->PCNF0 = (                            /* Packet configuration register 0                */
    (6 << RADIO_PCNF0_LFLEN_Pos)                     | /* 6 bits for LENGTH field                   */
    (3 << RADIO_PCNF0_S1LEN_Pos)                       /* 3 bits for PKT ID & NO_ACK field          */
  );

  NRF_RADIO->PCNF1 = (                            /* Packet configuration register 1                */
    (RADIO_PCNF1_ENDIAN_Big << RADIO_PCNF1_ENDIAN_Pos) | /* Most significant bit on air first       */
    (4 << RADIO_PCNF1_BALEN_Pos)                       | /* Base address length in number of bytes  */
    (32 << RADIO_PCNF1_MAXLEN_Pos)                       /* Max payload size in bytes               */
  );

  /************* SET ADDRESS ************************************************************************/

  NRF_RADIO->BASE0 = 0xE7E7E7E7;                /* Base address 0                                   */
  NRF_RADIO->PREFIX0 = 0xE7;                    /* Prefixes bytes for logical addresses 0           */
  NRF_RADIO->RXADDRESSES = 0x01;                /* Receive address select                           */

  /************* SET CRC PARAMETERS *****************************************************************/

  NRF_RADIO->CRCCNF = RADIO_CRCCNF_LEN_Two;     /* CRC configuration: 16bit                         */
  NRF_RADIO->CRCINIT = 0xFFFFUL;                /* CRC initial value                                */
  NRF_RADIO->CRCPOLY = 0x11021UL;               /* CRC poly: x^16+x^12^x^5+1                        */

  /************* SET DATA BUFFER ********************************************************************/

  NRF_RADIO->PACKETPTR = (uint32_t)&nrf52_payload; /* Set pointer to payload buffer                 */

  /************* START RADIO ************************************************************************/
  
  NRF_RADIO->TASKS_TXEN = 1;                    /* Enable RADIO in TX mode                          */

  /************* INITIALIZE GPIO ********************************************************************/

  digitalWrite(NRF24_PIN_CSN, HIGH);
  pinMode(NRF24_PIN_CSN, OUTPUT);

  digitalWrite(NRF24_PIN_CE, LOW);
  pinMode(NRF24_PIN_CE, OUTPUT);

  /************* INITIALIZE SPI *********************************************************************/

  NRF_SPI0->FREQUENCY = SPI_FREQUENCY_FREQUENCY_M8;  /* SPI frequency = 8MHz */

  NRF_SPI0->PSEL.MISO = g_ADigitalPinMap[MISO];
  NRF_SPI0->PSEL.MOSI = g_ADigitalPinMap[MOSI];
  NRF_SPI0->PSEL.SCK  = g_ADigitalPinMap[SCK];

  NRF_SPI0->ENABLE = 1;  /* Enable SPI0  */

  /************* CONFIGURE NRF24L01+ *****************************/

  /* Enable dynamic payload and W_TX_PAYLOAD_NOACK command       */
  /* 'FEATURE' register address = 0x1D  */
  nrf24_write_register(0x1D,
    1 * (1 << 2)     |     /* Dynamic Payload:     1=ON, 0=OFF   */
    0 * (1 << 1)     |     /* Payload with ACK:    1=ON, 0=OFF   */
    1 * (1 << 0)           /* W_TX_PAYLOAD_NOACK:  1=ON, 0=OFF   */
  );

  /* 'DYNPD' register address = 0x1C                             */ 
  nrf24_write_register(0x1C, 0x01); /* enable DYNPD on RX0       */
  
  /***** POWER NRF24L01's RECEIVER ON ****************************/
  
  /* 'CONFIG' register address = 0x00                            */
  nrf24_write_register(0x00,
    1 * (1 << 0)     |     /* RX/TX control:  1=PRX, 0=PTX       */
    1 * (1 << 1)     |     /* Power control1: 1=UP, 0=DOWN       */
    1 * (1 << 2)     |     /* CRC encoding:   0=8bit, 1=16bit    */
    1 * (1 << 3)     |     /* CRC control:    0=OFF, 1=ON        */
    1 * (1 << 4)     |     /* MAX_RT IRQ:     0=ON,  1=OFF       */
    1 * (1 << 5)     |     /* TS_DS  IRQ:     0=ON,  1=OFF       */
    1 * (1 << 6)           /* RX_DR  IRQ:     0=ON,  1=OFF       */
  );

  /***** RISE CE PIN HIGH TO BEGIN RECEIVING *********************/

  digitalWrite(NRF24_PIN_CE, HIGH);

  Serial.begin(9600);  /* Initialize serial console              */
  
  delay(5);
}

uint32_t pkt_cnt = 0, pkt_err = 0;

void loop() {
  uint8_t i, p_len;

  /*********** CLEAR RX & TX BUFFERS *****************************/

  for (i = 0; i < PL_SIZE + 1; i++) {
    nrf24_payload[i] = nrf52_payload.pl[i] = 0;
  }

  /*********** MAKE PAYLOAD **************************************/
  
  nrf52_payload.len = (rand() % (PL_SIZE / 2) + 1) * 2;
  for (i = 0; i < nrf52_payload.len; i++) {
    nrf52_payload.pl[i] = rand() & 1 ? rand() % 10 + '0' : rand() % 6 + 'A';
  }
  nrf52_payload.id = rand() & 0x06; /* rnadom pkt-id */

  /*********** SEND PAYLOAD **************************************/

  NRF_RADIO->TASKS_START = 1;   /* start transmission            */
  delay(3);                     /* wait for data transmitting    */

  /*********** READ THE DATA RECEIVED ****************************/

  p_len = nrf24_get_payload_size();
  nrf24_read_payload(nrf24_payload, p_len);

  /*********** COMPARE DATA SENT AND RECEIVED ********************/

  for (i = 0; i < nrf52_payload.len; i++) {
    if (nrf52_payload.pl[i] != nrf24_payload[i]) {
      pkt_err++;
      break;
    }
  }

  /*********** PRINT DATA SENT AND RECEIVED **********************/

  Serial.print("TX: ");
  Serial.println((char*)&nrf52_payload.pl);

  /* pad payload sting with saces */
  for (i = p_len; i < sizeof(nrf24_payload); i++) nrf24_payload[i] = ' ';

  Serial.print("RX: ");
  Serial.print((char*)&nrf24_payload);

  /*********** PRINT STATISTICS **********************************/

  Serial.print(" Pkt stat (err/total): ");
  Serial.print(pkt_err);
  Serial.print(" / ");
  Serial.println(++pkt_cnt);
}

Работоспособность обоих скетчей проверена. Результаты удовлетворительные.

a5021
Offline
Зарегистрирован: 07.07.2013

nRF52832 умеет замерять уровень принимаемого с эфира сигнала. Такие измерения можно делать в любое время и любое количество раз, при условии, что радио находится в режиме приема. Измерение запускается просто:

NRF_RADIO->TASKS_RSSISTART = 1;

и по времени занимает 0.25 микросекунды. По окончанию замера, из регистра NRF_RADIO->RSSISAMPLE можно забирать результат. Результат в регистре представлен без знака, хотя значение соответствует отрицательной величине. Т.е. если из регистра прочитано, к примеру, значение 86, то это означает, что сила измеряемого сигнала была равна -86dBm.

Измерения силы принимаемого сигнала можно автоматизировать через регистр сочетаний:

  NRF_RADIO->SHORTS |=  RADIO_SHORTS_ADDRESS_RSSISTART_Msk ;

Данное выражение предписывает блоку радио производить измерения всякий раз, когда наступает событие "ADDRESS MATCH", т.е. непосредственно в процессе приема адресованного данному приемнику пакета. Это довольно удобно.

b707
Offline
Зарегистрирован: 26.05.2017

a5021 пишет:

Работоспособность обоих скетчей проверена. Результаты удовлетворительные.

что с дальностью?

a5021
Offline
Зарегистрирован: 07.07.2013

Это не самый простой вопрос, чтобы ответить на него односложно. С дальностью у nRF52 просто обязано все быть лучше, чем у nRF24. Большая мощность и лучшая чувствительность очень к тому располагают.

У меня по жилищу время от времени летают разные байтики через nRF24. Раньше о качестве связи можно было судить лишь на уровне проходит/непроходит. Теперь можно добавить кой-какую визуализацию:

RX: 8115392A16098060  -79dBm
RX: 8115392A16098060  -79dBm
RX: 8115392A16098060  -79dBm
RX: 8315392A16  -80dBm
RX: 8315392A16  -80dBm
RX: 8315392A16  -80dBm
RX: 8515392A16  -80dBm
RX: 8515392A16  -79dBm
RX: 8515392A16  -79dBm
RX: 4715392A16  -78dBm
RX: 4715392A16  -78dBm
RX: 4715392A16  -78dBm
RX: 4915392A16  -77dBm
RX: 4915392A16  -77dBm
RX: 4915392A16  -77dBm
RX: 4B15392A16  -81dBm
RX: 4B15392A16  -81dBm
RX: 4B15392A16  -81dBm
RX: 4D15392A16  -80dBm
RX: 4D15392A16  -80dBm
RX: 4D15392A16  -81dBm
RX: 4F15392A16  -78dBm
RX: 4F15392A16  -78dBm
RX: 4F15392A16  -78dBm
RX: 4115392A16098060  -83dBm
RX: 4115392A16098060  -83dBm
RX: 4115392A16098060  -83dBm

Передатчик (NRF24, 0dBm, 2mbit) и приемник (NRF52) находятся в разных помещениях и общаются через ж/б стену. По прямой между ними где-то 3,5м, плюс предметы интерьера, которые связи тоже не улучшают. Сеансы представляют собой тройную отправку пачки байтов через каждую минуту. Мощность сигнала гуляет. Конкретно на этом куске не видно, но когда сигнал ослабевает до -88дбм и ниже, начинаются выпадения из троек.

Для интереса можно будет попробовать включить на прием и nRF24 в параллель. Посмотреть, кто больше потеряет. Есть, правда, ощущение предопределенности этого эксперимента.
 

ps. кусок кода, который рисовал вышеприведенный журнал:

char t_buf[40];

void loop() {
  while(NRF_RADIO->EVENTS_CRCOK == 0); /* wait for CRC OK flag               */
  NRF_RADIO->EVENTS_CRCOK = 0;         /* clear event flag                   */
  memset(t_buf, 0, sizeof(t_buf));     /* clear print buffer                 */
  for (uint8_t i = 0; i < rx_payload.len; i++) { /* for every received byte  */
    sprintf(&t_buf[i * 2], "%02X", rx_payload.pl[i]); /* print in HEX        */
  }
  Serial.print("RX: ");
  Serial.print(t_buf);
  Serial.print("  -");
  Serial.print(NRF_RADIO->RSSISAMPLE);
  Serial.println("dBm");
}

 

a5021
Offline
Зарегистрирован: 07.07.2013

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

uint8_t spi_putc(uint8_t c) {
  NRF_SPI0->TXD = c;
  while (NRF_SPI0->EVENTS_READY == 0);
  NRF_SPI0->EVENTS_READY = 0;
  return NRF_SPI0->RXD;
}

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

Разница в реализации SPI начинает проявляться, если у nRF52832 задействовать этот интерфейс в более продвинутом виде с использованием EasyDMA. EasyDMA здесь служит в качестве средства блоковой записи/чтения, когда данные поблочно выдаются в линию из буфера и так же поблочно считываются с линии в буфер.

Чтобы отличать простой режим от продвинутого, нордики один и тот же блок периферии обозвали по разному. Простой -- это SPI, а продвинутый -- SPIM или SPIS, в зависимости от того, мастер или слейв.

Инициализация периферийного блока SPIM во многом похожа на инициализацию SPI. Сначала распределяем ноги:

#define PIN_MAP(PIN)     g_ADigitalPinMap[PIN]

NRF_SPIM0->PSEL.SCK  = PIN_MAP(SCK);  /* Pin select for SCK         */
NRF_SPIM0->PSEL.MOSI = PIN_MAP(MOSI); /* Pin select for MOSI signal */
NRF_SPIM0->PSEL.MISO = PIN_MAP(MISO); /* Pin select for MISO signal */  

Скорость передачи:

NRF_SPIM0->FREQUENCY = SPIM_FREQUENCY_FREQUENCY_M8;  /* SPI frequency.       */

А вот дальше возникает "разница". Необходимо указать, где находятся буферы и какими объемами писать/читать.

  NRF_SPIM0->TXD.PTR = (uint32_t) &tx_buf; /* set pointer to TX buffer       */
  NRF_SPIM0->RXD.PTR = (uint32_t) &rx_buf; /* set pointer to TX buffer       */

  NRF_SPIM0->RXD.MAXCNT = 2; /* Maximum number of bytes in receive buffer    */  
  NRF_SPIM0->TXD.MAXCNT = 2; /* Maximum number of bytes in transmit buffer   */

В данном случае чтение и запись предполагается производить блоками по 2 байта. Это связано с тем, что у NRF24L01, на примере работы с которым я и рассматриваю здесь функционал SPIM, большинство обменов -- двухбайтовые -- сначала передается номер регистра, а потом считывается или записывается значение.

К вышеприведенному коду стоит добавить, что переменные tx_buf и rx_buf глобальные и имеют формат простого массива.

Последнее действие -- включить интерфейс:

NRF_SPIM0->ENABLE = SPIM_ENABLE_ENABLE_Enabled;      /* Enable SPIM  */

В этом месте я словил довольно увесистые подводные грабли, когда по легкомыслию записал в пробном коде

NRF_SPIM0->ENABLE = 1;      /* Enable SPIM  */

Лишь спустя довольно продолжительное время выяснилось, что единственное значение, которое можно записывать в NRF_SPIM0->ENABLE -- это число 7 и именно оно разрешает работу этого блока периферии. Все остальные значения работу запрещают.  Неожиданный ход разработчиков чипа.

Немного о том, как происходит обмен: первым делом прижимается к земле линия CSN, что сообщает модулю NRF24L01, что даные, которые сейчас будут переданы через SPI, адресованы ему. Первый байт -- это номер регистра и действие с ним (запись/чтение), второй байт -- значение или пустышка в случае чтения. Синхронно NRF24L01 передает в обратном направлении свои данные. Вот так это может быть изображено в коде:

  digitalWrite(SS, LOW);

  NRF_SPIM0->TASKS_START = 1;  /* Start SPI transaction                       */
  
  while(NRF_SPIM0->EVENTS_END == 0);
  NRF_SPIM0->EVENTS_END = 0;

  digitalWrite(SS, HIGH);

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

#define aprintf(...) for(char _b[100]; snprintf(_b, sizeof(_b), __VA_ARGS__), Serial.print(_b), 0;)

#define PIN_MAP(PIN)     g_ADigitalPinMap[PIN]
#define NRF24_PIN_CE     9

uint8_t tx_buf[2] = {0x00, 0xFF};
uint8_t rx_buf[2] = {0x00, 0x00};

void setup() {
  digitalWrite(NRF24_PIN_CE, LOW);
  pinMode(NRF24_PIN_CE, OUTPUT);  

  digitalWrite(SS, HIGH);
  pinMode(SS, OUTPUT);  

  NRF_SPIM0->PSEL.SCK  = PIN_MAP(SCK);       /* Pin select for SCK             */
  NRF_SPIM0->PSEL.MOSI = PIN_MAP(MOSI);      /* Pin select for MOSI signal     */
  NRF_SPIM0->PSEL.MISO = PIN_MAP(MISO);      /* Pin select for MISO signal     */  

  NRF_SPIM0->FREQUENCY = SPIM_FREQUENCY_FREQUENCY_M8;  /* SPI frequency.       */

  NRF_SPIM0->TXD.PTR = (uint32_t) &tx_buf;   /* set pointer to TX buffer       */
  NRF_SPIM0->RXD.PTR = (uint32_t) &rx_buf;   /* set pointer to TX buffer       */

  NRF_SPIM0->RXD.MAXCNT = 2;   /* Maximum number of bytes in receive buffer    */  
  NRF_SPIM0->TXD.MAXCNT = 2;   /* Maximum number of bytes in transmit buffer   */

  NRF_SPIM0->ENABLE = SPIM_ENABLE_ENABLE_Enabled;      /* Enable SPIM          */

  Serial.begin(9600);
}

void loop() {

  digitalWrite(SS, LOW);

  NRF_SPIM0->TASKS_START = 1;                /* Start SPI transaction          */
  
  while(NRF_SPIM0->EVENTS_END == 0);
  NRF_SPIM0->EVENTS_END = 0;

  digitalWrite(SS, HIGH);

  aprintf("R%02X = 0x%02X\n\r", tx_buf[0], rx_buf[1]);

  if(++tx_buf[0] > 0x1D) { 
    aprintf("=================\n\r");
    tx_buf[0] = 0;
    delay(5000);
  }
}

 

a5021
Offline
Зарегистрирован: 07.07.2013

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

Как уже говорилось ранее, передачу данных через SPI предваряет понижение уровня на выводе CSN, а после того, как передача завершена, уровень этот нужно вернуть обратно в высокое состояние. Делать это приходится в коде всякий раз, когда случается передача:

digitalWrite(SS, LOW);

  // sending data over SPI

digitalWrite(SS, HIGH);

И вот этот digitalWrite лично мне как-то действует на нервы.  Хочется предпринять что-то, чтобы пин CSN автоматически понижался перед началом передачи и возвращался назад после ее окончания. Помнится пытался я придумать какую-нибудь реализацию этого механизма на STM32, но там мне не удалось выполнить это изящно. Зато удалось теперь на nRF52832 и вот каким образом:

1) У nRF52832 есть такой блок периферии, как GPIOTE. По сути, весь блок -- это надстройка над единственным пином, чтобы сделать этот пин событийно-управляемым, плюс умеющим самостоятельно генерировать события.

Событийно-управляемым -- это когда произошедшее в каком либо блоке периферии событие оказывает непосредственное влияние на состояние пина, причем, без участия ядра (ЦП). Закончился период преобразования АЦП, пин автоматически меняет полярность. Досчитал таймер до  нужного значения, пин перещелкнулся. И тому подобное. Но здесь есть одно но: GPIOTE пином управлять может, но узнавать о событиях, произошедших где-то вне зоны его отвественности, нет. Для того, чтобы транслировать информацию о событиях между разными блоками и нужен п.2:

2) События между разными блоками периферии передаются через PPI -- Programmable peripheral interconnect -- матрицу коммутации периферийных устройств. Если мы хотим, чтобы какое-либо событие в одном блоке вызывало реакцию в другом, надо через элемент PPI соединить выход первого со входом второго. PPI здесь, если говорить условно, это что-то навроде веревки, за которую надо дергать или провод по которому бежит электрический сигнал.

Теперь собираем 1) и 2) вместе. Сначала инициализируем GPIOTE.

  NRF_GPIOTE->CONFIG[0] = (
    (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos)       |
    ((g_ADigitalPinMap[SS]) << GPIOTE_CONFIG_PSEL_Pos)        |
    (GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos)
  );

Строка 1 указывает, что используется регистр конфигурации элемена 0 блока GPIOTE. Всего таких элементов может быть 8.

Вторая строка предписывает, что пин работает, как выход. "Task" -- это исполнитель заданий, по задумке нордиков -- меняет состояние выхода по требованию со стороны. Бывает еще "Event" -- генератор событий в зависимости от состояния входа -- остылает требования другим блокам.

В строке 3 указывается, каким именно пином данный элемент GPIOTE управляет.

Строка 4 определяет, в каком состоянии должен пин находится изначально.

Стоит отметить, что с момента, когда пин "отдан" GPIOTE, обычным образом использовать его уже более невозможно.

У любого элемента блока GPIOTE есть несколько "входов", дергая за которые можно менять состояние пина. Выполнив такое присваивание:

NRF_GPIOTE->TASKS_CLR[0] = 1;

пин будет переведен в низкое состояние, а при таком:

NRF_GPIOTE->TASKS_SET[0] = 1;

обратно в высокое.

Но дрыгание пином посредством манипуляций в коде -- это не совсем то, что в данном случае интересует. Нужно, чтобы пин дрыгался сам, исключительно от событий внути переиферии. Для этого нужно проложить связи через матрицу коммутаций PPI.  В рамках этого примера нужно связать событие в блоке SPIM под названием "EVENTS_STARTED" с заданием элементу блока GPIOTE перевести пин в низкое состояние. В коде это будет выглядеть так:

  NRF_PPI->CH[0].EEP = (uint32_t)&NRF_SPIM0->EVENTS_STARTED;
  NRF_PPI->CH[0].TEP = (uint32_t)&NRF_GPIOTE->TASKS_CLR[0];

После выполнения данного кода, всякий раз, когда будет отдаваться команда начать передачу по SPI (NRF_SPIM0->TASKS_START = 1;), пин CSN будет уходить вниз автоматически. Остается сделать то же самое,  в обратном порядке, для завершения передачи. Блок SPIM, когда выполнил все задания, генерирует событие  "EVENTS_END", которое и нужно "связать" с переводом CSN в высокое положение:

  NRF_PPI->CH[1].EEP = (uint32_t)&NRF_SPIM0->EVENTS_END;
  NRF_PPI->CH[1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[0];

Последним штрихом, чтобы это все заработало, должно быть разрешение на работу каналов 0 и 1 PPI:

  NRF_PPI->CHEN = PPI_CHEN_CH0_Msk | PPI_CHEN_CH1_Msk;

Наконец, пример, не использующий digitalWrite() для дерганья за CSN целиком:

#define aprintf(...) for(char _b[100]; snprintf(_b, sizeof(_b), __VA_ARGS__), Serial.print(_b), 0;)

#define PIN_MAP(PIN)     g_ADigitalPinMap[PIN]

uint8_t tx_buf[2] = {0x00, 0xFF};
uint8_t rx_buf[2] = {0x00, 0x00};

void setup() {

  NRF_GPIOTE->CONFIG[0] = (
    (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos)           |
    ((g_ADigitalPinMap[SS]) << GPIOTE_CONFIG_PSEL_Pos) |
    (GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos)
  );

  NRF_PPI->CH[0].EEP = (uint32_t)&NRF_SPIM0->EVENTS_STARTED;
  NRF_PPI->CH[0].TEP = (uint32_t)&NRF_GPIOTE->TASKS_CLR[0];
  
  NRF_PPI->CH[1].EEP = (uint32_t)&NRF_SPIM0->EVENTS_END;
  NRF_PPI->CH[1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[0]; 

  NRF_PPI->CHEN = PPI_CHEN_CH0_Msk | PPI_CHEN_CH1_Msk;

  NRF_SPIM0->PSEL.SCK  = PIN_MAP(SCK);       /* Pin select for SCK             */
  NRF_SPIM0->PSEL.MOSI = PIN_MAP(MOSI);      /* Pin select for MOSI signal     */
  NRF_SPIM0->PSEL.MISO = PIN_MAP(MISO);      /* Pin select for MISO signal     */  

  NRF_SPIM0->FREQUENCY = SPIM_FREQUENCY_FREQUENCY_M8;  /* SPI frequency.       */

  NRF_SPIM0->TXD.PTR = (uint32_t) &tx_buf;   /* set pointer to TX buffer       */
  NRF_SPIM0->RXD.PTR = (uint32_t) &rx_buf;   /* set pointer to TX buffer       */

  NRF_SPIM0->RXD.MAXCNT = 2;   /* Maximum number of bytes in receive buffer    */  
  NRF_SPIM0->TXD.MAXCNT = 2;   /* Maximum number of bytes in transmit buffer   */

  NRF_SPIM0->ENABLE = SPIM_ENABLE_ENABLE_Enabled;      /* Enable SPIM          */

  Serial.begin(9600);
}

void loop() {

  NRF_SPIM0->TASKS_START = 1;                /* Start SPI transaction          */
  
  while(NRF_SPIM0->EVENTS_END == 0);
  NRF_SPIM0->EVENTS_END = 0;

  aprintf("R%02X = 0x%02X\r\n", tx_buf[0], rx_buf[1]);

  if(++tx_buf[0] > 0x1D) { 
    aprintf("=================\n\r");
    tx_buf[0] = 0;
    delay(5000);
  }
}