四轴飞控6——无线通信任务
·
目录
一、原理图
①飞控板

②遥控板

二、SI24R1
①状态转换图

②数据包处理协议
(1)ARQ包格式
1、前导码
也叫帧头校验,主要用于接收数据同步,发射时芯片自动附上,接收时芯片自动去掉,对用户不可见,唯一性。
2、地址
地址字段为接收数据方地址,只有当该地址与芯片的地址寄存器中地址相同时才会接收。地址长度可以通过配置寄存器 AW 配置为 3、或 4、或5 字节。
需要注意的是,地址的最高字节不可设为 0xFF、0x00、0xA5、0x5A、0xAA、0x55,否则可能导致接收失败。
3、包控制字
包控制字段长度为 9bit,数据包长度子字段指定数据包的长度,可以为0到32字节例如:000000=0byte(包为空)
100000=32 byte(数据包长度为32字节)
PID 子字段告知接收端这个包是一个新的包还是一个重发的包,可以防止接收端多次接收同一个包。发射方通过SPI写 FIFO,PID 的值自动累加。接收端通过对比 PID和 CRC 来判断接收的此包是新包还是重发包。如果 PID 和上一包的 PID 相同则比对CRC,如果 CRC也相同,则判断为上一数据的重发并将数据丢弃。

