基于STM32+FreeRTOS的四轴机械臂

这篇具有很好参考价值的文章主要介绍了基于STM32+FreeRTOS的四轴机械臂。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

代码:

注释写的较少,但本文出现的代码都有注释,所以请直接在本文里看注释

项目概述:

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

 二 裸机测试功能

1.摇杆控制

接线:

CubeMX配置:

代码

2.蓝牙控制

接线:

CubeMX配置

代码:

3.示教器控制

4.记录动作信息

5.执行记录的动作

注:

三 FreeRTOS上完成项目

1.加入IIC的OLED屏显 和 动作执行(数组版) 

CubeMX配置:

 代码:

2.链表的增删遍历实现动作记忆和执行

3.SPI扩容

功能测试

w25q128芯片分析与功能实现


代码:

注释写的较少,但本文出现的代码都有注释,所以请直接在本文里看注释

 链接:https://pan.baidu.com/s/14GJF8ZCnkNkKkz5R0uJwOA?pwd=1111 
提取码:1111 
--来自百度网盘超级会员V4的分享

项目概述:

基于STM32的FreeRTOS四轴机械臂

        基于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配置:

基本配置(后面每个工程都是这一套)

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

 基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

 ADC1:4路

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

 基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

 DMA:搬运ADC数据的

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

PWM输出:选用799*1799,这样可以把舵机有效的 0.5~2.5ms / 20ms 这个区间分成180段,对应0~180度。

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

 usart,9600波特率给蓝牙模块用。

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

然后 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串口打印信息 

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

2.蓝牙控制

这里提前写了一点示教器的业务代码,执行切换模式操作会切换获取摇杆模拟值还是电位器模拟值。

注意:我这里整活儿搞了个ADC通道切换,但实测还是存在一点问题,你们直接使用8通道一起就好。

接线:

先不使用示教器,但是可以先测试一下功能,切换模式和采集一下数据。

把四个旋钮电位器接好,四根线接到ADC 5 6 7 8

CubeMX配置

打开串口中断,中断接收数据。 

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

我这里是ADC再开四个,其它不配置。

你们开启共8个之后把下面个数也4改成8,新的组别5678也改成IN4 5 6 7 四个通道

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

代码:

都写在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上完成项目

1.加入IIC的OLED屏显 和 动作执行(数组版) 

下面是移植到FreeRTOS操作系统上运行,没法介绍太详细,建议先系统学一下STM32 HAL开发以及FreeRTOS,再进行。

快速简述,就是把需求分给多个任务去执行,

一个任务负责角度信息处理,

一个任务负责串口发送数据

一个任务负责OLED屏显

且这里开始把adc改成直接测8组了

接线,把OLED的SDA和SCL对应板子接好 

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

CubeMX配置:

打开IIC

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

 设置4个外部中断,把原来的 ADC IN1~7 ==> IN2~9,因为我板载两个按钮位于A0和A1

然后B4 B5我自己外接了两个按钮。中断都是下降沿触发

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

FreeRTOS配置:使用了V1版本,创建三个任务,优先级都设为普通

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

 基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

打开定时器用于两个外部中断按钮B4 B5的定时器消抖,我这两个没有硬件防抖,如果你们的按钮有电容,就不用。

 基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

使用单次定时器 

 基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

 代码:

usart.c

/* USER CODE BEGIN 0 */
#include "stdio.h"
#include "string.h"
#include "PWM.h"

#include "adc.h"
#include "dma.h"


/*机械臂控制模式,默认为1
1:摇杆控制	
2:示教器控制
3:执行记忆动作
*/
uint8_t Mode = 1;

/*蓝牙控制机械臂指令:
s/m	停/储存当前动作
l/r 左右
u/d 上下
f/b 前后
o/c 开合*/
uint8_t cmd_BLE = 's';

extern uint16_t adc_dma[8];//DMA搬运的ADC采集值
extern uint8_t angle[4];

uint8_t k;

//覆写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;

						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");
						
					}

					//获取蓝牙控制指令
					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 位置为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        解释注释在前面说过

#include "PWM.h"
#include "main.h"
#include "tim.h"
#include "stdio.h"
#include "string.h"



extern uint16_t adc_dma[8];//DMA搬运的ADC采集值
extern uint8_t angle[4];//舵机角度
extern uint8_t Mode;
extern uint8_t cmd_BLE;

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;

//根据输入的角度获取对应pwm占空比参数
uint8_t Angle(uint8_t pwm_pulse)
{
	return pwm_pulse + 44;
}

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();
}

//舵机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]--;
		}
	}

}

PWM.h

#ifndef __PWM_H__
#define __PWM_H__

#define location_cnt 20
 
