这一步我建议先有一个手柄在做,我有做手柄但还没发出来,等无人机这弄完了我再发。他奶奶滴,我不用手柄直接调pwm,拿板子的时候被螺旋桨把手削破了,怪疼。

看原理图上四个PWM,可以知道,这是绑在TIM3上的四个PWM,我们使能TIM3就能用了

注意

另外,要用PWM缓慢启动,电机启动瞬间,转子不动,接近短路,于是电源电压过低,所以单片机会被干复位
所以,如果你之前用GPIO的推挽输出直接测试电机的话,比如下面的代码,你的电路板有可能直接挂掉,持续复位,电机死活动不了

GPIOB->BSRR |= 1;
		GPIOB->BSRR |= 1<<1;
		GPIOA->BSRR |= 1<<6;
		GPIOA->BSRR |= 1<<7;
		HAL_Delay(10);
		GPIOB->BSRR |= 1<<16;
		GPIOB->BSRR |= 1<<1<<16;
		GPIOA->BSRR |= 1<<6<<16;
		GPIOA->BSRR |= 1<<7<<16;
		HAL_Delay(10);

但是你如果这样

GPIOB->BSRR |= 1;
		GPIOB->BSRR |= 1<<1;
		GPIOA->BSRR |= 1<<6;
		GPIOA->BSRR |= 1<<7;
		HAL_Delay(1);
		GPIOB->BSRR |= 1<<16;
		GPIOB->BSRR |= 1<<1<<16;
		GPIOA->BSRR |= 1<<6<<16;
		GPIOA->BSRR |= 1<<7<<16;
		HAL_Delay(1);

电机开启马上就关掉,你会发现可以动。

PWM

PWM是在TIM计时器下面的一个功能,PWM都是什么就不说了,应该都懂
这里先说一下TIM通用定时器

TIM定时器

16位递增计数器(0~65535)
16位预分频器(1~65536)
计数器溢出时产生更新时间(可以产生中断或DMA请求)

预分频系数psc是2的话,就是 TIM的时钟周期等于两个系统时钟周期(比如系统时钟1秒一个,则tim就2秒一个)
tim的一个时钟产生后,计数器就会加1;当计数器加到设定的计数器值(自动重装载寄存器arr)的时候,就会产生事件。

预分频器和自动重装载寄存器是可以改的;不过他们有影子寄存器,我们改的值会在产生事件时再给到影子寄存器,实际起作用的也是影子寄存器,所以我们更改是有延时的【使能预装载才这样,不使能就直接能变】【我们不使能】。

PWM的控制

image-1679048585795
我们注意的是比较值pulse,比如我们把pulse设置为预分频系数psc的一半(pulse=psc/2)那么pwm就是50%的占空比
0和1谁在前面是OCxREF和CCXP寄存器共同决定的,在hal库是选择pwm模式(mode)和输出极性(CH polarity),我们选模式1和输出高极性,这样当比较值为0的时候,pwm就是全0,数值越大1的占比越大,看起来比较直观。

PWM1:
向上计数时,TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平
向下计数时,TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否
则为有效电平(OC1REF=1)
总之,TIMx_CNT<TIMx_CCR1时通道1为有效电平

PWM2:
向上计数时,TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平
向下计数时,TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平
总之,TIMx_CNT<TIMx_CCR1时通道1为无效电平

PWM1:(TIMx_CNT<TIMx_CCR1时通道1为有效电平)
高极性:TIMx_CNT<TIMx_CCR输出为高电平 TIMx_CNT>TIMx_CCR输出为低电平
低极性:TIMx_CNT<TIMx_CCR输出为低电平 TIMx_CNT><TIMx_CCR输出为高电平

PWM2: TIMx_CNT<TIMx_CCR1时通道1为无效电平
高极性:TIMx_CNT<TIMx_CCR输出为低电平 TIMx_CNT>TIMx_CCR输出为高电平
低极性:TIMx_CNT<TIMx_CCR输出为高电平 TIMx_CNT>TIMx_CCR输出为低电平

后面我们的操作要通过pwm的占空比来控制电机的转速,__HAL_TIM_SET_COMPARE()
这个函数可以改变比较值
image-1679069965589

初始化

