基于STM32与FreeRTOS的四轴机械臂项目

news2025/1/19 10:19:40

目录

一、项目介绍

二、前期准备

1.硬件准备

2.开发环境

3.CubeMX配置

三、裸机各种模块测试

1.舵机模块

2.蓝牙模块

3.按键摇杆传感器模块和旋钮电位器模块

4.OLED模块

5.W25Q128模块

四、裸机三种控制测试

1.摇杆控制

2.示教器控制

3.蓝牙控制

五、裸机与FreeRTOS

1.CubeMX配置

2.移植裸机三种控制代码

六、项目演示视频


一、项目介绍

        该项目是基于FreeRTOS实时操作系统,主控为 STM32F103C8T6 开发板 ,机械臂为四轴分别被四个舵机控制。本项目实现了 3 种控制方法,分别为摇杆控制示教器控制串口蓝牙控制,采用8路ADC采集按键摇杆传感器和旋钮电位器的模拟量并由DMA搬运数据,可自制手机蓝牙APP或者直接使用官方手机蓝牙助手作为上位机,USART串口蓝牙实时收发信息,IIC驱动OLED屏幕实时显示机械臂移动张爪夹爪信息,人为控制抓取目标物。

        扩展:后续可以通过二维数组或者链表实现存储动作,通过SPI驱动W25Q128模块进行动作记忆扩容,即可以录制上百组动作,还可以附加树莓派等开发板进行视觉抓取开发,等以后有时间我再把扩展功能一起实现呈现给大家。

二、前期准备

1.硬件准备

本项目可用步进电机和驱动器作为支撑,以便提高项目扩展性,但在这里我直接用四个舵机实现。

首先你可以自己建模3D打印四轴机械臂模型,也可以直接去网上购买一套成品套件。然后需要四个舵机控制机械臂,型号无所谓,控制起来是一样的,注意需要是180度的角度型舵机,而不是360度的速度型舵机。

然后需要购买两个按键摇杆传感器实现摇杆控制,购买蓝牙模块实现串口蓝牙控制,购买四个旋钮电位器实现示教器控制。

硬件清单:

  • 四轴机械臂模型
  • 四个舵机
  • 两个按键摇杆传感器
  • HC系列蓝牙串口模块
  • 四个旋钮电位器
  • IIC协议OLED屏幕
  • SPI协议W25Q128模块

2.开发环境

单片机型号为STM32F103C8T6,开发环境为STM32CubeMX和Keil5,蓝牙控制需要手机下载蓝牙助手,在这里我下载的是官方给的HC蓝牙助手,(需要蓝牙调试助手的可以私信作者提供

STM32F103C8T6原理图:

3.CubeMX配置

我用STM32CubeMX配置如下,仅供参考:

RCC:配置外部高速晶振        

SYS:Debug设置成Serial Wire

      ADC:打开8个通道

   

DMA:搬运ADC数据

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

usart:设置波特率为9600,因为蓝牙模块默认波特率为9600,开启NVIC中断接收信息

I2C:用来显示OLED模块

时钟树配置:

最后点击 generate code 生成代码

三、裸机各种模块测试

硬件模块接线:

  • 四个舵机分别接CH1_A15,CH2_B3,CH3_B10, CH4_B11
  • 蓝牙模块TX接RX,RX接TX
  • 两个按键摇杆传感器分别接 PA0 到 PA3 对应 IN0 到 IN3
  • 四个旋钮电位器分别接 PA4 到 PA7 对应 IN4 到 IN7
  • OLED模块 SCL 和 SDA 分别接 PB6 和 PB7
  • W25Q128模块自行扩展

1.舵机模块

舵机模块测试可以看之前我写过的文章,链接如下:

SG90舵机模块测试

然后对四个舵机进行函数封装,舵机初始化角度跟运动角度自行配置调试:

//舵机A,夹爪	CH4_B11  45-135 张开闭合 初始化135
void sg90_A()
{
	if(adc_dma[3] > 4000 && angle[3] < 135)
	{
		angle[3]++;
	}
	else if(adc_dma[3] <1000 && angle[3] > 45)
	{
		angle[3]--;
	}
}

//舵机B,上下	CH3_B10  45-180    初始化180
void sg90_B()
{
	if(adc_dma[2] <1000 && angle[2] < 180)
	{
		angle[2]++;
	}
	else if(adc_dma[2] > 4000 && angle[2] > 45)
	{
		angle[2]--;
	}
}

//舵机C,前后	CH2_B3  45-180  前后   初始化45
void sg90_C()
{
	if(adc_dma[1] <1000 && angle[1] < 180)
	{
		angle[1]++;
	}
	else if(adc_dma[1] > 4000 && angle[1] > 45)
	{
		angle[1]--;
	}
}

//舵机D,底座	CH1_A15 45-135 左到右   初始化45
void sg90_D()
{
	if(adc_dma[0] <1000 && angle[0] < 135)
	{
		angle[0]++;
	}
	else if(adc_dma[0] > 4000 && angle[0] > 45)
	{
		angle[0]--;
	}
}

	//开启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);

2.蓝牙模块

可以使用HC-05(下图蓝色)或者HC-08(下图绿色),这两种我都测试通过。

有关蓝牙模块具体使用跟AT指令可以看以下两篇博客,链接如下:

HC-05介绍 和 HC-08介绍

使用串口中断测试收发的数据,串口重映射设置:

重映射代码:

int fputc(int ch, FILE *f)
{      
	unsigned char temp[1]={ch};
	HAL_UART_Transmit(&huart1,temp,1,0xffff);  
	return ch;
}

中断接收信息代码:

// 接收完成回调函数,收到一个数据后,在这里处理
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;
				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);
	}
}

