目录
概要
基于STM32单片机实现智能的直流电机控制平台,支持四种不同模式。
整体架构流程
通过按键Key1,实现不同的模式切换,同时在OLED上显示;模式1,使用按键key2可以调节电机的速度,实现不同速度档位的切换;模式2,使用基于IIC协议的MUP6050获取加速度信号,根据位置信息调节电机速度信号;模式3,利用热敏传感器和光敏传感器,AD转换通道,实现电机速度随着光线亮暗程度自动调节;模式4,设置定时器,定时启动电机。
模式分类介绍
模式1,使用按键key2可以调节电机的速度,实现不同速度档位的切换
没有什么模块,很简单主要是Key的使用;那么有人问啦,怎么没有判断你输入的keyNum是什么呢?答,在while(1)循环里面一直在遍历KeyNum = Key_GetNum();//读取键码这句话啊啊
void Motor_Speed_Show1()//"MODE1:Key_CMD "
{
OLED_ShowString(4,1,"MODE:Key_CMD ");
if(KeyNum == 2)
{
Speed += 20;
if (Speed > 100)
{
Speed = -100;
}
}
Motor_SetSpeed(Speed);
OLED_ShowSignedNum(2, 7, Speed, 3);
}
模式2,使用基于IIC协议的MUP6050获取加速度信号,根据位置信息调节电机速度信号;
这里使用的模块:MPU6050 使用IIC通信协议获得MPU6050的姿态信息
void Motor_Speed_Show2()// MODE2:MPU6050_CMD"
{
OLED_ShowString(4,1,"MODE:MPU6050_CMD");
// 正向旋转
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
if (AX > 0 & AX < 500)
{
Speed = 20;
}
else if(AX > 501 & AX < 1200)
{
Speed = 40;
}
else if(AX > 1201 & AX < 2140)
{
Speed = 60;
}
// 反向旋转
else if(AX < -1201 & AX > -2140)
{
Speed = -60;
}
else if(AX < -501 & AX > -1200)
{
Speed = -40;
}
else if(AX > -500 & AX < 0)
{
Speed = -20;
}
Motor_SetSpeed(Speed);
OLED_ShowSignedNum(2, 7, Speed, 3);
}
模式3,利用热敏传感器和光敏传感器,AD转换通道,实现电机速度随着光线亮暗程度和温度自动调节;
void Motor_Speed_Show3()// MODE3:AD_CMD"
{
AD0 = AD_GetValue(ADC_Channel_0);
AD1 = AD_GetValue(ADC_Channel_1);
OLED_ShowString(3, 1, "AD0:");
OLED_ShowString(3, 9, "AD1:");
OLED_ShowNum(3, 5, AD0, 4);
OLED_ShowNum(3, 13, AD1, 4);
if(KeyNum == 3)
{
if(Show3_MODE == 1){Show3_MODE = 2;}
else if(Show3_MODE == 2){Show3_MODE =1;}
}
// Show3_MODE全局变量 已经初始化为1
if(Show3_MODE == 1)
{
OLED_ShowString(4,1,"MODE:AD_CMD_AD0 ");
if (AD0 > 1670 & AD0 < 1770){Speed = 20;}
else if(AD0 < 1670){Speed = 60;}// 热敏电阻
}
if(Show3_MODE == 2)
{
OLED_ShowString(4,1,"MODE:AD_CMD_AD1 ");
if (AD1 < 2100){Speed = 20;}
else if(AD1 < 2500){Speed = 60;}// 光敏电阻
}
Motor_SetSpeed(Speed);
OLED_ShowSignedNum(2, 7, Speed, 3);
}
模式4,设置定时器,定时启动电机。5s开动一次 每次转动5s
TIM3
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
// 启动时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_InternalClockConfig(TIM3);// TIM3的时基单元由内部时钟来驱动;
// 初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000-1;//ARR 自动重装载寄存器
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;//PSC 预分频器
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 高级定时器
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
// 清除中断中断标志位,避免刚初始化就进中断的问题
// TimeBase 单元生成了更新事件,触发了更新中断,此时更新了 中断标志位
TIM_ClearFlag(TIM3, TIM_FLAG_Update);
// 更新中断
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
// NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
//启动定时器
TIM_Cmd(TIM3,DISABLE);
}
void MyTIM3_CMDENABLE(void)
{
TIM_Cmd(TIM3,ENABLE);
}
void MyTIM3_CMDDISABLE(void)
{
TIM_Cmd(TIM3,DISABLE);
}
/*
void TIM2_IRQHandler(void)// 定时器2的中断函数
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)// 检查中断标志位
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除标志位
}
}
*/
void Motor_Speed_Show4()// MODE4:Timer3 _CMD"
{
OLED_ShowString(4,1,"MODE:Timer_CMD ");
MyTIM3_CMDENABLE();
if(MotorFlag)
{
Motor_SetSpeed(40);
OLED_ShowSignedNum(2, 7, 40, 3);
}
else
{
Motor_SetSpeed(0);
OLED_ShowSignedNum(2, 7, 0, 3);
}
}
int main()
{
.......
}
void TIM3_IRQHandler(void)// 定时器3的中断函数用于模式4
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET)// 检查中断标志位
{
static unsigned int T0count = 0;
T0count++;
if(T0count >= 5)
{
MotorFlag=!MotorFlag;//标志位取反
T0count= 0;
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);//清除标志位
}
}
}
PWM波形
(天下武功无快不破,呼吸灯)和电机调速,主要使用TIM2的OC通道 (输出比较 output compare)