//根据输入的角度获取对应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 check_sg_cmd(void);

void if_BLE_cmd(void);

void translate(void);

void get_target(void);
	
void reach_target(void);

#endif

OLED.c

#include "OLED.h"
#include "i2c.h"
#include "oledfont.h"


void Oled_Write_Cmd(uint8_t OLED_cmd)
{
	HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT, &OLED_cmd, 1, 0xff);
}


void Oled_Write_Data(uint8_t OLED_data)
{
	HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT, &OLED_data, 1, 0xff);
}

//OLED初始化代码,直接复制粘贴
void Oled_Init(void){
	Oled_Write_Cmd(0xAE);//--display off
	Oled_Write_Cmd(0x00);//---set low column address
	Oled_Write_Cmd(0x10);//---set high column address
	Oled_Write_Cmd(0x40);//--set start line address
	Oled_Write_Cmd(0xB0);//--set page address
	Oled_Write_Cmd(0x81); // contract control
	Oled_Write_Cmd(0xFF);//--128
	Oled_Write_Cmd(0xA1);//set segment remap
	Oled_Write_Cmd(0xA6);//--normal / reverse
	Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
	Oled_Write_Cmd(0x3F);//--1/32 duty
	Oled_Write_Cmd(0xC8);//Com scan direction
	Oled_Write_Cmd(0xD3);//-set display offset
	Oled_Write_Cmd(0x00);//
	Oled_Write_Cmd(0xD5);//set osc division
	Oled_Write_Cmd(0x80);//
	Oled_Write_Cmd(0xD8);//set area color mode off
	Oled_Write_Cmd(0x05);//
	Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
	Oled_Write_Cmd(0xF1);//
	Oled_Write_Cmd(0xDA);//set com pin configuartion
	Oled_Write_Cmd(0x12);//
	Oled_Write_Cmd(0xDB);//set Vcomh
	Oled_Write_Cmd(0x30);//
	Oled_Write_Cmd(0x8D);//set charge pump enable
	Oled_Write_Cmd(0x14);//
	Oled_Write_Cmd(0xAF);//--turn on oled panel
}
 
void Oled_Clear()
{
	unsigned char i,j; //-128 --- 127
	for(i=0;i<8;i++){
		Oled_Write_Cmd(0xB0 + i);//page0--page7
		//每个page从0列
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);
		//0到127列,依次写入0,每写入数据,列地址自动偏移
		for(j = 0;j<128;j++){
			Oled_Write_Data(0);
		}
	}
}

//在屏幕上显示
//===============================官方提供的代码============================================
void Oled_Show_Char(char row,char col,uint8_t oledChar){ //row*2-2
	unsigned int  i;
	Oled_Write_Cmd(0xb0+(row*2-2));                           //page 0
	Oled_Write_Cmd(0x00+(col&0x0f));                          //low
	Oled_Write_Cmd(0x10+(col>>4));                            //high	
	for(i=((oledChar-32)*16);i<((oledChar-32)*16+8);i++){
		Oled_Write_Data(F8X16[i]);                            //写数据oledTable1
	}
 
	Oled_Write_Cmd(0xb0+(row*2-1));                           //page 1
	Oled_Write_Cmd(0x00+(col&0x0f));                          //low
	Oled_Write_Cmd(0x10+(col>>4));                            //high
	for(i=((oledChar-32)*16+8);i<((oledChar-32)*16+8+8);i++){
		Oled_Write_Data(F8X16[i]);                            //写数据oledTable1
	}		
}
 
 
/******************************************************************************/
// 函数名称:Oled_Show_Char 
// 输入参数:oledChar 
// 输出参数:无 
// 函数功能:OLED显示单个字符
/******************************************************************************/
void Oled_Show_Str(char row,char col,char *str){
	while(*str!=0){
		Oled_Show_Char(row,col,*str);
		str++;
		col += 8;	
	}		
}

OLED.h

#ifndef __OLED_H__
#define __OLED_H__

void Oled_Init(void);
void Oled_Clear(void);
	
void Oled_Show_Str(char row,char col,char *str);
	
#endif

main.c        放了一点初始化配置

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

uint16_t adc_dma[8];//DMA搬运的ADC采集值

uint8_t angle[4] = {90,90,90,90};//舵机角度

/* USER CODE END PV */

  /* USER CODE BEGIN 2 */
	// 开启接收中断
	printf("Start\r\n");//程序开始运行
	HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,8); //开启ADC和DMA
	//系统稳定半秒
	HAL_Delay(500);
	
  /* USER CODE END 2 */

freeRTOS.c

uint8_t anti_shake = 0;//定时器按钮消抖标志位

