feat(battery): ADD Battery Controller.

This commit is contained in:
GuanYuankai 2025-12-31 08:05:25 +00:00
parent e91238d114
commit 4302cf6d4f
1 changed files with 352 additions and 0 deletions

352
src/rk_uart_main.cpp Normal file
View File

@ -0,0 +1,352 @@
#include <fcntl.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <atomic>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
// --- 协议常量定义 (需与 STM32 保持一致) ---
#define FRAME_HEAD 0xA5
#define CMD_REPORT 0x01
#define CMD_SET_MODE 0x02
#define CMD_SET_ALARM 0x03
#define CMD_ACK 0xAA
// --- 数据结构定义 (字节对齐) ---
#pragma pack(push, 1)
struct BatteryPacket_t {
uint8_t battery_cap; // 1. 电量百分比
uint16_t time_to_full; // 2. 预测充满时间
uint16_t time_to_empty; // 3. 预测放电时间
uint8_t battery_status; // 4. 电池状态
int16_t temperature; // 5. 温度
uint8_t system_mode; // 6. 模式
uint8_t alarm_flag; // 7. 报警标志
};
#pragma pack(pop)
// --- 简单的串口封装类 ---
class SerialPort {
public:
SerialPort(const std::string& device, int baudRate) : fd_(-1) {
openPort(device, baudRate);
}
~SerialPort() {
if (fd_ >= 0)
close(fd_);
}
bool isOpen() const {
return fd_ >= 0;
}
int writeData(const uint8_t* data, size_t len) {
if (fd_ < 0)
return -1;
return write(fd_, data, len);
}
int readData(uint8_t* buffer, size_t maxLen) {
if (fd_ < 0)
return -1;
return read(fd_, buffer, maxLen);
}
private:
int fd_;
void openPort(const std::string& device, int baudRate) {
fd_ = open(device.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
if (fd_ < 0) {
perror("Failed to open serial port");
return;
}
// 恢复阻塞模式 (或者根据需要保持非阻塞)
fcntl(fd_, F_SETFL, 0);
struct termios options;
tcgetattr(fd_, &options);
// 设置波特率
speed_t speed;
switch (baudRate) {
case 9600:
speed = B9600;
break;
case 115200:
speed = B115200;
break;
default:
speed = B115200;
break;
}
cfsetispeed(&options, speed);
cfsetospeed(&options, speed);
// 配置为 8N1, Raw 模式
options.c_cflag |= (CLOCAL | CREAD); // 启用接收
options.c_cflag &= ~PARENB; // 无校验
options.c_cflag &= ~CSTOPB; // 1停止位
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8; // 8数据位
options.c_cflag &= ~CRTSCTS; // 无流控
// 原始输入输出模式 (禁止回显、信号处理等)
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁止软件流控
options.c_iflag &= ~(ICRNL | INLCR); // 禁止CR/LF转换
options.c_oflag &= ~OPOST; // 原始输出
tcsetattr(fd_, TCSANOW, &options);
tcflush(fd_, TCIOFLUSH);
}
};
// --- 通信协议处理类 ---
class CommProtocol {
public:
CommProtocol(SerialPort* serial) : serial_(serial), running_(false) {}
~CommProtocol() {
stop();
}
// 启动接收线程
void start() {
running_ = true;
rxThread_ = std::thread(&CommProtocol::receiveLoop, this);
}
void stop() {
running_ = false;
if (rxThread_.joinable())
rxThread_.join();
}
// 发送指令:设置模式
void sendSetMode(uint8_t mode) {
uint8_t payload = mode;
sendPacket(CMD_SET_MODE, &payload, 1);
}
// 发送指令:设置报警
void sendSetAlarm(uint8_t alarm) {
uint8_t payload = alarm;
sendPacket(CMD_SET_ALARM, &payload, 1);
}
private:
SerialPort* serial_;
std::thread rxThread_;
std::atomic<bool> running_;
std::vector<uint8_t> rxBuffer_; // 接收缓冲区
// CRC32 计算 (对应 STM32 HAL_CRC_Calculate + XOR)
// 注意:这里使用的是标准 CRC32 算法 (Polynomial 0x04C11DB7)
// 如果 STM32 端是默认配置,这应该能对上。
uint32_t calculateCRC32(const uint8_t* data, size_t len) {
uint32_t crc = 0xFFFFFFFF; // Initial Value
for (size_t i = 0; i < len; i++) {
uint32_t byte = data[i];
// 标准 CRC32 是反转输入和输出的,这里模拟 STM32 常见配置
// 这里使用最通用的查表法或者位移法
// 为简化代码,使用位移法 (效率较低但足够)
// 注意STM32 硬件CRC如果不开启 REVERSE_INPUT/OUTPUT行为是 BigEndian 风格
// 但你在 STM32 代码里写了 `crc ^ 0xFFFFFFFF`,通常是为了匹配标准 CRC32
// 下面这个是标准 Ethernet CRC32 算法:
crc = crc ^ byte;
for (int j = 0; j < 8; j++) {
if (crc & 1)
crc = (crc >> 1) ^ 0xEDB88320;
else
crc = crc >> 1;
}
}
return crc ^ 0xFFFFFFFF;
// **注意**如果发现校验一直不过STM32 硬件CRC可能没有开启 Input/Output Reverse。
// 如果是那样,请尝试替换为非反转的 CRC32 算法。
}
// 如果上面的 CRC 算不对,试试这个 STM32 默认硬件CRC (无反转) 的版本:
/*
uint32_t calculateCRC32_STM32_Default(const uint8_t* data, size_t len) {
uint32_t crc = 0xFFFFFFFF;
for(size_t i=0; i<len; i++) {
crc = crc ^ (uint32_t)data[i] << 24;
for(int j=0; j<8; j++) {
if(crc & 0x80000000) crc = (crc << 1) ^ 0x04C11DB7;
else crc = crc << 1;
}
}
return crc; // STM32 代码有 ^ 0xFFFFFFFF 就加上,没有就不加
}
*/
void sendPacket(uint8_t cmd, const uint8_t* payload, uint8_t len) {
std::vector<uint8_t> packet;
packet.push_back(FRAME_HEAD);
packet.push_back(cmd);
packet.push_back(len);
if (len > 0 && payload != nullptr) {
packet.insert(packet.end(), payload, payload + len);
}
// 计算 CRC (Header + Cmd + Len + Payload)
uint32_t crc = calculateCRC32(packet.data(), packet.size());
// 小端序发送 CRC
packet.push_back((crc >> 0) & 0xFF);
packet.push_back((crc >> 8) & 0xFF);
packet.push_back((crc >> 16) & 0xFF);
packet.push_back((crc >> 24) & 0xFF);
serial_->writeData(packet.data(), packet.size());
std::cout << "[Tx] Cmd: " << std::hex << (int)cmd << " Len: " << (int)len << std::dec
<< std::endl;
}
void processFrame(const uint8_t* frame, size_t totalLen) {
uint8_t cmd = frame[1];
// uint8_t len = frame[2]; // payload length
const uint8_t* payload = &frame[3];
switch (cmd) {
case CMD_REPORT: {
if (totalLen >= sizeof(BatteryPacket_t) + 7) {
BatteryPacket_t data;
memcpy(&data, payload, sizeof(BatteryPacket_t));
std::cout << "\n=== STM32 Status Report ===" << std::endl;
std::cout << "Cap: " << (int)data.battery_cap << "%" << std::endl;
std::cout << "Time Full: " << data.time_to_full << " min" << std::endl;
std::cout << "Time Empty: " << data.time_to_empty << " min" << std::endl;
std::cout << "Status: " << (int)data.battery_status << std::endl;
std::cout << "Temp: " << (float)data.temperature / 10.0f << " C" << std::endl;
std::cout << "Mode: " << (int)data.system_mode << std::endl;
std::cout << "Alarm: " << (int)data.alarm_flag << std::endl;
// 回复 ACK
uint8_t ack[] = "OK";
sendPacket(CMD_ACK, ack, 2);
}
break;
}
case CMD_ACK:
std::cout << "[Rx] Received ACK from STM32" << std::endl;
break;
default:
std::cout << "[Rx] Unknown CMD: " << std::hex << (int)cmd << std::endl;
break;
}
}
void receiveLoop() {
uint8_t buf[128];
while (running_) {
int n = serial_->readData(buf, sizeof(buf));
if (n > 0) {
// 将新数据追加到 vector 缓冲区
rxBuffer_.insert(rxBuffer_.end(), buf, buf + n);
// 尝试解析帧
while (rxBuffer_.size() >= 7) { // 最小帧长 Header+Cmd+Len+CRC(4) = 7
// 1. 寻找帧头
if (rxBuffer_[0] != FRAME_HEAD) {
rxBuffer_.erase(rxBuffer_.begin()); // 丢弃无效字节
continue;
}
uint8_t dataLen = rxBuffer_[2];
size_t frameLen = 3 + dataLen + 4;
// 2. 检查缓冲区是否有完整的一帧
if (rxBuffer_.size() < frameLen) {
break; // 数据不够,等待下次接收
}
// 3. 提取 CRC 并校验
uint32_t receivedCRC = 0;
receivedCRC |= rxBuffer_[frameLen - 4] << 0;
receivedCRC |= rxBuffer_[frameLen - 3] << 8;
receivedCRC |= rxBuffer_[frameLen - 2] << 16;
receivedCRC |= rxBuffer_[frameLen - 1] << 24;
uint32_t calcCRC = calculateCRC32(rxBuffer_.data(), frameLen - 4);
if (calcCRC == receivedCRC) {
// 校验通过,处理帧
processFrame(rxBuffer_.data(), frameLen);
// 移除已处理的帧
rxBuffer_.erase(rxBuffer_.begin(), rxBuffer_.begin() + frameLen);
} else {
std::cerr << "CRC Error! Calc: " << std::hex << calcCRC
<< " Recv: " << receivedCRC << std::endl;
// 校验失败,移除头字节,尝试重新寻找下一帧
rxBuffer_.erase(rxBuffer_.begin());
}
}
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
}
};
int main() {
// 1. 配置串口设备 (请根据实际情况修改,例如 /dev/ttyUSB0 或 /dev/ttyS4)
// RK3588 的硬件串口通常是 /dev/ttyS0 ~ /dev/ttyS9或者 /dev/ttyFIQ0
std::string devPath = "/dev/ttyS4";
std::cout << "Opening serial port: " << devPath << std::endl;
SerialPort serial(devPath, 115200);
if (!serial.isOpen()) {
return -1;
}
// 2. 启动协议处理
CommProtocol protocol(&serial);
protocol.start();
// 3. 交互循环
while (true) {
std::cout << "\nChoose Action:\n1. Set Mode WORK\n2. Set Mode SLEEP\n3. Set Alarm ON\n4. "
"Set Alarm OFF\nInput: ";
int choice;
std::cin >> choice;
switch (choice) {
case 1:
protocol.sendSetMode(1); // MODE_WORK
break;
case 2:
protocol.sendSetMode(2); // MODE_SLEEP
break;
case 3:
protocol.sendSetAlarm(1);
break;
case 4:
protocol.sendSetAlarm(0);
break;
default:
break;
}
// 简单延时防止刷屏
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
return 0;
}