Радиомодуль nRF24L01+ быстрый старт.

Попался ко мне в руки вот такой радио модуль nRF24L01+, вот отсюда.


Довольно таки известный радио модуль. Вот решил попробовать сие изобретение человечества. Цель статьи это быстрый старт. Скажем передача байта от одного радио модуля к другому и обратно. Пихаем байт в USART, из USART он попадает уже в SPI радио модуля и далее в пространство. Это все. Никаких ненужных лишних подробностей, которые не касаются быстрого старта.

nRF24L01+ работает по SPI интерфейсу. Это значит что нужна какая нибудь платка с микроконтроллером, на борту которого имеется SPI интерфейс. Такую платку можно сделать самому или нарыть готовую. Я пошел по второму пути. И нарыл вот такую


Готовая такая платка для работы с nRF24L01+. Ничего лишнего. Описание и схема платы есть на сайте. Плата разработана на базе любимыми многими МК Atmega8. На плате выведены ноги МК RESET, SCK, MISO, MOSI. Это значит, что в плату можно залить свою прошивку, удалив существующую. Что я и решил сделать. Та прошивка которая есть в плате, позволяет по USART отправлять байты другому радио модулю. Удобно однако. Это значит можно вообще не думать про сам радио модуль. Просто отправляй нужные тебе байты, а с другой стороны принимай. Вот и все, быстро и удобно. Кому не охота разбираться с самим радио модулем само то. Но в таком случае разобраться как работает сам радио модуль нам не светит. Поэтому будем грызть железку сами.

Вот схема платки.


Как видим все просто. Кварц выбран 7.3728, это чтоб минимизировать при передаче байтов через USART количество ошибок. Селектор скорости, для выбора необходимой скорости USART. Три светодиода для отладки. Есть еще четыре свободных пина РС2, РС3, РС4, РС5. На них можно повесить кнопки какие или еще чего. Ну это уж, что пользователь решит.

Для начала инициализируем ноги Atmega8 в соответствии со схемой.

/*НАСТРОЙКА ПОРТОВ*/
#if 1
      DDRB =  0b11101111;	//если 0 значит вход
      PORTB = 0b00010100;	//если 1 подтяжка включена

      DDRC =  0b00001110;	
      PORTC = 0b11110001;	

      DDRD =  0b10000000;
      PORTD = 0b01111111;       
#endif

Начнем пожалуй с интерфейса SPI. В Atmega8 для его настройки существует всего два регистра SPCR и SPSR. В регистре SPCR битом SPE — включим интерфейс SPI. А битом MSTR — укажем что наш МК будет мастером, то есть обмен данными с радио модулем будем всегда начинать мы (других вариантов и нету). В регистре SPSR битом SPI2X — ускорим скорость SPI до максимума. Ну это я так сделал чисто для теста. Этого можно не делать. На этом настройка SPI закончена.

/*Настройка SPI*/				  
#if 1
	
