目录

一、原理图

①按键

②摇杆

二、STM32CubeMX

①按键

②摇杆

三、Keil

①按键

②摇杆

③遥控处理和发送数据

④飞控接收和处理数据

四、VSCode

①按键

(1)int_key.h

(2)int_key.c

(3)App_freertos_task.c

②摇杆

(1)int_joystick.h

(2)int_jotstick.c

③遥控处理和发送数据

 (1)Com_tool.h

(2)Com_tool.c

(3)App_process_data.h

(4)App_process_data.c

(5)App_transmit_data.h

(6)App_transmit_data.c

(7)Int_SI2421.H

④飞控接收和处理数据

(1)Com_config.h

(2)Int_IP5305T.h

(3)Int_IP5305T.c

(4)App_receive_data.h

(5)App_receive_data.c

(6)App_freertos_task.c

五、可能会遇到的问题


一、原理图

①按键

②摇杆

二、STM32CubeMX

①按键

(1)六个引脚均为GPIO_Input

(2)均为上拉

(3)设置用户标签

②摇杆

(1)在ADC1中打开IN1,2,3,6

(2)配置中打开连续转换

(3)选择转换通道为4个

(4)四个Rank的顺序是1623,采样周期为13.5

(5)配置DMA,添加ADC1,模式为循环模式

(6)时钟配置,将ADC的分频器为/6(ADC原则上不能超过12M)

三、Keil

①按键

(1)在interface下创建int_key.c/.h文件

(2)添加.c/.h文件

②摇杆

(1)在interface下创建int_joystick.c/.h文件

(2)添加.c/.h文件

③遥控处理和发送数据

(1)在Application中创建App_process_data.c/.h文件

(2)在Application中创建App_transmit_data.c/.h文件

(3)在common中创建Com_tool.c/.h文件

(4)添加.c/.h文件

④飞控接收和处理数据

(1)在Application中创建App_receive_data.c/.h文件

(2)添加.c/.h文件

四、VSCode

①按键

(1)int_key.h

#ifndef __INT_KEY__
#define __INT_KEY__

#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
typedef enum
{
    KEY_NONE = 0,
    KEY_UP,
    KEY_DOWN,
    KEY_LEFT,
    KEY_RIGHT,
    KEY_LEFT_X,
    KEY_RIGHT_X,
    KEY_RIGHT_X_LONG,
}Key_type;


/**
 * @brief 获取当前按键是否被按下
 * 
 * @return Key_type KEY_NONE:没有按键按下  其他都是对应被按键的标记
 */
Key_type Int_key_get(void);

#endif // __INT_KEY__

(2)int_key.c

#include "Int_key.h"

/**
 * @brief 获取当前按键是否被按下
 *
 * @return Key_type KEY_NONE:没有按键按下  其他都是对应被按键的标记
 */
