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 к ПК.

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

 

Предыдущий урок Программирование МК STM32 Следующая часть

 

 

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

Модуль NRF24L01+ без антенны можно купить здесь (целых 4 штуки) NRF24L01+

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

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

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

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

 

 

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

 

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

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

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

*