STM32实现四自由度机械臂(SG90舵机)多功能控制(软件篇freertos)

书接上回的硬件篇STM32控制四自由度机械臂(SG90舵机)(硬件篇)(简单易复刻)-CSDN博客

此时硬件平台已经搭建完毕,软件总共设计了三种模式,分别为

模式1:摇杆&蓝牙模式,此模式下可用摇杆或手机操作机械臂

模式2:示教器模式,此模式下由电位器控制机械臂

模式3:执行记忆动作,此模式下机械臂重复数组/链表中存储的动作

三种模式的切换以及存储动作可由按键或者手机蓝牙切换。

代码使用了FREERTOS操作系统,接下来我会将代码分开解析,这样大家看完后再结合起来回顾整体,就会更加容易理解了,请大家不要着急,慢慢看下去,一定能够复刻成功。

先说模式1(摇杆&蓝牙)的代码,其中cmd_BLE是蓝牙的数据,adc_dma[0-3]是摇杆的数据。

void check_sg_cmd()//摇杆&蓝牙模式控制舵机函数
{check_A();//舵机Acheck_B();//舵机Bcheck_C();//舵机Ccheck_D();//舵机D
}
//舵机A,夹爪	CH4_B11-D1;adc4_A3
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-D2;adc3_A2
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-D3;adc2_A1
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-D0;adc1_A0
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]--;}}
}

再看模式2(示教器)的代码,先通过translate()获取舵机目标角度,再通过reach_target()控制舵机达到目标角度,还有什么不懂的代码里的注释也很详细。

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()//示教器获取舵机目标角度函数
{//adc_dma[4-7]表示四个电位器,将电位器数据转换成目标角度angle_target[3] = (uint8_t)((double)adc_dma[7] / 22.75)/2;angle_target[2] = (uint8_t)((double)adc_dma[6] / 22.75);angle_target[1] = (uint8_t)((double)adc_dma[5] / 22.75) - 10;angle_target[0] = 180 - (uint8_t)((double)adc_dma[4] / 22.75);//180减只是改变一下电位器方向,不影响总体//限幅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;
}

再看模式3(记忆动作)的代码,memory[i][j]数组里存储的就是记忆数据。

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]++;}}
}

到这里上述说的三个模式的代码都在代码工程的PWM.c中可以找到。 

接下来给大家下说一下freertos.c里面主要的三个任务 ,分别是舵机任务Start_check_angle(),主要用于在不同的模式下控制舵机,在舵机任务我们也能看到上述提到了三个模式下的函数,蓝牙串口S任务tart_usart_show(),主要用于打印信息到手机app上,这里并没有通过手机控制机械臂的函数,那个在串口函数里,后面会说,oled显示屏任务Start_OLED_Task(),和蓝牙串口任务类似,只不过它是打印信息到oled显示屏上。

void Start_check_angle(void const * argument)//舵机任务
{/* USER CODE BEGIN Start_check_angle *///开启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);/* 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);//通过调整此延时可以改变机械臂运行速度}/* USER CODE END Start_check_angle */
}void Start_usart_show(void const * argument)//蓝牙串口任务
{/* USER CODE BEGIN Start_usart_show *//* Infinite loop */for(;;){printf("Angle = {%d, %d, %d, %d}\r\n",angle[0],angle[1],angle[2],angle[3]);printf("adc_dma1 = {%d, %d, %d, %d}\r\n",adc_dma[0],adc_dma[1],adc_dma[2],adc_dma[3]);printf("adc_dma2 = {%d, %d, %d, %d}\r\n",adc_dma[4],adc_dma[5],adc_dma[6],adc_dma[7]);printf("\r\n");osDelay(1000);}/* USER CODE END Start_usart_show */
}void Start_OLED_Task(void const * argument)//oled显示屏任务
{/* USER CODE BEGIN Start_OLED_Task */Oled_Init();Oled_Clear();/* Infinite loop */for(;;){//串口数据的字符串拼装,speed是格子,每个格子1cmsprintf(speedMes,"A: %d ",angle[0]);sprintf(speedMes1,"B: %d ",angle[1]);sprintf(speedMes2,"C: %d ",angle[2]);sprintf(speedMes3,"D: %d ",angle[3]);sprintf(speedMes4,"Mode %d ",Mode);sprintf(speedMes5,"S %d ",i);Oled_Show_Str(1,5,speedMes);Oled_Show_Str(1,69,speedMes1);Oled_Show_Str(2,5,speedMes2);Oled_Show_Str(2,69,speedMes3);Oled_Show_Str(4,0,speedMes4);Oled_Show_Str(4,64,speedMes5);osDelay(500);}/* USER CODE END Start_OLED_Task */
}

接下来给大家说一下有关蓝牙部分的相关代码以及蓝牙手机app的使用

首先是usart.c里的一段代码,这里最重要的部分就是通过蓝牙模式控制机械臂时候,切换模式只需发送M1,M2,M3就可切换相应模式,而执行相关动作时,必须在发送的指令前面加上A开头,例如Ao就是夹爪开。

/*机械臂控制模式,默认为1
1:摇杆控制	
2:示教器控制
3:执行记忆动作
*/
uint8_t Mode = 1;/*蓝牙控制机械臂指令:
s/m	停/储存当前动作
l/r 左右
u/d 上下
f/b 前后
o/c 开合*/
uint8_t cmd_BLE = 's';// 串口中断:接收完成回调函数,收到一个数据后,在这里处理
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;//=======中断信息处理=======//模式切换在手机蓝牙app中只需发送M1,M2,M3即可切换模式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");}//蓝牙指令的切换则必须以A开头,例:发送Ao就是夹爪开else if(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);}
}