Key_type Int_key_get(void)
{
    if (HAL_GPIO_ReadPin(KEY_UP_GPIO_Port, KEY_UP_Pin) == GPIO_PIN_RESET)
    {
        // 1. 电弧抖动 => 需要进行消抖
        vTaskDelay(5);
        if (HAL_GPIO_ReadPin(KEY_UP_GPIO_Port, KEY_UP_Pin) == GPIO_PIN_RESET)
        {
            // 2. 人按下的时间通常比较长  =>  为了不被多次判断 => 等待抬起按键才返回
            while (HAL_GPIO_ReadPin(KEY_UP_GPIO_Port, KEY_UP_Pin) == GPIO_PIN_RESET)
            {
                vTaskDelay(1);
            }

            // KEY_UP被按下
            return KEY_UP;
        }
    }
    else if (HAL_GPIO_ReadPin(KEY_DOWN_GPIO_Port, KEY_DOWN_Pin) == GPIO_PIN_RESET)
    {
        // 1. 电弧抖动 => 需要进行消抖
        vTaskDelay(5);
        if (HAL_GPIO_ReadPin(KEY_DOWN_GPIO_Port, KEY_DOWN_Pin) == GPIO_PIN_RESET)
        {
            // 2. 人按下的时间通常比较长  =>  为了不被多次判断 => 等待抬起按键才返回
            while (HAL_GPIO_ReadPin(KEY_DOWN_GPIO_Port, KEY_DOWN_Pin) == GPIO_PIN_RESET)
            {
                vTaskDelay(1);
            }
            // KEY_DOWN被按下
            return KEY_DOWN;
        }
    }
    else if (HAL_GPIO_ReadPin(KEY_LEFT_GPIO_Port, KEY_LEFT_Pin) == GPIO_PIN_RESET)
    {
        // 1. 电弧抖动 => 需要进行消抖
        vTaskDelay(5);
        if (HAL_GPIO_ReadPin(KEY_LEFT_GPIO_Port, KEY_LEFT_Pin) == GPIO_PIN_RESET)
        {
            // 2. 人按下的时间通常比较长  =>  为了不被多次判断 => 等待抬起按键才返回
            while (HAL_GPIO_ReadPin(KEY_LEFT_GPIO_Port, KEY_LEFT_Pin) == GPIO_PIN_RESET)
            {
                vTaskDelay(1);
            }
            // KEY_LEFT被按下
            return KEY_LEFT;
        }
    }
    else if (HAL_GPIO_ReadPin(KEY_RIGHT_GPIO_Port, KEY_RIGHT_Pin) == GPIO_PIN_RESET)
    {
        // 1. 电弧抖动 => 需要进行消抖
        vTaskDelay(5);
        if (HAL_GPIO_ReadPin(KEY_RIGHT_GPIO_Port, KEY_RIGHT_Pin) == GPIO_PIN_RESET)
        {
            // 2. 人按下的时间通常比较长  =>  为了不被多次判断 => 等待抬起按键才返回
            while (HAL_GPIO_ReadPin(KEY_RIGHT_GPIO_Port, KEY_RIGHT_Pin) == GPIO_PIN_RESET)
            {
                vTaskDelay(1);
            }
            // KEY_RIGHT被按下
            return KEY_RIGHT;
        }
    }

    // 左上按键
    else if (HAL_GPIO_ReadPin(KEY_LEFT_X_GPIO_Port, KEY_LEFT_X_Pin) == GPIO_PIN_RESET)
    {
        // 1. 电弧抖动 => 需要进行消抖
        vTaskDelay(5);
        if (HAL_GPIO_ReadPin(KEY_LEFT_X_GPIO_Port, KEY_LEFT_X_Pin) == GPIO_PIN_RESET)
        {
            // 2. 人按下的时间通常比较长  =>  为了不被多次判断 => 等待抬起按键才返回
            while (HAL_GPIO_ReadPin(KEY_LEFT_X_GPIO_Port, KEY_LEFT_X_Pin) == GPIO_PIN_RESET)
            {
                vTaskDelay(1);
            }

            // 左上按键被按下
            return KEY_LEFT_X;
        }
    }
    // 右上按键
    else if (HAL_GPIO_ReadPin(KEY_RIGHT_X_GPIO_Port, KEY_RIGHT_X_Pin) == GPIO_PIN_RESET)
    {
        // 此时开始计时 => 长按为超过1s
        TickType_t count1 = xTaskGetTickCount();
        // 1. 电弧抖动 => 需要进行消抖
        vTaskDelay(5);
        if (HAL_GPIO_ReadPin(KEY_RIGHT_X_GPIO_Port, KEY_RIGHT_X_Pin) == GPIO_PIN_RESET)
        {
            // 被人按下 不是电弧抖动
            while (HAL_GPIO_ReadPin(KEY_RIGHT_X_GPIO_Port, KEY_RIGHT_X_Pin) == GPIO_PIN_RESET)
            {
                // 1ms的时间 cpu是释放的
                vTaskDelay(1);
            }
            TickType_t count2 = xTaskGetTickCount();
            if (count2 - count1 > 1000)
            {
                return KEY_RIGHT_X_LONG;
            }
            else
            {
                return KEY_RIGHT_X;
            }
        }
    }

    // 没有按键按下
    return KEY_NONE;
}

(3)App_freertos_task.c

#include "App_freeRTOS_Task.h"

// STM32F103C8T6 => SRAM 20k  => 分配12K给操作系统
// 电源管理任务
void power_task(void *args);
// 最小推荐填写128 => 128*4 = 512B
#define POWER_TASK_STACK_SIZE 128
// 任务优先级 => 数值越小 优先级越小  => 最大4  => 不推荐使用最小优先级0
#define POWER_TASK_PRIORITY 4
TaskHandle_t power_task_handle;
#define POWER_TASK_PERIOD 10000

// 通讯任务
void com_task(void *args);
#define COM_TASK_STACK_SIZE 128
#define COM_TASK_PRIORITY 3
TaskHandle_t com_task_handle;
// 任务周期
#define COM_TASK_PERIOD 6

