STM Урок 115. NRF24L01. Несколько приемников. Часть 1

Продолжаем работу с беспроводным приёмо-передатчиком NRF24L01. И сегодня та плата, которая у нас выступала в роли приёмника, будет выступать уже в роли передатчика. И задачу мы уже будем решать обратную. Передать с модуля, подключенного к данной плате пакеты, наоборот, на несколько приемников. Приемников у нас будет три и наша задача будет состоять в том, чтобы с передатчика мы могли в любой момент передать пакет на любой из этих трех приёмников. Данная задача оказалась выполнимой, поковыряться, конечно пришлось, но цель была достигнута, поэтому я незамедлительно решил поделиться с вами своими наработками.

Проект для нашего передатчика на плате Nucleo F401RE был создан из проекта приёмника урока 113 NRF24_RX_LCD и назван был, соответственно, NRF24_TX_LCD.

Запустим наш проект в Cube MX и включим там какой-нибудь таймер, думаю, он нам сегодня пригодится

 

 

Перейдём в configuration и настроим там период нашего таймер приблизительно на 1 милисекунду

 

 

Также настроим прерывания от таймера

 

 

Теперь зайдём в настройки NVIC и уменьшим приоритет прерываний от таймера. Самый высокий приоритет у нас должен быть у прерываний от ножки модуля

 

 

Сохраним настройки, сгенерируем проект для Keil, запустим его, настроим программатор на автоперезагрузку, а также включим уровень оптимизации в 1. Также подключим в дерево проекта файлы NRF24.c и lcd.c, а затем попробуем собрать наш проект.

Если всё нормально, то двигаемся дальше.

Уберем в функции main() из бесконечного цикла вызов функции приёма пакета

 

/* USER CODE BEGIN 3 */

  NRF24L01_Receive();

 

Не забываем запустить наш таймер

 

NRF24_ini();

HAL_TIM_Base_Start_IT(&htim1);

 

Переходим в файл NRF24.c и в самом низу добавим функцию-обработчик для событий таймера

 

//--------------------------------------------------

void TIM1_Callback(void)

{

}

//--------------------------------------------------

 

Создадим для данной функции прототип, вернёмся в файл main.c и создадим глобальную переменную для подсчёта тиков нашего таймера

 

uint8_t buf1[20]={0};

uint32_t TIM1_Count=0;

 

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

 

//-------------------------------------------------------------

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  if(htim==&htim1)

  {

    TIM1_Callback();

    TIM1_Count++;

    if(TIM1_Count>3000000) TIM1_Count=0;

  }

}

//-------------------------------------------------------------

 

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

Возвращаемся теперь в наш библиотечный файл NRF24.c. Остальной код теперь будет только там.

Добавим флаг для передачи

 

volatile uint8_t rx_flag = 0, tx_flag = 0;

 

Убавим размер буфера, так как передавать количество непереданных пакетов мы теперь не будет. Мы с этим наигрались, убедились, что у нас нормально всё передаётся

 

#define TX_PLOAD_WIDTH 5

 

Так как мы будем передавать пакеты на разные приёмники, то адрес передачи будет у нас в определённом месте меняться, также нам придётся менять адреса в приёмных каналах, так как в pipe0 всегда должен находиться адрес передачи. Поэтому добавим ещё некоторые макросы адресов

 

#define TX_PLOAD_WIDTH 5

#define TX_ADDRESS0_MSB 0xb5

#define TX_ADDRESS1_MSB 0xb7

#define TX_ADDRESS2 0xb6

uint8_t TX_ADDRESS0[TX_ADR_WIDTH] = {0xb5,0xb5,0xa1};

uint8_t TX_ADDRESS1[TX_ADR_WIDTH] = {0xb7,0xb5,0xa1};

uint8_t TX_ADDRESS2_FULL[TX_ADR_WIDTH] = {0xb6,0xb5,0xa1};

 

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

 

 

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

 

void NRF24L01_TX_Mode(uint8_t TX_num, uint8_t *pBuf)

{

  switch(TX_num)

  {

    case 1:

      NRF24_Write_Buf(TX_ADDR, TX_ADDRESS0, TX_ADR_WIDTH);

      NRF24_Write_Buf(RX_ADDR_P0, TX_ADDRESS0, TX_ADR_WIDTH);

      NRF24_Write_Buf(RX_ADDR_P1, TX_ADDRESS1, TX_ADR_WIDTH);

      NRF24_WriteReg(RX_ADDR_P2, TX_ADDRESS2);

      break;

    case 2:

      NRF24_Write_Buf(TX_ADDR, TX_ADDRESS1, TX_ADR_WIDTH);

      NRF24_Write_Buf(RX_ADDR_P0, TX_ADDRESS1, TX_ADR_WIDTH);

      NRF24_Write_Buf(RX_ADDR_P1, TX_ADDRESS0, TX_ADR_WIDTH);

      NRF24_WriteReg(RX_ADDR_P2, TX_ADDRESS2);

      break;

    case 3:

      NRF24_Write_Buf(TX_ADDR, TX_ADDRESS2_FULL, TX_ADR_WIDTH);

      NRF24_Write_Buf(RX_ADDR_P0, TX_ADDRESS2_FULL, TX_ADR_WIDTH);

      NRF24_Write_Buf(RX_ADDR_P1, TX_ADDRESS0, TX_ADR_WIDTH);

      NRF24_WriteReg(RX_ADDR_P2, TX_ADDRESS1_MSB);

      break;

    default:

      break;

  }

  CE_RESET;

  // Flush buffers

  NRF24_FlushRX();

  NRF24_FlushTX();

}

 

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

