stm32f103c8t6+freertos基于标准库实现四自由度自动仿生机械手

项目概述

本项目使用stm32f103c8t6基于标准库实现,使用freertos提高程序运行的实时性。使用三种控制方法,包括蓝牙控制,遥杆控制,旋转编码器控制,并且可进行动作录制和自动执行。

蓝牙控制使用Usart协议,摇杆通过ADC四路转换+DMA数据转运,IIC控制OLED屏显,旋转编码器使用TIM输入捕获和中断计数的方式实现。动作记忆使用二维数组或链表记录存储。并且通过SPI协议驱动W25Q64实现扩容。

一准备

stm32f103c8t6单片机,四个旋转编码器,两个摇杆,W25Q64,sg90舵机,HC-05蓝牙模块,OLED。开发环境使用keil5.

 二引脚连接

引脚连接如上图,其中舵机通道一和通道二使用tim2 的引脚重映射至PA15和PB3

三摇杆控制

 首先是摇杆控制STM32,需要4路1ADC+DMA采集摇杆输出的模拟量。根据这个数据来控制舵机角度。

摇杆4个输出模拟量的引脚连接stm32的A0,A1,A2,A3,VCC这里接5V。

这里我使用了ADC1和ADC2双ADC模式+DMA转运

uint32_t AD_Value[2];

void AD_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);		//开启DMA1的时钟
	
	/*设置ADC时钟*/
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0、PA1、PA2和PA3引脚初始化为模拟输入
	
	/*规则组通道配置*/
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);	//规则组序列1的位置,配置为通道0
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5);	//规则组序列2的位置,配置为通道1
	
	ADC_RegularChannelConfig(ADC2, ADC_Channel_2, 1, ADC_SampleTime_239Cycles5);	//规则组序列3的位置,配置为通道2
	ADC_RegularChannelConfig(ADC2, ADC_Channel_3, 2, ADC_SampleTime_239Cycles5);	//规则组序列4的位置,配置为通道3

	/*ADC初始化*/
	ADC_InitTypeDef ADC_InitStructure;											//定义结构体变量
	ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;							//模式,选择独立模式,即单独使用ADC1
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;						//数据对齐,选择右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;			//外部触发,使用软件触发,不需要外部触发
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;							//连续转换,使能,每转换一次规则组序列后立刻开始下一次转换
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;								//扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定
	ADC_InitStructure.ADC_NbrOfChannel = 2;										//通道数,为4,扫描规则组的前4个通道
	ADC_Init(ADC1, &ADC_InitStructure);											//将结构体变量交给ADC_Init,配置ADC1
	
	ADC_InitTypeDef ADC_InitStructure1;											//定义结构体变量
	ADC_InitStructure1.ADC_Mode = ADC_Mode_RegSimult;							//模式,选择独立模式,即单独使用ADC1
	ADC_InitStructure1.ADC_DataAlign = ADC_DataAlign_Right;						//数据对齐,选择右对齐
	ADC_InitStructure1.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;			//外部触发,使用软件触发,不需要外部触发
	ADC_InitStructure1.ADC_ContinuousConvMode = ENABLE;							//连续转换,使能,每转换一次规则组序列后立刻开始下一次转换
	ADC_InitStructure1.ADC_ScanConvMode = ENABLE;								//扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定
	ADC_InitStructure1.ADC_NbrOfChannel = 2;										//通道数,为4,扫描规则组的前4个通道
	ADC_Init(ADC2, &ADC_InitStructure1);
	/*DMA初始化*/
	DMA_InitTypeDef DMA_InitStructure;											//定义结构体变量
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;				//外设基地址,给定形参AddrA
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;	//外设数据宽度,选择半字,对应16为的ADC数据寄存器
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;			//外设地址自增,选择失能,始终以ADC数据寄存器为源
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;					//存储器基地址,给定存放AD转换结果的全局数组AD_Value
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;			//存储器数据宽度,选择半字,与源数据宽度对应
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;						//存储器地址自增,选择使能,每次转运后,数组移到下一个位置
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;							//数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
	DMA_InitStructure.DMA_BufferSize = 2;										//转运的数据大小(转运次数),与ADC通道数一致
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;								//模式,选择循环模式,与ADC的连续转换一致
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;								//存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
	DMA_InitStructure.DMA_Priority = DMA_Priority_Low;						//优先级,选择中等
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);								//将结构体变量交给DMA_Init,配置DMA1的通道1
	
	/*DMA和ADC使能*/
	DMA_Cmd(DMA1_Channel1, ENABLE);							//DMA1的通道1使能
	ADC_DMACmd(ADC1, ENABLE);								//ADC1触发DMA1的信号使能
	ADC_Cmd(ADC1, ENABLE);									//ADC1使能
	ADC_Cmd(ADC2, ENABLE);
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
	ADC_ResetCalibration(ADC2);								//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC2) == SET);
	ADC_StartCalibration(ADC2);
	while (ADC_GetCalibrationStatus(ADC2) == SET);
	/*ADC触发*/
	ADC_ExternalTrigConvCmd( ADC2, ENABLE);
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);	//软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作
}
//A15,张爪夹爪
void AD_CheackA()
{
	if((ble==0x45||(AD_Value[0]&0xffff)>4000)&&angle[0]<89)
	{
		angle[0]++;
	}
	else if((ble==0x42||(AD_Value[0]&0xffff)<1000)&&angle[0]>0)
	{
		angle[0]--;
	}
}
//B3,上下
void AD_CheackB()
{
	if((ble==0x47||((AD_Value[0]&0xffff0000)>>16)<1000)&&angle[1]<135)
	{
		angle[1]++;
	}
	else if((ble==0x4B||((AD_Value[0]&0xffff0000)>>16)>4000)&&angle[1]>45)
	{
		angle[1]--;
	}
}
//B10,前后
void AD_CheackC()
{
	if((ble==0x44||(AD_Value[1]&0xffff)<1000)&&angle[2]<140)
	{
		angle[2]++;
	}
	else if((ble==0x46||(AD_Value[1]&0xffff)>4000)&&angle[2]>45)
	{
		angle[2]--;
	}
}
void AD_CheackD()
{
	if((ble==0x48||((AD_Value[1]&0xffff0000)>>16)<1000)&&angle[3]<180)
	{
		angle[3]++;
	}
	else if((ble==0x4A||((AD_Value[1]&0xffff0000)>>16)>4000)&&angle[3]>0)
	{
		angle[3]--;
	}
}

 四蓝牙控制

