项目开发背景
随着全球贸易和电子商务的快速发展,冷链物流在食品、药品等易腐物品的运输中扮演着关键角色。这些物品对温度、湿度等环境条件极为敏感,任何偏差都可能导致质量下降、腐败甚至安全风险,因此确保运输过程中的环境稳定性至关重要。传统冷链监控往往依赖人工记录或简单设备,无法实现实时、全面的数据采集和远程管理,这增加了运营成本并降低了可靠性。
在实际运输中,温度波动、湿度变化以及意外震动等异常情况频繁发生,但现有系统多数缺乏高效的实时监测和自动响应机制。这导致问题发现延迟、追溯困难,以及潜在的货物损失。此外,GPS定位和轨迹记录的缺失使得物流公司难以优化路线、提高效率,并应对突发事件。
为了应对这些挑战,本项目旨在开发一个基于STM32的智能冷链物流监控系统,通过集成多传感器和无线通信技术,实现全天候的环境数据采集、精确定位和云端数据传输。该系统不仅能够实时监测温度、湿度和震动,还能在异常时自动报警并记录日志,从而提升冷链运输的透明度和可控性。
通过结合STM32微控制器的低成本、高性能特点,以及华为云平台的数据处理能力,本项目为冷链物流行业提供了一个高效、可靠的解决方案,有助于降低损耗、提高客户满意度,并推动物流智能化的发展。
设计实现的功能
(1)实时监测环境温度数据(通过DS18B20传感器)
(2)实时监测湿度数据(通过DHT22传感器)
(3)实时监测震动数据(通过SW-420传感器)
(4)精确定位运输位置(通过ATGM336H GPS模块)
(5)记录运输轨迹(STM32处理并存储GPS数据)
(6)检测异常情况(如温度、湿度、震动超出阈值)
(7)自动报警异常情况(通过STM32触发报警机制)
(8)记录事件日志(STM32存储异常事件信息)
(9)上传实时数据至华为云(通过ESP8266 Wi-Fi模块)
(10)支持QT上位机显示温湿度曲线和轨迹地图(基于上传的云数据)
项目硬件模块组成
(1)STM32F103C8T6最小系统核心板作为主控制器
(2)DS18B20防水温度传感器监测环境温度
(3)DHT22温湿度传感器监测湿度数据
(4)SW-420震动传感器检测运输震动
(5)ATGM336H GPS模块实现精确定位
(6)ESP8266-01S Wi-Fi模块上传数据至华为云
设计意义
基于STM32设计的智能冷链物流监控系统通过集成温度、湿度和震动传感器,结合GPS定位和无线通信技术,实现了对冷链运输环境的全方位实时监控,有效提升了物流过程的透明度和可控性,这对于保障生鲜食品、药品等易腐货物的质量安全具有重要意义。
该系统能够实时采集并传输温度、湿度和震动数据,确保运输环境始终处于预设的安全范围内,一旦检测到异常如温度超标或剧烈震动,便会自动触发报警机制并记录事件日志,从而帮助运营人员及时干预,避免货物变质或损坏,减少经济损失。
GPS模块提供精确定位和轨迹记录功能,配合QT上位机软件可视化显示温湿度曲线和地图轨迹,使得用户能够直观跟踪运输全程,优化物流路线和管理决策,增强了对运输过程的洞察力和响应能力。
通过Wi-Fi模块将数据上传至华为云平台,系统支持数据的远程存储和访问,便于进行历史数据分析和趋势预测,这不仅提高了监控的效率和可靠性,还为冷链物流的智能化升级提供了坚实的技术基础,符合现代物联网应用的发展方向。
设计思路
设计思路基于STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行。系统通过集成多种传感器实时采集冷链运输环境中的数据,包括使用DS18B20防水温度传感器监测环境温度,DHT22温湿度传感器监测湿度数据,以及SW-420震动传感器检测运输过程中的震动情况。同时,ATGM336H GPS模块用于精确定位运输位置并记录轨迹数据,确保全程监控。
数据采集后,STM32控制器进行实时处理和分析,检查温度、湿度和震动数据是否超出预设阈值。如果检测到异常情况,如温度过高或震动过大,系统会自动触发报警机制,并通过内部日志记录事件详情,包括时间、位置和异常类型,以便后续查询和分析。
为了远程监控和数据存储,系统使用ESP8266-01S Wi-Fi模块将采集到的传感器数据、GPS定位信息以及事件日志上传至华为云平台。数据传输采用定期上传和事件触发上传相结合的方式,确保数据的实时性和可靠性,同时减少功耗。
在上位机端,基于QT开发的上位机软件接收华为云的数据,并实时显示运输全程的温湿度曲线和轨迹地图。用户可以通过界面直观查看历史数据和当前状态,支持数据导出和报警通知功能,从而实现对冷链物流过程的全面可视化监控。
框架图
智能冷链物流监控系统框架图:
数据流:传感器数据(温度、湿度、震动、GPS)采集到STM32,经处理通过ESP8266上传至华为云,QT上位机从云获取数据并显示曲线和地图。异常报警由STM32触发并通过云记录。
系统总体设计
系统总体设计基于STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行。该系统旨在实时监测冷链运输过程中的环境参数,包括温度、湿度和震动数据,并通过集成多种传感器实现数据采集。DS18B20防水温度传感器用于精确测量环境温度,DHT22温湿度传感器同时监测湿度和温度数据,SW-420震动传感器检测运输过程中的震动情况,确保货物在运输中的稳定性。
GPS定位部分采用ATGM336H模块,实现精确定位并记录运输轨迹,数据通过串口通信传输至STM32主控制器进行处理和存储。异常情况如温度超标、湿度异常或震动过大时,系统会自动触发报警机制,并通过事件日志记录相关数据,便于后续分析和追溯。
数据传输通过ESP8266-01S Wi-Fi模块实现,将采集到的传感器数据、GPS位置信息以及事件日志上传至华为云平台,实现远程监控和数据备份。云平台负责数据存储和管理,为上位机提供数据源。
上位机部分采用QT开发,显示运输全程的温湿度曲线和轨迹地图,用户可以通过图形界面实时查看历史数据和当前状态,辅助决策和监控。整个系统设计注重实际应用,确保数据准确性和可靠性,满足冷链物流的监控需求。
系统功能总结
| 功能 | 描述 | 实现硬件/方式 |
|---|---|---|
| 温度监测 | 实时监测环境温度 | DS18B20防水温度传感器 |
| 湿度监测 | 实时监测环境湿度 | DHT22温湿度传感器 |
| 震动监测 | 检测运输过程中的震动 | SW-420震动传感器 |
| 定位与轨迹记录 | GPS精确定位运输位置并记录轨迹 | ATGM336H GPS模块 |
| 数据上传 | 将传感器数据上传至云平台 | ESP8266-01S Wi-Fi模块上传至华为云 |
| 异常报警与日志记录 | 当温度、湿度或震动异常时自动报警,并记录事件日志 | STM32主控制器处理数据,判断异常,并通过Wi-Fi发送报警或本地记录 |
| 上位机显示 | QT软件显示运输全程的温湿度曲线和轨迹地图 | 数据通过Wi-Fi上传至云,QT软件从云获取数据并可视化 |
设计的各个功能模块描述
STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行,初始化各个传感器和模块,实时采集和处理温度、湿度、震动以及GPS数据,并根据预设阈值判断异常情况,触发报警机制,同时控制数据上传和日志记录。
DS18B20防水温度传感器用于监测环境温度,通过单总线协议与STM32通信,提供高精度的温度读数,其防水设计使其适合冷链运输的潮湿环境,确保温度数据的可靠性和实时性。
DHT22温湿度传感器用于监测湿度数据,同时提供辅助温度信息,通过数字信号输出,STM32定期读取其数据以补充湿度监测,并与DS18B20数据交叉验证,增强系统可靠性。
SW-420震动传感器检测运输过程中的震动事件,当震动强度超过设定阈值时,会向STM32发送信号,触发异常报警,STM32记录震动事件并纳入事件日志中。
ATGM336H GPS模块实现精确定位功能,STM32通过串口接收GPS数据,解析出经纬度信息,实时记录运输位置并生成轨迹数据,用于上传和后续地图显示。
ESP8266-01S Wi-Fi模块负责无线通信,将STM32处理后的传感器数据、GPS轨迹和事件日志通过MQTT或HTTP协议上传至华为云平台,实现数据的远程存储和实时监控。
QT上位机软件运行于PC端,从华为云平台获取数据,图形化显示运输全程的温湿度变化曲线和轨迹地图,用户可通过界面查看历史数据和报警事件,完成监控需求。
上位机代码设计
以下是基于QT C++开发的智能冷链物流监控系统上位机代码设计。代码包括主窗口类、MQTT客户端处理、图表显示和地图集成。假设数据通过MQTT从华为云接收,数据格式为JSON,包含温度、湿度、震动、经纬度等信息。
项目文件结构:
main.cpp:应用程序入口。mainwindow.h:主窗口头文件。mainwindow.cpp:主窗口实现文件。map.html:用于显示地图的HTML文件(作为资源嵌入)。ColdChainMonitor.pro:QT项目文件。
代码实现:
1. ColdChainMonitor.pro(QT项目文件):
QT += core gui mqtt charts webenginewidgets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = ColdChainMonitor
TEMPLATE = app
SOURCES += main.cpp
mainwindow.cpp
HEADERS += mainwindow.h
RESOURCES += resources.qrc
2. resources.qrc(资源文件,包含map.html):
<RCC>
<qresource prefix="/">
<file>map.html</file>
</qresource>
</RCC>
3. main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
4. mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtMqtt/QtMqtt>
#include <QChartView>
#include <QLineSeries>
#include <QWebEngineView>
#include <QTextEdit>
QT_CHARTS_USE_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onConnected();
void onMessageReceived(const QByteArray &message, const QMqttTopicName &topic);
void updateChart(double temperature, double humidity);
void updateMap(double lat, double lon);
void addLog(const QString &log);
private:
QMqttClient *m_client;
QChart *m_chart;
QLineSeries *m_tempSeries;
QLineSeries *m_humSeries;
QChartView *m_chartView;
QWebEngineView *m_mapView;
QTextEdit *m_logEdit;
void setupUI();
void setupMQTT();
};
#endif // MAINWINDOW_H
5. mainwindow.cpp:
#include "mainwindow.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QWebChannel>
#include <QMessageBox>
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setupUI();
setupMQTT();
}
MainWindow::~MainWindow()
{
if (m_client)
m_client->disconnect();
}
void MainWindow::setupUI()
{
QWidget *centralWidget = new QWidget(this);
QHBoxLayout *mainLayout = new QHBoxLayout(centralWidget);
QVBoxLayout *leftLayout = new QVBoxLayout();
m_chart = new QChart();
m_tempSeries = new QLineSeries();
m_tempSeries->setName("Temperature (°C)");
m_humSeries = new QLineSeries();
m_humSeries->setName("Humidity (%)");
m_chart->addSeries(m_tempSeries);
m_chart->addSeries(m_humSeries);
m_chart->createDefaultAxes();
m_chart->setTitle("Temperature and Humidity Monitoring");
m_chartView = new QChartView(m_chart);
leftLayout->addWidget(m_chartView);
m_logEdit = new QTextEdit();
m_logEdit->setReadOnly(true);
leftLayout->addWidget(m_logEdit);
m_mapView = new QWebEngineView();
m_mapView->load(QUrl("qrc:/map.html"));
mainLayout->addLayout(leftLayout, 2);
mainLayout->addWidget(m_mapView, 3);
setCentralWidget(centralWidget);
setWindowTitle("Smart Cold Chain Monitoring System");
resize(1200, 600);
}
void MainWindow::setupMQTT()
{
m_client = new QMqttClient(this);
m_client->setHostname("your_mqtt_broker_address"); // Replace with actual Huawei Cloud MQTT broker
m_client->setPort(1883); // Default MQTT port
// If authentication is required, set username and password:
// m_client->setUsername("username");
// m_client->setPassword("password");
connect(m_client, &QMqttClient::connected, this, &MainWindow::onConnected);
connect(m_client, &QMqttClient::messageReceived, this, [this](const QByteArray &message, const QMqttTopicName &topic) {
onMessageReceived(message, topic);
});
m_client->connectToHost();
}
void MainWindow::onConnected()
{
addLog("Connected to MQTT broker");
m_client->subscribe("coldchain/data"); // Subscribe to the topic where data is published
}
void MainWindow::onMessageReceived(const QByteArray &message, const QMqttTopicName &topic)
{
QString msg = QString::fromUtf8(message);
addLog("Received: " + msg);
QJsonDocument doc = QJsonDocument::fromJson(message);
if (doc.isNull()) {
addLog("Error: Invalid JSON data");
return;
}
QJsonObject obj = doc.object();
double temperature = obj["temperature"].toDouble();
double humidity = obj["humidity"].toDouble();
double vibration = obj["vibration"].toDouble();
double lat = obj["latitude"].toDouble();
double lon = obj["longitude"].toDouble();
updateChart(temperature, humidity);
updateMap(lat, lon);
if (vibration > 100) { // Example vibration threshold
addLog("Alert: High vibration detected - " + QString::number(vibration));
QMessageBox::warning(this, "Vibration Alert", "High vibration detected during transport!");
}
if (temperature < 2 || temperature > 8) { // Temperature range for cold chain
addLog("Alert: Temperature out of range - " + QString::number(temperature));
QMessageBox::warning(this, "Temperature Alert", "Temperature is outside safe range (2-8°C)!");
}
}
void MainWindow::updateChart(double temperature, double humidity)
{
static int xValue = 0;
m_tempSeries->append(xValue, temperature);
m_humSeries->append(xValue, humidity);
xValue++;
m_chart->axes(Qt::Horizontal).first()->setRange(0, xValue);
m_chart->axes(Qt::Vertical).first()->setRange(0, 100); // Adjust based on expected range
}
void MainWindow::updateMap(double lat, double lon)
{
m_mapView->page()->runJavaScript(QString("updateMarker(%1, %2);").arg(lat).arg(lon));
}
void MainWindow::addLog(const QString &log)
{
m_logEdit->append(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") + " - " + log);
}
6. map.html(保存为资源文件):
<!DOCTYPE html>
<html>
<head>
<title>Transport Map</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<style>
#map { height: 100%; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
var map = L.map('map').setView([39.9042, 116.4074], 10); // Initial view
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '? OpenStreetMap contributors'
}).addTo(map);
var marker = L.marker([39.9042, 116.4074]).addTo(map);
function updateMarker(lat, lon) {
map.setView([lat, lon], 13);
marker.setLatLng([lat, lon]);
}
</script>
</body>
</html>
使用说明:
- 将上述代码保存到相应文件中。
- 在QT Creator中创建新项目,并替换默认文件内容。
- 添加
resources.qrc文件,包含map.html。 - 更新MQTT broker信息(主机名、端口、认证等)以匹配华为云配置。
- 编译并运行项目。
此代码提供了一个基本的上位机界面,实时显示温湿度曲线、GPS轨迹地图和事件日志。异常情况会弹出警报并记录日志。确保华为云MQTT服务已正确设置,设备数据发布到"coldchain/data"主题。
模块代码设计
#include "stm32f10x.h"
// 寄存器地址定义
#define RCC_BASE 0x40021000
#define GPIOA_BASE 0x40010800
#define GPIOB_BASE 0x40010C00
#define UART1_BASE 0x40013800
#define UART2_BASE 0x40004400
#define UART3_BASE 0x40004800
#define RCC_CR (*(volatile unsigned int *)(RCC_BASE))
#define RCC_CFGR (*(volatile unsigned int *)(RCC_BASE + 0x04))
#define RCC_APB2ENR (*(volatile unsigned int *)(RCC_BASE + 0x18))
#define RCC_APB1ENR (*(volatile unsigned int *)(RCC_BASE + 0x1C))
#define GPIOA_CRL (*(volatile unsigned int *)(GPIOA_BASE + 0x00))
#define GPIOA_CRH (*(volatile unsigned int *)(GPIOA_BASE + 0x04))
#define GPIOA_IDR (*(volatile unsigned int *)(GPIOA_BASE + 0x08))
#define GPIOA_ODR (*(volatile unsigned int *)(GPIOA_BASE + 0x0C))
#define GPIOB_CRL (*(volatile unsigned int *)(GPIOB_BASE + 0x00))
#define GPIOB_CRH (*(volatile unsigned int *)(GPIOB_BASE + 0x04))
#define GPIOB_IDR (*(volatile unsigned int *)(GPIOB_BASE + 0x08))
#define GPIOB_ODR (*(volatile unsigned int *)(GPIOB_BASE + 0x0C))
#define UART1_SR (*(volatile unsigned int *)(UART1_BASE))
#define UART1_DR (*(volatile unsigned int *)(UART1_BASE + 0x04))
#define UART1_BRR (*(volatile unsigned int *)(UART1_BASE + 0x08))
#define UART1_CR1 (*(volatile unsigned int *)(UART1_BASE + 0x0C))
#define UART2_SR (*(volatile unsigned int *)(UART2_BASE))
#define UART2_DR (*(volatile unsigned int *)(UART2_BASE + 0x04))
#define UART2_BRR (*(volatile unsigned int *)(UART2_BASE + 0x08))
#define UART2_CR1 (*(volatile unsigned int *)(UART2_BASE + 0x0C))
#define UART3_SR (*(volatile unsigned int *)(UART3_BASE))
#define UART3_DR (*(volatile unsigned int *)(UART3_BASE + 0x04))
#define UART3_BRR (*(volatile unsigned int *)(UART3_BASE + 0x08))
#define UART3_CR1 (*(volatile unsigned int *)(UART3_BASE + 0x0C))
// 引脚定义
#define DS18B20_PIN 0 // PA0
#define DHT22_PIN 1 // PA1
#define SW420_PIN 2 // PA2
// 函数声明
void SystemClock_Config(void);
void GPIO_Init(void);
void UART1_Init(void);
void UART2_Init(void);
void UART3_Init(void);
void DS18B20_Init(void);
float DS18B20_ReadTemp(void);
void DHT22_Init(void);
void DHT22_Read(float *temp, float *hum);
uint8_t SW420_Read(void);
void GPS_Init(void);
void GPS_Read(void);
void WiFi_Init(void);
void WiFi_SendData(float temp, float hum, uint8_t shock, const char *gps_data);
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
// 全局变量
char gps_buffer[100];
volatile uint8_t uart2_rx_index = 0;
volatile uint8_t uart2_rx_buffer[100];
int main(void) {
SystemClock_Config();
GPIO_Init();
UART1_Init(); // For debug
UART2_Init(); // For GPS
UART3_Init(); // For WiFi
DS18B20_Init();
DHT22_Init();
// SW420 is input, already initialized in GPIO_Init
while (1) {
float temperature = DS18B20_ReadTemp();
float humidity, temp_dht;
DHT22_Read(&temp_dht, &humidity); // DHT22 also provides temperature, but we use DS18B20 for temp
uint8_t shock = SW420_Read();
GPS_Read(); // Read GPS data into gps_buffer
// Check for anomalies
if (temperature > 8.0 || temperature < 2.0 || humidity > 80.0 || shock == 1) {
// Trigger alarm, e.g., set a GPIO or send via WiFi
// Here, we'll send via WiFi
}
WiFi_SendData(temperature, humidity, shock, gps_buffer);
Delay_ms(5000); // Send data every 5 seconds
}
}
void SystemClock_Config(void) {
// Enable HSE
RCC_CR |= (1 << 16); // HSEON
while (!(RCC_CR & (1 << 17))); // Wait for HSERDY
// Configure PLL: HSE as source, multiply by 9 -> 72MHz
RCC_CFGR |= (1 << 16); // PLLSRC = HSE
RCC_CFGR |= (7 << 18); // PLLMUL = 9 (0111)
RCC_CR |= (1 << 24); // PLLON
while (!(RCC_CR & (1 << 25))); // Wait for PLLRDY
// Configure FLASH latency
FLASH_ACR = 0x12; // Two wait states for 48 < HCLK <= 72MHz
// Switch to PLL
RCC_CFGR |= (2 << 0); // SW = PLL
while ((RCC_CFGR & (3 << 2)) != (2 << 2)); // Wait for SWS to be PLL
}
void GPIO_Init(void) {
// Enable GPIOA and GPIOB clocks
RCC_APB2ENR |= (1 << 2) | (1 << 3); // GPIOA and GPIOB enable
// DS18B20 on PA0: output open-drain
GPIOA_CRL &= ~(0xF << (0 * 4));
GPIOA_CRL |= (0x4 << (0 * 4)); // Output open-drain, 50MHz
// DHT22 on PA1: output open-drain
GPIOA_CRL &= ~(0xF << (1 * 4));
GPIOA_CRL |= (0x4 << (1 * 4)); // Output open-drain, 50MHz
// SW420 on PA2: input pull-up
GPIOA_CRL &= ~(0xF << (2 * 4));
GPIOA_CRL |= (0x8 << (2 * 4)); // Input with pull-up/pull-down
GPIOA_ODR |= (1 << 2); // Set pull-up
// UART2 on PA2 (TX) and PA3 (RX)
GPIOA_CRL &= ~(0xFF << (2 * 4)); // Clear bits for PA2 and PA3
GPIOA_CRL |= (0x4 << (2 * 4)); // PA2: Alternate function push-pull, 50MHz
GPIOA_CRL |= (0x4 << (3 * 4)); // PA3: Input floating (RX is input)
// UART3 on PB10 (TX) and PB11 (RX)
GPIOB_CRH &= ~(0xFF << (2 * 4)); // Clear bits for PB10 and PB11 (bits 8-15 for CRH)
GPIOB_CRH |= (0x4 << (10 - 8) * 4); // PB10: Alternate function push-pull, 50MHz
GPIOB_CRH |= (0x4 << (11 - 8) * 4); // PB11: Input floating
}
void UART1_Init(void) {
// Enable UART1 clock
RCC_APB2ENR |= (1 << 14); // USART1 enable
// Configure UART1: 9600 baud, 8 data bits, no parity, 1 stop bit
UART1_BRR = 0x1D4C; // 72MHz / 9600 = 7500 -> 0x1D4C
UART1_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE
}
void UART2_Init(void) {
// Enable UART2 clock
RCC_APB1ENR |= (1 << 17); // USART2 enable
// Configure UART2: 9600 baud for GPS
UART2_BRR = 0x1D4C; // 72MHz / 9600 = 7500 -> 0x1D4C
UART2_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE
// Enable RX interrupt for GPS
UART2_CR1 |= (1 << 5); // RXNEIE
NVIC_EnableIRQ(USART2_IRQn);
}
void UART3_Init(void) {
// Enable UART3 clock
RCC_APB1ENR |= (1 << 18); // USART3 enable
// Configure UART3: 115200 baud for WiFi
UART3_BRR = 0x0271; // 72MHz / 115200 ≈ 625 -> 0x0271
UART3_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE
}
void DS18B20_Init(void) {
// Initialization is done in read function
}
float DS18B20_ReadTemp(void) {
uint8_t temp_l, temp_h;
int16_t temp;
// Reset pulse
GPIOA_ODR &= ~(1 << DS18B20_PIN); // Pull low
Delay_us(480);
GPIOA_ODR |= (1 << DS18B20_PIN); // Release
Delay_us(60);
if (!(GPIOA_IDR & (1 << DS18B20_PIN))) {
// Presence pulse detected
Delay_us(240);
}
// Skip ROM command
DS18B20_WriteByte(0xCC);
// Convert T command
DS18B20_WriteByte(0x44);
Delay_ms(750); // Wait for conversion
// Reset again
GPIOA_ODR &= ~(1 << DS18B20_PIN);
Delay_us(480);
GPIOA_ODR |= (1 << DS18B20_PIN);
Delay_us(60);
if (!(GPIOA_IDR & (1 << DS18B20_PIN))) {
Delay_us(240);
}
// Skip ROM
DS18B20_WriteByte(0xCC);
// Read scratchpad
DS18B20_WriteByte(0xBE);
temp_l = DS18B20_ReadByte();
temp_h = DS18B20_ReadByte();
temp = (temp_h << 8) | temp_l;
return temp / 16.0;
}
void DS18B20_WriteByte(uint8_t data) {
for (int i = 0; i < 8; i++) {
GPIOA_ODR &= ~(1 << DS18B20_PIN); // Pull low
if (data & 0x01) {
Delay_us(5);
GPIOA_ODR |= (1 << DS18B20_PIN); // Release for logic 1
} else {
Delay_us(60);
GPIOA_ODR |= (1 << DS18B20_PIN);
}
data >>= 1;
Delay_us(60);
}
}
uint8_t DS18B20_ReadByte(void) {
uint8 data = 0;
for (int i = 0; i < 8; i++) {
GPIOA_ODR &= ~(1 << DS18B20_PIN);
Delay_us(1);
GPIOA_ODR |= (1 << DS18B20_PIN);
Delay_us(14);
if (GPIOA_IDR & (1 << DS18B20_PIN)) {
data |= (1 << i);
}
Delay_us(45);
}
return data;
}
void DHT22_Init(void) {
// Initialization is done in read function
}
void DHT22_Read(float *temp, float *hum) {
uint8_t data[5] = {0};
uint8_t checksum;
// Send start signal
GPIOA_ODR &= ~(1 << DHT22_PIN);
Delay_ms(1);
GPIOA_ODR |= (1 << DHT22_PIN);
Delay_us(30);
// Wait for response
while (GPIOA_IDR & (1 << DHT22_PIN));
while (!(GPIOA_IDR & (1 << DHT22_PIN)));
while (GPIOA_IDR & (1 << DHT22_PIN));
// Read data
for (int i = 0; i < 40; i++) {
while (!(GPIOA_IDR & (1 << DHT22_PIN))); // Wait for low to go high
Delay_us(40);
if (GPIOA_IDR & (1 << DHT22_PIN)) {
data[i/8] |= (1 << (7 - (i % 8)));
while (GPIOA_IDR & (1 << DHT22_PIN));
}
}
checksum = data[0] + data[1] + data[2] + data[3];
if (checksum == data[4]) {
*hum = ((data[0] << 8) | data[1]) / 10.0;
*temp = (((data[2] & 0x7F) << 8) | data[3]) / 10.0;
if (data[2] & 0x80) *temp = -(*temp);
} else {
*hum = 0.0;
*temp = 0.0;
}
}
uint8_t SW420_Read(void) {
return (GPIOA_IDR & (1 << SW420_PIN)) ? 0 : 1; // Active low? Check sensor datasheet. Assuming shock when low.
}
void GPS_Init(void) {
// UART2 already initialized
}
void GPS_Read(void) {
// Data is received via interrupt, so we handle it in ISR
// For simplicity, we assume gps_buffer is filled in ISR
}
void USART2_IRQHandler(void) {
if (UART2_SR & (1 << 5)) { // RXNE
uint8_t data = UART2_DR;
if (data == 'n') {
gps_buffer[uart2_rx_index] = '?';
uart2_rx_index = 0;
} else {
gps_buffer[uart2_rx_index++] = data;
if (uart2_rx_index >= sizeof(gps_buffer) - 1) uart2_rx_index = 0;
}
}
}
void WiFi_Init(void) {
// Send AT commands to initialize ESP8266
// This is done once at startup
const char *init_cmds[] = {
"ATrn",
"AT+CWMODE=1rn",
"AT+CWJAP="SSID","PASSWORD"rn", // Replace with your WiFi credentials
"AT+CIPSTART="TCP","华为云地址",端口号rn", // Replace with Huawei cloud details
NULL
};
for (int i = 0; init_cmds[i] != NULL; i++) {
WiFi_SendCommand(init_cmds[i]);
Delay_ms(1000);
}
}
void WiFi_SendCommand(const char *cmd) {
while (*cmd) {
UART3_DR = *cmd++;
while (!(UART3_SR & (1 << 7))); // Wait for TXE
}
}
void WiFi_SendData(float temp, float hum, uint8_t shock, const char *gps_data) {
char buffer[100];
sprintf(buffer, "AT+CIPSEND=%drn", strlen(buffer)); // First send length
WiFi_SendCommand(buffer);
Delay_ms(100);
sprintf(buffer, "temp=%.2f,hum=%.2f,shock=%d,gps=%srn", temp, hum, shock, gps_data);
WiFi_SendCommand(buffer);
}
void Delay_us(uint32_t us) {
us *= 72; // For 72MHz, approximate
while (us--) {
__NOP();
}
}
void Delay_ms(uint32_t ms) {
while (ms--) {
Delay_us(1000);
}
}
项目核心代码
#include "stm32f10x.h"
// 假设其他模块的头文件已存在
#include "ds18b20.h"
#include "dht22.h"
#include "gps.h"
#include "wifi.h"
// 定义传感器引脚
#define DS18B20_PIN GPIO_Pin_0
#define DS18B20_PORT GPIOA
#define DHT22_PIN GPIO_Pin_1
#define DHT22_PORT GPIOA
#define SW420_PIN GPIO_Pin_2
#define SW420_PORT GPIOA
// 异常阈值
#define TEMP_LOW_LIMIT -20.0
#define TEMP_HIGH_LIMIT 10.0
#define HUMIDITY_LIMIT 80.0
#define SHAKE_THRESHOLD 1 // 假设震动次数阈值
// 全局变量
float temperature = 0.0;
float humidity = 0.0;
uint8_t shake_count = 0;
char gps_data[100] = {0};
uint8_t alert_flag = 0;
// 函数声明
void System_Init(void);
void GPIO_Init(void);
void USART_Init(void);
void Timer_Init(void);
void Read_Sensors(void);
void Process_GPS(void);
void Check_Alerts(void);
void Upload_Data(void);
void Delay_ms(uint32_t ms);
int main(void) {
System_Init();
GPIO_Init();
USART_Init();
Timer_Init();
// 初始化模块
DS18B20_Init();
DHT22_Init();
GPS_Init();
WIFI_Init();
while (1) {
Read_Sensors();
Process_GPS();
Check_Alerts();
Upload_Data();
Delay_ms(5000); // 每5秒执行一次
}
}
void System_Init(void) {
// 设置系统时钟为72MHz
RCC->CFGR |= RCC_CFGR_PLLMULL9; // PLL multiplier 9
RCC->CFGR |= RCC_CFGR_PLLSRC; // PLL source HSE
RCC->CR |= RCC_CR_HSEON; // Enable HSE
while (!(RCC->CR & RCC_CR_HSERDY)); // Wait for HSE ready
RCC->CR |= RCC_CR_PLLON; // Enable PLL
while (!(RCC->CR & RCC_CR_PLLRDY)); // Wait for PLL ready
RCC->CFGR |= RCC_CFGR_SW_PLL; // Switch to PLL
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // Wait for switch
}
void GPIO_Init(void) {
// 启用GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 配置DS18B20引脚为推挽输出(初始)
DS18B20_PORT->CRL &= ~(0x0F << (0 * 4)); // PA0
DS18B20_PORT->CRL |= (0x03 << (0 * 4)); // Output, 50MHz
// 配置DHT22引脚为推挽输出(初始)
DHT22_PORT->CRL &= ~(0x0F << (1 * 4)); // PA1
DHT22_PORT->CRL |= (0x03 << (1 * 4)); // Output, 50MHz
// 配置SW-420引脚为输入上拉
SW420_PORT->CRL &= ~(0x0F << (2 * 4)); // PA2
SW420_PORT->CRL |= (0x08 << (2 * 4)); // Input, pull-up
SW420_PORT->ODR |= SW420_PIN; // Set pull-up
}
void USART_Init(void) {
// 启用USART1时钟(用于Wi-Fi)
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
// 启用USART2时钟(用于GPS)
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
// 配置USART1 (PA9 TX, PA10 RX)
GPIOA->CRH &= ~(0xFF << 4); // Clear PA9 and PA10
GPIOA->CRH |= (0x0B << 4); // PA9: Output, 50MHz, alt func
GPIOA->CRH |= (0x04 << 8); // PA10: Input, pull-up
USART1->BRR = 72000000 / 115200; // Baud rate 115200
USART1->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // Enable USART, TX, RX
// 配置USART2 (PA2 TX, PA3 RX) for GPS
GPIOA->CRL &= ~(0xFF << 8); // Clear PA2 and PA3
GPIOA->CRL |= (0x0B << 8); // PA2: Output, 50MHz, alt func
GPIOA->CRL |= (0x04 << 12); // PA3: Input, pull-up
USART2->BRR = 72000000 / 9600; // Baud rate 9600 for GPS
USART2->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // Enable USART, TX, RX
}
void Timer_Init(void) {
// 启用TIM2时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
TIM2->PSC = 7200 - 1; // 预分频,10kHz
TIM2->ARR = 10000 - 1; // 自动重载值,1秒
TIM2->CR1 |= TIM_CR1_ARPE; // 自动重载预装载
TIM2->DIER |= TIM_DIER_UIE; // 更新中断使能
TIM2->CR1 |= TIM_CR1_CEN; // 启动定时器
NVIC_EnableIRQ(TIM2_IRQn); // 使能TIM2中断
}
void TIM2_IRQHandler(void) {
if (TIM2->SR & TIM_SR_UIF) {
TIM2->SR &= ~TIM_SR_UIF; // 清除中断标志
// 定时任务,例如检查震动
if (SW420_PORT->IDR & SW420_PIN) {
shake_count++; // 震动计数
}
}
}
void Read_Sensors(void) {
temperature = DS18B20_ReadTemp(); // 假设函数返回float
humidity = DHT22_ReadHumidity(); // 假设函数返回float
// 震动数据通过中断计数,这里重置或处理
}
void Process_GPS(void) {
// 假设GPS_ReadData函数从USART2读取并解析NMEA,存储到gps_data
GPS_ReadData(gps_data);
}
void Check_Alerts(void) {
alert_flag = 0;
if (temperature < TEMP_LOW_LIMIT || temperature > TEMP_HIGH_LIMIT) {
alert_flag |= 0x01; // 温度异常
}
if (humidity > HUMIDITY_LIMIT) {
alert_flag |= 0x02; // 湿度异常
}
if (shake_count > SHAKE_THRESHOLD) {
alert_flag |= 0x04; // 震动异常
shake_count = 0; // 重置计数
}
if (alert_flag) {
// 记录事件日志,假设有函数实现
Log_Event(alert_flag, temperature, humidity, shake_count, gps_data);
}
}
void Upload_Data(void) {
char data_str[200];
sprintf(data_str, "Temp:%.2f,Hum:%.2f,Shake:%d,GPS:%s,Alert:%d",
temperature, humidity, shake_count, gps_data, alert_flag);
WIFI_SendData(data_str); // 假设函数通过USART1发送到ESP8266
}
void Delay_ms(uint32_t ms) {
for (uint32_t i = 0; i < ms * 1000; i++) {
__NOP();
}
}
总结
本系统基于STM32F103C8T6微控制器设计,实现了智能冷链物流监控的核心功能,能够实时监测运输过程中的温度、湿度和震动数据,确保货物在运输途中处于适宜的环境条件。通过集成多种传感器和模块,系统有效提升了冷链物流的监控精度和响应速度。
硬件组成包括STM32F103C8T6最小系统核心板作为主控制器,DS18B20防水温度传感器用于环境温度监测,DHT22温湿度传感器提供湿度数据,SW-420震动传感器检测运输震动,ATGM336H GPS模块实现精确定位和轨迹记录,以及ESP8266-01S Wi-Fi模块将采集的数据上传至华为云平台,实现远程监控和数据存储。
此外,系统通过QT上位机显示运输全程的温湿度曲线和轨迹地图,支持异常情况自动报警并记录事件日志,增强了冷链物流的可靠性和管理效率。整体设计紧凑、成本效益高,适用于实际物流应用,为冷链运输提供了全面的智能化解决方案。
1369
