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

370 lines
13 KiB
C
Raw Normal View History

/* 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 状态机上报 */
}
}