3_如何获取电机实际输出的速度


使用PID有三个重要的参数:设定输出量、实际输出量、偏差量。
比如设定了控制速度,需要测量电机实际输出的速度以获取偏差值,从而进行反馈控制,使得控制更加精准。

〇、补一点电机的知识

在这里插入图片描述
(1)减速比:
减速比=输入速度/输出速度
(2)624线:
电机每转一圈输出624个脉冲
(3)416RPM
RMP,rotation per minute,每分钟416转(圈)

一、如何控制小车的速度

使用PWM调节占空比从而调节它的速度

比如:
设置自动重装载值为ARR=3000,
TIM_TimeBaseStructure.TIM_Period = 3000;
设置比较值CCR=2000,
TIM_SetCompare2(TIM2,2000);
设置计数模式为向上计数,
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
设置极性为高电平有效,
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
在这里插入图片描述
因此得到的输出速度是做到速度的(3000-2000)/3000=33.3%。

二、 如何解决不能使用三个定时器的问题

控制小车的运动需要一个定时器(TIM2);需要两个测量电机实际输出速度的定时器(TIM3、TIM4),也即是读取一定时间内编码器的脉冲数。
然后就会出现一个玄学问题,一旦使用三个及以上的定时器就会卡住,原因没找到,但找到了一个解决方法就是再使用一个系统滴答定时器,并且在系统定时器中断里执行程序。

1、系统滴答计时器与普通定时器的区别:

(1)功能上,滴答计时器只有最基本的计时器的功能;而普通计时器有更多的功能
(2)使用方面,滴答计时器一般是用于操作系统的计时
(3)在中断优先级方面,滴答定时器不受外界干扰,稳定的进入中断;而普通的定时器受中断优先级的约束,可以被高优先级中断打断。

2、如何使用系统滴答定时器:

(1)初始化定时器(初始化要放在最后,不然它初始化后就开始计时,放它后面的外设初始化等可能初始化不了)

void SysTick_Init(void)
{
	/* SystemFrequency / 100     10ms中断一次
	   SystemFrequency / 1000    1ms中断一次
	   SystemFrequency / 100000	 10us中断一次
	   SystemFrequency / 1000000 1us中断一次
	 */
	SystemCoreClockUpdate();

	if (SysTick_Config(SystemCoreClock/10 ))	 //0.1s中断一次
	{ 	
		while (1);
	}
	 	SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;// 开启滴答定时器 
}

(2)然后在stm32f10x_it.c中的 SysTick_Handler(void)函数中写出中断要执行的程序即可

void SysTick_Handler(void)
{		
		Motor_Left(1);
		Motor_Right(1);
	    GetMotorPulse();
}

三、使用正交编码器测量脉冲

1、正交编码器初始化代码:

(参考这位大佬的

void TIM3_PWM_Init(u16 arr,u16 psc)
{ 	
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
  TIM_ICInitTypeDef TIM_ICInitStructure;  
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//使能定时器3的时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PA端口时钟
	
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;	//端口配置
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);					      //根据设定参数初始化GPIOB
  
  TIM_DeInit(TIM3);
	
  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  TIM_TimeBaseStructure.TIM_Prescaler = psc; // 预分频器 
  TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数  
  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
  TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
  TIM_ICStructInit(&TIM_ICInitStructure);
  TIM_ICInitStructure.TIM_ICFilter = 10;
  TIM_ICInit(TIM3, &TIM_ICInitStructure);
  TIM_ClearFlag(TIM3, TIM_FLAG_Update);//清除TIM的更新标志位
  TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
  //Reset counter
  TIM_SetCounter(TIM3,0);
  TIM_Cmd(TIM3, ENABLE);	
}
2、关于正交编码器定时器初始化的几点:

(1)使用编码器模式只能使用每个定时器的通道1(CH1)和通道2(CH2)
因为定时器在捕获时,
在这里插入图片描述
而正交编码器在计数时,使用的是TI1和TI2,其对应的信号是TI1FP1和TI2FP2,所以只能用CH1和CH2。