SPCR = (0<<SPIE)|(1<<SPE)|(0<<DORD)|(1<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(0<<SPR0);
	
  /*    SPIE - разрешение прерывания от SPI, 1-ON,0-OFF
	SPE  - вкл/выкл SPI,                 1-ON,0-OFF
	DORD - порядок передачи данных.      1-байт передается с младшего бита, 0->7
			                     0-байт передается со старшего бита.7->0	 		   
        MSTR - Выбор режима (Master/Slave)   1-Master, 0-Slave
        CPOL - полярность тактового сигнала  0-импульсы положительной полярности
				             1-импульсы отрицательной полярности
        CPHA - Фаза тактового сигнала        0-биты считываются по переднему фронту ноги SCK
				             1-биты считываются по заднему фронту ноги SCK
        SPR1 - Скорость передачи
        SPR0 - Скорость передачи
	
	SPI2X  SPR1  SPR0   Частота SCK
	  0     0     0		  F_CPU/4
	  0     0     1		  F_CPU/16
	  0     1     0		  F_CPU/64
	  0     1     1		  F_CPU/128
	  1     0     0		  F_CPU/2
	  1     0     1		  F_CPU/8
	  1     1     0		  F_CPU/32
	  1     1     1		  F_CPU/64
	*/
			          
SPSR = (0<<SPIF)|(0<<WCOL)|(1<<SPI2X);

  /*    SPIF  - Флаг прерывания от SPI. уст в 1 по окончании передачи байта
	WCOL  - Флаг конфликта записи. Уст в 1 если байт не передан, а уже попытка записать новый.
	SPI2X - Удвоение скорости обмена.*/
#endif

Теперь надо бы настроить USART. Там малек посложнее будет. Но тоже ничего, справиться можно.

/*Настройка USART*/
					  
#if	1	    
	
uint8_t a=(PIND>>2) & 0b00000111;//это мы считали состояние нашего селектора скорости и в следующем блоке кода
                                 //установим скорость USART.
switch (a)
{
	case 0:UBRRL=3;//115200  000
	break;
	case 1:UBRRL=5;//76800   001
	break;
	case 2:UBRRL=7;//57600   010
	break;
	case 3:UBRRL=11;//38400  011
	break;
	case 4:UBRRL=15;//28800  100
	break;
	case 5:UBRRL=23;//19200  101
	break;
	case 6:UBRRL=31;//14400  110
	break;
	case 7:UBRRL=47;//9600   111
	break;
	
	default:UBRRL=47;
			break;
}	
			  
UCSRA = (0<<RXC)|(0<<TXC)|(0<<UDRE)|(0<<FE)|(0<<DOR)|(0<<PE)|(0<<U2X)|(0<<MPCM);
  /*       RXC -	Флаг завершения приема. Флаг устанавливается в 1 при наличии  
			непрочитанных данных в буфере приемника (регистр данных UDR). Сбрасывается 
			флаг аппаратно после опустошения буфера. Если бит RXCIE0  
			регистра UCSR0B установлен, то при установке флага генерируется 
			запрос на прерывание «прием завершен» 
	   TXC -	Флаг завершения передачи. Флаг устанавливается в 1 после передачи всех  
			битов посылки из сдвигового регистра передатчика при условии, что в регистр 
			данных UDR не было загружено новое значение. Если бит TXCIE0 
			регистра UCSR0B установлен, то при установке флага  
			генерируется прерывание «передача завершена». Флаг сбрасывается аппаратно при  
			выполнении подпрограммы обработки прерывания или программно, записью 
			в него лог. 1 
	   UDRE -       Флаг опустошения регистра данных. Данный флаг устанавливается в 1 
			при пустом буфере передатчика (после пересылки байта из регистра данных 
			UDR в сдвиговый регистр передатчика). Установленный флаг означает, 
			что в регистр данных можно загружать новое значение. Если бит UDRIE 
			регистра UCSR0B установлен, генерируется запрос на прерывание «регистр 
			данных пуст». Флаг сбрасывается аппаратно, при записи в регистр данных 
	    FE -	Флаг ошибки кадрирования. Флаг устанавливается в 1 при обнаружении 
			ошибки кадрирования, т. е. если первый стоп-бит принятой посылки равен 0. 
			Флаг сбрасывается при приеме стоп-бита, равного 1 
	    DOR -       Флаг переполнения. Флаг устанавливается в 1, если в момент обнаружения 
			нового старт-бита в сдвиговом регистре приемника находится последнее 
			принятое слово, а буфер приемника полон (содержит два байта). Флаг  
			сбрасывается при пересылке принятых данных из сдвигового регистра  
			приемника в буфер. 
	    UPE -       Флаг ошибки контроля четности. Флаг устанавливается в 1, если в данных, 
			находящихся в буфере приемника, выявлена ошибка контроля четности. 
			При отключенном контроле четности этот бит постоянно сброшен в 0 
	    U2X -       Удвоение скорости обмена. Если этот бит установлен в 1, то коэффициент 
			деления предделителя контроллера скорости передачи уменьшается с 16 до 
			8, удваивая тем самым скорость асинхронного обмена по  последовательному
			каналу. Этот бит используется только при асинхронном режиме работы 
			и в синхронном режиме должен быть сброшен 
	    MPCM -      Режим мультипроцессорного обмена. Если этот бит установлен в 1, ведомый 
			микроконтроллер ожидает приема кадра, содержащего адрес. Кадры, 
			не содержащие адреса устройства, игнорируются */

    UCSRB = (1<<RXCIE)|(0<<TXCIE)|(0<<UDRIE)|(1<<RXEN)|(1<<TXEN)|(0<<UCSZ2)|(0<<RXB8)|(0<<TXB8);
  /*     RXCIE - Разрешение прерывания по завершении приема. Если данный бит установлен 
			 в 1, то при установке флага RXC0 регистра UCSR0A  генерируется прерывание 
			 «прием завершен» (если флаг I регистра SREG установлен в1) 

	 TXCIE - Разрешение прерывания по завершении передачи. Если данный бит установлен 
			 в 1, то при установке флага TXC регистра UCSRA генерируется прерывание 
			 «передача завершена» (если флаг 1 регистра SREG  установлен в 1) 

	  UDRIE - Разрешение прерывания при очистке регистра данных UART. Если данный бит 
			 установлен в 1, то при установке флага UDRE  регистра UCSRA 
			 генерируется прерывание «регистр данных пуст» (если флаг I  
			 регистра SREG установлен в 1) 

	   RXEN  - Разрешение приема. При установке этого бита в 1 разрешается работа  
			 приемника USART и переопределяется функционирование вывода RXD 
			 При сбросе бита RXEN0 работа приемника запрещается, а его буфер 
			 сбрасывается. Значения флагов ТХС0, DOR0 и FE0 при 
			 этом становятся недействительными 

	   TXEN  - Разрешение передачи. При установке этого бита в 1 разрешается работа  
			 передатчика UART и переопределяется функционирование вывода TXD. 
			 Если бит сбрасывается в 0 во время передачи, то выключение передатчика 
			 произойдет только после завершения передачи данных, находящихся в  
			 сдвиговом регистре и буфере передатчика 

	   UCSZ2 - Формат посылок. Этот бит совместно с битами UCSZ01:0 регистра UCSR0C 
			 используется для задания размера слов данных, передаваемых по 
			 последовательному каналу. 

	   RXB8  - 8-й бит принимаемых данных. При использовании 9-битных слов данных этот 
             бит содержит значение старшего бита принятого слова. Содержимое этого 
			 бита должно быть считано до прочтения регистра данных UDR0. 

	   TXB8  - 8-й бит передаваемых данных. При использовании 9-битных слов данных  
			 содержимое этого бита является старшим битом передаваемого слова.  
			 Требуемое значение должно быть занесено в этот бит до загрузки байта данных в  
			 регистр UDR0 */

   UCSRC = (1<<URSEL)|(0<<UMSEL)|(0<<UPM1)|(0<<UPM0)|(0<<USBS)|(1<<UCSZ1)|(1<<UCSZ0)|(0<<UCPOL);

/*
Режим работы USART
-----------------------------------  
 UMSEL  Режим работы USART
-----------------------------------
   0     Асинхронный USART
   1     Синхронный USART
-----------------------------------

Режим работы схемы контроля и формирования бита четности. 
-----------------------------------
UPM1	UPM0	Режим работы схемы
-----------------------------------
   0       0    Disabled
   0       1    Reserved
   1       0    Включена, проверка на четность  Even Parity
   1       1    Включена, проверка на нечетность  Odd Parity
-----------------------------------

Количество стоповых битов
-----------------------------------
USBS  Stop Bit(s)
 0      1-bit
 1      2-bit
-----------------------------------

Определение размера слова данных 
-----------------------------------
UCSZ2 UCSZ1 UCSZ0  Размер слова данных
-----------------------------------
   0      0      0    5-bit
   0      0      1    6-bit
   0      1      0    7-bit
   0      1      1    8-bit
   1      0      0    Reserved
   1      0      1    Reserved
   1      1      0    Reserved
   1      1      1    9-bit
-----------------------------------


UCPOL - Полярность тактового сигнала. Значение этого бита определяет момент  
		 выдачи и считывания данных на выводах модуля. Бит используется только при 
		 работе в синхронном режиме. При работе в асинхронном режиме он должен 
		 быть сброшен в 0. 
-------------------------------------------------------------------
UCPOL  Выдача данных на вывод TXD  Считывание данных с вывода RXD
-------------------------------------------------------------------
	0    Спадающий фронт ХСК         Нарастающий фронт ХСК 
	1    Нарастающий фронт ХСК       Спадающий фронт ХСК
-------------------------------------------------------------------	
Обратите внимание на то, что в моделях ATmega8515x/8535x, 
ATmega8x/16x/32x и ATmegal62x регистр UBRRH размещается по тому же 
адресу, что и регистр управления UCSRC. Поэтому при обращении по 
этим адресам необходимо выполнить ряд дополнительных действий для 
выбора конкретного регистра. 
При записи регистр определяется состоянием старшего бита  
записываемого значения URSEL. Если этот бит сброшен в 0, изменяется  
содержимое регистра UBRRH. Если же старший бит значения установлен в 1,  
изменяется содержимое регистра управления UCSRC. */
#endif

Как работает USART разбираться не будем, (уже разобрано не один раз) ибо тема сейчас другая. Этот кусок кода можно скопировать, и при желании разобраться потом.

Вот как бы с настройкой МК все. Теперь надо бы слегка вникнуть в сам радио модуль. В чипе nRF24L01+ есть память ОЗУ. То есть набор байтов. В эти байты надо в нужные места воткнуть нули или единицы. Ну точно так же как мы это сделали с Atmega8 выше. Воткнув их радио модуль будет готов к работе. Для начала ознакомимся с некоторыми регистрами радио модуля.

Регистр STATUS — самый популярный и часто используемый регистр. В нем находится информация что происходит с нашим радио модулем. Ну значит определим его и его биты для использования в нашей программе. То что не прокомментировано, значит это для быстрого старта пока не надо.

/*регистр STATUS*/
	#define STATUS		0x07
	#define RX_DR		6 /*прерывание: данные получены. Для сброса записать 1.*/
	#define TX_DS		5 /*прерывание: данные переданы. Для сброса записать 1.*/
	#define MAX_RT		4 /*прерывание: данные не переданы. Для сброса записать 1.*/
	#define RX_P_NO2	3
	#define RX_P_NO1	2
	#define RX_P_NO0	1
	#define TX_FULL0	0 /*флаг переполнения TX FIFO буфера передачи. 1-переполнен, 0-есть еще место.*/

Как видим адрес регистра STATUS 0х07. Всего у нас регистров управления nRf24L01 аж 29 штук. Следующий по нужности регистр это регистр CONFIG. Определим и его.

/*регистр CONFIG*/    //Конфигурационный регистр
	#define CONFIG		0x00
	#define MASK_RX_DR  6 //вкл/откл прерывание от бита RX_DR в рег. STATUS. 0-вкл, 1-выкл.
	#define MASK_TX_DS  5 //вкл/откл прерывание от бита TX_DS в рег. STATUS. 0-вкл, 1-выкл.
	#define MASK_MAX_RT 4 //вкл/откл прерывание от бита MAX_RT в рег. STATUS. 0-вкл, 1-выкл.
	#define EN_CRC      3 //включение CRC. По умолчанию вкл. если один из битов регистра EN_AA включен.
	#define CRCO        2 //режим CRC. 0-1 байт, 1-2 байта.
	#define PWR_UP      1 //1-POWER UP, 0-POWER DOWN, по умолчанию 0.
	#define PRIM_RX     0 //0-режим передачи, 1-режим приема.
Он идет самым первым в адресной карте регистров.

Ну и еще один регистр это регистр RX_PW_P0.

#define RX_PW_P0	0x11//указываем в нем из скольких байтов будет состоять наше поле данных для отправки.
Ну как бы для быстрого старта достаточно этих трех регистров.

Сейчас надо разобраться как считать данные из регистра и записать новые данные. Для этого есть команды. Всего команд в нашем радио модуле 12. Вот они.

#if 1 //Описание команд
	#define R_REGISTER			0x00 //читаем регистр
	#define W_REGISTER			0x20 //пишем в регистр
	#define R_RX_PAYLOAD		        0x61 //считывание из буфера принятых данных из космоса
	#define W_TX_PAYLOAD		        0xA0 //запись данных в буфер для отправки в космос
	#define FLUSH_TX			0xE1 //очистка буфера отправки
	#define FLUSH_RX			0xE2 //очистка буфера приема
	#define REUSE_TX_PL			0xE3
	#define ACTIVATE			0x50 
	#define R_RX_PL_WID			0x60
	#define W_ACK_PAYLOAD		        0xA8
	#define W_TX_PAYLOAD_NOACK	        0x58
	#define NOP				0xFF //команда заглушка, ничего не делает.
#endif
Мне пригодились только первые четыре команды и последняя NOP. Это все. Теперь надо бы написать пару функций для работы с радио модулем.

Но перед этим еще определим пару макросов для удобной и наглядной работы с битами.

#define Bit(bit)  (1<<(bit))

#define ClearBit(reg, bit)       reg &= (~(1<<(bit)))
//пример: ClearBit(PORTB, 1); //сбросить 1-й бит PORTB

#define SetBit(reg, bit)          reg |= (1<<(bit))

#define BitIsClear(reg, bit)    ((reg & (1<<(bit))) == 0)
//пример: if (BitIsClear(PORTB,1)) {...} //если бит очищен

#define BitIsSet(reg, bit)       ((reg & (1<<(bit))) != 0)
//пример: if(BitIsSet(PORTB,2)) {...} //если бит установлен

#define InvBit(reg, bit)	  reg ^= (1<<(bit))
//пример: InvBit(PORTB, 1); //инвертировать 1-й бит PORTB
Да кстати надо еще пояснить, что означают ноги радио модуля.

IRQ — выход прерывания. Когда все пучком, то нога находится прижатой к питанию (высокий уровень). Но как только что нибудь произошло, например их космоса/в космос поймали/отправили байт, или байт не отправился, то этот пин сразу падает на землю. Как тока он упал, надо все бросить и считать регистр STATUS. И в этом регистре уже конкретно видно что произошло. Это видно из трех битов-флагов — RX_DR, TX_DS, MAX_RT. Что они означают выше прокомментировано. Как только стало ясно какие биты равны значению 1, их тут же надо сбросить, записью 1. Иначе нога IRQ всегда будет лежать на земле. Причем сбросить надо только те биты в которых значение 1. Те в которых 0 писать ничего ненадо, ну 0 можно записать, но не 1. Иначе IRQ опять упадет на землю.

CE — вход. Если модуль в режиме приема, то СЕ прижимаем к питанию. Если режим передачи, то СЕ лежит на земле. И когда надо передать байт, СЕ поднимаем к питанию не менее чем на 10 мксек, потом снова прижимаем к земле. Байт улетел в пространство.

CSN — вход. Всегда находится прижатым к питанию. Если надо что то записать/считать в радио модуль, то перед началом любых телодвижений прижимаем к земле. Записали/считали — прижимаем снова к питанию.

CSK, MOSI, MISO — пины SPI интерфейса. Подключаются к одноименным выводам МК. MISO определяем на вход, MOSI, CSK на выход. Все это делаем ручками, так как при включения интерфейса SPI эти выводы сами по себе не переопределяются по назначению.

Ну и как же считать значение регистра STATUS?
1.Прижимаем вывод CSN(SS) МК к земле, тем самым сообщаем о начале обмена данных.
ClearBit(PORTB,CSN);
2.Пишем в регистр отправки данных SPI что нибудь, пусть это будет NOP.
SPDR=NOP;
3.Ждем когда байт улетит. Когда он улетит то бит SPIF станет 1.
while(BitIsClear(SPSR,SPIF));
Бит SPIF сам сбросится аппаратно если разрешено прерывание «байт отправлен». Но прерывание у нас запрещено. Поэтому SPIF можно сбросить вторым способом. Надо записать/считать байт отправки/передачи данных SPDR. Байт отправки/передачи данных SPDR это на самом деле два разных байта находящихся под разными адресами, но имя одинаковое. Тоже самое, что и байт UDR в USART.
4.Если байт NOP улетел то прижимаем SCN к питанию. Обмен завершен.
SetBit(PORTB,CSN);
5.Считываем принятый байт из SPDR.
uint8_t a=SPDR;
Обмен данными завершен.

Вывод: всегда 1й вывалившийся байт из SPI радиомодуля это будет значение байта STATUS. Что бы мы туда не писали это будет железно значение STATUS. Не всегда это надо, если не надо то этот байт можно игнорировать. Итак функция чтения регистра статус может иметь такой вид.

uint8_t r_register(uint8_t a)//чтение байта из озу. a-адрес считываемого байта.
{
	ClearBit(PORTB,CSN);//Прижимаем вывод CSN(SS) МК к земле, тем самым сообщаем о начале обмена данных.
	SPDR=a;//пишем в SPDR что нибудь.
	while(BitIsClear(SPSR,SPIF));//ожидаем когда освободится SPI.
	SetBit(PORTB,CSN);//Вывод CSN(SS) МК к питанию, обмен данных завершен.
	return SPDR;
}//uint8_t a=r_register(STATUS);
Как же считать значение регистра CONFIG или любого другого?
Отправляем в радиомодуль сперва адрес регистра который хотим считать, а потом отправляем например NOP. Считываем из SPDR наше значение считываемого регистра.
Приведенную функцию для чтения STATUS можно причесать и сделать универсальной для чтения всех регистров.

uint8_t r_register(uint8_t a)//чтение байта из озу. a-адрес байта
{
	ClearBit(PORTB,CSN);//Прижимаем вывод CSN(SS) МК к земле, тем самым сообщаем о начале обмена данных.
	SPDR=a;
	while(BitIsClear(SPSR,SPIF));//ожидаем когда освободится SPI для последующей записи байта
	if (a==STATUS)
	{
		SetBit(PORTB,CSN);//Вывод CSN(SS) МК к питанию, обмен данных завершен.
		return SPDR;
	}
	SPDR=NOP;
	while(BitIsClear(SPSR,SPIF));
	SetBit(PORTB,CSN);//Вывод CSN(SS) МК к питанию, обмен данных завершен.
	return SPDR;
}//uint8_t a=r_register(CONFIG);
Как записать что нибудь в регистр CONFIG?
Для записи данных в регистры, есть команда W_REGISTER 0x20. Берем значение 0х20 и накладываем маску с адресом регистра в который хотим записать что нибудь и отправляем в SPI. Потом отправляем в SPI уже то значение которое хотим записать в регистр. Все. Функция записи может иметь такой вид.

void w_register(uint8_t a,uint8_t b)//а-адрес регистра, b-что пишем в регистр.
{
	a=a | W_REGISTER;//накладываем маску
	ClearBit(PORTB,CSN);
	SPDR=a;
	while(BitIsClear(SPSR,SPIF));
	SPDR=b;
	while(BitIsClear(SPSR,SPIF));
	a=SPDR;//это для сброса флага SPIF
	SetBit(PORTB,CSN);
}//W_REGISTER (CONFIG,0b00000110);
Как сбросить флаги в регистре STATUS?
Считываем сначала значение регистра STATUS, а потом это же значение туда записываем.

uint8_t b= r_register(STATUS);//прочитали статус регистр
w_register(STATUS, b);//сброс флагов прерываний - обязательно
Как перевести модуль на прием данных?
1.Нужно в регистре CONFIG выставить бит PWR_UP в 1.
2.Ждем не менее 1.5 мсек
3.Выставляем в CONFIG бит PRIM_RX в 1.
4.Ногу CE поднимаем к питанию.
5.Ждем не менее 135 мксек.

Как раз таки на этих 135 мксек я и погорел. После смены значение бита PRIM_RX радио модуль находится в неопределенном состоянии в течении 135 мксек. В этот момент в него нельзя ничего писать. Все равно не поймет. Так что эта выдержка обязательна. Кстати шаг 1 и 2 делается один раз в начале инициализации модуля, а потом только меняем значение бита PRIM_RX и ноги СЕ.

w_register(CONFIG,(1<<PWR_UP)|(1<<EN_CRC)|(0<<PRIM_RX));
_delay_ms(2);
w_register(CONFIG,(1<<PWR_UP)|(1<<EN_CRC)|(1<<PRIM_RX));
SetBit(PORTC,CE);
_delay_us(135);
//режим према RX
А так переводим на передачу данных
ClearBit(PORTC,CE);
w_register(CONFIG,(1<<PWR_UP)|(1<<EN_CRC)|(0<<PRIM_RX));
_delay_us(135);
Перед всеми манипуляциями с модулем один раз в начале в момент инициализации надо в регистре RX_PW_P0 указать чему будет равен размер поля данных. В нашем случае будем стрелять по одному байту. Поэтому поле будет равно 1 байту. Максимум можно до 32 байт.

w_register(RX_PW_P0,1);//размер поля данных 1 байт.
Конкретно как отправить 1 байт в пространство?
Замутим для начала две функции, настройка модуля на прием и передачу.

void prx(void)//Настроим nRF на прием.
{
	w_register(CONFIG,(1<<PWR_UP)|(1<<EN_CRC)|(1<<PRIM_RX));
	SetBit(PORTC,CE);
	_delay_us(135);
	//режим према RX
}
void ptx(void)//Настроим nRF на передачу и сразу же отправим байт в пространство.
{
	ClearBit(PORTC,CE);
	w_register(CONFIG,(1<<PWR_UP)|(1<<EN_CRC)|(0<<PRIM_RX));
	SetBit(PORTC,CE);
	_delay_us(15);
	ClearBit(PORTC,CE);
	_delay_us(135);
}
А теперь функцию отправки байта.

void send_byte(uint8_t a)//отправка байта.
{
	w_register(W_TX_PAYLOAD,a);//запись байта в буфер TX для отправки
	ptx();//передача байта
	while (BitIsSet(PINC,IRQ));//Ждем пока байт не передан
	uint8_t b= r_register(STATUS);//прочитали статус регистр
	w_register(STATUS, b);//сброс флагов прерываний - обязательно
	prx();//на прием
}
Получается что сперва надо:
1. Положить отправляемый байт в буфер отправки данных w_register(W_TX_PAYLOAD,a);
2. Функцией ptx(); отправили байт в космос.
3. Подождали когда байт улетел.
4. Сбросили флаги прерываний.
5. Настроили модуль на прием.
Ну вот и все.

В обычном режиме модуль все время находится на приеме данных. А что делать если вдруг модуль поймал байт от другого модуля? В главном цикле можно замутить опрос флагов прерываний. И анализируя эти флаги можно выяснить, принят ли байт.

if (BitIsClear(PINC,IRQ))//если нога IRQ упала, значит надо выяснить, может принят байт?
	{
		uint8_t a= r_register(STATUS);//прочитали статус регистр
		w_register(STATUS, a);//сброс флагов прерываний - обязательно
		if (BitIsSet(a,RX_DR))//если этот бит равен 1 то байт принят.
		{
			buff_RX[IN_RX++]=r_register(R_RX_PAYLOAD);//чтение 1 байта из буфера приема RX_FIFO в озу buff и отправка в USART.
		}
	}
Байты ведь могут сыпаться один за одним. И если пытаться принятые байты пихать сразу в USART, то особо ничего не выйдет. Ибо скорость работы SPI и USART очень сильно отличаются. Поэтому кажется неплохо бы замутить кольцевые буфера. В один буфер складывать данные которые получены, а в другой складывать то, что надо отправить в эфир. Ну и потом пусть по прерываниям сам МК выгребает данные и распихивает их по интерфейсам, отправляемые в SPI, принятые в USART.

#define BUF_SIZE 256
static uint8_t buff_RX[BUF_SIZE], IN_RX, OUT_RX;//тут у нас буфер в который складываем принимаемые данные до 256 байт
static uint8_t buff_TX[BUF_SIZE], IN_TX, OUT_TX;;//А сюда кладем данные для отправки до 256 байт
IN_RX, OUT_RX, IN_TX, OUT_TX это маркеры начала и конца кольцевого буфера.

В главном цикле
if (IN_TX != OUT_TX)//если конец и начало не совпадают значит в буфер отправки что то попало из USART. И это что то надо выдать в эфир
	{
		send_byte(buff_TX[OUT_TX++]);//отправим из буфера 1 байт в пространство.
	}


Весь проект как есть выкладываю в топик. Писал в AtmelStudio6.2. Он сильно черновой и с магическими числами, но рабочий. Прошивку можно сразу загрузить в платку и она будет работать. Если что, то код можно напильником допилить. Надо бы будет как нибудь, замутить статью про более продвинутые возможности данного радио модуля.
Файлы в топике: nRF_SPI_USART.zip

Комментарии (140)

RSS свернуть / развернуть
Надо будет прикупить себе парочку таких платок.
-1
Давно работаю с этими модулями. Как получил-начались мучения, ибо вроде все верно, а не работает. В конце концов, все заработало. Но статья для меня все равно оказалась интересной))) К примеру, я не знал, что такие платки вообще есть))) К сожалению, плюсануть не могу-силы не хватит(
0
А какие проекты делаешь, если не секрет конечно? Мне интересно просто, что народ на них творит. В основном замечал что обычно пытаются температуру передать из одного угла в другой.
0
Да не секрет))) Я, как только разобрался с ними, сделал систему цифрового радиоуправления))) Прицепил авр-ку с драйвером двигателей на 4 IRF7389(2 Н-моста), nrf24l01, УЗ-дальномером HC-SR04 и цифровым термометром на DS18b20 к шасси игрушечной машинки(штатную электронику выкинул нафиг). С другой-преобразователь USB-UART с ебея и ATMega8 с nrf24l01. Ну и на скорую руку накатал прогу для общения с виртуальным COM-портом)))) Получилась машинка, управляемая с компа)
0
у микросхем nRF24L01+ есть китайский аналог — BK2423, на них тоже радиомодули делают, можно посмотреть на ebay или aliexpress. отличаются в основном тем, что у них есть второй банк памяти, который надо забить магическими числами.
0
А смысл, если модули на nrf24l01+ на ebay стоят по 30 рублей за штуку?
0
а есть еще одно отличие, про которое я подзабыл за давностью лет: максимальная выходная мощность у nRF24L01+ составляет 0 dBm, а у BK2423 +5 dBm
0
А вот большая мощность-это уже вкусно)
0
еще кстати есть модуль RFM73, вроде там микросхема точно такая же как BK2423, но глубоко не смотрел
0
Эх… Если бы у меня на работе программисты так код оформляли…
0
Кстати да, за оформление кода, автор, лови жирный плюс. Хотя почти вся информация есть в даташите, перед глазами ее иметь все же удобнее)))
0
Как говорили в известном фильме
«За изобретение ставлю 5, а по предмету — НЕУД.»

