Целью этого проекта Bluetooth LE является считывание данных датчика качества воздуха и отображение их на ЖК-дисплее, подключенном к плате STM32. Веб-браузер считывает данные датчика и передает их на плату STM32 с помощью BleuIO.
1. Введение
Проект основан на STM32 Nucleo-144, который управляет ЖК-дисплеем с помощью BleuIO.
Для этого проекта нам понадобятся два USB-ключа BleuIO, один из которых подключен к плате Nucleo, а другой — к компьютеру, на котором запущен веб-скрипт, и HibouAir — устройство для мониторинга качества воздуха.
Когда ключ BleuIO подключенный к USB-порту платы Nucleo, STM32 распознает его и сразу начнет рекламу. Это позволяет ключу на порту компьютера подключаться к веб-скрипту.
С помощью веб-скрипта на компьютере мы можем сканировать и получать данные датчика качества воздуха от HibouAir. Затем мы отправляем эти данные на ЖК-экран, подключенный к STM32 с помощью Bluetooth.
В этом примере мы использовали плату разработки STM32 Nucleo-144 с микроконтроллером STM32H743ZI (STM32H743ZI с поддержкой micro mbed Development Nucleo-144, серия ARM® Cortex®-M7 MCU, 32-разрядная встроенная оценочная плата). Эта плата разработки имеет USB-хост, к которому мы подключаем ключ BleuIO.
Если вы хотите использовать другую настройку, вам нужно убедиться, что она поддерживает USB-хост, и помнить, что настройка GPIO может отличаться и может потребоваться перенастройка в файле .ioc.
О Кодексе
Исходный код проекта доступен на Github.
https://github.com/smart-sensor-devices-ab/stm32_ble_sensor_lcd.git
Либо клонируйте проект, либо загрузите его в виде zip-файла и разархивируйте его в рабочее пространство STM32CubeIDE.
Если вы загружаете проект в виде zip-файла, вам нужно будет переименовать папку проекта с «stm32_bleuio_lcd-master» на «stm32_bleuio_lcd».
Подключите SDA к PF0 на плате Nucleo, а SCL к PF1.
Затем настройте I2C2 в файле ioc STM32Cube следующим образом. (Обязательно измените частоту скорости I2C на 50 кГц в соответствии с требованиями ЖК-дисплея.)
В функции USBH_CDC_ReceiveCallback в USB_HOST\usb_host.c мы копируем CDC_RX_Buffer во внешнюю переменную с именем dongle_response, доступную из файла main.c.
void USBH_CDC_ReceiveCallback(USBH_HandleTypeDef *phost) { if(phost == &hUsbHostFS) { // Handles the data recived from the USB CDC host, here just printing it out to UART rx_size = USBH_CDC_GetLastReceivedDataSize(phost); HAL_UART_Transmit(&huart3, CDC_RX_Buffer, rx_size, HAL_MAX_DELAY);
// Copy buffer to external dongle_response buffer strcpy((char *)dongle_response, (char *)CDC_RX_Buffer);
// Reset buffer and restart the callback function to receive more data memset(CDC_RX_Buffer,0,RX_BUFF_SIZE); USBH_CDC_Receive(phost, CDC_RX_Buffer, RX_BUFF_SIZE); }
return; }
В main.c мы создаем простой интерпретатор, чтобы мы могли реагировать на данные, которые мы получаем от ключа.
void dongle_interpreter(uint8_t * input) {
if(strlen((char *)input) != 0) { if(strstr((char *)input, "\r\nADVERTISING...") != NULL) { isAdvertising = true; } if(strstr((char *)input, "\r\nADVERTISING STOPPED") != NULL) { isAdvertising = false; } if(strstr((char *)input, "\r\nCONNECTED") != NULL) { isConnected = true; HAL_GPIO_WritePin(GPIOE, GPIO_PIN_1, GPIO_PIN_SET); } if(strstr((char *)input, "\r\nDISCONNECTED") != NULL) { isConnected = false; HAL_GPIO_WritePin(GPIOE, GPIO_PIN_1, GPIO_PIN_RESET); }
if(strstr((char *)input, "L=0") != NULL) {
isLightBulbOn = false; //HAL_GPIO_WritePin(Lightbulb_GPIO_Port, Lightbulb_Pin, GPIO_PIN_RESET); lcd_clear();
writeToDongle((uint8_t*)DONGLE_SEND_LIGHT_OFF);
uart_buf_len = sprintf(uart_tx_buf, "\r\nClear screen\r\n"); HAL_UART_Transmit(&huart3, (uint8_t *)uart_tx_buf, uart_buf_len, HAL_MAX_DELAY); }
if(strstr((char *)input, "L=1") != NULL) { isLightBulbOn = true; writeToDongle((uint8_t*)DONGLE_SEND_LIGHT_ON);
lcd_clear();
lcd_write(input);
}
} memset(&dongle_response, 0, RSP_SIZE); }
Мы помещаем функцию интерпретатора внутрь основного цикла.
/* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ MX_USB_HOST_Process();
/* USER CODE BEGIN 3 */ // Simple handler for uart input handleUartInput(uartStatus);
// Inteprets the dongle data dongle_interpreter(dongle_response);
// Starts advertising as soon as the Dongle is ready. if(!isAdvertising && !isConnected && isBleuIOReady) { HAL_Delay(200); writeToDongle((uint8_t*)DONGLE_CMD_AT_ADVSTART); isAdvertising = true; } } /* USER CODE END 3 */
Использование примера проекта
Что нам понадобится
- Два ключа BleuIO (https://www.bleuio.com/)
- Плата с микроконтроллером STM32 с портом USB. (При разработке этого примера использовалась макетная плата Nucleo-144: NUCLEO-H743ZI2. (https://www.st.com/en/evaluation-tools/nucleo-h743zi.html)
Для подключения к плате Nucleo можно использовать кабель USB A — Micro USB B с адаптером USB A мама-мама.)
- STM32CubeIDE (https://www.st.com/en/development-tools/stm32cubeide.html)
- Модуль жидкокристаллического дисплея — NHD-0420D3Z-NSW-BBW-V3 (https://www.digikey.com/en/products/detail/newhaven-display-intl/NHD-0420D3Z-NSW-BBW-V3/2626390? s=N4IgTCBcDaIHIAkAiBaADAFjGpBmAWinAMoDqKAQheQGq4gC6AvkA)
- HibouAir — устройство для мониторинга качества воздуха (https://www.hibouair.com/)
Импорт в качестве существующего проекта
В STM32CubeIDE выберите «Файл»> «Импорт…».
Затем выберите «Общие»> «Существующие проекты» в рабочей области, затем нажмите «Далее»
Убедитесь, что вы выбрали свое рабочее пространство в «Выбрать корневой каталог:»
Вы должны увидеть проект «stm32_bleuio_SHT85_example», отметьте его и нажмите «Готово».
Запуск примера
Загрузите код в STM32 и запустите пример. USB-ключ, подключенный к STM32, начнет рекламу автоматически.
Отправка данных датчика на ЖК-экран из веб-браузера
Подключите ключ BleuIO к компьютеру. Запустите веб-скрипт для подключения к другому ключу BleuIO на STM32. Теперь вы можете отправлять данные датчика на ЖК-экран.
Для работы этого скрипта нам нужно
- USB-ключ BleuIO, подключенный к компьютеру.
- JavaScript-библиотека BleuIO
- Chrome 78 или более поздней версии, и вам необходимо включить флаг #enable-experimental-web-platform-features в chrome://flags.
- Веб-бандер — (parcel js)
Создайте простой HTML-файл с именем index.html, который будет служить интерфейсом скрипта. Этот HTML-файл содержит несколько кнопок, которые помогают подключаться, считывать рекламные данные с HibouAir для получения данных датчика качества воздуха и отправлять эти данные на ЖК-экран, подключенный к stm32.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" /> <title>Bluetooth LE Air quality sensor data to LCD screen</title> </head> <body class="mt-5"> <div class="container mt-5"> <img src="https://www.bleuio.com/blog/wp-content/themes/bleuio/images/logo.png" /> <h1 class="mb-5">Bluetooth LE Air quality sensor data to LCD screen</h1>
<div class="row"> <div class="col-md-4 pt-5"> <button class="btn btn-success mb-2" id="connect">Connect</button> <form method="post" id="sendDataForm" name="sendMsgForm" hidden> <div class="mb-3"> <label for="sensorID" class="form-label">Sensor ID</label> <input type="text" class="form-control" name="sensorID" id="sensorID" required maxlength="60" value="0578E0" /> </div>
<button type="submit" class="btn btn-primary">Get Data</button> </form> <br /> <button class="btn btn-danger" id="clearScreen" disabled> Clear screen </button> </div> <div class="col-md-8"> <img src="air_quality_lcd.jpg" class="img-fluid" /> </div> </div> </div>
<script src="script.js"></script> </body> </html>
Создайте файл js с именем script.js и включите его в конец файла HTML. Этот js-файл использует js-библиотеку BleuIO для написания AT-команд и связи с другим ключом.
import * as my_dongle from 'bleuio' import 'regenerator-runtime/runtime'
const dongleToConnect='[0]40:48:FD:E5:2F:17' //const sensorID = '0578E0' document.getElementById('connect').addEventListener('click', function(){ my_dongle.at_connect() document.getElementById("clearScreen").disabled=false; document.getElementById("connect").disabled=true; document.getElementById("sendDataForm").hidden=false; })
document.getElementById("sendDataForm").addEventListener("submit", function(event){ event.preventDefault() const sensorID = document.getElementById('sensorID').value getSensorData(sensorID) setInterval(function () {getSensorData(sensorID)}, 10000);
});
const getSensorData =((sensorID)=>{ my_dongle.ati().then((data)=>{ //make central if not if(JSON.stringify(data).includes("Peripheral")){ console.log('peripheral') my_dongle.at_dual().then((x)=>{ console.log('central now') }) } }) .then(()=>{ // connect to dongle my_dongle.at_getconn().then((y)=>{ if(JSON.stringify(y).includes(dongleToConnect)){ console.log('already connected') }else{ my_dongle.at_gapconnect(dongleToConnect).then(()=>{ console.log('connected successfully') }) } }) .then(async()=>{ return my_dongle.at_findscandata(sensorID,6).then((sd)=>{ console.log('scandata',sd) let advData = sd[sd.length - 1].split(" ").pop() let positionOfID= advData.indexOf(sensorID); let tempHex = advData.substring(positionOfID+14, positionOfID+18) let temp = parseInt('0x'+tempHex.match(/../g).reverse().join(''))/10;
let co2Hex = advData.substring(positionOfID+38, positionOfID+42) let co2 = parseInt('0x'+co2Hex); //console.log(temp,co2) return { 'CO2' :co2, 'Temp' :temp, } }) }) .then((x)=>{ console.log(x.CO2) console.log(x.Temp) var theVal = "L=1 SENSOR ID "+sensorID+" TEMPERATURE " + x.Temp + ' °c CO2 '+ x.CO2+' ppm'; console.log('Message Send 1 ') // send command to show data my_dongle.at_spssend(theVal).then(()=>{ console.log('Message Send '+theVal) }) }) }) })
document.getElementById('clearScreen').addEventListener('click', function(){ my_dongle.ati().then((data)=>{ //make central if not if(JSON.stringify(data).includes("Peripheral")){ console.log('peripheral') my_dongle.at_central().then((x)=>{ console.log('central now') }) } }) .then(()=>{ // connect to dongle my_dongle.at_getconn().then((y)=>{ if(JSON.stringify(y).includes(dongleToConnect)){ console.log('already connected') }else{ my_dongle.at_gapconnect(dongleToConnect).then(()=>{ console.log('connected successfully') }) } }) .then(()=>{ // send command to clear the screen my_dongle.at_spssend('L=0').then(()=>{ console.log('Screen Cleared') }) }) }) })
В скрипте есть кнопка для подключения к COM порту на компьютере. Имеется текстовое поле, в котором вы можете написать идентификатор датчика устройства контроля качества воздуха. После подключения скрипт попытается получить объявленные данные от датчика и преобразовать их в значимые данные. После этого он отправит эти данные на плату STM32, которые затем отобразятся на ЖК-экране.
Чтобы подключиться к ключу BleuIO на STM32, убедитесь, что STM32 включен и к нему подключен ключ BleuIO.
Получить MAC-адрес
Следуйте инструкциям, чтобы получить MAC-адрес ключа, подключенного к STM32.
- Open this site https://bleuio.com/web_terminal.html and click connect to dongle.
- Select the appropriate port to connect.
- Once it says connected, type ATI. This will show dongle information and current status.
- If the dongle is on peripheral role, set it to central by typing AT+CENTRAL
- Now do a gap scan by typing AT+GAPSCAN
- Once you see your dongle on the list ,stop the scan by pressing control+c
- Copy the ID and paste it into the script (script.js) line #4
Запустите веб-скрипт
Вам понадобится веб-бандер. Вы можете использовать parcel.js
После установки пакета js перейдите в корневой каталог веб-скрипта и введите "parcel index.html". Это запустит вашу среду разработки.
Откройте скрипт в браузере. Для этого примера мы открыли https://localhost:1234
Вы можете легко подключиться к ключу и увидеть данные о качестве воздуха на ЖК-экране. Ответ отобразится на экране консоли браузера.
Веб-скрипт выглядит так
Выход
Сообщение отобразится на ЖК-экране.