STM32与树莓派(上位机)交互控制机械臂



通常的机械臂都是由多路舵机组成,我使用的是某宝上(并不)常见的五自由度机械臂。尽管商家称它为六自由度。

这里使用STM32F407VGT6的6路PWM输出通道来控制6个舵机的运动,树莓派(上位机)通过USB转TTL模块与STM32进行串口通讯

PWM舵机控制原理

标准的 PWM 舵机有三条控制线,分别为:电源、地及信号线。
小型舵机
市面上大多数180°舵机需要的PWM波周期通常为20ms,高电平接收时间通常为0.5 ~ 2.5ms,对应舵机旋转角度为0 ~ 180°。用PWM波控制舵机时,只需将时钟的周期设置为20ms(50Hz),并且通过改变比较值pulse改变PWM波的高电平时间来控制舵机旋转的角度.

STM32CubeMx主要配置

对于STM32,我使用的是STM32CubeIDE + Mx 进行开发。时钟框图为默认配置,如图。
STM32时钟框图

TIMER

由于需要控制6个舵机,我选择了TIM3(4路PWM输出)和TIM9(2路PWM输出)。
PWM输出的频率由时钟APB2决定,由时钟框图可知此处的APB2频率为16MHz;PWM波频率计算公式为:

W = APB2 / (PSC + 1)(ARR + 1)

其中PSC为分频系数,ARR为自动重装载值。这里我将PSE设置为39,ARR设置为7999。
Timer配置
注意,这里的计数模式(Counter Mode)不同时,会导致接下来比较值值相同时舵机的旋转方向不同。

由于PWM波高电平时间需控制在0.5 ~ 2.5ms内,所以各PWM通道的比较值(pulse)必须控制在200 ~ 1000之内; (8000 x 0.5 / 20 = 200)(8000 x 2.5 / 20 = 1000)

当舵机旋转角度为90°时,pulse值应为600。这里我将各个PWM输出通道的比较值均设置为600。
PWM通道配置
由于我手头上的机械臂中的一个舵机用于控制机械爪,其为90°舵机,所以在配置控制该舵机的PWM通道时,比较值范围仅能为200 ~ 600,中间值为400我使用的单片机为STM32F407VGT6,其TIM3和TIM9所对应的PWM输出通道分别为PA6, PA7, PB0, PB1和PE5, PE6。
这里只需将这些IO口设置为复用推挽输出以及上拉即可。
GPIO--TIM

串口配置

这里我选择USART_2用作串口通讯,其Tx与Rx分别对应为PA2、PA3。

串口的配置如图所示。串口配置
这里使用异步通信模式,注意波特率等参数需与上位机相匹配。

串口GPIO
PA2与PA3均设置为复用推挽输出,上拉。

中断控制

由于这里我只使用了串口接收中断,所以NVIC这里可以不用配置;若STM32除了控制机械臂外还有其余任务,则可能需要配置NVIC。

STM32CubeIDE代码实现

在STM32CubuMx配置完毕并生成工程之后,我们的主要任务只有设计上位机与单片机的通讯协议,以及完成串口接收中断函数即可。

通讯协议设计

我初步的设计为串口发送一次数据,控制单个舵机的角度;所以发送的数据中需要包括舵机的编号以及舵机的目标角度(或者由角度转换而成的比较值)。

在这里我没有设计通讯头以及对舵机转速的控制,因为这里我的串口只用于传输控制舵机的指令,以及我使用的是小型舵机,其转速并不是很快,可以满足我的要求,无需控制其速度。

设计思路如下:
上位机每次发送16位的数据(2个uint8_t类型,串口只能发送串口),舵机编号为0~5,发送的pulse值为 (200 ~ 1000) - 200,将舵机编号数值乘以1000加上pulse值减去200得到一个uint16_t类型的数值;再将其转换为十六进制进行发送。

例:对3号舵机进行操作,角度为45°。

数据处理:
3 x 1000 + (45° / 180°) x 800 = 3200;
3200转换为十六进制为:0x0C80。

STM32进行逆向操作得到原数据。

STM32代码实现

由于串口发送的是两个uint8_t类型的数据,所以在解码之前还需将两个uint8_t类型数据转换为一个uint16_t类型的数据。

在打开USART2的接收中断时,设置为接受到两个uint8_t类型的数据进入接收中断,以便于在接收中断处理函数中进行数据处理。

uint8_t pulse[2];
HAL_UART_Receive_IT(&huart2, pulse, sizeof(pulse));

串口接受中断函数代码如下:



void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    
    
	if(huart->Instance == USART2)
	{
    
    
		uint8_t rec0, rec1;

		// 获取接收到的两个字节的数据
		rec0 = *((huart->pRxBuffPtr) - 2);
		rec1 = *((huart->pRxBuffPtr) - 1);

		uint16_t numPart[2], armControl, arm targetPulse;

		// 通过移位与强制转换得到uint16_t类型数据
		numPart[0] = (uint16_t) rec0;
		numPart[1] = (uint16_t) rec1;
		armControl = ((numPart[0] << 8) | numPart[1]);

		// 逆向解码
		arm = armControl / 1000;  // 舵机编号
		targetPulse = armControl % 1000 + 200;	// 目标比较值

		// 通过switch语句改变指定PWM通道的比较值
		switch(arm)
		{
    
    
			case 1:
				__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, targetPulse);
				break;

			case 2:
				__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, targetPulse);
				break;

			case 3:
				__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, targetPulse);
				break;

			case 4:
				__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_4, targetPulse);
				break;

			case 5:
				__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_1, targetPulse);
				break;

			case 6:
				__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_2, targetPulse);
				break;
		}
	}
}

在进入while循环之前还需将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);
  HAL_TIM_PWM_Start(&htim9, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim9, TIM_CHANNEL_2);

并且在USART2_IRQHandler函数中使能串口接收中断:

void USART2_IRQHandler(void)
{
    
    
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
  HAL_UART_Receive_IT(&huart2, pulse, sizeof(pulse));
  /* USER CODE END USART2_IRQn 1 */
}

测试

在测试时我使用的环境是安装有Ubuntu 18.04的树莓派,串口调试工具为CuteCom;由5V锂电池为单片机与舵机(机械臂)供电,每次手动发送两个字节的数据,十进制转十六进制在科学计算器上进行。机械臂的运动符合预期。

经过测试,同时发送12个字节的数据,机械臂也能够正常运行。

这里需要注意,在接线时,舵机的地线必须与单片机共地。

之后就可以将上位机串口发送的代码写入C++或者Python程序,通过上位机控制机械臂。

猜你喜欢

转载自blog.csdn.net/weixin_46068920/article/details/108646782