Позволю себе немного критики (с ТС мы общались достаточно много, на критику он реагирует абсолютно нормально, думаю он не обидится на прямоту)

Касательно оформления кода. Давайте рассмотрим функцию

void w_register(uint8_t a,uint8_t b)//а-адрес регистра, b-что пишем в регистр.
{
        a=a | W_REGISTER;//накладываем маску
        ClearBit(PORTB,CSN);
        SPDR=a;
        while(BitIsClear(SPSR,SPIF));
        SPDR=b;
        while(BitIsClear(SPSR,SPIF));
        a=SPDR;//это для сброса флага SPIF
        SetBit(PORTB,CSN);
}//W_REGISTER (CONFIG,0b00000110);


1. Старайтесь, чтобы по сигнатуре функции был понятен ее смысл и как ее использовать (без комментариев). Например,

void WriteReg(uint8_t address,uint8_t value);


Для экспортируемых функций лучше избегать общих названий, типа WriteReg(), w_register() и т. д. Когда периферии много и у каждой есть регистры в которые можно писать, то тяжело понять, к чему именно относится WriteReg().
Для экспортируемых функций лучше выбирать уникальные и осмысленные имена, например, условно, RF24WriteReg(...).

2. Ну нельзя называть переменные буквами алфавита в порядке их появления. Ели в функции 2 локальные переменные — можно запомнить, что «а» — хранит адрес, а «b» — данные. А если локальных переменных 10? Что хранит «f»?

