嵌入式安防监控项目———真实数据上传
前言
本实验计划采用STM32作为终端控制器,搭载E22-400T22D LoRa模块,通过定点传输模式,将采集到的传感器数据发送至IMX6ULL主控端,并最终在前端网页上进行实时刷新与显示。
LORA介绍
E22-400T22D是一款基于SEMTECH LoRa扩频方案的UART串口无线模块,工作于410.125~493.125MHz频段(默认433.125MHz),发射功率达22dBm,理想通信距离可达5km。模块采用TTL电平输出,兼容3.3V与5V IO口电压,尺寸仅为21×36mm,重量约6.7g,适用于嵌入式设备集成。在本实验中,它作为数据链路的无线承载层,实现STM32终端与IMX6ULL网关之间的可靠数据传输。
E22-400T22D模块提供了四种工作模式,由硬件引脚 M1 和 M0 的高低电平组合来控制。您可以根据实际应用场景,在功耗、响应速度和配置灵活性之间做出选择。
| 模式 | M1引脚 | M0引脚 | 模式介绍 |
|---|---|---|---|
| 0 传输模式 | 0 | 0 | 串口打开,无线打开,透明传输 |
| 1 WOR模式 | 0 | 1 | 可定义为WOR发送方或WOR接收方,支持空中唤醒 |
| 2 配置模式 | 1 | 0 | 用户可通过串口访问寄存器,控制模块工作状态 |
| 3 深度休眠 | 1 | 1 | 模块进入休眠 |
E22-400T22D模块在传输模式(模式0,M1=0,M0=0)下,根据数据包的目标寻址方式,可进一步细分为三种传输方式:透传模式、定点传输和广播模式。其中,定点传输是本实验采用的方式,通过在数据包中指定目标模块的地址,实现精准寻址通信,确保传感器数据仅被IMX6ULL网关接收,有效避免同频干扰。以下是三种方式的对比:
| 传输方式 | 核心特点 | 适用场景 |
|---|---|---|
| 透传模式 | 串口收到的数据原封不动无线发出,接收端直接串口输出,不解析地址 | 简单的一对一通信,无需区分设备 |
| 定点传输 | 发送数据时携带目标模块地址,仅地址匹配的模块接收 | 多设备环境下的精准寻址通信(本实验采用) |
| 广播模式 | 同信道内所有模块均可接收,无需指定目标地址 | 系统通知、参数同步等全局下发场景 |
要实现E22-400T22D模块的定点传输,发送端需要在普通数据前补全3个字节作为数据帧头,格式为:
[目标地址高字节] [目标地址低字节] [目标信道] [用户数据]
示意图如下图所示:
假设您的接收端(IMX6ULL侧)LoRa模块配置的地址为0x0000,信道为0x17,需要发送字符串"Hello"(十六进制:48 65 6C 6C 6F),那么完整的待发送数据帧为:00 00 17 48 65 6C 6C 6F
- 00 00 → 目标地址高/低字节(指向地址为0x0000的接收端)
- 17 → 目标信道(0x17)
- 48 65 6C 6C 6F → 实际数据(“Hello”)
实验开始前,需对两端LoRa模块进行如下地址分配:IMX6ULL网关端的LoRa模块地址配置为 0x00,信道配置为 0x17;STM32终端端的LoRa模块地址配置为 0x01,信道配置为 0x18。后续数据交互中,STM32端向IMX6ULL发送数据时,需在帧头指定目标地址为 00、目标信道为 17;反之,IMX6ULL向STM32发送数据时,则需指定目标地址为 01、目标信道为 18。
具体上位机配置如下图所示:
STM32采集温湿度数据并通过LoRa定点上传
本次实验采用 STM32F103C8T6 作为终端控制器,搭载E22-400T22D LoRa模块,通过定点传输方式将采集到的温湿度传感器数据发送至IMX6ULL网关端。
LORA和单片机连接如图所示:
STM32分配的引脚如下表:
| 引脚功能 | STM32端口 | STM32引脚 | 功能说明 |
|---|---|---|---|
| M0 | GPIOB | Pin 6 | 工作模式选择位0,与M1配合控制模块工作状态 |
| M1 | GPIOB | Pin 7 | 工作模式选择位1,与M0配合控制模块工作状态 |
| AUX | GPIOB | Pin 5 | 模块状态指示引脚(输入),用于判断模块是否忙碌或准备就绪 |
| TXD | GPIOA | Pin 2 | 模块串口发送端,连接STM32的USART2_RX(PA3) |
| RXD | GPIOA | Pin 3 | 模块串口接收端,连接STM32的USART2_TX(PA2) |
完成GPIO与USART2初始化后,即可调用自定义的定点传输函数 LORA_SendFixed(),该函数会自动在用户数据前封装目标地址与信道信息,并通过LoRa模块发送至指定终端。
// ============================================
// 定点传输发送
// 参数:
// targetAddr: 目标设备地址(16位)
// targetChannel: 目标设备信道(8位)
// data: 要发送的数据指针
// len: 数据长度
// ============================================
void LORA_SendFixed(uint16_t targetAddr, uint8_t targetChannel, uint8_t *data, uint16_t len)
{
uint8_t header[3];
// 构造头部:目标地址(高字节 + 低字节)+ 目标信道
header[0] = (targetAddr >> 8) & 0xFF; // 地址高字节
header[1] = targetAddr & 0xFF; // 地址低字节
header[2] = targetChannel; // 信道
// 先发送 3 字节头部
LORA_SendData(header, 3);
// 再发送数据
LORA_SendData(data, len);
}
然后在main函数中,定时2s传输一次数据。
int main(void)
{
System_Init();
while(1)
{
if (DHT11_Read_Data(&temperature, &humidity) == 0)
{
Display_Data();
// ============================================
// 使用定点传输发送温湿度数据
// 目标地址:0x0003,目标信道:0x04
// ============================================
uint8_t txBuffer[32];
uint16_t len;
sprintf((char *)txBuffer, "T:%d.%d,H:%d.%d",
temperature / 10, temperature % 10,
humidity / 10, humidity % 10);
len = strlen((char *)txBuffer);
// 定点发送给地址 0x0003,信道 23 的设备
LORA_SendFixed(0x0000, 23, txBuffer, len);
// 同时OLED显示发送状态
OLED_ShowString(3, 1, "Sent Fixed ");
}
else
{
OLED_ShowString(3, 1, "Read Error!");
}
delay_ms(2000);
}
}
IMX6ULL接收温湿度数据
将带有USB转串口的LoRa模块插入IMX6ULL开发板的USB接口后,首先需确认Linux内核是否已内置CH340串口驱动,具体图形化配置的详细步骤可参考相关博客。
效果如下图所示:
在A7端创建独立线程,采用非阻塞I/O方式持续读取串口数据,具体实现可参考《正点原子Linux C应用编程》中串口编程相关内容。
将LoRa模块通过USB转串口接入IMX6ULL开发板后,系统自动识别为 /dev/ttyUSB0。
串口初始化需配置通信参数,包括波特率(与LoRa模块默认115200保持一致)、数据位(8位)、停止位(1位)及奇偶校验位(无校验)。通过 tcgetattr() 获取当前 termios 结构体,修改 c_cflag 寄存器完成参数设置,最后调用 tcsetattr() 使配置生效。具体实现如下:
int serial_init(const char *device, int baudrate)
{
struct termios options = {0};
// 1. 打开串口
int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("open serial port failed");
return -1;
}
// 2. 保存旧配置
if (tcgetattr(fd, &old_cfg) < 0)
{
fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));
close(fd);
return -1;
}
// 3. 设置波特率
speed_t speed;
switch (baudrate)
{
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
case 57600:
speed = B57600;
break;
case 115200:
speed = B115200;
break;
case 230400:
speed = B230400;
break;
default:
speed = B115200;
break;
}
// 4. 配置串口
cfmakeraw(&options);
cfsetispeed(&options, speed);
cfsetospeed(&options, speed);
options.c_cflag |= (CLOCAL | CREAD); // 本地连接,接收使能
options.c_cflag &= ~CSIZE; // 清除数据位
options.c_cflag |= CS8; // 8数据位
options.c_cflag &= ~PARENB; // 无校验
options.c_cflag &= ~CSTOPB; // 1停止位
options.c_cflag &= ~CRTSCTS; // 🔧 关闭硬件流控(明确添加)
options.c_cc[VMIN] = 0; // 不等待固定字节数
options.c_cc[VTIME] = 0; // 立即返回
// 5. 清空缓冲区
tcflush(fd, TCIOFLUSH);
// 6. 应用配置
if (tcsetattr(fd, TCSANOW, &options) < 0)
{
fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));
close(fd);
return -1;
}
return fd;
}
线程启动后首先调用 init_serial() 完成串口初始化,随后进入主循环,通过非阻塞方式不断轮询串口缓冲区,检测是否有新数据到达并读取。
整个pthread_transfer.c如下所示:
#include "data_global.h"
#include <termios.h>
static struct termios old_cfg; // 用于保存终端的配置参数
int serial_fd;
unsigned char rx_buffer[256];
// 函数声明
void file_env_info_struct(struct env_info_client_addr *client_addr, int home_id);
// ================================================================
// 解析 LoRa 数据并填充到结构体
// ================================================================
void parse_lora_data(const char *data, struct env_info_client_addr *client_addr, int home_id)
{
float temp = 0, humi = 0;
// 示例格式: "T:25.5,H:60.0"
if (strstr(data, "T:") && strstr(data, "H:"))
{
sscanf(data, "T:%f,H:%f", &temp, &humi);
client_addr->monitor_no[home_id].zigbee_data.tempature = temp;
client_addr->monitor_no[home_id].zigbee_data.humidity = humi;
}
}
void *pthread_transfer(void *arg)
{
int home_id = 0; // 家庭ID,假设为1
serial_fd = serial_init(ttyUSB, 115200);
if (serial_fd < 0)
{
printf("Serial init failed!\n");
return NULL;
}
while (1)
{
pthread_mutex_lock(&mutex_client_transfer);
file_env_info_struct(&client_addr, home_id); // 填入虚拟环境信息
pthread_mutex_unlock(&mutex_client_transfer);
pthread_cond_signal(&cond_client_transfer); // 发送转发条件
sleep(1);
}
}
// 模拟数据上传
void file_env_info_struct(struct env_info_client_addr *client_addr, int home_id) // 填入虚拟环境信息
{
int ret = serial_read(serial_fd, rx_buffer, sizeof(rx_buffer) - 1);
if (ret > 0)
{
rx_buffer[ret] = '\0';
parse_lora_data((char *)rx_buffer, client_addr, home_id);
}
client_addr->monitor_no[home_id].zigbee_data.humiMax = 60.0;
client_addr->monitor_no[home_id].zigbee_data.humiMin = 40.0;
client_addr->monitor_no[home_id].zigbee_data.tempMax = 30.0;
client_addr->monitor_no[home_id].zigbee_data.tempMin = 20.0;
client_addr->monitor_no[home_id].imx6ull_data.adc = 1023;
client_addr->monitor_no[home_id].imx6ull_data.accx = 100;
client_addr->monitor_no[home_id].imx6ull_data.accy = 200;
client_addr->monitor_no[home_id].imx6ull_data.accz = 300;
client_addr->monitor_no[home_id].imx6ull_data.gyrox = 10;
client_addr->monitor_no[home_id].imx6ull_data.gyroy = 20;
client_addr->monitor_no[home_id].imx6ull_data.gyroz = 30;
}
int serial_init(const char *device, int baudrate)
{
struct termios options = {0};
// 1. 打开串口
int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("open serial port failed");
return -1;
}
// 2. 保存旧配置
if (tcgetattr(fd, &old_cfg) < 0)
{
fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));
close(fd);
return -1;
}
// 3. 设置波特率
speed_t speed;
switch (baudrate)
{
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
case 57600:
speed = B57600;
break;
case 115200:
speed = B115200;
break;
case 230400:
speed = B230400;
break;
default:
speed = B115200;
break;
}
// 4. 配置串口
cfmakeraw(&options);
cfsetispeed(&options, speed);
cfsetospeed(&options, speed);
options.c_cflag |= (CLOCAL | CREAD); // 本地连接,接收使能
options.c_cflag &= ~CSIZE; // 清除数据位
options.c_cflag |= CS8; // 8数据位
options.c_cflag &= ~PARENB; // 无校验
options.c_cflag &= ~CSTOPB; // 1停止位
options.c_cflag &= ~CRTSCTS; // 🔧 关闭硬件流控(明确添加)
options.c_cc[VMIN] = 0; // 不等待固定字节数
options.c_cc[VTIME] = 0; // 立即返回
// 5. 清空缓冲区
tcflush(fd, TCIOFLUSH);
// 6. 应用配置
if (tcsetattr(fd, TCSANOW, &options) < 0)
{
fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));
close(fd);
return -1;
}
return fd;
}
int serial_write(int fd, const unsigned char *data, int len)
{
int ret = write(fd, data, len);
if (ret < 0)
{
perror("write error");
}
return ret;
}
int serial_read(int fd, unsigned char *buffer, int len)
{
int ret = read(fd, buffer, len);
if (ret < 0)
{
perror("read error");
}
return ret; // 返回实际读取的字节数
}
将数据填充结构体,然后交给刷新线程用于上传网页,之后的数据就和模拟数据上传一样,刷新现成一旦获取锁,就通过共享内存的方式将数据交给CGI进程,随后转发到HTML网页显示,如下图所示:
更多推荐
所有评论(0)