通过获取蓝牙输入的值来执行对应动作(动作执行代码在上面遥杆中一起)

 uint8_t ble;
 void My_USART1_Init(void)  
{  
    GPIO_InitTypeDef GPIO_InitStrue;  
    USART_InitTypeDef USART_InitStrue;  
    NVIC_InitTypeDef NVIC_InitStrue;  
      
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);   
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
      
    GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP;  
    GPIO_InitStrue.GPIO_Pin=GPIO_Pin_9;  
    GPIO_InitStrue.GPIO_Speed=GPIO_Speed_50MHz;  
    GPIO_Init(GPIOA,&GPIO_InitStrue);  
      
    GPIO_InitStrue.GPIO_Mode=GPIO_Mode_IPU;  
    GPIO_InitStrue.GPIO_Pin=GPIO_Pin_10;  
    GPIO_InitStrue.GPIO_Speed=GPIO_Speed_50MHz;  
    GPIO_Init(GPIOA,&GPIO_InitStrue);  

    USART_InitStrue.USART_BaudRate=9600;  
    USART_InitStrue.USART_HardwareFlowControl=USART_HardwareFlowControl_None;  
    USART_InitStrue.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;  
    USART_InitStrue.USART_Parity=USART_Parity_No;  
    USART_InitStrue.USART_StopBits=USART_StopBits_1;  
    USART_InitStrue.USART_WordLength=USART_WordLength_8b;  
      
			  
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
			     
		NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
    USART_Init(USART1,&USART_InitStrue);
      
    USART_Cmd(USART1,ENABLE);
		
    NVIC_InitStrue.NVIC_IRQChannel=USART1_IRQn;  
    NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE;  
    NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=2;  
    NVIC_InitStrue.NVIC_IRQChannelSubPriority=1;  
    NVIC_Init(&NVIC_InitStrue);  
      
}  
  
void USART1_IRQHandler(void)  
{  
      
     if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)  
		{  
			ble=USART_ReceiveData(USART1);
       USART_ClearITPendingBit(USART1,USART_IT_RXNE);
		}  
}  
 

 五旋转编码器控制

旋转编码器两路使用定时器输入捕获,两路使用中断来计数旋转编码器转过的数目,通过计算得出对应舵机应转的角度值

1.中断代码