上面说了一点关于pwm的初始化相关的东西,那么现在开始配置
关于图片上的预分频系数,往大了写,1000以上。像图片上71的预分频系数,到后面比较值也写71,空心杯也转不快。预分频越大转的越快。具体不太懂,和PWM有关,我也没示波器,还老是逃课所以不知道是为什么。
image-1679135287425
仔细看的人会发现,Output compare preload选的是使能,这个默认是使能的,我没改,不知道为什么这么选了以后,代码里预装载还是没使能的,我也就不动了。
image-1679135514162

可以看到,我们是72个系统时钟产生一次tim时钟,1000次tim时钟后产生一个事件(虽然没用到,但得看懂)
模式1,有效电平为高,就是先1后0,这样比较值为0就是输出低电平。

上面是自动配置的,他里面没用启动PWM,只是配置好了,那么我们在初始化代码里面启动PWM

	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);

可以随便找地方写这一串代码
我是放在了void MX_TIM3_Init(void)最下面image-1679135849490
图片的写法是错的,分开写,这个东西用 | 合到一块写是没作用的;关于TIM_CHANNEL_x的都分开写。

在其他地方改变比较值可以这样(这样在代码里之间设置比较值的时候不要放螺旋桨,不然速度太大碰到手就要划伤了)

	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,50);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2,50);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_3,50);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_4,50);

这个比较值也是不能合在一块写。注意要一点一点的往上加,中间延时一点时间(开启太块无人机电压会不稳,直接复位)

测试

使用遥控传来的油门数据,先初步控制PWM让他可以飞起来

void motor_test(uint16_t yk_thr)
{
	uint8_t ccr;
	if(yk_thr>4095)yk_thr=4095;
	if(yk_thr<0)yk_thr=0;
	ccr=yk_thr*1000/4095;
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,ccr);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2,ccr);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_3,ccr);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_4,ccr);
}

前面的两个if,不加也没事,还能省时间,因为ADC本来也给不出0~4095范围以外的值,我写上只是感觉好看。
这就是把ADC数据(04095)换算成我们的比较值(预分频系数1000所以比较值是01000)
这串代码放到我们接收NF03数据的函数这里,我们一接收到,马上就改变比较值。
image-1679240899869

这是很初步的使用;没调整过的板子,四个螺旋桨速度一样,受力又不均匀,电机也有细微差距,就会到处飞而不是竖直向上。要让他稳定飞起来不是让四个电机的PWM相同

另外这样操作电机变化太大,操控特别困难,我们可以给一个初始值,遥控发来一个偏差,让初始值加上偏差,得到新值;这样做累加运算,让他可以慢慢变化。

四轴的控制

上面我们知道了怎么控制四轴的电机工作,下面就要设计控制运动状态了,这部分比较难。
我们先不说众所周知(名字)的PID先说一下四轴的姿态控制
image-1679234987974
image-1679235014830
我们按理想情况说(不考虑电机差异和重量分布,只讨论电机转速)但受力分析就不做了,我都忘差不多了,就简单说说
为什么要两个正转两个反转:螺旋桨旋转不只有向下的力,还有向四周的力,对称方向的旋转不同,两个向四周的力就相互抵消掉,不会乱飞。
具体就是通过控制部分电机的转速,使得扭矩改变,产生转动
垂直升降:那自然是四个电机都是一样的速度
俯仰角度:看图,M1M2加速(减速),M3M4减速(加速)
横滚角度:M1M4加速(减速),M2M3减速(加速)
偏航角度:M2M4加速(减速),M1M3减速(加速)

简单控制

这一部分的函数后面是用不到的,但是看下来比较方便理解
image-1679243555396
这里我们还是比较简单的摇杆发来的ADC1234数据分别对应PWM1234的增减

void motor_test2(uint16_t PWM1,uint16_t PWM2,uint16_t PWM3,uint16_t PWM4)
{
	
	static int16_t ccr1=0,ccr2=0,ccr3=0,ccr4=0;
	ccr1 = ccr1+((int16_t)PWM1-2048)*1000/4096/40;
	ccr2 = ccr2+((int16_t)PWM2-2048)*1000/4096/40;
	ccr3 = ccr3+((int16_t)PWM3-2048)*1000/4096/40;
	ccr4 = ccr4+((int16_t)PWM4-2048)*1000/4096/40;
	if(ccr1>=1000)ccr1=999;
	if(ccr1<0)ccr1=0;
	if(ccr2>=1000)ccr2=999;
	if(ccr2<0)ccr2=0;
	if(ccr3>=1000)ccr3=999;
	if(ccr3<0)ccr3=0;
	if(ccr4>=1000)ccr4=999;
	if(ccr4<0)ccr4=0;
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,ccr1);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2,ccr2);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_3,ccr3);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_4,ccr4);
}