然后是PWM.c中的if_BLE_cmd(),这里蓝牙控制记忆动作模式同样需要在发送指令前面加上一个A开头才可以。

//蓝牙控制记忆动作模式
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;}
}

最后介绍一下HC蓝牙助手手机app(APP会在文末与代码一起开源分享给大家)的使用方法 ,话不多说,直接上图。

接下来再给大家介绍一下按键控制的代码,A0按键切换模式1(摇杆&蓝牙模式),A1按键切换模式2(示教器模式),B4按键切换模式3(执行记忆动作),B5按键用于存储动作。

void Callback01(void const * argument)
{/* USER CODE BEGIN Callback01 */anti_shake = 0;/* USER CODE END Callback01 */
}void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)//按键任务
{switch(GPIO_Pin){case GPIO_PIN_0://A0按键HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_RESET);//led亮HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_SET);//led灭Mode = 1;printf("摇杆模式\r\n");		break;case GPIO_PIN_1://A1按键HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_SET);//led灭HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);//led亮Mode = 2;printf("示教模式\r\n");		break;case GPIO_PIN_4://B4按键if(anti_shake == 0){anti_shake = 1;Mode = 3;HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_RESET);//led亮HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);//led亮printf("执行记忆动作\r\n");osTimerStart(myTimer01Handle,800);}break;case GPIO_PIN_5://B5按键if(anti_shake == 0){//软件消抖		      anti_shake = 1;			osTimerStart(myTimer01Handle,800);//存储动作		if(i<location_cnt){for(j=0;j<4;j++){memory[i][j] = angle[j];}printf("储存动作\r\n");i++;}else if(i>=9){printf("动作已满\r\n");}					}break;			}
}

到此为止,核心代码基本上都介绍差不多了,还有一些例如i2c.c,OLED.c显示屏引脚初始化用,gpio.c按键以及led初始化使用,adc.c,dma.c摇杆以及电位器初始化使用,tim.c舵机pwm初始化使用,这些代码主要是初始化相关引脚,而相关引脚对应在硬件篇我已经介绍过了,当然对于这些代码感兴趣的也可以自己去代码工程中看。

最后再带大家简单看一下main.c中的代码:

int main(void)
{/* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();  //GPIO初始化,四个按键和两个LEDMX_DMA_Init();   //DMA初始化MX_ADC1_Init();  //ADC初始化,2个摇杆用and4个电位器用MX_TIM2_Init();  //定时器PWM初始化,舵机用MX_USART1_UART_Init();//串口初始化,蓝牙用MX_I2C1_Init();  //I2C初始化,oled显示屏用/* USER CODE BEGIN 2 */printf("Start\r\n");//程序开始运行HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,8); //开启ADC和DMAHAL_Delay(500);/* Call init function for freertos objects (in freertos.c) */MX_FREERTOS_Init();//freertos任务初始化/* Start scheduler */osKernelStart();//freertos开启任务调度while (1){}
}

相信大家结合这篇软件篇代码解说来阅读工程代码看到这里就算不会freertos的人也已经复刻成功了,创造不易,感谢c友的一键三联,球球了,这个对我真的很重要! 

工程文件和蓝牙资料的链接给大家放这里了!!!http://通过网盘分享的文件:stm32机械臂源代码.zip 链接: https://pan.baidu.com/s/1o1IBFdVu9Ybk9gXJIZTY8Q 提取码: 0531

 

 

 

 

 

 

 

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

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

相关文章

docker常用命令集(2)

接前一篇文章&#xff1a;docker常用命令集&#xff08;1&#xff09; 本文内容参考&#xff1a; Docker build 命令 | 菜鸟教程 docker基础(二)之docker build-CSDN博客 Docker push 命令 | 菜鸟教程 Docker pull 命令 | 菜鸟教程 特此致谢&#xff01; 3. docker build …

舒尔特方格训练小游戏流量主微信小程序开源

功能特点 游戏核心功能&#xff1a; 随机生成55舒尔特方格 按顺序点击数字1-25 实时计时和尝试次数统计 错误点击反馈&#xff08;视觉和触觉&#xff09; 数据统计&#xff1a; 记录每次完成时间 保存历史最佳成绩 保存最近5次尝试记录 统计尝试次数&#xff08;错误点击&…

在Spring Boot 开发中 Bean 的声明和依赖注入最佳的组合方式是什么?

在Spring Boot 开发中&#xff0c;社区和 Spring 官方已经形成了一套非常明确的最佳实践。这个黄金组合就是&#xff1a; Bean 声明&#xff1a;使用构造型注解&#xff08;Stereotype Annotations&#xff09;&#xff0c;如 Service, Repository, Component 等。依赖注入&…

Oxygen XML Editor 26.0编辑器

Oxygen XML Editor 26.0编辑器 欢迎使用Oxygen XML Editor 26.0编辑器准备工作安装javajdk安装jdk验证Oxygen XML Editor 26.0安装欢迎使用Oxygen XML Editor 26.0编辑器 准备工作安装java Java官网下载地址:https://www.oracle.com/java/technologies/ Oxygen XML Editor 2…

AWS Lambda Container 方式部署 Flask 应用并通过 API Gateway 提供访问

前言 一年前写过一篇 Lambda 运行 Flask 应用的博文: https://lpwmm.blog.csdn.net/article/details/139756140 当时使用的是 ZIP 包方式部署应用代码, 对于简单的 API 开发用起来还是可以的, 但是如果需要集成到 CI/CD pipeline 里面就有点不太优雅. 本文将介绍使用容器方式…

React虚拟DOM的进化之路

引言 在Web前端开发中&#xff0c;用户交互的流畅性和页面性能一直是核心挑战。早期&#xff0c;开发者直接操作真实DOM&#xff08;Document Object Model&#xff09;时&#xff0c;频繁的重排&#xff08;reflow&#xff09;和重绘&#xff08;repaint&#xff09;导致性能…

(7)机器学习小白入门 YOLOv:机器学习模型训练详解

— (1)机器学习小白入门YOLOv &#xff1a;从概念到实践 (2)机器学习小白入门 YOLOv&#xff1a;从模块优化到工程部署 (3)机器学习小白入门 YOLOv&#xff1a; 解锁图片分类新技能 (4)机器学习小白入门YOLOv &#xff1a;图片标注实操手册 (5)机器学习小白入门 YOLOv&#xff…

初识MySQL(三)之主从配置与读写分离实战

主重复制 主重复制原理master开启二进制日志记录slave开启IO进程&#xff0c;从master中读取二进制日志并写入slave的中继日志slave开启SQL进程&#xff0c;从中继日志中读取二进制日志并进行重放最终&#xff0c;达到slave与master中数据一致的状态&#xff0c;我们称作为主从…

RabbitMQ面试精讲 Day 2:RabbitMQ工作模型与消息流转

【RabbitMQ面试精讲 Day 2】RabbitMQ工作模型与消息流转 开篇 欢迎来到"RabbitMQ面试精讲"系列的第2天&#xff0c;今天我们将深入探讨RabbitMQ的工作模型与消息流转机制。这是面试中最常被问到的核心知识点之一&#xff0c;90%的RabbitMQ面试都会涉及消息流转流程…

基于SpringBoot3集成Kafka集群

1. build.gradle依赖引入 implementation org.springframework.kafka:spring-kafka:3.2.02. 新增kafka-log.yml文件 在resource/config下面新增kafka-log.yml&#xff0c;配置主题与消费者组 # Kafka消费者群组 kafka:consumer:group:log-data: log-data-grouptopic:log-data: …

wpf Canvas 导出图片

在WPF中将Canvas导出为图片主要涉及以下关键步骤和注意事项: ‌核心实现方法‌使用RenderTargetBitmap将Canvas渲染为位图,再通过PngBitmapEncoder保存为PNG文件。需注意临时移除Canvas的布局变换(LayoutTransform)以避免渲染异常‌1。示例代码片段:CanvasExporter.cs pu…

lvs负载均衡实操模拟

目录 一、配置准备 二、NET模式 修改LVS端 开启路由 修改对内网卡 ens160 修改对外网卡 ens224 加载网卡配置文件 修改web1端 修改网卡信息 重启网络 检测 配置web2 检测 验证配置是否正常 启动nginx服务 验证以上配置 添加lvs规则 验证 三、DR模式 修改…

Spring Boot 是如何简化 IoC 的配置的?

首先Spring Boot 并没有发明新的 IoC 理论&#xff0c;它做的也不是替换掉 Spring IoC 容器。相反&#xff0c;Spring Boot 是 Spring IoC 思想的实践者和简化者。它通过**“约定优于配置”&#xff08;Convention over Configuration&#xff09;**的理念&#xff0c;将原本繁…

Go语言中的组合式接口设计模式

文章目录Go语言中的组合式接口设计模式背景和需求组合式接口设计Go语言中的组合式接口设计模式 背景和需求 在微服务架构和复杂业务系统中&#xff0c;我们经常需要调用多个外部服务或内部模块。传统的做法是将所有方法都放在一个大接口中&#xff0c;但这种设计会导致接口臃…

React - createPortal

什么是createPortal&#xff1f;注意这是一个API&#xff0c;不是组件&#xff0c;他的作用是&#xff1a;将一个组件渲染到DOM的任意位置&#xff0c;跟Vue的Teleport组件类似。用法 import { createPortal } from react-dom;const App () > {return createPortal(<div…

Cursor的使用

Cursor的使用 Ctrl L 打开历史对话记录 Tab智能助手 1.单行/多行补全 已有代码片段&#xff1a; //需求&#xff1a;写一个工具类计算数组平均值 public class ArrayUtils {//按tab会完成补全 }按tab键- Cursor 自动生成代码: //需求&#xff1a;写一个工具类计算数组平均值 p…

17.使用DenseNet网络进行Fashion-Mnist分类

17.1 DenseNet网络结构设计import torch from torch import nn from torchsummary import summary #卷积层 def conv_block(input_channels,num_channels):netnn.Sequential(nn.BatchNorm2d(input_channels),nn.ReLU(),nn.Conv2d(input_channels,num_channels,kernel_size3,pad…

网安系列【16】之Weblogic和jboss漏洞

文章目录一 Weblogic1.1 Weblogic相关漏洞1.2 Weblogic漏洞发现1.3 Weblogic漏洞利用二 Jboss2.1 Jboss漏洞2.2 Jboss识别与漏洞利用一 Weblogic WebLogic 是由 Oracle公司 开发的一款基于Java EE&#xff08;现称Jakarta EE&#xff09;的企业级应用服务器&#xff0c;主要用…

Unity URP + XR 自定义 Skybox 在真机变黑问题全解析与解决方案(支持 Pico、Quest 等一体机)

在使用 Unity 的 URP 渲染管线开发 XR 应用&#xff08;如 Pico Neo、Pico 4、Quest 2/3 等一体机&#xff09;时&#xff0c;很多开发者遇到一个奇怪的问题&#xff1a;打包后&#xff0c;Skybox&#xff08;天空盒&#xff09;在某些角度下突然变黑&#xff0c;只在转动头部后…

Cursor、飞算JavaAI、GitHub Copilot、Gemini CLI 等热门 AI 开发工具合集

Cursor&#xff1a;代码编写的智能伙伴​Cursor 是 Anysphere 公司推出的一款 AI 编程工具&#xff0c;它基于微软开源代码编辑器 VS Code 开发&#xff0c;将 AI 技术深度整合到开发人员的工作流程中。Cursor 的功能十分强大&#xff0c;不仅能够自动用纯英文编写代码&#xf…