Как мы помним, мы из функции отправки пакета удалили вообще весь код. Теперь мы его добавим заново, только уже с учётом того, что на ножке прерываний изменения уровня мы уже будем отслеживать аппаратно. Также в нашей функции добавится ещё один входной аргумент — номер приёмника, на который будет отправляться пакет

 

uint8_t NRF24L01_Send(uint8_t TX_num, uint8_t *pBuf)

{

  uint8_t regval=0x00;

  NRF24L01_TX_Mode(TX_num, pBuf);

  regval = NRF24_ReadReg(CONFIG);

  //если модуль ушел в спящий режим, то разбудим его, включив бит PWR_UP и выключив PRIM_RX

  regval |= (1<<PWR_UP);

  regval &= ~(1<<PRIM_RX);

  NRF24_WriteReg(CONFIG,regval);

  DelayMicro(150); //Задержка минимум 130 мкс

  //Отправим данные в воздух

  NRF24_Transmit(WR_TX_PLOAD, pBuf, TX_PLOAD_WIDTH);

  CE_SET;

  DelayMicro(15); //minimum 10us high pulse (Page 21)

  CE_RESET;

  return 0;

}

 

В прототипе также добавим новый аргумент.

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

Также в некоторых местах, где мы обращались к адресам более 5-го подправим код, чтобы не было при сборке даже предупреждений.

Сначала подправим это в функции NRF24L01_Receive, а также уберём лишний аргумент в вызове функции sprintf

 

LCD_SetPos(0, *(RX_BUF+5));

sprintf(str1,"%5u %5u ", *(int16_t*)RX_BUF, *(int16_t*)(RX_BUF+2));

 

Теперь в функции IRQ_Callback

 

*(RX_BUF+5) = pipe;

 

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

 

//------------------------------------------------

void NRF24L01_LCD(uint16_t num, uint16_t pos)

{

  if(tx_flag==1)

  {

    LCD_SetPos(0, pos);

    sprintf(str1,"%5u ", num);

    LCD_String(str1);

    tx_flag = 0;

  }

}

//------------------------------------------------

 

Теперь идём в функцию обработки прерываний от ножки прерываний IRQ_Callback. Первым делом удалим оттуда невостребованную переменную

 

uint16_t dt=0;

 

Уберём мигание светодиодом, мы будем им мигать теперь только при возникновении определённых прерываний

 

LED_TGL;

 

Мигнём светодиодом в случае возникновения прерывания в случае приёма пакета

 

if(status & 0x40)

{

  LED_TGL;

 

Добавим в условия прерываний передачи и ошибки пакетов

 

    rx_flag = 1;

  }

  if(status&TX_DS) //tx_ds == 0x20

  {

    LED_TGL;

    NRF24_WriteReg(STATUS, 0x20);

    NRF24L01_RX_Mode();

    tx_flag = 1;

  }

  else if(status&MAX_RT)

  {

    NRF24_WriteReg(STATUS, 0x10);

    NRF24_FlushTX();

  }

}

 

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

 

uint8_t ErrCnt_Fl = 0;

extern uint32_t TIM1_Count;

extern uint8_t buf1[20];

uint16_t cnt1=0, cnt2=0, cnt3=0;

 

Ну и осталось нам лишь разослать пакеты по нашим приёмникам в функции-обработчике от таймера

 

void TIM1_Callback(void)

{

  if(TIM1_Count%1000==0)

  {

    cnt1++;

    NRF24L01_LCD(cnt1, 0);

    memcpy(buf1,(uint8_t*)&cnt1,2);

    NRF24L01_Send(1, buf1);

    HAL_Delay(1);

  }

  if(TIM1_Count%600==0)

  {

    cnt2++;

    NRF24L01_LCD(cnt2, 1);

    memcpy(buf1,(uint8_t*)&cnt2,2);

    NRF24L01_Send(2, buf1);

    HAL_Delay(1);

  }

  if(TIM1_Count%1500==0)

  {

    cnt3++;

    NRF24L01_LCD(cnt3, 2);

    memcpy(buf1,(uint8_t*)&cnt3,2);

    NRF24L01_Send(3, buf1);

    HAL_Delay(1);

  }

}

 

