只简单介绍,更基础的原理请查阅立创梁山派基础课程
PWM(Pulse Width Modulation,脉宽调制)是一种在嵌入式系统中常用的技术,它可以用来模拟信号,控制设备的功率输出或者实现对设备的精确控制。PWM信号是一种类似于方波的信号,具有固定的频率,但脉冲宽度(占空比)可以调整。在一定频率下,我们可以通过调整这个占空比来改变他的有效电压,在一定程度上可以实现D/A转换(数字量转模拟量,不过一般来说都是用DAC,立创梁山派自带DAC)。
在嵌入式系统中,PWM的应用场景非常广泛,例如:
举个例子:假设我们有一个LED灯,想实现亮度调节。我们可以生成一个PWM信号,频率为1kHz(周期为1ms),然后通过调整占空比来实现LED灯的亮度调节。如果占空比为100%,那么LED灯将一直处于亮的状态;如果占空比为50%,那么LED灯将以1ms为周期,半亮半暗;如果占空比为0%,那么LED灯将一直处于熄灭状态。通过不断调整占空比,就可以实现LED灯的亮度调节。如果PWM信号的频率过低,我们可能会感觉到它在闪烁。所以,在设计PWM驱动的LED灯时,一般会选择较高的频率来避免可见的闪烁。通常我们是看不到它闪烁的,这主要是因为两个原因:第一个是PWM信号的频率够高,第二个是人眼的视觉暂留效应。即当光源瞬间消失时,我们还能在极短的时间内感知到光源。这种效应导致人眼对快速闪烁的光源产生平滑的感觉。当PWM频率足够高时,视觉暂留效应会使我们感觉到LED灯的亮度是连续的。
我们平时一直摸的手机屏幕有一部分就是用PWM来调节的。在智能手机屏幕中,PWM调光和DC调光是两种常见的屏幕亮度调节技术。它们的主要区别在于亮度调节的实现方式。
PWM调光:如之前所说,PWM(脉宽调制)调光是通过调整占空比来控制屏幕亮度的。在这种方法中,屏幕背光源会周期性地开启和关闭。占空比越高,背光源亮的时间越长,屏幕亮度越高;占空比越低,背光源亮的时间越短,屏幕亮度越低。
DC调光:DC(直流)调光是通过调整屏幕背光源的电流来实现亮度调节的。在这种方法中,背光源会一直保持开启状态,但电流的大小会改变,从而调整屏幕亮度。
这两种调光技术各有优缺点:
PWM调光优缺点:
DC调光优缺点:
各种技术的选择取决于手机制造商的考虑和市场需求。有一些高端手机是用的混合调光,在高亮度的模式下用DC调光,亮度低于一定值后用PWM调光。
做一个小车最基础的要求就是让他能动起来,至少先能前进后退。
一般来说,驱动直流减速电机的方式有以下几种,最省时省力还可靠的方式还是使用集成H桥驱动器的方式:
上面的电路是参考芯片的数据手册设计的,大家在设计原理图时第一参考永远是原厂的手册或原厂的工程。
一个AT8870可以驱动一个直流电机正反转,我们要驱动两个直流电机,所以这里要用两个驱动芯片。他的手册是全中文的,重点要看的参数主要是主供电要求,逻辑供电要求,逻辑输入要求,连续输出电流,峰值输出电流,最大PWM频率等。 了解上面以上参数后我们就可以选电机了,要确保电机的工作电流电压要在电机驱动芯片的工作范围以下,我选的是轮趣科技的MG513P20_12V ,这个电机减速比是20,工作电压是12V,额定电流是0.36A,堵转电流是3.2A。
我们所用的AT8870峰值输出电流是3.6A,那还是有可能在长时间堵转的情况下烧毁的,所以我们需要设置驱动芯片的保护,这款芯片的ISEN引脚可以设置芯片的电流控制电流(这个电阻的取值和参考电压以及要设置的电流有关)。详细介绍可以看AT8870的datasheet的第8页,有详细的介绍和公式。在上面原理图中我就选择0.15欧姆了。代表参考电压为3.3V时,目标电流为2.2A。
因为AT8870目前已经在立创买不到了,所以我们可以找个PINtoPIN的料,AT8236,查看手册可以看到,他的峰值输出电流是6A,为了保护电机和驱动芯片,设置了电流保护,这个芯片的ISEN引脚接入一个电阻就可以设置芯片的电流,当这个电流达到设置的阈值时,驱动器就会输出关断(这个电阻的取值和参考电压以及要设置的电流有关)。详细介绍可以看AT8236的datasheet的第6页,有详细的介绍和公式。在上面原理图中我就选择0.15欧姆了。代表参考电压为3.3V时,目标电流为2.2A。
3.3/(10*0.15)=2.2A ,实测这样配置电流就算电机运行在堵转,最大速度瞬间切换到最小速度,最大速度瞬间外力刹车等情况下均不会导致驱动芯片或电机损坏。
再有就是我们看一下手册中快衰减和慢衰减说的是什么,这里的快慢指的是电流,而不是电机转动的速度。
由于电机是感性负载(当通电时,线圈内会形成磁场。这个线圈的磁场会产生自感电动势,抵抗电流的改变。),当断开电机两端的电压时,电流会产生反向电动势。这可能会对驱动芯片造成损坏。因此,要让电机停下来,除了断开电源,还需要建立一个续流回路来释放电机中的能量。慢衰减相当于加在电机(感性原件)两端电压消失,将电机两端正负短接。快衰减相当于加在电机(感性原件)两端电压消失,将电机两端快速接上与驱动电流相反的电流。如果大家小时候鼓捣过四驱车的马达,就能发现如果把马达两端短接,再用手去转动这个马达就会明显感受到阻力,转的越快阻力越大。
在送药小车中,我们希望小车在停止时电机就立马停止而不是向前滑行一段,所以我这里在电机停止转动时就选择慢衰竭(刹车)模式。
近期发现立创商城上面AT8870停止进货了,那可以选一个更好的,AT8236(网页下面可以直接看手册),最大峰值驱动输出电流可达6A,连续输出驱动电流可达4A,还是PINtoPIN的,可以直接替换,实际焊接的时候直接替换就好了,这也就提醒了我们,设计时要尽量选用有替代方案的物料,不然做成产品出货时后有可能会被一个物料卡住脖子。
要想输出确定PWM频率的信号,首先要知道当前所用定时器的时钟频率,查看GD32F4xx用户手册中的图4-2.时钟树:
送药小车项目中,用的是外部时钟,APB1和APB2配置如下所示:
//截取至system_gd32f4xx.c
system_clock_240m_25m_hxtal();
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB/2 */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;
/* APB1 = AHB/4 */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV4;
可知AHB为240Mhz,定时器的时钟有两个路线,一路定时器的时钟从APB1过来,又因为上面这个TIMERSEL时钟源选择这个位,APB1是四分频,定时器的时钟要是APB时钟的两倍。所以APB1下时钟频率为120MHz,也就是TIMER1,2,3,4,5,6,11,12,13是120MHz。另一路定时器时钟从APB2过来,这些定时器也要乘以2,所以TIMER0,7,8,9,10时钟频率为240MHz。
代码中这里用的是定时器的输出PWM功能,选用EAPWM(边沿对齐PWM),EAPWM 的周期(频率)由 TIMERx_CAR 寄存器值决定,占空比由 TIMERx_CHxCV 寄存器值决定。
上图中的Cx OUT
就是对应的引脚输出的PWM波了,当CNT(计数器值)小于CHxVAL
时引脚输出高有效电平,当CNT(计数器值)大于CHxVAL
引脚就输出低有效电平。
该项目所用到的电机驱动芯片AT8870||AT8236最高可以接收100KHz的频率(频率越高驱动电机也就越顺滑)。所以配置PWM频率时不能超过100KHz。
首先我们需要知道当前所用定时器的时钟频率,在这个项目中,要驱动两个电机,每个电机驱动需要两路PWM值。电机驱动1用的是定时器8,结合上面的基础知识,他的时钟频率是240MHz。电机驱动2用的是定时器11。他的时钟频率是120MHz。分别都是用的相应定时器的通道1和通道2,详情看代码沃。
在下面的代码中,预分频(prescaler)和周期(period)都是从0开始计数的,所以一分频就是0,四分频是3,写成下面的形式只是为了表示更直观。定时器周期(period)是定时器计数的最大值,当计数器的值到达这个值时会重新开始计数。一般的电机驱动PWM频率20KHz就够用,这里我就都配置成60KHz了。
//定时器8时钟频率配置关键代码
timer_param_type.period = 1000-1; //60kHz
timer_param_type.prescaler = 4-1; //240Mhz/4=60MHz
//定时器11时钟频率配置关键代码
timer_param_type.period = 1000-1; //60kHz
timer_param_type.prescaler = 2-1; //120Mhz/2=60MHz
在下面的代码中,配置了定时器的通道0,关键的配置设置了ocpolarity = TIMER_OC_POLARITY_HIGH;
,timer_channel_output_pulse_value_config(M1_MOTOR_TIMER, TIMER_CH_0, 1000);
相当于把占空比设置为了100%。相当于把这个引脚拉高了。当电机驱动的两个引脚都为高电平时,会进入刹车模式,这时候转动车轮能感觉到明显的阻力。
timer_channel_output_struct_para_init(&timer_oc_init_struct);
timer_oc_init_struct.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_oc_init_struct.outputstate = TIMER_CCX_ENABLE;
timer_oc_init_struct.outputnstate = TIMER_CCXN_DISABLE;
timer_oc_init_struct.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
timer_oc_init_struct.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
timer_oc_init_struct.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
timer_channel_output_config(M1_MOTOR_TIMER, TIMER_CH_0, &timer_oc_init_struct);
timer_channel_output_pulse_value_config(M1_MOTOR_TIMER, TIMER_CH_0, 1000);
timer_channel_output_mode_config(M1_MOTOR_TIMER,TIMER_CH_0,TIMER_OC_MODE_PWM0);
timer_channel_output_shadow_config(M1_MOTOR_TIMER,TIMER_CH_0,TIMER_OC_SHADOW_DISABLE);
其他通道的配置也和这个一样。
详情请看bsp->bsp_motor.c
文件。
代码简要介绍:
motor_pwm_gpio_init()
:用于初始化电机驱动的PWM控制引脚。通过配置引脚的模式和复用功能,将引脚设置为PWM输出模式。motor_timer_init()
:用于初始化电机驱动的硬件定时器。该函数通过配置定时器的参数,包括时钟分频、计数方向、周期和占空比等,设置定时器为PWM模式。motor1_in1_pwm_pulse_set()
、motor1_in2_pwm_pulse_set()
、motor2_in1_pwm_pulse_set()
、motor2_in2_pwm_pulse_set()
:分别用于设置电机1和电机2的PWM脉宽值。这些函数通过配置定时器的通道和脉冲值,控制对应引脚的PWM输出。motor1_pwm_value_set()
、motor2_pwm_value_set()
:用于调节电机1和电机2的PWM值,即控制电机的速度。通过调整对应引脚的PWM脉宽值来实现速度调节。打开1_turn_the_motor
下的工程,编译并烧录到立创梁山派。
如果供电正常,此时两个电机就会开始转动。
结合原理图,可以知道我们电机1的PWM驱动在PA2和PA3上,我们把示波器的两个通道连接到这两个引脚上,接地也要接好。
CH1连接到了PA2,CH2连接到了PA3
通过测量可以知道他的频率约为60.002Khz,周期约为16.666us,满足我们之前计算出来的频率,这两个引脚都在高电平的时候驱动芯片处于刹车状态,当PA2变为低电平的时候驱动芯片就会给电机供电,不过这个频率足够高而且电机旋转也有惯性,所以实际运行中我们是感觉不到电机旋转时的顿挫感的。
再测量一下他的低电平占空比,测量可以知道占空比约为20.07%。结合程序中,我们设置的PWMCHxCV 寄存器为200,周期为1000。所以程序设置的占空比为20%,符合实际预期。
舵机(Servo Motor)是一种特殊的电机,它可以精确地控制旋转角度。舵机广泛应用于模型船、航空模型、遥控车和其他需要精确角度控制的领域。
舵机的工作原理主要基于脉冲宽度调制(PWM)信号。PWM信号由高电平和低电平组成,其中高电平的时长称为脉冲宽度。通过调节脉冲宽度,可以控制舵机的旋转角度。也有通过串口通讯来控制的舵机,会比较贵。
舵机通常由电机、减速器、电子电路和输出轴组成。舵机的基础知识:
因为K210的摄像头要识别地图上的红线,也要识别数字,所以需要微调摄像头的角度来切换这两种模式。
这里就是用了一个双排侧插的排针把 地,+5V,PWM引脚给引出来了。舵机的内部已经有控制电路了,所以我们要用的话就给电源和PWM信号就可以了。需要注意的是我这里用的舵机是5V舵机,因为带一个小摄像头旋转也不需要多大的力矩,而市面上扭矩大的舵机一般都是7v以上供电的,在选型的时候就要注意这个的供电。
定时器7的时钟频率是240MHz(怎么得出来的查看上面的 获取GD32定时器的时钟频率部分),在下面的代码中,舵机需要的周期是20ms也就是50Hz
//定时器7时钟频率配置关键代码
timer_param_type.period = 1200-1; //50Hz
timer_param_type.prescaler = 4000-1; //240Mhz/4000=60000Hz
PWM的通道配置和上面电机驱动的配置一样。
设置具体的脉宽就是调用timer_channel_output_pulse_value_config(SERVO_TIMER, TIMER_CH_0, SERVO_FIND_LINE_ANGLE);
来改变舵机转动的角度。
详情请看bsp->bsp_servo.c
文件。
servo_pwm_gpio_init()
和servo_timer_init()
。这些函数用于初始化舵机的GPIO和定时器。servo_pwm_gpio_init()
函数中,首先使能了舵机的GPIO时钟,然后配置了GPIO的模式和复用功能。最后,设置了输出选项,包括输出类型、输出速度等。servo_timer_init()
函数中,首先使能了舵机所使用的定时器的时钟。然后进行了定时器的初始化设置,包括计数方向、计数模式、计数周期、预分频等参数。接着,配置了定时器的通道输出模式和脉冲值,并设置了定时器的输出极性和空闲状态。servo1_pwm_pulse_set()
和servo2_pwm_pulse_set()
函数分别用于设置舵机1和舵机2的脉冲宽度。这些函数通过调用timer_channel_output_pulse_value_config()
函数来设置定时器的通道输出脉冲值,从而控制舵机的角度。servo_init()
函数,用于初始化舵机控制。该函数调用了servo_pwm_gpio_init()
和servo_timer_init()
函数来进行初始化操作。打开1_turn_the_motor
下的工程,编译并烧录到立创梁山派。
在上电前把舵机用手转到偏一点的位置,上电后舵机会自动转到中位上(这个具体和你安装的角度有关)
结合原理图,可以知道我们舵机的PWM驱动引脚在PC6上,我们把示波器的通道1连接到这个引脚上,接地接好。
通过测量可以知道他的周期约为20.001ms,频率约为49.998Hz,满足对舵机控制的要求。测上面的高电平脉宽约为1.423ms,在程序中,我设置的巡红线环角度值寄存器为85,这个定时器的周期寄存器值为1200,在这里1200就代表了50Hz ,也就是20ms,理论值为(85/1200)*20ms=1.41ms,和实际预期符合。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。