在过程贴中已经使用TB6612驱动两个直流电机,在最终成果中使用TB6612驱动二相四线的42步进电机制作一个简单的追光平台。装置以FRDM-MCXA153评估板(后面简称A153开发板)为控制核心,以1.8寸SPI接口、ST7735驱动的TFT显示屏为显示部件,以TB6612驱动工作电压12V的42步进电机。硅光电池作为A153开发板的ADC输入源。
一、系统框图

二、电路原理图
略。见系统框图中的电气连接。
三、程序运行逻辑

处理逻辑很简单,就是在180度的范围内查找最佳的光照位置。步进电机的步进转角为1.8度,也就是说步进100步之内,通过硅光电池接收到的光信号产生的电压作为判断依据。在电机带着硅光电池转动的过程中,不断获取电压数据,找到最大值,确定最佳位置。
四、主要参数情况
主板使用Type C口提供的5V电源工作。
TB6612使用3.3V工作。电机驱动电源12V。
电机电源TXN 50-112为AC-DC转换器,输入220V,输出12V,50W。
1.8寸TFT显示屏使用3.3V电源。
42步进电机为美蓓亚17PM-M210-11V,实际工作电压12V。
硅光电池为儿童玩具上拆机件,无型号。
装置实图:

五、实现步骤
1、项目概述
目前在嵌入式的应用中,设计电机控制的应用非常多。学习使用单片机开发电机方面的程序,应该也算是单片机工程师必备的一个技能。在分立件盛行的时代,以H桥方式驱动电机,需要储备一定的知识才能实现,而现在随着半导体的发展,像TB6612这样的专用电机驱动芯片的出现,大大简化了电机驱动的应用开发。本次由电子产品世界和得捷联合举办的“Let's Do 第四期”的课程中使用FRDM-MCXA153开发板和TB6612驱动模块,让大家一起来学习和时间电机驱动方面的知识。
2、程序处理逻辑
我在本次课程中准备使用课程提供的材料,加上一块硅光电池,搭建一个小的追光平台。其基本设计思想是,通过旋转硅光电池,寻找获取光照最佳的位置。因为是作为学识电机驱动知识的项目,因此选择的项目也很简单。
3、项目代码
主程序代码:
/*
* Copyright (c) 2015, Freescale Semiconductor, Inc.
* Copyright 2016-2017 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pin_mux.h"
#include "board.h"
#include "fsl_debug_console.h"
#include "fsl_lpadc.h"
#include <stdio.h>
#include "fsl_clock.h"
#include "fsl_reset.h"
#include <stdbool.h>
#include "fsl_device_registers.h"
#include "fsl_lpspi.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "Lcd_Driver.h"
#include "fsl_port.h"
#include "fsl_pwm.h"
#include "fsl_inputmux.h"
#include "motor.h"
/*******************************************************************************
* ADC
******************************************************************************/
#define DEMO_LPADC_BASE ADC0
// 配置中断模式时使用
#define DEMO_LPADC_IRQn ADC0_IRQn
#define LPADC_IRQ_HANDLER_FUNC ADC0_IRQHandler
// 使用的通道 A1 : P2_1
#define DEMO_LPADC_USER_CHANNEL 1U
#define DEMO_LPADC_USER_CMDID 1U /* CMD1 */
#define DEMO_LPADC_VREF_SOURCE kLPADC_ReferenceVoltageAlt3 /* VDDA */
#define DEMO_LPADC_DO_OFFSET_CALIBRATION true
#define DEMO_LPADC_USE_HIGH_RESOLUTION true
/*******************************************************************************
* SPI
******************************************************************************/
#define EXAMPLE_LPSPI_MASTER_BASEADDR (LPSPI0)
#define EXAMPLE_LPSPI_MASTER_IRQN (LPSPI0_IRQn)
#define EXAMPLE_LPSPI_DEALY_COUNT 0xFFFFF
#define LPSPI_MASTER_CLK_FREQ (CLOCK_GetLpspiClkFreq(0))
#define EXAMPLE_LPSPI_MASTER_PCS_FOR_INIT (kLPSPI_Pcs0)
#define EXAMPLE_LPSPI_MASTER_PCS_FOR_TRANSFER (kLPSPI_MasterPcs0)
#define EXAMPLE_LPSPI_MASTER_IRQHandler (LPSPI0_IRQHandler)
#define TRANSFER_SIZE 64U /*! Transfer dataSize */
#define TRANSFER_BAUDRATE 500000U /*! Transfer baudrate - 500k */
/*******************************************************************************
* Variables
******************************************************************************/
lpspi_transfer_t masterXfer;
uint8_t masterRxData[TRANSFER_SIZE] = {0U};
uint8_t masterTxData[TRANSFER_SIZE] = {0U};
/* SysTick延时相关 */
volatile uint32_t g_systickCounter = 1U;
const uint32_t g_LpadcResultShift = 0U;
lpadc_conv_result_t g_LpadcResultConfigStruct;
volatile bool g_LpadcConversionCompletedFlag = false;// ADC转换完成标志(中断方式)
volatile uint32_t g_adcval = 0;
volatile uint32_t g_mapAdcVal = 0;
int g_stepDitection = 1;// 步进电机的方向,1:顺时针;-1:逆时针
uint32_t g_maxAdcMapVal = 0;// 步进电机转动中测得的ADC最大值
uint32_t g_maxAdcMapValStep = 0;// 步进电机转动中测得的ADC最大值时走掉的步数
uint32_t g_stepcount = 0; // 步进步数
uint8_t g_searchMode = 0;// 1:寻找最亮位置时的模式,0:普通模式,与寻找无关
/*******************************************************************************
* Definitions
******************************************************************************/
#define BOARD_LED_GPIO BOARD_LED_RED_GPIO
#define BOARD_LED_GPIO_PIN BOARD_LED_RED_GPIO_PIN
/*******************************************************************************
* Prototypes
******************************************************************************/
/*!
* @brief delay a while.
*/
void delay(void);
void startSearch(void);
/*******************************************************************************
* Variables
******************************************************************************/
/*******************************************************************************
* Code
******************************************************************************/
uint32_t map(uint32_t x, uint32_t in_min, uint32_t in_max, uint32_t out_min, uint32_t out_max) {
// 检查除零
if ((in_max - in_min) == 0) {
return -1; // 或者返回某个默认值
}
// 使用double提高精度
double dividend = (double)(out_max - out_min);
double divisor = (double)(in_max - in_min);
double delta = (double)(x - in_min);
delta = delta<0?0:delta;
uint32_t result = (uint32_t)((delta * dividend) / divisor + out_min);
result = result>100?100:result;
return result;
}
/* 初始化SysTick定时器 */
void SysTick_Init(void) {
/* 设置SysTick重载值,产生1ms中断 */
if (SysTick_Config(SystemCoreClock / 1000U)) {
while (1); /* 初始化失败 */
}
/* 设置SysTick中断优先级(可选) */
NVIC_SetPriority(SysTick_IRQn, 3U);
}
// 1mS定时中断
void SysTick_Handler(void) {
if (g_systickCounter != 0U) {
g_systickCounter--;
}
}
// ADC中断处理函数
void LPADC_IRQ_HANDLER_FUNC(void) {
if (LPADC_GetConvResult(DEMO_LPADC_BASE, &g_LpadcResultConfigStruct)) {
g_adcval = ((g_LpadcResultConfigStruct.convValue) >> g_LpadcResultShift);
g_LpadcConversionCompletedFlag = true;
}
SDK_ISR_EXIT_BARRIER;
}
void delay_ms(uint32_t ms) {
g_systickCounter = ms;
while (g_systickCounter != 0U) {
}
}
/* 微秒级延时函数(使用循环计数) */
void delay_us(uint32_t us) {
volatile uint32_t i = 0;
for (i = 0; i < 4800*us; ++i) {
__asm("NOP");
}
}
void delay(void) {
volatile uint32_t i = 0;
for (i = 0; i < 4800000; ++i) {
__asm("NOP");
/* delay */
}
}
// 设置ADC
int configAdcA1(void) {
lpadc_config_t mLpadcConfigStruct;
lpadc_conv_trigger_config_t mLpadcTriggerConfigStruct;
lpadc_conv_command_config_t mLpadcCommandConfigStruct;
lpadc_conv_result_t mLpadcResultConfigStruct;
//PRINTF("LPADC Polling Example\r\n");
//输出:
//LPADC Polling Example
LPADC_GetDefaultConfig(&mLpadcConfigStruct);
mLpadcConfigStruct.enableAnalogPreliminary = true;
mLpadcConfigStruct.referenceVoltageSource = DEMO_LPADC_VREF_SOURCE;
mLpadcConfigStruct.conversionAverageMode = kLPADC_ConversionAverage128;
LPADC_Init(DEMO_LPADC_BASE, &mLpadcConfigStruct);
/* Request offset calibration. */
LPADC_DoOffsetCalibration(DEMO_LPADC_BASE);
/* Request gain calibration. */
LPADC_DoAutoCalibration(DEMO_LPADC_BASE);
/* Set conversion CMD configuration. */
LPADC_GetDefaultConvCommandConfig(&mLpadcCommandConfigStruct);
mLpadcCommandConfigStruct.channelNumber = DEMO_LPADC_USER_CHANNEL;
mLpadcCommandConfigStruct.conversionResolutionMode = kLPADC_ConversionResolutionHigh;
LPADC_SetConvCommandConfig(DEMO_LPADC_BASE, DEMO_LPADC_USER_CMDID, &mLpadcCommandConfigStruct);
// 配置触发
LPADC_GetDefaultConvTriggerConfig(&mLpadcTriggerConfigStruct);
mLpadcTriggerConfigStruct.targetCommandId = DEMO_LPADC_USER_CMDID;
mLpadcTriggerConfigStruct.enableHardwareTrigger = false;
LPADC_SetConvTriggerConfig(DEMO_LPADC_BASE, 0U, &mLpadcTriggerConfigStruct); /* Configurate the trigger0. */
}
/*!
* @brief Main function
*/
int main(void) {
uint32_t loopCount = 1U;
uint32_t srcClock_Hz;
gpio_pin_config_t led_config = { kGPIO_DigitalOutput, 0, };
// 接收ADC结果
lpadc_conv_result_t mLpadcResultConfigStruct;
uint8_t buffer[32]={0};
uint32_t pwmVal = 4;
/* Board pin, clock, debug console init */
CLOCK_EnableClock(kCLOCK_GateGPIO3);
CLOCK_EnableClock(kCLOCK_GateGPIO1);
// ADC处理用到的时钟
CLOCK_SetClockDiv(kCLOCK_DivADC0, 1u);
CLOCK_AttachClk(kFRO12M_to_ADC0);
BOARD_InitPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();
PRINTF("\r\n Start main ... \r\n");
/* 初始化SysTick定时器 */
PRINTF("\r\n Start SysTick ... \r\n");
SysTick_Init();
// 配置ADC
PRINTF("\r\n config adc ... \r\n");
configAdcA1();
// 初始化显示屏
PRINTF("\r\n init tft ... \r\n");
Lcd_Init();
// 清屏
Lcd_Clear(BLACK);
Gui_DrawFont_GBK16(0,0,WHITE,BLACK, " EEPW & DigiKey ");
Gui_DrawFont_GBK16(0,20,WHITE,BLACK," Let's Do 4 ");
Gui_DrawFont_GBK16(0,40,WHITE,BLACK,"================");
/* Init output LED GPIO. */
GPIO_PinInit(BOARD_LED_GPIO, BOARD_LED_GPIO_PIN, &led_config);
GPIO_PortSet(GPIO3, 1u << PIN_PWMB);
GPIO_PortSet(GPIO3, 1u << PIN_PWMA);
// 切换全步进模式
setStepMode(0);
stepMultiple(10, 10); // 1步
stepMultiple(-10, 10); // 1步
// 开始寻找
startSearch();
while (1) {
//delay();
GPIO_PortToggle(BOARD_LED_GPIO, 1u << BOARD_LED_GPIO_PIN);
// ADC轮询模式
LPADC_DoSoftwareTrigger(DEMO_LPADC_BASE, 1U); /* 1U is trigger0 mask. */
while (!LPADC_GetConvResult(DEMO_LPADC_BASE, &mLpadcResultConfigStruct));
g_adcval = ((mLpadcResultConfigStruct.convValue) >> g_LpadcResultShift);
g_mapAdcVal = map(g_adcval, 0,56500,0,100);
PRINTF("ADC value: %d\r\n", g_mapAdcVal);
sprintf(buffer, "ADC: %d ", g_mapAdcVal);
Gui_DrawFont_GBK16(0, 60,WHITE,BLACK, buffer);
}
}
void startSearch(void) {
lpadc_conv_result_t mLpadcResultConfigStruct;
uint8_t buffer[32]={0};
// 初始化步数
g_stepcount = 0;
while (g_stepcount < 100) {
// ADC轮询模式
LPADC_DoSoftwareTrigger(DEMO_LPADC_BASE, 1U); /* 1U is trigger0 mask. */
while (!LPADC_GetConvResult(DEMO_LPADC_BASE, &mLpadcResultConfigStruct));
g_adcval = ((mLpadcResultConfigStruct.convValue) >> g_LpadcResultShift);
g_mapAdcVal = map(g_adcval, 0,56500,0,100);
PRINTF("ADC value: %d\r\n", g_mapAdcVal);
sprintf(buffer, "ADC: %4d", g_mapAdcVal);
Gui_DrawFont_GBK16(0, 60,WHITE,BLACK, buffer);
// 获取最大值
if (g_mapAdcVal > g_maxAdcMapVal) {
g_maxAdcMapVal = g_mapAdcVal;
g_maxAdcMapValStep = g_stepcount;
PRINTF("Steps of MaxVal : %4d\r\n", g_maxAdcMapValStep);
sprintf(buffer, "MaxSteps:%4d", g_maxAdcMapValStep);
Gui_DrawFont_GBK16(0, 100,WHITE,BLACK, buffer);
PRINTF("MaxVal : %4d\r\n", g_mapAdcVal);
sprintf(buffer, "MaxVal:%4d", g_mapAdcVal);
Gui_DrawFont_GBK16(0, 120,WHITE,BLACK, buffer);
}
if (g_mapAdcVal < (g_maxAdcMapVal-2)) {
break;
}
// 寻找模式下,使步进电机转动一步
stepMultiple(1, 5); // 1步
g_stepcount++;
PRINTF("All Steps: %4d\r\n", g_stepcount);
sprintf(buffer, "AllSteps:%4d", g_stepcount);
Gui_DrawFont_GBK16(0, 80,WHITE,BLACK, buffer);
}
PRINTF("Cur Steps: %4d\r\n", (g_stepcount - g_maxAdcMapValStep));
sprintf(buffer, "Back Steps:%4d", (g_stepcount - g_maxAdcMapValStep));
Gui_DrawFont_GBK16(0, 140,WHITE,BLACK, buffer);
stepMultiple(g_maxAdcMapValStep - g_stepcount ,5);
g_stepcount = g_stepcount - g_maxAdcMapValStep;
}
电机驱动部分代码:
/*
* motor.c
*
* Created on: 2026年1月1日
* Author: HP
*/
#include "motor.h"
#include "math.h"
extern void delay_ms(uint32_t ms);
// 步进电机步进序列(全步进)
const int stepSequence[4][4] = {
{1, 0, 0, 1}, // 步骤1: A+ B-
{1, 0, 1, 0}, // 步骤2: A+ B+
{0, 1, 1, 0}, // 步骤3: A- B+
{0, 1, 0, 1} // 步骤4: A- B-
};
// 半步序列(半步进,步距角减半)
const int halfStepSequence[8][4] = {
{1, 0, 0, 0}, // 步骤1: A+
{1, 0, 1, 0}, // 步骤2: A+ B+
{0, 0, 1, 0}, // 步骤3: B+
{0, 1, 1, 0}, // 步骤4: A- B+
{0, 1, 0, 0}, // 步骤5: A-
{0, 1, 0, 1}, // 步骤6: A- B-
{0, 0, 0, 1}, // 步骤7: B-
{1, 0, 0, 1} // 步骤8: A+ B-
};
int currentStep = 0;
int stepMode = 0; // 0: 全步进, 1: 半步进
// 正转
void Motor_Forward(motor_choice_t motor_t) {
if (motor_t == MotorA) {
GPIO_PortSet(GPIO3, 1u << PIN_INA1);
GPIO_PortClear(GPIO3, 1u << PIN_INA2);
} else {
GPIO_PortSet(GPIO3, 1u << PIN_INB2);
GPIO_PortClear(GPIO3, 1u << PIN_INB1);
}
}
// 反转
void Motor_Back(motor_choice_t motor_t) {
if (motor_t == MotorA) {
GPIO_PortSet(GPIO3, 1u << PIN_INA2);
GPIO_PortClear(GPIO3, 1u << PIN_INA1);
} else {
GPIO_PortSet(GPIO3, 1u << PIN_INB1);
GPIO_PortClear(GPIO3, 1u << PIN_INB2);
}
}
// 停止正转
void Motor_Stop(motor_choice_t motor_t) {
if (motor_t == MotorA) {
GPIO_PortClear(GPIO3, 1u << PIN_INA1);
GPIO_PortClear(GPIO3, 1u << PIN_INA2);
} else {
GPIO_PortClear(GPIO3, 1u << PIN_INB1);
GPIO_PortClear(GPIO3, 1u << PIN_INB2);
}
}
// 设置全步进/半步进模式
void setStepMode(int mode) {
stepMode = mode;
//PRINTF("\r\n步进模式: ");、5
//PRINTF(mode == 0 ? "全步进" : "半步进");
}
void setMotorStep(int a1, int a2, int b1, int b2) {
// 控制A相
GPIO_PinWrite(GPIO3, PIN_INA1, a1);
GPIO_PinWrite(GPIO3, PIN_INA2, a2);
// 控制B相
GPIO_PinWrite(GPIO3, PIN_INB1, b1);
GPIO_PinWrite(GPIO3, PIN_INB2, b2);
}
void step(int direction) {
if (stepMode == 0) {
// 全步进模式
currentStep += direction;
if (currentStep >= 4) currentStep = 0;
if (currentStep < 0) currentStep = 3;
setMotorStep(
stepSequence[currentStep][0],
stepSequence[currentStep][1],
stepSequence[currentStep][2],
stepSequence[currentStep][3]
);
} else {
// 半步进模式
currentStep += direction;
if (currentStep >= 8) currentStep = 0;
if (currentStep < 0) currentStep = 7;
setMotorStep(
halfStepSequence[currentStep][0],
halfStepSequence[currentStep][1],
halfStepSequence[currentStep][2],
halfStepSequence[currentStep][3]
);
}
}
// 步进多步
void stepMultiple(int steps, int delayMs) {
int direction = (steps > 0) ? 1 : -1;
steps = abs(steps);
for (int i = 0; i < steps; i++) {
step(direction);
delay_ms(delayMs);
}
}
// 停止电机转动
void stopStepMotor(void) {
setMotorStep(0, 0, 0, 0); // 停止所有电机
}4、开发中遇到的问题
在处理TFT显示屏的代码时,本来想使用SPI外设完成像显示屏外设发送数据,但结果不如人意。调试花了一些时间,担心会影响进度,索性改成了软件模拟的方式。另外为了方便连接,单独焊接了一块IO口扩展板,在测试中由于漏焊A0导致显示屏连接问题,总是显示不出来。而电机连接部分,也因为解除不到位,导致明明程序没问题,电机动作就是不正常。这些低级错误,说出来就很惭愧啊。实际上,任何连接完成后,一定要用万用表再三确认才行。
5、演示效果

我要赚赏金