3. Не делайте комментариев уровня «капитан очевидность».

a=a | W_REGISTER;//накладываем маску


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

a=SPDR;//это для сброса флага SPIF


это действительно нужный комментарий, т. к. поведение кода не очевидно.

3. Перечитывайте код после написания, подумайте, как его можно сократить. Вот например, в этой функции

uint8_t r_register(uint8_t a)//чтение байта из озу. a-адрес байта
{
        ClearBit(PORTB,CSN);//Прижимаем вывод CSN(SS) МК к земле, тем самым сообщаем о начале обмена данных.
        SPDR=a;
        while(BitIsClear(SPSR,SPIF));//ожидаем когда освободится SPI для последующей записи байта
        if (a==STATUS)
        {
                SetBit(PORTB,CSN);//Вывод CSN(SS) МК к питанию, обмен данных завершен.
                return SPDR;
        }
        SPDR=NOP;
        while(BitIsClear(SPSR,SPIF));
        SetBit(PORTB,CSN);//Вывод CSN(SS) МК к питанию, обмен данных завершен.
        return SPDR;
}


можно избавиться от нескольких точек выхода и сократить код

uint8_t r_register(uint8_t a)//чтение байта из озу. a-адрес байта
{
        ClearBit(PORTB,CSN);//Прижимаем вывод CSN(SS) МК к земле, тем самым сообщаем о начале обмена данных.
        SPDR=a;
        while(BitIsClear(SPSR,SPIF));//ожидаем когда освободится SPI для последующей записи байта
        if (a!=STATUS)
        {
       SPDR=NOP;
        while(BitIsClear(SPSR,SPIF));
        }
        SetBit(PORTB,CSN);//Вывод CSN(SS) МК к питанию, обмен данных завершен.
        return SPDR;
}
+2
Дополнительно поясню, почему много комментариев — это плохо.

