feat: report wheel speed and GPIO state periodically

This commit is contained in:
shenlefengnai-rgb
2026-10-05 19:49:49 +08:00
parent 34627fa6a0
commit 68e8fdbcb8
7 changed files with 234 additions and 335 deletions
+45 -136
View File
@@ -64,9 +64,12 @@ typedef struct
#define UART_LINE_SIZE 32U #define UART_LINE_SIZE 32U
#define ADC_REPORT_PERIOD_MS 500U #define ADC_REPORT_PERIOD_MS 500U
#define PWM_REPORT_PERIOD_MS 20U #define PWM_REPORT_PERIOD_MS 20U
#define GPIO_REPORT_PERIOD_MS 1000U
#define CAN_HEARTBEAT_PERIOD_MS 100U #define CAN_HEARTBEAT_PERIOD_MS 100U
#define DAC_CAN_TIMEOUT_MS 100U #define DAC_CAN_TIMEOUT_MS 100U
#define PWM_TIMEOUT_MS 1000U #define PWM_TIMEOUT_MS 1000U
#define WHEEL_PULSES_PER_REV 49U
#define WHEEL_CIRCUMFERENCE_MM 1414U
#define CAN_HEARTBEAT_STD_ID 0x6A0U #define CAN_HEARTBEAT_STD_ID 0x6A0U
#define CAN_ADC_REPORT_STD_ID 0x6A1U #define CAN_ADC_REPORT_STD_ID 0x6A1U
@@ -100,19 +103,19 @@ I2C_HandleTypeDef hi2c2;
TIM_HandleTypeDef htim3; TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim5; TIM_HandleTypeDef htim5;
TIM_HandleTypeDef htim9;
TIM_HandleTypeDef htim12;
UART_HandleTypeDef huart1; UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */ /* USER CODE BEGIN PV */
static CaptureChannel capture_channels[4]; /* PA0 left-front and PA6 right-front wheel-speed inputs only. */
static CaptureChannel capture_channels[2];
static uint16_t gpio_last_state; static uint16_t gpio_last_state;
static volatile uint16_t gpio_shadow_state; static volatile uint16_t gpio_shadow_state;
static volatile uint16_t gpio_report_state; static volatile uint16_t gpio_report_state;
static volatile uint16_t gpio_pending_changed_mask; static volatile uint16_t gpio_pending_changed_mask;
static uint32_t last_adc_report_ms; static uint32_t last_adc_report_ms;
static uint32_t last_pwm_report_ms; static uint32_t last_pwm_report_ms;
static uint32_t last_gpio_report_ms;
static uint32_t last_heartbeat_ms; static uint32_t last_heartbeat_ms;
static uint32_t last_can_heartbeat_ms; static uint32_t last_can_heartbeat_ms;
static uint32_t dac_can_last_command_ms[2]; static uint32_t dac_can_last_command_ms[2];
@@ -136,8 +139,6 @@ static void MX_CAN2_Init(void);
static void MX_I2C2_Init(void); static void MX_I2C2_Init(void);
static void MX_TIM3_Init(void); static void MX_TIM3_Init(void);
static void MX_TIM5_Init(void); static void MX_TIM5_Init(void);
static void MX_TIM9_Init(void);
static void MX_TIM12_Init(void);
static void MX_USART1_UART_Init(void); static void MX_USART1_UART_Init(void);
static void MX_ADC1_Init(void); static void MX_ADC1_Init(void);
/* USER CODE BEGIN PFP */ /* USER CODE BEGIN PFP */
@@ -163,6 +164,7 @@ static const char *DacName(uint8_t dac_id);
static uint16_t ReadGpioInputState(void); static uint16_t ReadGpioInputState(void);
static void QueueGpioSnapshot(uint16_t state); static void QueueGpioSnapshot(uint16_t state);
static uint32_t ReadAdcInputMillivolts(void); static uint32_t ReadAdcInputMillivolts(void);
static uint32_t WheelSpeedMmPerSecond(uint32_t frequency_hz);
static uint32_t GetTimerClockHz(const TIM_HandleTypeDef *htim); static uint32_t GetTimerClockHz(const TIM_HandleTypeDef *htim);
static void InitCaptureChannels(void); static void InitCaptureChannels(void);
static void ConfigureCanRuntime(CAN_HandleTypeDef *hcan); static void ConfigureCanRuntime(CAN_HandleTypeDef *hcan);
@@ -221,8 +223,6 @@ int main(void)
MX_I2C2_Init(); MX_I2C2_Init();
MX_TIM3_Init(); MX_TIM3_Init();
MX_TIM5_Init(); MX_TIM5_Init();
MX_TIM9_Init();
MX_TIM12_Init();
MX_USART1_UART_Init(); MX_USART1_UART_Init();
MX_ADC1_Init(); MX_ADC1_Init();
/* USER CODE BEGIN 2 */ /* USER CODE BEGIN 2 */
@@ -548,88 +548,6 @@ static void MX_TIM5_Init(void)
} }
/**
* @brief TIM9 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM9_Init(void)
{
/* USER CODE BEGIN TIM9_Init 0 */
/* USER CODE END TIM9_Init 0 */
TIM_IC_InitTypeDef sConfigIC = {0};
/* USER CODE BEGIN TIM9_Init 1 */
/* USER CODE END TIM9_Init 1 */
htim9.Instance = TIM9;
htim9.Init.Prescaler = 159;
htim9.Init.CounterMode = TIM_COUNTERMODE_UP;
htim9.Init.Period = 0xFFFF;
htim9.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim9.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_IC_Init(&htim9) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim9, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM9_Init 2 */
/* USER CODE END TIM9_Init 2 */
}
/**
* @brief TIM12 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM12_Init(void)
{
/* USER CODE BEGIN TIM12_Init 0 */
/* USER CODE END TIM12_Init 0 */
TIM_IC_InitTypeDef sConfigIC = {0};
/* USER CODE BEGIN TIM12_Init 1 */
/* USER CODE END TIM12_Init 1 */
htim12.Instance = TIM12;
htim12.Init.Prescaler = 159;
htim12.Init.CounterMode = TIM_COUNTERMODE_UP;
htim12.Init.Period = 0xFFFF;
htim12.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim12.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_IC_Init(&htim12) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim12, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM12_Init 2 */
/* USER CODE END TIM12_Init 2 */
}
/** /**
* @brief USART1 Initialization Function * @brief USART1 Initialization Function
* @param None * @param None
@@ -767,6 +685,7 @@ static void AppInit(void)
gpio_pending_changed_mask = 0U; gpio_pending_changed_mask = 0U;
last_adc_report_ms = HAL_GetTick(); last_adc_report_ms = HAL_GetTick();
last_pwm_report_ms = last_adc_report_ms; last_pwm_report_ms = last_adc_report_ms;
last_gpio_report_ms = last_adc_report_ms;
last_heartbeat_ms = last_adc_report_ms; last_heartbeat_ms = last_adc_report_ms;
last_can_heartbeat_ms = last_adc_report_ms; last_can_heartbeat_ms = last_adc_report_ms;
dac_can_last_command_ms[0] = 0U; dac_can_last_command_ms[0] = 0U;
@@ -787,7 +706,6 @@ static void AppInit(void)
UartWrite("UART: DAC1 1.234 or DAC2 2500\r\n"); UartWrite("UART: DAC1 1.234 or DAC2 2500\r\n");
UartWrite("CAN cmd 0x6A3: [dac_id][mV_L][mV_H] or ASCII text\r\n"); UartWrite("CAN cmd 0x6A3: [dac_id][mV_L][mV_H] or ASCII text\r\n");
UartWrite("CAN cmd 0x6A5: [ASSI state 0=OFF,1=READY,2=DRIVING,3=EMERGENCY,4=FINISHED]\r\n"); UartWrite("CAN cmd 0x6A5: [ASSI state 0=OFF,1=READY,2=DRIVING,3=EMERGENCY,4=FINISHED]\r\n");
BroadcastGpioReport(0U, gpio_last_state);
} }
static void PollUart(void) static void PollUart(void)
@@ -903,29 +821,35 @@ static void PollCaptureChannels(void)
static void PollGpioChanges(void) static void PollGpioChanges(void)
{ {
uint32_t now = HAL_GetTick();
uint16_t state; uint16_t state;
uint16_t changed; uint16_t changed;
char line[96];
__disable_irq(); __disable_irq();
QueueGpioSnapshot(ReadGpioInputState()); QueueGpioSnapshot(ReadGpioInputState());
__enable_irq();
if ((now - last_gpio_report_ms) < GPIO_REPORT_PERIOD_MS)
{
return;
}
__disable_irq();
changed = gpio_pending_changed_mask; changed = gpio_pending_changed_mask;
state = gpio_report_state; state = gpio_report_state;
gpio_pending_changed_mask = 0U; gpio_pending_changed_mask = 0U;
__enable_irq(); __enable_irq();
if (changed != 0U) last_gpio_report_ms = now;
{ gpio_last_state = state;
char line[96]; snprintf(line,
sizeof(line),
gpio_last_state = state; "GPIO state=0x%04X changed=0x%04X\r\n",
snprintf(line, (unsigned int)state,
sizeof(line), (unsigned int)changed);
"GPIO changed mask=0x%04X state=0x%04X\r\n", UartWrite(line);
(unsigned int)changed, BroadcastGpioReport(changed, state);
(unsigned int)state);
UartWrite(line);
BroadcastGpioReport(changed, state);
}
} }
static void SendPeriodicReport(void) static void SendPeriodicReport(void)
@@ -941,12 +865,12 @@ static void SendPeriodicReport(void)
snprintf(line, snprintf(line,
sizeof(line), sizeof(line),
"ADC=%lumV PWM=%lu,%lu,%lu,%luHz\r\n", "ADC=%lumV WheelSpeed=LF:%lu.%03lum/s RF:%lu.%03lum/s\r\n",
(unsigned long)adc_mv, (unsigned long)adc_mv,
(unsigned long)capture_channels[0].frequency_hz, (unsigned long)(WheelSpeedMmPerSecond(capture_channels[0].frequency_hz) / 1000U),
(unsigned long)capture_channels[1].frequency_hz, (unsigned long)(WheelSpeedMmPerSecond(capture_channels[0].frequency_hz) % 1000U),
(unsigned long)capture_channels[2].frequency_hz, (unsigned long)(WheelSpeedMmPerSecond(capture_channels[1].frequency_hz) / 1000U),
(unsigned long)capture_channels[3].frequency_hz); (unsigned long)(WheelSpeedMmPerSecond(capture_channels[1].frequency_hz) % 1000U));
UartWrite(line); UartWrite(line);
BroadcastAdcReport(adc_mv); BroadcastAdcReport(adc_mv);
} }
@@ -1338,6 +1262,12 @@ static uint32_t GetTimerClockHz(const TIM_HandleTypeDef *htim)
return (pclk * multiplier) / prescaler; return (pclk * multiplier) / prescaler;
} }
static uint32_t WheelSpeedMmPerSecond(uint32_t frequency_hz)
{
return (uint32_t)(((uint64_t)frequency_hz * WHEEL_CIRCUMFERENCE_MM +
(WHEEL_PULSES_PER_REV / 2U)) / WHEEL_PULSES_PER_REV);
}
static void InitCaptureChannels(void) static void InitCaptureChannels(void)
{ {
capture_channels[0].htim = &htim5; capture_channels[0].htim = &htim5;
@@ -1347,43 +1277,21 @@ static void InitCaptureChannels(void)
capture_channels[0].timer_clock_hz = GetTimerClockHz(&htim5); capture_channels[0].timer_clock_hz = GetTimerClockHz(&htim5);
capture_channels[0].last_edge_ms = HAL_GetTick(); capture_channels[0].last_edge_ms = HAL_GetTick();
capture_channels[1].htim = &htim9; capture_channels[1].htim = &htim3;
capture_channels[1].channel = TIM_CHANNEL_1; capture_channels[1].channel = TIM_CHANNEL_1;
capture_channels[1].flag = TIM_FLAG_CC1; capture_channels[1].flag = TIM_FLAG_CC1;
capture_channels[1].counter_mask = 0xFFFFU; capture_channels[1].counter_mask = 0xFFFFU;
capture_channels[1].timer_clock_hz = GetTimerClockHz(&htim9); capture_channels[1].timer_clock_hz = GetTimerClockHz(&htim3);
capture_channels[1].last_edge_ms = HAL_GetTick(); capture_channels[1].last_edge_ms = HAL_GetTick();
capture_channels[2].htim = &htim3;
capture_channels[2].channel = TIM_CHANNEL_1;
capture_channels[2].flag = TIM_FLAG_CC1;
capture_channels[2].counter_mask = 0xFFFFU;
capture_channels[2].timer_clock_hz = GetTimerClockHz(&htim3);
capture_channels[2].last_edge_ms = HAL_GetTick();
capture_channels[3].htim = &htim12;
capture_channels[3].channel = TIM_CHANNEL_1;
capture_channels[3].flag = TIM_FLAG_CC1;
capture_channels[3].counter_mask = 0xFFFFU;
capture_channels[3].timer_clock_hz = GetTimerClockHz(&htim12);
capture_channels[3].last_edge_ms = HAL_GetTick();
if (HAL_TIM_IC_Start(&htim5, TIM_CHANNEL_1) != HAL_OK) if (HAL_TIM_IC_Start(&htim5, TIM_CHANNEL_1) != HAL_OK)
{ {
Error_Handler(); Error_Handler();
} }
if (HAL_TIM_IC_Start(&htim9, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_IC_Start(&htim3, TIM_CHANNEL_1) != HAL_OK) if (HAL_TIM_IC_Start(&htim3, TIM_CHANNEL_1) != HAL_OK)
{ {
Error_Handler(); Error_Handler();
} }
if (HAL_TIM_IC_Start(&htim12, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
} }
static void ConfigureCanRuntime(CAN_HandleTypeDef *hcan) static void ConfigureCanRuntime(CAN_HandleTypeDef *hcan)
@@ -1480,14 +1388,15 @@ static void BroadcastAdcReport(uint32_t input_mv)
static void BroadcastPwmReport(void) static void BroadcastPwmReport(void)
{ {
uint8_t payload[8]; uint8_t payload[4];
size_t i; size_t i;
for (i = 0U; i < 4U; i++) for (i = 0U; i < 2U; i++)
{ {
uint16_t clamped = (capture_channels[i].frequency_hz > 0xFFFFU) ? uint32_t speed_mm_per_s = WheelSpeedMmPerSecond(capture_channels[i].frequency_hz);
uint16_t clamped = (speed_mm_per_s > 0xFFFFU) ?
0xFFFFU : 0xFFFFU :
(uint16_t)capture_channels[i].frequency_hz; (uint16_t)speed_mm_per_s;
payload[i * 2U] = (uint8_t)(clamped & 0xFFU); payload[i * 2U] = (uint8_t)(clamped & 0xFFU);
payload[(i * 2U) + 1U] = (uint8_t)(clamped >> 8); payload[(i * 2U) + 1U] = (uint8_t)(clamped >> 8);
} }
+2 -2
View File
@@ -146,8 +146,8 @@ public partial class MainWindow : Window
case AdcId when d.Length >= 2: case AdcId when d.Length >= 2:
string adc = $"{U16(d, 0)} mV ({U16(d, 0) / 1000.0:F3} V)"; string adc = $"{U16(d, 0)} mV ({U16(d, 0) / 1000.0:F3} V)";
AdcValue.Text = adc; return adc; AdcValue.Text = adc; return adc;
case PwmId when d.Length >= 8: case PwmId when d.Length >= 4:
string pwm = string.Join(" / ", Enumerable.Range(0, 4).Select(i => $"{U16(d, i * 2)} Hz")); string pwm = $"左前 PA0={U16(d, 0) / 1000.0:F3} m/s / 右前 PA6={U16(d, 2) / 1000.0:F3} m/s";
PwmValue.Text = pwm; return pwm; PwmValue.Text = pwm; return pwm;
case DacAckId when d.Length >= 4: case DacAckId when d.Length >= 4:
string ack = $"DAC{d[1]} {(d[0] == 1 ? "OK" : "ERR")} = {U16(d, 2)} mV"; string ack = $"DAC{d[1]} {(d[0] == 1 ? "OK" : "ERR")} = {U16(d, 2)} mV";
+6 -6
View File
@@ -76,8 +76,8 @@
<h2>1. 功能概览</h2> <h2>1. 功能概览</h2>
<table> <table>
<tr><th style="width:20%">功能</th><th>当前实现</th></tr> <tr><th style="width:20%">功能</th><th>当前实现</th></tr>
<tr><td>状态采集</td><td>采集 PD0–PD14 共 15 路数字输入。上电和任一输入变化时,向 CAN1、CAN2 发送完整状态与变化掩码。</td></tr> <tr><td>状态采集</td><td>采集 PD0–PD14 共 15 路数字输入。每 1 秒向 CAN1、CAN2 发送完整状态与该秒内累积的变化掩码。</td></tr>
<tr><td>频率采集</td><td>采集 PA0、PA2、PA6、PB14 的上升沿频率,每 20 ms 通过 CAN 输出;串口每 500 ms 汇总输出。</td></tr> <tr><td>轮速采集</td><td>仅采集左前 PA0、右前 PA6 的上升沿;以每圈 49 个方波、轮周长 1414 mm 换算为 m/s,每 20 ms 通过 CAN 输出。</td></tr>
<tr><td>电压采集</td><td>ADC1_IN8(PB0)测量外部经分压后的输入;对外输出分压前、已标定且滤波后的电压。</td></tr> <tr><td>电压采集</td><td>ADC1_IN8(PB0)测量外部经分压后的输入;对外输出分压前、已标定且滤波后的电压。</td></tr>
<tr><td>CAN</td><td>CAN1 与 CAN2 均为 500 kbit/s、11 位标准数据帧,并使用相同协议。</td></tr> <tr><td>CAN</td><td>CAN1 与 CAN2 均为 500 kbit/s、11 位标准数据帧,并使用相同协议。</td></tr>
<tr><td>DAC</td><td>通过 I2C2 控制两颗 MCP4725,接收 CAN 命令或串口命令设置 0–5.000 V 输出。</td></tr> <tr><td>DAC</td><td>通过 I2C2 控制两颗 MCP4725,接收 CAN 命令或串口命令设置 0–5.000 V 输出。</td></tr>
@@ -88,7 +88,7 @@
<table> <table>
<tr><th>接口</th><th>MCU 引脚 / 外设</th><th>说明</th></tr> <tr><th>接口</th><th>MCU 引脚 / 外设</th><th>说明</th></tr>
<tr><td>15 路状态输入</td><td>PD0–PD14 / EXTI</td><td>bit0 对应 PD0,bit14 对应 PD14。</td></tr> <tr><td>15 路状态输入</td><td>PD0–PD14 / EXTI</td><td>bit0 对应 PD0,bit14 对应 PD14。</td></tr>
<tr><td>频率输入</td><td>PA0/TIM5、PA2/TIM9、PA6/TIM3、PB14/TIM12</td><td>上升沿输入捕获。</td></tr> <tr><td>轮速输入</td><td>左前 PA0/TIM5、右前 PA6/TIM3</td><td>上升沿输入捕获。</td></tr>
<tr><td>电压输入</td><td>PB0 / ADC1_IN8</td><td>报告值为分压前电压,单位 mV。</td></tr> <tr><td>电压输入</td><td>PB0 / ADC1_IN8</td><td>报告值为分压前电压,单位 mV。</td></tr>
<tr><td>CAN1 / CAN2</td><td>PB8/PB9;PB5/PB6</td><td>分别为 RX/TX。</td></tr> <tr><td>CAN1 / CAN2</td><td>PB8/PB9;PB5/PB6</td><td>分别为 RX/TX。</td></tr>
<tr><td>DAC I2C</td><td>PB10/PB11 / I2C2</td><td>DAC1 地址 0x61,DAC2 地址 0x60(7 位地址)。</td></tr> <tr><td>DAC I2C</td><td>PB10/PB11 / I2C2</td><td>DAC1 地址 0x61,DAC2 地址 0x60(7 位地址)。</td></tr>
@@ -101,11 +101,11 @@
<tr><th style="width:13%">CAN ID</th><th style="width:19%">名称</th><th style="width:22%">方向 / 时机</th><th>数据定义</th></tr> <tr><th style="width:13%">CAN ID</th><th style="width:19%">名称</th><th style="width:22%">方向 / 时机</th><th>数据定义</th></tr>
<tr><td><code>0x6A0</code></td><td>心跳</td><td>板卡发送,每 100 ms</td><td><code>A1 01 seq_L seq_H uptime_0..3</code>;板号 A1、协议版本 1、序号、运行时间 ms。</td></tr> <tr><td><code>0x6A0</code></td><td>心跳</td><td>板卡发送,每 100 ms</td><td><code>A1 01 seq_L seq_H uptime_0..3</code>;板号 A1、协议版本 1、序号、运行时间 ms。</td></tr>
<tr><td><code>0x6A1</code></td><td>输入电压</td><td>板卡发送,每 500 ms</td><td><code>uint16</code>,单位 mV。例如 <code>90 2F</code> = 0x2F90 = 12176 mV = 12.176 V。</td></tr> <tr><td><code>0x6A1</code></td><td>输入电压</td><td>板卡发送,每 500 ms</td><td><code>uint16</code>,单位 mV。例如 <code>90 2F</code> = 0x2F90 = 12176 mV = 12.176 V。</td></tr>
<tr><td><code>0x6A2</code></td><td>四路频率</td><td>板卡发送,每 20 ms</td><td>4 个 <code>uint16</code>,单位 Hz;依次为 PA0、PA2、PA6、PB14。</td></tr> <tr><td><code>0x6A2</code></td><td>双路轮速</td><td>板卡发送,每 20 ms</td><td>DLC=4;左前 PA0、右前 PA6 各一个 <code>uint16</code>,原始单位 mm/s(DBC 物理量纲 <code>0.001 m/s</code>)。按每圈 49 方波、轮周长 1414 mm 换算。</td></tr>
<tr><td><code>0x6A3</code></td><td>DAC 设置</td><td>外部发送给板卡</td><td><code>[DAC 编号][mV_L][mV_H]</code>,编号仅允许 1 或 2;每路 100 ms 未续帧归零。</td></tr> <tr><td><code>0x6A3</code></td><td>DAC 设置</td><td>外部发送给板卡</td><td><code>[DAC 编号][mV_L][mV_H]</code>,编号仅允许 1 或 2;每路 100 ms 未续帧归零。</td></tr>
<tr><td><code>0x6A4</code></td><td>DAC 应答</td><td>板卡发送,设置后</td><td><code>[结果][DAC 编号][mV_L][mV_H]</code>;结果 1 成功,0 失败。</td></tr> <tr><td><code>0x6A4</code></td><td>DAC 应答</td><td>板卡发送,设置后</td><td><code>[结果][DAC 编号][mV_L][mV_H]</code>;结果 1 成功,0 失败。</td></tr>
<tr><td><code>0x6A5</code></td><td>ASSI RGB 灯控</td><td>外部发送给板卡</td><td><code>byte0</code>:0=关、1=黄常亮、2=黄闪、3=蓝闪、4=蓝常亮。</td></tr> <tr><td><code>0x6A5</code></td><td>ASSI RGB 灯控</td><td>外部发送给板卡</td><td><code>byte0</code>:0=关、1=黄常亮、2=黄闪、3=蓝闪、4=蓝常亮。</td></tr>
<tr><td><code>0x6A6</code></td><td>15 路状态</td><td>板卡发送,上电或状态变化</td><td>状态 <code>uint16</code>、变化掩码 <code>uint16</code>、时间戳 <code>uint32 ms</code>。</td></tr> <tr><td><code>0x6A6</code></td><td>15 路状态</td><td>板卡发送,每 1 s</td><td>状态 <code>uint16</code>、变化掩码 <code>uint16</code>、时间戳 <code>uint32 ms</code>;变化掩码在本秒内累积。</td></tr>
</table> </table>
<h3>DAC 设置示例</h3> <h3>DAC 设置示例</h3>
@@ -118,7 +118,7 @@ DAC2 输出 1.140 V:ID = 0x6A3,DLC = 3,DATA = 02 74 04
<h2>4. ADC 电压说明</h2> <h2>4. ADC 电压说明</h2>
<p>ADC 输出为分压前输入电压,换算使用 100 kΩ / 30 kΩ 分压参数,并采用实测标定系数 <code>12300 / 11656</code>。单次报告包含 16 次 ADC 平均,随后对最近 4 次报告做移动平均;输出刷新周期为 500 ms,滤波时间约 2 秒。</p> <p>ADC 输出为分压前输入电压,换算使用 100 kΩ / 30 kΩ 分压参数,并采用实测标定系数 <code>12300 / 11656</code>。单次报告包含 16 次 ADC 平均,随后对最近 4 次报告做移动平均;输出刷新周期为 500 ms,滤波时间约 2 秒。</p>
<pre>串口示例:ADC=12300mV PWM=10,0,0,0Hz</pre> <pre>串口示例:ADC=12300mV WheelSpeed=LF:0.289m/s RF:0.577m/s</pre>
<h2>5. 串口使用</h2> <h2>5. 串口使用</h2>
<p>串口参数:115200、8 数据位、1 停止位、无校验、无流控。USB-TTL 应交叉连接:PA9/TX → 转换器 RX,PA10/RX → 转换器 TX,并连接公共地。</p> <p>串口参数:115200、8 数据位、1 停止位、无校验、无流控。USB-TTL 应交叉连接:PA9/TX → 转换器 RX,PA10/RX → 转换器 TX,并连接公共地。</p>
+8 -12
View File
@@ -43,11 +43,9 @@ BO_ 1696 SDC_Heartbeat: 8 SDC_ACQ_DAC
BO_ 1697 SDC_ADC_InputVoltage: 2 SDC_ACQ_DAC BO_ 1697 SDC_ADC_InputVoltage: 2 SDC_ACQ_DAC
SG_ Input_Voltage : 0|16@1+ (0.001,0) [0|65.535] "V" VehicleController SG_ Input_Voltage : 0|16@1+ (0.001,0) [0|65.535] "V" VehicleController
BO_ 1698 SDC_InputFrequencies: 8 SDC_ACQ_DAC BO_ 1698 SDC_WheelSpeed: 4 SDC_ACQ_DAC
SG_ Frequency_PA0 : 0|16@1+ (1,0) [0|65535] "Hz" VehicleController SG_ WheelSpeed_LeftFront_PA0 : 0|16@1+ (0.001,0) [0|65.535] "m/s" VehicleController
SG_ Frequency_PA2 : 16|16@1+ (1,0) [0|65535] "Hz" VehicleController SG_ WheelSpeed_RightFront_PA6 : 16|16@1+ (0.001,0) [0|65.535] "m/s" VehicleController
SG_ Frequency_PA6 : 32|16@1+ (1,0) [0|65535] "Hz" VehicleController
SG_ Frequency_PB14 : 48|16@1+ (1,0) [0|65535] "Hz" VehicleController
BO_ 1699 SDC_DAC_Set: 3 VehicleController BO_ 1699 SDC_DAC_Set: 3 VehicleController
SG_ DAC_Select : 0|8@1+ (1,0) [1|2] "" SDC_ACQ_DAC SG_ DAC_Select : 0|8@1+ (1,0) [1|2] "" SDC_ACQ_DAC
@@ -74,15 +72,13 @@ VAL_ 1701 ASSI_State 0 "AS_OFF" 1 "AS_READY" 2 "AS_DRIVING" 3 "AS_EMERGENCY" 4 "
CM_ BO_ 1696 "Standard ID 0x6A0. Sent every 100 ms on CAN1 and CAN2."; CM_ BO_ 1696 "Standard ID 0x6A0. Sent every 100 ms on CAN1 and CAN2.";
CM_ BO_ 1697 "Standard ID 0x6A1. Filtered and calibrated physical input voltage before the hardware divider."; CM_ BO_ 1697 "Standard ID 0x6A1. Filtered and calibrated physical input voltage before the hardware divider.";
CM_ BO_ 1698 "Standard ID 0x6A2. Sent every 20 ms. Frequency order is PA0, PA2, PA6, PB14."; CM_ BO_ 1698 "Standard ID 0x6A2. Sent every 20 ms with two wheel speeds: left-front PA0 then right-front PA6. Raw value is mm/s (physical scale 0.001 m/s), derived from 49 pulses/rev and 1414 mm wheel circumference.";
CM_ BO_ 1699 "Standard ID 0x6A3. Accepted on CAN1 and CAN2. The on-wire voltage is an unsigned mV value. Each DAC is reset to 0 mV if its own control frame is not received again within 100 ms."; CM_ BO_ 1699 "Standard ID 0x6A3. Accepted on CAN1 and CAN2. The on-wire voltage is an unsigned mV value. Each DAC is reset to 0 mV if its own control frame is not received again within 100 ms.";
CM_ BO_ 1700 "Standard ID 0x6A4. Sent on CAN1 and CAN2 after a DAC command. Result 0 indicates the I2C write failed."; CM_ BO_ 1700 "Standard ID 0x6A4. Sent on CAN1 and CAN2 after a DAC command. Result 0 indicates the I2C write failed.";
CM_ BO_ 1701 "Standard ID 0x6A5. Accepted on CAN1 and CAN2. Byte 0 selects the latched ASSI RGB lamp state: AS_OFF, AS_READY, AS_DRIVING, AS_EMERGENCY, or AS_FINISHED."; CM_ BO_ 1701 "Standard ID 0x6A5. Accepted on CAN1 and CAN2. Byte 0 selects the latched ASSI RGB lamp state: AS_OFF, AS_READY, AS_DRIVING, AS_EMERGENCY, or AS_FINISHED.";
CM_ BO_ 1702 "Standard ID 0x6A6. Sent at startup and whenever PD0-PD14 state changes, on CAN1 and CAN2."; CM_ BO_ 1702 "Standard ID 0x6A6. Sent every 1000 ms on CAN1 and CAN2. Changed-mask bits accumulate PD0-PD14 transitions since the preceding report.";
CM_ SG_ 1696 Board_Identifier "Fixed board identifier 0xA1."; CM_ SG_ 1696 Board_Identifier "Fixed board identifier 0xA1.";
CM_ SG_ 1698 Frequency_PA0 "TIM5_CH1 input capture on PA0."; CM_ SG_ 1698 WheelSpeed_LeftFront_PA0 "Left-front wheel speed. TIM5_CH1 input capture on PA0; 49 pulses/rev and 1414 mm circumference.";
CM_ SG_ 1698 Frequency_PA2 "TIM9_CH1 input capture on PA2."; CM_ SG_ 1698 WheelSpeed_RightFront_PA6 "Right-front wheel speed. TIM3_CH1 input capture on PA6; 49 pulses/rev and 1414 mm circumference.";
CM_ SG_ 1698 Frequency_PA6 "TIM3_CH1 input capture on PA6.";
CM_ SG_ 1698 Frequency_PB14 "TIM12_CH1 input capture on PB14.";
CM_ SG_ 1702 SDC_Input_State "Bit 0 through bit 14 map directly to PD0 through PD14."; CM_ SG_ 1702 SDC_Input_State "Bit 0 through bit 14 map directly to PD0 through PD14.";
CM_ SG_ 1702 SDC_Changed_Mask "A set bit identifies an input that changed since the preceding GPIO-status frame."; CM_ SG_ 1702 SDC_Changed_Mask "A set bit identifies an input that changed during the preceding 1000 ms reporting interval.";
+8 -12
View File
@@ -43,11 +43,9 @@ BO_ 1696 SDC_Heartbeat: 8 SDC_ACQ_DAC
BO_ 1697 SDC_ADC_InputVoltage: 2 SDC_ACQ_DAC BO_ 1697 SDC_ADC_InputVoltage: 2 SDC_ACQ_DAC
SG_ Input_Voltage : 0|16@1+ (0.001,0) [0|65.535] "V" VehicleController SG_ Input_Voltage : 0|16@1+ (0.001,0) [0|65.535] "V" VehicleController
BO_ 1698 SDC_InputFrequencies: 8 SDC_ACQ_DAC BO_ 1698 SDC_WheelSpeed: 4 SDC_ACQ_DAC
SG_ Frequency_PA0 : 0|16@1+ (1,0) [0|65535] "Hz" VehicleController SG_ WheelSpeed_LeftFront_PA0 : 0|16@1+ (0.001,0) [0|65.535] "m/s" VehicleController
SG_ Frequency_PA2 : 16|16@1+ (1,0) [0|65535] "Hz" VehicleController SG_ WheelSpeed_RightFront_PA6 : 16|16@1+ (0.001,0) [0|65.535] "m/s" VehicleController
SG_ Frequency_PA6 : 32|16@1+ (1,0) [0|65535] "Hz" VehicleController
SG_ Frequency_PB14 : 48|16@1+ (1,0) [0|65535] "Hz" VehicleController
BO_ 1699 SDC_DAC_Set: 3 VehicleController BO_ 1699 SDC_DAC_Set: 3 VehicleController
SG_ DAC_Select : 0|8@1+ (1,0) [1|2] "" SDC_ACQ_DAC SG_ DAC_Select : 0|8@1+ (1,0) [1|2] "" SDC_ACQ_DAC
@@ -74,15 +72,13 @@ VAL_ 1701 ASSI_State 0 "AS_OFF" 1 "AS_READY" 2 "AS_DRIVING" 3 "AS_EMERGENCY" 4 "
CM_ BO_ 1696 "Standard ID 0x6A0. Sent every 100 ms on CAN1 and CAN2."; CM_ BO_ 1696 "Standard ID 0x6A0. Sent every 100 ms on CAN1 and CAN2.";
CM_ BO_ 1697 "Standard ID 0x6A1. Filtered and calibrated physical input voltage before the hardware divider."; CM_ BO_ 1697 "Standard ID 0x6A1. Filtered and calibrated physical input voltage before the hardware divider.";
CM_ BO_ 1698 "Standard ID 0x6A2. Sent every 20 ms. Frequency order is PA0, PA2, PA6, PB14."; CM_ BO_ 1698 "Standard ID 0x6A2. Sent every 20 ms with two wheel speeds: left-front PA0 then right-front PA6. Raw value is mm/s (physical scale 0.001 m/s), derived from 49 pulses/rev and 1414 mm wheel circumference.";
CM_ BO_ 1699 "Standard ID 0x6A3. Accepted on CAN1 and CAN2. The on-wire voltage is an unsigned mV value. Each DAC is reset to 0 mV if its own control frame is not received again within 100 ms."; CM_ BO_ 1699 "Standard ID 0x6A3. Accepted on CAN1 and CAN2. The on-wire voltage is an unsigned mV value. Each DAC is reset to 0 mV if its own control frame is not received again within 100 ms.";
CM_ BO_ 1700 "Standard ID 0x6A4. Sent on CAN1 and CAN2 after a DAC command. Result 0 indicates the I2C write failed."; CM_ BO_ 1700 "Standard ID 0x6A4. Sent on CAN1 and CAN2 after a DAC command. Result 0 indicates the I2C write failed.";
CM_ BO_ 1701 "Standard ID 0x6A5. Accepted on CAN1 and CAN2. Byte 0 selects the latched ASSI RGB lamp state: AS_OFF, AS_READY, AS_DRIVING, AS_EMERGENCY, or AS_FINISHED."; CM_ BO_ 1701 "Standard ID 0x6A5. Accepted on CAN1 and CAN2. Byte 0 selects the latched ASSI RGB lamp state: AS_OFF, AS_READY, AS_DRIVING, AS_EMERGENCY, or AS_FINISHED.";
CM_ BO_ 1702 "Standard ID 0x6A6. Sent at startup and whenever PD0-PD14 state changes, on CAN1 and CAN2."; CM_ BO_ 1702 "Standard ID 0x6A6. Sent every 1000 ms on CAN1 and CAN2. Changed-mask bits accumulate PD0-PD14 transitions since the preceding report.";
CM_ SG_ 1696 Board_Identifier "Fixed board identifier 0xA1."; CM_ SG_ 1696 Board_Identifier "Fixed board identifier 0xA1.";
CM_ SG_ 1698 Frequency_PA0 "TIM5_CH1 input capture on PA0."; CM_ SG_ 1698 WheelSpeed_LeftFront_PA0 "Left-front wheel speed. TIM5_CH1 input capture on PA0; 49 pulses/rev and 1414 mm circumference.";
CM_ SG_ 1698 Frequency_PA2 "TIM9_CH1 input capture on PA2."; CM_ SG_ 1698 WheelSpeed_RightFront_PA6 "Right-front wheel speed. TIM3_CH1 input capture on PA6; 49 pulses/rev and 1414 mm circumference.";
CM_ SG_ 1698 Frequency_PA6 "TIM3_CH1 input capture on PA6.";
CM_ SG_ 1698 Frequency_PB14 "TIM12_CH1 input capture on PB14.";
CM_ SG_ 1702 SDC_Input_State "Bit 0 through bit 14 map directly to PD0 through PD14."; CM_ SG_ 1702 SDC_Input_State "Bit 0 through bit 14 map directly to PD0 through PD14.";
CM_ SG_ 1702 SDC_Changed_Mask "A set bit identifies an input that changed since the preceding GPIO-status frame."; CM_ SG_ 1702 SDC_Changed_Mask "A set bit identifies an input that changed during the preceding 1000 ms reporting interval.";
+5 -5
View File
@@ -47,7 +47,7 @@ endobj
endobj endobj
6 0 obj 6 0 obj
<< <<
/Author (\(anonymous\)) /CreationDate (D:20260922213520+08'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260922213520+08'00') /Producer (ReportLab PDF Library - \(opensource\)) /Author (\(anonymous\)) /CreationDate (D:20261005194858+08'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261005194858+08'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\376\377\000S\000D\000C\000-\000A\000C\000Q\000_\000D\000A\000C\000 \000C\000A\000N\000 b\245e\207\213\364f\016\210h) /Trapped /False /Subject (\(unspecified\)) /Title (\376\377\000S\000D\000C\000-\000A\000C\000Q\000_\000D\000A\000C\000 \000C\000A\000N\000 b\245e\207\213\364f\016\210h) /Trapped /False
>> >>
endobj endobj
@@ -58,10 +58,10 @@ endobj
endobj endobj
8 0 obj 8 0 obj
<< <<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2151 /Filter [ /ASCII85Decode /FlateDecode ] /Length 2289
>> >>
stream stream
Gb!;fh/h((&:`#5Tg!QhR79`J0:Nh#kSMSP$(!`jF>-9*)FC,(+b;*3d(d7Eq=JfBT?eu4mA(J_G-tMWp:%YsNt[7tgpPI($2JI.DZ.K[#;UWl\>5;3[`OT:NOKjSc7(9DGcbbU^eb=EE#t/kdpSsqP\Y!#-5>25`Q8>,jC&p#AuE-A&74*;5hQ<+#\kghUsFKsU2DlJZ)MShYg)pNfs8lYd%/hO0X*qH#u>GTa,M)s+OtH6bV[+(,IDjG0Wq[u?@G7RYK6&WfueG('27o!b7%WQUJu*Td*9@N?pHe>"[WM^7BMuW#ZXPu'eJ$0;WTK1gIJjpc;[J8W2htJ.O/kT]IseM\CG4)BS8)L'4<oE2Cp\Fs0Z^(D"EF!h?@V[m7].M9'pZ2%q">2B$ps^%rTV1Mo4f9_s+>13[c#E56s>@OCfCSo\YX)$F`RaW7\S+Zu\=u42!i17Yg_o\o+Y2drl;^2J;aMhe$#2>&ti'[omFFCT'5o@fjP?31Sqi>a6-kfhBnoK-V8RjMah-CB*EY]OD2.cuU739?mu=XHfqF3`9f[aZ@Y/CIeZ\`A8XIeq8P`'ZcQiTNkUt:+g3gb[:ncYS=AWg/>EW_VEK^fS#6*/_DPQUD2WcUYu=)1?)p<guqJSKI(!RL'in@V,/%g8)/'cqUE?3B)&H9^k4]DPU2$nB!c0MfK><o:_f@(!s.SEOF#W:7&bRbP#@rM8@b!BQ!>e@[:_,=CqD)4PffjTkp0A""Z*ejWK,4aIt%*u!<G7m]FOO5KB17+<RJCFR_U'*<Z%SfPQnI)!r<qK:ch#pAk]BqT/>>jl#/2CJ(4NaM!Op]KftHrB$s`THC;&?_nQtef@O@g!Y^o%OE[MI;jT*]2D]p_0IX:[cjFQKPrP_:m5")Em]T`n9VqnQM,Rm*f=f<ilefsF':*hqD0'N_\"U=d?:hgPW:*'HEZ4]_oA/VAiPR3#bT`QoQf$Pe][,?;cnSQm*il)pq^::4T`\L<7?4fS!bTX,m:.-U!Z%$&PSRNL=(kCt@0tKBPPKmNQ$N5eFTYfYjj;H,,@9:X6Ot7XH4%'lT$UE5m'tEk<P93'SjM27&UIi4kjcf;kqFUX=P*V0amj^8FVs^NH69qM(9m2#JaNQF#M/P;m_di2qr(KgnMUY0#$2'e(^m];mB-\-A]eJm>iTOH9q]=;N+K<k0ihu=09b8+1B/,"kl(X)(K]2VZ-($*"oQeS\."!AY[DL0r,N2M>p]!>%tdoV:Btb5N40jh!]4#K?F)BM(t8<5GMs:t&CaEsKh*")O^>!A!72N)]`@hQ(`3_S10k'T5W+F+V<d,(iq!AMg!^PjR>bI"(E.?(G`0u=0%6YDoqr0UEb24uHKM.tC5mpo2p#M$=UWG+R0c:9dep%P&;,YN\J/@f_S8,H\'`t9.5lEkCE<)F]9k^jEO^eLX)6BM"El)p&F=E_fF*tVQE4Di.k"S^"abUg1>9I^g!"maLc9^L\6dr,g3!k[bL*gQe"m<Dkl)]"0SRXTkq<VVP'%Nb^`=?p(ue>Yo](RV_jt0$\QF8C"A6M5&<poT-ju<PQ4.#q/"T#2I-NrE#1H:6j)3Smq&>ZVe"^_'W#(JJ(0q$&Ho)hdFQm,fI?FEZp]C<%otU\a/LVs45"e?V+abtl0mQU_\8Q?n"(Vi'Dj"YSnd;u<QM/m$_)X:B</d3&@0g;8MT*Y1bZrsIHsc!+Po^8#s3Zg+i?t#Hn6/nAWpX95LA*1$iuWn"i_7"MO7D-6"LF#j&HSWZo*'Jn^jkPlrA38@>r85IY>'=W4o,el]kn6K`UKo&LX.(u]9,9\IEND$cJhhrhgo-jaicR0epbX=?YrUa%%oQ.$NFnV_scF9TL+dU_UnS3pk@CEs!1N5@OZk>_^o4i=F(,'YPALHFot<JbM-nW5s>\9XBMM9m!426P^knmOB"ogSgQf&qL3]!D7m/(puhEE>cBVN=E<Un3bUhXD#YKSJr(6LQ5hrW53_qZL)"60HVN&8TNGTHMd).J+HB?.Zpa^hg<JeRi(nf<;pBed,9dJYJ^'7bG8F&C$\nuT:CunF1NGt*G'EO7"M'4rk'ed;rZpW$?euB/op^?Y%[31rgq6d7MP;?LX3efVYR&;@cD&Na$9j0)Hk/+,*dmq\J?DF.2Z<a(l#A-~>endstream Gau`VD0+2b&H88.Tml3e1.=aGHEims"+>ku?@'.,%"h.*U`mAsKE*Tt;gs'pnl`8Gp0FbNA.B=.>-1i+I3,M$9.&jMI<g(SfFKc]q\R@"Yh:fZ'j#4[_1%uX]j6hC8QB^]92dSTQ7L<ImAQH]"'0-UV,:81`hC+25@,S*13@$Ym"DqM-;2qoB\*8-Ea&N?rTp_Ag@\&D0m1H2Y5$BZ[p3o+geM+Z+%0QA$Wm>\Q@F'SJ^R%'#[;ku$B9@4$#/Tm4TCGd?F&6YFt9tW>VMjYT,XTg<pS2ETjL(++@(C-!`1-%=;kTU`jO/^!=O?S>W86(h5-SMmjb"jPXQHqPuOs]N)p3Rg`CJB<+p+p&DW7gKa<C!J)uOi=1iLkqk3=^h,:9";r-:'k&^B7-PZupG@RcspSU*Bdr"PrcmEFU&8$r"SZNIJk6eVo.7/?<]5EY;mK@fRf1/o00TKi4Xeg'X4bPf>M*Hcn=V[*d&lj.E[Ge!9;DKpe#V:\I.CB7lQj(niYTt/%4$R3#DN[olS'/kc=u'dR1;$Ra>1-Mf;]#k;dXkM2BVdAU)07X$Wtek&'3V#1'eAt*dq'_p["pMk`rR_fQ6orSot1S1W!bM*4BI0;i$N1>aUPd.5V8s?O-(BVWRYB"QAjp=!-4&B5*Aqb4_,$7R[(3OJ.1S99bn/#7ds=>D"f?o5o3o6*<]8@W6DWcQ;M@s"f&kpP'eo#Y,'LMCsG/jCj6^(7O?:pCdM!<'NX?7eMC?@LSmot5hLD0fJ)bOBMRR=AG&Sg'3ITV&ngi7BlH_>?oXf8Xg>YOed_oVE@%`Xror,6RgK,sYO@2llsV_.dC(=Oo8H,4ItN&$emDEJ$Wgi2lrcYNr,N[[R4'&06)JRO(R/tl`Bb)aQUib9Il#FE&fW5i]FbOVhO$FPs1L6L&aA[E<ued1D+O(QQIan'[I6JWEY@I,e(GQPOfLoY[0V*9/rmP`q,"FsU19aV11j,@pQ*)`O7lgr'IE$<$%trU2id)V-*X%JVUPp_-:<.P/#?&W?.i#<>hLKc?<*5s,jE[me.Z0X.]GpuV4LBHY(q>oN#bDOIG&roojmpm'%@$DWM_JJc\><@j'Vt>PG8Y7l7pk7ksGIBBu%/i"#Br>#$B?0gRG1GGJgR3ib^!g`)QjJ`W@a@]'BB#T!=M4QaBu3,g]pZ`Y)Rp(s@$e,qu49@n_9LjdfF$MLCT?"hm@Z.EsI?$LK.d(.;hYCUncq#eNX&jB?rbWu&nSCu_MLMsNj-rZ2a0HHE"k'79%>F+-E.JbCf-%MpkgMr[Q"SA0$\EpL#QU%=]'gDJj4FEksZ10'`UPC?Vhm`Gdo,M,;EpPXJ\S6l!HiG7uaK]M*6p=s9hdWQ7)Q!STfa:8a[V04Bbb[)TdiCH#Q1i<JdFa0t)C3-Nsc";70,&OXqWF'7QpTX:o!bHRL,dBbX+GjYjo+E$tUH'mOQN#a5FF<.jDg1m(f=(7*\3&rYnlq<U2)"XeCn8;k'Q?kTLd]l8cXNZY-,2S"W0aT?L_H$Ik(n/QhXm_LHT1YB.!Ua+>-%e;<`]QM2%o8D`a)7&P%Q:Y?B,29]t^qo%HDQm?@]t(bXC80mPFh<<H=Eg>6]%Bi=C0,CV>UerYf%glDMf*G9$dA*LC]Ohlq>bgjSP?DuK&Q:nkHd;!O19bV\`k<o?)EBq$]5(uD_,f1eWk?RN(a2guofn(1d$#2ts6d7_8PNeV&8/cEB[f8^ZMn=,*+3W`N9WBi7af;t,I6j+T8^4b2j[epNRQ2i03B5^t3D0+g$qIr]jmV#>4)%"Kp)P!k%<#JPGX&4_B\[ed1D6=H,AgG%\CqGuZ2p5/O3UJ0<R.gXS_0&.gU1"V`H&hePa8b;FSA:]E2V@q)[Cn?lkcNOBUZ5'&`0XYVquS@3F7b+HB7l=9\d(JfV\FrNkM6Q[D\-,sl>m>A.<VlBML'@\PVp70\3g4>qnhUng[Rb_Zb8@e*5Bo$QYYBN"c@Y5d(fF`KUlK%3,8._pc.4X^2S;*YM%E,M0=SD*fH=l94U;Mqm`>"j_dGN::bYYTaK7L(gE7sUGB9_8J<%IW1mQ`G>/7>`+h050MjQp)cLeuLEs:1fu-mGRog1*H.0@8NjU:>L7#"O2bj6]kbAiL(N*6$C=b+h71a]:;bU==4lp@:,5kcWad3C:de`[9R"&@o&`0VplPFcR]DL"$2FBBLNV_"m,U6drX,>1/=PT#+[^F+.K^K>P:tX'W>OV>SUI>4@DAKCLl:<-g-mA^ICufrl[jJ"d(Aj6Sd&Y/000]s1@fj+45FK'i8H~>endstream
endobj endobj
xref xref
0 9 0 9
@@ -77,7 +77,7 @@ xref
trailer trailer
<< <<
/ID /ID
[<e1e46c8f2a916049ff773458531b69dd><e1e46c8f2a916049ff773458531b69dd>] [<3d22dd34981c7eff4009fb12bd7de53c><3d22dd34981c7eff4009fb12bd7de53c>]
% ReportLab generated PDF document -- digest (opensource) % ReportLab generated PDF document -- digest (opensource)
/Info 6 0 R /Info 6 0 R
@@ -85,5 +85,5 @@ trailer
/Size 9 /Size 9
>> >>
startxref startxref
4097 4235
%%EOF %%EOF
+13 -15
View File
@@ -11,8 +11,8 @@
1. 接收并处理串口命令。 1. 接收并处理串口命令。
2. 接收 CAN1、CAN2 的 DAC 控制帧。 2. 接收 CAN1、CAN2 的 DAC 控制帧。
3. 读取四路定时器捕获值并计算频率。 3. 读取四路定时器捕获值并计算频率。
4. 汇总 15 路 GPIO 变化并上报。 4. 汇总 15 路 GPIO 状态与本周期变化,并每秒上报一次。
5. 每 500 ms 上报 ADC、每 20 ms 上报四路频率。 5. 每 500 ms 上报 ADC、每 20 ms 上报双路轮速。
6. 每 500 ms 翻转运行指示灯,每 100 ms 发送 CAN 心跳。 6. 每 500 ms 翻转运行指示灯,每 100 ms 发送 CAN 心跳。
`PE0` 为正常运行心跳灯;若进入 `Error_Handler()`,`PE0` 和 `PE1` 会快速同时翻转。 `PE0` 为正常运行心跳灯;若进入 `Error_Handler()`,`PE0` 和 `PE1` 会快速同时翻转。
@@ -22,10 +22,8 @@
| 功能 | MCU 引脚/外设 | 当前配置 | | 功能 | MCU 引脚/外设 | 当前配置 |
|---|---|---| |---|---|---|
| 15 路状态输入 | `PD0`~`PD14` | 双边沿 EXTI,状态位直接对应端口位 | | 15 路状态输入 | `PD0`~`PD14` | 双边沿 EXTI,状态位直接对应端口位 |
| 频率 1 | `PA0` / `TIM5_CH1` | 上升沿输入捕获,32 位计数器,16 MHz 计数时钟 | | 左前轮速 | `PA0` / `TIM5_CH1` | 上升沿输入捕获,32 位计数器,16 MHz 计数时钟 |
| 频率 2 | `PA2` / `TIM9_CH1` | 上升沿输入捕获,16 位计数器,100 kHz 计数时钟 | | 右前轮速 | `PA6` / `TIM3_CH1` | 上升沿输入捕获,16 位计数器,100 kHz 计数时钟 |
| 频率 3 | `PA6` / `TIM3_CH1` | 上升沿输入捕获,16 位计数器,100 kHz 计数时钟 |
| 频率 4 | `PB14` / `TIM12_CH1` | 上升沿输入捕获,16 位计数器,100 kHz 计数时钟 |
| 电压采集 | `PB0` / `ADC1_IN8` | 480 周期采样,16 次过采样 + 4 次报告均值滤波 | | 电压采集 | `PB0` / `ADC1_IN8` | 480 周期采样,16 次过采样 + 4 次报告均值滤波 |
| CAN1 | `PB8` RX、`PB9` TX | 标准帧,500 kbit/s | | CAN1 | `PB8` RX、`PB9` TX | 标准帧,500 kbit/s |
| CAN2 | `PB5` RX、`PB6` TX | 标准帧,500 kbit/s | | CAN2 | `PB5` RX、`PB6` TX | 标准帧,500 kbit/s |
@@ -38,15 +36,15 @@
## 3. 15 路状态输入 ## 3. 15 路状态输入
`PD0`~`PD14` 的逻辑电平组成一个 15 位无符号数:bit0 对应 `PD0`,bit14 对应 `PD14`。任何一路变化会触发 EXTI 回调,主循环中合并同一时段的变化并输出: `PD0`~`PD14` 的逻辑电平组成一个 15 位无符号数:bit0 对应 `PD0`,bit14 对应 `PD14`。任何一路变化会触发 EXTI 回调,变化掩码会在当前 1 秒周期内累积;`0x6A6` 固定每秒发送一次:
```text ```text
GPIO changed mask=0x0004 state=0x0005 GPIO changed mask=0x0004 state=0x0005
``` ```
- `state`:当前 15 路完整状态。 - `state`:当前 15 路完整状态。
- `mask`:相对于上一次 GPIO 状态帧发生变化的位。 - `mask`:过去 1 秒内发生过变化的位;同一路即使变化多次也只置 1。
- 上电时也会发送一帧状态,变化掩码为 0。 - 上电后首个周期结束时发送第一帧;若无变化,变化掩码为 0。
每秒串口还会打印一次完整状态,例如 `GPIO state=0x0005`。 每秒串口还会打印一次完整状态,例如 `GPIO state=0x0005`。
@@ -61,16 +59,16 @@ ADC 原始值 -> 3.3 V ADC 电压 -> 100 kΩ / 30 kΩ 分压反算 -> 标定系
标定系数固定为 `12300 / 11656`:即以实测输入 12.3 V、旧换算结果 11656 mV 为基准。每次报告读取 16 次 ADC 后求平均,再对最近 4 次报告做移动平均;报告周期为 500 ms,滤波窗口约 2 秒。输出示例: 标定系数固定为 `12300 / 11656`:即以实测输入 12.3 V、旧换算结果 11656 mV 为基准。每次报告读取 16 次 ADC 后求平均,再对最近 4 次报告做移动平均;报告周期为 500 ms,滤波窗口约 2 秒。输出示例:
```text ```text
ADC=12300mV PWM=10,0,0,0Hz ADC=12300mV WheelSpeed=LF:0.289m/s RF:0.577m/s
``` ```
若硬件分压电阻或外部标定基准改变,应同步修改 `main.c` 中的 `ADC_DIVIDER_*` 和 `ADC_CALIBRATION_*` 宏。 若硬件分压电阻或外部标定基准改变,应同步修改 `main.c` 中的 `ADC_DIVIDER_*` 和 `ADC_CALIBRATION_*` 宏。
## 5. 四路频率输入 ## 5. 双路轮速输入
四路频率按 `PA0`、`PA2`、`PA6`、`PB14` 顺序输出。频率由相邻两个上升沿的计数差计算;超过 1 秒没有边沿则显示 0 Hz。当前固件只测频率,不测 PWM 占空比。 仅采集左前轮速 `PA0` 和右前轮速 `PA6`。频率由相邻两个上升沿的计数差计算;超过 1 秒没有边沿则输出 0 m/s。每圈 49 个方波、轮周长 1414 mm,因此 `轮速(m/s) = 方波频率(Hz) × 1.414 / 49`。
后三路 16 位定时器采用 100 kHz 计数时钟,以避免低频输入时 16 位溢出。其可无歧义测量的最低频率约为 1.53 Hz;TIM5 为 32 位计数器,低频范围更大。 `0x6A2` 每 20 ms 发送两个 `uint16`:左前在 Byte0–1,右前在 Byte2–3。原始值的单位为 mm/s,DBC 的物理量纲为 `0.001 m/s`;最高表示 65.535 m/s。PA6 的 TIM3 为 100 kHz、16 位计数器,低频无歧义测量下限约 1.53 Hz;PA0 的 TIM5 为 32 位计数器,低频范围更大。
## 6. CAN 通信 ## 6. CAN 通信
@@ -80,11 +78,11 @@ CAN1 和 CAN2 使用相同协议,所有主动上报均分别发送到两条总
|---:|---|---|---| |---:|---|---|---|
| `0x6A0` | 心跳 | 每 100 ms | `A1 01 seq_L seq_H uptime_0..3` | | `0x6A0` | 心跳 | 每 100 ms | `A1 01 seq_L seq_H uptime_0..3` |
| `0x6A1` | ADC 电压 | 每 500 ms | 输入电压 `uint16`,单位 mV | | `0x6A1` | ADC 电压 | 每 500 ms | 输入电压 `uint16`,单位 mV |
| `0x6A2` | 四路频率 | 每 20 ms | 4 个 `uint16`,单位 Hz,顺序 PA0/PA2/PA6/PB14 | | `0x6A2` | 双路轮速 | 每 20 ms | 左前 PA0、右前 PA6 各一个 `uint16`,单位 `0.001 m/s`,DLC=4 |
| `0x6A3` | DAC 设置命令 | 接收 | `[DAC 编号][mV_L][mV_H]`;每路在 100 ms 内未收到本路下一帧时归零 | | `0x6A3` | DAC 设置命令 | 接收 | `[DAC 编号][mV_L][mV_H]`;每路在 100 ms 内未收到本路下一帧时归零 |
| `0x6A4` | DAC 应答 | 每次 DAC 设置后 | `[结果][DAC 编号][mV_L][mV_H]` | | `0x6A4` | DAC 应答 | 每次 DAC 设置后 | `[结果][DAC 编号][mV_L][mV_H]` |
| `0x6A5` | ASSI 灯控 | 接收 | `byte0`:`0=AS_OFF`、`1=AS_READY`、`2=AS_DRIVING`、`3=AS_EMERGENCY`、`4=AS_FINISHED` | | `0x6A5` | ASSI 灯控 | 接收 | `byte0`:`0=AS_OFF`、`1=AS_READY`、`2=AS_DRIVING`、`3=AS_EMERGENCY`、`4=AS_FINISHED` |
| `0x6A6` | GPIO 状态 | 上电、任一输入变化 | 状态 `uint16`、变化掩码 `uint16`、时间戳 `uint32 ms` | | `0x6A6` | GPIO 状态 | 每 1 s | 状态 `uint16`、变化掩码 `uint16`、时间戳 `uint32 ms` |
心跳帧中板号为 `0xA1`、协议版本为 `0x01`,序号与上电时间均为低字节在前。完整信号定义和量纲以 [DBC 文件](SDC_ACQ_DAC.dbc) 为准。 心跳帧中板号为 `0xA1`、协议版本为 `0x01`,序号与上电时间均为低字节在前。完整信号定义和量纲以 [DBC 文件](SDC_ACQ_DAC.dbc) 为准。