int16_t Encoder_Count;
int16_t Encoder1_Count;
/*һȦ±àÂëÆ÷¼ÆÊý20,±àÂëÆ÷תһ¸ñ¶ÔÓ¦¶æ»úÊ®°Ë¶È*/

void EXTI_Encoder_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	GPIO_InitTypeDef GPIO_InitStructure1;
	GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure1);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5);
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1|EXTI_Line4 | EXTI_Line5;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_Init(&EXTI_InitStructure);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);

	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStructure);
}

int16_t EXTI_Encoder1_Get(void)
{
	return Encoder_Count;
}
int16_t EXTI_Encoder2_Get()
{
	return Encoder1_Count;
}

void EXTI0_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line0) == SET)
	{
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
			{
				Encoder_Count --;
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line0);
	}
}

void EXTI1_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line1) == SET)
	{
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
			{
				Encoder_Count ++;
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line1);
	}
}
void EXTI4_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line4) == SET)
	{
		if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == 0)
			{
				Encoder1_Count --;
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line4);
	}
	
}
void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line5) == SET)
	{
		if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4) == 0)
			{
				Encoder1_Count ++;
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line5);
	}
}

2.输入捕获代码


/**
  * 函    数:编码器初始化
  * 参    数:无
  * 返 回 值:无
  */
void Encoder_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    /* GPIO初始化 */
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitTypeDef GPIO_InitStructure1;
    GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 ;
    GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure1);

    /* 时基单元初始化 */
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure3;
    TIM_TimeBaseInitStructure3.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure3.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure3.TIM_Period = 65536 - 1;
    TIM_TimeBaseInitStructure3.TIM_Prescaler = 1 - 1;
    TIM_TimeBaseInitStructure3.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure3);

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure4;
    TIM_TimeBaseInitStructure4.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure4.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure4.TIM_Period = 65536 - 1;
    TIM_TimeBaseInitStructure4.TIM_Prescaler = 1 - 1;
    TIM_TimeBaseInitStructure4.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure4);

    /* 输入捕获初始化 */
    TIM_ICInitTypeDef TIM_ICInitStructure3;
    TIM_ICStructInit(&TIM_ICInitStructure3);
    TIM_ICInitStructure3.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure3.TIM_ICFilter = 0xF;
    TIM_ICInit(TIM3, &TIM_ICInitStructure3);
    TIM_ICInitStructure3.TIM_Channel = TIM_Channel_2;
    TIM_ICInit(TIM3, &TIM_ICInitStructure3);

    TIM_ICInitTypeDef TIM_ICInitStructure4;
    TIM_ICStructInit(&TIM_ICInitStructure4);
    TIM_ICInitStructure4.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure4.TIM_ICFilter = 0xF;
    TIM_ICInit(TIM4, &TIM_ICInitStructure4);
    TIM_ICInitStructure4.TIM_Channel = TIM_Channel_2;
    TIM_ICInit(TIM4, &TIM_ICInitStructure4);

    /* 编码器接口配置 */
    TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
		
    /* TIM使能 */
    TIM_Cmd(TIM3, ENABLE);
    TIM_Cmd(TIM4, ENABLE);
}

/**
  * 函    数:获取编码器的增量值
  * 参    数:无
  * 返 回 值:自上此调用此函数后,编码器的增量值
  */

int16_t Encoder3_Get(void)
{
	return TIM_GetCounter(TIM3);
}
int16_t Encoder4_Get(void)
{
	return TIM_GetCounter(TIM4);
}

六动作记忆与执行

1.数组实现

最开始我是使用二维数组记录角度值,数组是10*4的,也就是说一共能记忆十组动作,执行时从数组获得位置信息并转换位角度目标值,随后当前角度值像目标值逐渐靠近。当然如果想记忆更多动作可以适当扩大数组容量。(记忆动作时,每一组的角度在记忆时都需要发送命令告诉单片机这组数据是我要记忆的,命令可以通过蓝牙发送或者使用按键提醒都可以)

2.链表实现

使用单向循环链表,每个链表节点包含四个数据分别对应四个舵机角度,编写对应增删查改函数,实现对链表的操作,对于动作记忆和执行的操作和上面的数组实现相同,链表的链式结构使动作的记忆和执行更加灵活,缺点是相对于数组所占用的内存更大。

七SPI扩容

如果记忆少量动作,在单片机的内部实现完全足够,如果记忆大量动作,就需要扩容,使用spi通信控制w25q64存储便是一个好方法。