在main.c函数中开启中断测试,然后打开蓝牙助手:

  // 开启接收中断
  HAL_UART_Receive_IT(&huart1, &buf, 1);

  while (1)
  {
		//判断判断串口是否接收完成
		if(UART1_RX_STA & 0x8000)
		{
			if(!strcmp((const char *)UART1_RX_Buffer, "open"))
			{
                printf("张爪\r\n");
			}
			else if (!strcmp((const char *)UART1_RX_Buffer, "close"))
			{
                printf("夹爪\r\n");
			}
			else
			{
				if(UART1_RX_Buffer[0] != '\0')
					printf("指令发送错误:%s\r\n", UART1_RX_Buffer);
			}
			printf("\r\n");
			// 重新开始下一次接收
			UART1_RX_STA = 0;
		}
		HAL_Delay(40);
  }

手机打开蓝牙助手,记得把发送新行勾上,不然中断接收不到数据

3.按键摇杆传感器模块和旋钮电位器模块

这里用两个按键摇杆传感器分别接收 IN0~3 ADC的模拟量,用四个旋钮电位器分别接收 IN4~7 ADC的模拟量

利用DMA传输接收到的ADC的值通过串口打印进行调试,通过按钮和电位器控制角度

代码示例:

uint16_t adc_dma[8];//DMA搬运的ADC采集值
 
uint8_t angle[4] = {45,45,180,135};//舵机角度
 
uint8_t cnt = 0;//计数用,定时串口打印信息

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


  //开始ADC和DMA采集
  HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,8);

  while (1)
  {
		sg90_A();
		sg90_B();
		sg90_C();
		sg90_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]);
			printf("adc_dma = {%d, %d, %d, %d, %d, %d, %d, %d}\r\n",adc_dma[0],adc_dma[1],adc_dma[2],adc_dma[3],adc_dma[4],adc_dma[5],adc_dma[6],adc_dma[7]);
			cnt = 0;
		}
		HAL_Delay(20);//每20ms循环一次
  }

4.OLED模块

在这里我们可以用取模软件显示机械臂动作,张爪夹爪,向左向右,向前向后,向上向下,也可以后续自行扩展其他内容,例如角度和控制模式等。

oled介绍可看我之前写过的:OLED取模生成文字图片

代码实现如下:

/*--  文字:  向  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char x1[16] = {0x00,0xF8,0x08,0x08,0x0C,0xCA,0x49,0x48,0x48,0xC8,0x08,0x08,0x08,0xF8,0x00,0x00};
char x2[16] = {0x00,0xFF,0x00,0x00,0x00,0x1F,0x08,0x08,0x08,0x1F,0x00,0x40,0x80,0x7F,0x00,0x00};

/*--  文字:  前  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char f1[16] = {0x08,0x08,0xE8,0x29,0x2E,0x28,0xE8,0x08,0x08,0xC8,0x0C,0x0B,0xE8,0x08,0x08,0x00};
char f2[16] = {0x00,0x00,0xFF,0x09,0x49,0x89,0x7F,0x00,0x00,0x0F,0x40,0x80,0x7F,0x00,0x00,0x00};

/*--  文字:  后  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char b1[16] = {0x00,0x00,0x00,0xFC,0x24,0x24,0x24,0x24,0x22,0x22,0x22,0x23,0x22,0x20,0x20,0x00};
char b2[16] = {0x40,0x20,0x18,0x07,0x00,0xFE,0x42,0x42,0x42,0x42,0x42,0x42,0xFE,0x00,0x00,0x00};

/*--  文字:  上  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char u1[16] = {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00,0x00};
char u2[16] = {0x40,0x40,0x40,0x40,0x40,0x40,0x7F,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00};

/*--  文字:  下  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char d1[16] = {0x02,0x02,0x02,0x02,0x02,0x02,0xFE,0x02,0x02,0x42,0x82,0x02,0x02,0x02,0x02,0x00};
char d2[16] = {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x01,0x06,0x00,0x00,0x00};

/*--  文字:  左  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char l1[16] = {0x08,0x08,0x08,0x08,0x88,0x78,0x0F,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00};
char l2[16] = {0x20,0x10,0x48,0x46,0x41,0x41,0x41,0x41,0x7F,0x41,0x41,0x41,0x41,0x40,0x40,0x00};

/*--  文字:  右  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char r1[16] = {0x08,0x08,0x08,0x08,0xC8,0x38,0x0F,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00};
char r2[16] = {0x08,0x04,0x02,0x01,0xFF,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0xFF,0x00,0x00,0x00};

/*--  文字:  张  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char o1[16] = {0x02,0xE2,0x22,0x22,0x3E,0x80,0x80,0xFF,0x80,0xA0,0x90,0x88,0x86,0x80,0x80,0x00};
char o2[16] = {0x00,0x43,0x82,0x42,0x3E,0x00,0x00,0xFF,0x40,0x21,0x06,0x08,0x10,0x20,0x40,0x00};

/*--  文字:  夹  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char c1[16] = {0x00,0x08,0x08,0x28,0x48,0x08,0x08,0xFF,0x08,0x08,0x48,0x28,0x08,0x08,0x00,0x00};
char c2[16] = {0x81,0x81,0x41,0x41,0x21,0x11,0x0D,0x03,0x0D,0x11,0x21,0x41,0x41,0x81,0x81,0x00};

/*--  文字:  爪  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char z1[16] = {0x00,0x00,0x00,0xFC,0x04,0x04,0xFC,0x04,0x02,0x02,0xFE,0x03,0x02,0x00,0x00,0x00};
char z2[16] = {0x80,0x60,0x18,0x07,0x00,0x00,0x7F,0x00,0x00,0x00,0x01,0x0E,0x30,0x40,0x80,0x00};

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

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

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_Screen_Clear(void){
	unsigned char i,n;
	Oled_Write_Cmd (0x20);                    //set memory addressing mode
	Oled_Write_Cmd (0x02);                    //page addressing mode

	for(i=0;i<8;i++){
		Oled_Write_Cmd(0xb0+i);               //
		Oled_Write_Cmd(0x00);                 //
		Oled_Write_Cmd(0x10);                 //
		for(n=0;n<128;n++)Oled_Write_Data(0x00); 			
	}	
}

void Oled_Show_open()
{
		unsigned char  i;

		Oled_Init();
		// 选择一个位置确认页寻址模式
		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();
		// 选择PAGE0   1011 0000 0xB0			
		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(o1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(z1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(o2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(z2[i]);
		}

}

void Oled_Show_close()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(c1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(z1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(c2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(z2[i]);
		}

}

void Oled_Show_up()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(u1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(u2[i]);
		}

}

void Oled_Show_down()
{
		unsigned char  i;
	
		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(d1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(d2[i]);
		}

}

void Oled_Show_left()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(l1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(l2[i]);
		}

}

void Oled_Show_right()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(r1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(r2[i]);
		}

}

void Oled_Show_front()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(f1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(f2[i]);
		}

}

void Oled_Show_behind()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(b1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(b2[i]);
		}

}

  while (1)
  {
			Oled_Show_open();
			HAL_Delay(1000);
			Oled_Show_close();
			HAL_Delay(1000);
			Oled_Show_up();
			HAL_Delay(1000);
			Oled_Show_down();
			HAL_Delay(1000);
			Oled_Show_left();
			HAL_Delay(1000);
			Oled_Show_right();
			HAL_Delay(1000);
			Oled_Show_front();
			HAL_Delay(1000);	
			Oled_Show_behind();
			HAL_Delay(1000);
  }

5.W25Q128模块

W25Q128模块测试可看我之前写过的文章然后自行扩展:W25Q128模块测试

四、裸机三种控制测试

首先我在Core文件夹里面的Src和Inc里分别创建pwm和oled的.c和.h文件

1.摇杆控制

摇杆控制在这里我用上面讲到的两个按键摇杆传感器模块实现,只需要开启四路ADC采集,两个按键摇杆传感器分别接收 IN0~3 ADC的模拟量,其中需要结合实际操作控制舵机初始化角度和运动过程中的角度变化。

代码示例:

pwm.c

extern uint16_t adc_dma[4];//DMA搬运的ADC采集值
 
extern int8_t angle[4] = {45,45,180,135};//舵机角度

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

//舵机A,夹爪	CH4_B11  45-135 张开闭合 初始化135
void sg90_A()
{
	if(adc_dma[3] > 4000 && angle[3] < 135)
	{
		angle[3]++;
	}
	else if(adc_dma[3] <1000 && angle[3] > 45)
	{
		angle[3]--;
	}
}

//舵机B,上下	CH3_B10  45-180    初始化180
void sg90_B()
{
	if(adc_dma[2] <1000 && angle[2] < 180)
	{
		angle[2]++;
	}
	else if(adc_dma[2] > 4000 && angle[2] > 45)
	{
		angle[2]--;
	}
}

//舵机C,前后	CH2_B3  45-180  前后   初始化45
void sg90_C()
{
	if(adc_dma[1] <1000 && angle[1] < 180)
	{
		angle[1]++;
	}
	else if(adc_dma[1] > 4000 && angle[1] > 45)
	{
		angle[1]--;
	}
}

//舵机D,底座	CH1_A15 45-135 左到右   初始化45
void sg90_D()
{
	if(adc_dma[0] <1000 && angle[0] < 135)
	{
		angle[0]++;
	}
	else if(adc_dma[0] > 4000 && angle[0] > 45)
	{
		angle[0]--;
	}
}

pwm.h

#ifndef __PWM_H__
#define __PWM_H__
 
unsigned char Angle(unsigned char pwm_pulse);
 
void sg90_A(void);

void sg90_B(void);

void sg90_C(void);
 
void sg90_D(void);
 
#endif

oled.c

#include "main.h"
#include "i2c.h"
#include "oled.h"

/*--  文字:  向  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char x1[16] = {0x00,0xF8,0x08,0x08,0x0C,0xCA,0x49,0x48,0x48,0xC8,0x08,0x08,0x08,0xF8,0x00,0x00};
char x2[16] = {0x00,0xFF,0x00,0x00,0x00,0x1F,0x08,0x08,0x08,0x1F,0x00,0x40,0x80,0x7F,0x00,0x00};

/*--  文字:  前  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char f1[16] = {0x08,0x08,0xE8,0x29,0x2E,0x28,0xE8,0x08,0x08,0xC8,0x0C,0x0B,0xE8,0x08,0x08,0x00};
char f2[16] = {0x00,0x00,0xFF,0x09,0x49,0x89,0x7F,0x00,0x00,0x0F,0x40,0x80,0x7F,0x00,0x00,0x00};

/*--  文字:  后  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char b1[16] = {0x00,0x00,0x00,0xFC,0x24,0x24,0x24,0x24,0x22,0x22,0x22,0x23,0x22,0x20,0x20,0x00};
char b2[16] = {0x40,0x20,0x18,0x07,0x00,0xFE,0x42,0x42,0x42,0x42,0x42,0x42,0xFE,0x00,0x00,0x00};

/*--  文字:  上  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char u1[16] = {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00,0x00};
char u2[16] = {0x40,0x40,0x40,0x40,0x40,0x40,0x7F,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00};

/*--  文字:  下  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char d1[16] = {0x02,0x02,0x02,0x02,0x02,0x02,0xFE,0x02,0x02,0x42,0x82,0x02,0x02,0x02,0x02,0x00};
char d2[16] = {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x01,0x06,0x00,0x00,0x00};

/*--  文字:  左  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char l1[16] = {0x08,0x08,0x08,0x08,0x88,0x78,0x0F,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00};
char l2[16] = {0x20,0x10,0x48,0x46,0x41,0x41,0x41,0x41,0x7F,0x41,0x41,0x41,0x41,0x40,0x40,0x00};

/*--  文字:  右  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char r1[16] = {0x08,0x08,0x08,0x08,0xC8,0x38,0x0F,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00};
char r2[16] = {0x08,0x04,0x02,0x01,0xFF,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0xFF,0x00,0x00,0x00};

/*--  文字:  张  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char o1[16] = {0x02,0xE2,0x22,0x22,0x3E,0x80,0x80,0xFF,0x80,0xA0,0x90,0x88,0x86,0x80,0x80,0x00};
char o2[16] = {0x00,0x43,0x82,0x42,0x3E,0x00,0x00,0xFF,0x40,0x21,0x06,0x08,0x10,0x20,0x40,0x00};

/*--  文字:  夹  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char c1[16] = {0x00,0x08,0x08,0x28,0x48,0x08,0x08,0xFF,0x08,0x08,0x48,0x28,0x08,0x08,0x00,0x00};
char c2[16] = {0x81,0x81,0x41,0x41,0x21,0x11,0x0D,0x03,0x0D,0x11,0x21,0x41,0x41,0x81,0x81,0x00};

/*--  文字:  爪  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char z1[16] = {0x00,0x00,0x00,0xFC,0x04,0x04,0xFC,0x04,0x02,0x02,0xFE,0x03,0x02,0x00,0x00,0x00};
char z2[16] = {0x80,0x60,0x18,0x07,0x00,0x00,0x7F,0x00,0x00,0x00,0x01,0x0E,0x30,0x40,0x80,0x00};

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

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

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_Screen_Clear(void){
	unsigned char i,n;
	Oled_Write_Cmd (0x20);                    //set memory addressing mode
	Oled_Write_Cmd (0x02);                    //page addressing mode

	for(i=0;i<8;i++){
		Oled_Write_Cmd(0xb0+i);               //
		Oled_Write_Cmd(0x00);                 //
		Oled_Write_Cmd(0x10);                 //
		for(n=0;n<128;n++)Oled_Write_Data(0x00); 			
	}	
}

void Oled_Show_open()
{
		unsigned char  i;

		Oled_Init();
		// 选择一个位置确认页寻址模式
		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();
		// 选择PAGE0   1011 0000 0xB0			
		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(o1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(z1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(o2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(z2[i]);
		}

}

void Oled_Show_close()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(c1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(z1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(c2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(z2[i]);
		}

}

void Oled_Show_up()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(u1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(u2[i]);
		}

}

void Oled_Show_down()
{
		unsigned char  i;
	
		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(d1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(d2[i]);
		}

}

void Oled_Show_left()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(l1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(l2[i]);
		}

}

void Oled_Show_right()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(r1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(r2[i]);
		}

}

void Oled_Show_front()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(f1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(f2[i]);
		}

}

void Oled_Show_behind()
{
		unsigned char  i;

		Oled_Init();

		Oled_Write_Cmd(0x20);
		Oled_Write_Cmd(0x02);
		Oled_Screen_Clear();

		Oled_Write_Cmd(0xB0);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x1[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(b1[i]);
		}

		Oled_Write_Cmd(0xB1);
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);

		for(i=0;i<16;i++){
			Oled_Write_Data(x2[i]);
		}
		for(i=0;i<16;i++){
			Oled_Write_Data(b2[i]);
		}

}

oled.h

#ifndef __OLED_H__
#define __OLED_H__

void Oled_Write_Cmd(uint8_t dataCmd);

void Oled_Write_Data(uint8_t dataData);

void Oled_Init(void);

void Oled_Screen_Clear(void);

// oled显示封装
void Oled_Show_open();
void Oled_Show_close();
void Oled_Show_up();
void Oled_Show_down();
void Oled_Show_left();
void Oled_Show_right();
void Oled_Show_front();
void Oled_Show_behind();

#endif

main.c

uint16_t adc_dma[8];//DMA搬运的ADC采集值
 
uint8_t angle[4] = {45,45,180,135};//舵机角度
 
uint8_t cnt = 0;//计数用,定时串口打印信息


//覆写printf,用于串口打印数据
int fputc(int ch, FILE *f)
{      
    unsigned char temp[1]={ch};
    HAL_UART_Transmit(&huart1,temp,1,0xffff);  
    return ch;
}

//int main

    //开始ADC和DMA采集
	HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4);
	
	//开启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);
	printf("test\r\n");
	
  while (1)
  {
		
		sg90_A();
		sg90_B();
		sg90_C();
		sg90_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更流畅)
		
	}

2.示教器控制

示教器控制在这里我用上面讲到的四个旋钮电位器模块实现,跟上面摇杆控制一样只需要开启四路ADC采集,代码跟摇杆控制基本差不多,新增加一个函数封装把采集的模拟值转换为角度,即0~4095 变为 0~180,除以22.75即可

在pwm.c上新增加一个转换函数


void translate()//直接用8通道就是adc_dma[4~7]
{
	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 - 即可
}

在main.c上增加打印调试信息在while(1)循环里面

printf("adc_dma = {%d, %d, %d, %d}\r\n",adc_dma[0],adc_dma[1],adc_dma[2],adc_dma[3]);

3.蓝牙控制

使用蓝牙模块,打开串口中断,用串口调试助手查看中断测试收发的数据,只需要新增加串口接收中断代码和在原先的pwm.c上面做一些修改

usart.c

#include "stdio.h"
#include "string.h"
#include "pwm.h"
 
#include "adc.h"
#include "dma.h"
 
/*蓝牙控制机械臂指令:
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;
					
					//=======中断信息处理=======
 
					//获取蓝牙控制指令,A打头,后面一个字母就是指令内容
				    if(UART1_RX_Buffer[0] == 'A')
					{
						HAL_ADC_Stop_DMA(&hadc1);//停止ADC DMA
						MX_ADC1_Init();//初始化ADC1
						HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_dma,4); //开启ADC DMA
						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);
	}
}
 

	// 在串口初始化中开启接收中断
	HAL_UART_Receive_IT(&huart1, &buf, 1);

pwm.c

#include "pwm.h"
#include "main.h"
#include "oled.h"

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

extern uint8_t Mode;
extern uint8_t cmd_BLE;

//根据输入的0~180角度获取对应pwm占空比参数

unsigned char Angle(unsigned char pwm_pulse)
{
	return pwm_pulse + 44;
}

//舵机A,夹爪	CH4_B11  45-135 张开闭合 初始化135
void sg90_A()
{
	if((cmd_BLE == 'c' || adc_dma[3] > 4000) && angle[3] < 135)//合
	{
		angle[3]++;
		Oled_Show_close();
		
	}
	else if((cmd_BLE == 'o' || adc_dma[3] <1000) && angle[3] > 45)//开
	{
		angle[3]--;
		Oled_Show_open();
	}

}

//舵机B,上下	CH3_B10  45-180    初始化180
void sg90_B()
{
	if((cmd_BLE == 'u' || adc_dma[2] <1000) && angle[2] < 180)//上
	{
		angle[2]++;
		Oled_Show_up();
	}
	else if((cmd_BLE == 'd' || adc_dma[2] > 4000) && angle[2] > 45)//下
	{
		angle[2]--;
		Oled_Show_down();
	}
}

//舵机C,前后	CH2_B3  45-180  前后   初始化45
void sg90_C()
{
	if((cmd_BLE == 'f' || adc_dma[1] <1000) && angle[1] < 180)//前
	{
		angle[1]++;
		Oled_Show_front();
	}
	else if((cmd_BLE == 'b' || adc_dma[1] > 4000) && angle[1] > 45)//后
	{
		angle[1]--;
		Oled_Show_behind();
	}
}

//舵机D,底座	CH1_A15 45-135 左到右   初始化45
void sg90_D()
{
	if((cmd_BLE == 'l' || adc_dma[0] <1000) && angle[0] < 135)//左
	{
		angle[0]++;
		Oled_Show_left();
	}
	else if((cmd_BLE == 'r' || adc_dma[0] > 4000) && angle[0] > 45)//右
	{
		angle[0]--;
		Oled_Show_right();
	}
}

五、裸机与FreeRTOS

移植 FreeRTOS 到 STM32F103C8T6上我们可以手动移植或者使用CubeMX快速移植,在这里我用CubeMX快速移植,具体介绍可看我之前写过的文章,链接如下:CubeMx快速移植FreeRTOS

接着我们要创建三个任务,一个任务负责角度信息处理,一个任务负责串口收发数据,一个任务负责显示OLED屏幕,具体创建删除任务介绍可看我之前写过的文章,链接如下:任务的创建和删除

1.CubeMX配置

2.移植裸机三种控制代码

在这里我们只需要在CubeMX生成的freertos.c文件中移植我们裸机控制的代码放在对应的任务中,可自行进行代码扩展,例如:  

六、项目演示视频

四轴机械臂演示视频

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

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

相关文章

linux应用 进程间通信之共享内存(POSIX)

1、前言 1.1 定义 POSIX共享内存是一种在UNIX和类UNIX系统上可用的进程间通信机制。它允许多个进程共享同一块内存区域&#xff0c;从而可以在这块共享内存上进行读写操作。 1.2 应用场景 POSIX共享内存适用于需要高效地进行大量数据交换的场景&#xff0c;比如多个进程需要…

Hive的Join连接

前言 Hive-3.1.2版本支持6种join语法。分别是&#xff1a;inner join&#xff08;内连接&#xff09;、left join&#xff08;左连接&#xff09;、right join&#xff08;右连接&#xff09;、full outer join&#xff08;全外连接&#xff09;、left semi join&#xff08;左…

C++ bfs再探迷宫游戏(五十五)【第二篇】

今天我们用bfs解决迷宫游戏。 1.再探迷宫游戏 前面我们已经接触过了迷宫游戏&#xff0c;并且学会了如何使用 DFS 来解决迷宫最短路问题。用 DFS 求解迷宫最短路有一个很大的缺点&#xff0c;需要枚举所有可能的路径&#xff0c;读入的地图一旦很大&#xff0c;可能的搜索方案…

VM和Linux安装

VM和Linux安装 一、下载VM 1.官网地址&#xff1a;https://www.vmware.com/cn.html 2.其他地址&#xff1a;http://ww7.nocmd.com/windows/740.html 许可证这个&#xff0c;大家可以自己上网搜索&#xff0c;很容易就搜索到就可以使用了 上面内容就是安装VM的步骤 安…

BFS与DFS初级练习(排列数字,n-皇后,走迷宫)

BFS与DFS初步了解 DFS&#xff08;深度优先搜索&#xff09;和BFS&#xff08;广度优先搜索&#xff09;是两种常用的图遍历算法。 DFS是一种递归的搜索算法&#xff0c;它从起始节点开始&#xff0c;沿着路径依次访问与当前节点相邻的未访问节点&#xff0c;直到无法继续访问…

Pytorch的可视化

1 使用 wandb进行可视化训练过程 本文章将从wandb的安装、wandb的使用、demo的演示进行讲解。 1.1 如何安装wandb&#xff1f; wandb的安装比较简单&#xff0c;在终端中执行如下的命令即可&#xff1a; pip install wandb在安装完成之后&#xff0c;我们需要&#xff0c;去…

【力扣】5.最长回文子串

这道题我主要是通过动态规划来进行解题&#xff0c;看了我好久&#xff08;解析&#xff09;&#xff0c;生疏了呀。 首先就是判断一个字符串是不是回文&#xff0c;我们可以设置两个指针&#xff0c;从前往后进行判断即可&#xff0c;运用暴力解题法&#xff0c;这里运用的动…

【C++】内存详解(堆,栈,静态区)

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

【FPGA】VHDL:八段码到8421BCD码转换电路

目录 EDA设计基础练习题 &#xff1a; 实验要求如下&#xff1a; 代码 八段码到8421BCD码转换电路 8421BCD码到八段码转换电路 八段码到8421BCD~运行结果展示 8421BCD转八段码~运行结果展示 特别注意 软件&#xff1a;Quartus II 13.0 (64-bit) 语言&#xff1a;VHDL E…

书生谱语-基于 InternLM 和 LangChain 搭建知识库

大语言模型与外挂知识库&#xff08;RAG&#xff09;的优缺点 RAG方案构建与优化 作业 在创建web_demo时&#xff0c;需要根据教程将服务器端口映射到本地端口&#xff0c;另外需要将链接的demo从服务器中复制出来&#xff0c;不要直接从服务器打开demo页面&#xff0c;不然会…

Blazor OIDC 单点登录授权实例5 - 独立SSR App (net8 webapp ) 端授权

目录: OpenID 与 OAuth2 基础知识Blazor wasm Google 登录Blazor wasm Gitee 码云登录Blazor OIDC 单点登录授权实例1-建立和配置IDS身份验证服务Blazor OIDC 单点登录授权实例2-登录信息组件wasmBlazor OIDC 单点登录授权实例3-服务端管理组件Blazor OIDC 单点登录授权实例4 …

低资源学习与知识图谱:构建与应用

目录 前言1 低资源学习方法1.1 数据增强1.2 特征增强1.3 模型增强 2 低资源知识图谱构建与推理2.1 元关系学习2.2 对抗学习2.3 零样本关系抽取2.4 零样本学习与迁移学习2.5 零样本学习与辅助信息 3 基于知识图谱的低资源学习应用3.1 零样本图像分类3.2 知识增强的零样本学习3.3…

鸿蒙小案例-你画我猜

鸿蒙小案例-你画我猜 1.准备组件(组件布局) 2.实现跟随鼠标画笔画出图案功能 3.实现复制上面的画笔的图案功能 4.其他小功能1.组件的准备 画布的组件官方给的API是Canvas&#xff0c;需要传递一个参数CanvasRenderingContext2D 直接搜索API 使用官方案例 private settings: …

【GO语言卵细胞级别教程】05.项目创建和函数讲解

感谢&#xff01;点点赞和评论呀&#xff01;我将继续更新 目录&#xff1a; 感谢&#xff01;点点赞和评论呀&#xff01;我将继续更新0.创建项目1.函数的引入2.注意事项3.详细介绍3.1 形参介绍 4.导入包4.1 基本知识4.2 注意事项 5.init函数6.匿名函数 0.创建项目 创建目录 …

力扣精选算法100道——矩阵区域和 (前缀和专题)

目录 &#x1f388;了解题意 &#x1f388;算法原理 &#x1f388;实现代码 &#x1f388;了解题意 给定一个大小为 m x n 的矩阵 mat 和一个整数 k&#xff0c;你需要计算一个新的矩阵 answer&#xff0c;其中每个 answer[i][j] 表示矩阵 mat 中以坐标 (i, j) 为中心、边…

JavaScript 事件循环:Event Loop

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ​ ​ ✨ 前言 事件循环 是 web 开发中的一个核心概念&#xff0c;它是 JavaScript…

【Linux】线程概念和线程控制

线程概念 一、理解线程1. Linux中的线程2. 重新定义线程和进程3. 进程地址空间之页表4. 线程和进程切换5. 线程的优点6. 线程的缺点7. 线程异常8. 线程用途9. 线程和进程 二、线程控制1. pthread 线程库&#xff08;1&#xff09;pthread_create()&#xff08;2&#xff09;pth…

[Doris] Doris的安装和部署 (二)

文章目录 1.安装要求1.1 Linux操作系统要求1.2 软件需求1.3 注意事项1.4 内部端口 2.集群部署2.1 操作系统安装要求2.2 下载安装包2.3 解压2.4 配置FE2.5 配置BE2.6 添加BE2.7 FE 扩容和缩容2.8 Doris 集群群起脚本 3.图形化 1.安装要求 1.1 Linux操作系统要求 1.2 软件需求 1…

Acwing---842.排列数字

排列数字 1.题目2.基本思想3.代码实现 1.题目 给定一个整数 n&#xff0c;将数字 1∼n排成一排&#xff0c;将会有很多种排列方法。 现在&#xff0c;请你按照字典序将所有的排列方法输出。 输入格式 共一行&#xff0c;包含一个整数 n。 输出格式 按字典序输出所有排列方案…

mysql Day05

sql性能分析 sql执行频率 show global status like Com_______ 慢查询日志 执行时间超过10秒的sql语句 profile详情 show profiles帮助我们了解时间都耗费到哪里了 #查看每一条sql的耗时情况 show profiles#查看指定query_id的sql语句各个阶段的耗时情况 show profile fo…