1. Если в коде прокомментирована каждая строка — я перестаю обращать внимание на комментарии вообще.

2. Часто код меняют, а комментарий забывают поменять. Появляются фрагменты, которые вызывают диссонанс. Типа
B = A * 4 // Умножаем A на 8

И вот пойми, автор ошибся в реализации или в комментарии.

3. Плюс, как я уже когда-то говорил, комментарии не должны заменять собой учебник или даташит. Проще дать в комментариях ссылку на тот самый раздел даташита.
+1
Ой да ладно придираться, бОльшая часть программистов даже так не оформляет, хотя в курсе о рекомендациях по оформлению кода (того же Макконнелла все читали с его совершенным кодом).

Почти все читали MISRA-C, но порой ситуация как с оформлением кода…
0
Если уж упомянул MISRA-C, то грех не затронуть CERT C Coding Standart (link)
0
Мои наблюдения диаметрально противоположенные.

Большая часть программистов придержутся установленных для проекта стандартов оформления кода, архитектурных решений и т. д. За отклонения от этих стандартов – программист тут-же получает по рукам от своих коллег. Даже небольшие компании внедряют адекватные методологии управления проектом, с code review, покрытием кода тесками и т. д.

Шарашкиных контор, в которых работают по принципу «я кодю как умею», становиться все меньше.
0
Большая часть программистов придерживаются установленных для проекта стандартов оформления кода, архитектурных решений и т. д.
по каким критериям появляются стандарты? из книги «совершенный код»? или часто просто играет роль личный опыт программиста?
0
из книги «совершенный код»?
Подобные книги – это общее руководство, его можно в конкретных случаях трактовать по-разному. На практике, тебе просто дают описание того, как нужно оформлять код (в т. ч. называть переменные и т. д.). Например, вот. За отклонение – расстрел на месте :)
0
Хорошая страничка, надо взять себе на вооружение. Правда я в ангельском дуб-дубом.
0
3. Плюс, как я уже когда-то говорил, комментарии не должны заменять собой учебник или даташит. Проще дать в комментариях ссылку на тот самый раздел даташита.
Категорически не соглашусь. Я частенько в комменты пишу (копипастю, конечно ;) ) подобную портянку именно чтобы не лезть лишний раз в даташит. Особенно полезно когда играешься с новой железкой — все в одном окне рядом и не нужно переключать окно и контекст. А фолдинг помогает не загромождать визуально код. Зато когда надо глянуть на флаг — просто развернуть. Удобно.
0
Поддерживаю. Для документирования кода весьма удобно использовать Doxygen.

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

К сожалению, современные реалии таковы, что нужно быстрее вывести устройство на рынок, а код, если что, можно и потом до ума довести.
0
не, я не о доксигене и прочей ереси. я о том, что хоть и пространные, но такие расшифровки порой очень помогают в работе.
0
Не суть важно, я о том, что если документировать код, то делать это нормально, а не использовать «расшифровки».
0
код (как минимум имена локальных переменных) должен быть самодокументируемым.
0
такие основы не должны обсуждаться в принципе
0
хе-хе. это относительно кода. вы гляньте в разводку, и какие бока всплывают, что смотреть страшно. (соседний топик про W5200) впрочем, косячной платы там уже нет, к сожалению.
0
но в комментах я там таки хорошо отметился — просто не мог пройти мимо.
0
Ну это расточительство, у меня переменные обозначаются как temp0, temp1, temp2 и тд, если в каком-то месте програмы мне нужна переменная для чего-то локального, что в другом месте уже не важно-зачем занимать ячейку ОЗУ только для понятного названия?..
0
Для чего-то локального есть локальные переменные, которые существуют только в этой локации.
Кроме того, даже в эмбеде в 95% случаев ячейки ОЗУ не такой уж дефицит, чтобы экономить на мелких переменных (тем более локальных).
0
Локальные переменные существуют только в теле функции, а я говорю про основную програму..
У меня часто бывают проблемы с ОЗУ… выделяю блоки под буферы а на переменные почти не остается места…
Сейчас подумал… возможно можно обозвать одну и ту-же ячейку разными именами например через #define
чтобы не было путаницы сначала поместить букву… скажем так…
a_res_time, a_error_count, a_blue_index, b_rnd_counter, b_out_mask и тд
0
Прямой путь к ошибкам.
Хотя с большими переменными действительно возникают проблемы, но это как раз те самые 5% случаев, когда такая экономия оправдана (разумеется только в том случае, когда памяти действительно не хватает). Впрочем, в таком случае я бы задумался о динамической памяти (MISRA ее не одобряет, но переиспользование переменных еще хуже и, полагаю, мисрой забанено).
0
лучше выбирать уникальные и осмысленные имена
Ну да согласен, когда увидишь LCDread и nRFread, то сразу ясно к чему этот read будет относится.
Ну нельзя называть переменные буквами алфавита в порядке их появления
А как ты обычно называешь? Каждой локальной переменной даешь осмысленные имена?
«капитан очевидность»
Ну да согласен, кстати этот комментарий я тока в статье привел, а так в коде его у меня нету.
можно избавиться от нескольких точек выхода и сократить код
Ну… тут уже наверное от опыта программиста зависит, насколько он умеет писать компактно, ну или как извилина шевельнулась в данный момент времени, так алгоритм и написался.
Да за советы спасибо, осталось только научится все это применять а точнее не забывать. Часть этих советов я уже читал раньше не один раз а вот с применением что то забывается.
0
Ну да согласен, кстати этот комментарий я тока в статье привел, а так в коде его у меня нету.
ну и зачем учить плохому?
0
А как ты обычно называешь? Каждой локальной переменной даешь осмысленные имена?
Разумеется! А уж названия аргументов функций — и подавно.
+1
Кажется одна из причин почему локальным переменным дают имена типа a,b,c, потому что это быстро. А вот чтобы дать осмысленное имя надо остановиться и начать думать, а это занимает время. Возможно поэтому я часто вижу в чужих программах только a, b, c. И поэтому и сам начал так делать. Да надо искоренять плохие привычки.
0
Эээ… Это программирование, думать тут нужно как минимум 100% времени!
Возможно поэтому я часто вижу в чужих программах только a, b, c.
Руки отрывать. И яйца. Однобуквенные переменные допустимы только в нескольких традиционных случаях — счетчик i (и то не рекомендуется), координаты.
А то была тут какая-то поделка… Заглянул в сорец, увидел полсотни одно-двухбуквенных глобальных переменных, выматерился и закрыл нахрен — этот код нечитаем.
+1
Это расточительно (я ответил выше), эффективнее использовать одну и ту-же ячейку памяти для разных функций в разных местах програмы, тогда и вопрос названия отпадает… остаются буквы
0
Во первых, как я уже сказал, для этой задачи есть локальные переменные (и динамическая память, но с ней свои проблемы).
Во вторых, экономия памяти не самоцель. Нет никакой разницы, задействовано 500 байт из 512 или 100. Абсолютно никакой, если только это не используется для того, чтобы сменить контроллер на более дешевый.
При этом читаемость программы самым прямым образом влияет на ее глючность и цену.
В результате такая «экономия» в 95% случаев приведет к выбрасыванию впустую оперативки (незадействованная прошивкой оперативка — считай выброшена впустую) ценой резкого снижения читаемости и поддерживаемости программы (что, в свою очередь, означает резкое возрастание числа ошибок в программе цены ее разработки).
0
P.S. На итхаппенсе есть хороший пример такой экономии.
+1
Ну… у меня обычно те переменные что используются всегда для одной и той-же цели названы осмысленно, а те что меняют своё предназначение в разных местах програмы названы temp и в комментарии уже описано для чего они…
вот пример только что выложил
Your text to link...
0
Это как раз пример того, как делать не надо.
Пример, когда такой подход оправдан — буфер пакета в цикле статей Lifelover'а про ENC28J60. Он сжирает 60% (600/1024) памяти и потому переиспользование его для, например, работы с FAT (которой тоже нужен буфер, занимающий 5% памяти) — необходимость.
0
(которой тоже нужен буфер, занимающий 5% памяти)
*50%
0
Ну… тут уже наверное от опыта программиста зависит, насколько он умеет писать компактно, ну или как извилина шевельнулась в данный момент времени, так алгоритм и написался.
Да за советы спасибо, осталось только научится все это применять а точнее не забывать. Часть этих советов я уже читал раньше не один раз а вот с применением что то забывается.
ой, я вас до сих пор путал с PapaBubaDiop
А оказывается…
0
Нет я Papandopala.
0
тады ОЙ. я в вас заподозрил бОльший профессионализм по созвучию ников.
0
Мой профессионализм очЕнЬ маленький.
0
То им стоитло бы оторвать руки.
e_mc2 все правильно написал.
Качество кода оставляет желать лучшего.
Что мне категорически не нравиться в стиле, дополняя e_mc2