定时器内部,我们知道,主要是时基单元,比较CNT(计数器)和ARR(重装计数器),CNT自增,CCR是我们给定的值,比较,输出一定频率和占空比的PWM波形


//使用TIM2的OC3通道
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC3Init(TIM2, &TIM_OCInitStructure);//TIM2的OC1通道借用那个GPIO呢?
TIM_Cmd(TIM2, ENABLE);
借用哪个GPIO呢?可以看到通道3借用的PA2

这是PWM模块 如何设置CCR的代码
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare);
}
-
电机介绍
- •直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
- •直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
- •TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

motor.c 实现正反转和速度切换
#include "stm32f10x.h" // Device header
#include "PWM.h"
#define Motor_IN1 GPIO_Pin_3
#define Motor_IN2 GPIO_Pin_5
void Motor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 控制电机方向的引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;// 推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
PWM_Init();
}
void Motor_SetSpeed(int8_t Speed)
{
if (Speed >= 0)
{
GPIO_SetBits(GPIOA, Motor_IN1);
GPIO_ResetBits(GPIOA, Motor_IN2);
PWM_SetCompare3(Speed);
}
else
{
GPIO_ResetBits(GPIOA, Motor_IN1);
GPIO_SetBits(GPIOA, Motor_IN2);
PWM_SetCompare3(-Speed);
}
}
遇到的问题
关于if语句的问题
一开始,我用的if语句进行每个语句的执行,发现这样是不行的
针对模式1,在while(1)循环里面,当你按下key1(KeyNum = 1),进入Motor_Speed_Show1();,在这个子函数里面,我们还需要键入Key2;这样问题就来了 ,在子函数里面无法进行key2的使用;主要原因是没有一个循环Show1()的功能,
可以这样想一想,我们平常见到的,都是这样的情况,都是在遍历在循环里面
int main(void)
{
Timer_Init();
while (1)
{
......
}
}
针对模式3,因为要不断地刷新Motor_Speed_Show3();才可以MPU6050实时的采集位置信息,从而控制电机的速度;传统的 if(KeyNum = 3){Motor_Speed_Show3();}是不可行的;如果使用,会导致如下的情况:你按下key3,进入Show3()执行,就执行一次,并不是在Show3()里面循环,而是又回到while(1)循环里面,判断你输入的KeyNum,你依旧输入KeyNum = 3,然后悄悄地改变MPU6050的位置,这时候进入Show3(),也是执行一次,但由于当前位置比上一次的位置
变化了,会导致速度的不同;但依旧执行一次,返回while(1)循环;
int main(void)
{
Timer_Init();
AD_Init();
Key_Init();
OLED_Init();
Motor_Init();
MPU6050_Init();
OLED_ShowString(1, 1, "ID:");
ID = MPU6050_GetID();
OLED_ShowHexNum(1, 4, ID, 2);
OLED_ShowString(2, 1, "SPEED:");
while (1)
{
if(KeyNum == 1)
{
Motor_Speed_Show1();
}
if(KeyNum == 2)
{
Motor_Speed_Show2();
}
if(KeyNum == 3)
{
Motor_Speed_Show3();
}
}
}
其实也可以这样,定义
全局遍历MODE,然后在if(KeyNum ==1){MODE = 1};
if(MODE = 1){Motor_Speed_Show1();}
int main(void)
{
Timer_Init();
while (1)
{
KeyNum = Key_GetNum();//读取键码
if(KeyNum == 1){MODE = 1;}
else if(KeyNum == 2){MODE = 2;}
if(MODE == 1)
{
Motor_Speed_Show1();
}
else if(MODE == 2)
{
Motor_Speed_Show2();
}
}
}
江科大STM32 可以使用swich case 这样写;因为有了MODE变量,注意这是一个全局变量,
我们知道,MODE默认为1,已经初始化;板子上电,KeyNum = 0,这是默认的,程序找了一圈没有找到key的入口,然后到了switch(MODE),-->switch(1)-->:Motor_Speed_Show1();在不按下任何key的时候,在while(1)循环里面不断地遍历switch(1)-->:Motor_Speed_Show1();按下key1的时候,MODE在变化 ,从而可以进入不同的switch case;
int main(void)
{
Timer_Init();
AD_Init();
Key_Init();
OLED_Init();
Motor_Init();
MPU6050_Init();
OLED_ShowString(1, 1, "ID:");
ID = MPU6050_GetID();
OLED_ShowHexNum(1, 4, ID, 2);
OLED_ShowString(2, 1, "SPEED:");
while (1)
{
MyTIM3_CMDDISABLE();
KeyNum = Key_GetNum();//读取键码
//OLED_ShowNum(2, 1, KeyNum,2);// 0
if(KeyNum == 1)
{
if(MODE == 1)
{
MODE = 2;
}
else if(MODE == 2)
{
MODE = 3;
}
else if(MODE == 3)
{
MODE = 4;
}
else if(MODE == 4)
{
MODE = 1;
}
}
switch(MODE)
{
case 1:Motor_Speed_Show1();break;// MODE1:Key_CMD
case 2:Motor_Speed_Show2();break;// MODE2:MPU6050_CMD
case 3:Motor_Speed_Show3();break;// MODE3:AD_CMD
case 4:Motor_Speed_Show4();break;// MODE2:MPU6050_CMD
}
}
}
关于定时器冲突的问题
这个问题太头疼啦 可以看到PWM模块里面,使用TIM2的OC(输出比较),然后呢 我在模式4里面涉及到了定时器,我当时也是直接使用的TIM2,计数器的值达到重装值,产生一个更新事件,触发更新中断;发现怎么也不对,事件对不上哈 后来发现在PWM模块里面,使用TIM2的OC(输出比较);然后我就使用TIM3,1s进一次中断 尽在模式4的时候进行中断使能