// 按键任务
void key_task(void *args);
#define KEY_TASK_STACK_SIZE 128
#define KEY_TASK_PRIORITY 2
TaskHandle_t key_task_handle;
// 任务周期
#define KEY_TASK_PERIOD 20

// 摇杆任务
void joy_task(void *args);
#define JOY_TASK_STACK_SIZE 128
#define JOY_TASK_PRIORITY 2
TaskHandle_t joy_task_handle;
// 任务周期
#define JOY_TASK_PERIOD 20

/**
 * @brief 启动freeRTOS操作系统
 *
 */
void App_freeRTOS_start(void)
{
    // 1. 创建电源管理任务
    xTaskCreate(power_task, "power_task", POWER_TASK_STACK_SIZE, NULL, POWER_TASK_PRIORITY, &power_task_handle);

    // 2. 创建通讯任务
    xTaskCreate(com_task, "com_task", COM_TASK_STACK_SIZE, NULL, COM_TASK_PRIORITY, &com_task_handle);

    // 3. 按键任务
    xTaskCreate(key_task, "key_task", KEY_TASK_STACK_SIZE, NULL, KEY_TASK_PRIORITY, &key_task_handle);

    // 4. 摇杆任务
    xTaskCreate(joy_task, "joy_task", JOY_TASK_STACK_SIZE, NULL, JOY_TASK_PRIORITY, &joy_task_handle);

    // 5. 启动调度器
    vTaskStartScheduler();
}

void power_task(void *args)
{
    // 获取当前的基准时间
    TickType_t xLastWakeTime = xTaskGetTickCount();
    while (1)
    {

        // 每10s执行一次  =>  启动电源  避免自动关机
        vTaskDelayUntil(&xLastWakeTime, POWER_TASK_PERIOD);

        // 启动电源
        Int_IP5305T_start();
    }
}

void joy_task(void *args)
{
    TickType_t xLastWakeTime = xTaskGetTickCount();
    // 1. 初始化摇杆ADC
    Int_joystick_init();
    while (1)
    {
        // 统一的处理方式
        App_process_joystick_data();
        vTaskDelayUntil(&xLastWakeTime, JOY_TASK_PERIOD);
    }
}

void key_task(void *args)
{
    TickType_t xLastWakeTime = xTaskGetTickCount();
    while (1)
    {
        // 统一的处理方式
        App_process_key_data();
        vTaskDelayUntil(&xLastWakeTime, KEY_TASK_PERIOD);
    }
}

void com_task(void *args)
{
    // 获取当前的基准时间
    TickType_t xLastWakeTime = xTaskGetTickCount();
    while (1)
    {
        // 将打包完成的数据发送到飞机
        App_transmit_data();
        // 6ms执行一次
        vTaskDelayUntil(&xLastWakeTime, COM_TASK_PERIOD);
    }
}

②摇杆

(1)int_joystick.h

#ifndef __INT_JOYSTICK__
#define __INT_JOYSTICK__

#include "adc.h"

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

/**
 * @brief 初始化ADC遥控  打开ADC
 *
 */
void Int_joystick_init(void);

/**
 * @brief 读取ADC数据 保存到结构体地址中
 * 
 * @param joystick 
 */
void Int_joystick_get(Joystick_Struct *joystick);

#endif // __INT_JOYSTICK__

(2)int_jotstick.c

#include "Int_joystick.h"


uint16_t adc_buff[4] = {0};


/**
 * @brief 初始化ADC遥控  打开ADC
 *
 */
void Int_joystick_init(void)
{
    // 直接使用HAL中的函数  开启ADC
    // 16位数据的地址值  其实是32位   32位数据的地址值也是32位
    // 32单片机 使用的ADC转换为12位精度  范围 0-4095
    HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buff,4);
}

/**
 * @brief 读取ADC数据 保存到结构体地址中
 * 
 * @param joystick 
 */
void Int_joystick_get(Joystick_Struct *joystick)
{
    // DMA不依赖与CPU的计算的  所以读取的数据是实时保存到ADC_BUFF中
    // 顺序是自定义的RAN1234  一定要对齐
    joystick->thr = adc_buff[0];
    joystick->yaw = adc_buff[1];
    joystick->pit = adc_buff[2];
    joystick->rol = adc_buff[3];
}