最下面的函数就是外部中断回调函数,anti_shake为0才能通过判断,一旦进入就将anti_shake置为1,避免因为按下的抖动导致按一次触发好几次中断。通过判断之后,启动单次定时器800ms,定时器到时间后触发定时器中断回调函数,在这里再把anti_shake重新置为0。

其中由于我的板载按键0和1本身有硬件消抖,所以不用软件定时器消抖。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "PWM.h"
#include "tim.h"
#include "adc.h"
#include "OLED.h"
/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
uint8_t anti_shake = 0;//定时器按钮消抖标志位

extern uint16_t adc_dma[8];//DMA搬运的ADC采集值

extern uint8_t angle[4];//舵机角度

extern uint8_t Mode;

extern uint8_t memory[location_cnt][4];
extern uint8_t i,j;


//角度信息字符串
char speedMes[8];  //IIC发送角度数据的字符串缓冲区
char speedMes1[8];
char speedMes2[8];
char speedMes3[8];
char speedMes4[8];
char speedMes5[8];

/* USER CODE END Variables */


/* USER CODE BEGIN Header_Start_check_angle */
/**
  * @brief  Function implementing the check_angle thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_Start_check_angle */
void Start_check_angle(void const * argument)
{
  /* USER CODE BEGIN Start_check_angle */
	//开启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);
  /* 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 */
}

/* USER CODE BEGIN Header_Start_usart_show */
/**
* @brief Function implementing the usart_show thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_usart_show */
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 */
}

/* USER CODE BEGIN Header_Start_OLED_Task */
/**
* @brief Function implementing the OLED_Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_OLED_Task */
void Start_OLED_Task(void const * argument)
{
  /* USER CODE BEGIN Start_OLED_Task */
	Oled_Init();
	Oled_Clear();
  /* Infinite loop */
  for(;;)
  {
		//串口数据的字符串拼装,speed是格子,每个格子1cm
		sprintf(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 */
}


/* Callback01 function */
void Callback01(void const * argument)
{
  /* USER CODE BEGIN Callback01 */
	anti_shake = 0;
  /* USER CODE END Callback01 */
}


/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	switch(GPIO_Pin)
	{
		case GPIO_PIN_0:
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
			Mode = 1;
			printf("摇杆模式\r\n");
		
		break;
		case GPIO_PIN_1:
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
			Mode = 2;
			printf("示教模式\r\n");
		
		break;
		
		case GPIO_PIN_4:
			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");
				}
			}
			
		case GPIO_PIN_5:
			if(anti_shake == 0)
			{
				anti_shake = 1;
				
				Mode = 3;
				
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
				
				printf("执行记忆动作\r\n");
				
				osTimerStart(myTimer01Handle,800);
			}
	}
}
/* USER CODE END Application */

2.链表的增删遍历实现动作记忆和执行

创建 Memary_LinkList.c和.h文件,放链表相关代码

#include "Memary_LinkList.h"
#include "main.h"
#include "stdio.h"
#include <stdlib.h>

struct Memary//每个链表节点的结构组成
{
	uint8_t A,B,C,D,cnt;//四个角度和节点的对应编号
	
	struct Memary *next;//下一个链表节点的地址
}*head,*tail,*temp;//声明三个指针,分别指向链表头尾,和一个临时指针

void Memary_Init()//初始化链表
{
	head = (struct Memary *)malloc(sizeof(struct Memary));
	
	head -> cnt = 0;//链表里没记忆信息时编号只有0
	head -> next = NULL;
	tail = head;
	temp = head;
}


void prinrt_List()//打印整个链表的内容
{
	temp = head;
	while(1)
	{
		printf("%d:{%d,%d,%d,%d}\r\n",temp->cnt,temp->A,temp->B,temp->C,temp->D);
		if(temp->next != NULL)	temp = temp->next;
		else	break;
	}
}

void delete_List()//清空整个链表
{
	while(head->next != NULL)
	{
		temp = head;
		head = head->next;
		free(temp);
	}
	temp = head;
	head->cnt = 0;
}

void addNode(uint8_t angle[4])//记忆动作,即新增一个节点
{
	if(head->cnt != 0)//编号cnt从1开始,从头节点开始存数据
	{
		struct Memary *p = (struct Memary *)malloc(sizeof(struct Memary));

		tail->next = p;
		p->cnt = tail->cnt;
		tail = p;
	}
	tail->A = angle[0];
	tail->B = angle[1];
	tail->C = angle[2];
	tail->D = angle[3];
	tail->cnt++;
	
	temp = tail;
}

uint8_t angle_temp[4];//用于提取记忆的角度
uint8_t *p = angle_temp;//用指针传递

