基于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路PWMHAL_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++;//计数,每循环一次+1if(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 位置为1UART1_RX_STA |= 0x8000;//=======中断信息处理=======//模式切换if (!strcmp((const char *)UART1_RX_Buffer, "M1")) {Mode = 1;HAL_ADC_Stop_DMA(&hadc1);//停止ADC DMAMX_ADC1_Init();//初始化ADC1HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4); //开启ADC DMAprintf("摇杆模式\r\n");}else if(!strcmp((const char *)UART1_RX_Buffer, "M2")) {Mode = 2;HAL_ADC_Stop_DMA(&hadc1);//停止ADC DMAMX_ADC1_Mode2_Init();//自定义初始化ADC1,把1234换成5678采集电位器HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4); //开启ADC DMAprintf("示教器模式\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 位置为1UART1_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,再进行。

(待更新)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分布式 - 消息队列Kafka:Kafka 消费者消费位移的提交方式

文章目录 1. 自动提交消费位移2. 自动提交消费位移存在的问题&#xff1f;3. 手动提交消费位移1. 同步提交消费位移2. 异步提交消费位移3. 同步和异步组合提交消费位移4. 提交特定的消费位移5. 按分区提交消费位移 4. 消费者查找不到消费位移时怎么办&#xff1f;5. 如何从特定…

不花一分钱,利用免费电脑软件将视频MV变成歌曲音频MP3

教程 1.点击下载电脑软件下载地址&#xff0c;点击下载&#xff0c;安装。&#xff08;没有利益关系&#xff0c;没有打广告&#xff0c;只是单纯教学&#xff09; 2.安装完成后&#xff0c;点击格式工厂 3.然后如图所示依次&#xff0c;点击【音频】->【-MP3】 3.然后点击…

C#反编译工具ILSPY

ILSPY ILSpy 是一个开源的.Net程序集浏览器和反编译工具。 Visual Studio 2022附带了默认情况下启用的F12反编译支持&#xff08;使用我们的引擎v7.1&#xff09;。 在Visual Studio 2019中&#xff0c;您必须手动启用F12支持。转到“工具”/“选项”/“文本编辑器”/C#/Adva…

Kotlin协程runBlocking并发launch,Semaphore同步1个launch任务运行

Kotlin协程runBlocking并发launch&#xff0c;Semaphore同步1个launch任务运行 <dependency><groupId>org.jetbrains.kotlinx</groupId><artifactId>kotlinx-coroutines-core</artifactId><version>1.7.3</version><type>pom&…

2023国赛数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米&#xff0c;宽为12米&…

LVS+keepalived群集

keepalived概述 keepalived软件就是通过vrrp协议来实现高可用功能 vrrp通信原理 vrrp就是虚拟路由冗余协议&#xff0c;它的出现就是为了解决静态路由的单点故障 vrrp是通过一种竞选的一种协议机制&#xff0c;来将路由交给某台vrrp路由器 vrrp用的IP是多播的方式&#xff…

设计模式之原型模式

文章目录 一、介绍二、实现步骤三、案例四、应用五、细胞分裂六、改造细胞分裂逻辑七、总结 一、介绍 原型模式属于创建型设计模式&#xff0c;用于创建重复的对象&#xff0c;且同时又保证了性能。 该设计模式的好处是将对象的创建与调用方分离。 其目的就是**根据一个对象…

【UE】Web Browser内嵌网页在场景中的褪色问题

使用WebBrowser放置在场景中时&#xff0c;网页颜色会出现异常的褪色。 这是因为 Web 浏览器插件以 sRGB 格式输出其颜色数据&#xff0c;而 Widget/3D Widget 需要线性 RGB 格式的数据。 可以通过创建在 3D Widget 中使用的新材质&#xff08;而不是默认的 Widget3DPassthr…

数字化施工:解决传统施工难题,提高施工效率和质量的行业革命

建筑行业是我国国民经济的重要组成部分&#xff0c;也是支柱性产业之一。然而&#xff0c;建筑业同时也是一个安全事故多发的高风险行业。如何加强施工现场的安全管理&#xff0c;降低事故发生的频率&#xff0c;避免各种违规操作和不文明施工&#xff0c;提高建筑工程的质量&a…

chatglm2-6b模型在9n-triton中部署并集成至langchain实践 | 京东云技术团队