Здесь мы определяем, на какой именно нам приёмник пора отправлять пакет. Определяем мы это за счёт достижения определённого количества тиков таймера. На первый приёмник мы отправляем пакет приблизительно раз в одну секунду, на второй — раз в 600 милисекунд, на третий — раз в полторы секунды. Задержка в условиях на 1 милисекунду нужна для случая совпадения условий, так как без задержки мы одновременно на несколько передатчиков пакеты не передадим. Я пробовал. Одна милисекунда была выбрана примерно, возможно, достаточно будет и меньшей задержки, так что можете поиграть с задержками в микросекундах, создав прототип на функцию задержки в файле lcd.h.

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

 

 

В качестве первого приёмника мы будем использовать схему первого передатчика на контроллере F103, расположенном на недорогой плате. Только ко всему прочему мы к нему подключим для наглядности 8-разрядный индикатор на микросхеме MAX7219.

Выглядеть наш первый приёмник будет следующим образом

 

 

Проект для него мы сделаем из проекта передатчика урока 113 NRF24_TX_PIPE0 и назовём его NRF24_RX_00.

Запустим наш проект в проектогенераторе Cube MX и включим SPI2 для 8-разрядного индикатора

 

 

Также включим ножку PB12 для ножки CS

 

 

Зайдём в Configuration и настроим наш SPI2

 

 

Сохраним настройки, сгерерируем проект для Keil, откроем его в нём, настроим программатор на авторезет, уровень оптимизации установим в 1.

Файлы max7219.c и max7219.h скопируем в наш проект из проекта урока 107 по датчику температуры DHT22 с одноимённым названием.

Вернёмся в проект и подключим файлы NRF24.c и max7219.c к дереву проекта.

Попробуем собрать наш проект.

Если всё нормально, подключим библиотеку для индикатора в файле main.c

 

#include "NRF24.h"

#include "max7219.h"

 

Из функции main() удалим все локальные переменные

 

//uint8_t dt_reg=0;

uint8_t dt;

uint16_t i=1,retr_cnt_full=0, cnt_lost=0;

 

В этой же функции инициализируем наш индикатор

 

/* USER CODE BEGIN 2 */

HAL_Delay(100);

Init_7219();

NRF24_ini();

 

Из бесконечного цикла удалим полностью весь код.

Идём в файл NRF24.h и подключим туда тоже библиотеку индикатора

 

#include "stm32f1xx_hal.h"

#include "max7219.h"

 

Переходим теперь в файл NRF24.c.

Изменим там количество байт в пакете, а также адрес, так как мы его изменили в передатчике

 

#define TX_PLOAD_WIDTH 5

uint8_t TX_ADDRESS[TX_ADR_WIDTH] = {0xb5,0xb5,0xa1};

 

Добавим после функции передачи пакета NRF24L01_Send функцию приёма пакета, для нас она уже стандартная

 

//------------------------------------------------

void NRF24L01_Receive(void)

{

  uint8_t status=0x01;

  while((GPIO_PinState)IRQ == GPIO_PIN_SET) {}

  status = NRF24_ReadReg(STATUS);

  DelayMicro(10);

  status = NRF24_ReadReg(STATUS);

  if(status & 0x40)

  {

    NRF24_Read_Buf(RD_RX_PLOAD,RX_BUF,TX_PLOAD_WIDTH);

    LED_TGL;

    Clear_7219();

    Number_7219(*(uint16_t*)RX_BUF);

    NRF24_WriteReg(STATUS, 0x40);

  }

}

//------------------------------------------------

 

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

Вернёмся в файл main.c и в функции main() в бесконечном цикле вызовем функцию приёма пакета

 

/* USER CODE BEGIN 3 */

  NRF24L01_Receive();

}

 

Соберём код, прошьём контроллер и увидим, что пакеты от передатчика нам приходят нормально

 

 

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

Теперь подключим наш первый приёмник от независимого источника питания, причём через USB OTG разъём, так как ST-LINK нам потребуется для второго передатчика. У меня есть ещё две платы F103, но нет ST-Link-ов дешевых, через которые можно питать платы. Вот одну из этих плат мы и возьмём в качестве второго приёмника. Выглядит она несколько по-другому, чем наша привычная, но смысл тот же, причём такая даже удобнее

 

 

Подключим к плате модуль NRF и индикатор к тем же ножкам, что и у первого передатчика, а также подключим ST-Link

 

 

Подключим ST-Link к ПК.

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

 

 

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

Модуль NRF24L01+ с антенной можно купить здесь NRF24L01+

Модуль NRF24L01+ без антенны можно купить здесь NRF24L01+

Адаптер для NRF24L01 можно купить здесь (5 штук) Адаптер для NRF24L01

Индикатор светодиодный семиразрядный с драйвером MAX7219

 

 

Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)

STM NRF24L01. Несколько приемников

 

Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)

STM NRF24L01. Несколько приемников

1 комментарий к “STM Урок 115. NRF24L01. Несколько приемников. Часть 1

  1. «Задержка в условиях на 1 милисекунду нужна для случая совпадения условий»

    Вместо этого лучше отслеживать прерывания от ножки irq по окончании передачи. Но для этого нужно немного логику изменить.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

*