项目:工业 HVAC 控制器
工业级控制器:HMI + 实时控制 + 多协议通信。
一、目标
- HVAC(暖通空调)控制器
- 实时温度控制(PID)
- 多设备通信(Modbus / BACnet / MQTT)
- 本地 HMI 显示
- 报警与日志
二、硬件
| 模块 | 功能 |
|---|---|
| 主控 | i.MX 8M / RK3568 |
| 屏幕 | 7" 触摸屏(LVGL) |
| 通信 | RS485 (Modbus RTU) |
| 温湿度 | I2C 传感器 |
| 继电器输出 | 控制风机、阀门 |
三、软件架构
┌─────────────────────────────────────┐
│ HMI 应用(LVGL) │
├─────────────────────────────────────┤
│ 控制逻辑(PID + 状态机) │
├─────────────────────────────────────┤
│ 通信层(Modbus/MQTT) │
├─────────────────────────────────────┤
│ 驱动(I2C/GPIO/RS485) │
└─────────────────────────────────────┘
四、状态机
typedef enum {
STATE_OFF,
STATE_COOLING,
STATE_HEATING,
STATE_FAN_ONLY,
STATE_ALARM,
STATE_MAINTENANCE,
} hvac_state_t;
static hvac_state_t current_state = STATE_OFF;
void state_machine_update(void)
{
float t = read_temperature();
float setpoint = config.setpoint;
switch (current_state) {
case STATE_OFF:
if (t > setpoint + 1.0) current_state = STATE_COOLING;
if (t < setpoint - 1.0) current_state = STATE_HEATING;
break;
case STATE_COOLING:
if (t < setpoint - 0.5) current_state = STATE_FAN_ONLY;
if (sensor_fault()) current_state = STATE_ALARM;
break;
case STATE_HEATING:
if (t > setpoint + 0.5) current_state = STATE_FAN_ONLY;
if (sensor_fault()) current_state = STATE_ALARM;
break;
case STATE_ALARM:
// 等人工确认
break;
}
}
五、PID 控制
typedef struct {
float kp, ki, kd;
float integral;
float prev_error;
float output_min, output_max;
} pid_t;
void pid_init(pid_t *pid, float kp, float ki, float kd,
float min, float max)
{
pid->kp = kp; pid->ki = ki; pid->kd = kd;
pid->integral = 0;
pid->prev_error = 0;
pid->output_min = min;
pid->output_max = max;
}
float pid_update(pid_t *pid, float setpoint, float measured, float dt)
{
float error = setpoint - measured;
pid->integral += error * dt;
float derivative = (error - pid->prev_error) / dt;
pid->prev_error = error;
float output = pid->kp * error +
pid->ki * pid->integral +
pid->kd * derivative;
if (output > pid->output_max) output = pid->output_max;
if (output < pid->output_min) output = pid->output_min;
return output;
}
六、Modbus Slave(作为被控设备)
// 保持寄存器(被云端读写)
uint16_t holding_regs[100] = { 0 };
// 输入寄存器(只读)
uint16_t input_regs[100] = { 0 };
int modbus_slave()
{
modbus_t *ctx = modbus_new_rtu("/dev/ttymxc0", 9600, 'N', 8, 1);
modbus_set_slave(ctx, 1);
// 启动 Modbus TCP server
modbus_t *tcp = modbus_new_tcp("0.0.0.0", 502);
modbus_set_slave(tcp, 1);
modbus_connect(tcp);
while (running) {
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
int rc = modbus_receive(tcp, query);
if (rc > 0) {
// 写入云端 / 从本地寄存器
modbus_reply(tcp, query, rc, modbus_mapping);
}
}
}
七、HMI 显示(LVGL)
void ui_create_main_screen(void)
{
lv_obj_t *scr = lv_obj_create(NULL);
// 温度显示
lv_obj_t *temp_label = lv_label_create(scr);
lv_label_set_text(temp_label, "23.5 °C");
lv_obj_set_style_text_font(temp_label, &lv_font_montserrat_48, 0);
lv_obj_align(temp_label, LV_ALIGN_CENTER, 0, -50);
// 模式按钮
lv_obj_t *btn = lv_button_create(scr);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 50);
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
// 图表
lv_obj_t *chart = lv_chart_create(scr);
lv_chart_set_type(chart, LV_CHART_TYPE_LINE);
lv_chart_series_t *s = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
lv_obj_set_size(chart, 400, 200);
lv_obj_align(chart, LV_ALIGN_BOTTOM_MID, 0, -20);
}
void ui_update_temperature(float t)
{
lv_label_set_text_fmt(temp_label, "%.1f °C", t);
// 滚动更新 chart
}
八、报警与日志
typedef enum {
ALARM_NONE,
ALARM_HIGH_TEMP,
ALARM_LOW_TEMP,
ALARM_SENSOR_FAIL,
ALARM_COMM_FAIL,
} alarm_t;
void alarm_update(void)
{
static alarm_t prev = ALARM_NONE;
alarm_t cur = ALARM_NONE;
float t = read_temperature();
if (t > 35.0) cur = ALARM_HIGH_TEMP;
else if (t < 5.0) cur = ALARM_LOW_TEMP;
else if (sensor_fault()) cur = ALARM_SENSOR_FAIL;
if (cur != ALARM_NONE && cur != prev) {
log_alarm(cur);
send_mqtt_alarm(cur);
show_ui_alarm(cur);
}
prev = cur;
}
九、远程监控(MQTT)
// 上报周期
void publish_telemetry(void)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "id", config.device_id);
cJSON_AddNumberToObject(root, "temp", read_temperature());
cJSON_AddNumberToObject(root, "humidity", read_humidity());
cJSON_AddStringToObject(root, "state", state_to_string(current_state));
cJSON_AddNumberToObject(root, "alarm", current_alarm);
char *json = cJSON_PrintUnformatted(root);
mqtt_publish("hvac/telemetry", json, strlen(json));
cJSON_Delete(root);
free(json);
}
十、安全
- 看门狗(防止死机)
- 关键参数持久化
- 安全启动(防止恶意固件)
- OTA 签名验证
- 操作权限分级
十一、实时性
- 控制循环 < 100ms
- 屏响应 < 200ms
- 网络上报 < 1s
// 用 SCHED_FIFO
struct sched_param sp = { .sched_priority = 80 };
pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp);
mlockall(MCL_CURRENT|MCL_FUTURE);