基于STM32+FreeRTOS的四轴机械臂

目录

项目概述:

一 准备阶段(都是些废话)

 二 裸机测试功能

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,再进行。

(待更新)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/79617.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

el-table 多个表格切换多选框显示bug

今天写了个功能&#xff0c;点击左侧的树做判断&#xff0c;一级树节点显示系统页面&#xff0c;二级树节点显示数据库页面&#xff0c;三级树节点显示表页面。 数据库页面和表页面分别有2个el-table ,上面的没有多选框&#xff0c;下面的有多选框 现在出现bug&#xff0c;在…

Linux学习之iptables过滤规则的使用

cat /etc/redhat-release看到操作系统是CentOS Linux release 7.6.1810&#xff0c;uname -r看到内核版本是3.10.0-957.el7.x86_64&#xff0c;iptables --version可以看到iptables版本是v1.4.21。 iptables -t filter -A INPUT -s 10.0.0.8 -j ACCEPT会在最后一行插入。 10…

winform 封装unity web player 用户控件

环境&#xff1a; VS2015Unity 5.3.6f1 (64-bit) 目的&#xff1a; Unity官方提供的UnityWebPlayer控件在嵌入Winform时要求读取的.unity3d文件路径&#xff08;Src&#xff09;必须是绝对路径&#xff0c;如果移动代码到另一台电脑&#xff0c;需要重新修改src。于是考虑使…

Hadoop学习:深入解析MapReduce的大数据魔力之数据压缩(四)

Hadoop学习&#xff1a;深入解析MapReduce的大数据魔力之数据压缩&#xff08;四&#xff09; 4.1 概述1&#xff09;压缩的好处和坏处2&#xff09;压缩原则 4.2 MR 支持的压缩编码4.3 压缩方式选择4.3.1 Gzip 压缩4.3.2 Bzip2 压缩4.3.3 Lzo 压缩4.3.4 Snappy 压缩4.3.5 压缩…

Apache JMeter

下载 Apache JMeter 并安装 java链接 打开 apache-jmeter-5.4.1\bin 找到jmeter.bat 双击打开 或者 ApacheJMeter.jar 双击打开 设置中文 找到 options 》choose Language 》chinese 新建 计划 创建线程组 添加Http请求 配置元件添加请求头参数&#xff08;content-type&…

腾讯云 CODING 荣获 TiD 质量竞争力大会 2023 软件研发优秀案例

点击链接了解详情 8 月 13-16 日&#xff0c;由中关村智联软件服务业质量创新联盟主办的第十届 TiD 2023 质量竞争力大会在北京国家会议中心召开。本次大会以“聚焦数字化转型 探索智能软件研发”为主题&#xff0c;聚焦智能化测试工程、数据要素、元宇宙、数字化转型、产融合作…

报名开启 | HarmonyOS第一课“营”在暑期系列直播

<HarmonyOS第一课>2023年再次启航&#xff01; 特邀HarmonyOS布道师云集华为开发者联盟直播间 聚焦HarmonyOS 4版本新特性 邀您一同学习赢好礼&#xff01; 你准备好了吗&#xff1f; ↓↓↓预约报名↓↓↓ 点击关注了解更多资讯&#xff0c;报名学习

CS:GO升级 Linux不再是“法外之地”

在前天的VAC大规模封禁中&#xff0c;有不少Linux平台的作弊玩家也迎来了“迟到”的VAC封禁。   一直以来&#xff0c;Linux就是VAC封禁的法外之地。虽然大部分玩家都使用Windows平台进行游戏。但实际上&#xff0c;使用Linux畅玩CS:GO的玩家也不在少数。 以前V社主要打击W…

LVS - DR

LVS-DR 数据流向 客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。Director Server 和 Real Server 在同一个网络中&#xff0c;数据通过二层数据链路层来传…

商城-学习整理-高级-商城业务-商品上架es(十)

目录 一、商品上架1、sku在ES中存储模型分析2、nested数据类型场景3、构造基本数据&#xff08;商品上架&#xff09; 二、首页1、项目介绍2、整合thymeleaf&#xff08;spring-boot下模板引擎&#xff09;渲染页面3、页面修改不重启服务器实时更新4、渲染二级三级数据 三、搭建…

「UG/NX」Block UI 面收集器FaceCollector

✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#

LeetCode150道面试经典题-- 求算数平方根(简单)

1.题目 给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.5) 或者 x ** 0.5 。 2.示例 …

【目标检测中对IoU的改进】GIoU,DIoU,CIoU的详细介绍

文章目录 1、IoU2、GIoU(Generalized Intersection over Union)3、DIoU4、CIoU 1、IoU IoU为交并比&#xff0c;即对于pred和Ground Truth&#xff1a;交集/并集 1、IoU可以作为评价指标使用&#xff0c;也可以用于构建IoU loss 1 - IoU 缺点&#xff1a; 2、对于pred和GT相…

机器学习|DBSCAN 算法的数学原理及代码解析

机器学习&#xff5c;DBSCAN 算法的数学原理及代码解析 引言 聚类是机器学习领域中一项重要的任务&#xff0c;它可以将数据集中相似的样本归为一类。DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种是一种经典的密度聚类…

博客系统之单元测试

对博客系统进行单元测试 1、测试查找已存在的用户 测试名称 selectByUsernameTest01 测试源码 //查找用户&#xff0c;存在 Test public void selectByUsernameTest01 () { UserDao userDao new UserDao(); String ret1 userDao.selectByUsername("张三").toStr…

全开放式耳机什么品牌好?全开放式耳机推荐

​在音乐的世界中&#xff0c;开放式耳机提供了更真实、更通透的音质体验&#xff0c;开放式耳机采用不入耳设计&#xff0c;佩戴更为稳固舒适&#xff0c;还允许外界的声音自由地流入&#xff0c;使你在享受音乐的同时&#xff0c;也能保持对周围环境的感知&#xff0c;户外运…

自动驾驶卡车量产-第一章-用户需求

1、中国干线物流行业现状 万亿级市场&#xff0c;规模巨大。由中重卡承运的干线运输占到整体公路货运市场的82%&#xff0c;全国中重卡保有量约730 万台1&#xff0c;市场规模达4.6 万亿元1&#xff0c;体量全球第一&#xff0c;超过同城物流及乘用出租市场规模之和。同样&…

SpringBoot 的 RedisTemplate、Redisson

一、Jedis、Lettuce、Redisson的简介 优先使用Lettuce&#xff0c; 需要分布式锁&#xff0c;分布式集合等分布式的高级特性&#xff0c;添加Redisson结合使用。 对于高并发&#xff0c;1000/s的并发&#xff0c;数据库可能由行锁变成表锁&#xff0c;性能下降会厉害。 1.1、…

卷积神经网络全解!CNN结构、训练与优化全维度介绍!

目录 一、引言1.1 背景和重要性1.2 卷积神经网络概述 二、卷积神经网络层介绍2.1 卷积操作卷积核与特征映射卷积核大小多通道卷积 步长与填充步长填充 空洞卷积&#xff08;Dilated Convolution&#xff09;分组卷积&#xff08;Grouped Convolution&#xff09; 2.2 激活函数R…

Eclipse集成MapStruct

Eclipse集成MapStruct 在Eclipse中添加MapStruct依赖配置Eclipse支持MapStruct①安装 m2e-aptEclipse Marketplace的方式安装Install new software的方式安装&#xff08;JDK8用到&#xff09; ②添加到pom.xml 今天拿到同事其他项目的源码&#xff0c;导入并运行的时候抛出了异…