External Interface Design
Two pins have been reserved. The external interface has four pins: originally, I intended one for VCC, one for GND, and the other two for device control—one for fan speed and one for on/off. The idea was: when air quality is poor, the fan turns on. I planned to use a 12cm PC fan, combined with PM2.5 filter paper, to make an air purifier.
However, I realized I hadn’t considered the switch. Without one, the device would stay on until the battery died. Since I had already printed the case, I repurposed two pins as a switch by breaking the positive line of the battery. Once the pin header is inserted, the positive line is connected, acting as a switch.
Hardware
Dev Board: Ai-Thinker Ai-M61-32S Kit
Sensor: 21VOC 5-in-1 Air Quality Module (TVOC, CH₂O, CO₂, Temp, Humidity)
21VOC (TVOC, Formaldehyde, CO2, Temp & Humidity) Module Manual - V01.01.pdf
21VOC | Ai-M61-32S |
GND | GND |
3V3 | 3V3 |
RX | IO25 |
TX | IO26 |
Wiring Schemes
21VOC to Ai-M61-32S
- GND → GND
- 3V3 → 3V3
- RX → IO25
- TX → IO26
- GND → GND
- BUTTON → IO18
- Pin1 → Bin+
- Pin2 → Bout+
- Pin3 → IO reserved
- Pin4 → IO reserved
Display: 1.3” TFT (240x240)
Button to Ai-M61-32S
Vent Holes: Small perforated aluminum mesh (diamond or hexagonal holes).
External Interface (4-Pin) Mainly used to control external devices or serve as a switch.
UI Design (LVGL)
The detection device UI doesn’t need to be fancy, so I built a simple layout.
- Create a new project
- Choose LVGL v8.3.10
- Select device template
- Select application template
In project config: set panel type to Custom and name it. My screen is 240x240, so I set that resolution.
Code
voc.h
#ifndef VOC_H
#define VOC_H
typedef enum {
SINGLE_CLICK,
DOUBLE_CLICK,
LONG_CLICK,
NONE_CLICK,
} click_t;
void voc_init(void);
float convert_temperature(float temperature);
void voc21Task (void *pvParameters);
void send_sensor_data(int voc, int ch2o, int eco2, int temperature, int humidity);
#endif
voc.c
#include "bflb_mtimer.h"
#include "board.h"
#include "bflb_uart.h"
#include "bflb_gpio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "cJSON.h"
#include "math.h"
#include <FreeRTOS.h>
#define DBG_TAG "MAIN"
#include "log.h"
#include <task.h>
#include <queue.h>
#include "custom.h"
#define BUFFER_SIZE 1024*2
// UART serial port reading mov21
struct bflb_device_s *voc_uart;
// Total number of cached data, default mov21 data length is 12 bytes starting with 0x2C
int BUFFER_LEN = 12;
// Current array index
int voc_index = 0;
// Data reading status flag
int flag = 0;
// Data buffer array
uint8_t UART_RECEIVE_BUFFER[12];
custom_event_t custom_event = CUSTOM_EVENT_GET_PM25_DATA;
extern QueueHandle_t queue;
float convert_temperature(uint16_t raw) {
// Check if the highest bit is 1 (negative case)
if (raw & 0x8000) {
return -(0xFFFF - raw) * 0.1f;
}
return raw * 0.1f;
}
static void uart_isr(int irq, void* arg)
{
uint32_t intstatus = bflb_uart_get_intstatus(voc_uart);
uint32_t rx_data_len = 0;
char* queue_buff = pvPortMalloc(64);
if (intstatus & UART_INTSTS_RX_FIFO) {
LOG_I("rx fifo\r\n");
while (bflb_uart_rxavailable(voc_uart)) {
int ch = bflb_uart_getchar(voc_uart);
...
Read more »
Ai-Thinker
Pradeeplogu
Maker-fabs-J
C A Fillekes