想要从云端控制宠物自动喂食器,最重要的部分就是电机啦,电机的作用就是用于将装在食物储物槽里的食物倒出来供宠物食用,如下图所示,这是某宝常见的一款宠物喂食器产品,其实下图所示动力舱其实就是一个电机,用于控制出料。
涂鸦官方推荐此时需要用一个减速电机来完成这个产品的设计,然而我手上没有这个电机,所以我就用步进电机来代替了,一样也可以完成这个功能,那么如何来实现呢?先来看看实验效果:
我手上的这个是一个步进模块:
步进电机选用的型号是:28BYJ48-H12
这里在软件编程上有一个比较重要参数需要了解一下,就是步距角。那么什么是步距角呢?度娘给你答案,可以详细看看。
https://baike.baidu.com/item/%E6%AD%A5%E8%B7%9D%E8%A7%92/5946465?fr=aladdin
来看看下面这个换算公式,或许你就明白了。如上图所示,步距角=5.625°/64
,意思就是每64个脉冲步进电机就会转5.625度,因此我们很容易得出以下计算公式:电机转一圈有360°,那么转一圈的脉冲数 = 360 / 5.625 * 64 = 4096 个脉冲。进而很容易得到以下角度与脉冲的简单算法:
/* Rotation_Angle:旋转角度 返回:Motor_Pulse 根据公式计算得出的脉冲个数 */ int Motor_Angle_Cal(int Rotation_Angle) { if(Rotation_Angle < 0 || Rotation_Angle > 360) return -1 ; Motor_Pulse = (int)((double)(Rotation_Angle / 5.625) * 64) ; return Motor_Pulse ; }
下面开始在CubeMX上配置管脚,然后编写程序:
编写步进电机驱动程序:
motor.h
#ifndef __MOTOR_H #define __MOTOR_H #include "main.h" //4相控制定义 #define MOTOR_A_ON HAL_GPIO_WritePin(MOTOR_A_GPIO_Port, MOTOR_A_Pin, GPIO_PIN_SET); #define MOTOR_A_OFF HAL_GPIO_WritePin(MOTOR_A_GPIO_Port, MOTOR_A_Pin, GPIO_PIN_RESET); #define MOTOR_B_ON HAL_GPIO_WritePin(MOTOR_B_GPIO_Port, MOTOR_B_Pin, GPIO_PIN_SET); #define MOTOR_B_OFF HAL_GPIO_WritePin(MOTOR_B_GPIO_Port, MOTOR_B_Pin, GPIO_PIN_RESET); #define MOTOR_C_ON HAL_GPIO_WritePin(MOTOR_C_GPIO_Port, MOTOR_C_Pin, GPIO_PIN_SET); #define MOTOR_C_OFF HAL_GPIO_WritePin(MOTOR_C_GPIO_Port, MOTOR_C_Pin, GPIO_PIN_RESET); #define MOTOR_D_ON HAL_GPIO_WritePin(MOTOR_D_GPIO_Port, MOTOR_D_Pin, GPIO_PIN_SET); #define MOTOR_D_OFF HAL_GPIO_WritePin(MOTOR_D_GPIO_Port, MOTOR_D_Pin, GPIO_PIN_RESET); extern int direction ; extern uint16_t Motor_Pulse ; //电机脉冲计算 int Motor_Angle_Cal(int Rotation_Angle); //电机8节拍控制 void MOTOR_CONTROLD(uint8_t step, uint8_t direction); //关闭电机 void CLOSE_MOTOR(void); #endif //__MOTOR_H
motor.c
#include "Motor.h" //电机旋转的方向 int direction = 0 ; //电机旋转的脉冲个数 uint16_t Motor_Pulse = 0 ; //电机控制,采用8节拍来做 //A->AB->B->BC->C->CD->D->DA void MOTOR_CONTROLD(uint8_t step, uint8_t direction) { uint8_t __step = step ; //判断电机的旋转方向,如果为1,则逆向旋转 if(1 == direction) __step = 8 - step ; switch(__step) { //A case 0: MOTOR_A_ON; MOTOR_B_OFF; MOTOR_C_OFF; MOTOR_D_OFF; break ; //AB case 1: MOTOR_A_ON; MOTOR_B_ON; MOTOR_C_OFF; MOTOR_D_OFF; break ; //B case 2: MOTOR_A_OFF; MOTOR_B_ON; MOTOR_C_OFF; MOTOR_D_OFF; break ; //BC case 3: MOTOR_A_OFF; MOTOR_B_ON; MOTOR_C_ON; MOTOR_D_OFF; break ; //C case 4: MOTOR_A_OFF; MOTOR_B_OFF; MOTOR_C_ON; MOTOR_D_OFF; break ; //CD case 5: MOTOR_A_OFF; MOTOR_B_OFF; MOTOR_C_ON; MOTOR_D_ON; break ; //D case 6: MOTOR_A_OFF; MOTOR_B_OFF; MOTOR_C_OFF; MOTOR_D_ON; //DA case 7: MOTOR_A_ON; MOTOR_B_OFF; MOTOR_C_OFF; MOTOR_D_ON; break ; } } //关闭电机 void CLOSE_MOTOR(void) { MOTOR_A_OFF; MOTOR_B_OFF; MOTOR_C_OFF; MOTOR_D_OFF; } /* Rotation_Angle:旋转角度 返回:Motor_Pulse 根据公式计算得出的脉冲个数 */ int Motor_Angle_Cal(int Rotation_Angle) { if(Rotation_Angle < 0 || Rotation_Angle > 360) return -1 ; Motor_Pulse = (int)((double)(Rotation_Angle / 5.625) * 64) ; return Motor_Pulse ; }
编写主程序控制逻辑,在这里我们采用嘀嗒定时器来驱动步进电机:
我们在中断服务函数这个文件这里讲嘀嗒定期器产生的中断处理添加到这里,跳转进去发现这是个弱函数,也就是带__weak
关键字修饰的,这并不是标准C的语法,而是拓展的,并没有实现。加上了__weak
修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak
声明的函数,并且编译器不会报错。所以我们可以在别的地方定义一个相同名字的函数,而不必也尽量不要修改之前的函数。
接下来我们定义个跟这个弱函数一模一样的处理函数,实现它。
main.c
/* USER CODE BEGIN 4 */ void HAL_SYSTICK_Callback(void) { static uint8_t step = 0 ; //如果当前设置的脉冲数不为0 if(Motor_Pulse) { //控制步进电机旋转 MOTOR_CONTROLD(step, direction); step++ ; if(8 == step) step = 0 ; //脉冲计数不断减,当减到0时,说明控制到位,即需要执行else的关闭电机步骤。 Motor_Pulse-- ; } else { //关闭电机 CLOSE_MOTOR(); } }
然后在下行指令区调用电机转动函数即可:
效果如演示视频所示。