③遥控处理和发送数据

 (1)Com_tool.h

#ifndef __COM_TOOL__
#define __COM_TOOL__

#include "main.h"

/**
 * @brief 限幅函数
 * 
 * @param value 
 * @param min 
 * @param max 
 * @return int16_t 
 */
int16_t Com_limit(int16_t value, int16_t min, int16_t max);

#endif // __COM_TOOL__

(2)Com_tool.c

#include "Com_tool.h"

/**
 * @brief 限幅函数
 *
 * @param value
 * @param min
 * @param max
 * @return int16_t
 */
int16_t Com_limit(int16_t value, int16_t min, int16_t max)
{
    int16_t result = value;
    if (value > max)
    {
        result = max;
    }
    else if (value < min)
    {
        result = min;
    }
    return result;
}

(3)App_process_data.h

#ifndef __APP_PROCESS_DATA__
#define __APP_PROCESS_DATA__

#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 shutdown;   // 1: 关闭  0: 不关机
    uint8_t fix_height; // 1. 切换定高和不定高 0: 不切换
} Remote_Data;

/**
 * @brief 处理按键数据 => 如果有按键按下 需要进行对应的记录
 *
 */
void App_process_key_data(void);

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

#endif // __APP_PROCESS_DATA__

(4)App_process_data.c

#include "App_process_data.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_offset = 0;
int16_t yaw_offset = 0;
int16_t pit_offset = 0;
int16_t rol_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;
        yaw_sum += joystick.yaw - 500;
        pit_sum += joystick.pit - 500;
        rol_sum += joystick.rol - 500;
        vTaskDelay(10);
    }

    // 零偏校准的偏移值没有累加的效果  会造成两次校准退回的情况
    thr_offset += thr_sum / 10;
    yaw_offset += yaw_sum / 10;
    pit_offset += pit_sum / 10;
    rol_offset += rol_sum / 10;
}

/**
 * @brief 如果freeRTOS的两个任务优先级相等 => 两个任务会交替运行
 *
 */

/**
 * @brief 处理按键数据 => 如果有按键按下 需要进行对应的记录
 *
 */
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 == KEY_LEFT_X)
    {
        remote_data.shutdown = 1;
    }
    else if (key == KEY_RIGHT_X)
    {
        remote_data.fix_height = 1;
    }
    else if (key == KEY_RIGHT_X_LONG)
    {
        // 校准摇杆 => 等到摇杆写完之后才知道怎么校准
        // 触发校准之后  摇杆的值THR为0  YAW PIT ROL 为500
        App_calibrate_joystick();
    }
}

/**
 * @brief 处理摇杆数据  => 修正极性相位和标准值
 *
 */
void App_process_joystick_data(void)
{
    // 如何解决freeRTOS的任务切换问题
    // 使用临界区解决
    taskENTER_CRITICAL();

    // 1. 获取摇杆监控的ADC值
    Int_joystick_get(&joystick);

    // 理论上  摇杆的任务运行到这一行  有可能跳转到按键任务的 => 会出现按键任务使用的摇杆数据没有经过处理
    // 2. 处理范围和极性  想要使用的范围值0-1000 => ADC的范围是0-4095
    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);

    // 3. 处理零偏校准
    joystick.thr -= thr_offset;
    joystick.yaw -= yaw_offset;
    joystick.pit -= pit_offset;
    joystick.rol -= rol_offset;

    // 4. 考虑按键的微调值
    joystick.pit += key_pit_offset;
    joystick.rol += key_roll_offset;

    // 5. 计算上偏移值可能超出范围  => 限制在0-1000
    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();

    // 将处理完成的数据赋值给遥控数据
    remote_data.thr = joystick.thr;
    remote_data.yaw = joystick.yaw;
    remote_data.pit = joystick.pit;
    remote_data.rol = joystick.rol;

    // debug_printf(":%d,%d,%d,%d\n", joystick.thr, joystick.yaw, joystick.pit, joystick.rol);
}

(5)App_transmit_data.h

#ifndef __APP_TRANSMIT_DATA__
#define __APP_TRANSMIT_DATA__

#include "Int_SI24R1.h"
#include "App_process_data.h"
#include "FreeRTOS.h"
#include "task.h"
// 定义帧头校验的值
#define FRAME_HEAD_CHECK_1 's'
#define FRAME_HEAD_CHECK_2 'g'
#define FRAME_HEAD_CHECK_3 'g'

