Featured image of post 按键和摇杆

按键和摇杆

按键和摇杆

按键

image-20260417161355778

image-20260417161420945

image-20260417161607474

1
2
3
4
5
6
7
目前这几个按键对应的功能是
    KEY1   -----------------  定高
    KEY2   -----------------  基准校验
    KEY5   -----------------  UP			PITCH 俯仰 + 5
    KEY6   -----------------  DOWN			PITCH 俯仰 - 5
    KEY7   -----------------  LEFT			ROLL 横滚 - 5
    KEY8   -----------------  RIGHT			ROLL 横滚 + 

Int_key.c

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
#include "Int_key.h"
static Key_type Int_key_scan(void);

Key_type Int_key_get(void){
    return Int_key_scan();
}

/**
 * @brief 扫描按键(无阻塞实现)
 * @return 按键类型
 * @note 在FreeRTOS中使用状态机实现,避免阻塞
 */
static Key_type Int_key_scan(void)
{
    // 方向键检测
    if(HAL_GPIO_ReadPin(KEY_UP_GPIO_Port, KEY_UP_Pin) == GPIO_PIN_RESET){
        vTaskDelay(10); // 消抖
        if(HAL_GPIO_ReadPin(KEY_UP_GPIO_Port, KEY_UP_Pin) == GPIO_PIN_RESET){
            while(HAL_GPIO_ReadPin(KEY_UP_GPIO_Port, KEY_UP_Pin) == GPIO_PIN_RESET){
                vTaskDelay(1);
            }
            return KEY_UP;
        }
    }
    else if(HAL_GPIO_ReadPin(KEY_DOWN_GPIO_Port, KEY_DOWN_Pin) == GPIO_PIN_RESET){
        vTaskDelay(10); // 消抖
        if(HAL_GPIO_ReadPin(KEY_DOWN_GPIO_Port, KEY_DOWN_Pin) == GPIO_PIN_RESET){
            while(HAL_GPIO_ReadPin(KEY_DOWN_GPIO_Port, KEY_DOWN_Pin) == GPIO_PIN_RESET){
                vTaskDelay(1);
            }
            return KEY_DOWN;
        }
    }
    else if(HAL_GPIO_ReadPin(KEY_LEFT_GPIO_Port, KEY_LEFT_Pin) == GPIO_PIN_RESET){
        vTaskDelay(10); // 消抖
        if(HAL_GPIO_ReadPin(KEY_LEFT_GPIO_Port, KEY_LEFT_Pin) == GPIO_PIN_RESET){
            while(HAL_GPIO_ReadPin(KEY_LEFT_GPIO_Port, KEY_LEFT_Pin) == GPIO_PIN_RESET){
                vTaskDelay(1);
            }
            return KEY_LEFT;
        }
    }
    else if(HAL_GPIO_ReadPin(KEY_RIGHT_GPIO_Port, KEY_RIGHT_Pin) == GPIO_PIN_RESET){
        vTaskDelay(10); // 消抖
        if(HAL_GPIO_ReadPin(KEY_RIGHT_GPIO_Port, KEY_RIGHT_Pin) == GPIO_PIN_RESET){
            while(HAL_GPIO_ReadPin(KEY_RIGHT_GPIO_Port, KEY_RIGHT_Pin) == GPIO_PIN_RESET){
                vTaskDelay(1);
            }
            return KEY_RIGHT;
        }
    }

    // 左上按键 KEY1 - 长按检测
    else if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET){
        
        TickType_t count1= xTaskGetTickCount();
        vTaskDelay(10); // 消抖
        if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET){
                while(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET){
                    vTaskDelay(1);
                }
                TickType_t count2 = xTaskGetTickCount();
                if(count2 - count1 >= 1000){
                    return KEY1_LONG;
                }
        }   
    }
    // 右上按键 KEY2 - 长按检测
    else if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET){
        TickType_t count1= xTaskGetTickCount();
        vTaskDelay(10); // 消抖
        if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET){
           
                while(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET){
                    vTaskDelay(1);
                }
                TickType_t count2 = xTaskGetTickCount();

                if(count2 - count1 >= 1000){
                    return KEY2_LONG;
                }
                return KEY2;
        }
    }
    else if(HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET){
        vTaskDelay(10); // 消抖
        if(HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET){
            while(HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET){
                vTaskDelay(1);
            }
            return KEY3;
        }
    }
    else if(HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET){
        vTaskDelay(10); // 消抖
        if(HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET){
            while(HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET){
                vTaskDelay(1);
            }
            return KEY4;
        }
    }

    return KEY_NONE;
}

由于使用FreeRTOS调度的任务,所以使用系统的HAL库延时函数,HAL库的延时函数具有阻塞的特点

应该使用FreeRTOS中的延时函数,这个在延时的时候,会释放CPU,不会阻塞