uint8_t *getNode()//把记忆信息传出去,自动循环传整个链表
{
	angle_temp[0] = temp->A;
	angle_temp[1] = temp->B;
	angle_temp[2] = temp->C;
	angle_temp[3] = temp->D;
	
	if(temp->next == NULL)
		temp = head;
	else
		temp = temp->next;
	
	return p;
}

uint8_t sizeof_List()//别管名字,看temp在哪用的,
{
	//反应在OLED上就是可以看见当前存储到第几个动作了或者正在执行第一个动作
	return temp->cnt;
}
#ifndef __MEMARY_LINKLIST__
#define __MEMARY_LINKLIST__

#include "main.h"

void Memary_Init(void);

void addNode(uint8_t angle[4]);

void prinrt_List(void);

void delete_List(void);

uint8_t *getNode(void);

uint8_t sizeof_List(void);

#endif

原PWM.c改成了MG90s.c

#include "MG90s.h"
#include "main.h"
#include "tim.h"
#include "stdio.h"
#include "string.h"
#include "Memary_LinkList.h"

extern uint16_t adc_dma[8];//DMA搬运的ADC采集值
extern uint8_t angle[4];//舵机角度
extern uint8_t Mode;
extern uint8_t cmd_BLE;

uint8_t i;

uint8_t angle_target[4] = {90,90,90,90};
uint8_t *p_angle_target;

uint8_t angle_target_flag = 4;//默认第一次为4



//根据输入的角度获取对应pwm占空比参数
uint8_t Angle(uint8_t pwm_pulse)
{
	return pwm_pulse + 44;
}

void getAngleFromMemary()
{	
	if(angle_target_flag == 4)
	{
		p_angle_target = getNode();
		for(i = 0;i <4;i++)
		{
			angle_target[i] = *(p_angle_target + i);
		}
	}
	
	angle_target_flag = 0;
	
	for(i=0;i<4;i++)
	{
		if(angle[i] == angle_target[i])	angle_target_flag++;
	}
}

void reach_target()
{
	for(i = 0;i <4;i++)
	{
		if(angle[i] > angle_target[i])
		{
			angle[i]--;
		}
		else if(angle[i] < angle_target[i])
		{
			angle[i]++;
		}
	}
}

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':
			
			addNode(angle);
			printf("储存动作\r\n");
			cmd_BLE = 's';
				
		break;
		
		case 'g':

			prinrt_List();
			cmd_BLE = 's';
		break;
				
		case 'D':
			
			delete_List();
			printf("已清除动作\r\n");
			cmd_BLE = 's';
		break;
	}
}

void check_sg_cmd()
{
	check_A();
	check_B();
	check_C();
	check_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]--;
		}
	}

}

FreeRTOS.c要修改的部分

//                任务1

/* USER CODE END Header_Start_check_angle */
void Start_check_angle(void const * argument)
{
  /* USER CODE BEGIN Start_check_angle */
	Memary_Init();
	//开启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);
  /* Infinite loop */
  for(;;)
  {
		if(Mode == 1)
		{
			check_sg_cmd();
		}
		else if(Mode == 2)
		{
			translate();
			reach_target();
		}
		else if(Mode == 3)
		{
			getAngleFromMemary();
			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(18);//通过调整此延时可以改变机械臂运行速度
  }
  /* USER CODE END Start_check_angle */
}


//         任务3

/* USER CODE END Header_Start_OLED_Task */
void Start_OLED_Task(void const * argument)
{
  /* USER CODE BEGIN Start_OLED_Task */
	Oled_Init();
	Oled_Clear();
  /* Infinite loop */
  for(;;)
  {
		//串口数据的字符串拼装,speed是格子,每个格子1cm
		sprintf(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 ",sizeof_List());
		
		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,54,speedMes5);
		
		
    osDelay(500);
  }
  /* USER CODE END Start_OLED_Task */
}




//         外部中断

		case GPIO_PIN_4:
			if(anti_shake == 0)
			{
				anti_shake = 1;
				osTimerStart(myTimer01Handle,800);
				
				addNode(angle);

				printf("储存动作\r\n");
			}

到这里位置,视频里我实现的内容都可以完成了。

至于这个链表最大能存多长的动作我是没去实测,反正我们玩绝对是够用了。

3.SPI扩容

 不幸的是我板子的SPI2坏了,只有SPI1正常,所以我没真正去完成这个功能。

但是这里我提供了完整的存储思路。大家可自己来实现

功能测试

 PB12设为GPIO输出引脚默认设为高电平,充当CS口,接线就是CS对CS,SCK对CLK,MOSI对DI,MISO对DO

VCC用3.3,5v没试过。基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

 在spi.c中添加        比如如果SPI1就是hspi1,SPI2就是hspi2,别的都不用动

/* USER CODE BEGIN 1 */
uint8_t spi2_read_write_byte(uint8_t data)
{
    uint8_t rec_data = 0;
    
    HAL_SPI_TransmitReceive(&hspi2, &data, &rec_data, 1, 1000);
    
    return rec_data;
}
/* USER CODE END 1 */

spi.h

/* USER CODE BEGIN Prototypes */
uint8_t spi2_read_write_byte(uint8_t data);
/* USER CODE END Prototypes */

w25q128.h        这里你CS用的哪个GPIO_OUT,W25Q128_CS_GPIO就改成对应的。

#ifndef __W25Q128_H__
#define __W25Q128_H__

#include "stdint.h"

/* W25Q128片选引脚定义 */
#define W25Q128_CS_GPIO_PORT           GPIOB
#define W25Q128_CS_GPIO_PIN            GPIO_PIN_12

/* W25Q128片选信号 */
#define W25Q128_CS(x)      do{ x ? \
                                  HAL_GPIO_WritePin(W25Q128_CS_GPIO_PORT, W25Q128_CS_GPIO_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(W25Q128_CS_GPIO_PORT, W25Q128_CS_GPIO_PIN, GPIO_PIN_RESET); \
                            }while(0)

/* FLASH芯片列表 */
#define W25Q128     0XEF17          /* W25Q128  芯片ID */

/* 指令表 */
#define FLASH_WriteEnable						0x06 
#define FLASH_ReadStatusReg1				0x05 
#define FLASH_ReadData							0x03 
#define FLASH_PageProgram						0x02 
#define FLASH_SectorErase						0x20 
#define FLASH_ChipErase							0xC7 
#define FLASH_ManufactDeviceID			0x90 

/* 静态函数 */
static void w25q128_wait_busy(void);               /* 等待空闲 */
static void w25q128_send_address(uint32_t address);/* 发送地址 */
static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen);    /* 写入page */
static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写flash,不带擦除 */

