青蜂侠 · 2 天前

【Mini-F5375-OB开发板评测】DIY风速可调的桌面小风扇

一、引言

根据《DS_MM32F5370_V0.53_SC》数据手册可知,板载MCU集成多组定时器,各定时器特性有如下:
① – 1个16位超级定时器组(MindPWM),每个定时器组配有4个16位定时器,可产生8个独立的PWM输出或4对互补PWM输出,支持硬件死区插入,支持故障刹车,所有8个输出支持最高208ps高精度。
② – 2个16位4通道高级定时器(TIM1/TIM8),每个通道配有2个PWM输出,其中包括1路互补输出,并支持硬件死区插入和故障检测后的紧急刹车功能。
③ – 2个16位4通道通用定时器(TIM3/TIM4)和2个32位4通道通用定时器(TIM2/TIM5),每个通道配有1个PWM输出,并支持输入捕捉和输出比较,也可用于红外、霍尔传感器或者编码器信号的解码。
④ – 2个16位基础定时器(TIM6/TIM7)可用作通用定时和产生中断。
⑤ – 1个16位低功耗定时器(LPTIM1),可在除待机(Standby)模式以外的所有低功耗模式下唤醒处理器。
前面帖子有分享正常驱动TFT屏,今天来分享下使用Mini-F5375-OB开发板驱动直流电机,实现一个风速可调的桌面小风扇。

二、电机驱动模块

此次DIY借助L298N电机驱动模块,该模块应用比较广泛,不仅可以驱动直流电机,而且支持驱动42步进电机,下面介绍一下该驱动模块的使用。
L298N电机驱动模块的实物图及管脚分布如下图所示:
L298N模块.png
由此可知,该模块支持驱动两路直流电机,当然也可以用来驱动42步进电机。L298N的供电可选择12V或5V,根据电机的供电需求来选择。由于此次DIY用来驱动一个直流电机,因此只需要着重关注通道A使能管脚,即ENA;以及逻辑IN1与IN2逻辑输入管脚。另外我们要对直流电机进行PWM调速控制,则需要设置IN1和IN2高低电平,确定电机的转动方向,然后对使能端输出PWM脉冲,即可实现调速。注意当使能信号为0时,电机处于自动停止状态;当使能信号为1,且IN1和IN2为00或11时,电机处于制动状态,阻止电机转动。各管脚的逻辑关系列表如下图:
逻辑列表.png

三、硬件连线

硬件实物连线如下图所示:
硬件实物连接.jpg
电机是采用12V的,驱动风力够强。螺纹转轴,加带磁风扇叶,拧紧螺丝。电机的两根线连接在“输出A”端子上,然后PB12连接IN1逻辑输入引脚,PB13连接IN2逻辑输入引脚。PA8做为TIM1_CH1复用引脚,是输出PWM信号脚,连接“通道A使能”引脚。
开发板上用到POT电位器模块,因此要将P5引脚通过跳线帽短接起来,这样才能正常使用旋转变阻器。
电位器模块原理图.png
关于LCD的硬件连线见上期的帖子,这里不再赘述,只不过是将BLK背光信号脚由原来的PA1更改成PC4,因为该脚与电位器PA1相冲突。见上面实物图。

四、驱动源码

在上期的工程中,增加驱动电机的PWM代码,用到电位器模块,则需增加ADC驱动代码。再将ADC电压值与PWM占空比进行转换,实时的值通过串口打印输出,并显示在LCD屏上。
ADC.c

/* Define to prevent recursive inclusion */
#define _ADC_C_

/* Files include */
#include <stdio.h>
#include "platform.h"
#include "ADC.h"

/*******************************************************************************
  * @brief
  * @note   none
  * @param  none
  * @retval none
  *******************************************************************************/
