Compare commits
3 Commits
65478d9f02
...
e5b2ad20a3
| Author | SHA1 | Date | |
|---|---|---|---|
| e5b2ad20a3 | |||
| 9313675630 | |||
| a6359364fd |
@@ -58,6 +58,7 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/Core/Bsp/bsp_pid.c
|
${CMAKE_CURRENT_SOURCE_DIR}/Core/Bsp/bsp_pid.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Core/Bsp/bsp_track_ir.c
|
${CMAKE_CURRENT_SOURCE_DIR}/Core/Bsp/bsp_track_ir.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Core/Bsp/bsp_sr04.c
|
${CMAKE_CURRENT_SOURCE_DIR}/Core/Bsp/bsp_sr04.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/Core/Bsp/bsp_rc522.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add include paths
|
# Add include paths
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "bsp_hall.h"
|
#include "bsp_hall.h"
|
||||||
#include "bsp_sr04.h"
|
#include "bsp_sr04.h"
|
||||||
|
#include "bsp_rc522.h"
|
||||||
|
|
||||||
|
|
||||||
/* 霍尔传感器数据结构体定义描述 */
|
/* 霍尔传感器数据结构体定义描述 */
|
||||||
@@ -23,6 +24,7 @@ void hall_init(void)
|
|||||||
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
|
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
|
||||||
{
|
{
|
||||||
hall_pulse_callback(GPIO_Pin);
|
hall_pulse_callback(GPIO_Pin);
|
||||||
|
rc522_irq_callback(GPIO_Pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
632
Core/Bsp/bsp_rc522.c
Normal file
632
Core/Bsp/bsp_rc522.c
Normal file
@@ -0,0 +1,632 @@
|
|||||||
|
#include "bsp_rc522.h"
|
||||||
|
|
||||||
|
#include "spi.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* ========================= RC522 寄存器定义 ========================= */
|
||||||
|
#define RC522_REG_COMMAND 0x01U
|
||||||
|
#define RC522_REG_COM_I_EN 0x02U
|
||||||
|
#define RC522_REG_DIV_I_EN 0x03U
|
||||||
|
#define RC522_REG_COM_IRQ 0x04U
|
||||||
|
#define RC522_REG_DIV_IRQ 0x05U
|
||||||
|
#define RC522_REG_ERROR 0x06U
|
||||||
|
#define RC522_REG_STATUS2 0x08U
|
||||||
|
#define RC522_REG_FIFO_DATA 0x09U
|
||||||
|
#define RC522_REG_FIFO_LEVEL 0x0AU
|
||||||
|
#define RC522_REG_CONTROL 0x0CU
|
||||||
|
#define RC522_REG_BIT_FRAMING 0x0DU
|
||||||
|
#define RC522_REG_COLL 0x0EU
|
||||||
|
#define RC522_REG_MODE 0x11U
|
||||||
|
#define RC522_REG_TX_MODE 0x12U
|
||||||
|
#define RC522_REG_RX_MODE 0x13U
|
||||||
|
#define RC522_REG_TX_CONTROL 0x14U
|
||||||
|
#define RC522_REG_TX_ASK 0x15U
|
||||||
|
#define RC522_REG_CRC_RESULT_H 0x21U
|
||||||
|
#define RC522_REG_CRC_RESULT_L 0x22U
|
||||||
|
#define RC522_REG_T_MODE 0x2AU
|
||||||
|
#define RC522_REG_T_PRESCALER 0x2BU
|
||||||
|
#define RC522_REG_T_RELOAD_H 0x2CU
|
||||||
|
#define RC522_REG_T_RELOAD_L 0x2DU
|
||||||
|
|
||||||
|
/* ========================= RC522 命令定义 ========================= */
|
||||||
|
#define RC522_CMD_IDLE 0x00U
|
||||||
|
#define RC522_CMD_CALC_CRC 0x03U
|
||||||
|
#define RC522_CMD_TRANSCEIVE 0x0CU
|
||||||
|
#define RC522_CMD_SOFT_RESET 0x0FU
|
||||||
|
|
||||||
|
/* ========================= PICC 命令定义 ========================= */
|
||||||
|
#define PICC_CMD_REQA 0x26U
|
||||||
|
#define PICC_CMD_SEL_CL1 0x93U
|
||||||
|
#define PICC_CMD_SEL_CL2 0x95U
|
||||||
|
#define PICC_CMD_SEL_CL3 0x97U
|
||||||
|
#define PICC_CMD_HLTA 0x50U
|
||||||
|
#define PICC_CMD_CT 0x88U
|
||||||
|
|
||||||
|
#define RC522_SPI_TIMEOUT_MS 20U
|
||||||
|
|
||||||
|
static rc522_card_info_t g_last_card;
|
||||||
|
static uint32_t g_run_led_hold_until_ms = 0U;
|
||||||
|
static uint32_t g_last_new_event_tick_ms = 0U;
|
||||||
|
static volatile uint8_t g_irq_pending = 1U;
|
||||||
|
|
||||||
|
#if (RC522_RUN_LED_ON_LEVEL == GPIO_PIN_SET)
|
||||||
|
#define RC522_RUN_LED_OFF_LEVEL GPIO_PIN_RESET
|
||||||
|
#else
|
||||||
|
#define RC522_RUN_LED_OFF_LEVEL GPIO_PIN_SET
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void rc522_set_run_led(uint8_t on)
|
||||||
|
{
|
||||||
|
#if (RC522_RUN_LED_ENABLE == 1U)
|
||||||
|
HAL_GPIO_WritePin(RUN_LED_GPIO_Port,
|
||||||
|
RUN_LED_Pin,
|
||||||
|
(on != 0U) ? RC522_RUN_LED_ON_LEVEL : RC522_RUN_LED_OFF_LEVEL);
|
||||||
|
#else
|
||||||
|
(void)on;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rc522_update_run_led_state(void)
|
||||||
|
{
|
||||||
|
#if (RC522_RUN_LED_ENABLE == 1U)
|
||||||
|
uint32_t now = HAL_GetTick();
|
||||||
|
if ((int32_t)(g_run_led_hold_until_ms - now) > 0) {
|
||||||
|
rc522_set_run_led(1U);
|
||||||
|
} else {
|
||||||
|
rc522_set_run_led(0U);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t rc522_uid_equal(const rc522_card_info_t *a, const rc522_card_info_t *b)
|
||||||
|
{
|
||||||
|
if (a == NULL || b == NULL) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
if (a->uid_len != b->uid_len) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
if (a->uid_len == 0U || a->uid_len > RC522_UID_MAX_LEN) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
if (memcmp(a->uid, b->uid, a->uid_len) != 0) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
return 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t rc522_cache_is_fresh(void)
|
||||||
|
{
|
||||||
|
if (g_last_card.valid == 0U) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
if ((HAL_GetTick() - g_last_card.last_seen_tick_ms) > RC522_CACHE_EXPIRE_MS) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
return 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------- 底层 SPI 辅助 ------------------------- */
|
||||||
|
static void rc522_cs_low(void)
|
||||||
|
{
|
||||||
|
HAL_GPIO_WritePin(SDA_GPIO_Port, SDA_Pin, GPIO_PIN_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rc522_cs_high(void)
|
||||||
|
{
|
||||||
|
HAL_GPIO_WritePin(SDA_GPIO_Port, SDA_Pin, GPIO_PIN_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
static rc522_status_t rc522_write_reg(uint8_t reg, uint8_t value)
|
||||||
|
{
|
||||||
|
uint8_t tx[2];
|
||||||
|
tx[0] = (uint8_t)((reg << 1U) & 0x7EU);
|
||||||
|
tx[1] = value;
|
||||||
|
|
||||||
|
rc522_cs_low();
|
||||||
|
if (HAL_SPI_Transmit(&hspi1, tx, 2U, RC522_SPI_TIMEOUT_MS) != HAL_OK) {
|
||||||
|
rc522_cs_high();
|
||||||
|
return RC522_ERR_SPI;
|
||||||
|
}
|
||||||
|
rc522_cs_high();
|
||||||
|
return RC522_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rc522_status_t rc522_read_reg(uint8_t reg, uint8_t *value)
|
||||||
|
{
|
||||||
|
uint8_t tx[2];
|
||||||
|
uint8_t rx[2] = {0};
|
||||||
|
|
||||||
|
if (value == NULL) {
|
||||||
|
return RC522_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx[0] = (uint8_t)(((reg << 1U) & 0x7EU) | 0x80U);
|
||||||
|
tx[1] = 0x00U;
|
||||||
|
|
||||||
|
rc522_cs_low();
|
||||||
|
if (HAL_SPI_TransmitReceive(&hspi1, tx, rx, 2U, RC522_SPI_TIMEOUT_MS) != HAL_OK) {
|
||||||
|
rc522_cs_high();
|
||||||
|
return RC522_ERR_SPI;
|
||||||
|
}
|
||||||
|
rc522_cs_high();
|
||||||
|
|
||||||
|
*value = rx[1];
|
||||||
|
return RC522_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rc522_status_t rc522_set_bitmask(uint8_t reg, uint8_t mask)
|
||||||
|
{
|
||||||
|
uint8_t val = 0;
|
||||||
|
rc522_status_t st = rc522_read_reg(reg, &val);
|
||||||
|
if (st != RC522_OK) {
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
return rc522_write_reg(reg, (uint8_t)(val | mask));
|
||||||
|
}
|
||||||
|
|
||||||
|
static rc522_status_t rc522_clear_bitmask(uint8_t reg, uint8_t mask)
|
||||||
|
{
|
||||||
|
uint8_t val = 0;
|
||||||
|
rc522_status_t st = rc522_read_reg(reg, &val);
|
||||||
|
if (st != RC522_OK) {
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
return rc522_write_reg(reg, (uint8_t)(val & (uint8_t)(~mask)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static rc522_status_t rc522_antenna_on(void)
|
||||||
|
{
|
||||||
|
return rc522_set_bitmask(RC522_REG_TX_CONTROL, 0x03U);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------- 核心通信流程 ------------------------- */
|
||||||
|
static rc522_status_t rc522_calculate_crc(const uint8_t *data, uint8_t len, uint8_t *crc_out)
|
||||||
|
{
|
||||||
|
uint8_t irq = 0;
|
||||||
|
uint32_t start = HAL_GetTick();
|
||||||
|
|
||||||
|
if (data == NULL || crc_out == NULL) {
|
||||||
|
return RC522_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_write_reg(RC522_REG_COMMAND, RC522_CMD_IDLE);
|
||||||
|
rc522_write_reg(RC522_REG_DIV_IRQ, 0x04U);
|
||||||
|
rc522_write_reg(RC522_REG_FIFO_LEVEL, 0x80U);
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < len; i++) {
|
||||||
|
rc522_write_reg(RC522_REG_FIFO_DATA, data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_write_reg(RC522_REG_COMMAND, RC522_CMD_CALC_CRC);
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc522_read_reg(RC522_REG_DIV_IRQ, &irq);
|
||||||
|
if ((HAL_GetTick() - start) > 20U) {
|
||||||
|
return RC522_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
} while ((irq & 0x04U) == 0U);
|
||||||
|
|
||||||
|
rc522_read_reg(RC522_REG_CRC_RESULT_L, &crc_out[0]);
|
||||||
|
rc522_read_reg(RC522_REG_CRC_RESULT_H, &crc_out[1]);
|
||||||
|
return RC522_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rc522_status_t rc522_transceive(const uint8_t *send_data,
|
||||||
|
uint8_t send_len,
|
||||||
|
uint8_t *back_data,
|
||||||
|
uint8_t *back_len,
|
||||||
|
uint8_t valid_bits)
|
||||||
|
{
|
||||||
|
uint8_t irq_en = 0x77U;
|
||||||
|
uint8_t wait_irq = 0x30U;
|
||||||
|
uint8_t irq = 0;
|
||||||
|
uint8_t error = 0;
|
||||||
|
uint8_t fifo_level = 0;
|
||||||
|
uint8_t control = 0;
|
||||||
|
uint32_t start = HAL_GetTick();
|
||||||
|
|
||||||
|
if (send_data == NULL || send_len == 0U || back_len == NULL) {
|
||||||
|
return RC522_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_write_reg(RC522_REG_COMMAND, RC522_CMD_IDLE);
|
||||||
|
rc522_write_reg(RC522_REG_COM_I_EN, (uint8_t)(irq_en | 0x80U));
|
||||||
|
rc522_write_reg(RC522_REG_COM_IRQ, 0x7FU);
|
||||||
|
rc522_write_reg(RC522_REG_FIFO_LEVEL, 0x80U);
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < send_len; i++) {
|
||||||
|
rc522_write_reg(RC522_REG_FIFO_DATA, send_data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_write_reg(RC522_REG_BIT_FRAMING, valid_bits);
|
||||||
|
rc522_write_reg(RC522_REG_COMMAND, RC522_CMD_TRANSCEIVE);
|
||||||
|
rc522_set_bitmask(RC522_REG_BIT_FRAMING, 0x80U);
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc522_read_reg(RC522_REG_COM_IRQ, &irq);
|
||||||
|
if ((HAL_GetTick() - start) > 30U) {
|
||||||
|
rc522_clear_bitmask(RC522_REG_BIT_FRAMING, 0x80U);
|
||||||
|
return RC522_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
} while ((irq & wait_irq) == 0U && (irq & 0x01U) == 0U);
|
||||||
|
|
||||||
|
rc522_clear_bitmask(RC522_REG_BIT_FRAMING, 0x80U);
|
||||||
|
|
||||||
|
rc522_read_reg(RC522_REG_ERROR, &error);
|
||||||
|
if ((error & 0x13U) != 0U) {
|
||||||
|
return RC522_ERR_INTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_read_reg(RC522_REG_FIFO_LEVEL, &fifo_level);
|
||||||
|
if (fifo_level == 0U) {
|
||||||
|
*back_len = 0U;
|
||||||
|
return RC522_NO_CARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (back_data != NULL) {
|
||||||
|
uint8_t to_read = fifo_level;
|
||||||
|
if (to_read > *back_len) {
|
||||||
|
to_read = *back_len;
|
||||||
|
}
|
||||||
|
for (uint8_t i = 0; i < to_read; i++) {
|
||||||
|
rc522_read_reg(RC522_REG_FIFO_DATA, &back_data[i]);
|
||||||
|
}
|
||||||
|
*back_len = to_read;
|
||||||
|
} else {
|
||||||
|
*back_len = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_read_reg(RC522_REG_CONTROL, &control);
|
||||||
|
(void)control;
|
||||||
|
|
||||||
|
return RC522_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rc522_status_t rc522_request_a(uint16_t *atqa)
|
||||||
|
{
|
||||||
|
uint8_t cmd = PICC_CMD_REQA;
|
||||||
|
uint8_t recv[2] = {0};
|
||||||
|
uint8_t recv_len = sizeof(recv);
|
||||||
|
|
||||||
|
if (atqa == NULL) {
|
||||||
|
return RC522_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* REQA 是 7bit 命令,BitFramingReg 低 3 位写 0x07 */
|
||||||
|
rc522_status_t st = rc522_transceive(&cmd, 1U, recv, &recv_len, 0x07U);
|
||||||
|
if (st != RC522_OK) {
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
if (recv_len != 2U) {
|
||||||
|
return RC522_NO_CARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
*atqa = (uint16_t)(((uint16_t)recv[0] << 8U) | recv[1]);
|
||||||
|
return RC522_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t rc522_get_sel_cmd_by_level(uint8_t level)
|
||||||
|
{
|
||||||
|
if (level == 1U) {
|
||||||
|
return PICC_CMD_SEL_CL1;
|
||||||
|
}
|
||||||
|
if (level == 2U) {
|
||||||
|
return PICC_CMD_SEL_CL2;
|
||||||
|
}
|
||||||
|
return PICC_CMD_SEL_CL3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rc522_status_t rc522_anticoll_level(uint8_t level, uint8_t uid_part[5])
|
||||||
|
{
|
||||||
|
uint8_t sel_cmd = rc522_get_sel_cmd_by_level(level);
|
||||||
|
uint8_t send_buf[2] = {sel_cmd, 0x20U};
|
||||||
|
uint8_t recv[5] = {0};
|
||||||
|
uint8_t recv_len = sizeof(recv);
|
||||||
|
|
||||||
|
rc522_status_t st = rc522_transceive(send_buf, 2U, recv, &recv_len, 0x00U);
|
||||||
|
if (st != RC522_OK) {
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
if (recv_len != 5U) {
|
||||||
|
return RC522_ERR_INTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t bcc = (uint8_t)(recv[0] ^ recv[1] ^ recv[2] ^ recv[3]);
|
||||||
|
if (bcc != recv[4]) {
|
||||||
|
return RC522_ERR_BCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(uid_part, recv, 5U);
|
||||||
|
return RC522_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rc522_status_t rc522_select_level(uint8_t level, const uint8_t uid_part[5], uint8_t *sak)
|
||||||
|
{
|
||||||
|
uint8_t send_buf[9] = {0};
|
||||||
|
uint8_t crc[2] = {0};
|
||||||
|
uint8_t recv[3] = {0};
|
||||||
|
uint8_t recv_len = sizeof(recv);
|
||||||
|
|
||||||
|
if (sak == NULL) {
|
||||||
|
return RC522_ERR_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_buf[0] = rc522_get_sel_cmd_by_level(level);
|
||||||
|
send_buf[1] = 0x70U;
|
||||||
|
send_buf[2] = uid_part[0];
|
||||||
|
send_buf[3] = uid_part[1];
|
||||||
|
send_buf[4] = uid_part[2];
|
||||||
|
send_buf[5] = uid_part[3];
|
||||||
|
send_buf[6] = uid_part[4];
|
||||||
|
|
||||||
|
rc522_status_t st = rc522_calculate_crc(send_buf, 7U, crc);
|
||||||
|
if (st != RC522_OK) {
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_buf[7] = crc[0];
|
||||||
|
send_buf[8] = crc[1];
|
||||||
|
|
||||||
|
st = rc522_transceive(send_buf, 9U, recv, &recv_len, 0x00U);
|
||||||
|
if (st != RC522_OK) {
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
if (recv_len < 1U) {
|
||||||
|
return RC522_ERR_INTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*sak = recv[0];
|
||||||
|
return RC522_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rc522_halt(void)
|
||||||
|
{
|
||||||
|
uint8_t send_buf[4] = {PICC_CMD_HLTA, 0x00U, 0x00U, 0x00U};
|
||||||
|
uint8_t crc[2] = {0};
|
||||||
|
uint8_t recv_len = 0;
|
||||||
|
|
||||||
|
if (rc522_calculate_crc(send_buf, 2U, crc) != RC522_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
send_buf[2] = crc[0];
|
||||||
|
send_buf[3] = crc[1];
|
||||||
|
|
||||||
|
(void)rc522_transceive(send_buf, 4U, NULL, &recv_len, 0x00U);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------- 对外接口 ------------------------- */
|
||||||
|
rc522_status_t rc522_init(void)
|
||||||
|
{
|
||||||
|
memset(&g_last_card, 0, sizeof(g_last_card));
|
||||||
|
g_run_led_hold_until_ms = 0U;
|
||||||
|
g_last_new_event_tick_ms = 0U;
|
||||||
|
g_irq_pending = 1U;
|
||||||
|
|
||||||
|
/* SPI 片选空闲电平应为高 */
|
||||||
|
rc522_cs_high();
|
||||||
|
rc522_set_run_led(0U);
|
||||||
|
|
||||||
|
/* RST: 硬件复位脉冲 */
|
||||||
|
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_RESET);
|
||||||
|
HAL_Delay(5);
|
||||||
|
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_SET);
|
||||||
|
HAL_Delay(50);
|
||||||
|
|
||||||
|
/* 软复位 */
|
||||||
|
if (rc522_write_reg(RC522_REG_COMMAND, RC522_CMD_SOFT_RESET) != RC522_OK) {
|
||||||
|
return RC522_ERR_SPI;
|
||||||
|
}
|
||||||
|
HAL_Delay(50);
|
||||||
|
|
||||||
|
/* 定时器配置: 与常见 RC522 初始化配置兼容 */
|
||||||
|
rc522_write_reg(RC522_REG_T_MODE, 0x8DU);
|
||||||
|
rc522_write_reg(RC522_REG_T_PRESCALER, 0x3EU);
|
||||||
|
rc522_write_reg(RC522_REG_T_RELOAD_L, 30U);
|
||||||
|
rc522_write_reg(RC522_REG_T_RELOAD_H, 0U);
|
||||||
|
|
||||||
|
rc522_write_reg(RC522_REG_TX_ASK, 0x40U);
|
||||||
|
rc522_write_reg(RC522_REG_MODE, 0x3DU);
|
||||||
|
rc522_write_reg(RC522_REG_TX_MODE, 0x00U);
|
||||||
|
rc522_write_reg(RC522_REG_RX_MODE, 0x00U);
|
||||||
|
|
||||||
|
/* 开启天线 */
|
||||||
|
return rc522_antenna_on();
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_status_t rc522_poll(rc522_card_info_t *out_card)
|
||||||
|
{
|
||||||
|
rc522_card_info_t temp = {0};
|
||||||
|
uint8_t level_data[5] = {0};
|
||||||
|
uint8_t uid_pos = 0U;
|
||||||
|
uint8_t level = 1U;
|
||||||
|
rc522_status_t st;
|
||||||
|
|
||||||
|
rc522_update_run_led_state();
|
||||||
|
|
||||||
|
st = rc522_request_a(&temp.atqa);
|
||||||
|
if (st != RC522_OK) {
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (level <= 3U) {
|
||||||
|
st = rc522_anticoll_level(level, level_data);
|
||||||
|
if (st != RC522_OK) {
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
st = rc522_select_level(level, level_data, &temp.sak);
|
||||||
|
if (st != RC522_OK) {
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level_data[0] == PICC_CMD_CT) {
|
||||||
|
if ((uid_pos + 3U) > RC522_UID_MAX_LEN) {
|
||||||
|
return RC522_ERR_INTERNAL;
|
||||||
|
}
|
||||||
|
temp.uid[uid_pos++] = level_data[1];
|
||||||
|
temp.uid[uid_pos++] = level_data[2];
|
||||||
|
temp.uid[uid_pos++] = level_data[3];
|
||||||
|
} else {
|
||||||
|
if ((uid_pos + 4U) > RC522_UID_MAX_LEN) {
|
||||||
|
return RC522_ERR_INTERNAL;
|
||||||
|
}
|
||||||
|
temp.uid[uid_pos++] = level_data[0];
|
||||||
|
temp.uid[uid_pos++] = level_data[1];
|
||||||
|
temp.uid[uid_pos++] = level_data[2];
|
||||||
|
temp.uid[uid_pos++] = level_data[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SAK bit2=1 表示还有下一层级 UID */
|
||||||
|
if ((temp.sak & 0x04U) == 0U) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uid_pos == 0U) {
|
||||||
|
return RC522_ERR_INTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp.valid = 1U;
|
||||||
|
temp.uid_len = uid_pos;
|
||||||
|
temp.last_seen_tick_ms = HAL_GetTick();
|
||||||
|
|
||||||
|
if (!rc522_uid_equal(&temp, &g_last_card)) {
|
||||||
|
g_last_new_event_tick_ms = temp.last_seen_tick_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_last_card = temp;
|
||||||
|
g_run_led_hold_until_ms = temp.last_seen_tick_ms + RC522_RUN_LED_HOLD_MS;
|
||||||
|
rc522_set_run_led(1U);
|
||||||
|
g_irq_pending = 0U;
|
||||||
|
|
||||||
|
if (out_card != NULL) {
|
||||||
|
*out_card = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_halt();
|
||||||
|
return RC522_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rc522_get_last_card(rc522_card_info_t *out_card)
|
||||||
|
{
|
||||||
|
if (out_card == NULL) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
if (!rc522_cache_is_fresh()) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_card = g_last_card;
|
||||||
|
return 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rc522_clear_last_card(void)
|
||||||
|
{
|
||||||
|
memset(&g_last_card, 0, sizeof(g_last_card));
|
||||||
|
g_run_led_hold_until_ms = 0U;
|
||||||
|
g_last_new_event_tick_ms = 0U;
|
||||||
|
rc522_set_run_led(0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rc522_has_valid_card(void)
|
||||||
|
{
|
||||||
|
return rc522_cache_is_fresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rc522_get_new_card(rc522_card_info_t *out_card)
|
||||||
|
{
|
||||||
|
if (out_card == NULL) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
if (!rc522_cache_is_fresh()) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
if (g_last_new_event_tick_ms != g_last_card.last_seen_tick_ms) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_card = g_last_card;
|
||||||
|
g_last_new_event_tick_ms = 0U;
|
||||||
|
return 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rc522_uid_to_string(const rc522_card_info_t *card, char *out_str, uint16_t out_len)
|
||||||
|
{
|
||||||
|
uint16_t need_len;
|
||||||
|
uint16_t pos = 0;
|
||||||
|
|
||||||
|
if (card == NULL || out_str == NULL || card->uid_len == 0U || card->uid_len > RC522_UID_MAX_LEN) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 每字节2字符,字节间空格(uid_len-1),再加结尾\0 */
|
||||||
|
need_len = (uint16_t)((card->uid_len * 2U) + (card->uid_len - 1U) + 1U);
|
||||||
|
|
||||||
|
if (out_len < need_len) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < card->uid_len; i++) {
|
||||||
|
int n = snprintf(&out_str[pos], (size_t)(out_len - pos), "%02X", card->uid[i]);
|
||||||
|
if (n <= 0) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
pos = (uint16_t)(pos + (uint16_t)n);
|
||||||
|
if (i != (uint8_t)(card->uid_len - 1U)) {
|
||||||
|
if (pos + 1U >= out_len) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
out_str[pos++] = ' ';
|
||||||
|
out_str[pos] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_status_t rc522_service(rc522_card_info_t *out_card)
|
||||||
|
{
|
||||||
|
rc522_update_run_led_state();
|
||||||
|
|
||||||
|
if (!rc522_cache_is_fresh()) {
|
||||||
|
g_last_card.valid = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (RC522_USE_IRQ_TRIGGER == 1U)
|
||||||
|
if (g_irq_pending == 0U) {
|
||||||
|
return RC522_NO_CARD;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return rc522_poll(out_card);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rc522_irq_callback(uint16_t GPIO_Pin)
|
||||||
|
{
|
||||||
|
if (GPIO_Pin == IOR_Pin) {
|
||||||
|
g_irq_pending = 1U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 站点UID白名单(可扩展) */
|
||||||
|
static const uint8_t g_station_1_uid[] = STATION_1_UID;
|
||||||
|
static const uint8_t g_station_2_uid[] = STATION_2_UID;
|
||||||
|
|
||||||
|
station_id_t rc522_match_station(const uint8_t *uid, uint8_t uid_len)
|
||||||
|
{
|
||||||
|
if (uid == NULL || uid_len == 0U) {
|
||||||
|
return STATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uid_len == sizeof(g_station_1_uid) && memcmp(uid, g_station_1_uid, uid_len) == 0) {
|
||||||
|
return STATION_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uid_len == sizeof(g_station_2_uid) && memcmp(uid, g_station_2_uid, uid_len) == 0) {
|
||||||
|
return STATION_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return STATION_NONE;
|
||||||
|
}
|
||||||
160
Core/Bsp/bsp_rc522.h
Normal file
160
Core/Bsp/bsp_rc522.h
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#ifndef __BSP_RC522_H
|
||||||
|
#define __BSP_RC522_H
|
||||||
|
|
||||||
|
#include "main.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* RC522 典型 UID 最大长度为 10 字节 */
|
||||||
|
#define RC522_UID_MAX_LEN 10U
|
||||||
|
|
||||||
|
// ================== 站点UID白名单(用户可根据实际卡片填写) ==================
|
||||||
|
|
||||||
|
// 站点1(A)卡片 UID: 29 AF 22 07
|
||||||
|
#define STATION_1_UID {0x29, 0xAF, 0x22, 0x07}
|
||||||
|
// 站点2(B)卡片 UID: 1B 45 F3 06
|
||||||
|
#define STATION_2_UID {0x1B, 0x45, 0xF3, 0x06}
|
||||||
|
|
||||||
|
|
||||||
|
// 站点枚举(可扩展)
|
||||||
|
typedef enum {
|
||||||
|
STATION_NONE = 0,
|
||||||
|
STATION_1 = 1, // 29 AF 22 07
|
||||||
|
STATION_2 = 2, // 1B 45 F3 06
|
||||||
|
// 可继续添加更多站点
|
||||||
|
} station_id_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据UID判断属于哪个站点
|
||||||
|
* @param uid UID字节数组
|
||||||
|
* @param uid_len UID长度
|
||||||
|
* @return station_id_t 站点编号
|
||||||
|
*/
|
||||||
|
station_id_t rc522_match_station(const uint8_t *uid, uint8_t uid_len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RUN 提示灯策略:
|
||||||
|
* 1) 轮询到卡片后点亮一段时间 (RC522_RUN_LED_HOLD_MS)
|
||||||
|
* 2) 超时自动熄灭
|
||||||
|
* 如需关闭该功能,设置 RC522_RUN_LED_ENABLE 为 0
|
||||||
|
*/
|
||||||
|
#ifndef RC522_RUN_LED_ENABLE
|
||||||
|
#define RC522_RUN_LED_ENABLE 1U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef RC522_RUN_LED_HOLD_MS
|
||||||
|
#define RC522_RUN_LED_HOLD_MS 300U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 按当前板卡默认 RUN_LED 低电平点亮 */
|
||||||
|
#ifndef RC522_RUN_LED_ON_LEVEL
|
||||||
|
#define RC522_RUN_LED_ON_LEVEL GPIO_PIN_RESET
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 最近卡缓存超时时间:超过该时间认为缓存失效 */
|
||||||
|
#ifndef RC522_CACHE_EXPIRE_MS
|
||||||
|
#define RC522_CACHE_EXPIRE_MS 1500U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 是否使用 IRQ 触发后再读卡:
|
||||||
|
* 0 = 纯轮询(推荐先用)
|
||||||
|
* 1 = 依赖 IOR 中断触发(需要在 EXTI 回调中调用 rc522_irq_callback)
|
||||||
|
*/
|
||||||
|
#ifndef RC522_USE_IRQ_TRIGGER
|
||||||
|
#define RC522_USE_IRQ_TRIGGER 0U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RC522 读卡状态
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
RC522_OK = 0,
|
||||||
|
RC522_NO_CARD,
|
||||||
|
RC522_ERR_TIMEOUT,
|
||||||
|
RC522_ERR_CRC,
|
||||||
|
RC522_ERR_BCC,
|
||||||
|
RC522_ERR_SPI,
|
||||||
|
RC522_ERR_PARAM,
|
||||||
|
RC522_ERR_INTERNAL
|
||||||
|
} rc522_status_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 最近一次读到的卡片信息
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t valid; // 1: 有效卡信息, 0: 无效
|
||||||
|
uint8_t uid[RC522_UID_MAX_LEN]; // UID 原始字节
|
||||||
|
uint8_t uid_len; // UID 长度 (支持 4/7/10)
|
||||||
|
uint8_t sak; // Select Acknowledge
|
||||||
|
uint16_t atqa; // Answer To Request, 高字节在前
|
||||||
|
uint32_t last_seen_tick_ms; // 最近一次读到该卡的系统毫秒时间
|
||||||
|
} rc522_card_info_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 RC522 模块 (SPI + 寄存器)
|
||||||
|
* @note SPI1 已由 CubeMX 初始化,本函数只做 RC522 芯片侧初始化。
|
||||||
|
*/
|
||||||
|
rc522_status_t rc522_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 轮询一次读卡,并缓存结果
|
||||||
|
* @param out_card 可选输出,为 NULL 时仅更新内部缓存
|
||||||
|
* @return rc522_status_t
|
||||||
|
*/
|
||||||
|
rc522_status_t rc522_poll(rc522_card_info_t *out_card);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RC522 周期服务函数(推荐在任务中调用)
|
||||||
|
* @param out_card 可选输出,为 NULL 时仅更新内部状态
|
||||||
|
* @return RC522_OK: 本次成功读到卡; RC522_NO_CARD: 当前无卡; 其他: 读卡异常
|
||||||
|
*/
|
||||||
|
rc522_status_t rc522_service(rc522_card_info_t *out_card);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取最近一次成功读到的卡片信息
|
||||||
|
* @param out_card 输出参数
|
||||||
|
* @return 1: 成功复制, 0: 当前无有效卡信息
|
||||||
|
*/
|
||||||
|
uint8_t rc522_get_last_card(rc522_card_info_t *out_card);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 当前是否存在有效缓存卡
|
||||||
|
* @return 1: 有效, 0: 无效
|
||||||
|
*/
|
||||||
|
uint8_t rc522_has_valid_card(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断当前卡是否“新卡事件”(与上次输出不同)
|
||||||
|
* @param out_card 输出参数
|
||||||
|
* @return 1: 新卡, 0: 非新卡或无卡
|
||||||
|
*/
|
||||||
|
uint8_t rc522_get_new_card(rc522_card_info_t *out_card);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除内部缓存的最近卡片信息
|
||||||
|
*/
|
||||||
|
void rc522_clear_last_card(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 UID 格式化为 HEX 字符串 (例如 "DE AD BE EF")
|
||||||
|
* @param card 卡片信息
|
||||||
|
* @param out_str 输出缓冲区
|
||||||
|
* @param out_len 输出缓冲区长度
|
||||||
|
* @return 1: 成功, 0: 参数错误或缓冲区不足
|
||||||
|
*/
|
||||||
|
uint8_t rc522_uid_to_string(const rc522_card_info_t *card, char *out_str, uint16_t out_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief EXTI 回调入口(可选)
|
||||||
|
* @param GPIO_Pin EXTI 引脚号
|
||||||
|
*/
|
||||||
|
void rc522_irq_callback(uint16_t GPIO_Pin);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __BSP_RC522_H */
|
||||||
@@ -12,6 +12,10 @@
|
|||||||
#include "bsp_hall.h"
|
#include "bsp_hall.h"
|
||||||
#include "bsp_pid.h"
|
#include "bsp_pid.h"
|
||||||
#include "bsp_uart.h"
|
#include "bsp_uart.h"
|
||||||
|
#include "bsp_rc522.h"
|
||||||
|
#include "bsp_beep.h"
|
||||||
|
#include "bsp_sr04.h"
|
||||||
|
#include "bsp_track_ir.h"
|
||||||
#include "checksum.h"
|
#include "checksum.h"
|
||||||
#include "cmsis_os.h"
|
#include "cmsis_os.h"
|
||||||
#include "elog.h"
|
#include "elog.h"
|
||||||
@@ -46,6 +50,10 @@ static float target_v_w = 0; // 原地旋转分量
|
|||||||
static uint8_t car_running = 0; // 小车运行状态(1=运行,0=停止)
|
static uint8_t car_running = 0; // 小车运行状态(1=运行,0=停止)
|
||||||
static uint8_t car_speed_percent = 0; // 当前整体速度百分比(0~100)
|
static uint8_t car_speed_percent = 0; // 当前整体速度百分比(0~100)
|
||||||
static uint16_t car_target_station = 0; // 目标站点编号
|
static uint16_t car_target_station = 0; // 目标站点编号
|
||||||
|
static uint8_t car_target_reached = 0; // 到站锁存,避免重复触发
|
||||||
|
|
||||||
|
#define CAR_STATION_MIN ((uint16_t)STATION_1)
|
||||||
|
#define CAR_STATION_MAX ((uint16_t)STATION_2)
|
||||||
|
|
||||||
/* 4个电机的 PID 控制器 */
|
/* 4个电机的 PID 控制器 */
|
||||||
static PID_TypeDef motor_pid[MOTOR_COUNT];
|
static PID_TypeDef motor_pid[MOTOR_COUNT];
|
||||||
@@ -85,6 +93,46 @@ static void CarCtrl_StopAll(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 到站蜂鸣提示
|
||||||
|
* @param times 蜂鸣次数
|
||||||
|
*/
|
||||||
|
static void CarCtrl_BeepTimes(uint8_t times)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < times; i++) {
|
||||||
|
BEEP_On();
|
||||||
|
osDelay(120);
|
||||||
|
BEEP_Off();
|
||||||
|
osDelay(120);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 运行期间检测是否到达目标站点
|
||||||
|
*/
|
||||||
|
static void CarCtrl_CheckTargetStation(void)
|
||||||
|
{
|
||||||
|
rc522_card_info_t card;
|
||||||
|
station_id_t station;
|
||||||
|
|
||||||
|
if (car_running == 0U || car_target_reached != 0U || car_target_station == 0U) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc522_get_new_card(&card) == 0U) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
station = rc522_match_station(card.uid, card.uid_len);
|
||||||
|
if ((uint16_t)station == car_target_station) {
|
||||||
|
car_running = 0U;
|
||||||
|
car_target_reached = 1U;
|
||||||
|
CarCtrl_StopAll();
|
||||||
|
elog_i(Protocol_TAG, "到达目标站点%u,停车并蜂鸣", car_target_station);
|
||||||
|
CarCtrl_BeepTimes(3U);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 按当前 car_speed_percent 设置所有电机速度
|
* @brief 按当前 car_speed_percent 设置所有电机速度
|
||||||
*/
|
*/
|
||||||
@@ -155,8 +203,8 @@ void CarCtrl_UpdateClosedLoop(void)
|
|||||||
const char* motor_names[] = {"LR", "LF", "RF", "RR"};
|
const char* motor_names[] = {"LR", "LF", "RF", "RR"};
|
||||||
static uint32_t last_log_time = 0;
|
static uint32_t last_log_time = 0;
|
||||||
if (HAL_GetTick() - last_log_time > 500) {
|
if (HAL_GetTick() - last_log_time > 500) {
|
||||||
elog_d(Protocol_TAG, "M[%s] T:%.1f A:%.1f Pw:%d",
|
elog_d(Protocol_TAG, "M[%s] T:%.1f A:%.1f Pw:%d",
|
||||||
motor_names[i], target_rpms[i], actual_rpm, (int16_t)final_pwm);
|
motor_names[i], target_rpms[i], actual_rpm, (int16_t)final_pwm);
|
||||||
if (i == MOTOR_COUNT - 1) last_log_time = HAL_GetTick();
|
if (i == MOTOR_COUNT - 1) last_log_time = HAL_GetTick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,14 +232,38 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
// 启动/停止命令(仅作为自动循迹任务的启动/暂停信号,不直接控制电机)
|
// 启动/停止命令(仅作为自动循迹任务的启动/暂停信号,不直接控制电机)
|
||||||
if (strncmp(cmd_payload, "ST", 2) == 0) {
|
if (strncmp(cmd_payload, "ST", 2) == 0) {
|
||||||
if (strcmp(arg + 1, "RUN") == 0) {
|
if (strcmp(arg + 1, "RUN") == 0) {
|
||||||
|
if (car_target_reached != 0U) {
|
||||||
|
car_running = 0U;
|
||||||
|
CarCtrl_StopAll();
|
||||||
|
elog_w(Protocol_TAG, "已到达站点,请先重新设置目标站点 (GS:xxx) 之后再启动");
|
||||||
|
CarCtrl_BeepTimes(2U);
|
||||||
|
Protocol_SendFeedback("ST", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (car_target_station < CAR_STATION_MIN || car_target_station > CAR_STATION_MAX) {
|
||||||
|
car_running = 0U;
|
||||||
|
CarCtrl_StopAll();
|
||||||
|
elog_w(Protocol_TAG, "未设置有效站点,拒绝启动。请先下发 GS:001 或 GS:002");
|
||||||
|
CarCtrl_BeepTimes(2U);
|
||||||
|
Protocol_SendFeedback("ST", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
car_running = 1;
|
car_running = 1;
|
||||||
|
car_target_reached = 0;
|
||||||
elog_i(Protocol_TAG, "小车自动循迹启动, speed=%u%%, station=%u", car_speed_percent,
|
elog_i(Protocol_TAG, "小车自动循迹启动, speed=%u%%, station=%u", car_speed_percent,
|
||||||
car_target_station);
|
car_target_station);
|
||||||
|
Protocol_SendFeedback("ST", 1);
|
||||||
} else if (strcmp(arg + 1, "STOP") == 0) {
|
} else if (strcmp(arg + 1, "STOP") == 0) {
|
||||||
car_running = 0;
|
car_running = 0;
|
||||||
|
car_target_reached = 0;
|
||||||
|
CarCtrl_StopAll();
|
||||||
elog_i(Protocol_TAG, "小车自动循迹暂停");
|
elog_i(Protocol_TAG, "小车自动循迹暂停");
|
||||||
|
Protocol_SendFeedback("ST", 1);
|
||||||
} else {
|
} else {
|
||||||
elog_w(Protocol_TAG, "未知启动/停止命令: %s", cmd_payload);
|
elog_w(Protocol_TAG, "未知启动/停止命令: %s", cmd_payload);
|
||||||
|
Protocol_SendFeedback("ST", 0);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -213,8 +285,10 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
if (car_running != 0U) {
|
if (car_running != 0U) {
|
||||||
CarCtrl_ApplySpeed();
|
CarCtrl_ApplySpeed();
|
||||||
}
|
}
|
||||||
|
Protocol_SendFeedback("SP", 1);
|
||||||
} else {
|
} else {
|
||||||
elog_w(Protocol_TAG, "速度参数解析失败: %s", cmd_payload);
|
elog_w(Protocol_TAG, "速度参数解析失败: %s", cmd_payload);
|
||||||
|
Protocol_SendFeedback("SP", 0);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -225,14 +299,21 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
|
|
||||||
// 解析站点编号(0~999)
|
// 解析站点编号(0~999)
|
||||||
if (sscanf(arg + 1, "%u", &station) == 1) {
|
if (sscanf(arg + 1, "%u", &station) == 1) {
|
||||||
if (station > 999U) {
|
if (station < CAR_STATION_MIN || station > CAR_STATION_MAX) {
|
||||||
station = 999U;
|
elog_w(Protocol_TAG, "站点%03u未配置,目前仅支持 %03u~%03u", station,
|
||||||
|
CAR_STATION_MIN, CAR_STATION_MAX);
|
||||||
|
CarCtrl_BeepTimes(2U);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
car_target_station = (uint16_t)station;
|
car_target_station = (uint16_t)station;
|
||||||
|
car_target_reached = 0;
|
||||||
elog_i(Protocol_TAG, "设置目标站点: %03u", car_target_station);
|
elog_i(Protocol_TAG, "设置目标站点: %03u", car_target_station);
|
||||||
|
CarCtrl_BeepTimes(1U);
|
||||||
|
Protocol_SendFeedback("GS", 1);
|
||||||
} else {
|
} else {
|
||||||
elog_w(Protocol_TAG, "站点参数解析失败: %s", cmd_payload);
|
elog_w(Protocol_TAG, "站点参数解析失败: %s", cmd_payload);
|
||||||
|
Protocol_SendFeedback("GS", 0);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -241,6 +322,65 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
elog_w(Protocol_TAG, "未支持的控制命令: %s", cmd_payload);
|
elog_w(Protocol_TAG, "未支持的控制命令: %s", cmd_payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打包并发送协议帧到上位机 (TCP)
|
||||||
|
* @param payload 有效载荷文本 (不含帧头 LOGI:, 分隔符 :, 校验位 CS 和 帧尾 #)
|
||||||
|
*/
|
||||||
|
static void Protocol_SendPacket(const char *payload) {
|
||||||
|
char packet[64] = {0};
|
||||||
|
uint8_t cs = 0;
|
||||||
|
|
||||||
|
// 1. 组装基础段: LOGI:PAYLOAD
|
||||||
|
snprintf(packet, sizeof(packet), "LOGI:%s", payload);
|
||||||
|
|
||||||
|
// 2. 计算校验和 (从 LOGI 开始到 payload 结束的累加和)
|
||||||
|
cs = Calculate_CheckSum((uint8_t *)packet, 0, (uint16_t)strlen(packet));
|
||||||
|
|
||||||
|
// 3. 拼接校验位和帧尾
|
||||||
|
size_t current_len = strlen(packet);
|
||||||
|
snprintf(packet + current_len, sizeof(packet) - current_len, ":%02X#", cs);
|
||||||
|
|
||||||
|
// 4. 发送
|
||||||
|
ESP12F_TCP_SendMessage(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送状态报告
|
||||||
|
* 格式示例: STAT:SP:080,STA:001,RUN:1,DIS:12.5,TRK:0010,RPM:25:25:25:25
|
||||||
|
*/
|
||||||
|
void Protocol_SendStatusReport(void) {
|
||||||
|
char payload[128] = {0};
|
||||||
|
float dist = sr04_get_distance();
|
||||||
|
uint8_t trk_mask = track_ir_get_line_mask();
|
||||||
|
|
||||||
|
// STAT:SP:xxx,STA:xxx,RUN:x,DIS:xxx.x,TRK:xxxx,RPM:m1:m2:m3:m4
|
||||||
|
// TRK 此时上报 4 位二进制掩码 (H4 H3 H2 H1)
|
||||||
|
// RPM 为各个电机的实际 RPM,保留整数
|
||||||
|
snprintf(payload, sizeof(payload),
|
||||||
|
"STAT:SP:%03u,STA:%03u,RUN:%u,DIS:%.1f,TRK:%u%u%u%u,RPM:%d:%d:%d:%d",
|
||||||
|
car_speed_percent, car_target_station, car_running, dist,
|
||||||
|
(trk_mask & TRACK_IR_H4_BIT) ? 1 : 0,
|
||||||
|
(trk_mask & TRACK_IR_H3_BIT) ? 1 : 0,
|
||||||
|
(trk_mask & TRACK_IR_H2_BIT) ? 1 : 0,
|
||||||
|
(trk_mask & TRACK_IR_H1_BIT) ? 1 : 0,
|
||||||
|
(int)hall_get_speed(MOTOR_1),
|
||||||
|
(int)hall_get_speed(MOTOR_2),
|
||||||
|
(int)hall_get_speed(MOTOR_3),
|
||||||
|
(int)hall_get_speed(MOTOR_4));
|
||||||
|
|
||||||
|
Protocol_SendPacket(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送反馈
|
||||||
|
* 格式示例: FB:ST:1 (ST成功), FB:GS:0 (GS失败)
|
||||||
|
*/
|
||||||
|
void Protocol_SendFeedback(const char *cmd_type, uint8_t status) {
|
||||||
|
char payload[16] = {0};
|
||||||
|
snprintf(payload, sizeof(payload), "FB:%s:%u", cmd_type, status);
|
||||||
|
Protocol_SendPacket(payload);
|
||||||
|
}
|
||||||
|
|
||||||
/* 引用在 freertos.c 中定义的消息队列句柄 */
|
/* 引用在 freertos.c 中定义的消息队列句柄 */
|
||||||
extern osMessageQueueId_t CmdQueueHandle;
|
extern osMessageQueueId_t CmdQueueHandle;
|
||||||
/**
|
/**
|
||||||
@@ -333,21 +473,33 @@ void Protocol_HandleMessage(uint8_t *data, uint16_t len) {
|
|||||||
void CarCtrl_Task(void *argument) {
|
void CarCtrl_Task(void *argument) {
|
||||||
/* USER CODE BEGIN CarCtrl_Task */
|
/* USER CODE BEGIN CarCtrl_Task */
|
||||||
char cmd_payload[16] = {0};
|
char cmd_payload[16] = {0};
|
||||||
|
uint32_t last_report_tick = 0;
|
||||||
|
|
||||||
/* 初始化闭环 PID 控制器 */
|
/* 初始化闭环 PID 控制器 */
|
||||||
CarCtrl_InitClosedLoop();
|
CarCtrl_InitClosedLoop();
|
||||||
|
|
||||||
/* Infinite loop */
|
/* Infinite loop */
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
uint32_t now = HAL_GetTick();
|
||||||
|
|
||||||
/* 1. 处理控制指令 (非阻塞获取,如果没有指令则继续执行闭环) */
|
/* 1. 处理控制指令 (非阻塞获取,如果没有指令则继续执行闭环) */
|
||||||
if (osMessageQueueGet(CmdQueueHandle, cmd_payload, NULL, 0) == osOK) {
|
if (osMessageQueueGet(CmdQueueHandle, cmd_payload, NULL, 0) == osOK) {
|
||||||
elog_d(CarCtrlTask_TAG, "CarCtrl: Command %s", cmd_payload);
|
elog_d(CarCtrlTask_TAG, "CarCtrl: Command %s", cmd_payload);
|
||||||
CarCtrl_HandleCommand(cmd_payload);
|
CarCtrl_HandleCommand(cmd_payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 1.1 运行中检查 RFID 是否到达目标站点 */
|
||||||
|
CarCtrl_CheckTargetStation();
|
||||||
|
|
||||||
/* 2. 执行 PID 闭环控制更新 */
|
/* 2. 执行 PID 闭环控制更新 */
|
||||||
CarCtrl_UpdateClosedLoop();
|
CarCtrl_UpdateClosedLoop();
|
||||||
|
|
||||||
|
/* 3. 定期向应用层/上位机发送状态报告 (每 500ms) */
|
||||||
|
if (now - last_report_tick >= 500) {
|
||||||
|
Protocol_SendStatusReport();
|
||||||
|
last_report_tick = now;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 与 hall_update_speed() 的 100ms 采样周期对齐,避免 PID 使用过期速度反复修正。
|
* 与 hall_update_speed() 的 100ms 采样周期对齐,避免 PID 使用过期速度反复修正。
|
||||||
* 如果后续把测速周期改成 20ms,这里也要同步改回 20ms。
|
* 如果后续把测速周期改成 20ms,这里也要同步改回 20ms。
|
||||||
|
|||||||
@@ -10,6 +10,18 @@
|
|||||||
*/
|
*/
|
||||||
void Protocol_HandleMessage(uint8_t *data, uint16_t len);
|
void Protocol_HandleMessage(uint8_t *data, uint16_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送状态报告到上位机 (TCP)
|
||||||
|
* @details 包含当前速度、目标站点、循迹状态、超声波距离等
|
||||||
|
*/
|
||||||
|
void Protocol_SendStatusReport(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发送命令执行结果反馈
|
||||||
|
* @param cmd_type 命令类型 (如 "ST", "SP", "GS")
|
||||||
|
* @param status 状态 (0: 失败, 1: 成功)
|
||||||
|
*/
|
||||||
|
void Protocol_SendFeedback(const char *cmd_type, uint8_t status);
|
||||||
|
|
||||||
void CarCtrl_Task(void *argument);
|
void CarCtrl_Task(void *argument);
|
||||||
|
|
||||||
|
|||||||
122
Core/Bsp/task_README.md
Normal file
122
Core/Bsp/task_README.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# RC522 任务接入说明 (CubeMX 友好)
|
||||||
|
|
||||||
|
你要求不要直接改自动生成任务代码,避免下次 CubeMX 重新生成被覆盖。下面是推荐接入方式。
|
||||||
|
|
||||||
|
## 1. 已新增驱动文件
|
||||||
|
|
||||||
|
- `Core/Bsp/bsp_rc522.h`
|
||||||
|
- `Core/Bsp/bsp_rc522.c`
|
||||||
|
|
||||||
|
核心接口:
|
||||||
|
|
||||||
|
```c
|
||||||
|
rc522_status_t rc522_init(void);
|
||||||
|
rc522_status_t rc522_poll(rc522_card_info_t *out_card);
|
||||||
|
rc522_status_t rc522_service(rc522_card_info_t *out_card);
|
||||||
|
uint8_t rc522_get_last_card(rc522_card_info_t *out_card);
|
||||||
|
uint8_t rc522_has_valid_card(void);
|
||||||
|
uint8_t rc522_get_new_card(rc522_card_info_t *out_card);
|
||||||
|
void rc522_clear_last_card(void);
|
||||||
|
uint8_t rc522_uid_to_string(const rc522_card_info_t *card, char *out_str, uint16_t out_len);
|
||||||
|
void rc522_irq_callback(uint16_t GPIO_Pin);
|
||||||
|
```
|
||||||
|
|
||||||
|
RUN 灯提示(已内置在驱动里):
|
||||||
|
|
||||||
|
- 成功读到卡后自动点亮 `RUN_LED`
|
||||||
|
- 保持 `RC522_RUN_LED_HOLD_MS` 毫秒后自动熄灭
|
||||||
|
- 默认低电平点亮,可在 `bsp_rc522.h` 里改:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define RC522_RUN_LED_ENABLE 1U
|
||||||
|
#define RC522_RUN_LED_HOLD_MS 300U
|
||||||
|
#define RC522_RUN_LED_ON_LEVEL GPIO_PIN_RESET
|
||||||
|
#define RC522_CACHE_EXPIRE_MS 1500U
|
||||||
|
#define RC522_USE_IRQ_TRIGGER 0U
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 硬件连接确认
|
||||||
|
|
||||||
|
按你现在配置:
|
||||||
|
|
||||||
|
- `SDA (CS)` -> `PC4` (输出)
|
||||||
|
- `RST` -> `PB0` (输出)
|
||||||
|
- `IOR (IRQ)` -> `PC5` (EXTI,可选)
|
||||||
|
- `SPI1` -> `PA5/PA6/PA7`
|
||||||
|
|
||||||
|
> 注意:默认是 **轮询模式**(`RC522_USE_IRQ_TRIGGER=0`),最稳。
|
||||||
|
> 如果你想改为中断触发读卡:设为 `1`,并确保 EXTI 回调里调用 `rc522_irq_callback(GPIO_Pin)`。
|
||||||
|
|
||||||
|
## 3. 在 CubeMX 的 freertos.c 用户区接入
|
||||||
|
|
||||||
|
### 3.1 头文件包含 (`USER CODE BEGIN Includes`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "bsp_rc522.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 在初始化阶段调用 (`USER CODE BEGIN Init`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
rc522_init();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 新建一个低优先级任务(推荐 50~100ms)
|
||||||
|
|
||||||
|
任务函数示例(你可以放在用户代码区):
|
||||||
|
|
||||||
|
```c
|
||||||
|
void rc522_task(void *argument)
|
||||||
|
{
|
||||||
|
rc522_card_info_t card;
|
||||||
|
char uid_str[3 * RC522_UID_MAX_LEN] = {0};
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
rc522_status_t st = rc522_service(&card);
|
||||||
|
if (st == RC522_OK) {
|
||||||
|
if (rc522_uid_to_string(&card, uid_str, sizeof(uid_str))) {
|
||||||
|
printf("RFID UID: %s, SAK:0x%02X, ATQA:0x%04X\r\n",
|
||||||
|
uid_str, card.sak, card.atqa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
osDelay(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果你只希望“新卡刷入时”触发一次业务,不重复触发:
|
||||||
|
|
||||||
|
```c
|
||||||
|
rc522_card_info_t new_card;
|
||||||
|
if (rc522_get_new_card(&new_card)) {
|
||||||
|
// 仅在识别到新 UID 时进入
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 在业务代码中获取“最近一次卡片信息”
|
||||||
|
|
||||||
|
驱动会自动缓存最近一次成功读到的卡,可在任意位置读取:
|
||||||
|
|
||||||
|
```c
|
||||||
|
rc522_card_info_t last_card;
|
||||||
|
if (rc522_get_last_card(&last_card)) {
|
||||||
|
// last_card 有效
|
||||||
|
} else {
|
||||||
|
// 当前没有有效卡缓存
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 常见问题
|
||||||
|
|
||||||
|
1. 一直读不到卡:
|
||||||
|
- 检查 RC522 供电是否 3.3V(不要 5V)
|
||||||
|
- 检查 `SDA(PC4)` 是否被其他器件复用
|
||||||
|
- 确认 SPI1 时钟极性/相位为 `CPOL=0, CPHA=1Edge`
|
||||||
|
|
||||||
|
2. UID 偶发错误:
|
||||||
|
- 降低 SPI 速率(在 CubeMX 调大分频,例如 Prescaler=8)
|
||||||
|
- 线长尽量短,地线要可靠
|
||||||
|
|
||||||
|
3. 任务被覆盖:
|
||||||
|
- 只在 `USER CODE BEGIN ... END` 区域放代码
|
||||||
|
- 或者把任务函数放到 `Core/Bsp` 内,由 `freertos.c` 用户区仅调用
|
||||||
75
Core/Bsp/up_readme.md
Normal file
75
Core/Bsp/up_readme.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
好的!为了方便你开发上位机,我为你整理了一份详细的**物流小车 TCP 通信协议文档**。这份文档完全基于我们目前代码中的逻辑实现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 🚚 物流小车 TCP 通信协议文档 (v1.2)
|
||||||
|
|
||||||
|
## 1. 协议基础格式
|
||||||
|
所有数据包(上行和下行)均采用以下固定包装结构:
|
||||||
|
`LOGI:<PAYLOAD>:<CS>#`
|
||||||
|
|
||||||
|
* **`LOGI:`**: 固定帧头。
|
||||||
|
* **`<PAYLOAD>`**: 有效载荷文本。
|
||||||
|
* **`:`**: 有效载荷与校验位之间的分隔符。
|
||||||
|
* **`<CS>`**: 2字节 ASCII 十六进制校验和(CheckSum)。
|
||||||
|
* *计算规则*:从 `L` 开始(下标0)累加到最后一个冒号 `:` 之前的所有字节。
|
||||||
|
* **`#`**: 固定帧尾。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 下行指令 (上位机 -> 小车)
|
||||||
|
|
||||||
|
| 指令前缀 | 示例 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **ST:RUN** | `LOGI:ST:RUN:3B#` | **启动**: 开始自动运输。必须先发 `GS` 设置目标站。 |
|
||||||
|
| **ST:STOP**| `LOGI:ST:STOP:8C#`| **停止**: 立即停止所有电机,清除到站锁存。 |
|
||||||
|
| **SP:xxx** | `LOGI:SP:050:D7#` | **速度**: 设置速度百分比 (000~100)。 |
|
||||||
|
| **GS:xxx** | `LOGI:GS:001:CA#` | **站点**: 设置目标站点 (目前支持 001/002)。会清除到站锁存并响铃。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 上行遥测 (小车 -> 上位机)
|
||||||
|
|
||||||
|
### 3.1 周期状态报告 (下位机每 500ms 自动推送)
|
||||||
|
**格式:** `LOGI:STAT:SP:速度,STA:站点,RUN:运行,DIS:距离,TRK:循迹:CS#`
|
||||||
|
**示例:** `LOGI:STAT:SP:050,STA:001,RUN:1,DIS:52.4,TRK:00100:B2#`
|
||||||
|
|
||||||
|
| 字段 | 示例值 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **SP** | `050` | 当前速度百分比 (0~100)。 |
|
||||||
|
| **STA**| `001` | 当前设置的目标站点编号。 |
|
||||||
|
| **RUN**| `1` | 运行状态 (1:运动中, 0:停止/待机)。 |
|
||||||
|
| **DIS**| `12.5`| 超声波测距 (单位: cm,保留1位小数)。 |
|
||||||
|
| **TRK**| `00100`| 5位循迹灯状态 (0: 未触发感应, 1: 感应到黑线)。从左到右对应 IR1-IR5。 |
|
||||||
|
|
||||||
|
### 3.2 指令执行反馈 (收到指令后立即回复)
|
||||||
|
**格式:** `LOGI:FB:指令类型:状态值:CS#`
|
||||||
|
**示例:** `LOGI:FB:GS:1:A5#` (代表站点设置成功)
|
||||||
|
|
||||||
|
| 字段 | 值意义 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **指令类型** | `ST` / `SP` / `GS` | 对应上位机发来的指令头。 |
|
||||||
|
| **状态值** | `1` / `0` | 1: 执行成功;0: 失败(如未设站点就点运行等)。 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 特殊业务逻辑说明 (供上位机逻辑参考)
|
||||||
|
|
||||||
|
1. **到站锁死机制**:
|
||||||
|
* 小车到达 `STA` 对应的 RFID 站点后,会自动进入 `STOP` 状态并推送 `RUN:0`。
|
||||||
|
* **此时直接发 `ST:RUN` 会被拒绝**(收到 `FB:ST:0` 反馈),蜂鸣器响两声。
|
||||||
|
* **解锁方式**:必须重新发送 `GS:xxx` 指令覆盖目标站点(即使设成同一个号也可以),然后再发 `ST:RUN`。
|
||||||
|
|
||||||
|
2. **安全保护**:
|
||||||
|
* 若上位机未发送过任何 `GS` 指令,尝试发 `ST:RUN` 会触发失败反馈。
|
||||||
|
|
||||||
|
3. **校验和计算 Python 参考代码**:
|
||||||
|
```python
|
||||||
|
def calculate_cs(data_str):
|
||||||
|
# 如 data_str 为 "LOGI:ST:RUN"
|
||||||
|
return sum(data_str.encode('ascii')) & 0xFF
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
你可以直接复制到你的上位机开发笔记里。如需增加更多字段(如电池电压、电机电流等),我可以随时在底层代码里帮你补齐。
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
#include "bsp_hall.h" // 添加对 bsp_hall.h 的包含
|
#include "bsp_hall.h" // 添加对 bsp_hall.h 的包含
|
||||||
#include "bsp_track_ir.h" // 添加对 bsp_track_ir.h 的包含
|
#include "bsp_track_ir.h" // 添加对 bsp_track_ir.h 的包含
|
||||||
#include "bsp_sr04.h" // 添加超声波头文件
|
#include "bsp_sr04.h" // 添加超声波头文件
|
||||||
|
#include "bsp_rc522.h" // 添加 RFID 头文件
|
||||||
/* USER CODE END Includes */
|
/* USER CODE END Includes */
|
||||||
|
|
||||||
/* Private typedef -----------------------------------------------------------*/
|
/* Private typedef -----------------------------------------------------------*/
|
||||||
@@ -83,6 +84,13 @@ const osThreadAttr_t sr04Task_attributes = {
|
|||||||
.stack_size = 128 * 4,
|
.stack_size = 128 * 4,
|
||||||
.priority = (osPriority_t) osPriorityLow,
|
.priority = (osPriority_t) osPriorityLow,
|
||||||
};
|
};
|
||||||
|
/* Definitions for rc522Task */
|
||||||
|
osThreadId_t rc522TaskHandle;
|
||||||
|
const osThreadAttr_t rc522Task_attributes = {
|
||||||
|
.name = "rc522Task",
|
||||||
|
.stack_size = 128 * 4,
|
||||||
|
.priority = (osPriority_t) osPriorityBelowNormal,
|
||||||
|
};
|
||||||
/* Definitions for CmdQueue */
|
/* Definitions for CmdQueue */
|
||||||
osMessageQueueId_t CmdQueueHandle;
|
osMessageQueueId_t CmdQueueHandle;
|
||||||
const osMessageQueueAttr_t CmdQueue_attributes = {
|
const osMessageQueueAttr_t CmdQueue_attributes = {
|
||||||
@@ -107,6 +115,7 @@ void StartDefaultTask(void *argument);
|
|||||||
void CarCtrl_Task(void *argument);
|
void CarCtrl_Task(void *argument);
|
||||||
void speed_get(void *argument);
|
void speed_get(void *argument);
|
||||||
void sr04_task(void *argument);
|
void sr04_task(void *argument);
|
||||||
|
void rc522_task(void *argument);
|
||||||
|
|
||||||
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
|
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
|
||||||
|
|
||||||
@@ -126,6 +135,9 @@ void MX_FREERTOS_Init(void) {
|
|||||||
hall_init(); // 初始化霍尔传感器
|
hall_init(); // 初始化霍尔传感器
|
||||||
track_ir_init(); // 初始化轨迹红外传感器
|
track_ir_init(); // 初始化轨迹红外传感器
|
||||||
sr04_init(); // 初始化超声波
|
sr04_init(); // 初始化超声波
|
||||||
|
rc522_init(); // 初始化 RFID 模块
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* USER CODE END Init */
|
/* USER CODE END Init */
|
||||||
|
|
||||||
@@ -162,6 +174,9 @@ void MX_FREERTOS_Init(void) {
|
|||||||
/* creation of sr04Task */
|
/* creation of sr04Task */
|
||||||
sr04TaskHandle = osThreadNew(sr04_task, NULL, &sr04Task_attributes);
|
sr04TaskHandle = osThreadNew(sr04_task, NULL, &sr04Task_attributes);
|
||||||
|
|
||||||
|
/* creation of rc522Task */
|
||||||
|
rc522TaskHandle = osThreadNew(rc522_task, NULL, &rc522Task_attributes);
|
||||||
|
|
||||||
/* USER CODE BEGIN RTOS_THREADS */
|
/* USER CODE BEGIN RTOS_THREADS */
|
||||||
/* add threads, ... */
|
/* add threads, ... */
|
||||||
/* USER CODE END RTOS_THREADS */
|
/* USER CODE END RTOS_THREADS */
|
||||||
@@ -256,6 +271,43 @@ void sr04_task(void *argument)
|
|||||||
/* USER CODE END sr04_task */
|
/* USER CODE END sr04_task */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* USER CODE BEGIN Header_rc522_task */
|
||||||
|
/**
|
||||||
|
* @brief Function implementing the rc522Task thread.
|
||||||
|
* @param argument: Not used
|
||||||
|
* @retval None
|
||||||
|
*/
|
||||||
|
/* USER CODE END Header_rc522_task */
|
||||||
|
void rc522_task(void *argument)
|
||||||
|
{
|
||||||
|
/* USER CODE BEGIN rc522_task */
|
||||||
|
rc522_card_info_t card;
|
||||||
|
char uid_str[3 * RC522_UID_MAX_LEN] = {0};
|
||||||
|
station_id_t station = STATION_NONE;
|
||||||
|
/* Infinite loop */
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
rc522_status_t st = rc522_poll(&card);
|
||||||
|
if (st == RC522_OK) {
|
||||||
|
if (rc522_uid_to_string(&card, uid_str, sizeof(uid_str))) {
|
||||||
|
printf("RFID UID: %s, SAK:0x%02X, ATQA:0x%04X\r\n",
|
||||||
|
uid_str, card.sak, card.atqa);
|
||||||
|
}
|
||||||
|
// 站点匹配逻辑
|
||||||
|
station = rc522_match_station(card.uid, card.uid_len);
|
||||||
|
if (station == STATION_1) {
|
||||||
|
printf("到达站点1!\r\n");
|
||||||
|
// 可在此处添加到站动作,如停车、蜂鸣等
|
||||||
|
} else if (station == STATION_2) {
|
||||||
|
printf("到达站点2!\r\n");
|
||||||
|
// 可在此处添加到站动作,如停车、蜂鸣等
|
||||||
|
}
|
||||||
|
}
|
||||||
|
osDelay(100);
|
||||||
|
}
|
||||||
|
/* USER CODE END rc522_task */
|
||||||
|
}
|
||||||
|
|
||||||
/* Private application code --------------------------------------------------*/
|
/* Private application code --------------------------------------------------*/
|
||||||
/* USER CODE BEGIN Application */
|
/* USER CODE BEGIN Application */
|
||||||
|
|
||||||
|
|||||||
44
README.md
44
README.md
@@ -178,6 +178,50 @@ const char *message = "Hello, ESP12F! This is a test message.";
|
|||||||
HAL_StatusTypeDef status = ESP12F_TCP_SendMessage(message);
|
HAL_StatusTypeDef status = ESP12F_TCP_SendMessage(message);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 物流小车 TCP 通信协议 (v1.2)
|
||||||
|
|
||||||
|
本项目采用自定义 ASCII 协议进行上位机控制与状态监控。
|
||||||
|
|
||||||
|
### 1. 指令帧结构
|
||||||
|
`LOGI:<PAYLOAD>:<CS>#`
|
||||||
|
- **LOGI**: 固定帧头
|
||||||
|
- **PAYLOAD**: 有效载荷 (详见下表)
|
||||||
|
- **CS**: 2字节十六进制校验和 (从 'L' 累加到 ':' 之前)
|
||||||
|
- **#**: 固定帧尾
|
||||||
|
|
||||||
|
### 2. 控制指令 (上位机 -> 小车)
|
||||||
|
|
||||||
|
| 功能 | 指令格式 | 示例 | 说明 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **设置站点** | `GS:NNN` | `LOGI:GS:001:CA#` | NNN为3位站点号 (支持 001/002) |
|
||||||
|
| **启动运行** | `ST:RUN` | `LOGI:ST:RUN:3B#` | 开始任务 (必须先设有效站点) |
|
||||||
|
| **停止运行** | `ST:STOP` | `LOGI:ST:STOP:8C#` | 立即停止 |
|
||||||
|
| **设置速度** | `SP:VVV` | `LOGI:SP:050:D7#` | VVV为000-100 (百分比) |
|
||||||
|
|
||||||
|
### 3. 上行遥测 (小车 -> 上位机)
|
||||||
|
|
||||||
|
#### 3.1 状态推送 (每 500ms 推送一次)
|
||||||
|
**格式**: `LOGI:STAT:SP:速度,STA:站点,RUN:运行,DIS:距离,TRK:循迹状态,RPM:M1:M2:M3:M4:CS#`
|
||||||
|
**字段**:
|
||||||
|
- `SP`: 当前速度 %
|
||||||
|
- `STA`: 目标站点号
|
||||||
|
- `RUN`: 运行状态 (1:运行, 0:停止)
|
||||||
|
- `DIS`: 避障距离 (cm)
|
||||||
|
- `TRK`: 4位红外状态 (0/1组合, 顺序为 H4 H3 H2 H1)
|
||||||
|
- `RPM`: 四路电机实际转速,以 `:` 分隔 (顺序为 LR:LF:RF:RR)
|
||||||
|
|
||||||
|
#### 3.2 指令反馈 (即时回复)
|
||||||
|
**格式**: `LOGI:FB:指令类型:状态值:CS#`
|
||||||
|
**示例**: `LOGI:FB:GS:1:A5#` (1代表成功, 0代表失败)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 业务逻辑约束
|
||||||
|
1. **到站锁存**: 到达站点后小车自动停下,`RUN` 变为 0。
|
||||||
|
2. **解锁流程**: 车辆停稳后,必须重新发送 `GS` 指令设置新站点(或覆盖旧站点),方可再次发送 `ST:RUN` 启动。否则,小车将报警并拒绝运行。
|
||||||
|
|
||||||
|
|
||||||
这些是上位机发送的指令:
|
这些是上位机发送的指令:
|
||||||
按照这些来写代码
|
按照这些来写代码
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Dma.USART2_TX.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphData
|
|||||||
FREERTOS.FootprintOK=true
|
FREERTOS.FootprintOK=true
|
||||||
FREERTOS.IPParameters=Tasks01,FootprintOK,Queues01,configTOTAL_HEAP_SIZE
|
FREERTOS.IPParameters=Tasks01,FootprintOK,Queues01,configTOTAL_HEAP_SIZE
|
||||||
FREERTOS.Queues01=CmdQueue,16,16,1,Dynamic,NULL,NULL
|
FREERTOS.Queues01=CmdQueue,16,16,1,Dynamic,NULL,NULL
|
||||||
FREERTOS.Tasks01=initTask,24,128,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;CarCtrlTask,24,256,CarCtrl_Task,As weak,NULL,Dynamic,NULL,NULL;timerTask,16,512,speed_get,As weak,NULL,Dynamic,NULL,NULL;sr04Task,8,128,sr04_task,Default,NULL,Dynamic,NULL,NULL
|
FREERTOS.Tasks01=initTask,24,128,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;CarCtrlTask,24,256,CarCtrl_Task,As weak,NULL,Dynamic,NULL,NULL;timerTask,16,512,speed_get,As weak,NULL,Dynamic,NULL,NULL;sr04Task,8,128,sr04_task,Default,NULL,Dynamic,NULL,NULL;rc522Task,16,128,rc522_task,Default,NULL,Dynamic,NULL,NULL
|
||||||
FREERTOS.configTOTAL_HEAP_SIZE=10000
|
FREERTOS.configTOTAL_HEAP_SIZE=10000
|
||||||
File.Version=6
|
File.Version=6
|
||||||
GPIO.groupedBy=Group By Peripherals
|
GPIO.groupedBy=Group By Peripherals
|
||||||
|
|||||||
Reference in New Issue
Block a user