/* 普通函数 */
void w25q128_init(void);                   /* 初始化25QXX */
uint16_t w25q128_read_id(void);            /* 读取FLASH ID */
void w25q128_write_enable(void);           /* 写使能 */
uint8_t w25q128_rd_sr1(void);							/* 读取寄存器1的值 */

void w25q128_erase_chip(void);             /* 整片擦除 */
void w25q128_erase_sector(uint32_t saddr); /* 扇区擦除 */
void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen);     /* 读取flash */
void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen);    /* 写入flash */

#endif

w25q128.c

#include "w25q128.h"
#include "spi.h"
#include "stdio.h"

/**
 * @brief       初始化W25Q128
 * @param       无
 * @retval      无
 */
void w25q128_init(void)
{
		uint16_t flash_type;
    spi2_read_write_byte(0xFF); /* 清除DR的作用 */
    W25Q128_CS(1);
		flash_type = w25q128_read_id();   /* 读取FLASH ID. */
		if (flash_type == W25Q128)
			printf("检测到W25Q128芯片\r\n");
		else
			printf("读取到的芯片ID为:%x\r\n",flash_type);
			printf("未检测到W25Q128芯片\r\n");
}

/**
 * @brief       等待空闲
 * @param       无
 * @retval      无
 */
static void w25q128_wait_busy(void)
{
    while ((w25q128_rd_sr1() & 0x01) == 0x01);   /* 等待BUSY位清空 */
}

/**
 * @brief       读取W25Q128的状态寄存器1的值
 * @param       无
 * @retval      状态寄存器值
 */
uint8_t w25q128_rd_sr1(void)
{
    uint8_t rec_data = 0;
    
    W25Q128_CS(0);
    spi2_read_write_byte(FLASH_ReadStatusReg1);     /* 读状态寄存器1 */
    rec_data = spi2_read_write_byte(0xFF);
    W25Q128_CS(1);
    
    return rec_data;
}

/**
 * @brief       W25Q128写使能
 *   @note      将S1寄存器的WEL置位
 * @param       无
 * @retval      无
 */
void w25q128_write_enable(void)
{
    W25Q128_CS(0);
    spi2_read_write_byte(FLASH_WriteEnable);   /* 发送写使能 */
    W25Q128_CS(1);
}

/**
 * @brief       W25Q128发送地址
 * @param       address : 要发送的地址
 * @retval      无
 */
static void w25q128_send_address(uint32_t address)
{
    spi2_read_write_byte((uint8_t)((address)>>16));     /* 发送 bit23 ~ bit16 地址 */
    spi2_read_write_byte((uint8_t)((address)>>8));      /* 发送 bit15 ~ bit8  地址 */
    spi2_read_write_byte((uint8_t)address);             /* 发送 bit7  ~ bit0  地址 */
}