1) Пробелы рядом с присвоением и вобще с операндами

case 7:UBRRL=47;//9600   111
или
case 7:
   UBRRL = 47;//9600   111

Смотриться гораздо лучше.

SetBit(PORTB,TX); 
или
SetBit(PORTB, TX);

Вы же не пишете предложения вот так.Как, я сейчас, тут, от, балды, просто, знаки, припинания.
if (BitIsClear(PINC,PC5) && F2==0)
	{
		_delay_ms(100);
		F2=1;
		send_byte(0xAA);
	} 
	else if(BitIsSet(PINC,PC5) && F2==1)
	{
		_delay_ms(50);
		F2=0;
	}

Или
if (BitIsClear(PINC, PC5) && F2 == 0)
	{
		_delay_ms(100);
		F2 = 1;
		send_byte(0xAA);
	} 
	else if(BitIsSet(PINC, PC5) && F2 == 1)
	{
		_delay_ms(50);
		F2 = 0;
	}


2) Переменные ОБЯЗАТЕЛЬНОдаются звучные имена. Через месяц открыв код вы невспомните, что такое C0,C1,C2,F1,F2,F3

3)
Вот это вобще зло
uint8_t a= r_register(STATUS);//прочитали статус регистр
		w_register(STATUS, a);//сброс флагов прерываний - обязательно
		if (BitIsSet(a,RX_DR))
		{
			uint8_t a =IN_RX;
			buff_RX[IN_RX++]=r_register(R_RX_PAYLOAD);//чтение 1 байта из буфера приема RX_FIFO в озу buff и отправка в USART.
			if (buff_RX[a]==0xAA)
			{
				F1=1;
			}
			else if (buff_RX[a]==0xBB)
			{
				F1=2;
			}
		}

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

Я вобще не понимаю зачем вам там вторая переменная нужна.

uint8_t status =  r_register(STATUS);
w_register(STATUS, status);//сброс флагов прерываний - обязательно
		if (BitIsSet(status, RX_DR))
		{
			buff_RX[IN_RX] = r_register(R_RX_PAYLOAD);//чтение 1 байта из буфера приема RX_FIFO в озу buff и отправка в USART.
			if (buff_RX[IN_RX] == 0xAA)
			{
				F1 = 1;
			}
			else if (buff_RX[IN_RX] == 0xBB)
			{
				F1 = 2;
			}
			IN_RX++;
		}
0
И да еще. Я вас наверно огорчу, но вот этот код неделает ничего.
a=SPDR;//для сброса флага SPIF

Он точно ничего неможет сбросить.
-3
Еще как может.
+1
Сама процедура записи или чтения из SPDR сбрасывает флаг SPIF. Смотрите даташит на МК.
+1
Да я заметил просто этот код выглядит очень странно, и вызывает подозрения, что что-то тут не так.
Так как в функцию параметр а передается по значению, и перед выходом туда что-то пишется. Сразу заставляет насторожится.
Лучше возвращайте например из w_register значение SPDR. Так и пользы больше.
0
Я ж писал в конце статьи что код черновой, черновой это значит черновой.
0
1) Это придирки к стилю оформления. А стиль — вопрос субъективный.
2, 3) +1
0
Да возможно первый вариант, все-таки больше конечно стилевой, просто уже привык, что везде оформляется пробелами в таком стиле.
0
Что мне категорически не нравиться в стиле...
Смотриться гораздо лучше.
Прежде чем придираться к пробелам, следовало бы выучить правило (всего из двух вариантов), когда в глаголах ставиться мягкий знак, а когда он не должен ставится!
вы невспомните
ненадо так делать
Иктотоговоритчтотопропробелы?! И это они запрещают ковыряться в носу?
Как, я сейчас, тут, от, балды, просто, знаки, припинания.
Именно что от балды вы не ставите знаки припенание. Примеров можно кучку нацитировать. Н-н-н-нада?

Я придираюсь, да? Неужели выучить родной русский язык труднее, чем учить других стилю программирования на C? Если ваш родной язык не русский, то укажите страну/язык/место проживания. Во избежание впредь.
0
он чуркажой. простим.
0
Прежде чем придираться к пробелам, следовало бы выучить правило (всего из двух вариантов), когда в глаголах ставиться мягкий знак, а когда он не должен ставится!

ставиться

Это такой тонкий юмор или я чего недопонимаю и это инфинитив?

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

Не понимаю, к чему эта агрессия? Коллега отписался по делу, хотя пункт 1 — это действительно лишнее, это больше дело вкуса. Также, не понимаю, зачем коллеге слили рейтинг?..
+1
 когда в глаголах ставиться мягкий знак

