#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "esp_log.h"
#include "driver/gpio.h"
// =======================
// 引脚定义
// =======================
#define PIN_MISO 16 // ESP32 接 GMD1032 DO/SDO
#define PIN_MOSI 19 // ESP32 接 GMD1032 DI/SDI
#define PIN_CLK 18
#define PIN_CS 17
#define TAG "GMD1032"
// 使用 SPI2_HOST
#define MY_SPI_HOST SPI2_HOST
// =======================
// GMD1032 命令表
// =======================
#define CMD_READ_CELL_VOLTAGE 0xD0
#define CMD_READ_STATUS_REG 0xC9
#define CMD_WAKEUP_CLEAR_STATUS 0x22
#define CMD_CLEAR_ISENSE_ACC 0x23
#define CMD_CLEAR_ADC_RESULT 0x24
#define CMD_RESTART_ADC 0x2E
#define CMD_ENTER_MEASURE_MODE 0x2A
#define CMD_SELF_TEST_CFG_FUSE 0x31
// 对应 CRC8(来自手册或计算)
#define CRC8_0x22 0x2E
#define CRC8_0x23 0x29
#define CRC8_0x24 0x3C
#define CRC8_0x2A 0x16
#define CRC8_0x2E 0x0A
#define CRC8_0x31 0x57
#define CRC8_0xC9 0xB1
#define CRC8_0xD0 0xFE
// =======================
// CRC8 实现 (X^8 + X^2 + X + 1, Init: 0x41)
// =======================
uint8_t crc8(const uint8_t *data, size_t len) {
uint8_t crc = 0x41;
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x07;
} else {
crc <<= 1;
}
}
}
return crc;
}
// =======================
// CRC10 实现 (X^10 + X^7 + X^3 + X^2 + X + 1, Init: 0x10)
// 输入字节左移 2 位以对齐 10bit 框架(常见于 BMS 芯片)
// =======================
uint16_t crc10(const uint8_t *data, size_t len) {
uint16_t crc = 0x10;
for (size_t i = 0; i < len; i++) {
crc ^= (uint16_t)(data[i]) << 2;
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x400) {
crc = (crc << 1) ^ 0x26D;
} else {
crc <<= 1;
}
crc &= 0x3FF;
}
}
return crc;
}
// =======================
// 验证响应帧的 CRC10,并输出详细信息
// =======================
bool validate_response(uint8_t *data) {
if (data == NULL) return false;
uint8_t input[7];
memcpy(input, data, 6);
input[6] = data[6] & 0x3F; // 屏蔽高2位
uint16_t calc_crc = crc10(input, 7);
uint16_t recv_crc = ((data[6] & 0xC0) << 4) | data[7];
recv_crc &= 0x3FF;
ESP_LOGI(TAG, "CRC10 Debug: Calc=0x%03X, Recv=0x%03X", calc_crc, recv_crc);
if (calc_crc != recv_crc) {
ESP_LOGW(TAG, "CRC10 mismatch! Expected: 0x%03X, Got: 0x%03X", calc_crc, recv_crc);
return false;
}
ESP_LOGI(TAG, "CRC10 validation PASSED.");
return true;
}
// =======================
// 强制发送命令并始终打印原始响应(无论成败)
// =======================
esp_err_t send_command_and_read_response(uint8_t cmd_code, uint8_t crc8_val, uint8_t *out_rx_data, size_t rx_len) {
uint8_t local_rx[8] = {0};
uint8_t *rx_data = (out_rx_data != NULL) ? out_rx_data : local_rx;
uint8_t tx_data[8] = {cmd_code, crc8_val, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
spi_transaction_t t = {
.length = 8 * 8,
.tx_buffer = tx_data,
.rx_buffer = rx_data,
};
gpio_set_level(PIN_CS, 0);
esp_rom_delay_us(15);
esp_err_t ret = spi_device_polling_transmit(spi, &t);
esp_rom_delay_us(15);
gpio_set_level(PIN_CS, 1);
// === 强制打印 TX 和 RX 原始数据 ===
ESP_LOG_BUFFER_HEX_LEVEL("TX_CMD", tx_data, 8, ESP_LOG_INFO);
ESP_LOG_BUFFER_HEX_LEVEL("RX_RAW_FORCE", rx_data, 8, ESP_LOG_INFO);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI transaction failed: %s", esp_err_to_name(ret));
}
return ret;
}
// =======================
// 通用带重试的命令发送函数(增强版日志)
// =======================
bool send_gmd1032_command(uint8_t cmd, uint8_t expected_crc8, uint8_t *rx_buf, size_t rx_len, int retries) {
uint8_t crc = crc8(&cmd, 1);
if (expected_crc8 != 0xFF && crc != expected_crc8) {
ESP_LOGW(TAG, "CRC8 mismatch! Expected: 0x%02X, Calculated: 0x%02X", expected_crc8, crc);
return false;
}
for (int i = 0; i < retries; i++) {
ESP_LOGI(TAG, "Attempt %d/%d for command 0x%02X", i + 1, retries, cmd);
uint8_t temp_rx[8] = {0};
uint8_t *use_rx = (rx_buf != NULL) ? rx_buf : temp_rx;
esp_err_t ret = send_command_and_read_response(cmd, crc, use_rx, rx_len);
if (ret == ESP_OK) {
if (rx_len == 0) {
ESP_LOGI(TAG, "No response expected for command 0x%02X.", cmd);
return true;
} else if (validate_response(use_rx)) {
return true;
} else {
ESP_LOGW(TAG, "CRC10 failed on attempt %d.", i + 1);
}
} else {
ESP_LOGE(TAG, "SPI error on attempt %d: %s", i + 1, esp_err_to_name(ret));
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
ESP_LOGE(TAG, "Command 0x%02X failed after %d retries.", cmd, retries);
return false;
}
// =======================
// 【新增】模拟响应测试函数
// =======================
void run_simulation_test(void) {
esp_log_level_set(TAG, ESP_LOG_VERBOSE); // 确保 HEX 能打印出来
ESP_LOGI(TAG, "=== Running CRC10 Simulation Test ===");
// 构造一个模拟的有效响应帧
uint8_t fake_response[8] = {
0x0E, 0x10,
0x0E, 0x70,
0x0E, 0x46,
0x00, 0x00
};
uint8_t crc_input[7];
memcpy(crc_input, fake_response, 6);
crc_input[6] = fake_response[6] & 0x3F;
uint16_t correct_crc = crc10(crc_input, 7);
ESP_LOGI(TAG, "Simulated CRC10: 0x%03X", correct_crc);
// 填入 CRC
fake_response[6] |= ((correct_crc >> 4) & 0xC0);
fake_response[7] = correct_crc & 0xFF;
ESP_LOGI(TAG, "Fake Response Frame Generated:");
ESP_LOG_BUFFER_HEX("FAKE_FRAME", fake_response, 8); // 正确调用
bool result = validate_response(fake_response);
ESP_LOGI(TAG, "Simulation Test Result: %s", result ? "PASSED" : "FAILED");
if (result) {
ESP_LOGI(TAG, "CRC10 implementation is CORRECT. Problem likely in HARDWARE connection.");
} else {
ESP_LOGE(TAG, "CRC10 logic has BUGS! Please check algorithm or bit alignment.");
}
}
// =======================
// 初始化 SPI 总线
// =======================
spi_device_handle_t spi;
esp_err_t init_spi(void) {
spi_bus_config_t buscfg = {
.miso_io_num = PIN_MISO,
.mosi_io_num = PIN_MOSI,
.sclk_io_num = PIN_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
esp_err_t ret = spi_bus_initialize(MY_SPI_HOST, &buscfg, SPI_DMA_DISABLED);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
return ret;
}
spi_device_interface_config_t devcfg = {
.mode = 1,
.clock_speed_hz = 100 * 1000,
.spics_io_num = -1,
.queue_size = 1,
};
// 使用全局 spi 变量
ret = spi_bus_add_device(MY_SPI_HOST, &devcfg, &spi);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
return ret;
}
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << PIN_CS),
};
gpio_config(&io_conf);
gpio_set_level(PIN_CS, 1);
ESP_LOGI(TAG, "SPI initialized successfully.");
return ESP_OK;
}
// =======================
// 发送命令并读取响应(可使用全局 spi)
// =======================
esp_err_t send_command_and_read_response(uint8_t cmd_code, uint8_t crc8_val, uint8_t *out_rx_data, size_t rx_len) {
uint8_t local_rx[8] = {0};
uint8_t *rx_data = (out_rx_data != NULL) ? out_rx_data : local_rx;
uint8_t tx_data[8] = {cmd_code, crc8_val, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
spi_transaction_t t = {
.length = 8 * 8,
.tx_buffer = tx_data,
.rx_buffer = rx_data,
};
gpio_set_level(PIN_CS, 0);
esp_rom_delay_us(15);
// 使用全局 spi 变量
esp_err_t ret = spi_device_polling_transmit(spi, &t);
esp_rom_delay_us(15);
gpio_set_level(PIN_CS, 1);
ESP_LOG_BUFFER_HEXDUMP("TX_CMD", tx_data, 8, ESP_LOG_INFO);
ESP_LOG_BUFFER_HEXDUMP("RX_RAW_FORCE", rx_data, 8, ESP_LOG_INFO);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI transaction failed: %s", esp_err_to_name(ret));
}
return ret;
}
// =======================
// 主任务:读取电池电压等
// =======================
void gmd1032_task(void *arg) {
uint8_t rx_data[8];
if (init_spi() != ESP_OK) {
ESP_LOGE(TAG, "SPI init failed");
vTaskDelete(NULL);
return;
}
ESP_LOGI(TAG, "Starting real GMD1032 communication...");
while (1) {
ESP_LOGI(TAG, "=== New Cycle ===");
if (send_gmd1032_command(CMD_READ_CELL_VOLTAGE, CRC8_0xD0, rx_data, 8, 3)) {
uint16_t cell1 = ((rx_data[0] & 0x0F) << 8) | rx_data[1];
uint16_t cell2 = ((rx_data[2] & 0x0F) << 8) | rx_data[3];
uint16_t cell3 = ((rx_data[4] & 0x0F) << 8) | rx_data[5];
ESP_LOGI(TAG, "Cell1: %.3f V", cell1 * 0.001f);
ESP_LOGI(TAG, "Cell2: %.3f V", cell2 * 0.001f);
ESP_LOGI(TAG, "Cell3: %.3f V", cell3 * 0.001f);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
// =======================
// 入口函数
// =======================
void app_main(void) {
// 🔍 第一步:运行模拟测试,验证 CRC 是否正确
run_simulation_test();
// 给用户一点时间看日志
vTaskDelay(2000 / portTICK_PERIOD_MS);
// 启动主任务
xTaskCreate(gmd1032_task, "gmd1032_task", 4096, NULL, 10, NULL);
}