/**
 * @brief 自动切换SI24R1的模式 => 将采集完成的遥控数据打包发送到飞机
 * 
 */
void App_transmit_data(void);


#endif // __APP_TRANSMIT_DATA__

(6)App_transmit_data.c

#include "App_transmit_data.h"

extern Remote_Data remote_data;
uint8_t transmit_buff[TX_PLOAD_WIDTH] = {0};
/**
 * @brief 自动切换SI24R1的模式 => 将采集完成的遥控数据打包发送到飞机
 *
 */
void App_transmit_data(void)
{
    // 1. 切换到发送模式
    Int_SI24R1_TX_Mode();

    // 2. 发送数据 => 唯一性和可靠性
    // (1) => 帧头校验指定发送给对应的设备 唯一性
    // (2) => 发送数据的结尾 => 添加检验和(将发送的数据累加得到的值添加到数据的末尾) 可靠性

    uint32_t sum = 0;
    //  帧头校验设置为3字节 => 数据本身是10字节 => 校验和设置为4字节
    transmit_buff[0] = FRAME_HEAD_CHECK_1;
    transmit_buff[1] = FRAME_HEAD_CHECK_2;
    transmit_buff[2] = FRAME_HEAD_CHECK_3;

    // 高8位在前
    transmit_buff[3] = (remote_data.thr >> 8) & 0xFF;
    transmit_buff[4] = remote_data.thr & 0xFF;

    transmit_buff[5] = (remote_data.yaw >> 8) & 0xFF;
    transmit_buff[6] = remote_data.yaw & 0xFF;

    transmit_buff[7] = (remote_data.pit >> 8) & 0xFF;
    transmit_buff[8] = remote_data.pit & 0xFF;

    transmit_buff[9] = (remote_data.rol >> 8) & 0xFF;
    transmit_buff[10] = remote_data.rol & 0xFF;

    // 此处的关机和定高其实是会有0或1
    taskENTER_CRITICAL();
    transmit_buff[11] = remote_data.shutdown;// 正好在此处运行完成之后切换任务 
    remote_data.shutdown = 0;  // 此时shutdown=1  但是上一行代码没有记录下来
    transmit_buff[12] = remote_data.fix_height;
    remote_data.fix_height = 0;
    taskEXIT_CRITICAL();

    for (uint8_t i = 0; i < 13; i++)
    {
        sum += transmit_buff[i];
    }
    // 高位在前
    transmit_buff[13] = (sum >> 24) & 0xFF;
    transmit_buff[14] = (sum >> 16) & 0xFF;
    transmit_buff[15] = (sum >> 8) & 0xFF;
    transmit_buff[16] = sum & 0xFF;

    debug_printf(":%d,%d,%d,%d,%d,%d\n",remote_data.thr,remote_data.yaw,remote_data.pit,remote_data.rol,remote_data.shutdown,remote_data.fix_height);

    Int_SI24R1_TxPacket(transmit_buff);

    // 3. 切换回接收模式
    Int_SI24R1_RX_Mode();
}

(7)Int_SI2421.H

#define TX_PLOAD_WIDTH 17 // 数据通道有效数据宽度
//同时飞控代码也需要修改为17

④飞控接收和处理数据

(1)Com_config.h

#ifndef _COM_CONFIG_H
#define _COM_CONFIG_H

#include "main.h"

// 连接状态
typedef enum
{
    REMOTE_CONNECTED = 0,
    REMOTE_DISCONNECTED,
}Remote_State;

// 飞行状态
typedef enum
{
    IDLE = 0,
    NORMAL,
    FIX_HEIGHT,
    FAIL,
}Flight_State;

typedef struct
{
    int16_t thr;
    int16_t yaw;
    int16_t pit;
    int16_t rol;
    uint8_t shutdown;   // 1: 关闭  0: 不关机
    uint8_t fix_height; // 1. 切换定高和不定高 0: 不切换
} Remote_Data;

#endif // !_COM_CONFIG_H

(2)Int_IP5305T.h

#ifndef __INT_IP5305T__
#define __INT_IP5305T__

#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
/**
 * @brief 启动IP5305T电源 避免自动关机
 * 
 */
void Int_IP5305T_start(void);

/**
 * @brief 软件执行关机指令
 * 
 */
void Int_IP5305T_shutdown(void);

#endif // __INT_IP5305T__

(3)Int_IP5305T.c

#include "Int_IP5305T.h"