Нет, ну вот правда, ЕМНИП, в «ставиться» в данном случае, мягкого знака быть не должно. Или я ошибаюсь? Или само выражение «когда в глаголах ставиться мягкий знак, а когда он не должен ставится » это какой-то известный «мэм»?
0
Заметили ошибку? Как минимум, там их две должно быть. Мягкие знаки преднамеренно поставлены неправильно в двух случаях. Это было сделано по приколу, примерно как здесь.
0
Извините, но мы находимся на техническом ресурсе, а не на ресурсе посвященному грамматике русского языка.
Если бы я писал свою статью или, что либо для публикации то я бы проверил на ошибки, а проверять комментарий на наличие грамматических ошибок я считаю излишней тратой времени.
0
Разве на техническом ресурсе орфография какая-то другая? Разве автоматические спеллчекеры стоят денег? Разве сложно просто перечитать свой текст хоть разик? Наверно, у меня чрезмерно завышенные хотелки.
По всей видимости, торжество абырвалга грядёт неизбежно. Указывать на ошибки теперь неприлично, а их делать — достойно почёта и уважения. Я обязан извинится перед вами лично и перед всеми участниками и посетителями за беспокойство и отнятое время.
0
Разве на техническом ресурсе орфография какая-то другая?
Нет. Но, во первых, к комментам придираться ИМХО перебор, а во вторых — админ не любит GN и периодически выдает им по шапке.
0
Ну если выбросили вторую переменную тогда уж надо написать

buff_RX[IN_RX+1]
0
Простите, но я не понял что вы хотели этим сказать? Нам все равно нужно инкрементировать переменную.
0
Я не разбирался в дебрях программы, и на сколько все это правильно, но у автора кусок кода:
uint8_t a =IN_RX;
                        buff_RX[IN_RX++]=r_register(R_RX_PAYLOAD);//чтение 1 байта из буфера приема RX_FIFO в озу buff и отправка в USART.
                        if (buff_RX[a]==0xAA)
                        {
                                F1=1;
                        }
                        else if (buff_RX[a]==0xBB)
                        {
                                F1=2;
                        }

А Вы исправили на

                        buff_RX[IN_RX++]=r_register(R_RX_PAYLOAD);//чтение 1 байта из буфера приема RX_FIFO в озу buff и отправка в USART.
                        if (buff_RX[IN_RX]==0xAA)
                        {
                                F1=1;
                        }
                        else if (buff_RX[IN_RX]==0xBB)
                        {
                                F1=2;
                        }

Если присмотреться, то у автора переменная инкрементируется только в одном месте, но используеться дальше ее старая копия. Вы же ее используете уже увеличенную на единицу.
0
0
У меня инкремент после всех использований.
0
Да, недосмотрел. Извините.
0
+1 Доходчиво, полезно, интересно.
0
Как раз сегодня в 11:30 я закончил читать даташит nrf24l01+. :)
Теперь начну писать свою библиотеку.
P.s. Быстрый старт — это хорошо, но данный трансивер интересен в первую очередь своим Enhanced ShockBurst™, а это уже другая история. :)
0
О это класс. Вдруг может появится когда нибудь желание продолжить тему… думаю народу это очень даже пригодится.
0
а зачем ты пишеш всегда #if 1?
0
О… для меня это очень важная фишка. Скачай проект из топика и посмотри файл init.c Там увидишь, что каждый аппаратный модуль МК обрамлен в такую директиву условной компиляции. #if 1...#endif. Этот файл init.c я тягаю из проекта в проект. И уже конкретно в каждом проекте я либо разрешаю #if 1 и настраиваю нужный мне модуль, либо запрещаю #if 0. В таком случае компилятор просто вырежет этот кусок кода. Так же очень удобно сворачивать куски кода этой директивой. Получается примерно так.
Так же в atmelstudio есть команда в контекстном меню toggle all outlining позволяющая быстро свернуть/развернуть куски кода обрамленные в #if 1...#endif Как кому а мне это очень удобно.

0
Вообще, для этого обычно используется конструкция вида #ifdef USE_USART… #endif, #ifdef USE_SPI… #endif, а сами дефайны или собираются кучей в начале файла (ненужные комментируются), или задаются из настроек компилятора.
+1
Валяется несколько микрух nRF24L01, всё никак платы не закажу под них:)
Плюсец тебе, ТС :)
0
У тебя сами микрухи или готовые радио модули? А то у меня тоже есть сами микрухи nRF24L01. Вот думаю надо изобрести что нибудь.
0
Их паять заколебешься))) Хотя, при малом радиус кривизны рук можно попробовать. Я как-то запаивал акселерометр lis3dsh, размером 3х3 мм и имеющий 16 выводов.
0
При наличие фена и хорошего флюса паяется всё, если паяется плохо, то надо облудить выводы микрухи перед пайкой, так как окисляются они бывает, и припой не ложится:)
0
Сами микрухи, яж это написал «микрухи» :D Ещё некоторое время назад, до нового года ещё, я под них набросал печатные платы, но на завод заказать, всё никак руки не доходят, надо заказать в ближайшее время, да работа отвлекает блин :D
0
На iteadstudio классно делают)) Заказывал два раза уже, первый раз 15х15, заказывал 5 штук, прислали 7 зачем-то. Сейчас заказал второй раз 10х15, ждем-с)
0
Я заказываю у таберушников. У нас в фирме требуется, чтобы срочно делали, тобиш 3 дня на производство, они идеально удовлетворяют это:) А я свои заказы просто докидываю, и если заказ большой, то цена платы мне получается около 30р :)
0
У таберушников?)))) Мой последний заказ в iteadstudio вышел на 2000 рублей с доставкой( 5+2 за опенсорс плат 10х15.
0
а время изготовления с доставкой?

Яж неаписал, что да. у таберушников дороже, но с момента отправки им заказа, до момента его получения у курьера (а мы аж в 500км от мск) проходит 4 дня :) Это то, что нам в первую очередь нужно:)
0
С доставкой около месяца в общем выходит)))
0
Плюсую. 2 недели назад закончил связки nRF24L01+STM32F4Dis, nRF24L01+STM32F3Dis
0
Метров на 10 работает?
0
Написано в документации что эти радио модули берут 100 метров в зоне прямой видимости. Если с препятствиями то уж как получится. А есть такие же радио модули но с усилителем то там уже 1100м в зоне прямой видимости.
0
УГУ. на заборе тоже написано Х.Й. А на самом деле там дрова…
0
По дальности: у меня модули без усилителя, дома пробивает 2 насыпные\деревянные стены(у меня частный дом, половина внутренних стен насыпные, половина деревянные). Но если через 1 стену работает более-менее, то через 2-работает только вблизи стены. Тестировал на улице на прямой видимости-уверенный прием только на 15-20 метрах. Дальше-уже потери идут. Все тесты проводил на самой низкой скорости передачи.
0
Забыл добавить-100 метров модуль не берет вообще. У меня на тестовой плате были 2 светодиода: зеленый мигал при передаче пакета, красный загорался при обрыве связи, при восстановлении-гасился. После 75-80 метров красный не гас вообще.
0
код не смотрел, но для повышения качества связи попробуйте:
1) установите канал в районе 100, (частота 2,5ГГц) — там нет данных от Wi-Fi, что дает более уверенный прием на бОльших расстояниях при наличии wi-fi устройств вокруг
2) установите максимальную выходную мощность (0dBm)(да, банально, но не все об этом помнят/знают) и максимальную скорость(мне помогало при длинных пакетах) в регистре RF_SETUP
3) если время отклика не критично (на минимальной задержке между трансляциями и максимальным количеством трансляций задержка будет от передачи в одну сторону не больше 10-20 мс), то используйте авторетрансмит в SETUP_RETR (ack должен быть включен)
мне помогало добиться норм связи в расстоянии 10-15 метров внутри помещения через 2-3 стены из неизвестного мне материала с обилием wi-fi точек (10-12 штук)
и кстати при плохой связи и больших пакетах (более 32 байт) надо писать обвязку для передачи данных, иначе могут теряться части пакетов, и вы этого не заметите, если будете «кушать» сырые данные.
0
Спасибо, но про все вами описанное уже знаю и все это я учитывал)))) Пакеты у меня были по 16 байт)
0
кстати есть еще один способ повысить качество связи, это ориентация антенны, но применимо только к стационарным устройствам, опытным путем замечено существенное улучшение качества связи при расположении антенн в одной плоскости (это конечно очевидно, но не всегда об этом помнишь на модулях с интегрированной антенной)
0
Про это тоже знаю, но в моем случае это было неприменимо, ибо у меня платформа подвижная)))
0
А зачем такая борьба за каждый метр приема? Вот используйте эти радио модули и все вопросы с дальностью должны исчезнуть.
0
Тут вопрос в цене))) Кстати, я последние 2 года покупаю детали только на ебее, так дешевле)
0
Если у меня есть 3 такие платы — как сделать обмен двумя с одной (2 slave — 1 master)
0
Можно думаю разными способами. Если так как написано в статье то по протоколу модбас. Типа мастер отправляет запрос, слейвы его ловят оба, но отвечает только тот кому предназначен запрос. Так можно и 50 модулей опрашивать. Но это теория, надо на практике сперва сделать чтоб точно утверждать.
0
там есть адресное пространство на уровне протокола общения между модулями, настраивается в регистрах, так что никаких проблем с этим нет, просто задайте им нужные адреса
0
По даташиту команда W_TX_PAYLOAD_NOACK = 0xB0, а не 0x58.
И что за команда ACTIVATE = 0x50? Нет такой.
0
Activate 0х50 есть такая команда. W_TX_PAYLOAD_NOACK = 0х58 а не 0xB0
0
Всё ясно. У Вас даташит на nRF24L01 без '+'. Это устаревший чип. Правильная документация лежит здесь.
А ошибки всё-таки исправьте :)
0
Да у меня за 2007 год. Чета я лоханулся так.
0
Для чего нужен этот хвост?
0
Я этого не знаю.
0
это так называемая Wiggle Antenna, можно тут например посмотреть
0
Ого интересно однако. Получается что на такой частоте важна точность до сотых долей миллиметра?
0
Спасибо!
0
Этот отмеченный «хвост», ЕМНИП, заземляет антенну по постоянному току.