操作也很简单,就是将我们动作记忆的链表或数组写入w25q64内,在需要执行动作时读出即可。

需要注意的是,写入之前必须执行擦除操作,并且写入时最多只能写入一页。

八IIC控制OLED屏显

这一部分比较简单,网上大部分都有OLED的控制教程,主要了解OLED显示原理和IIC底层原理即可,也可以使用SPI控制OLED。

九FreeRTOS

使用抢占式和时间片调度算法,设置摇杆和蓝牙控制为最高优先级。因为无论是旋转编码器控制还是动作自动执行,都是通过控制关于角度的数组来实现的,所以各个任务之间交替控制舵机,必然会造成舵机的失控从而得不到我们想要的现象。在实现摇杆和蓝牙控制时,需将旋转编码器控制的任务挂起防止对舵机角度的干扰;在执行旋转编码器控制时,则需将摇杆控制挂起;在执行自动动作时,则需将摇杆蓝牙控制、旋转编码器控制的任务挂起。这样,既可以实现各个任务之间的互不干扰,也可以提高系统的实时性和程序的执行效率。挂起和恢复任务也均可以使用蓝牙串口或者按键控制

小结

在项目的实现中,遇到了诸多困难

1摇杆控制

1.ADC通道间信号干扰

最开始使用摇杆控制舵机的时候,使用ADC转换,直接将ADC转换的值通过计算得出对应的角度值控制舵机,就是0~4095对应舵机旋转0~180度。这样在静止时,不可避免地会出现机械抖动使舵机抖动,我使用了连续采取数据(奇数个),并且对这些数据进行排序,将最大最小的数据去掉,然后求平均值的方法解决,实现静止时的软件消抖。

但是,问题又来了。在我控制摇杆摆动时,其他ADC通道的舵机居然出现了大幅抖动,观察到其他通道的ADC采样数据也出现了大幅变化。在网上查阅资料后,总结有可能是因为使用单个ADC模式下通道间数据会出现干扰,于是我转换成双ADC模式,但是问题并没有有效解决。

经过深入研究和网上参考大量资料后,我将单片机和摇杆共地端单独开了一路ADC转换,发现当我大幅度摆动摇杆时,共地端的ADC输入不为零了!也就是说此时的共地端电压不为0,那么0~4095的数据自然对应的不是0~5V电压,肯定会导致大幅抖动。之后我在每一次ADC采样前都采样一次共地端的ADC值,并等待共地端ADC转换值为0时再进行转换。抖动似乎解决了。不过我仍旧认为摇杆控制的不够丝滑。

2.摇杆控制的最终实现

之后,我通过思考,在ADC输入大于4000时使舵机角度逐渐加一,在小于1000时,使舵机角度逐渐减一,这样完美的解决了摇杆控制问题,不过上面的消抖似乎也多余了

2引脚冲突

通过引脚重映射解决

  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
基于FreeRTOS操作系统的stm32F103C8T6项目可以实现多任务并发运行。FreeRTOS是一个小巧、可裁剪的RTOS系统,具有以下特点: 1. 内核支持抢占式、合作式和时间片调度,可以根据任务的优先级来决定下一刻应该运行哪个任务。 2. 提供了一个用于低功耗的Tickless模式,可以有效节省能源。 3. 支持动态或静态RAM,可以根据需要选择创建任务、消息队列、信号量、软件定时器等组件时使用的内存类型。 4. 已经在超过30种架构的芯片上进行了移植,包括stm32F103系列。 5. FreeRTOS系统简单、小巧、易用,通常情况下内核占用4k-9k字节的空间。 6. 支持实时任务和协程,任务与任务、任务与中断之间可以使用任务通知、消息队列、信号量等进行通信和同步。 7. 具有优先级继承特性的互斥信号量,高效的软件定时器,强大的跟踪执行功能和堆栈溢出检测功能。 8. 任务数量和优先级没有限制,可以根据项目需求创建任意数量和优先级的任务。 因此,基于FreeRTOS操作系统的stm32F103C8T6项目可以充分利用该RTOS系统的特点,实现多任务并发运行,提高系统的实时性和可靠性。 #### 引用[.reference_title] - *1* [stm32F103C8T6基于FreeRTOS操作系统的多任务(STM32CUBEMX)](https://blog.csdn.net/Mouer__/article/details/121616118)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [stm32F103C8T6基于FreeRTOS操作系统的多任务](https://blog.csdn.net/weixin_46129506/article/details/121659483)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值