、
通常的机械臂都是由多路舵机组成,我使用的是某宝上(并不)常见的五自由度机械臂(尽管商家称它为六自由度)。
这里使用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 进行开发。时钟框图为默认配置,如图。
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。
注意,这里的计数模式(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。
由于我手头上的机械臂中的一个舵机用于控制机械爪,其为90°舵机,所以在配置控制该舵机的PWM通道时,比较值范围仅能为200 ~ 600,中间值为400我使用的单片机为STM32F407VGT6,其TIM3和TIM9所对应的PWM输出通道分别为PA6, PA7, PB0, PB1和PE5, PE6。
这里只需将这些IO口设置为复用推挽输出以及上拉即可。
串口配置
这里我选择USART_2用作串口通讯,其Tx与Rx分别对应为PA2、PA3。
串口的配置如图所示。
这里使用异步通信模式,注意波特率等参数需与上位机相匹配。
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进行逆向操作得到原数据。
——————新增——————
可以使用C语言共同体union,无需复杂的数据类型转换(当时不知道这个东西…)
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程序,通过上位机控制机械臂。