开篇介绍
Gitee仓库:https://gitee.com/baby-sea/verimotor.git
电子DIY中经常会使用到电机,电机由许多类,比如
- 直流电机
- 交流电机
- 步进电机
- 伺服电机(舵机)。
我们在做一些DIY项目时最常使用的就是直流电机。但是在使用开发板控制直流电机的过程中经常会遇到许多问题:
- 使用单片机IO直接连接电机来作为电机的供电端,导致电机不能正常工作。这是因为芯片本身的IO驱动能力有限,就比如说CH32V307这款芯片,翻开它的数据手册,找到4.2节绝对最大值可以看到这个芯片的IO的供电电压在-0.3-4.0V,供电电流在0-25mA。所以对于一些电机来说是不能够驱动的。(CH32V307数据手册:https://www.wch.cn/downloads/CH32V307DS0_PDF.html)。
- 使用IO连接电机后用手快速的旋转电机,单片机被烧毁。这个现象往往发生在有减速组的电机上。当用手旋转电机,而恰好减速组末端的转速很快,此时电机相当于发电机,快速旋转的电机会产生较大的反电动势,反电动势作用到芯片的管脚上,如果电压过高就会烧毁芯片。
- 有点时候会我们常常会粗心大意,会把控制电机的电源直接接在开发板上导致开发板烧坏。
为了在用开发板来控制电机时,对开发板有一定的保护,
针对第一个问题:驱动板需要提高IO的驱动能力,常见的驱动电路是通过MOS组成桥式电路。桥式电路,通常指的是H桥电路(H-Bridge Circuit),是一种可以控制电机正反转和制动的电路结构。市面上针对这个应用场景开发了许多内置桥式电路的电机驱动芯片,我们只需要控制电机驱动芯片进而控制电机的运动。
针对第二个问题:市面上的电机驱动芯片的耐压通常设计会高过芯片正常工作范围,所以只需要选择适合电机电压的驱动芯片。
针对第三个问题:需要在驱动板上设计不同于开发板上常见的排针排母的接口防止插错,也就是防呆设计。
由此设计了一款由CJDR8838作为驱动芯片的电机驱动板,首先我们来看看这个芯片的数据手册。(CJDR8838数据手册:https://www.jscj-elec.com/s.html?q=CJDR8838&__searchtoken__=3ecedc7c37faee42a4a6e9932db34dbb)
芯片选型
通过数据手册开篇的介绍可以看到CJDR8838是一款专为直流电机驱动设计的集成电路。
它具备以下特点:
- 输入电压:支持12V直流电源输入。
- 输出电流:能够提供高达1.5A的连续输出电流,适合驱动小型至中型直流电机。
- 低功耗:在睡眠模式下,功耗电流小于1微安(μA),有助于延长电池寿命。
- 保护功能:
热关断保护:当IC温度超过预设值时,自动降低功率或关闭输出以防止过热。
欠压保护:当电源电压低于设定阈值时,IC将关闭输出以保护电路。
输出短路保护:检测到输出短路时,IC将关闭输出以防止损坏。
过流保护:当输出电流超过设定值时,IC将采取措施保护电路。
- PWM输入接口:具备PWM(PH/EN)输入接口,允许通过脉宽调制信号控制电机的速度和方向,与工业标准设备兼容。
然后接着往下翻看到第6节可以看到该芯片的绝对最大额定值和推荐操作条件。在日常使用中,需要按照以下标准来设计电路。
推荐操作条件:
- 负载功率输入电压范围 (Load power input voltage range, Vm): 推荐的操作电压范围是0到12伏特。
- 逻辑电源输入电压范围 (Logic power input voltage range, Vcc): 推荐的操作电压范围是2.0到7.0伏特。
- 连续输出电流 (Continuous output current, Iout): 推荐的操作电流是0到1.5安培。
- 外部PWM频率 (External PWM frequency, fpwm): 推荐的操作频率是0到400千赫兹。
但有时在某些偶发的极端情况会超过上表出现的范围,这时候就需要参考一下绝对最大额定值了。
绝对最大额定值
- 负载功率输入电压范围 (Load power input voltage range, Vm): 设备能够接受的电压范围是0到16伏特。
- 逻辑电源输入电压范围 (Logic power input voltage range, Vcc): 设备的逻辑电源能够接受的电压范围是-0.3到7.0伏特。
- 逻辑输入电压范围 (Logic input voltage range, ViN): 设备逻辑输入能够接受的电压范围是-0.5到7.0伏特。
- 连续输出电流 (Continuous output current, Iout): 设备能够持续提供的电流是±1.5安培。
- 峰值输出电流 (Peak output current, Iout Max): 设备能够提供的峰值电流是±2.5安培。
紧接着翻到第7.2节可以看到该芯片的功能框图
可以发现该芯片是外部的信号直接连接到芯片的逻辑电路上,之后通过四个MOS管来组成桥式电路控制电机。但由于这颗芯片是没有做隔离保护的,是可能会发生故障烧毁开发板的。
最后再看看该芯片的典型应用电路,可以发现该款芯片仅需要两个10uF的电容就可以驱动电机了,电路设计十分简单,而且芯片小巧易于控制。
电路设计
新手在电路设计时,首先是要对需求进行分析,然后再根据分析结果寻找对应的芯片,参考芯片的数据手册来绘制所需的电路板。
下面就是这块板子的原理图,可以看到使用了ME6208A33M3G来对输入电压降压至3.3V为芯片供电,并且在输入口并联了SMF12A防止热插拔时产生的电压过充烧毁后续电路(在没接上这个TVS时,测试的时候烧了很多块板子)。在输出口处还增加了LED指示灯,方便观察芯片控制状态。(如何解决热插拔时的电压过冲的相关文档:https://www.ti.com.cn/cn/lit/an/zhcacn5/zhcacn5.pdf?ts=1719376125061&ref_url=https%253A%252F%252Fwww.bing.com%252F)。
各类开发板代码
硬件完成了紧接着就是使用开发板编写代码来控制电机。
Arduino
// 引脚命名
#define MOTOR_DIR_1 2
#define MOTOR_PWM_1 3
#define MOTOR_DIR_2 4
#define MOTOR_PWM_2 5
#define MOTOR_DIR_3 6
#define MOTOR_PWM_3 7
#define MOTOR_DIR_4 8
#define MOTOR_PWM_4 9
void setup() {
pinMode(MOTOR_DIR_1, OUTPUT); //设置DIR_1为输出模式
pinMode(MOTOR_PWM_1, OUTPUT); //设置PWM_1为输出模式
pinMode(MOTOR_DIR_2, OUTPUT); //设置DIR_2为输出模式
pinMode(MOTOR_PWM_2, OUTPUT); //设置PWM_2为输出模式
pinMode(MOTOR_DIR_3, OUTPUT); //设置DIR_3为输出模式
pinMode(MOTOR_PWM_3, OUTPUT); //设置PWM_3为输出模式
pinMode(MOTOR_DIR_4, OUTPUT); //设置DIR_4为输出模式
pinMode(MOTOR_PWM_4, OUTPUT); //设置PWM_4为输出模式
analogWrite(MOTOR_PWM_1, 50); //输出PWM,占空比为 50/255
analogWrite(MOTOR_PWM_2, 100); //输出PWM,占空比为 100/255
analogWrite(MOTOR_PWM_3, 150); //输出PWM,占空比为 150/255
analogWrite(MOTOR_PWM_4, 200); //输出PWM,占空比为 200/255
}
void loop() {
digitalWrite(MOTOR_DIR_1, HIGH); //设置DIR_1为低电平
digitalWrite(MOTOR_DIR_2, LOW); //设置DIR_2为低电平
digitalWrite(MOTOR_DIR_3, HIGH); //设置DIR_3为高电平
digitalWrite(MOTOR_DIR_4, LOW); //设置DIR_4为高电平
delay(5000);
digitalWrite(MOTOR_DIR_1, LOW); //设置DIR_1为低电平
digitalWrite(MOTOR_DIR_2, HIGH); //设置DIR_2为低电平
digitalWrite(MOTOR_DIR_3, LOW); //设置DIR_3为高电平
digitalWrite(MOTOR_DIR_4, HIGH); //设置DIR_4为高电平
delay(5000);
}
树莓派
from gpiozero import PWMLED, LED
from time import sleep
motor_pwm_1 = PWMLED(2)
motor_dir_1 = LED(3)
motor_pwm_2 = PWMLED(14)
motor_dir_2 = LED(15)
motor_pwm_3 = PWMLED(17)
motor_dir_3 = LED(27)
motor_pwm_4 = PWMLED(23)
motor_dir_4 = LED(24)
motor_pwm_1.value = 0.5
motor_pwm_2.value = 0.5
motor_pwm_3.value = 0.5
motor_pwm_4.value = 0.5
while True:
motor_dir_1.on()
motor_dir_2.off()
motor_dir_3.on()
motor_dir_4.off()
sleep(5)
motor_dir_1.off()
motor_dir_2.on()
motor_dir_3.off()
motor_dir_4.on()
sleep(5)
沁恒CH32V203
#include "debug.h"
#define DIR1_PIN GPIO_Pin_2
#define DIR2_PIN GPIO_Pin_3
#define DIR3_PIN GPIO_Pin_4
#define DIR4_PIN GPIO_Pin_5
#define PWM1_PIN GPIO_Pin_8
#define PWM2_PIN GPIO_Pin_9
#define PWM3_PIN GPIO_Pin_10
#define PWM4_PIN GPIO_Pin_11
void DIR_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = DIR1_PIN | DIR2_PIN | DIR3_PIN | DIR4_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void PWM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // 使能TIM3时钟
GPIO_InitStructure.GPIO_Pin = PWM1_PIN | PWM2_PIN | PWM3_PIN | PWM4_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 定时器基本配置
TIM_TimeBaseStructure.TIM_Period = 999; // 定时器周期
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
// PWM模式配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 499; // PWM占空比,这里设置为50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_OC2Init(TIM1, &TIM_OCInitStructure);
TIM_OC3Init(TIM1, &TIM_OCInitStructure);
TIM_OC4Init(TIM1, &TIM_OCInitStructure);
TIM_CtrlPWMOutputs(TIM1, ENABLE );
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM1, ENABLE); // 使能ARR预装载寄存器
TIM_Cmd(TIM1, ENABLE); // 启动定时器
}
void Set_PWM_DutyCycle(uint16_t dutyCycle_1,uint16_t dutyCycle_2,uint16_t dutyCycle_3,uint16_t dutyCycle_4)
{
TIM_SetCompare1(TIM1,dutyCycle_1);
TIM_SetCompare2(TIM1,dutyCycle_2);
TIM_SetCompare3(TIM1,dutyCycle_3);
TIM_SetCompare4(TIM1,dutyCycle_4);
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n",SystemCoreClock);
printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
printf("This is printf example\r\n");
DIR_Init();
PWM_Init();
Set_PWM_DutyCycle(200, 400, 600, 800);
while(1)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2);
GPIO_ResetBits(GPIOA, GPIO_Pin_3);
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
Delay_Ms(2000);
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
GPIO_SetBits(GPIOA, GPIO_Pin_3);
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
GPIO_SetBits(GPIOA, GPIO_Pin_5);
Delay_Ms(2000);
}
}
鸭蛋
module pwm_4(
input clk, // 时钟信号
output reg [3:0] wave_PWM, // 4路PWM方波输出
output reg [3:0] wave_DIR // 4路DIR方波输出
);
// clk为24MHz
parameter CLOCK_FREQ = 24_000_000;
parameter WAVE_FREQ = 1_000;
// 计算计数器最大值
parameter MAX_COUNT = CLOCK_FREQ / ( WAVE_FREQ) - 1;//12000-1
parameter DIR_COUNT = 2000;
// 定义占空比
parameter DUTY_CYCLE_20 = (MAX_COUNT * 20) / 100;
parameter DUTY_CYCLE_40 = (MAX_COUNT * 40) / 100;
parameter DUTY_CYCLE_60 = (MAX_COUNT * 60) / 100;
parameter DUTY_CYCLE_80 = (MAX_COUNT * 80) / 100;
parameter DIR_50 = (DIR_COUNT * 50) / 100;
reg [15:0] counter = 0; // 计数器
reg [24:0] counter_1 = 0; // 计数器
always @(posedge clk) begin
if (counter < MAX_COUNT) begin
counter <= counter + 1;
end else begin
counter <= 0;
if (counter_1 < DIR_COUNT) begin
counter_1 <= counter_1+1;
end else begin
counter_1<=0;
end
end
// 生成四路方波
wave_PWM[0] <= (counter < DUTY_CYCLE_20) ? 1'b1 : 1'b0;
wave_PWM[1] <= (counter < DUTY_CYCLE_40) ? 1'b1 : 1'b0;
wave_PWM[2] <= (counter < DUTY_CYCLE_60) ? 1'b1 : 1'b0;
wave_PWM[3] <= (counter < DUTY_CYCLE_80) ? 1'b1 : 1'b0;
wave_DIR[0] <= (counter_1 < DIR_50) ? 1'b1 : 1'b0;
wave_DIR[1] <= (counter_1 < DIR_50) ? 1'b1 : 1'b0;
wave_DIR[2] <= (counter_1 < DIR_50) ? 1'b1 : 1'b0;
wave_DIR[3] <= (counter_1 < DIR_50) ? 1'b1 : 1'b0;
end
endmodule