/**
 * @brief 启动IP5305T电源 避免自动关机
 *
 */
void Int_IP5305T_start(void)
{
    HAL_GPIO_WritePin(POWER_KEY_GPIO_Port, POWER_KEY_Pin, GPIO_PIN_RESET);
    vTaskDelay(100);
    HAL_GPIO_WritePin(POWER_KEY_GPIO_Port, POWER_KEY_Pin, GPIO_PIN_SET);
}

/**
 * @brief 软件执行关机指令
 *
 */
void Int_IP5305T_shutdown(void)
{
    // 1s中之内连续短按两次按钮 => 关机
    HAL_GPIO_WritePin(POWER_KEY_GPIO_Port, POWER_KEY_Pin, GPIO_PIN_RESET);
    vTaskDelay(100);
    HAL_GPIO_WritePin(POWER_KEY_GPIO_Port, POWER_KEY_Pin, GPIO_PIN_SET);
    vTaskDelay(200);

    HAL_GPIO_WritePin(POWER_KEY_GPIO_Port, POWER_KEY_Pin, GPIO_PIN_RESET);
    vTaskDelay(100);
    HAL_GPIO_WritePin(POWER_KEY_GPIO_Port, POWER_KEY_Pin, GPIO_PIN_SET);
}

(4)App_receive_data.h

#ifndef __APP_RECEIVE_DATA__
#define __APP_RECEIVE_DATA__

#include "Int_SI24R1.h"
#include "Com_config.h"

// 定义帧头校验的值
#define FRAME_HEAD_CHECK_1 's'
#define FRAME_HEAD_CHECK_2 'g'
#define FRAME_HEAD_CHECK_3 'g'

// 最大重试次数
#define MAX_RETRY_TIMES 10

/**
 * @brief 接收遥控器发送的遥控数据 => 解析为结构体
 * 
 * @return uint8_t 0:校验通过 是正常的数据 1:没收到数据 或者 校验失败
 */
uint8_t App_receive_data(void);


/**
 * @brief 处理连接状态的状态
 * 
 * @param res 上一次接收数据的返回值
 */
void App_process_connect_state(uint8_t res);

#endif // __APP_RECEIVE_DATA__

(5)App_receive_data.c

#include "App_receive_data.h"

extern Remote_Data remote_data;

uint8_t rx_buff[TX_PLOAD_WIDTH] = {0};

extern Remote_State remote_state;

// 重试次数
uint8_t retry_count = 0;
/**
 * @brief 接收遥控器发送的遥控数据 => 解析为结构体
 *
 * @return uint8_t 0:校验通过 是正常的数据 1:没收到数据 或者 校验失败
 */
uint8_t App_receive_data(void)
{
    memset(rx_buff, 0, TX_PLOAD_WIDTH);
    Int_SI24R1_RxPacket(rx_buff);
    if (strlen((char *)rx_buff) == 0)
    {
        return 1;
    }

    // 1. 帧头校验
    if (rx_buff[0] != FRAME_HEAD_CHECK_1 || rx_buff[1] != FRAME_HEAD_CHECK_2 || rx_buff[2] != FRAME_HEAD_CHECK_3)
    {
        return 1;
    }

    // 2. 帧尾校验
    uint32_t sum = 0;
    uint32_t sum_receive = 0;

    for (uint8_t i = 0; i < 13; i++)
    {
        sum += rx_buff[i];
    }
    // 高位在前
    sum_receive = rx_buff[13] << 24 | rx_buff[14] << 16 | rx_buff[15] << 8 | rx_buff[16];

    if (sum != sum_receive)
    {
        return 1;
    }

    // 3. 保存数据
    remote_data.thr = (rx_buff[3] << 8) | rx_buff[4];
    remote_data.yaw = (rx_buff[5] << 8) | rx_buff[6];
    remote_data.pit = (rx_buff[7] << 8) | rx_buff[8];
    remote_data.rol = (rx_buff[9] << 8) | rx_buff[10];
    remote_data.shutdown = rx_buff[11];
    remote_data.fix_height = rx_buff[12];

    debug_printf(":%d,%d,%d,%d,%d,%d\n", remote_data.thr, remote_data.yaw, remote_data.pit, remote_data.rol, remote_data.shutdown, remote_data.fix_height);
    return 0;
}

/**
 * @brief 处理连接状态的状态
 *
 * @param res 上一次接收数据的返回值
 */