4、负载数据
负载数据字段为发射数据内容,可以最长 32 字节。
5、CRC(循环冗余性校验)
CRC 字段为包的 CRC值,CRC支持 8bit和 16bit 两种,CRC 的长度通过 CONFIG寄存器中的 CRCO 位配置。
三、STM32CubeMX
以飞控板为例
①SPI1模式为全双工模式
②prescaler分频器为8
③PA4和PA8均为GPIO_Output
④PA4默认为高电平,用户标签为SPI1_NSS
⑤PA8默认为低电平,用户标签为SI_EN
四、Keil
①在interface下创建int_SI24R1.c/.h文件
②添加.c/.h文件
五、VSCode
①int_SI24R1.h
#ifndef __nRF24L01P__
#define __nRF24L01P__
#include "spi.h"
#include "Com_debug.h"
// (1) STM32开发板使用SI24R1需要先到CUBEMX中配置SPI
// 拉低片选
#define CS_LOW HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, GPIO_PIN_RESET);
// 拉高片选
#define CS_HIGH HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, GPIO_PIN_SET);
// 拉低使能
#define CE_LOW HAL_GPIO_WritePin(SI_EN_GPIO_Port, SI_EN_Pin, GPIO_PIN_RESET);
// 拉高使能
#define CE_HIGH HAL_GPIO_WritePin(SI_EN_GPIO_Port, SI_EN_Pin, GPIO_PIN_SET);
// 选择使用的射频通道
#define CHANNEL 40
#define TX_ADR_WIDTH 5 // 5字节宽度的发送/接收地址
#define TX_PLOAD_WIDTH 32 // 数据通道有效数据宽度
//********************************************************************************************************************//
// SPI(SI24R1) commands
#define SI24R1_READ_REG 0x00 // Define read command to register
#define SI24R1_WRITE_REG 0x20 // Define write command to register
#define RD_RX_PLOAD 0x61 // Define RX payload register address
#define WR_TX_PLOAD 0xA0 // Define TX payload register address
#define FLUSH_TX 0xE1 // Define flush TX register command
#define FLUSH_RX 0xE2 // Define flush RX register command
#define REUSE_TX_PL 0xE3 // Define reuse TX payload register command
#define NOP 0xFF // Define No Operation, might be used to read status register
//********************************************************************************************************************//
// SPI(SI24R1) registers(addresses)
#define CONFIG 0x00 // 'Config' register address
#define EN_AA 0x01 // 'Enable Auto Acknowledgment' register address
#define EN_RXADDR 0x02 // 'Enabled RX addresses' register address
#define SETUP_AW 0x03 // 'Setup address width' register address
#define SETUP_RETR 0x04 // 'Setup Auto. Retrans' register address
#define RF_CH 0x05 // 'RF channel' register address
#define RF_SETUP 0x06 // 'RF setup' register address
#define STATUS 0x07 // 'Status' register address
#define OBSERVE_TX 0x08 // 'Observe TX' register address
#define RSSI 0x09 // 'Received Signal Strength Indecator' register address
#define RX_ADDR_P0 0x0A // 'RX address pipe0' register address
#define RX_ADDR_P1 0x0B // 'RX address pipe1' register address
#define RX_ADDR_P2 0x0C // 'RX address pipe2' register address
#define RX_ADDR_P3 0x0D // 'RX address pipe3' register address
#define RX_ADDR_P4 0x0E // 'RX address pipe4' register address
#define RX_ADDR_P5 0x0F // 'RX address pipe5' register address
#define TX_ADDR 0x10 // 'TX address' register address
#define RX_PW_P0 0x11 // 'RX payload width, pipe0' register address
#define RX_PW_P1 0x12 // 'RX payload width, pipe1' register address
#define RX_PW_P2 0x13 // 'RX payload width, pipe2' register address
#define RX_PW_P3 0x14 // 'RX payload width, pipe3' register address
#define RX_PW_P4 0x15 // 'RX payload width, pipe4' register address
#define RX_PW_P5 0x16 // 'RX payload width, pipe5' register address
#define FIFO_STATUS 0x17 // 'FIFO Status Register' register address
//********************************************************************************************************************//
// STATUS Register
#define RX_DR 0x40 /**/
#define TX_DS 0x20
#define MAX_RT 0x10
//********************************************************************************************************************//
// FUNCTION's PROTOTYPES //
//********************************************************************************************************************//
// SI24R1 API Functions
/********************************************************
函数功能:写寄存器的值(单字节)
入口参数:reg:寄存器映射地址(格式:SI24R1_WRITE_REG|reg)
value:寄存器的值
返回 值:状态寄存器的值
*********************************************************/
uint8_t Int_SI24R1_Write_Reg(uint8_t reg, uint8_t value);
/********************************************************
函数功能:写寄存器的值(多字节)
入口参数:reg:寄存器映射地址(格式:SI24R1_WRITE_REG|reg)
pBuf:写数据首地址
bytes:写数据字节数
返回 值:状态寄存器的值
*********************************************************/
uint8_t Int_SI24R1_Write_Buf(uint8_t reg, const uint8_t *pBuf, uint8_t size);
/********************************************************
函数功能:读取寄存器的值(单字节)
入口参数:reg:寄存器映射地址(格式:SI24R1_READ_REG|reg)
返回 值:寄存器值
*********************************************************/
uint8_t Int_SI24R1_Read_Reg(uint8_t reg);
/********************************************************
函数功能:读取寄存器的值(多字节)
入口参数:reg:寄存器映射地址(SI24R1_READ_REG|reg)
pBuf:接收缓冲区的首地址
bytes:读取字节数
返回 值:状态寄存器的值
*********************************************************/
uint8_t Int_SI24R1_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t size);
/********************************************************
函数功能:SI24R1接收模式初始化
入口参数:无
返回 值:无
*********************************************************/
void Int_SI24R1_RX_Mode(void);
/********************************************************
函数功能:SI24R1发送模式初始化
入口参数:无
返回 值:无
*********************************************************/
void Int_SI24R1_TX_Mode(void);
/********************************************************
函数功能:读取接收数据 硬件直接接收数据保存到 FIFO队列中 => 通过状态标志位判断队列中是否有数据
入口参数:rxbuf:接收数据存放首地址
返回 值:0:接收到数据
1:没有接收到数据
*********************************************************/
uint8_t Int_SI24R1_RxPacket(uint8_t *rxbuf);
/********************************************************
函数功能:发送一个数据包
入口参数:txbuf:要发送的数据
返回 值: 0: 发送成功 1: 发送失败
*********************************************************/
uint8_t Int_SI24R1_TxPacket(uint8_t *txbuf);
/**
* @brief 硬件接口层SI24R1的初始化
*
*/
void Int_SI24R1_Init(void); // SI24R1 Pin Init
//********************************************************************************************************************//
#endif
②int_SI24R1.c
#include "Int_SI24R1.h"
// 定义一个静态的发送地址 => 发送地址与接收地址相同
uint8_t TX_ADDRESS[TX_ADR_WIDTH] = {0x0A, 0x01, 0x06, 0x1E, 0x01}; // 定义一个静态发送地址
// SPI读写一个字节 => 写入的字节是传入的参数 读取的字节是返回值
static uint8_t SPI_RW(uint8_t byte)
{
uint8_t rx_data = 0;
HAL_SPI_TransmitReceive(&hspi1, &byte, &rx_data, 1, 1000);
return rx_data;
}
/********************************************************
函数功能:写寄存器的值(单字节)
入口参数:reg:寄存器映射地址(格式:SI24R1_WRITE_REG|reg)
value:寄存器的值
返回 值:状态寄存器的值
*********************************************************/
uint8_t Int_SI24R1_Write_Reg(uint8_t reg, uint8_t value)
{
uint8_t status;
CS_LOW;
status = SPI_RW(reg);
SPI_RW(value);
CS_HIGH;
return (status);
}
/********************************************************
函数功能:写寄存器的值(多字节)
入口参数:reg:寄存器映射地址(格式:SI24R1_WRITE_REG|reg)
pBuf:写数据首地址
bytes:写数据字节数
返回 值:状态寄存器的值
*********************************************************/
uint8_t Int_SI24R1_Write_Buf(uint8_t reg, const uint8_t *pBuf, uint8_t size)
{
uint8_t status, byte_ctr;
CS_LOW;
status = SPI_RW(reg);
for (byte_ctr = 0; byte_ctr < size; byte_ctr++)
{
SPI_RW(*pBuf++);
}
CS_HIGH;
return (status);
}
/********************************************************
函数功能:读取寄存器的值(单字节)
入口参数:reg:寄存器映射地址(格式:SI24R1_READ_REG|reg)
返回 值:寄存器值
*********************************************************/
uint8_t Int_SI24R1_Read_Reg(uint8_t reg)
{
uint8_t value;
CS_LOW;
SPI_RW(reg);
value = SPI_RW(0);
CS_HIGH;
return (value);
}
/********************************************************
函数功能:读取寄存器的值(多字节)
入口参数:reg:寄存器映射地址(SI24R1_READ_REG|reg)
pBuf:接收缓冲区的首地址
bytes:读取字节数
返回 值:状态寄存器的值
*********************************************************/
uint8_t Int_SI24R1_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t size)
{
uint8_t status, byte_ctr;
CS_LOW;
status = SPI_RW(reg);
for (byte_ctr = 0; byte_ctr < size; byte_ctr++)
{
pBuf[byte_ctr] = SPI_RW(0); // 读取数据,低字节在前
}
CS_HIGH;
return (status);
}
/********************************************************
函数功能:SI24R1接收模式初始化
入口参数:无
返回 值:无
*********************************************************/
void Int_SI24R1_RX_Mode(void)
{
CE_LOW;
Int_SI24R1_Write_Buf(SI24R1_WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // 接收设备接收通道0使用和发送设备相同的发送地址
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + EN_AA, 0x01); // 使能接收通道0自动应答
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + EN_RXADDR, 0x01); // 使能接收通道0
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + RF_CH, CHANNEL); // 选择射频通道40
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + RX_PW_P0, TX_PLOAD_WIDTH); // 接收通道0选择和发送通道相同有效数据宽度
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + RF_SETUP, 0x06); // 数据传输率1Mbps,发射功率4dBm
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + CONFIG, 0x0f); // CRC使能,16位CRC校验,上电,接收模式
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + STATUS, 0xff); // 清除所有的中断标志位
CE_HIGH; // 拉高CE启动接收设备
}
/********************************************************
函数功能:SI24R1发送模式初始化
入口参数:无
返回 值:无
*********************************************************/
void Int_SI24R1_TX_Mode(void)
{
CE_LOW;
Int_SI24R1_Write_Buf(SI24R1_WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH); // 写入发送地址
Int_SI24R1_Write_Buf(SI24R1_WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // 为了应答接收设备,接收通道0地址和发送地址相同
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + EN_AA, 0x01); // 使能接收通道0自动应答
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + EN_RXADDR, 0x01); // 使能接收通道0
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + SETUP_RETR, 0x0a); // 自动重发延时等待250us+86us,自动重发10次
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + RF_CH, CHANNEL); // 选择射频通道0x40
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + RF_SETUP, 0x06); // 数据传输率1Mbps,发射功率4dBm
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + CONFIG, 0x0e); // CRC使能,16位CRC校验,上电
CE_HIGH;
}
/********************************************************
函数功能:读取接收数据 硬件直接接收数据保存到 FIFO队列中 => 通过状态标志位判断队列中是否有数据
入口参数:rxbuf:接收数据存放首地址
返回 值:0:接收到数据
1:没有接收到数据
*********************************************************/
uint8_t Int_SI24R1_RxPacket(uint8_t *rxbuf)
{
uint8_t state;
// 将读取到的值 原封不动再写回状态寄存器 => 因为状态寄存器中的标志位设计为写1清除
state = Int_SI24R1_Read_Reg(STATUS); // 读取状态寄存器的值
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + STATUS, state); // 清除RX_DS中断标志
if (state & RX_DR) // 接收到数据
{
Int_SI24R1_Read_Buf(RD_RX_PLOAD, rxbuf, TX_PLOAD_WIDTH); // 读取数据
Int_SI24R1_Write_Reg(FLUSH_RX, 0xff); // 清除RX FIFO寄存器
return 0;
}
return 1; // 没收到任何数据
}
/********************************************************
函数功能:发送一个数据包
入口参数:txbuf:要发送的数据
返回 值: 0: 发送成功 1: 发送失败
*********************************************************/
uint8_t Int_SI24R1_TxPacket(uint8_t *txbuf)
{
uint8_t state;
CE_LOW; // CE拉低,使能SI24R1配置
Int_SI24R1_Write_Buf(WR_TX_PLOAD, txbuf, TX_PLOAD_WIDTH); // 写数据到TX FIFO,32个字节
CE_HIGH; // CE置高,使能发送
// 没有使用中断判断是否发送完成 => 使用轮询读取状态标志位
// while (IRQ == 1)
// ; // 等待发送完成
state = Int_SI24R1_Read_Reg(STATUS); // 读取状态寄存器的值
while (((state & TX_DS) == 0) && ((state & MAX_RT) == 0))
{
state = Int_SI24R1_Read_Reg(STATUS);
}
Int_SI24R1_Write_Reg(SI24R1_WRITE_REG + STATUS, state); // 清除TX_DS或MAX_RT中断标志
if (state & MAX_RT) // 达到最大重发次数
{
Int_SI24R1_Write_Reg(FLUSH_TX, 0xff); // 清除TX FIFO寄存器
return 1;
}
if (state & TX_DS) // 发送完成
{
return 0;
}
return 1; // 发送失败
}
uint8_t si24r1_rx_buff[5] = {0};
/**
* @brief SI24R1的初始化检测
*
* @return uint8_t 0:检测成功 1:检测失败
*/
uint8_t Int_SI24R1_Check(void)
{
// 1. 测试SPI通信能够正常读写寄存器
// 1.0 SI24R1芯片需要先读取一次 保证SPI正常之后再写入
Int_SI24R1_Read_Buf(SI24R1_READ_REG + TX_ADDR, si24r1_rx_buff, TX_ADR_WIDTH);
// 1.1 写入发送地址
Int_SI24R1_Write_Buf(SI24R1_WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH);
// 1.2 读取同样的数据
Int_SI24R1_Read_Buf(SI24R1_READ_REG + TX_ADDR, si24r1_rx_buff, TX_ADR_WIDTH);
for (uint8_t i = 0; i < TX_ADR_WIDTH; i++)
{
if (si24r1_rx_buff[i] != TX_ADDRESS[i])
{
return 1;
}
}
return 0;
}
/**
* @brief 硬件接口层SI24R1的初始化
*
*/
void Int_SI24R1_Init(void)
{
// 上电之后的芯片延迟 >100ms
HAL_Delay(200);
// 校验检测
while (Int_SI24R1_Check() == 1)
{
// 每两次检测间隔10ms
HAL_Delay(10);
}
// 设置默认的状态为接收模式 => 每次发送数据的时候 切换到发送状态
Int_SI24R1_RX_Mode();
debug_printf("SI24R1 Init Success!\r\n");
}
③App_freertos_task.h
#ifndef __APP_FREERTOS_TASK__
#define __APP_FREERTOS_TASK__
#include "FreeRTOS.h"
#include "task.h"
#include "Com_debug.h"
#include "Com_config.h"
#include "Int_IP5305T.h"
#include "Int_motor.h"
#include "Int_led.h"
#include "Int_SI24R1.h"
/**
* @brief 启动freeRTOS操作系统
*
*/
void App_freeRTOS_start(void);
#endif // __APP_FREERTOS_TASK__
④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;
// 电源管理任务
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();
}
}
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);
}
}
uint8_t com_data[TX_PLOAD_WIDTH] = {0};
void com_task(void *args)
{
// 获取当前的基准时间
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1)
{
// 1. 接收数据到缓冲区
uint8_t res = Int_SI24R1_RxPacket(com_data);
if (res == 0)
{
printf("%s\n", com_data);
}
// 6ms执行一次 接收数据的时间间隔应该等于发送数据的时间间隔
vTaskDelayUntil(&xLastWakeTime, COM_TASK_PERIOD);
}
}
更多推荐
所有评论(0)