хорошим тоном считается на входе устройства устраивать короткозамкнутый шлейф (шлейф — это кусочек линии с некоторой привязанной к длине волны длиной), чтобы заземлить антенну по постоянному току и защитить устройство от статики и грозовых низкочастотных (меньше 1 ГГц) разрядов

Взято отсюда.
+1
«хвост», ЕМНИП, заземляет антенну по постоянному току
Я правильно понимаю, что для этого длинна линии должна быть больше длинны волны? Если 2.4 ГГц, то длинна волны 12.5 см., а длинна всей линии меньше длинны волны.
0
Я правильно понимаю, что для этого длинна линии должна быть больше длинны волны? 

Не совсем. Насколько я понимаю, главное чтобы выполнялось условие
«длина = n*лямбда/4 (где n — нечетное: 1, 3, 5…). »

Но да, что-то не сходится, т. к. при длине волны 12,5 см и n = 1, шлейф  должен быть порядка 3 см. А он явно меньше. Может кто из специалистов по ВЧ прояснит ситуацию…
0
Линия на текстолите, а у него ε = 3..5. Это, кстати, означает, что такие антенны считаются под конкретную марку текстолита и так просто их не скопируешь.

А вот что меня интересует — является ли этот земляной хвостик наобходимым для F- и Patch-антенн, или он только для защиты?
+1
А, и еще, этот хвостик идет над земляным полигоном с другой стороны платы, формируя микрополосковую линию. Это тоже дает вклад в уменьшение длины волны.
0
Получается что без этого хвоста над земляным полигоном антенна не будет работать? И что если будет другая марка текстолита, нужны другие размеры антенны?
0
1) Понятия не имею. Видел wiggle-антенны без хвоста, но если отрезать конкретно от этой — она может рассогласоваться.
2) Да. И если будет другая толщина текстолита — тоже. И от погоды на Марсе оно тоже немного зависит...)
0
Да к сожалению на погоду на Марсе повлиять никак не возможно.
0
Линия на текстолите, а у него ε = 3..5. 

Спасибо за пояснения, я совершенно забыл этот момент.
0
является ли этот земляной хвостик наобходимым
Я думаю что нет. На Bluetooth HC-06 такого нет, хотя частота тоже 2.4.
0
а кому фейки достались?
у вас оно работает вообще?
вот тут отличия zeptobars.ru/ru/read/Nordic-NRF24L01P-SI24R1-real-fake-copy
+1
Там говорят shockburst не работает, и потребление выше. А так норм.
0
я мало чего понял из объяснений на хакадее
ты не в курсе подробностей в плане кода и библиотек?
типа вот есть библиотека и там есть режим без плюшек, который будет работать на подделках
0
Самый обычный режим он и есть без плюшек, как я понимаю.
Вот тут больше инфы по разным совместимым чипам: sigrok.org/wiki/Protocol_decoder:Nrf24l01
0
Проверил кучку модулей, что у меня была — все выводные(с двухрядной 2.54 гребенкой) — оригинальные, модули в smd-исполнении(несколько с двумя рядами выводов, с двух сторон и один модуль, у которого все выводы с одной стороны) — фейки. Выводные работали, а вот в smd — исполнении, у которых выводы с двух разных сторон, я так и не смог запустить и отложил в долгий ящик.
0
Недавно пробовал эти смд, запустились без проблем. У них кстати полярность питания противоположная, по сравнению с двухрядными — 1-й плюс 2-й минус.
0
У меня лежит один экземпляр точно такого же. Маркировка, кажется, неоригинальная, но до запуска пока руки не дошли. Двурядные смд так нормально и не запустил. Самый первый пакет отправляется, а дальше — глухо.
0
Попробуй через эту штуку запустить chipster.ru/catalog/arduino-and-modules/wireless/ism/2998.html
0
Меня жаба задавит отдавать за это 450 рублей) Вот если бы еще прошивку этой меги найти можно было бы…
0
Зачем прошивка если она уже итак прошита?
0
Чтобы сделать самому, деталей там на 100 рублей.
0
Похоже у меня фэйк, в первом приближении работает, shockburst не тестил.
0
Чисто академический интерес. А кто-нибудь пробовал использовать корректирующее кодирование без/совместно с перемежением с этим модулями для повышения качества и дальности связи?
0
А, там же итак используется аппаратный CRC.
0
А, там же итак используется аппаратный CRC.

Ну, CRC просто позволяет выявить и отбраковать «битые» пакеты данных, это просто контроль целостности. Это позволяет выявить (но не исправить) ошибку.

Я экспериментировал с кодом Хемминга (на большее меня не хватило :) на похожем модуле (RFM22B). Эксперименты показали, что применение этого кода практически не влияет на «дальность», но его применение улучшило прием в условия помех и забитого диапазона.

Интересно было бы поэкспериментировать с кодами Рида — Соломона.
0
на похожем модуле (RFM22B).
Хотя точно не уверен, возможно это был модуль на основе СС1010
0
О, значит я невнимательно читал про CRC. Уже интереснее)

Интересно было бы поэкспериментировать с кодами Рида — Соломона.
Почему именно Рид-Соломон? Почему не сверточные например? Перемежение тоже не пробовал?
0
О, значит я невнимательно читал про CRC
CRC это хеш/свертка, по нему нельзя вычислить или скорректировать исходные данные.

Почему именно Рид-Соломон? Почему не сверточные например? Перемежение тоже не пробовал?

Как я писал, я пробовал только код Хемминга, на большее меня не хватило. Касательно Рид-Соломон – слышал, что этот алгоритм достаточно эффективен для применения в подобных условиях
0
Я в описание модуля не сильно глядел, но там описаны все процедуры совершаемые от записи информационной комбинации, до отправки сообщения в канал? Если так, то можно сделать модель канала в матлабовском Simulink и проверить как разные коды будут бороться с одиночными/групповыми ошибками. Ну а можно сразу и на железке поиграться)
0
Я в описание модуля не сильно глядел

Я тоже не глядел в описание данного модуля :) Но все те модули с которыми я работал позволяли передавать данные «как есть» (RAW) (пакетный режим и режим контроля CRC включались дополнительными опциями).

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

Можно, только как вы создадите модель канала с помехами, которая бы соответствовала реальности?
0
Можно, только как вы создадите модель канала с помехами, которая бы соответствовала реальности?
Если заморочиться, то можно. Основная проблема узнать реальную помеховую обстановку.
Ну а так, можно ж с каким-нибудь белым гауссовским помоделировать.
0
А где Вы вычитали магическое число в 135 микросекунд ожидания? В даташите его я не нашёл.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.