程序就是

  • 检测是否按下,延时消抖
  • 再判断是否按下,这个按下是说明真的按下,而不是按键抖动
  • 等待松手

长短按就是通过定时器计数,数值超过一定值算长按,否则短按等

  • 检测是否按下,通过调用FreeRTOS的函数获取当前的时间,再消抖
  • 再判断是否按下,这个按下是说明真的按下,而不是按键抖动
  • 检测到松手后,马上再次获取当前时间
  • 两个时间之差,第二次 - 第一次 如果大于X( 自己设定)说明长按,否则短按

Int_key.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef __INT_KEY_H__
#define __INT_KEY_H__

#include  "main.h"
#include "FreeRTOS.h"
#include "task.h"

typedef enum{
    KEY_NONE = 0,
    KEY1,           // 左上
    KEY1_LONG,      // 左上长按
    KEY2,           // 右上
    KEY2_LONG,      // 右上长按
	KEY3,
	KEY4,
    KEY_LEFT,       // 左
    KEY_RIGHT,      // 右
    KEY_DOWN,       // 下
    KEY_UP,         // 上
} Key_type;

Key_type Int_key_get(void);

#endif // __INT_KEY_H__

通过程序返回的KEY的键值再设计对应的程序

摇杆

image-20260417193727361

image-20260417193746895

1
2
3
4
5
6
7
8
YAW			------------------- 		PA0
THR			------------------- 		PA1
ROLL		------------------- 		PA3
PITCH		------------------- 		PA2 
YAW			偏航 / 航向					控制飞机机头 左右转向
THR			油门						 控制飞机 上升 / 下降
PITCH		俯仰						 控制飞机 抬头 / 低头
ROLL		横滚 						 控制飞机 左右倾斜

image-20260417195310152

使用的是单片机的ADC1的四个通道

image-20260417195743771

打开DMA,直接传输数据,不经过CPU计算

image-20260417195846694

image-20260417200137875

ADC的分频要求不能超过12

Int_joystick.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "Int_joystick.h"
#include <stdint.h>

uint16_t adc_buffer[4]; //ADC数据缓冲区
/**!SECTION
*   初始化ADC遥控   打开ADC
*/
void Int_joystick_init(void){

    //打开ADC
    //16位数据地址  其实是32位的数据 32位数据的地址为32位
    //32单片机 使用的ADC转换为12位精度 0-4095
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 4);

    
}

/**!SECTION
*    读取ADC遥控数据  保存到结构体地址
*/
void Int_joystick_get(Joystick_Struct *joystick){
    //DMA不依赖CPU计算
    //实时保存到ADC数据缓冲区 对应通道顺序
    joystick->thr = adc_buffer[1];
    joystick->yaw = adc_buffer[0];
    joystick->pit = adc_buffer[2];
    joystick->rol = adc_buffer[3];

}

Int_joystick.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef __INT_JOYSTICK_H__
#define __INT_JOYSTICK_H__

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


typedef  struct{
    uint16_t thr;
    uint16_t yaw;
    uint16_t pit;
    uint16_t rol;
}Joystick_Struct;


/**!SECTION
*   初始化ADC遥控   打开ADC
*/
void Int_joystick_init(void);

/**!SECTION
*    读取ADC遥控数据  保存到结构体地址
*/
void Int_joystick_get(Joystick_Struct *joystick);

#endif // !

App_process_data.c

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include "App_process_data.h"
#include <stdint.h>

/*******************************************************************************************************/
Joystick_Struct joystick = {0}; //全局变量 存储遥控数据
Remote_Data remote_data = {0};
/*******************************************************************************************************/
//区分摇杆的控制值和按键的微调值
int16_t key_pit_offset = 0;
int16_t key_roll_offset = 0;

//零偏校准值
int16_t thr_zero_offset = 0;
int16_t yaw_zero_offset = 0;
int16_t pit_zero_offset = 0;
int16_t rol_zero_offset = 0;    

/*******************************************************************************************************/
void App_calibrate_joystick(void){
    //零偏校准的逻辑就是减去零偏的值
    
    //首先清空按键的为微调值
    key_pit_offset = 0;
    key_roll_offset = 0;

    //多次读取取平均值 作为零偏值
    int16_t thr_sum = 0;
    int16_t yaw_sum = 0;
    int16_t pit_sum = 0;
    int16_t rol_sum = 0;
    for(uint8_t i = 0; i < 10; i++){
        App_process_joystick_data(); 	// 获取当前的遥控数据 此时在中间位置
        thr_sum += joystick.thr - 0;    // 油门的零偏值为0
        yaw_sum += joystick.yaw - 500;  // 航向的零偏值为500
        pit_sum += joystick.pit - 500;  // 俯仰的零偏值为500
        rol_sum += joystick.rol - 500;  // 横滚的零偏值为500
        vTaskDelay(10); // 延时10ms
    }
    //零偏校准的偏移值没有累加的效果 会造成两次校准退回的情况
    //第二次退回 是因为第一次校准时已经准了 第二次时没有误差校准值为0 所以退回
    thr_zero_offset += thr_sum / 10;
    yaw_zero_offset += yaw_sum / 10;
    pit_zero_offset += pit_sum / 10; 
    rol_zero_offset += rol_sum / 10;

}