void App_process_connect_state(uint8_t res)
{
    if (res == 0)
    {
        // 接收数据成功一次 即为连接成功
        // 此处使用的全局变量 只有当前一个地方会修改 LED灯控任务当中是读取使用
        remote_state = REMOTE_CONNECTED;
        retry_count = 0;
    }
    else if (res == 1)
    {
        // 接收数据失败 即为
        retry_count++;
        if (retry_count >= MAX_RETRY_TIMES)
        {
            remote_state = REMOTE_DISCONNECTED;
            retry_count = 0;
        }
    }
}

(6)App_freertos_task.c

#include "App_freeRTOS_Task.h"

// STM32F103C8T6 => SRAM 20k  => 分配12K给操作系统
// 内存管理 => C语言中的结构体通常保存在堆中  不会自动垃圾回收  => 始终使用同一个结构体 不断循环使用

// 电机结构体
Motor_Struct left_top_motor = {.tim = &htim3, .channel = TIM_CHANNEL_1, .speed = 200};
Motor_Struct left_bottom_motor = {.tim = &htim4, .channel = TIM_CHANNEL_4, .speed = 200};
Motor_Struct right_top_motor = {.tim = &htim2, .channel = TIM_CHANNEL_2, .speed = 200};
Motor_Struct right_bottom_motor = {.tim = &htim1, .channel = TIM_CHANNEL_3, .speed = 200};

// LED结构体
LED_Struct left_top_led = {.port = LED1_GPIO_Port, .pin = LED1_Pin};
LED_Struct right_top_led = {.port = LED2_GPIO_Port, .pin = LED2_Pin};
LED_Struct right_bottom_led = {.port = LED3_GPIO_Port, .pin = LED3_Pin};
LED_Struct left_bottom_led = {.port = LED4_GPIO_Port, .pin = LED4_Pin};

// 表示当前连接状态
Remote_State remote_state = REMOTE_DISCONNECTED;

// 表示当前的飞行状态
Flight_State flight_state = NORMAL;

// 扩展获取接收的遥控数据
Remote_Data remote_data = {0};

// 电源管理任务
void power_task(void *args);
// 最小推荐填写128 => 128*4 = 512B
#define POWER_TASK_STACK_SIZE 128
// 任务优先级 => 数值越小 优先级越小  => 最大4  => 不推荐使用最小优先级0
#define POWER_TASK_PRIORITY 4
TaskHandle_t power_task_handle;
// 定义任务的周期
#define POWER_TASK_PERIOD 10000

// 飞行控制任务
void flight_task(void *args);
#define FLIGHT_TASK_STACK_SIZE 128
#define FLIGHT_TASK_PRIORITY 3
TaskHandle_t flight_task_handle;
#define FLIGHT_TASK_PERIOD 6

// LED任务
void led_task(void *args);
#define LED_TASK_STACK_SIZE 128
#define LED_TASK_PRIORITY 1
TaskHandle_t led_task_handle;
#define LED_TASK_PERIOD 100

// 通讯任务
void com_task(void *args);
#define COM_TASK_STACK_SIZE 128
#define COM_TASK_PRIORITY 2
TaskHandle_t com_task_handle;
// 任务周期
#define COM_TASK_PERIOD 6

/**
 * @brief 启动freeRTOS操作系统
 *
 */
void App_freeRTOS_start(void)
{
    // 1. 创建电源管理任务
    xTaskCreate(power_task, "power_task", POWER_TASK_STACK_SIZE, NULL, POWER_TASK_PRIORITY, &power_task_handle);

    // 2. 创建飞行控制任务
    xTaskCreate(flight_task, "flight_task", FLIGHT_TASK_STACK_SIZE, NULL, FLIGHT_TASK_PRIORITY, &flight_task_handle);

    // 3. 创建LED灯任务
    xTaskCreate(led_task, "led_task", LED_TASK_STACK_SIZE, NULL, LED_TASK_PRIORITY, &led_task_handle);

    // 4. 创建通讯任务
    xTaskCreate(com_task, "com_task", COM_TASK_STACK_SIZE, NULL, COM_TASK_PRIORITY, &com_task_handle);

    // 5. 启动调度器
    vTaskStartScheduler();
}

