本例要实现的电机PWM调速系统系统,基于前面的几个帖子。
分别涉及到PWM 输出、按键中断、LCD液晶。
PWM 输出 https://forum.eepw.com.cn/thread/401543/1
按键中断 https://forum.eepw.com.cn/thread/401475/1
LCD液晶 https://forum.eepw.com.cn/thread/401332/1
硬件部分,使用了基于TB6612的直流电机驱动板。
TB6612FNG
是东芝半导体公司生产的一款直流电机驱动器件,它具有大电流 MOSFET-H 桥结构,双通道电路输出,可同时驱动 2 个电机。可以实现控制电机的正反转,和PWM调速。
电路大概如下图所示,使用一个编码电机,可以通过输出对应的矩形波来测速(本例接逻辑分析仪分析波形)。
模块的14 、15脚用于控制电机正反转,本例直接将15脚接正极。
PWM接FRDM-MCXA366的J1-15(P3_12)

实物图如下:

接下来是软件部分
第一部分PWM
在原有的基础上增加了 更新PWM波的占空比 函数
CTIMER_UpdatePwmDutycycle(CTIMER,CTIMER_MAT_PWM_PERIOD_CHANNEL,CTIMER_MAT_OUT,duty_set);
其中最关键的变量就是duty_set,是0~100的整数。

第二部分 按键中断
在第一部分的基础上,增加SW2(调高PWM占空比)和SW3(调低PWM占空比)功能
由于前面提到的 MCUXpresso IDE v25.6.136还不支持MCXA366的图形化硬件配置。
详见:https://forum.eepw.com.cn/thread/401424/1
那就只好参考SDK手搓硬件配置了。
以SW2(P1_7)为例
首先初始化IO并配置中断:打开PORT1和GPIO1的时钟→开启复位后释放→功能配置为GPIO→方向设置为输入→输入缓冲打开→配置下降沿中断
这里最容易忽略的就是【输入缓冲打开】
pin_mux.c
//SW2 P1_7配置输入&中断
/* PORT1: Peripheral clock is enabled */
CLOCK_EnableClock(kCLOCK_GatePORT1);
CLOCK_EnableClock(kCLOCK_GateGPIO1);
/* PORT1 peripheral is released from reset */
RESET_ReleasePeripheralReset(kPORT1_RST_SHIFT_RSTn);
// GPIO也需要释放复位!!
RESET_ReleasePeripheralReset(kGPIO1_RST_SHIFT_RSTn);
/* PORT is configured as GPIO */
PORT_SetPinMux(PORT1, 7U, kPORT_MuxAlt0);
gpio_pin_config_t SW2_config = {
.pinDirection = kGPIO_DigitalInput,
};
/* Initialize GPIO functionality */
GPIO_PinInit(GPIO1, 7U, &SW2_config);
//输入缓冲打开
PORT1->PCR[7] = ((PORT1->PCR[7] &
/* Mask bits to zero which are setting */
(~(PORT_PCR_IBE_MASK)))
/* Input Buffer Enable: Enables. */
| PORT_PCR_IBE(0x01u));
/* Interrupt configuration : Interrupt on falling edge */
GPIO_SetPinInterruptConfig(GPIO1, 7U, kGPIO_InterruptFallingEdge);其次,开启中断
peripherals.c
/* Interrupt vector GPIO0_IRQn priority settings in the NVIC. */ NVIC_SetPriority(INT_0_IRQN, INT_0_IRQ_PRIORITY); /* Enable interrupt INT_0_IRQN request in the NVIC */ EnableIRQ(INT_0_IRQN);
最后,在中断函数中,写入对应功能:加减PWM占空比
main.c
/* GPIO1_IRQn interrupt handler */
void INT_1_IRQHANDLER(void) {
/* Place your code here */
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F
Store immediate overlapping exception return operation might vector to incorrect interrupt. */
uint32_t flag1 = GPIO_GpioGetInterruptFlags(GPIO1); //GPIO_PinGetInterruptFlag(GPIO_Type *base, uint32_t pin);
if (flag1 & (1u << 7))
{
PRINTF("SW2 INT TRIGGER!rn");
duty_set += 5;
if(duty_set>95)duty_set=95;
GPIO_PinClearInterruptFlag(GPIO1,7);
}
}SW3(P0_6)配置方法同样如此。
我们在main.c中的while(1)函数中,增加每200ms更新PWM占空比的函数,就可以实现按键中断调速了。
SDK_DelayMs(200); //延时
CTIMER_UpdatePwmDutycycle(CTIMER,CTIMER_MAT_PWM_PERIOD_CHANNEL,CTIMER_MAT_OUT,duty_set);
PRINTF("Duty is %drn",duty_set);
第三部分 液晶显示
液晶显示部分,直接用官方函数,不细究代码了。
关键部分是将PWM占空比通过处理,显示在液晶屏上。
SLCD_Engine_Show_Num(&slcdEngine, duty_set/10 ,1, 1); SLCD_Engine_Show_Num(&slcdEngine, duty_set%10 ,0, 1);
占空比95%时,编码器频率为1.2KHZ

