基于 STM32 + LDC1000 电感传感器的金属循迹三轮车程序

一、系统总体架构

复制代码
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
冲出轨迹 速度过快 降低基础速度
相关推荐
Teleger4 小时前
在window上使用c++控制鼠标点击,实现的exe
c++·单片机·计算机外设
黑白园6 小时前
STM32F103ZET6移植-电机2804-驱动板SimpleFOC Mini实现速度开环_位置开环控制(一、硬件介绍及接线)
stm32·单片机·嵌入式硬件
星夜夏空996 小时前
STM32单片机学习(12)——串口通信相关概念
stm32·单片机·学习
ytttr8736 小时前
基于 STM32 的示波器实现
stm32
Stream_Silver7 小时前
【 libusb4java实战:跨平台USB设备通信完全指南】
java·笔记·嵌入式硬件·microsoft
黑白园7 小时前
STM32F103ZET6移植-电机2804(星型接法)-驱动板SimpleFOC Mini实现速度开环_位置开环控制(四、功能演示)
stm32·单片机·嵌入式硬件
Jack_02207 小时前
基于51单片机的停车场刷卡进出计费设计
单片机·嵌入式硬件·51单片机
振浩微433射频芯片7 小时前
433射频方案在远距离工业遥控中的应用解析:从TM-03到RM521的成熟之道
网络·单片机·嵌入式硬件·物联网·智能家居
Hello_Embed7 小时前
libmodbus 移植到 STM32H5
笔记·stm32·单片机·嵌入式硬件·嵌入式·ai编程