В предыдущей части урока мы изучили ещё некоторые характеристики и особенности модуля, познакомились с практическими схемами подключения приёмника и передатчика, подготовили питание модуля, а также создали и настроили проект для работы с ним в среде программирования.
Для начала добавим для глобальных массива в файле main.c
/* Private variables ---------------------------------------------------------*/
char str1[20]={0};
uint8_t buf1[20]={0};
/* USER CODE END PV */
Затем создадим два файла библиотеки модуля NRF24.h и NRF24.c со следующим содержимым
NRF24.h:
#ifndef NRF24_H_
#define NRF24_H_
//------------------------------------------------
#include "stm32f1xx_hal.h"
#include <string.h>
//------------------------------------------------
#define CS_GPIO_PORT GPIOA
#define CS_PIN GPIO_PIN_4
#define CS_ON HAL_GPIO_WritePin(CS_GPIO_PORT, CS_PIN, GPIO_PIN_RESET)
#define CS_OFF HAL_GPIO_WritePin(CS_GPIO_PORT, CS_PIN, GPIO_PIN_SET)
#define CE_GPIO_PORT GPIOA
#define CE_PIN GPIO_PIN_3
#define CE_RESET HAL_GPIO_WritePin(CE_GPIO_PORT, CE_PIN, GPIO_PIN_RESET)
#define CE_SET HAL_GPIO_WritePin(CE_GPIO_PORT, CE_PIN, GPIO_PIN_SET)
#define IRQ_GPIO_PORT GPIOA
#define IRQ_PIN GPIO_PIN_2
#define IRQ HAL_GPIO_ReadPin(IRQ_GPIO_PORT, IRQ_PIN)
#define LED_GPIO_PORT GPIOC
#define LED_PIN GPIO_PIN_13
#define LED_ON HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_RESET)
#define LED_OFF HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_SET)
#define LED_TGL HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN)
//------------------------------------------------
#define ACTIVATE 0x50 //
#define RD_RX_PLOAD 0x61 // Define RX payload register address
#define WR_TX_PLOAD 0xA0 // Define TX payload register address
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
//------------------------------------------------
#define CONFIG 0x00 //'Config' register address
#define EN_AA 0x01 //'Enable Auto Acknowledgment' register address
#define EN_RXADDR 0x02 //'Enabled RX addresses' register address
#define SETUP_AW 0x03 //'Setup address width' register address
#define SETUP_RETR 0x04 //'Setup Auto. Retrans' register address
#define RF_CH 0x05 //'RF channel' register address
#define RF_SETUP 0x06 //'RF setup' register address
#define STATUS 0x07 //'Status' register address
#define RX_ADDR_P0 0x0A //'RX address pipe0' register address
#define RX_ADDR_P1 0x0B //'RX address pipe1' register address
#define TX_ADDR 0x10 //'TX address' register address
#define RX_PW_P0 0x11 //'RX payload width, pipe0' register address
#define RX_PW_P1 0x12 //'RX payload width, pipe1' register address
#define FIFO_STATUS 0x17 //'FIFO Status Register' register address
#define DYNPD 0x1C
#define FEATURE 0x1D
//------------------------------------------------
#define PRIM_RX 0x00 //RX/TX control (1: PRX, 0: PTX)
#define PWR_UP 0x01 //1: POWER UP, 0:POWER DOWN
#define RX_DR 0x40 //Data Ready RX FIFO interrupt
#define TX_DS 0x20 //Data Sent TX FIFO interrupt
#define MAX_RT 0x10 //Maximum number of TX retransmits interrupt
//------------------------------------------------
#define W_REGISTER 0x20 //запись в регистр
//------------------------------------------------
//------------------------------------------------
#endif /* NRF24_H_ */
NRF24.c:
#include "NRF24.h"
//------------------------------------------------
extern SPI_HandleTypeDef hspi1;
//------------------------------------------------
__STATIC_INLINE void DelayMicro(__IO uint32_t micros)
{
micros *= (SystemCoreClock / 1000000) / 9;
/* Wait till done */
while (micros--) ;
}
//--------------------------------------------------
Чтобы не терять время, мы сразу наполнили необходимыми макросами заголовочный файл. По мере изучения регистров и их использования мы будем ими пользоваться.
В main.c также подключим нашу библиотеку, а заодно и библиотеку для работы со строками
/* USER CODE BEGIN Includes */
#include "NRF24.h"
#include <string.h>
/* USER CODE END Includes */
Перейдём в файл NRF24.c и добавим функцию инициализации модуля
//--------------------------------------------------
void NRF24_ini(void)
{
}
//--------------------------------------------------
Создадим на данную функцию прототип в заголовочном файле и вызовем её в функции main()
/* USER CODE BEGIN 2 */
NRF24_ini();
/* USER CODE END 2 */
Вернёмся в файл и начнём писать тело функции инициализации рессивера
void NRF24_ini(void)
{
CE_RESET;
DelayMicro(5000);
}
Опустим сначала контакт CE и применим небольшую задержку. В таймингах немного непонятно написано, какая именно должна быть задержка, видимо в зависимости от типа трансивера (активная у него или пассивная антенна). Но максимально там 4,5 милисекунды. Дадим чуть больше, так как у нас функция задержки самодельная и она даёт задержку чуть меньше (проверено логическим анализатором).
Дальше уже идёт работа с регистрами, поэтому придется немного окунуться в теорию.
Как мы уже видели по диаграмме обмена, то сначала как правило идёт команда. Познакомимся с некоторыми из них.
Для начала посмотрим весь перечень команд
Теперь по порядку. Все команды восьмиразрядные, немного разберёмся с ними, а также с битами в них.
R_REGISTER — команда чтения регистра. Команда распознаётся по трём старшим битам. Если они в нулях, то это именно данная команда. В остальных пяти битах находится адрес регистра.
W_REGISTER — команда записи в регистр. Распознаётся также по трём старшим битам. Шестой должен быть в единице, остальные — нули. Адрес регистра также содержится в младших пяти битах команды.
R_RX_PAYLOAD — команда чтения буфера FIFO. После команды по MISO начинают передаваться данные с младжего байта. После окончания чтения буфер FIFO освобождается.
W_TX_PAYLOAD — команда записи данных в буфер FIFO. После команды по MOSI мы данные передаются в модуль и записываются в буфер FIFO. Загружать данные также следует с младшего байта.
FLUSH_TX — команда очистки буфера FIFO, предназначенного для передачи.
FLUSH_RX — команда очистки буфера FIFO, предназначенного для приёма.
ACTIVATE — команда активирует команды R_RX_PL_WID, W_ACK_PAYLOAD и W_TX_PAYLOAD_NOACK. В данном даташите, из которого был сделан скриншот, её нет, есть в другом.
Остальные команды нам пока не нужны. Если будут нужны, то мы с ними потом и познакомимся.
Добавим функции записи и чтения регистров выше функции инициализации
//--------------------------------------------------
uint8_t NRF24_ReadReg(uint8_t addr)
{
uint8_t dt=0, cmd;
CS_ON;
HAL_SPI_TransmitReceive(&hspi1,&addr,&dt,1,1000);
if (addr!=STATUS)//если адрес равен адрес регистра статус то и возварщаем его состояние
{
cmd=0xFF;
HAL_SPI_TransmitReceive(&hspi1,&cmd,&dt,1,1000);
}
CS_OFF;
return dt;
}
//------------------------------------------------
void NRF24_WriteReg(uint8_t addr, uint8_t dt)
{
addr |= W_REGISTER;//включим бит записи в адрес
CS_ON;
HAL_SPI_Transmit(&hspi1,&addr,1,1000);//отправим адрес в шину
HAL_SPI_Transmit(&hspi1,&dt,1,1000);//отправим данные в шину
CS_OFF;
}
//------------------------------------------------
Когда мы регистр читаем, мы сначала отправляем в шину SPI адрес регистра. Команду мы отдельно не используем, так как там нули, а так как адреса регистров у нас не превышают 0x1F, то они там уже будут.
Если это регистр STATUS, то при данной операции уже данные будут в возвращаемом значении, а если нет, то производим ещё один обмен на шине SPI и только после этого данные возвратятся.
А если мы в регистр пишем, то к адресу регистра добавляем команду, а затем отправляем поочерёдно этот адрес вместе с командой, а затем отправляем данные.
Теперь сами регистры. Думаю их также не следует изучать все. Причём изучать их лучше по одному по мере использования, как мы делали с датчиками MEMS.
Первый регистр — CONFIG — конфигурационный регистр
Название регистра говорит само о себе. Данный регистр предназначен для конфигурирования основных параметров трансивера.
7 бит не используется.
Следующие биты — биты маскирования прерываний. Если бит в нуле — то прерывание, за которое отвечает данный бит не будет использоваться. Есть три вида прерываний, характеризующих определённые события, при возниконовении которых ножка IRQ будет притягиваться к земле. Теперь побитно — какой бит за какое прерывание отвечает
MASK_RX_DR — прерывание, возникающее при получении пакета в приёмнике в тот момент, когда пакет появится в приёмном буфере.
MASK_TX_DS — прерывание, возникающего при успешной отправке пакета приёмнику в передающем рессивере. Если включено автоподтвержение, то данное прерывание происходит после получения подтверждения от приёмника.
MASK_MAX_RT — прерывание, происходящее при исчерпывании максимального количество повторных отправок пакета передатчиком. Максимальное количество устанваливается также в определённом регистре.
EN_CRC — включение использования контрольной суммы. Если хотя бы на один канал включено автоподтверждение, данный бит включится самостоятельно.
CRCO — количество байтов контрольной суммы. 0 — 1 байт, 1 — 2 байта.
PWR_UP — бит управления включением передатчика. 1 — включено, 0 — выключен (или спящий режим).
Продолжая функцию инициализации, занесём значения битов в регистр конфигурации
DelayMicro(5000);
NRF24_WriteReg(CONFIG, 0x0a); // Set PWR_UP bit, enable CRC(1 byte) &Prim_RX:0 (Transmitter)
Мы включили модуль в режим передатчика, контрольную сумму (в размере 1 байт).
Подождём ещё 5 милисекунд, чтобы передатчик включился. Вот тут как раз и нужна задержка. Ну пусть будет и перед включением и после. Не помешает.
NRF24_WriteReg(CONFIG, 0x0a); // Set PWR_UP bit, enable CRC(1 byte) &Prim_RX:0 (Transmitter)
DelayMicro(5000);//Дадим модулю включиться, по даташиту около 1,5 мсек, а лучше 5
Следующий регистр — EN_AA (Enhanced ShockBurst™). Это регистр, который использует указанную технологию и включает автоподтверждение для определённого канала обмена (не путать с частотными каналами, которых много)
Каждый бит отвечает за включение данного режима определённого канала, которых может быть до шести. Мы будем использовать 1 канал (не нулевой). В более поздних занятиях мы поймём почему не нулевой
DelayMicro(5000);//Дадим модулю включиться, по даташиту около 1,5 мсек, а лучше 5
NRF24_WriteReg(EN_AA, 0x02); // Enable Pipe1
Следующий регистр — EN_RXADDR, который как раз и включает использование каналов. А различаться данные каналы будут по адресам, по которым приёмники будут понимать, что данный пакет адресован именно ему
Занесём значения в данный регистр, показав тем самым модулю, что мы будем использовать именно 1 канал обмена
NRF24_WriteReg(EN_AA, 0x02); // Enable Pipe1
NRF24_WriteReg(EN_RXADDR, 0x02); // Enable Pipe1
Следующий регистр — SETUP_AW (Setup of Address Widths), который является общим для всех каналов обмена и устанавливает величину адресов приёмника и передатчика (можно сказать сетевых адресов) в байтах, которая может регулироваться от 3 до 5 байтов
Старшие шесть бит не несут никакой информации, а два младших устанавливают размер.
Установим ширину адреса в 3 байта
NRF24_WriteReg(EN_RXADDR, 0x01); // Enable Pipe1
NRF24_WriteReg(SETUP_AW, 0x01); // Setup address width=3 bytes
Следующий регистр — SETUP_RETR (Setup of Automatic Retransmission) — 'это регистр, который устанавливает параметры для повторных передач пакета при их неудачной отправке
Старшая тетрада содержит информацию о величине задержки между отправками одного и того же пакета при неудачной отправке, а младшая — о максимальном количестве таких отправок.
Инициализируем данный регистр у себя в коде
NRF24_WriteReg(SETUP_AW, 0x01); // Setup address width=3 bytes
NRF24_WriteReg(SETUP_RETR, 0x5F); // // 1500us, 15 retrans
Мы будем использовать задержку в 1500 микросекунд и максимальное количество попыток — 15.
Есть ещё одна команда — ACTIVATE, которую я сразу не нашел в документации, но она используется в библиотеке для Arduino, и я её решил тоже использовать. Лишней не будет. Все равно мы инициализируем модуль только один раз при включении или при перезагрузке. Затем я данную команду всё-таки нашел. Оказывается, документация бывает разная. Данная команда активирует команды R_RX_PL_WID, W_ACK_PAYLOAD и W_TX_PAYLOAD_NOACK. Для использования команды добавим ещё одну функцию выше функции инициализации
//------------------------------------------------
void NRF24_ToggleFeatures(void)
{
uint8_t dt[1] = {ACTIVATE};
CS_ON;
HAL_SPI_Transmit(&hspi1,dt,1,1000);
DelayMicro(1);
dt[0] = 0x73;
HAL_SPI_Transmit(&hspi1,dt,1,1000);
CS_OFF;
}
//-----------------------------------------------
Вызовем эту функцию в функции инициализации рессивера
NRF24_WriteReg(SETUP_RETR, 0x5F); // // 1500us, 15 retrans
NRF24_ToggleFeatures();
Следующий, регистр, который мы рассмотрим — это регистр FEATURE, который используется в режиме динамического количества байтов в пакете и содержит некоторые настройки данного режима
Старшие пять битов не используются. Бит EN_DPL включает режим динамического количества байт в пакете, бит EN_ACK_PAY включает отправку пакетов с ожиданием подтверждения, а бит EN_DYN_ACK — без подтверждения, в этом случае передатчик отправляет специальную команду приёмнику, чтобы тот понял, что подтверждать пакет не нужно.
Мы не будем использовать такой режим, мы будем использовать режим передачи фиксированного количества байт, поэтому мы данный режим не включаем
NRF24_ToggleFeatures();
NRF24_WriteReg(FEATURE, 0);
В следующей части урока мы закончим знакомство с регистрами модулями, закончим писать код его инициализации и проверим значения регистров, считав их и отобразив в терминальной программе.
Предыдущая часть Программирование МК STM32 Следующая часть
Модуль NRF24L01+ с антенной можно купить здесь NRF24L01+
Модуль NRF24L01+ без антенны можно купить здесь NRF24L01+
Адаптер для NRF24L01 можно купить здесь (5 штук) Адаптер для NRF24L01
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Техническая документация:
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Чтобы не было привязки к конкретной схеме в командах вроде, HAL_SPI_TransmitReceive(&hspi1,&cmd,&dt,1,1000), вместо «&hspi1» пишу
#define SPI_NRF hspi1
…
extern SPI_HandleTypeDef SPI_NRF;
Вызывать примерно так, HAL_SPI_Transmit(&SPI_NRF, (uint8_t*) aTxBuf, 2, 5000);
Изменяемые дефайны размещаю вначале файла, выделяю всю кучку таких дефайнов в отдельный блок
Здравствуйте,
У вас ошибка в коде (почти незаметная).
Вы дали слишком маленький размер переменной str1 (наверное нейучли \r\n). Изза нее отваливается USB у STM32F103. Я вчера промучился ища причину. Увеличте пожалуйста размер массива str1.
Добрый день! А почему мы не включили в SPI младшим битом вперёд?
if (addr!=STATUS)//если адрес равен адрес регистра статус то и возварщаем его
наверное нужно
if (addr==STATUS)
uint8_t NRF24_ReadReg(uint8_t addr)
{
uint8_t dt=0, cmd;
CS_ON;
HAL_SPI_TransmitReceive(&hspi1,&addr,&dt,1,1000);
if (addr!=STATUS)//если адрес равен адрес регистра статус то и возварщаем его состояние
{
cmd=0xFF;
HAL_SPI_TransmitReceive(&hspi1,&cmd,&dt,1,1000);
}
CS_OFF;
return dt;
}
В моем понимание надо сначала ножку CS включить, потом опустить, передать данные по SPI, потом включить опять
«Следующие биты – биты маскирования прерываний. Если бит в нуле – то прерывание, за которое отвечает данный бит не будет использоваться.»
Как раз наоборот! Если бит в нуле — прерывание привязано к событию, перечитайте еще раз биты регистра STATUS.