STM Урок 117. NRF24L01. Обмен информацией

 

 

 

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

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

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

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

Также программатор мы отключать от них уже не будем. Я вспомнил, что у меня есть оригинальный ST-Link, и подключил его ко 2-му устройству. А питать мы будем постоянно оба устройства, поэтому с дешёвым программатором мы по питанию соединяться не будем, будем использовать только 3 провода, а на оригинальный ST-Link будем либо вести 4 провода, либо соединять его с платой 20-пинным шлейфом.

Подключим пока только схему 1-го устройства

 

 

Проект для данной схемы мы создадим из проекта урока 115 для первого приёмника NRF24_RX_00. Назовём мы наш новый проект NRF24_RXTX_00.

Запустим проект в Cube MX и включим таймер, так как у нас это будет ещё и передатчик

 

 

На ножке PA2 мы теперь внешние прерывания от модуля будем отслеживать аппаратно, поэтому переведём её в соответствующий режим

 

 

Перейдём в Configuration и настроим наш таймер

 

 

Включим прерывания

 

 

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

 

 

Перейдём в GPIO и установим на ножку прерываний срабатывание по спаду

 

 

Сгенерируем проект, запустим его в Keil, настроим программатор на авторезет, включим уровень оптимизации 1, подключим файлы NRF24.c и max7219.c.

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

 

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

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

 

Немного добавим размер буфера хранения

 

uint8_t RX_BUF[TX_PLOAD_WIDTH+1] = {0};

 

Также добавим флаги для принятого и переданного пакетов

 

uint8_t RX_BUF[TX_PLOAD_WIDTH+1] = {0};

volatile uint8_t rx_flag = 0, tx_flag = 0;

 

В функции перевода в режим приёмники NRF24L01_RX_Mode добавим код изменения адреса

 

NRF24_WriteReg(CONFIG,regval);

NRF24_Write_Buf(TX_ADDR, TX_ADDRESS1, TX_ADR_WIDTH);

NRF24_Write_Buf(RX_ADDR_P0, TX_ADDRESS1, TX_ADR_WIDTH);

 

А в функции перевода в режим передатчика NRF24L01_TX_Mode мы в строке изменения адреса немного изменим имя макроса адреса, а также добавим строку изменения адреса канала PIPE0

 

NRF24_Write_Buf(TX_ADDR, TX_ADDRESS0, TX_ADR_WIDTH);

NRF24_Write_Buf(RX_ADDR_P0, TX_ADDRESS0, TX_ADR_WIDTH);

 

В функции инициализации подправим имя макроса, добавив к нему нолик

 

NRF24_Write_Buf(TX_ADDR, TX_ADDRESS0, TX_ADR_WIDTH);

NRF24_Write_Buf(RX_ADDR_P0, TX_ADDRESS0, TX_ADR_WIDTH);

 

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

 

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

void IRQ_Callback(void)

{

  uint8_t status=0x01;

  uint8_t pipe;

  DelayMicro(10);

  status = NRF24_ReadReg(STATUS);

  if(status & 0x40)

  {

    LED_TGL;

    pipe = (status>>1)&0x07;

    NRF24_Read_Buf(RD_RX_PLOAD,RX_BUF,TX_PLOAD_WIDTH);

    *(RX_BUF+5) = pipe;

    NRF24_WriteReg(STATUS, 0x40);

    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();

    //Уходим в режим приёмника

    NRF24L01_RX_Mode();

  }

}

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

 

Также ниже добавим ещё функцию-обработчик прерываний от таймера, пока пустую

 

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

void TIM1_Callback(void)

{

}

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

 

Добавим на наши функции-обработчики прототипы и перейдём в файл main.c, в котором добавим обработчик внешних прерываний от ножки

 

/* USER CODE BEGIN 4 */

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

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)

{

  if(GPIO_Pin== GPIO_PIN_2)

  {

    IRQ_Callback();

  }

  else

  {

    __NOP();

  }

}

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

/* USER CODE END 4 */

 

Ниже добавим функцию-обработчик от таймера, в котором мы просто вызовем нашу функцию, которую мы добавили в нашу библиотеку модуля

 

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

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  if(htim==&htim2)

  {

    TIM1_Callback();

  }

}

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

 

Также запустим наш таймер в функции main()

 

NRF24_ini();

HAL_TIM_Base_Start_IT(&htim2);

 

Перейдём в файл NRF24.h и удалим там макросы для ножки IRQ, так как мы уже данную ножку обрабатываем аппаратно

 

#define IRQ_GPIO_PORT GPIOA

#define IRQ_PIN GPIO_PIN_2

#define IRQ HAL_GPIO_ReadPin(IRQ_GPIO_PORT, IRQ_PIN)

 

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

 

uint8_t NRF24L01_Send(uint8_t *pBuf)