因此我还定义了失能和使能的函数

还有AD的转换通道;我使用的AD1的
然后可以看到AD12的通道入口PA0 PA1 
啊啊啊!!
小结
最近在学习STM32,根据江科大学习的,为巩固所学,做了一个具有四个模式调节的电机平台
按键的控制,
AD的转换 ,这里没有使用DMA转运,即简单AD单次非扫描模式,我只是每次改变不同的通道,
AD0 = AD_GetValue(ADC_Channel_0);
AD1 = AD_GetValue(ADC_Channel_1);
TIM2的输出比较通道 ,选择OC3,输出的GPIO固定选用PA2;然后这波形传递电机驱动电路
TIM3的定时器,每隔5s进行电机的启动
IIC通信协议,获取MPU6050的姿态信息,控制电机的速度
一定要学会找BUG,有的时候你在keill文件改完,它源文件代码并没有改变,然后爆出警告:定义不明确。。。。
还有接口的冲突问题;一开始我做的是PWM里面还有一个OC1通道,可以知道,借用的输出GPIO是PA0,然后AD1的通道1也在PA0,怎么搞也不行哈哈。
还有一个:
通用定时器都拥有4个输出比较通道,一个定时器可以初始化四个通道,然后在指定的GPIO输出,
类似这样
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;// 复用推挽输出;定时器控制引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC3Init(TIM2, &TIM_OCInitStructure); //TIM2的OC3通道借用那个GPIO呢? PA2
TIM_OC1Init(TIM2, &TIM_OCInitStructure);// TIM2的OC1通道借用那个GPIO呢? PA0
TIM_Cmd(TIM2, ENABLE);
}
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare);// 借用GPIOA_PIN2
}
void PWM_LED_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);// 借用GPIOA_PIN0
}
文章讲述了使用STM32开发的直流电机控制系统,包括不同模式的控制逻辑(如按键控制速度、IIC通信、AD传感器、定时器控制),遇到的问题与解决方案,如if语句和定时器冲突处理。
5717

被折叠的 条评论
为什么被折叠?