一.前言 近期&#xff0c; ChatGLM-6B 的第二代版本ChatGLM2-6B已经正式发布&#xff0c;引入了如下新特性&#xff1a; ①. 基座模型升级&#xff0c;性能更强大&#xff0c;在中文C-Eval榜单中&#xff0c;以51.7分位列第6&#xff1b; ②. 支持8K-32k的上下文&#xff1b…

955 神仙公司名单

你是否想过&#xff0c;有一种公司&#xff0c;每天上班不打卡&#xff0c;没有绩效考核&#xff0c;员工可以带着宠物上班&#xff0c;还有公司专门的健身房和游戏室&#xff1f;这样的公司&#xff0c;真的存在&#xff01;今天我们就来探秘这个传说中的955神仙公司&#xff…

java并发:synchronized锁详解

背景&#xff1a; 在java多线程当中&#xff0c;我们总有遇到过多个线程操作一个共享数据时&#xff0c;而这个最后的代码执行结果并没有按照我们的预期一样得到正确的结果。此时我们就需要让代码执行在操作共享变量时&#xff0c;要等一个线程操作完毕时&#xff0c;另一个线程…

【云原生,k8s】Helm应用包管理器介绍

目录 一、为什么需要Helm&#xff1f; &#xff08;一&#xff09;Helm介绍 &#xff08;二&#xff09;Helm有3个重要概念&#xff1a; &#xff08;三&#xff09;Helm特点 二、Helm V3变化 &#xff08;一&#xff09;架构变化 &#xff08;二&#xff09;自动创建名…

从其他地方复制的内容无法粘贴到idea中

问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 使用 idea 开发的时候&#xff0c;从其他地方复制的内容无法粘贴到 idea中&#xff0c;idea的版本是 2023.2 解决方案&#xff1a; 提示&#xff1a;这里填写该问题的具体解决方案&#xff1a; 网上查找资料…

redis — 基于Spring Boot实现redis延迟队列

1. 业务场景 延时队列场景在我们日常业务开发中经常遇到&#xff0c;它是一种特殊类型的消息队列&#xff0c;它允许把消息发送到队列中&#xff0c;但不立即投递给消费者&#xff0c;而是在一定时间后再将消息投递给消费者。延迟队列的常见使用场景有以下几种&#xff1a; 在…

SpringBoot3集成Kafka

标签&#xff1a;Kafka3.Kafka-eagle3&#xff1b; 一、简介 Kafka是一个开源的分布式事件流平台&#xff0c;常被用于高性能数据管道、流分析、数据集成和关键任务应用&#xff0c;基于Zookeeper协调的处理平台&#xff0c;也是一种消息系统&#xff0c;具有更好的吞吐量、内…

【云原生】3分钟快速在Kubernetes部署Prometheus2.42+Grafana9.5.1+Alertmanager0.25

文章目录 1、简介2、GitHub地址3、环境信息4、安装5、访问Grafana1、简介 Prometheus-operator帮助我们快速创建Prometheus+Grafana+Alertmanager等服务,而kube-prometheus更加完整的帮助我们搭建全套监控体系,这包括部署多个 Prometheus 和 Alertmanager 实例, 指标导出器…

axios / fetch 实现 stream 流式请求

axios 是一个支持node端和浏览器端的易用、简洁且高效的http库。本文主要介绍 axios 如何实现 stream 流式请求&#xff0c;注意这里需要区分 node 环境和浏览器环境。 一、node端 代码演示&#xff1a; const axios require(axios);axios({method: get,url: http://tiven.c…

系统架构设计师-信息安全技术(2)

目录 一、安全架构概述 1、信息安全所面临的威胁 二、安全模型 1、安全模型的分类 2、BLP模型 3、Biba 模型 4、Chinese Wall模型 三、信息安全整体架构设计 1、WPDRRC模型 2、各模型的安全防范功能 四、网络安全体系架构设计 1、开放系统互联安全体系结构 2、安全服务与安…

[保研/考研机试] KY103 2的幂次方 上海交通大学复试上机题 C++实现

题目链接&#xff1a; KY103 2的幂次方 https://www.nowcoder.com/share/jump/437195121691999575955 描述 Every positive number can be presented by the exponential form.For example, 137 2^7 2^3 2^0。 Lets present a^b by the form a(b).Then 137 is present…