智能电机控制平台(STM32F103C8T6)

文章讲述了使用STM32开发的直流电机控制系统,包括不同模式的控制逻辑(如按键控制速度、IIC通信、AD传感器、定时器控制),遇到的问题与解决方案,如if语句和定时器冲突处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

概要

整体架构流程

模式分类介绍

PWM波形

电机介绍

遇到的问题

关于if语句的问题

关于定时器冲突的问题 

小结


概要

基于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(输出比较);然后我就使用TIM31s进一次中断 尽在模式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
	
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值