void power_task(void *args)
{
    // 获取当前的基准时间
    TickType_t xLastWakeTime = xTaskGetTickCount();
    while (1)
    {

        // // 每10s执行一次  =>  启动电源  避免自动关机
        // vTaskDelayUntil(&xLastWakeTime, POWER_TASK_PERIOD);

        // // 启动电源
        // Int_IP5305T_start();

        // 使用直接任务通知的接收方法实现10s处理一次
        // 一直等任务通知  直到收到通知res=1  或者  超时res=0
        uint32_t res = ulTaskNotifyTake(pdTRUE, POWER_TASK_PERIOD);
        if (res != 0)
        {
            // 收到关机通知
            Int_IP5305T_shutdown();
        }
        else
        {
            // 不需要关机 => 正常执行启动
            Int_IP5305T_start();
        }
    }
}

void flight_task(void *args)
{
    // 获取当前的基准时间
    TickType_t xLastWakeTime = xTaskGetTickCount();
    while (1)
    {

        // 1. 设置电机的转速
        left_top_motor.speed = 400;

        // 2. 直接启动电机
        // Int_motor_start(&left_top_motor);
        // Int_motor_start(&right_bottom_motor);

        vTaskDelayUntil(&xLastWakeTime, FLIGHT_TASK_PERIOD);
    }
}

void led_task(void *args)
{

    // 获取当前的基准时间
    TickType_t xLastWakeTime = xTaskGetTickCount();
    uint8_t count = 0;
    while (1)
    {
        count++;
        // 前两个灯表示连接状态
        // 1. 判断当前连接状态
        if (remote_state == REMOTE_CONNECTED)
        {
            // 点亮前两个灯
            Int_led_turn_on(&left_top_led);
            Int_led_turn_on(&right_top_led);
        }
        else if (remote_state == REMOTE_DISCONNECTED)
        {
            // 关掉前两个灯
            Int_led_turn_off(&left_top_led);
            Int_led_turn_off(&right_top_led);
        }

        // 后两个灯表示飞行状态
        // 2. 判断当前飞行状态
        if (flight_state == IDLE)
        {
            // 灯慢闪烁 => 500ms亮 500ms灭
            if (count % 5 == 0)
            {
                // 循环5次  一次是100ms  5次等于500ms
                Int_led_toggle(&left_bottom_led);
                Int_led_toggle(&right_bottom_led);
            }
        }
        else if (flight_state == NORMAL)
        {
            // 灯快闪  =>  200ms亮 200ms灭
            if (count % 2 == 0)
            {
                // 循环2次  一次是100ms  2次等于200ms
                Int_led_toggle(&left_bottom_led);
                Int_led_toggle(&right_bottom_led);
            }
        }
        else if (flight_state == FIX_HEIGHT)
        {
            // 后两个灯常量
            Int_led_turn_on(&left_bottom_led);
            Int_led_turn_on(&right_bottom_led);
        }
        else if (flight_state == FAIL)
        {
            // 后两个灯灭
            Int_led_turn_off(&left_bottom_led);
            Int_led_turn_off(&right_bottom_led);
        }

        // 将count计数重置
        if (count == 10)
        {
            count = 0;
        }

        vTaskDelayUntil(&xLastWakeTime, LED_TASK_PERIOD);
    }
}

void com_task(void *args)
{
    // 获取当前的基准时间
    TickType_t xLastWakeTime = xTaskGetTickCount();
    while (1)
    {
        // 1. 接收数据
        uint8_t res = App_receive_data();

        // 2. 根据接收数据的返回值 处理当前飞机的连接状态
        App_process_connect_state(res);

        // 3. 处理关机命令
        if (remote_data.shutdown == 1)
        {
            // 使用Int_IP5305T_shutdown 关机  功能可以实现  但是项目结构比较奇怪
            // Int_IP5305T_shutdown();

            // 使用freeRTOS直接任务通知 => 通知电源任务 => 执行关机
            xTaskNotifyGive(power_task_handle);
        }

        // 6ms执行一次 接收数据的时间间隔应该等于发送数据的时间间隔
        vTaskDelayUntil(&xLastWakeTime, COM_TASK_PERIOD);
    }
}

五、可能会遇到的问题

摇杆极性和范围问题

处理完零偏校准后,有些会出现负数或超出1000,需要写入一个限幅函数。且按两次零偏校准会退回原来的情况,是因为零偏校准的偏移值没有累加导致

由于按键和摇杆是同一优先级,摇杆数据在处理的过程中发生任务的切换会造成错误的原始数据被计算使用,所以在处理数据前后使用临界区(概率很小)

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