一、系统总体架构
LDC1000_Left → SPI → STM32 → PWM → 左电机
LDC1000_Right → SPI → STM32 → PWM → 右电机
↓
金属检测(阈值判断)
↓
PD 控制转向
↓
三轮车底盘
LDC1000 优势:
- 非接触式检测
- 检测距离 0.5-3mm
- 抗干扰(不受光照、颜色影响)
二、硬件连接(STM32F103C8T6)
1、LDC1000 连接(SPI1)
| LDC1000 引脚 | STM32 引脚 | 功能 |
|---|---|---|
| CSB | PA4 | 片选 |
| SCLK | PA5 | SPI 时钟 |
| SDI | PA7 | SPI MOSI |
| SDO | PA6 | SPI MISO |
| INTB | PB0 | 中断(可选) |
| R_REF | 外部电阻 | 2.2KΩ |
2、电机驱动(TB6612FNG)
| TB6612 | STM32 | 功能 |
|---|---|---|
| PWMA | PA8 (TIM1_CH1) | 左电机 PWM |
| AIN1 | PB12 | 左电机方向1 |
| AIN2 | PB13 | 左电机方向2 |
| PWMB | PA9 (TIM1_CH2) | 右电机 PWM |
| BIN1 | PB14 | 右电机方向1 |
| BIN2 | PB15 | 右电机方向2 |
| STBY | 3.3V | 使能 |
三、LDC1000 驱动代码
1、寄存器定义
c
// ldc1000.h
#ifndef __LDC1000_H
#define __LDC1000_H
#include "stm32f1xx_hal.h"
#define LDC_CS_GPIO GPIOA
#define LDC_CS_PIN GPIO_PIN_4
// LDC1000 寄存器地址
#define LDC_REG_RP_MAX 0x00
#define LDC_REG_RP_MIN 0x01
#define LDC_REG_SENSORFREQ 0x02
#define LDC_REG_LDCCONFIG 0x03
#define LDC_REG_CLKCONFIG 0x04
#define LDC_REG_THRESHILSB 0x05
#define LDC_REG_THRESHIMSB 0x06
#define LDC_REG_THRESLOLSB 0x07
#define LDC_REG_THRESLOMSB 0x08
#define LDC_REG_INTCONFIG 0x09
#define LDC_REG_PWRCONFIG 0x0A
#define LDC_REG_STATUS 0x20
#define LDC_REG_PROXLSB 0x21
#define LDC_REG_PROXMSB 0x22
#define LDC_REG_FREQLSB 0x23
#define LDC_REG_FREQMSB 0x24
// 结构体存储传感器数据
typedef struct {
uint16_t proximity; // 接近度
uint16_t frequency; // 频率
uint8_t status; // 状态
} LDC1000_Data_t;
// 函数声明
void LDC1000_Init(void);
uint8_t LDC1000_ReadReg(uint8_t reg);
void LDC1000_WriteReg(uint8_t reg, uint8_t val);
void LDC1000_ReadData(LDC1000_Data_t *data);
uint8_t LDC1000_CheckID(void);
#endif
2、SPI 读写函数
c
// ldc1000.c
#include "ldc1000.h"
extern SPI_HandleTypeDef hspi1;
// 片选控制
#define LDC_CS_LOW() HAL_GPIO_WritePin(LDC_CS_GPIO, LDC_CS_PIN, GPIO_PIN_RESET)
#define LDC_CS_HIGH() HAL_GPIO_WritePin(LDC_CS_GPIO, LDC_CS_PIN, GPIO_PIN_SET)
// 读取寄存器
uint8_t LDC1000_ReadReg(uint8_t reg)
{
uint8_t tx_buf[2] = {0x80 | reg, 0x00}; // 读命令
uint8_t rx_buf[2];
LDC_CS_LOW();
HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, 2, 100);
LDC_CS_HIGH();
return rx_buf[1];
}
// 写入寄存器
void LDC1000_WriteReg(uint8_t reg, uint8_t val)
{
uint8_t tx_buf[2] = {reg, val};
LDC_CS_LOW();
HAL_SPI_Transmit(&hspi1, tx_buf, 2, 100);
LDC_CS_HIGH();
}
// 初始化 LDC1000
void LDC1000_Init(void)
{
// 等待上电稳定
HAL_Delay(10);
// 配置参考电阻范围
LDC1000_WriteReg(LDC_REG_RP_MAX, 0xFF); // 最大 Rp
LDC1000_WriteReg(LDC_REG_RP_MIN, 0x00); // 最小 Rp
// 设置传感器频率 (1.5MHz 典型)
LDC1000_WriteReg(LDC_REG_SENSORFREQ, 0x0F);
// 配置 LDC 和 CLK
LDC1000_WriteReg(LDC_REG_LDCCONFIG, 0x01); // 使能 LDC
LDC1000_WriteReg(LDC_REG_CLKCONFIG, 0x01); // 内部时钟
// 设置阈值(根据实际调整)
LDC1000_WriteReg(LDC_REG_THRESHILSB, 0x00);
LDC1000_WriteReg(LDC_REG_THRESHIMSB, 0x08);
LDC1000_WriteReg(LDC_REG_THRESLOLSB, 0x00);
LDC1000_WriteReg(LDC_REG_THRESLOMSB, 0x04);
// 中断配置
LDC1000_WriteReg(LDC_REG_INTCONFIG, 0x00); // 禁用中断
// 上电配置
LDC1000_WriteReg(LDC_REG_PWRCONFIG, 0x01); // 正常模式
}
// 读取传感器数据
void LDC1000_ReadData(LDC1000_Data_t *data)
{
data->status = LDC1000_ReadReg(LDC_REG_STATUS);
data->proximity = (LDC1000_ReadReg(LDC_REG_PROXMSB) << 8) |
LDC1000_ReadReg(LDC_REG_PROXLSB);
data->frequency = (LDC1000_ReadReg(LDC_REG_FREQMSB) << 8) |
LDC1000_ReadReg(LDC_REG_FREQLSB);
}
四、电机驱动代码
1、TB6612 控制
c
// motor.h
#ifndef __MOTOR_H
#define __MOTOR_H
#include "stm32f1xx_hal.h"
// 电机引脚定义
#define MOTOR_LEFT_AIN1_PORT GPIOB
#define MOTOR_LEFT_AIN1_PIN GPIO_PIN_12
#define MOTOR_LEFT_AIN2_PORT GPIOB
#define MOTOR_LEFT_AIN2_PIN GPIO_PIN_13
#define MOTOR_RIGHT_BIN1_PORT GPIOB
#define MOTOR_RIGHT_BIN1_PIN GPIO_PIN_14
#define MOTOR_RIGHT_BIN2_PORT GPIOB
#define MOTOR_RIGHT_BIN2_PIN GPIO_PIN_15
// 电机方向
typedef enum {
MOTOR_FORWARD = 0,
MOTOR_BACKWARD,
MOTOR_STOP
} Motor_Dir_t;
// 函数声明
void Motor_Init(void);
void Motor_SetLeft(int16_t speed);
void Motor_SetRight(int16_t speed);
void Motor_SetBoth(int16_t left, int16_t right);
void Motor_StopAll(void);
#endif
c
// motor.c
#include "motor.h"
extern TIM_HandleTypeDef htim1; // 用于PWM输出
// 初始化电机
void Motor_Init(void)
{
// 初始化GPIO
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 左电机方向引脚
GPIO_InitStruct.Pin = MOTOR_LEFT_AIN1_PIN | MOTOR_LEFT_AIN2_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(MOTOR_LEFT_AIN1_PORT, &GPIO_InitStruct);
// 右电机方向引脚
GPIO_InitStruct.Pin = MOTOR_RIGHT_BIN1_PIN | MOTOR_RIGHT_BIN2_PIN;
HAL_GPIO_Init(MOTOR_RIGHT_BIN1_PORT, &GPIO_InitStruct);
// 停止电机
Motor_StopAll();
}
// 设置左电机
void Motor_SetLeft(int16_t speed)
{
if (speed > 0) {
// 正转
HAL_GPIO_WritePin(MOTOR_LEFT_AIN1_PORT, MOTOR_LEFT_AIN1_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(MOTOR_LEFT_AIN2_PORT, MOTOR_LEFT_AIN2_PIN, GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, abs(speed));
}
else if (speed < 0) {
// 反转
HAL_GPIO_WritePin(MOTOR_LEFT_AIN1_PORT, MOTOR_LEFT_AIN1_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(MOTOR_LEFT_AIN2_PORT, MOTOR_LEFT_AIN2_PIN, GPIO_PIN_SET);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, abs(speed));
}
else {
// 停止
HAL_GPIO_WritePin(MOTOR_LEFT_AIN1_PORT, MOTOR_LEFT_AIN1_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(MOTOR_LEFT_AIN2_PORT, MOTOR_LEFT_AIN2_PIN, GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0);
}
}
// 设置右电机
void Motor_SetRight(int16_t speed)
{
if (speed > 0) {
HAL_GPIO_WritePin(MOTOR_RIGHT_BIN1_PORT, MOTOR_RIGHT_BIN1_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(MOTOR_RIGHT_BIN2_PORT, MOTOR_RIGHT_BIN2_PIN, GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, abs(speed));
}
else if (speed < 0) {
HAL_GPIO_WritePin(MOTOR_RIGHT_BIN1_PORT, MOTOR_RIGHT_BIN1_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(MOTOR_RIGHT_BIN2_PORT, MOTOR_RIGHT_BIN2_PIN, GPIO_PIN_SET);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, abs(speed));
}
else {
HAL_GPIO_WritePin(MOTOR_RIGHT_BIN1_PORT, MOTOR_RIGHT_BIN1_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(MOTOR_RIGHT_BIN2_PORT, MOTOR_RIGHT_BIN2_PIN, GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 0);
}
}
// 同时设置两个电机
void Motor_SetBoth(int16_t left, int16_t right)
{
Motor_SetLeft(left);
Motor_SetRight(right);
}
// 停止所有电机
void Motor_StopAll(void)
{
Motor_SetLeft(0);
Motor_SetRight(0);
}
五、循迹控制算法
1、状态判断与 PD 控制
c
// track.h
#ifndef __TRACK_H
#define __TRACK_H
#include "ldc1000.h"
// 循迹状态
typedef enum {
TRACK_ON_LINE = 0, // 正常循迹
TRACK_LEFT_OFF, // 左侧偏离
TRACK_RIGHT_OFF, // 右侧偏离
TRACK_ALL_OFF, // 完全偏离
TRACK_CROSS, // 十字交叉
TRACK_STOP // 停止
} Track_State_t;
// 循迹控制器
typedef struct {
uint16_t left_value; // 左传感器值
uint16_t right_value; // 右传感器值
uint16_t threshold; // 检测阈值
Track_State_t state; // 当前状态
float error; // 当前误差
float last_error; // 上次误差
float kp, kd; // PD 参数
int16_t base_speed; // 基础速度
} Track_Controller_t;
// 函数声明
void Track_Init(Track_Controller_t *ctrl);
void Track_Update(Track_Controller_t *ctrl, uint16_t left_val, uint16_t right_val);
int16_t Track_PD_Control(Track_Controller_t *ctrl);
void Track_Decision(Track_Controller_t *ctrl);
#endif
c
// track.c
#include "track.h"
#include "motor.h"
#include <math.h>
// 初始化循迹控制器
void Track_Init(Track_Controller_t *ctrl)
{
ctrl->threshold = 1000; // 需要根据实际标定
ctrl->state = TRACK_ON_LINE;
ctrl->error = 0;
ctrl->last_error = 0;
ctrl->kp = 0.8f; // P 系数
ctrl->kd = 0.2f; // D 系数
ctrl->base_speed = 500; // 基础速度
}
// 更新传感器数据并计算误差
void Track_Update(Track_Controller_t *ctrl, uint16_t left_val, uint16_t right_val)
{
ctrl->left_value = left_val;
ctrl->right_value = right_val;
// 计算误差:左传感器值 - 右传感器值
// 正值表示偏左,负值表示偏右
ctrl->error = (float)(left_val - right_val);
}
// PD 控制计算
int16_t Track_PD_Control(Track_Controller_t *ctrl)
{
float p_term = ctrl->error * ctrl->kp;
float d_term = (ctrl->error - ctrl->last_error) * ctrl->kd;
ctrl->last_error = ctrl->error;
return (int16_t)(p_term + d_term);
}
// 循迹决策
void Track_Decision(Track_Controller_t *ctrl)
{
uint8_t left_on = (ctrl->left_value > ctrl->threshold);
uint8_t right_on = (ctrl->right_value > ctrl->threshold);
if (left_on && right_on) {
ctrl->state = TRACK_CROSS; // 十字路口
}
else if (left_on && !right_on) {
ctrl->state = TRACK_LEFT_OFF; // 左侧偏离
}
else if (!left_on && right_on) {
ctrl->state = TRACK_RIGHT_OFF; // 右侧偏离
}
else if (!left_on && !right_on) {
ctrl->state = TRACK_ALL_OFF; // 完全偏离
}
else {
ctrl->state = TRACK_ON_LINE; // 正常
}
}
六、主控制程序
1、主函数
c
// main.c
#include "main.h"
#include "ldc1000.h"
#include "motor.h"
#include "track.h"
// 全局变量
LDC1000_Data_t ldc_left, ldc_right;
Track_Controller_t track_ctrl;
int main(void)
{
HAL_Init();
SystemClock_Config();
// 初始化外设
MX_GPIO_Init();
MX_SPI1_Init(); // LDC1000 SPI
MX_TIM1_Init(); // 电机 PWM
MX_USART1_UART_Init(); // 串口调试
// 初始化模块
LDC1000_Init();
Motor_Init();
Track_Init(&track_ctrl);
printf("LDC1000 金属循迹小车启动...\r\n");
// 启动 PWM
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
while (1)
{
// 1. 读取传感器数据
LDC1000_ReadData(&ldc_left);
LDC1000_ReadData(&ldc_right);
// 2. 更新循迹控制器
Track_Update(&track_ctrl, ldc_left.proximity, ldc_right.proximity);
// 3. 决策状态
Track_Decision(&track_ctrl);
// 4. 根据状态控制电机
switch (track_ctrl.state) {
case TRACK_ON_LINE:
{
// PD 控制转向
int16_t adjust = Track_PD_Control(&track_ctrl);
int16_t left_speed = track_ctrl.base_speed - adjust;
int16_t right_speed = track_ctrl.base_speed + adjust;
Motor_SetBoth(left_speed, right_speed);
break;
}
case TRACK_LEFT_OFF:
// 左偏,左转
Motor_SetBoth(300, 600);
break;
case TRACK_RIGHT_OFF:
// 右偏,右转
Motor_SetBoth(600, 300);
break;
case TRACK_ALL_OFF:
// 完全偏离,停止
Motor_StopAll();
break;
case TRACK_CROSS:
// 十字路口,直行
Motor_SetBoth(600, 600);
HAL_Delay(500); // 直行一段距离
break;
case TRACK_STOP:
Motor_StopAll();
break;
}
// 调试输出
printf("L:%4d R:%4d Err:%6.1f State:%d\r\n",
ldc_left.proximity, ldc_right.proximity,
track_ctrl.error, track_ctrl.state);
HAL_Delay(20); // 控制周期 20ms
}
}
2、串口 printf 重定向
c
// 在 main.c 中添加
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
七、传感器标定与调试
1、标定程序
c
void Sensor_Calibration(void)
{
uint16_t left_max = 0, left_min = 65535;
uint16_t right_max = 0, right_min = 65535;
printf("开始传感器标定...\r\n");
printf("请将小车放在金属轨迹上,然后按任意键开始\r\n");
// 等待按键
while(!HAL_UART_Receive(&huart1, &rx_data, 1, 100));
for(int i = 0; i < 100; i++) {
LDC1000_ReadData(&ldc_left);
LDC1000_ReadData(&ldc_right);
if(ldc_left.proximity > left_max) left_max = ldc_left.proximity;
if(ldc_left.proximity < left_min) left_min = ldc_left.proximity;
if(ldc_right.proximity > right_max) right_max = ldc_right.proximity;
if(ldc_right.proximity < right_min) right_min = ldc_right.proximity;
HAL_Delay(10);
}
printf("左传感器: 最小值=%d, 最大值=%d\r\n", left_min, left_max);
printf("右传感器: 最小值=%d, 最大值=%d\r\n", right_min, right_max);
// 计算阈值(取中间值)
uint16_t threshold_left = (left_max + left_min) / 2;
uint16_t threshold_right = (right_max + right_min) / 2;
printf("建议阈值: 左=%d, 右=%d\r\n", threshold_left, threshold_right);
}
参考代码 自动循迹小车 www.youwenfan.com/contentcsu/69941.html
八、性能优化
动态阈值调整:
c
uint16_t adaptive_threshold = (left_val + right_val) / 2;
数字滤波:
c
#define FILTER_SIZE 5
uint16_t filter_buffer[FILTER_SIZE];
uint16_t Moving_Average(uint16_t new_value)
{
static uint8_t index = 0;
static uint32_t sum = 0;
sum = sum - filter_buffer[index] + new_value;
filter_buffer[index] = new_value;
index = (index + 1) % FILTER_SIZE;
return sum / FILTER_SIZE;
}
速度规划:
c
void Speed_Ramp(int16_t *current, int16_t target, uint8_t step)
{
if(*current < target) *current += step;
else if(*current > target) *current -= step;
}
九、常见问题与调试
| 问题 | 原因 | 解决 |
|---|---|---|
| 检测不到金属 | 距离太远 | 调整传感器高度 |
| 误检测 | 环境金属干扰 | 增加屏蔽/降低灵敏度 |
| 转向振荡 | PD 参数不合适 | 减小 KP,增大 KD |
| 冲出轨迹 | 速度过快 | 降低基础速度 |