/**
 * @brief       擦除整个芯片
 *   @note      等待时间超长...
 * @param       无
 * @retval      无
 */
void w25q128_erase_chip(void)
{
    w25q128_write_enable();    /* 写使能 */
    w25q128_wait_busy();       /* 等待空闲 */
    W25Q128_CS(0);
    spi2_read_write_byte(FLASH_ChipErase);  /* 发送读寄存器命令 */ 
    W25Q128_CS(1);
    w25q128_wait_busy();       /* 等待芯片擦除结束 */
}

/**
 * @brief       擦除一个扇区
 *   @note      注意,这里是扇区地址,不是字节地址!!
 *              擦除一个扇区的最少时间:150ms
 *
 * @param       saddr : 扇区地址 根据实际容量设置
 * @retval      无
 */
void w25q128_erase_sector(uint32_t saddr)
{
    //printf("fe:%x\r\n", saddr);   /* 监视falsh擦除情况,测试用 */
    saddr *= 4096;
    w25q128_write_enable();        /* 写使能 */
    w25q128_wait_busy();           /* 等待空闲 */

    W25Q128_CS(0);
    spi2_read_write_byte(FLASH_SectorErase);    /* 发送写页命令 */
    w25q128_send_address(saddr);   /* 发送地址 */
    W25Q128_CS(1);
    w25q128_wait_busy();           /* 等待扇区擦除完成 */
}

/**
 * @brief       读取芯片ID
 * @param       无
 * @retval      FLASH芯片ID
 *   @note      芯片ID列表见: w25q128.h, 芯片列表部分
 */
uint16_t w25q128_read_id(void)
{
    uint16_t deviceid;

    W25Q128_CS(0);
    spi2_read_write_byte(FLASH_ManufactDeviceID);   /* 发送读 ID 命令 */
    spi2_read_write_byte(0);    /* 写入一个字节 */
    spi2_read_write_byte(0);
    spi2_read_write_byte(0);
    deviceid = spi2_read_write_byte(0xFF) << 8;     /* 读取高8位字节 */
    deviceid |= spi2_read_write_byte(0xFF);         /* 读取低8位字节 */
    W25Q128_CS(1);

    return deviceid;
}

/**
 * @brief       读取SPI FLASH
 *   @note      在指定地址开始读取指定长度的数据
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始读取的地址(最大32bit)
 * @param       datalen : 要读取的字节数(最大65535)
 * @retval      无
 */
void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t i;

    W25Q128_CS(0);
    spi2_read_write_byte(FLASH_ReadData);       /* 发送读取命令 */
    w25q128_send_address(addr);                /* 发送地址 */
    
    for(i=0;i<datalen;i++)
    {
        pbuf[i] = spi2_read_write_byte(0XFF);   /* 循环读取 */
    }
    
    W25Q128_CS(1);
}

/**
 * @brief       SPI在一页(0~65535)内写入少于256个字节的数据
 *   @note      在指定地址开始写入最大256字节的数据
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
 * @retval      无
 */
static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t i;

    w25q128_write_enable();    /* 写使能 */

    W25Q128_CS(0);
    spi2_read_write_byte(FLASH_PageProgram);    /* 发送写页命令 */
    w25q128_send_address(addr);                /* 发送地址 */

    for(i=0;i<datalen;i++)
    {
        spi2_read_write_byte(pbuf[i]);          /* 循环写入 */
    }
    
    W25Q128_CS(1);
    w25q128_wait_busy();       /* 等待写入结束 */
}

/**
 * @brief       无检验写SPI FLASH
 *   @note      必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
 *              具有自动换页功能
 *              在指定地址开始写入指定长度的数据,但是要确保地址不越界!
 *
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大65535)
 * @retval      无
 */
static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t pageremain;
    pageremain = 256 - addr % 256;  /* 单页剩余的字节数 */

    if (datalen <= pageremain)      /* 不大于256个字节 */
    {
        pageremain = datalen;
    }

    while (1)
    {
        /* 当写入字节比页内剩余地址还少的时候, 一次性写完
         * 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理
         */
        w25q128_write_page(pbuf, addr, pageremain);

        if (datalen == pageremain)   /* 写入结束了 */
        {
            break;
        }
        else     /* datalen > pageremain */
        {
            pbuf += pageremain;         /* pbuf指针地址偏移,前面已经写了pageremain字节 */
            addr += pageremain;         /* 写地址偏移,前面已经写了pageremain字节 */
            datalen -= pageremain;      /* 写入总长度减去已经写入了的字节数 */

            if (datalen > 256)          /* 剩余数据还大于一页,可以一次写一页 */
            {
                pageremain = 256;       /* 一次可以写入256个字节 */
            }
            else     /* 剩余数据小于一页,可以一次写完 */
            {
                pageremain = datalen;   /* 不够256个字节了 */
            }
        }
    }
}