/*******************************************************************************************************/
/**!SECTION
* 处理按键数据      => 如果有按键按下 需要进行对应的记录
*/
void App_process_key_data(void){
  Key_type key = Int_key_get();
  //如果进行摇杆的校准 将按键的值调整为0

  if(key == KEY_UP){
    //向前飞微调 俯仰角+
    key_pit_offset += 10;
  }
  else if(key == KEY_DOWN){
    //向后飞微调 俯仰角-
    key_pit_offset -= 10;
  }
  else if(key == KEY_LEFT){
    //向左飞微调 横滚角-
    key_roll_offset -= 10;
  }
  else if(key == KEY_RIGHT){
    //向右飞微调 横滚角+
    key_roll_offset += 10;
  }
  else if(key == KEY1){
    remote_data.fix_height = 1;     //定高开关打开
  }
  else if(key == KEY1_LONG){

  }
  else if(key == KEY2){
    //摇杆校准
    //触发校准流程  摇杆值 THR = 0 YAW = 500  PIT = 500 ROL = 500
  }
}

/**!SECTION
* 处理摇杆数据      => 修正极性相位和标准值
*/
void App_process_joystick_data(void){

    //进入临界区
    taskENTER_CRITICAL();
    
    //获取遥控数据
    Int_joystick_get(&joystick);

    //处理范围和极性 想要范围 0 - 1000 
    //adc范围 0-4095  1000/4095 = 0.244
    joystick.thr = 1000 - ((joystick.thr * 1000) / 4095);
    joystick.yaw = 1000 - ((joystick.yaw * 1000) / 4095) ; 
    joystick.pit = 1000 - ((joystick.pit * 1000) / 4095);
    joystick.rol = 1000 - ((joystick.rol * 1000) / 4095) ; 

    //零偏校准
    joystick.thr -= thr_zero_offset;
    joystick.yaw -= yaw_zero_offset;
    joystick.pit -= pit_zero_offset;
    joystick.rol -= rol_zero_offset;

    //按键微调
    joystick.pit += key_pit_offset;
    joystick.rol += key_roll_offset;

    //计算上偏移值限制范围
    joystick.thr = Com_limit(joystick.thr, 0, 1000);
    joystick.yaw = Com_limit(joystick.yaw, 0, 1000);
    joystick.pit = Com_limit(joystick.pit, 0, 1000);
    joystick.rol = Com_limit(joystick.rol, 0, 1000);

    //退出临界区
    taskEXIT_CRITICAL();
    debug_printf("joystick: thr=%d, yaw=%d, pit=%d, rol=%d\r\n", joystick.thr, joystick.yaw, joystick.pit, joystick.rol);
}

App_process_data.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef APP_PROCESS_DATA_H
#define APP_PROCESS_DATA_H

#include "Int_joystick.h"
#include "Int_key.h"
#include "Com_debug.h"
#include "Com_tool.h"
/**********************************************************************************************************/
typedef struct {
    int16_t thr; // 油门
    int16_t yaw; // 航向
    int16_t pit; // 俯仰
    int16_t rol; // 横滚
    uint8_t fix_height;  // 定高
}Remote_Data;
/**********************************************************************************************************/
/**!SECTION
* 处理按键数据      => 如果有按键按下 需要进行对应的记录
*/
void App_process_key_data(void);
/**!SECTION
* 处理摇杆数据      => 修正极性相位和标准值
*/
void App_process_joystick_data(void);

#endif // APP_PROCESS_DATA_H

就是通过ADC获取摇杆的数据

然后再对其进行划分极性和范围 油门一般 从下往上是加大油门

再进行零偏校准

  1. 摇杆的油门 按理来说从0开始
  2. 摇杆的 航向 俯仰 横滚 都是处于中间的500
  3. 一开始想成油门在最下面,就是摇杆往下掰,其他的也变了。实则不然,其他方向还是在中点

零偏校准最好采用采集多次取平均值,得到偏移值

最后再减去偏移值,就是最终的要的

但是有的时候会超出范围,就再写一个限制范围的函数

为什么使用临界区呢

就是因为微调时,就是按键微调这个任务也会修改遥控数据,所以添加个临界区

达到在处理App_process_joystick_data这个获取摇杆数据时,不被在设置某个值的时候,突然让按键任务打断,从而让按键修改摇杆数据值

最后更新于 2026-04-18 01:45