(2) GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
这里上拉输入和浮空输入都可以,没啥大的影响。

(3) TIM_TimeBaseStructure.TIM_Period = arr;

之前学习的时候,看到很多大佬说自动重装值是编码器的线数,如果使用四倍频的话,那就是该值就是 “线数×4-1”。

后来研究了一下,没什么道理,与定时器模式不同,在编码器获取脉冲值的模式中,自动重装载值是一个上限,超过了就会溢出,如果数值太大溢出,那么我们读取的脉冲数就是错的,所以我们只要保证设置的值一定大于固定时间内的脉冲数即可。当然了,最稳妥的,难道不是65535吗?

(4) TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3

定时器使用双通道检测模式(TIM_EncoderMode_TI12)
小车电机上的编码器会输出两列脉冲波,A相波和B相波(如下图),通过A相B相判断电机的正反转也如下图。

定时器的编码器有三种模式:
TIM_EncoderMode_TI1在TI1的边沿计数,只计A相脉冲
TIM_EncoderMode_TI2在TI2的边沿计数,只计B相脉冲
TIM_EncoderMode_TI12在TI1和TI2的边沿计数,计A、B相脉冲

使用模式3的话测出来的数据会更准确。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

使用上升沿计数模式( TIM_ICPolarity_Rising)
测量A、B两列波的上升沿,那么实际脉冲值=获得的脉冲值/2

那为什么不使用双边沿呢,使用双边沿四倍频不是更精准了?因为不知道什么问题,使用双边沿得到的脉冲值大到离谱,如下图,左边的脉冲值是上升沿检测的,右边的脉冲值是双边沿检测的😅大概40m/s,按照一般高速的120公里时速计算,每分钟就是2000米,每秒就是33米多一点,如果双边沿脉冲检测的是对的,那么这小车上个高速绰绰有余。(奇怪的知识又增加了)
在这里插入图片描述

四、数据处理

实际脉冲值:
实际脉冲值=获得的脉冲值/2

对应的速度值:
在这里插入图片描述
代码块:

//小车参数的定义
#define span 0.1    //系统定时0.1s,每0.1秒计算一次
#define dis_circle  3.14*6.5   //小车轮胎直径6.5cm
#define everyciecle_encode   624//转一圈的编码器数=编码器的线数

//读取电机脉冲
void GetMotorPulse(void)
{
	LeftMotorPulse = (short)TIM3->CNT/2;
	TIM3->CNT = 0;
    RightMotorPulse = (short)TIM4->CNT/2;
	TIM4->CNT = 0;
	
	LeftMotorSpeed=LeftMotorPulse/everyciecle_encode*dis_circle/span; //得到左车轮的速度
	RightMotorSpeed=RightMotorPulse/everyciecle_encode*dis_circle/span; //得到右车轮的速度
		
	printf("Pulse:%d  %d\r\n",LeftMotorPulse,RightMotorPulse);
	printf("Speed:%f  %f\r\n",LeftMotorSpeed,RightMotorSpeed);
}

💜附上测试程序:STM32F1.ControlDetect