/**
 * @brief       写SPI FLASH
 *   @note      在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
 *              SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block
 *              擦除的最小单位为Sector.
 *
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大65535)
 * @retval      无
 */
uint8_t g_w25q128_buf[4096];   /* 扇区缓存 */

void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;
    uint8_t *w25q128_buf;

    w25q128_buf = g_w25q128_buf;
    secpos = addr / 4096;       /* 扇区地址 */
    secoff = addr % 4096;       /* 在扇区内的偏移 */
    secremain = 4096 - secoff;  /* 扇区剩余空间大小 */

    //printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */
    if (datalen <= secremain)
    {
        secremain = datalen;    /* 不大于4096个字节 */
    }

    while (1)
    {
        w25q128_read(w25q128_buf, secpos * 4096, 4096);   /* 读出整个扇区的内容 */

        for (i = 0; i < secremain; i++)   /* 校验数据 */
        {
            if (w25q128_buf[secoff + i] != 0XFF)
            {
                break;      /* 需要擦除, 直接退出for循环 */
            }
        }

        if (i < secremain)   /* 需要擦除 */
        {
            w25q128_erase_sector(secpos);  /* 擦除这个扇区 */

            for (i = 0; i < secremain; i++)   /* 复制 */
            {
                w25q128_buf[i + secoff] = pbuf[i];
            }

            w25q128_write_nocheck(w25q128_buf, secpos * 4096, 4096);  /* 写入整个扇区 */
        }
        else        /* 写已经擦除了的,直接写入扇区剩余区间. */
        {
            w25q128_write_nocheck(pbuf, addr, secremain);  /* 直接写扇区 */
        }

        if (datalen == secremain)
        {
            break;  /* 写入结束了 */
        }
        else        /* 写入未结束 */
        {
            secpos++;               /* 扇区地址增1 */
            secoff = 0;             /* 偏移位置为0 */

            pbuf += secremain;      /* 指针偏移 */
            addr += secremain;      /* 写地址偏移 */
            datalen -= secremain;   /* 字节数递减 */

            if (datalen > 4096)
            {
                secremain = 4096;   /* 下一个扇区还是写不完 */
            }
            else
            {
                secremain = datalen;/* 下一个扇区可以写完了 */
            }
        }
    }
}

main函数里用这段代码测试 

  /* USER CODE BEGIN 2 */
	w25q128_init();
	
	/* 写入测试数据 */
	sprintf((char *)datatemp, "stm32f103c8t6");
	w25q128_write(datatemp, FLASH_WriteAddress, TEXT_SIZE);
	printf("数据写入完成!\r\n");
	
	/* 读出测试数据 */
	memset(datatemp, 0, TEXT_SIZE);
	w25q128_read(datatemp, FLASH_ReadAddress, TEXT_SIZE);
	printf("读出数据:%s\r\n", datatemp);
	
  /* USER CODE END 2 */

运行结果第一句首先一定是都没读取到 w25q128 这个芯片,这段判断代码在w25q128_init();里

w25q128芯片分析与功能实现

以前在这个文章里讲过 

STM32:SPI_我有在好好学习的博客-CSDN博客

总之, 整个存储空间的组成是

256个Block * 16个Sector * 16个Page * 256个字节 = 共16MB

而我们代码增删改查储存信息的单位是按照Sector为单位的,即一个扇区一个扇区的处理信息

阅读官方的芯片手册:

基于STM32+FreeRTOS的四轴机械臂,STM32,RTOS,stm32,单片机

可知存储地址为六位十六进制,刚好对应16MB

0        0        0        0        0        0

前两位表示BLOCK,16*16 即256个Block

第三位对应Sector,16个

第四位对应Page,16个

五六位对应16*16 即256个字节

所以通过设置这个六位的地址,我们就可以决定把数据存在哪个扇区

例如 3FD000 ,就表示第63Block的第13个Sector,又因为我们是按照Sector操作的

所以通过前三位来选择不同的扇区来存数据

存和读都是字符串形式。写两个函数分别把字符串转化为链表,链表转化为字符串即可。文章来源地址https://www.toymoban.com/news/detail-657487.html