{

  uint8_t regval=0x00;

  NRF24L01_TX_Mode(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;

}

 

В файле max7219.c добавим функции очистки левой и правой частей индикатора отдельно (левых и правых 4 разрядов) после функции очистки индикатора Clear_7219

 

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

void ClearL_7219 (void)

{

  uint8_t i;

  for(i=5;i<=8;i++)

  {

    Send_7219(i,0xF);//символ пустоты

  }

}

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

void ClearR_7219 (void)

{

  uint8_t i;

  for(i=1;i<=4;i++)

  {

    Send_7219(i,0xF);//символ пустоты

  }

}

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

 

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

Вернёмся в файл NRF24.c и исправим также функцию приёма. Там будет практически только отображение информации на индикаторе

 

void NRF24L01_Receive(void)

{

  if(rx_flag==1)

  {

    ClearL_7219();

    NumberL_7219(*(int16_t*)RX_BUF);

    rx_flag = 0;

  }

}

 

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

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

 

volatile uint8_t rx_flag = 0, tx_flag = 0;

uint32_t TIM1_Count=0;

uint16_t cnt1=0;

extern uint8_t buf1[20];

 

Теперь добавим код в обработчик таймера

 

void TIM1_Callback(void)

{

  if(TIM1_Count%2000==0)

  {

    cnt1++;

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

    NRF24L01_Send(buf1);

    ClearR_7219();

    Number_7219(*(uint16_t*)buf1);

    if(cnt1>=9999) cnt1=0;

    HAL_Delay(1);

  }

  TIM1_Count++;

  if(TIM1_Count>3000000) TIM1_Count=0;

}

 

Раз в 2 секунды мы будем отправлять пакет со значением счётчика на адрес второго устройства и отображать данное значение в правой части индикатора. Затем мы переходим в режим приёмника и ждём пакет от второго устройства и отображаем его содержимое в левой части индикатора.

Соберём код, прошьём контроллер. Только пока мы вряд ли увидим какие-то результаты, так как второе устройство ещё не умеет отвечать на пакеты. Принимать оно уже скорей всего их умеет, так как код остался с прошлого занятия, а отправлять пока не умеет. Но пока ещё нет и приёма, так как мы ещё второе устройство не подключали. Отключим ST-Link первого устройства от компьютера и займёмся вторым устройством. В качестве второго устройства у нас будет плата, которую мы использовали в уроке 115 для второго передатчика.

Как к нему подключать дешёвый ST-Link, мы уже знаем. Поэтому сегодня мы к нему подключим фирменный ST-Link (нажмите на картинку для увеличения изображения)

 

 

Сначала подключим четырьмя проводами. В данном случае питание подводить к ST-Link надо, но играет провод питание уже другую роль. Плата не питается от программатора, а, наоборот, ST-Link отслеживает то, что плата питается. Рассмотрим подключение покрупнее

 

 

Также мы можем подключить ST-Link именно к такой плате шлейфом, идущим в комплекте с программатором

 

 

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

Подключим питание к плате и подключим ST-Link к ПК.

Проект мы сделаем из проекта первого устройства и назовём его уже NRF24_RXTX_01.

Запустим проект в Cube MX и, ничего в нём не трогая, откроем его в Keil. Произведём традиционные настройки (авторезет и 1 уровень оптимизации), добавим файлы NRF24.c и max7219.c и попробуем собрать проект.

Если всё собралось без ошибок, то продолжим.

С этим проектом будет не всё так просто. Нам надо не просто поменять адрес, но и научить устройство отзываться.

В функции main() файла main.c уберём запуск таймера. Мы его будем запускать, только когда примем пакет

 

HAL_TIM_Base_Start_IT(&htim2);

 

Перейдём в файл NRF24.c и поменяем там местами адреса для приёма и передачи

 

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

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

 

Подключим хендл таймера

 

extern SPI_HandleTypeDef hspi1;

extern TIM_HandleTypeDef htim2;

 

Подправим тело условия в функции NRF24L01_Receive

 

if(rx_flag==1)

{

  cnt1=*(int16_t*)RX_BUF;

  ClearL_7219();

  NumberL_7219(cnt1);

  rx_flag = 0;

  TIM1_Count=0;

  HAL_TIM_Base_Start_IT(&htim2);

}

 

Вот тут мы и запускаем наш таймер, перед этим обнуляя все счётчики.

Теперь также внесём изменение и в функцию обработки событий от таймера

 

void TIM1_Callback(void)

{

  if(TIM1_Count>=1000)

  {

    cnt1 = 9999 - cnt1;

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

    NRF24L01_Send(buf1);

    HAL_TIM_Base_Stop_IT(&htim2);

    ClearR_7219();

    Number_7219(*(uint16_t*)buf1);

    TIM1_Count = 0;

  }

  TIM1_Count++;

}

 

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

Соберём пакет и прошьём контроллер. Получим вот такой результат

 

 

Слева модуль 2, а справа — первый. Всё работает прекрасно. Первый модуль передаёт данные раз в секунду, а второй отлично их принимает и в ответ передаёт свои данные, которые с успехом принимает первый.

Я пробовал передавать побыстрее, а также отвечать. Передавал я пакеты раз в 20 милисекунд и через 1 милисекунду на них отвечал. Всё отлично работает.

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

Всех благодарю за внимание!

 

 

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

 

Исходный код 1 устройства

Исходный код 2 устройства

 

 

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

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

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

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

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

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

 

 

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

 

STM NRF24L01. Обмен информацией

2 комментария на “STM Урок 117. NRF24L01. Обмен информацией
  1. magik:

    Уважаемый автор (к сожалению, не знаю как Вас зовут)! Выражаю огромную благодарность в столь понятных, актуальных (DS1820, LCD, TFT и прочие популярные устройства) и полезных уроках!
    Хотел бы спросить: будут ли уроки по взаимодействию МК (например, stm32f103) с GSM-модулями (например, sim900 (я покупал на алиекспрессе по ссылке https://ru.aliexpress.com/item/SIM900-GPRS-GSM-Shield-Development-Board-Quad-Band-Module-For-Arduino-Compatible/32649115586.html?spm=2114.13010708.0.0.MHBs9o))? Особенно интересует не просто отправка смс-ок, а передача информации с модуля на сайт (построенный, например, на WordPress). Было бы здорово еще и организовать хранение принятой информации в базе данных.
    Заранее благодарен за ответ. Еще раз большое спасибо за Ваши великолепные уроки!

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

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

*