目录
项目概述:
一 准备阶段(都是些废话)
二 裸机测试功能
1.摇杆控制
接线:
CubeMX配置:
代码:
2.蓝牙控制
接线:
CubeMX配置
代码:
3.示教器控制
4.记录动作信息
5.执行记录的动作
注:
三 FreeRTOS上完成项目
代码:
链接:https://pan.baidu.com/s/14GJF8ZCnkNkKkz5R0uJwOA?pwd=1111
提取码:1111
--来自百度网盘超级会员V4的分享
项目概述:
基于FreeRTOS实时操作系统,主控为 STM32F103C8T6 ,机械臂为四轴分别被四个Mg90s舵机控制。本项目实现了 3 种控制方法,分别为 摇杆控制 、 串口蓝牙控制 和 示教器控制。可以进行动作录制和执行。
采用8路ADC采集摇杆和示教器的模拟量并由DMA搬运数据,USART串口实时收发信息,IIC驱动OLED屏幕实时显示信息。并且实现了动作录制和执行功能,动作记忆可以由二维数组或者链表实现存储。通过SPI驱动W25Q128模块进行动作记忆扩容,即可以录制上百组动作。
一 准备阶段(都是些废话)
首先你需要一台四轴机械臂,才能开始这个项目。可以自己建模3D打印,也可以直接某宝购买了一套成品套件,来做功能实现。而你的机械臂会配备四个电机,本文采用的是舵机,型号无所谓,控制起来是一样的,注意需要是180度的角度型舵机,而不是360度的速度型舵机。
然后是单片机及开发环境,使用STM32F103C8T6。开发环境为STM32cubeM和Keil5。
(如果你没有STM32开发经验:首先你至少要有一点C语言基础,最基本的代码要能读懂什么意思;然后最好有过其它单片机开发经验,比如C51、ESP8266等等,或者直接学习一下STM32开发。板子随便买一个此型号的开发板就行,买最小系统板+面包板也可以。STM32cubeMX+Keil5,可以自行百度搜索并下载安装,我建议在B站找一个STM32HAL库的教程,跟着安装,且最好教程芯片型号与你使用的要一致。按照教程走一遍。确认开发板和开发环境可用之后,简单学习一下HAL库开发。然后可以继续下面的步骤。)
STM32cubeM和Keil5的教程推荐:
【中科大RM电控合集】手把手Keil+STM32CubeMX+VsCode环境配置_哔哩哔哩_bilibili
其它硬件准备:
HC系列蓝牙串口模块,实测HC-05和HC-08都可以 |
摇杆模块,买两个即可。 |
四个旋钮电位器,质量别太差。 |
IIC协议OLED屏幕 |
SPI协议W25Q128模块 |
按钮模块若干,我用了四个,有板载的按钮也可以,尽量买带电容的防抖按钮 |
舵机拓展板,可有可无,面包板也能用。 |
各式杜邦线若干。 |
二 裸机测试功能
1.摇杆控制
首先是摇杆控制STM32,需要4路1ADC+DMA采集摇杆输出的模拟量。根据这个数据来控制舵机角度。蓝牙串口把ADC信息和舵机角度打印出来。蓝牙直接用HC官方的HC蓝牙串口助手就行。
接线:
摇杆4个输出模拟量的引脚连接stm32的A0,A1,A2,A3,VCC这里接5V。
舵机A,夹爪 CH4_B11;adc4_A3
舵机B,上下 CH3_B10;adc3_A2
舵机C,前后 CH2_B3;adc2_A1
舵机D,底座 CH1_A15;adc1_A0
蓝牙TX对板子RX A10,
蓝牙RX对板子TX A9。
CubeMX配置:
基本配置(后面每个工程都是这一套)
ADC1:4路
DMA:搬运ADC数据的
PWM输出:选用799*1799,这样可以把舵机有效的 0.5~2.5ms / 20ms 这个区间分成180段,对应0~180度。
usart,9600波特率给蓝牙模块用。
然后 generate code 即可
代码:
注:只有这种注释之间是用户自己写业务代码的地方,写其它地方再重生成功能会被清除。
/* USER CODE BEGIN */
。。。。 。。。。
/* USER CODE END */
main.c
关键控制代码在于check的四个函数,首先限制舵机的角度范围避免损坏,再根据采集的摇杆信息值判断每个舵机的角度是增加还是减小。
注释比较清楚,直接看代码就行。
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint16_t adc_dma[4];//DMA搬运的ADC采集值
uint8_t angle[4] = {90,90,90,90};//舵机角度
uint8_t cnt = 0;//计数用,定时串口打印信息
/* USER CODE END PV */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//覆写printf,用于串口打印数据
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
//根据输入的0~180角度获取对应pwm占空比参数
uint8_t Angle(uint8_t pwm_pulse)
{
return pwm_pulse + 44;
}
//舵机A,夹爪 CH4_B11
void cheack_A()
{
if(adc_dma[3] > 4000 && angle[3] < 90)
{
angle[3]++;
}
else if(adc_dma[3] <1000 && angle[3] > 0)
{
angle[3]--;
}
}
//舵机B,上下 CH3_B10
void cheack_B()
{
if(adc_dma[2] <1000 && angle[2] < 135)
{
angle[2]++;
}
else if(adc_dma[2] > 4000 && angle[2] > 45)
{
angle[2]--;
}
}
//舵机C,前后 CH2_B3
void cheack_C()
{
if(adc_dma[1] <1000 && angle[1] < 135)
{
angle[1]++;
}
else if(adc_dma[1] > 4000 && angle[1] > 45)
{
angle[1]--;
}
}
//舵机D,底座 CH1_A15
void cheack_D()
{
if(adc_dma[0] <1000 && angle[0] < 180)
{
angle[0]++;
}
else if(adc_dma[0] > 4000 && angle[0] > 0)
{
angle[0]--;
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4); //开始ADC和DMA采集
//开启4路PWM
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
//延时半秒,系统稳定一下
HAL_Delay(500);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//根据摇杆DMA判断舵机该如何运动
cheack_A();
cheack_B();
cheack_C();
cheack_D();
//输出PWM波使舵机运动
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
cnt++;//计数,每循环一次+1
if(cnt>= 50)//循环50次,每次20ms,即一共1s。每一秒发送一次数据
{
printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);
cnt = 0;
}
HAL_Delay(20);//每20ms循环一次(改成15更流畅)
}
/* USER CODE END 3 */
这里要勾选才能使用printf串口打印信息
2.蓝牙控制
这里提前写了一点示教器的业务代码,执行切换模式操作会切换获取摇杆模拟值还是电位器模拟值。
注意:我这里整活儿搞了个ADC通道切换,但实测还是存在一点问题,你们直接使用8通道一起就好。
接线:
先不使用示教器,但是可以先测试一下功能,切换模式和采集一下数据。
把四个旋钮电位器接好,四根线接到ADC 5 6 7 8
CubeMX配置
打开串口中断,中断接收数据。
我这里是ADC再开四个,其它不配置。
你们开启共8个之后把下面个数也4改成8,新的组别5678也改成IN4 5 6 7 四个通道
代码:
都写在main.c里面会太冗长,我这里分文件编程了,不懂可以百度keil怎么添加.c .h文件,实在不行就都放在main.c里吧。。。
adc.c
纯粹整活儿,自定义了一个ADC初始化,把采集1234换成5678来采集电位器信号。直接用八个通道一起采集就行,然后把原来放采集数据的那个数组adc_dma长度也改成8。
/* USER CODE BEGIN 1 */
//写一个切换通道的函数
/* ADC1_Mode2 init function */
void MX_ADC1_Mode2_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 4;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_5;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_6;
sConfig.Rank = ADC_REGULAR_RANK_3;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_7;
sConfig.Rank = ADC_REGULAR_RANK_4;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE END 1 */
adc.h
/* USER CODE BEGIN Prototypes */
void MX_ADC1_Mode2_Init(void);
/* USER CODE END Prototypes */
usart.c
注:STM32串口接收到的信息都在这里进行处理,千万别忘了最下面一行代码,开启中断。
//=======中断信息处理=======
。。。。。。。。
//==========================
/* USER CODE BEGIN 0 */
#include "stdio.h"
#include "string.h"
#include "PWM.h"
#include "adc.h"
#include "dma.h"
/*机械臂控制模式,默认为1
1:摇杆控制
2:示教器控制 */
uint8_t Mode = 1;
/*蓝牙控制机械臂指令:
s 停
l/r 左右
u/d 上下
f/b 前后
o/c 开合*/
uint8_t cmd_BLE = 's';
extern uint16_t adc_dma[4];//DMA搬运的ADC采集值
//覆写printf
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
//=====串口(中断)=======
//串口接收缓存(1字节)
uint8_t buf=0;
//定义最大接收字节数 200,可根据需求调整
#define UART1_REC_LEN 200
// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN];
// 接收状态
// bit15, 接收完成标志
// bit14, 接收到0x0d
// bit13~0, 接收到的有效字节数目
uint16_t UART1_RX_STA=0;
// 串口中断:接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 判断中断是由哪个串口触发的
if(huart->Instance == USART1)
{
// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
if((UART1_RX_STA & 0x8000) == 0)
{
// 如果已经收到了 0x0d (回车),
if(UART1_RX_STA & 0x4000)
{
// 则接着判断是否收到 0x0a (换行)
if(buf == 0x0a)
{
// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
UART1_RX_STA |= 0x8000;
//=======中断信息处理=======
//模式切换
if (!strcmp((const char *)UART1_RX_Buffer, "M1"))
{
Mode = 1;
HAL_ADC_Stop_DMA(&hadc1);//停止ADC DMA
MX_ADC1_Init();//初始化ADC1
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4); //开启ADC DMA
printf("摇杆模式\r\n");
}
else if(!strcmp((const char *)UART1_RX_Buffer, "M2"))
{
Mode = 2;
HAL_ADC_Stop_DMA(&hadc1);//停止ADC DMA
MX_ADC1_Mode2_Init();//自定义初始化ADC1,把1234换成5678采集电位器
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4); //开启ADC DMA
printf("示教器模式\r\n");
}
//获取蓝牙控制指令,A打头,后面一个字母就是指令内容
else if(Mode == 1 && UART1_RX_Buffer[0] == 'A')
{
cmd_BLE = UART1_RX_Buffer[1];
}
else {
if(UART1_RX_Buffer[0] != '\0')
printf("指令发送错误:%s\r\n", UART1_RX_Buffer);
}
//==========================
memset(UART1_RX_Buffer, 0, strlen((const char *)UART1_RX_Buffer));
// 重新开始下一次接收
UART1_RX_STA = 0;
//==========================
}
else
// 否则认为接收错误,重新开始
UART1_RX_STA = 0;
}
else // 如果没有收到了 0x0d (回车)
{
//则先判断收到的这个字符是否是 0x0d (回车)
if(buf == 0x0d)
{
// 是的话则将 bit14 位置为1
UART1_RX_STA |= 0x4000;
}
else
{
// 否则将接收到的数据保存在缓存数组里
UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;
// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART1_REC_LEN - 1)
UART1_RX_STA = 0;
}
}
}
// 重新开启中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
/* USER CODE END 0 */
/* USER CODE BEGIN USART1_Init 2 */
// 开启接收中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
/* USER CODE END USART1_Init 2 */
我这里新建了两个PWM.c和.h文件。
把蓝牙指令控制和摇杆控制放在一起判断了。
#include "PWM.h"
#include "main.h"
extern uint16_t adc_dma[4];//DMA搬运的ADC采集值
extern uint8_t angle[4];//舵机角度
extern uint8_t Mode;
extern uint8_t cmd_BLE;
//根据输入的角度获取对应pwm占空比参数
uint8_t Angle(uint8_t pwm_pulse)
{
return pwm_pulse + 44;
}
//舵机A,夹爪 CH4_B11
void check_A()
{
if(Mode == 1)
{
if((cmd_BLE == 'c' || adc_dma[3] > 4000) && angle[3] < 90)//合
{
angle[3]++;
}
else if((cmd_BLE == 'o' || adc_dma[3] <1000) && angle[3] > 0)//开
{
angle[3]--;
}
}
}
//舵机B,上下 CH3_B10
void check_B()
{
if(Mode == 1)
{
if((cmd_BLE == 'u' || adc_dma[2] <1000) && angle[2] < 135)//上
{
angle[2]++;
}
else if((cmd_BLE == 'd' || adc_dma[2] > 4000) && angle[2] > 45)//下
{
angle[2]--;
}
}
}
//舵机C,前后 CH2_B3
void check_C()
{
if(Mode == 1)
{
if((cmd_BLE == 'f' || adc_dma[1] <1000) && angle[1] < 135)//前
{
angle[1]++;
}
else if((cmd_BLE == 'b' || adc_dma[1] > 4000) && angle[1] > 45)//后
{
angle[1]--;
}
}
}
//舵机D,底座 CH1_A15
void check_D()
{
if(Mode == 1)
{
if((cmd_BLE == 'l' || adc_dma[0] <1000) && angle[0] < 180)//左
{
angle[0]++;
}
else if((cmd_BLE == 'r' || adc_dma[0] > 4000) && angle[0] > 0)//右
{
angle[0]--;
}
}
}
#ifndef __PWM_H__
#define __PWM_H__
//根据输入的角度获取对应pwm占空比参数
unsigned char Angle(unsigned char pwm_pulse);
//舵机A,夹爪 CH4_B11
void check_A(void);
//舵机B,上下 CH3_B10
void check_B(void);
//舵机C,前后 CH2_B3
void check_C(void);
//舵机D,底座 CH1_A15
void check_D(void);
#endif
main.c
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "PWM.h"
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint16_t adc_dma[4];//DMA搬运的ADC采集值
uint8_t angle[4] = {90,90,90,90};//舵机角度
uint8_t cnt = 0;//计数用
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
printf("Start\r\n");
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
HAL_Delay(500);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//根据摇杆DMA判断舵机该如何运动
check_A();
check_B();
check_C();
check_D();
//输出PWM波使舵机运动
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
cnt++;
if(cnt>= 50)
{
printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);
cnt = 0;
}
HAL_Delay(20);
}
/* USER CODE END 3 */
3.示教器控制
把示教器控制的业务代码也写出来,和蓝牙/摇杆控制封装在一个函数里,main里直接调用这个函数就行。
主要是PWM.c添加了一些代码,直接修改上面代码即可。
extern uint16_t adc_dma[4];//DMA搬运的ADC采集值,直接用8通道就改长度8
extern uint8_t angle[4];//舵机角度
extern uint8_t Mode;
extern uint8_t cmd_BLE;
//根据输入的角度获取对应pwm占空比参数
uint8_t Angle(uint8_t pwm_pulse)
{
return pwm_pulse + 44;
}
//舵机角度如何变化和模式判断的函数
void sg()
{
if(Mode == 1)//蓝牙/摇杆模式
{
check_A();
check_B();
check_C();
check_D();
}
else if(Mode == 2)//示教器模式
{
translate();
}
//输出PWM波使舵机运动
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
HAL_Delay(20);
}
void translate()//把采集的模拟值转变为角度。即0~4095变为0~180,除以22.75即可。
{
angle[3] = (uint8_t)((double)adc_dma[0] / 22.75)/2;
angle[2] = (uint8_t)((double)adc_dma[1] / 22.75);
angle[1] = (uint8_t)((double)adc_dma[2] / 22.75) - 10;
angle[0] = 180 - (uint8_t)((double)adc_dma[3] / 22.75);//电位器装反,改为 180 - 即可
//直接用8通道就是adc_dma[4~7]
}
PWM.h
#ifndef __PWM_H__
#define __PWM_H__
//根据输入的角度获取对应pwm占空比参数
unsigned char Angle(unsigned char pwm_pulse);
//舵机A,夹爪 CH4_B11
void check_A(void);
//舵机B,上下 CH3_B10
void check_B(void);
//舵机C,前后 CH2_B3
void check_C(void);
//舵机D,底座 CH1_A15
void check_D(void);
void sg(void);
void translate(void);
#endif
main.c
/* USER CODE BEGIN 2 */
printf("Start\r\n");
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
HAL_Delay(500);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
sg();//判断舵机该如何运动
cnt++;
if(cnt>= 25)
{
printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);
//printf("adc_dma = {%d, %d, %d, %d}\r\n",adc_dma[0],adc_dma[1],adc_dma[2],adc_dma[3]);
cnt = 0;
}
}
/* USER CODE END 3 */
至此,基本的控制功能代码已经完成了。小白的话完成到这里已经很不错了。
4.记录动作信息
本质上就是保存当前的舵机的四个角度值。
这里暂时先用二维数组来做。
被添加的代码:
蓝牙指令A后的 m g D ,对应我们这里 记录当前角度、获取所有记录的角度、删除所有记录。
#include "stdio.h"
#include "string.h"
uint8_t memory[10][4];//记录用的数组
uint8_t i,j = 0;
void sg()
{
if(Mode == 1)
{
check_A();
check_B();
check_C();
check_D();
}
else if(Mode == 2)
{
translate();
if(cmd_BLE == 'm' && i<9)
{
for(j=0;j<4;j++)
{
memory[i][j] = angle[j];
}
printf("储存动作\r\n");
cmd_BLE = 's';
i++;
}
else if(cmd_BLE == 'm' && i>=9)
printf("动作已满\r\n");
cmd_BLE = 's';
}
if(cmd_BLE == 'g')
{
for(i=0;i<10;i++)
{
for(j=0;j<4;j++)
{
printf("%d ",memory[i][j] + 0x30);
}
printf("\r\n");
if(memory[i][j] == '\0') break;
}
cmd_BLE = 's';
}
else if(cmd_BLE == 'D')
{
for(i=0;i<10;i++)
{
memset(memory[i],'\0',4);
}
i = 0;
printf("已清除动作");
cmd_BLE = 's';
}
//输出PWM波使舵机运动
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
HAL_Delay(20);
}
5.执行记录的动作
这里开始已经转到FreeRTOS上了,没继续在裸机上做。所以没写对应源码,不过可以拿后面FreeRTOS上实现的代码放在这里。没区别一样可以用。需要你们自己来实现和调试。
PWM.c
主要就是下面这两个函数:
location_cnt是数组长度,宏定义出来就行,自己调整长度
uint8_t memory[location_cnt][4];
uint8_t i,j = 0;
uint8_t angle_target[4] = {90,90,90,90};
uint8_t angle_target_flag = 0;
void get_target()//从数组获得位置信息并转换位角度目标值
{
angle_target_flag = 0;
for(j=0;j<4;j++)
{
if(angle[j] == angle_target[j]) angle_target_flag++;
}
if(angle_target_flag == 4) i++;
for(j=0;j<4;j++)
{
if(memory[i][j] == '\0')
{
i = 0;
}
angle_target[j] = memory[i][j];
}
}
void reach_target()//角度值像角度目标值靠近,用于简单防抖和执行记忆动作
{
for(j = 0;j <4;j++)
{
if(angle[j] > angle_target[j])
{
angle[j]--;
}
else if(angle[j] < angle_target[j])
{
angle[j]++;
}
}
}
void translate()//根据实际情况做了一点角度矫正和限位
{
angle_target[3] = (uint8_t)((double)adc_dma[4] / 22.75)/2;
angle_target[2] = (uint8_t)((double)adc_dma[5] / 22.75);
angle_target[1] = (uint8_t)((double)adc_dma[6] / 22.75) - 10;
angle_target[0] = 180 - (uint8_t)((double)adc_dma[7] / 22.75);
if(angle_target[1]<45) angle_target[1]=45;
else if(angle_target[1]>135) angle_target[1]=135;
if(angle_target[2]<45) angle_target[1]=45;
else if(angle_target[2]>135) angle_target[1]=135;
}
//是否记录当前位置信息
void if_BLE_cmd()
{
switch(cmd_BLE)
{
case 'm':
if(i < location_cnt)
{
for(j=0;j<4;j++)
{
memory[i][j] = angle[j];
}
printf("储存动作\r\n");
cmd_BLE = 's';
i++;
}
else
{
printf("动作已满\r\n");
cmd_BLE = 's';
}
break;
case 'g':
for(i=0;i < location_cnt;i++)
{
for(j=0;j<4;j++)
{
printf("%d ",memory[i][j]);
}
printf("\r\n");
if(memory[i][j] == '\0') break;
}
cmd_BLE = 's';
break;
case 'D':
for(i=0; i < location_cnt ;i++)
{
memset(memory[i],'\0',4);
}
i = 0;
printf("已清除动作");
cmd_BLE = 's';
break;
}
}
void check_sg_cmd()//蓝牙和摇杆控制
{
check_A();
check_B();
check_C();
check_D();
}
usart.c
/*机械臂控制模式,默认为1
1:摇杆控制
2:示教器控制
3:执行记忆动作
*/
uint8_t Mode = 1;
//=======中断信息处理=======
//模式切换
if (!strcmp((const char *)UART1_RX_Buffer, "M1"))
{
Mode = 1;
printf("摇杆模式\r\n");
}
else if(!strcmp((const char *)UART1_RX_Buffer, "M2"))
{
Mode = 2;
printf("示教模式\r\n");
}
else if(!strcmp((const char *)UART1_RX_Buffer, "M3"))
{
Mode = 3;
printf("执行记忆动作\r\n");
}
freertos.c内相关代码
和main.c的while循环一样理解就行,一样用
/* Infinite loop */
for(;;)
{
if(Mode == 1)//摇杆和蓝牙控制
{
check_sg_cmd();
}
else if(Mode == 2)//示教器控制
{
translate();
reach_target();
}
else if(Mode == 3)//动作执行
{
get_target();
reach_target();
}
if_BLE_cmd();//蓝牙指令处理
//输出PWM波使舵机运动
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, Angle(angle[0]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, Angle(angle[1]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_3, Angle(angle[2]));
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_4, Angle(angle[3]));
osDelay(15);//通过调整此延时可以改变机械臂运行速度
}
注:
裸机开发到这里就结束了,大部分功能都简单实现出来了。
如果发现舵机运动每秒顿一次,请把每秒串口打印信息关掉就行,这是裸机的劣势所在。
三 FreeRTOS上完成项目
下面是移植到FreeRTOS操作系统上运行,没法介绍太详细,建议先系统学一下STM32 HAL开发以及FreeRTOS,再进行。
(待更新)