解释下为什么乘1000又除以40,这是我随便写的,目的是原始数据(int)除以4096后不为0【比如2048/4096=0】这样写,想让电机变化慢一点,只要调高40这个数就好了。
下面的if语句是必要的,想想比较值如果超出预分频系数的范围,具体会有什么后果我不知道,但我们起码不想要。而且限制住后,在临界值往回调会简单(-500到10与0到10,显然0到10比较快)。
同样是放到接收NF03的函数里面使用。

控制3.0

通过上面两个控制的函数,我们比较循序渐进的了解了怎么控制
那么想想我们协议里面发送的,本来就是描述各种角度的,这里我们通过手柄送来的ADC数据,来直接操作角度(上面图片上,俯仰角度、横滚角度、偏航角度的控制电机方式)

这里,我们的油门和角度依然都是累加的,而每个角度的变量,都控制两个电机
油门的累加速度快,控制四个电机
角度的累加速度慢,并且是在油门的基础上对电机进行微调

void motor_test3(uint16_t yk_thr,uint16_t yk_yaw,uint16_t yk_roll,uint16_t yk_pitch)
{
	
	static int16_t ccr1=0,ccr2=0,ccr3=0,ccr4=0;
	//油门的操作
	ccr1 = ccr1+((int16_t)yk_thr-2048)*1000/4096/40;
	ccr2 = ccr2+((int16_t)yk_thr-2048)*1000/4096/40;
	ccr3 = ccr3+((int16_t)yk_thr-2048)*1000/4096/40;
	ccr4 = ccr4+((int16_t)yk_thr-2048)*1000/4096/40;
	if(ccr1>=800)ccr1=800;
	if(ccr1<0)ccr1=0;
	if(ccr2>=800)ccr2=800;
	if(ccr2<0)ccr2=0;
	if(ccr3>=800)ccr3=800;
	if(ccr3<0)ccr3=0;
	if(ccr4>=800)ccr4=800;
	if(ccr4<0)ccr4=0;
	//yaw航偏角度 M2M4一组,M1M3一组,(一组加这个数,一组减这个数)
	ccr1 = ccr1+((int16_t)yk_yaw-2048)*1000/4096/60;
	ccr2 = ccr2-((int16_t)yk_yaw-2048)*1000/4096/60;
	ccr3 = ccr3+((int16_t)yk_yaw-2048)*1000/4096/60;
	ccr4 = ccr4-((int16_t)yk_yaw-2048)*1000/4096/60;
	//roll横滚角度,M1M4一组,M2M3一组
	ccr1 = ccr1+((int16_t)yk_roll-2048)*1000/4096/60;
	ccr2 = ccr2-((int16_t)yk_roll-2048)*1000/4096/60;
	ccr3 = ccr3-((int16_t)yk_roll-2048)*1000/4096/60;
	ccr4 = ccr4+((int16_t)yk_roll-2048)*1000/4096/60;
	//pitch俯仰角度,M1M2一组,M3M4一组
	ccr1 = ccr1+((int16_t)yk_pitch-2048)*1000/4096/60;
	ccr2 = ccr2-((int16_t)yk_pitch-2048)*1000/4096/60;
	ccr3 = ccr3-((int16_t)yk_pitch-2048)*1000/4096/60;
	ccr4 = ccr4+((int16_t)yk_pitch-2048)*1000/4096/60;
	if(ccr1>=1000)ccr1=999;
	if(ccr1<0)ccr1=0;
	if(ccr2>=1000)ccr2=999;
	if(ccr2<0)ccr2=0;
	if(ccr3>=1000)ccr3=999;
	if(ccr3<0)ccr3=0;
	if(ccr4>=1000)ccr4=999;
	if(ccr4<0)ccr4=0;
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,ccr1);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2,ccr2);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_3,ccr3);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_4,ccr4);

}