shuichan_old/V1.4/STM32F103_App1/HardWare/FEED_SCALE/feed_scale.c

370 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* feed_scale.c - 485 喂料秤控制
*
* 硬件: USART2 接 RS485 磅秤传感器PA2(TX), PA3(RX)9600-8N1
* 协议: 查询命令 {0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B}
* 应答: 9 字节(地址1 + 功能码1 + 字节数1 + 数据4 + CRC2),重量值在数据字节[3..6],小端 float单位 KG。
* 业务: 平台下发单次投喂量 onceWt(0.1KG 单位),继电器 1 吸合开始投喂,
* 当 起始剩余重量 - 当前重量 >= 单次投喂量 时停止。
*/
#include "feed_scale.h"
#include "relay.h"
#include "uart.h"
#include "log.h"
#include "network.h"
#include "24c02.h"
#include <string.h>
#include <stdio.h>
/* 查询秤的 Modbus 命令 */
static const uint8_t RS485_WEIGHT_CMD[8] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B};
/* 运行状态 */
static FeedScaleState feed_state = FEED_IDLE;
static uint16_t once_weight = 0; /* 单次投喂量0.1KG 单位,从 EEPROM 读取 */
static uint16_t start_weight = 0; /* 本次投喂开始时的剩余重量 */
static uint16_t last_weight = 0; /* 最近一次有效净重(已去皮) */
static uint16_t last_raw_weight = 0; /* 最近一次 485 原始毛重(未去皮) */
static uint16_t reduced_weight = 0; /* 本次投喂减少的重量 */
static uint16_t zero_weight = 0; /* 去皮重量EEPROM 掉电保持 */
/* 轮询计时 */
static uint32_t last_query_tick = 0;
static uint32_t feed_start_tick = 0; /* 投喂开始时刻,用于超时保护 */
static uint8_t first_query_sent = 0; /* 首次上电查询已发送 */
static uint8_t first_query_reported = 0; /* 首次上电有效重量已上报 */
/* 重量异常增加计数 */
static uint8_t add_weight_count = 0;
#define FEED_ADD_WEIGHT_LIMIT 20u /* 连续约 10s 重量反而增加,结束投喂 */
/* 单次投喂超时 10 分钟 */
#define FEED_TIMEOUT_MS (10u * 60u * 1000u)
/* EEPROM 中保存去皮重量和单次投喂量的偏移(在 MqttInfoStr 之外) */
#define FEED_SCALE_ZERO_ADDR 120u
#define FEED_SCALE_ONCEWT_ADDR 122u
#define FEED_SCALE_INIT_FLAG_ADDR 124u
#define FEED_SCALE_INIT_FLAG 0xA5u
/* 本地 CRC16 校验 (Modbus) */
static uint16_t crc16_modbus(const uint8_t *data, uint16_t len)
{
uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
/* 从 485 应答中解析重量,应答 9 字节: 01 03 04 41 97 30 33 0B F6 */
static int feed_scale_parse_weight(const uint8_t *buf, uint16_t len, uint16_t *weight_100g)
{
if (len < 9) {
log_warn("> FEED: 485 frame too short, len=%d", (int)len);
return -1;
}
uint16_t calc_crc = crc16_modbus(buf, 7); /* 前 7 字节参与 CRC 计算 */
uint16_t recv_crc = (uint16_t)buf[8] << 8 | buf[7]; /* 最后两字节为 CRC */
if (calc_crc != recv_crc) {
log_warn("> FEED: 485 CRC err, calc=0x%04X recv=0x%04X", calc_crc, recv_crc);
return -2;
}
/* 大端 float: 数据字节[3]=MSB, [6]=LSB参考 APP12 解析方式 */
float fweight;
uint8_t *p = (uint8_t *)&fweight;
p[0] = buf[6];
p[1] = buf[5];
p[2] = buf[4];
p[3] = buf[3];
/* 转成 0.1KG 整数单位,限制范围 */
int32_t wt = (int32_t)(fweight * 10.0f);
if (wt < 0) wt = 0;
if (wt > FEED_SCALE_WEIGHT_MAX) wt = FEED_SCALE_WEIGHT_MAX;
*weight_100g = (uint16_t)wt;
log_info("> FEED: 485 raw weight=%.1fKG (0.1kg=%d)", fweight, *weight_100g);
return 0;
}
/* 通过 USART2 发送 485 查询命令 */
static void feed_scale_send_query(void)
{
HAL_UART_Transmit(&huart2, (uint8_t *)RS485_WEIGHT_CMD, sizeof(RS485_WEIGHT_CMD), 100);
}
/* 读取 485 返回帧 */
static int feed_scale_read_frame(uint8_t *buf, uint16_t max_len)
{
uint16_t len = USART2_GetFrame(buf, max_len);
if (len == 0) return 0;
return (int)len;
}
/* 上报当前状态到平台: 开关、重量、单次投料量、去皮重量 */
static void feed_scale_report_weight(const char *header)
{
(void)header;
/* 云平台字段映射:把剩余料重放到 zero 字段,空载重量放到 weight 字段 */
/* sw1 与 feeding 均上报继电器 1 状态,平台两个开关都能同步刷新 */
NET_publish_status(
MqttInfoStr.Relay_State[1] ? 1 : 0,
MqttInfoStr.Relay_State[1] ? 1 : 0,
MqttInfoStr.Relay_State[2] ? 1 : 0,
MqttInfoStr.Relay_State[3] ? 1 : 0,
MqttInfoStr.Relay_State[4] ? 1 : 0,
(float)zero_weight / 10.0f,
(float)once_weight / 10.0f,
(float)last_weight / 10.0f);
log_info("> FEED: weight=%.1fKG onceWt=%.1fKG zero=%.1fKG",
(float)last_weight / 10.0f,
(float)once_weight / 10.0f,
(float)zero_weight / 10.0f);
}
void feedScaleInit(void)
{
uint8_t tmp[2] = {0};
/* 检测是否是空白/新刷机单片机,是则强制写入默认 1.0KG */
eepromReadData(FEED_SCALE_INIT_FLAG_ADDR, tmp, 1);
if (tmp[0] != FEED_SCALE_INIT_FLAG) {
tmp[0] = FEED_SCALE_INIT_FLAG;
eepromWriteData(FEED_SCALE_INIT_FLAG_ADDR, tmp, 1);
zero_weight = 10; /* 默认去皮 1.0KG */
once_weight = 10; /* 默认单次投料 1.0KG */
tmp[0] = (uint8_t)(zero_weight & 0xFF);
tmp[1] = (uint8_t)(zero_weight >> 8);
eepromWriteData(FEED_SCALE_ZERO_ADDR, tmp, 2);
tmp[0] = (uint8_t)(once_weight & 0xFF);
tmp[1] = (uint8_t)(once_weight >> 8);
eepromWriteData(FEED_SCALE_ONCEWT_ADDR, tmp, 2);
log_info("> FEED: first init, set default zero=1.0KG, onceWt=1.0KG");
} else {
eepromReadData(FEED_SCALE_ZERO_ADDR, tmp, 2);
zero_weight = (uint16_t)tmp[0] | ((uint16_t)tmp[1] << 8);
if (zero_weight == 0 || zero_weight > FEED_SCALE_WEIGHT_MAX) {
zero_weight = 10; /* 默认去皮 1.0KG */
}
eepromReadData(FEED_SCALE_ONCEWT_ADDR, tmp, 2);
once_weight = (uint16_t)tmp[0] | ((uint16_t)tmp[1] << 8);
if (once_weight == 0 || once_weight > FEED_SCALE_WEIGHT_MAX) {
once_weight = 10; /* 默认单次投料 1.0KG */
}
}
log_info("> FEED: init, zero=%.1fKG, onceWt=%.1fKG",
(float)zero_weight / 10.0f, (float)once_weight / 10.0f);
}
void feedScaleSetZero(uint16_t zero_100g)
{
if (zero_100g > FEED_SCALE_WEIGHT_MAX) return;
zero_weight = zero_100g;
/* 平台下发新空载重量后,用最新原始毛重重新计算剩余料重 */
if (last_raw_weight >= zero_weight) {
last_weight = last_raw_weight - zero_weight;
} else {
last_weight = 0;
}
uint8_t tmp[2] = { (uint8_t)(zero_weight & 0xFF), (uint8_t)(zero_weight >> 8) };
eepromWriteData(FEED_SCALE_ZERO_ADDR, tmp, 2);
log_info("> FEED: set zero=%.1fKG, recalc last_weight=%.1fKG",
(float)zero_weight / 10.0f, (float)last_weight / 10.0f);
}
void feedScaleSetOnceWeight(uint16_t once_wt_100g)
{
if (once_wt_100g > FEED_SCALE_WEIGHT_MAX) return;
once_weight = once_wt_100g;
uint8_t tmp[2] = { (uint8_t)(once_weight & 0xFF), (uint8_t)(once_weight >> 8) };
eepromWriteData(FEED_SCALE_ONCEWT_ADDR, tmp, 2);
log_info("> FEED: set onceWt=%.1fKG", (float)once_weight / 10.0f);
}
uint16_t feedScaleGetOnceWeight(void)
{
return once_weight;
}
uint16_t feedScaleGetWeight(void)
{
return last_weight;
}
uint16_t feedScaleGetZero(void)
{
return zero_weight;
}
/* 获取最近一次 485 原始毛重未去皮0.1KG 单位) */
uint16_t feedScaleGetRawWeight(void)
{
return last_raw_weight;
}
FeedScaleState feedScaleGetState(void)
{
return feed_state;
}
uint16_t feedScaleGetReduced(void)
{
return reduced_weight;
}
void feedScaleStart(uint16_t once_wt_100g)
{
if (once_wt_100g < FEED_SCALE_ONCEWT_MIN || once_wt_100g > FEED_SCALE_ONCEWT_MAX) {
log_warn("> FEED: invalid onceWt=%.1fKG", (float)once_wt_100g / 10.0f);
return;
}
if (feed_state == FEED_RUNNING) {
log_warn("> FEED: already running, ignore");
return;
}
if (first_query_reported == 0) {
log_warn("> FEED: scale not ready, cannot start feeding");
return;
}
if (last_weight == 0) {
log_warn("> FEED: last weight is 0, cannot start feeding");
return;
}
once_weight = once_wt_100g;
start_weight = last_weight; /* 取最近一次重量作为起始重量 */
reduced_weight = 0;
add_weight_count = 0;
feed_start_tick = HAL_GetTick();
feed_state = FEED_RUNNING;
relaySet(1, 1);
eepromWriteData(102, &MqttInfoStr.Relay_State[0], 7);
log_info("> FEED: start onceWt=%.1fKG, start=%.1fKG, relay1=ON",
(float)once_weight / 10.0f, (float)start_weight / 10.0f);
/* 投喂开始时立即上报一次状态 */
feed_scale_report_weight("iot.prop.post");
}
void feedScaleStop(void)
{
if (feed_state == FEED_IDLE) return;
relaySet(1, 0);
eepromWriteData(102, &MqttInfoStr.Relay_State[0], 7);
feed_state = FEED_DONE;
log_info("> FEED: stop, reduced=%.1fKG", (float)reduced_weight / 10.0f);
relayRequestReport();
}
static void feed_scale_finish_error(const char *reason)
{
relaySet(1, 0);
eepromWriteData(102, &MqttInfoStr.Relay_State[0], 7);
feed_state = FEED_ERROR;
log_warn("> FEED: error stop, %s", reason);
relayRequestReport();
}
void feedScaleProcess(void)
{
#if FEED_SCALE_DISABLED
return;
#endif
uint8_t rx_buf[32];
int len;
uint16_t cur_weight;
int32_t diff;
/* 触发 USART2 帧超时自动提交 */
USART2_Tick();
/* 空闲时 10s 查询一次; 投喂时 1s 查询一次;
不累计无响应计数,手动测试时漏回也不会一直刷超时 */
uint32_t query_interval = (feed_state == FEED_RUNNING) ? FEED_SCALE_QUERY_RUN_MS : FEED_SCALE_QUERY_IDLE_MS;
if (first_query_sent == 0 || (HAL_GetTick() - last_query_tick) >= query_interval) {
last_query_tick = HAL_GetTick();
feed_scale_send_query();
if (first_query_sent == 0) {
first_query_sent = 1;
log_info("> FEED: first query sent");
}
}
/* 读取 485 返回 */
len = feed_scale_read_frame(rx_buf, sizeof(rx_buf));
if (len > 0) {
if (feed_scale_parse_weight(rx_buf, len, &cur_weight) == 0) {
last_raw_weight = cur_weight; /* 保存原始毛重 */
last_weight = cur_weight > zero_weight ? (cur_weight - zero_weight) : 0; /* 净重 */
if (!first_query_reported) {
/* 首次上电: 查询到一次有效重量后上报平台 */
first_query_reported = 1;
feed_scale_report_weight("iot.prop.post");
log_info("> FEED: first weight reported");
}
if (feed_state == FEED_RUNNING) {
/* 投喂期间:每次有效重量都上报平台,保证实时 */
feed_scale_report_weight("iot.prop.post");
/* 计算减少量,去零后比较 */
diff = (int32_t)start_weight - (int32_t)last_weight;
if (diff < 0) diff = -diff;
reduced_weight = (uint16_t)diff;
log_info("> FEED: run start=%.1f cur=%.1f reduced=%.1f once=%.1f",
(float)start_weight / 10.0f, (float)last_weight / 10.0f,
(float)reduced_weight / 10.0f, (float)once_weight / 10.0f);
/* 条件1: 达到/超过单次投喂量,停止 */
if (reduced_weight >= once_weight) {
log_info("> FEED: target reached, stop");
feedScaleStop();
return;
}
/* 条件2: 重量反而增加,可能有人在加料,连续超过阈值后结束 */
if (last_weight > start_weight) {
add_weight_count++;
if (add_weight_count >= FEED_ADD_WEIGHT_LIMIT) {
feed_scale_finish_error("weight increased");
return;
}
} else {
add_weight_count = 0;
}
/* 条件3: 投喂超时 5 分钟 */
if (HAL_GetTick() - feed_start_tick > FEED_TIMEOUT_MS) {
feed_scale_finish_error("timeout");
return;
}
}
}
}
/* 清理已完成状态,让外部模块在下一循环前处理 */
if (feed_state == FEED_DONE || feed_state == FEED_ERROR) {
/* 状态保持一次,由 relay 状态机上报 */
}
}