好的,这个任务可以分成两个部分来完成:1)按键的检测和处理;2)LED的闪烁控制。下面我将给出基于STM32定时器的代码示例,实现三个按键控制三个LED的闪烁速度。 首先是按键检测和处理部分的代码: ```c // 定义按键端口和引脚 #define KEY1_PORT GPIOA #define KEY1_PIN GPIO_PIN_0 #define KEY2_PORT GPIOB #define KEY2_PIN GPIO_PIN_1 #define KEY3_PORT GPIOC #define KEY3_PIN GPIO_PIN_2 // 定义按键状态 typedef enum { KEY_RELEASED = 0, KEY_PRESSED = 1, KEY_DEBOUNCING = 2 } key_state_t; // 定义按键处理函数 void handle_key_press(uint32_t key); // 定义按键状态数组 static key_state_t key_states[3] = {KEY_RELEASED, KEY_RELEASED, KEY_RELEASED}; // 定义定时器中断处理函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { // 检测按键状态 if (key_states[0] == KEY_DEBOUNCING) { if (HAL_GPIO_ReadPin(KEY1_PORT, KEY1_PIN) == GPIO_PIN_RESET) { key_states[0] = KEY_PRESSED; handle_key_press(0); } else { key_states[0] = KEY_RELEASED; } } if (key_states[1] == KEY_DEBOUNCING) { if (HAL_GPIO_ReadPin(KEY2_PORT, KEY2_PIN) == GPIO_PIN_RESET) { key_states[1] = KEY_PRESSED; handle_key_press(1); } else { key_states[1] = KEY_RELEASED; } } if (key_states[2] == KEY_DEBOUNCING) { if (HAL_GPIO_ReadPin(KEY3_PORT, KEY3_PIN) == GPIO_PIN_RESET) { key_states[2] = KEY_PRESSED; handle_key_press(2); } else { key_states[2] = KEY_RELEASED; } } // 实现按键去抖 for (int i = 0; i < 3; i++) { if (key_states[i] == KEY_RELEASED && HAL_GPIO_ReadPin(KEY1_PORT + i, KEY1_PIN << i) == GPIO_PIN_RESET) { key_states[i] = KEY_DEBOUNCING; } } } // 定义按键处理函数 void handle_key_press(uint32_t key) { // 根据按键编号控制对应的LED闪烁速度 switch (key) { case 0: // 控制LED1的闪烁速度 break; case 1: // 控制LED2的闪烁速度 break; case 2: // 控制LED3的闪烁速度 break; default: break; } } ``` 接下来是LED闪烁控制部分的代码: ```c // 定义LED端口和引脚 #define LED1_PORT GPIOA #define LED1_PIN GPIO_PIN_1 #define LED2_PORT GPIOB #define LED2_PIN GPIO_PIN_2 #define LED3_PORT GPIOC #define LED3_PIN GPIO_PIN_3 // 定义LED状态 typedef enum { LED_OFF = 0, LED_ON = 1 } led_state_t; // 定义LED控制函数 void set_led_state(uint32_t led, led_state_t state); // 定义LED状态数组和闪烁频率数组 static led_state_t led_states[3] = {LED_OFF, LED_OFF, LED_OFF}; static int blink_freqs[3] = {0, 0, 0}; // 定义定时器中断处理函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { // 控制LED闪烁 for (int i = 0; i < 3; i++) { if (led_states[i] == LED_ON) { if (blink_freqs[i] == 0) { set_led_state(i, LED_OFF); } blink_freqs[i]--; } else { if (blink_freqs[i] == 0) { set_led_state(i, LED_ON); } blink_freqs[i]--; } } } // 定义LED控制函数 void set_led_state(uint32_t led, led_state_t state) { // 根据LED编号控制对应的LED switch (led) { case 0: HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, state == LED_ON ? GPIO_PIN_SET : GPIO_PIN_RESET); led_states[0] = state; break; case 1: HAL_GPIO_WritePin(LED2_PORT, LED2_PIN, state == LED_ON ? GPIO_PIN_SET : GPIO_PIN_RESET); led_states[1] = state; break; case 2: HAL_GPIO_WritePin(LED3_PORT, LED3_PIN, state == LED_ON ? GPIO_PIN_SET : GPIO_PIN_RESET); led_states[2] = state; break; default: break; } } // 定义控制函数 void set_blink_freq(uint32_t led, int freq) { // 根据LED编号设置闪烁频率 blink_freqs[led] = freq; } ``` 最后,在主函数中初始化定时器和GPIO,并启动定时器中断: ```c int main(void) { // 初始化定时器和GPIO // ... // 启动定时器中断 HAL_TIM_Base_Start_IT(&htim2); while (1) { // 程序主循环 } } ``` 这样,就完成了基于STM32定时器三个按键控制三个LED闪烁速度的代码实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值