到了这里,关于基于STM32+FreeRTOS的四轴机械臂的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【STM32】-串口开发经验分享-基于RTOS+空闲中断

    目录 1. 概述     2.串口介绍 2.1 原理框图 2.2 RS-232C 2.3 RS-422 2.4 RS-485 2.5 UART 3. STM32 USART介绍 4. CubeMx生成Uart初始化代码 4.1 NewProject选择单片机型号 4.2 设置rcc时钟  4.3 设置Usart 4.4 初始化代码 4.5 注意 5 工程源码解析 5.1 程序架构 5.2 源码 fml_ring_buffer.c fml_usart.c app_usart_task.c stm3

    2023年04月16日
    浏览(47)
  • FreeRTOS_Stm32F103系列单片机标准库移植

    链接:FreeRTOS 下面的教程是基于从github下载压缩包进行的,最好下载这个或者直接看3.1,从我百度网盘下载。如果是别的下载源也问题不大,大同小异。 此时我们需要下载以下两个仓库, 点进去按下面的步骤下载就行了,另一个也是这样下。 链接: FreeRTOS官网 打开链接我们

    2024年01月22日
    浏览(49)
  • 【单片机学习笔记】Windows+Vscode+STM32F4+freeRTOS+FatFs gcc环境搭建

    为摒弃在接受keil邮件,研究了下gun编译,以STM32F407为例,简单记录 Git 选择对应版本直接安装即可https://git-scm.com/download/win make gcc ​ 1)将上述软件包放置于C盘根目录 2)添加环境变量 3)cmd命令行测试环境 分别输入 启动文件及LD文件 目录路径表示问题 字节对齐及指定位置存

    2024年02月07日
    浏览(54)
  • FreeRTOS学习之路,以STM32F103C8T6为实验MCU(序章——浅谈单片机以及FreeRTOS)

    学习之路主要为FreeRTOS操作系统在STM32F103(STM32F103C8T6)上的运用,采用的是标准库编程的方式,使用的IDE为KEIL5。 注意!!!本学习之路可以通过购买STM32最小系统板以及部分配件的方式进行学习,也可以通过Proteus仿真的方式进行学习。 后续文章会同时发表在个人博客(jaso

    2024年02月06日
    浏览(54)
  • 基于STM32的实时操作系统FreeRTOS移植教程(手动移植)_stm32移植freertos(1)

    直接意识代码: 这是我们大脑最希望的添加代码方式,很显然他是 错的 , 两个任务之间产生了相互的影响 ,使得两个任务都执行错误,这种思想在 裸机开发 中肯定是 错的 ,但是在我们的 RTOS 中他就可以是 对的 。 任务型代码: 这是 独立的两个任务内容 ,我们只需要把

    2024年04月10日
    浏览(105)
  • FreeRTOS事件组 基于STM32

    文章对事件组的,应用场景,运作机制,以及事件的创建,删除,等待,置位,同步等操作 文章目录 概述 一、事件标志组简介 1、事件位(事件标志) 2、事件组 3、事件标志组和事件位的数据类型 二、事件的应用场景 三、事件运作机制 四、事件控制块  五、事件组函数 1.事

    2024年02月11日
    浏览(35)
  • FreeRTOS内存管理 基于STM32

    目录 一、内存管理的基本概念 二、内存管理的应用场景 三、heap_4.c 1.内存申请函数 pvPortMalloc() 2.内存释放函数 vPortFree()  四、内存管理的实验 五、内存管理的实验现象       在计算系统中,变量、中间数据一般存放在系统存储空间中,只有在实际使用时才将 它们从存储空

    2024年02月14日
    浏览(54)
  • 基于STM32CubeMX创建FreeRTOS—以STM32F429为例

    目录 1. 实验任务 2. 使用STM32CubeMX创建基础工程 2.1 使用STM32CubeMX创建项目 2.2 创建新项目 2.3 时钟设置 2.4 时钟配置树 2.5 修改时钟基准,打开串行调试 2.6 配置串口 2.7 配置状态指示灯 2.8 FreeRTOS配置 2.9 配置工程输出项 3. 代码编辑 3.1 printf重映射 3.1.1 使用ARMCC 5编译器时的print

    2024年01月22日
    浏览(42)
  • 搭建STM32F407的Freertos系统(基于STM32CubeMX)

           本人长期开发Linux、Windows上应用软件,一直以来MCU开发有所接触,但较少(最近项目需要,小公司么,都得会,被逼的),好在有STM32CubeMX这样工具,貌似就是我想要的工具。         本次demo目标立下:         1. 搭建或移植FreeRTOS到STM32上,毕竟对于长期在Linux环境

    2024年02月10日
    浏览(61)
  • FreeRTOS软件定时器 基于STM32

    文章目录 一·、软件定时器的基本概念 二、软件定时器应用场景 三、软件定时器的精度 四、软件定时器的运作机制 五、软件定时器函数接口讲解 1.软件定时器创建函数 xTimerCreate() 2.软件定时器启动函数 xTimerStart()  3.软件定时器停止函数  xTimerStop()  4.软件定时器任务 5.软件

    2024年02月11日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包