Продолжим работу с интересным беспроводным приёмо-передатчиком NRF24L01. И сегодня, продолжая развивать нашу тему программирования передачи данных по беспроводным каналам, попытаемся на плате, выступающей в роли приёмника, принять пакеты одновременно с трёх модулей NRF. У меня это получилось, и поэтому хочу незамедлительно этим поделиться и с вами.
Во-первых, чтобы наглядно проанализировать приём пакетов на приёмном устройстве, недостаточно 8 разрядов нашего любимого индикатора. Я даже купил ещё два таких, но мне не удалось их связать между собой каскадным способом, может модули корявые, может ещё что, вообщем, напряжение на входе каждого модуля было гораздо выше напряжения на выходе. Ну да ладно, я уже придумал, куда все три индикатора использовать в будущем.
Поэтому в качестве средства вывода информации я решил использовать наш любимый символьный дисплей 20×4 с переходником I2C, по которому было достаточно много уроков, да и, к тому же, ко мне приехал ещё один такой дисплей и я решил использовать его. Он уже синего цвета. а не зелёного и символы он отображает белым цветом, будет хоть какое-то разнообразие. Но всё дело не в этом. Переходник в этот дисплей был уже впаян. Данный дисплей у меня почему-то запустился не во всех моих проектах. Я попробовал запустить его, используя библиотеку Arduino, и он запустился. Я начал внимательно изучать данную библиотеку и нашел там много интересного и недокументированного в даташите на дисплей. Поэтому я решил и данными исправлениями с вами поделиться тоже, так как, судя по многим комментариям, у многих тоже дисплеи не запускаются, может заодно и им урок мой поможет. Причём, код работает ведь не только на дисплей, подключенный через переходник, а также и на дисплей, подключенный напрямую к ножкам портов контроллера посредством четырех- и восьмибитного способа подключения ножек данных.
Вот такой вот у нас план.
Поэтому начнём его как-то реализовывать.
Правда, вот ещё с чем я столкнулся. Нельзя, оказывается, использовать одновременно I2C и SPI одного номера, то есть I2C1 и SPI1 или I2C3 и SPI3. Я не стал разбираться, почему так. Посто предупреждаю заранее. Нужно выбирать данные шины разноканальные. То есть если мы используем SPI1, то использовать мы можем I2C2 или I2C3, но никак не I2C1.
Подключим пока что один наш передатчик, тот который у нас и был, правда датчик температуры нам уже подключать не надо, а зальём мы в него прошивку из урока 105. Затем мы передатчик подключим от независимого источника, а к ПК подключим приёмник, то есть плату Nucleo F401.
Пока у нас всё будет выглядеть вот так. На дисплее квадратики
К каким именно ножкам подключать дисплей, мы увидим, когда настроим проект в Cube MX.
Проект для приёмника создадим из проекта урока 109 NRF24_RX_DHT22 и назовём его NRF24_RX_LCD.
Файлы max7219.c и max7219.h из папок нового проекта можно удалить.
Откроем наш проект в Cube MX, отключим шину SPI3
Ножку PA15, которая у нас работала в качестве ножки CS для SPI3, мы тоже можем теперь отключить
Ножку порта PA8, которая у нас отвечала за контакт модуля NRF CE, также отключим, иначе мы не включим I2C
Вместо неё включим ножку PB10, так как она находится недалеко (не забываем также перекоммутировать и проводок)
Теперь мы можем включить шину I2C3
Посмотрим, какие у нас включились ножки, чтобы к ним подключить переходник дисплея
Питание на дисплей подадим от контакта 5 вольт, иначе не хватит контрастности.
Также сигналы на ножке прерываний от модуля NRF мы будем отлавливать аппаратным способом, задействовав на данной ножке внешние прерывания
Переходим в Configuration, откроем настройки I2C, чтобы убедиться, что там всё нормально настроено, а то ведь всякое случается
Также зайдём в NVIC и включим внешние прерывания
Также самое главное, что могут многие забыть, это переключить фронт срабатывания прерываний. У нас они отслеживаются именно по отрицательному фронту (по спаду). Поэтому зайдём в настройки GPIO и для нашей ножки настроим такой режим прерываний
Соберём проект и откроем его в Keil, настроим программатор на автоперезагрузку, включим уровень оптимизации в 1, подключим файл NRF24.c и попробуем наш проект собрать. Он скорей всего не соберётся, сославшись на недостаток библиотеки для индикатора. Да и ладно, он нам не нужен, мы же будем работать с дисплеем LCD. Поэтому из проекта урока 22 I2CLCD80 скопируем в наш проект файлы lcd.c и lcd.h.
Подключим файл lcd.c к проекту, а также в файле main.c удалим подключение библиотеки для индикатора, а подключим файл lcd.h
#include "max7219.h"
#include "lcd.h"
То же самое проделаем и в файле NRF24.h.
Раз уж мы пришли в данный файл, исправим в нём ножку для CE
#define CE_GPIO_PORT GPIOB
#define CE_PIN GPIO_PIN_10
Уберём в этом файле также всё то, что связано с ножкой IRQ, так как прерывания теперь у нас будут обрабатываться аппаратно.
#define IRQ_GPIO_PORT GPIOA
#define IRQ_PIN GPIO_PIN_9
#define IRQ HAL_GPIO_ReadPin(IRQ_GPIO_PORT, IRQ_PIN)
Перейдём в файл NRF24.c и удалим весь код из функции NRF24L01_Receive. Здесь будет позже другой код.
Также в функции передачи NRF24L01_Send уберём также весь код до будущих времён, иначе мы рискуем забыть о прерываниях. Когда будем работать с данной платой в режиме передатчика, тогда код и добавим.
Изменим размер глобального строкового массива
extern char str1[100];
Создадим в самом низу файла функцию для обработки прерывания
//--------------------------------------------------
void IRQ_Callback(void)
{
}
//--------------------------------------------------
Создадим на данную функцию прототип в заголовочном файле и перейдём в файл main.c, где создадим также функцию обработки прерывания, в которой отследим прерывание именно от нашей ножки, вызвав в теле условия только что добавленную нами функцию
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin== GPIO_PIN_9)
{
IRQ_Callback();
}
else
{
__NOP();
}
}
/* USER CODE END 4 */
Удалим также в данном файле строковый массив, он у нас объявлен теперь в другом месте
char str1[150];
Также в функции main() изменим немного инициализацию, так как у нас теперь индикация другая. Останется теперь только вот что
/* USER CODE BEGIN 2 */
LCD_ini();
NRF24_ini();
/* USER CODE END 2 */
Теперь надо поиграться с дисплеем, поэтому сначала перейдём в файл lcd.c и начнём там делать поправки.
Исправим номер шины I2C
extern I2C_HandleTypeDef hi2c3;
Исправим и здесь
HAL_I2C_Master_Transmit(&hi2c3,(uint16_t) 0x4E,buf,1,1000);
Вот теперь наш проект должен будет собраться безошибочно.
Если мы его прошьём, то скорей всего у нас уберутся квадратики на дисплее.
Внесём некоторые изменения в функцию отправки половинки байта в контроллер дисплея
void sendhalfbyte(uint8_t c)
{
c<<=4;
LCD_WriteByteI2CLCD(portlcd|c);
LCD_WriteByteI2CLCD((portlcd|=0x04)|c);//включаем линию E
DelayMicro(1);
LCD_WriteByteI2CLCD((portlcd&=~0x04)|c);//выключаем линию E
DelayMicro(50);
}
Теперь здесь несколько всё происходит по-другому. Мы сначала отправляем данные, включаем линию, затем ждём микросекунду и потом линию отключаем.
Из функции позиционирования уберём все задержки
void LCD_SetPos(uint8_t x, uint8_t y)
{
switch(y)
{
case 0:
sendbyte(x|0x80,0);
HAL_Delay(1);
break;
case 1:
sendbyte((0x40+x)|0x80,0);
HAL_Delay(1);
break;
case 2:
sendbyte((0x14+x)|0x80,0);
HAL_Delay(1);
break;
case 3:
sendbyte((0x54+x)|0x80,0);
HAL_Delay(1);
break;
}
}
Функцию инициализации тоже немного перепишем
void LCD_ini(void)
{
HAL_Delay(50);
LCD_WriteByteI2CLCD(0);
HAL_Delay
(100);
sendhalfbyte(0x03);
DelayMicro(4500);
sendhalfbyte(0x03);
DelayMicro(4500);
sendhalfbyte(0x03);
DelayMicro(200);
sendhalfbyte(0x02);
sendbyte(0x28,0);
//режим 4 бит, 2 линии (для нашего большого дисплея это 4 линии, шрифт 5х8
sendbyte(0x0C,0);
//дисплей включаем (D=1), курсоры никакие не нужны
HAL_Delay(1);
sendbyte(0x01,0);// уберем мусор
HAL_Delay(2);
sendbyte(0x06,0);// пишем влево
HAL_Delay(1);
sendbyte(0x02,0);//курсор на место
HAL_Delay(2);
setled();//подсветка
setwrite();//запись
}
С дисплеем вроде всё.
Идём в файл NRF24.c и начнём сочинять, как мы будем ловить пакеты в обработчике прерываний.
Наша задача состоит в том, чтобы кода в обработчике было как можно меньше, иначе мы рискуем пропустить пакеты. Обрабатывать принятые данные мы будем в функции приёма, которая по-прежнему у нас вызывается циклически в бесконечном цикле.
В функции IRQ_Callback добавим несколько локальных переменных
void IRQ_Callback(void)
{
uint8_t status=0x01;
uint8_t pipe;
uint16_t dt=0;
10 микросекунд нам всё же подождать придется
uint16_t dt=0;
DelayMicro(10);
Мигнём светодиодом
DelayMicro(10);
LED_TGL;
Узнаем статус, и если он тот, какой нам нужен, то считаем буфер в массив и сбросим флаг
LED_TGL;
status = NRF24_ReadReg(STATUS);
if(status & 0x40)
{
NRF24_Read_Buf(RD_RX_PLOAD,RX_BUF,TX_PLOAD_WIDTH);
NRF24_WriteReg(STATUS, 0x40);
}
Соберём проект и прошьём контроллер. Зелёный светодиод должен будет начать мигать приблизительно раз в секунду синхронно со светодиодом передатчика.
Если всё так и есть, то теперь нам надо будет как-то отобразить наши данные, которые мы получили с приёмника, на дисплее. Только всё это отображение мы будем производить в функции другой, не в обработчике. Только нам надо будет об этом как-то данной функции сказать. Сделаем это с помощью флага. Для этого добавим глобальную переменную
uint8_t RX_BUF[TX_PLOAD_WIDTH] = {0};
volatile uint8_t rx_flag = 0;
Вернёмся в наш обработчик IRQ_Callback и включим флаг
NRF24_WriteReg(STATUS, 0x40);
rx_flag = 1;
}
Теперь идём в функцию NRF24L01_Receive, отследим включенный флаг, выводим информацию из буфера на дисплей и сбрасываем флаг, чтобы не зациклиться
void NRF24L01_Receive(void)
{
if(rx_flag==1)
{
LCD_SetPos(0, 0);
sprintf(str1,"%5u %5u ", *(int16_t*)RX_BUF, *(int16_t*)(RX_BUF+2));
LCD_String(str1);
rx_flag = 0;
}
}
Собираем проект, прошиваем контроллер. Также перезагрузим контроллер передатчика, чтобы сбросились попытки передачи при ошибках, чтобы отследить, что у нас всё передаётся безошибочно
Только принимать мы будем сегодня ещё и количество ошибочно переданных пакетов с наших передатчиков, поэтому немного увеличим наш буфер
#define TX_PLOAD_WIDTH 7
Также подправим в функции NRF24L01_Receive вывод на дисплей, чтобы показать все три вида данных — число счёта, количество попыток при ошибках и количество непереданных пакетов
sprintf(str1,"%5u %5u %5u ", *(int16_t*)RX_BUF, *(int16_t*)(RX_BUF+2), *(int16_t*)(RX_BUF+4));
Соберём код, прошьём контроллер. Понятно, что теперь корректного приёма пакетов у нас не будет, так как надо размер буфера исправлять и в проекте передатчика.
В следующей части урока мы подготовим проекты для двух передатчиков, также в проект приёмника добавим возможность приёма пакетов от двух передатчиков и отображения их в разных строках дисплея и проверим наш код на практике.
Предыдущий урок Программирование МК STM32 Следующая часть
Модуль NRF24L01+ с антенной можно купить здесь NRF24L01+
Модуль NRF24L01+ без антенны можно купить здесь NRF24L01+
Адаптер для NRF24L01 можно купить здесь (5 штук) Адаптер для NRF24L01
Датчик температуры и влажности можно приобрести здесь DHT22
Отладочную плату можно приобрести здесь Nucleo STM32F401RE
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Отладочную плату NUCLEO-F303K8 можно купить здесь NUCLEO-F303K8
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Смотреть ВИДЕОУРОК (нажмите на картинку)
NRF01 у меня периодически зависал. Чтобы его победить, включил его питание через транзистор ss8550 от плюс 3.3в. Управление с ноги f103 через 10кОм. Нулём открывается.
В программе проверяю на зависание. Если что, переподключаю ногой питание и запускаю инициализацию снова.
Как ни экспериментировал с питанием — работает стабильно.
max7219 каскадно включал примерно так:
…
// отправка команды «data» в регистр «reg» дисплея «cnt_disp» (первый 0)
// отправка данных по элементно. Каждый каскад отдельно
void send_7219 (uint8_t reg, uint8_t data, uint8_t cnt_disp) {
cs_set();
aTxBuf[0]=reg;
aTxBuf[1]=data;
// если регистр не первый, то сформируем отправим дополнительно столько слов, сколько каскадов нужно пропустить
HAL_SPI_Transmit(&HSPI_7219, (uint8_t*) aTxBuf, (cnt_disp+1)*2, 5000);
cs_reset();
}
…