void ADC_Configure(void)
{
    ADC_InitTypeDef  ADC_InitStruct;
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    ADC_CalibrationConfig(ADC1, 0x1FE);

    ADC_StructInit(&ADC_InitStruct);
    ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStruct.ADC_Prescaler  = ADC_Prescaler_16;
    ADC_InitStruct.ADC_Mode       = ADC_Mode_Continue;
    ADC_InitStruct.ADC_DataAlign  = ADC_DataAlign_Right;
    ADC_Init(ADC1, &ADC_InitStruct);

    ADC_SampleTimeConfig(ADC1, ADC_Channel_0, ADC_SampleTime_240_5);
    ADC_SampleTimeConfig(ADC1, ADC_Channel_1, ADC_SampleTime_240_5);

    ADC_AnyChannelNumCfg(ADC1, 1);
    ADC_AnyChannelSelect(ADC1, ADC_AnyChannel_0, ADC_Channel_0);
    ADC_AnyChannelSelect(ADC1, ADC_AnyChannel_1, ADC_Channel_1);
    ADC_AnyChannelCmd(ADC1, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    /* PA0(ROT1) PA1(POT2) */
    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    ADC_Cmd(ADC1, ENABLE);
}

motor.c

/* Define to prevent recursive inclusion */
#define _MOTOR_C_

/* Files include */
#include <stdio.h>
#include "platform.h"
#include "motor.h"

/*************************************************************************
  * @brief
  * @note   none
  * @param  none
  * @retval none
  ***********************************************************************/
void Motor_Gpio_Configure(void)
{
    GPIO_InitTypeDef        GPIO_InitStruct;
    TIM_OCInitTypeDef       TIM_OCInitStruct;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;

    uint32_t TimerPeriod1 = 0, Channel1Pulse1 = 0;

    /* Compute the value to be set in ARR regiter to generate signal frequency at 100 Khz */
    TimerPeriod1 = TIM_GetTIMxClock(TIM1) / 100000;
    
    /* Compute CCR1 value to generate a duty cycle at 25% for channel 1 */
    Channel1Pulse1 = (uint32_t)250 * TimerPeriod1 / 1000;
   
    printf("\r\nT1:%d, %d ", TimerPeriod1, Channel1Pulse1);

    /* TIM1 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

    TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
    TIM_TimeBaseStruct.TIM_Prescaler         = 0;
    TIM_TimeBaseStruct.TIM_CounterMode       = TIM_CounterMode_Up;
    TIM_TimeBaseStruct.TIM_Period            = TimerPeriod1;
    TIM_TimeBaseStruct.TIM_ClockDivision     = TIM_CKD_Div1;
    TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStruct);

    TIM_OCStructInit(&TIM_OCInitStruct);
    TIM_OCInitStruct.TIM_OCMode       = TIM_OCMode_PWM1;
    TIM_OCInitStruct.TIM_OutputState  = TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_Pulse        = 0;
    TIM_OCInitStruct.TIM_OCPolarity   = TIM_OCPolarity_High;
    TIM_OCInitStruct.TIM_OCIdleState  = TIM_OCIdleState_Set;

    TIM_OCInitStruct.TIM_Pulse = Channel1Pulse1;;
    TIM_OC1Init(TIM1, &TIM_OCInitStruct);


    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
        
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_1);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_8;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    TIM_Cmd(TIM1, ENABLE);
    TIM_CtrlPWMOutputs(TIM1, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_12| GPIO_Pin_13;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
        
    /*Configure GPIO pin Output Level */
    GPIO_WriteBit(GPIOB, GPIO_Pin_12, Bit_SET);     // IN1
    GPIO_WriteBit(GPIOB, GPIO_Pin_13, Bit_RESET);   // IN2
}

由于用到串口打印,因此在platform.c中,将串口改成USART3,代码:

/***********************************************************************************************************************
  * @brief  Initialize console for printf
  * @note   none
  * @param  Baudrate : UART2 communication baudrate
  * @retval none
  *********************************************************************************************************************/
void PLATFORM_InitConsole(uint32_t Baudrate)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);

    USART_StructInit(&USART_InitStruct);
    USART_InitStruct.USART_BaudRate   = Baudrate;
    USART_InitStruct.USART_StopBits   = USART_StopBits_1;
    USART_InitStruct.USART_Parity     = USART_Parity_No;
    USART_InitStruct.USART_Mode       = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART3, &USART_InitStruct);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

    GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_7);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOC, &GPIO_InitStruct);

    USART_Cmd(USART3, ENABLE);
}

/***********************************************************************************************************************
  * @brief  redefine fputc function
  * @note   for printf
  * @param  ch
  * @param  f
  * @retval ch
  *********************************************************************************************************************/
int fputc(int ch, FILE *f)
{
    USART_SendData(USART3, (uint8_t)ch);

    while (RESET == USART_GetFlagStatus(USART3, USART_FLAG_TC))
    {
    }

    return (ch);
}

main.c

/* Define to prevent recursive inclusion */
#define _MAIN_C_

/* Files include */
#include "platform.h"
#include "gpio_led_toggle.h"
#include "main.h"
#include "lcd.h"
#include "lcd_init.h"
#include "pic.h"
#include <stdio.h>

/* Private typedef ****************************************************************************************************/
typedef struct 
{
    unsigned char Index[2];
    unsigned char Msk[32];
}typFNT_GB16; 

extern const typFNT_GB16 tfont16[];