占空比10%时,编码器频率为74HZ

最后是本项目的视频展示:
完整main.c代码
/*
* Copyright (c) 2015, Freescale Semiconductor, Inc.
* Copyright 2016-2019,2025 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "board.h"
#include "fsl_common.h"
#include "app.h"
#include "fsl_slcd.h"
#include "slcd_engine.h"
#include "fsl_debug_console.h"
#include "fsl_ctimer.h"
#include "peripherals.h"
/*******************************************************************************
* Definitions
******************************************************************************/
#define SDK_DelayMs(ms) SDK_DelayAtLeastUs((ms)*1000U, 100000000U)
volatile uint8_t duty_set = 50; //中断和main循环共享的全局变量,必须加volatile
/*******************************************************************************
* 宏定义
******************************************************************************/
#ifndef CTIMER_MAT_PWM_PERIOD_CHANNEL
// PWM周期使用的CTimer匹配通道
#define CTIMER_MAT_PWM_PERIOD_CHANNEL kCTIMER_Match_3
#endif
/*******************************************************************************
* 函数声明
******************************************************************************/
/*******************************************************************************
* 全局变量
******************************************************************************/
volatile uint32_t g_pwmPeriod = 0U; // PWM周期对应的匹配计数值
volatile uint32_t g_pulsePeriod = 0U; // PWM高/低电平对应的匹配计数值
/*******************************************************************************
* 代码实现
******************************************************************************/
/*!
* @brief 计算PWM周期与脉冲宽度对应的CTimer匹配值
* @param pwmFreqHz PWM输出频率,单位Hz
* @param dutyCyclePercent 占空比百分比 0~100
* @param timerClock_Hz CTimer定时器时钟频率,单位Hz
* @return 函数执行状态,kStatus_Success表示成功
*/
status_t CTIMER_GetPwmPeriodValue(uint32_t pwmFreqHz, uint8_t dutyCyclePercent, uint32_t timerClock_Hz)
{
/* 计算PWM周期匹配值:定时器计数到该值时完成一个PWM周期 */
g_pwmPeriod = (timerClock_Hz / pwmFreqHz) - 1U;
/*
* 计算脉冲宽度匹配值
* 公式:总周期计数 * (100 - 占空比) /100
* 注:此处逻辑为匹配到时输出翻转,代表低电平时间,可根据硬件输出极性调整理解
*/
g_pulsePeriod = (g_pwmPeriod + 1U) * (100 - dutyCyclePercent) / 100;
return kStatus_Success;
}
/*******************************************************************************
* Prototypes
******************************************************************************/
/*!
* @brief SLCD time delay.
* @param ms Milli-second
*/
static void SLCD_TimeDelay(uint32_t ms);
/* Demonstrate how to show number. */
static void SLCD_Show_Digital(void);
/* Demonstrate how to show icon. */
static void SLCD_Show_Icon(void);
#if SLCD_PANEL_SUPPORT_LETTER
/* Demonstrate how to show letter. */
static void SLCD_Show_Letter(void);
#endif
/* Demonstrate the blink feature. */
static void SLCD_Blink(void);
/*******************************************************************************
* Variables
******************************************************************************/
tSLCD_Engine slcdEngine;
/*******************************************************************************
* Code
******************************************************************************/
static void SLCD_SetLCDPin(lcd_set_type_t type, uint32_t lcd_pin, uint8_t pin_val, int32_t on)
{
assert(lcd_pin > 0);
uint8_t gpio_pin = 0;
uint8_t bit_val = 0;
uint8_t i = 0;
/* lcd _pin starts from 1. */
gpio_pin = slcd_lcd_gpio_seg_pin[lcd_pin - 1];
if (type == SLCD_Set_Num)
{
SLCD_SetFrontPlaneSegments(LCD, gpio_pin, (on ? pin_val : 0));
}
else
{
for (i = 0; i < 8; ++i)
{
bit_val = (uint8_t)(pin_val >> i) & 0x1U;
if (bit_val)
{
SLCD_SetFrontPlaneOnePhase(LCD, gpio_pin, (slcd_phase_index_t)i, on);
}
}
}
}
static void SLCD_Show_Digital(void)
{
uint8_t digital;
int32_t position;
PRINTF("\r\nShow digital numbers\r\n");
for (digital = 0; digital < 10; digital++)
{
for (position = 0; position < NUM_POSEND; position++)
{
SLCD_Engine_Show_Num(&slcdEngine, digital, position, 1);
}
SLCD_TimeDelay(500);
for (position = 0; position < NUM_POSEND; position++)
{
SLCD_Engine_Show_Num(&slcdEngine, digital, position, 0);
}
}
PRINTF("\r\nShow digital numbers finished\r\n");
}
#if SLCD_PANEL_SUPPORT_LETTER
static void SLCD_Show_Letter(void)
{
int8_t letter;
int8_t position;
PRINTF("\r\nShow letter\r\n");
for (letter = 'A'; letter <= 'z'; letter++)
{
for (position = 0; position < NUM_POSEND; position++)
{
SLCD_Engine_Show_Letter(&slcdEngine, letter, position, 1);
}
SLCD_TimeDelay(500);
for (position = 0; position < NUM_POSEND; position++)
{
SLCD_Engine_Show_Letter(&slcdEngine, letter, position, 0);
}
if (letter == 'Z')
{
letter = 'a';
}
}
PRINTF("\r\nShow letter finished\r\n");
}
#endif
static void SLCD_Show_Icon(void)
{
int32_t icon;
PRINTF("\r\nTurn on and off the icons one by one\r\n");
for (icon = 0; icon < ICON_END; icon++)
{
SLCD_Engine_Show_Icon(&slcdEngine, icon, 1);
SLCD_TimeDelay(500);
SLCD_Engine_Show_Icon(&slcdEngine, icon, 0);
}
PRINTF("\r\nShow icons finished\r\n");
}
static void SLCD_Blink(void)
{
int32_t icon;
for (icon = 0; icon < ICON_END; icon++)
{
SLCD_Engine_Show_Icon(&slcdEngine, icon, 1);
}
PRINTF("\r\nSLCD Displays All Segments.\r\n");
SLCD_TimeDelay(2000);
PRINTF("\r\nSLCD Starts Blink Mode.\r\n");
/* Blink mode Display. */
SLCD_StartBlinkMode(LCD, kSLCD_BlankDisplayBlink, kSLCD_BlinkRate01);
SLCD_TimeDelay(4000);
PRINTF("\r\nSLCD Stops Blink Mode.\r\n");
/* Stops SLCD blink display mode. */
SLCD_StopBlinkMode(LCD);
SLCD_TimeDelay(2000);
}
static void SLCD_TimeDelay(uint32_t ms)
{
SDK_DelayAtLeastUs(1000 * ms, SystemCoreClock);
}
int main(void)
{
slcd_config_t config;
ctimer_config_t config1; // CTimer配置结构体
uint32_t srcClock_Hz; // CTimer源时钟频率(Hz)
uint32_t timerClock; // 经过预分频后的定时器工作时钟(Hz)
/* Hardware initialize. */
BOARD_InitHardware();
BOARD_InitBootPeripherals(); //开启中断
PRINTF("MCUX SDK version: %s\r\n", MCUXSDK_VERSION_FULL_STR);
PRINTF("\r\nSLCD Example Starts.\r\n");
/* CTimer0计数器使用AHB时钟 */
srcClock_Hz = CTIMER_CLK_FREQ;
/* 获取CTimer默认配置参数 */
CTIMER_GetDefaultConfig(&config1);
/* 计算预分频之后的定时器时钟频率 */
timerClock = srcClock_Hz / (config1.prescale + 1);
/* 初始化CTimer外设 */
CTIMER_Init(CTIMER, &config1);
/* 获取10KHz、指定占空比的PWM周期与脉冲匹配计数值 */
CTIMER_GetPwmPeriodValue(10000, (uint8_t)DUTY_CYCLE, timerClock);
/* 配置CTimer生成PWM:周期通道、输出通道、周期计数值、脉冲计数值,不开启中断 */
CTIMER_SetupPwmPeriod(CTIMER, CTIMER_MAT_PWM_PERIOD_CHANNEL, CTIMER_MAT_OUT, g_pwmPeriod, g_pulsePeriod, false);
/* 启动CTimer定时器开始计数,输出PWM波形 */
CTIMER_StartTimer(CTIMER);
/* SLCD get default configure. */
/*
* config.displayMode = kSLCD_NormalMode;
* config.frameFreqIntEnable = false;
* config.faultConfig = NULL;
*/
SLCD_GetDefaultConfig(&config);
/* Verify and Complete the configuration structure. */
#if !(defined(FSL_FEATURE_SLCD_LP_CONTROL) && FSL_FEATURE_SLCD_LP_CONTROL)
/*
* config.powerSupply = kSLCD_InternalVll3UseChargePump;
* config.voltageTrim = kSLCD_RegulatedVolatgeTrim08;
* config.lowPowerBehavior = kSLCD_EnabledInWaitStop;
*/
config.clkConfig = &slcdClkConfig;
config.loadAdjust = kSLCD_HighLoadOrSlowestClkSrc;
#endif
config.dutyCycle = APP_SLCD_DUTY_CYCLE;
config.slcdLowPinEnabled = APP_SLCD_LOW_PIN_ENABLED;
config.slcdHighPinEnabled = APP_SLCD_HIGH_PIN_ENABLED;
config.backPlaneLowPin = APP_SLCD_BACK_PANEL_LOW_PIN;
config.backPlaneHighPin = APP_SLCD_BACK_PANEL_HIGH_PIN;
config.faultConfig = NULL;
/* SLCD Initialize. */
SLCD_Init(LCD, &config);
BOARD_SetSlcdBackPlanePhase();
memset(&slcdEngine, 0, sizeof(tSLCD_Engine));
SLCD_Engine_Init(&slcdEngine, SLCD_SetLCDPin);
/* Starts SLCD display. */
SLCD_StartDisplay(LCD);
// SLCD_Engine_Show_Letter(&slcdEngine, 'd' ,5, 1);
// SLCD_Engine_Show_Letter(&slcdEngine, 'u' ,4, 1);
// SLCD_Engine_Show_Letter(&slcdEngine, 't' ,3, 1);
// SLCD_Engine_Show_Letter(&slcdEngine, 'y' ,2, 1);
//SLCD_Show_Digital();
#if SLCD_PANEL_SUPPORT_LETTER
//SLCD_Show_Letter();
#endif
//SLCD_Show_Icon();
//SLCD_Blink();
PRINTF("\r\nSLCD Stops Display.\r\n");
/* Stops SLCD display. */
//SLCD_StopDisplay(LCD);
PRINTF("\r\nSLCD Example Ends.\r\n");
while (1)
{
SDK_DelayMs(200); //延时500ms
CTIMER_UpdatePwmDutycycle(CTIMER,CTIMER_MAT_PWM_PERIOD_CHANNEL,CTIMER_MAT_OUT,duty_set);
PRINTF("Duty is %d\r\n",duty_set);
SLCD_Engine_Show_Num(&slcdEngine, duty_set/10 ,1, 1);
SLCD_Engine_Show_Num(&slcdEngine, duty_set%10 ,0, 1);
}
}
/* GPIO0_IRQn interrupt handler */
void INT_0_IRQHANDLER(void) {
/* Place your code here */
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F
Store immediate overlapping exception return operation might vector to incorrect interrupt. */
uint32_t flag0 = GPIO_GpioGetInterruptFlags(GPIO0); //GPIO_PinGetInterruptFlag(GPIO_Type *base, uint32_t pin);
if (flag0 & (1u << 6))
{
PRINTF("SW3 INT TRIGGER!\r\n");
duty_set -= 5;
if(duty_set<0)duty_set=0;
GPIO_PinClearInterruptFlag(GPIO0,6);
}
}
/* GPIO1_IRQn interrupt handler */
void INT_1_IRQHANDLER(void) {
/* Place your code here */
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F
Store immediate overlapping exception return operation might vector to incorrect interrupt. */
uint32_t flag1 = GPIO_GpioGetInterruptFlags(GPIO1); //GPIO_PinGetInterruptFlag(GPIO_Type *base, uint32_t pin);
if (flag1 & (1u << 7))
{
PRINTF("SW2 INT TRIGGER!\r\n");
duty_set += 5;
if(duty_set>95)duty_set=95;
GPIO_PinClearInterruptFlag(GPIO1,7);
}
}
我要赚赏金