/* Private functions **************************************************************************************************/
void Display_title(void)
{
    LCD_ShowIntNum(8,10,2025,4,BLUE,GREEN,16);
    LCD_ShowChinese(40,10,(uint8_t *)&tfont16[0],BLUE,GREEN,16,0); //一
    LCD_ShowChinese(56,10,(uint8_t *)&tfont16[1],BLUE,GREEN,16,0); //起
    LCD_ShowChinese(72,10,(uint8_t *)&tfont16[2],BLUE,GREEN,16,0); //开
    LCD_ShowChinese(88,10,(uint8_t *)&tfont16[3],BLUE,GREEN,16,0); //发
    LCD_ShowChinese(104,10,(uint8_t *)&tfont16[4],BLUE,GREEN,16,0); //灵
    LCD_ShowChinese(8,30,(uint8_t *)&tfont16[5],BLUE,GREEN,16,0); //动
    LCD_ShowChinese(24,30,(uint8_t *)&tfont16[6],BLUE,GREEN,16,0); //微
    LCD_ShowChinese(40,30,(uint8_t *)&tfont16[13],BLUE,GREEN,16,0); //开
    LCD_ShowChinese(56,30,(uint8_t *)&tfont16[14],BLUE,GREEN,16,0); //发
    LCD_ShowChinese(72,30,(uint8_t *)&tfont16[15],BLUE,GREEN,16,0); //板
    LCD_ShowString(8,50,(uint8_t *)"Mini-F5375-OB",RED,GREEN,16,0);
    LCD_ShowPicture(12,70,100,32,gImage_1);
    LCD_ShowString(43,140,(uint8_t *)"2025-06-25",BLUE,GREEN,16,0);
    PLATFORM_DelayMS(100);
}

void Change_PWMDuty(uint32_t Channel1Pulse1)
{
    GPIO_InitTypeDef        GPIO_InitStruct;
    TIM_OCInitTypeDef       TIM_OCInitStruct;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

    TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
    TIM_TimeBaseStruct.TIM_Prescaler         = 0;
    TIM_TimeBaseStruct.TIM_CounterMode       = TIM_CounterMode_Up;
    TIM_TimeBaseStruct.TIM_Period            = TIM_GetTIMxClock(TIM1) / 100000;
    TIM_TimeBaseStruct.TIM_ClockDivision     = TIM_CKD_Div1;
    TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStruct);

    TIM_OCStructInit(&TIM_OCInitStruct);
    TIM_OCInitStruct.TIM_OCMode       = TIM_OCMode_PWM1;
    TIM_OCInitStruct.TIM_OutputState  = TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_Pulse        = 0;
    TIM_OCInitStruct.TIM_OCPolarity   = TIM_OCPolarity_High;
    TIM_OCInitStruct.TIM_OCIdleState  = TIM_OCIdleState_Set;

    TIM_OCInitStruct.TIM_Pulse = Channel1Pulse1;;
    TIM_OC1Init(TIM1, &TIM_OCInitStruct);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
        
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_1);

    GPIO_StructInit(&GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_8;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    TIM_Cmd(TIM1, ENABLE);
    TIM_CtrlPWMOutputs(TIM1, ENABLE);
    }
/***********************************************************************************************************************
  * @brief  This function is main entrance
  * @note   main
  * @param  none
  * @retval none
  *********************************************************************************************************************/
int main(void)
{
    TIM_OCInitTypeDef  TIM_OCInitStruct;
    
    uint32_t  Channel1Pulse1 = 0;
    float RVxVoltage[2];
    
    PLATFORM_Init();
    GPIO_Configure();
    ADC_Configure();
    Motor_Gpio_Configure();
        
    LCD_Init();
    PLATFORM_DelayMS(200);
    LCD_Fill(0,0,LCD_W,LCD_H,BLACK);

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    
    while (1)
    {
       while (RESET == ADC_GetFlagStatus(ADC1, ADC_FLAG_EOS))
       {
       }

     ADC_ClearFlag(ADC1, ADC_FLAG_EOS);

     RVxVoltage[0] = (float)ADC_GetAnyChannelConvertedValue(ADC1, ADC_AnyChannel_0) * (float)3.3 / (float)4096.0;
          
     Channel1Pulse1    = 1000 * RVxVoltage[0] / (float)3.3;

     Change_PWMDuty(Channel1Pulse1);
     PLATFORM_DelayMS(50);
     printf("\r\nPOT1 Voltage = %0.2fV  \tChannel1Pulse1 = %d\n", RVxVoltage[0],Channel1Pulse1);    
                
     Display_title();
     LCD_ShowString(5,110,(uint8_t *)"PWM Duty:",RED,BLACK,16,0);
     LCD_ShowIntNum(80,110,Channel1Pulse1/10 ,2,RED,BLACK,16);
     LCD_ShowString(100,110,(uint8_t *)"%",RED,BLACK,16,0);
    }
}

说明:根据上述代码可知,驱动了POT两路电位器,实际应用中只用到一路电位器。接口USB-DBG连接串口3,因此需要配置、使能串口3,方便实时打印输出相应信息。

五、效果演示

使用螺丝刀将板上“1”处的旋钮电位器,逆时针旋转,采样的电压值增加,则PWM占空比增大,此时电机转速加快;顺时针旋转,采样的电压值减少。则PWM占空比减少,此时电机转速减慢。程序下载完成后,逆时针旋转电位器1,串口同步打印输出log。
现象说明:由于电机附带的风扇叶质量比较重,因此当调节占空比至35%以上才能正常启动风扇吹风,这属正常情况。
操作演示效果见B站:
https://www.bilibili.com/vide...

推荐阅读
关注数
700
内容数
10
搭载安谋科技自研“星辰”STAR-MC1处理器,基于Armv8-M架构,专为电机控制、数字能源等实时控制应用打造
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息