PH47框架下BBDB飞控基础开发平台极简教程

1 硬件准备

1.1 一块WeAct 的Stm32F411核心板

1.2 2个USB-TTL模块。


1.3 Stm32开发所必须的如STlink烧写器。
1.4 必要的线材。

2 软件准备

2.1 Stm32开发所必备的Keil开发环境。
2.2 PH47框架代码,下载链接

2.3 CSS及BBDB 控制站工程,下载链接
2.4 一个串口调试工具软件。

3 硬件电路连接

3.1 Stm32F411核心板Usart1遥测串口、Usart6调试串口分别连接一个USB-TTL模块。

3.2 两个USB-TTL插入电脑,使用CSS打开遥测串口,使用串口调试工具打开调试串口,波特率均为115200。

3.3 Stm32F411核心板SWD调试口连接如Stlink的烧写器。

4 二次开发

4.1 用户代码位于DevStudio\Application\App_BBDB.cpp(.h)中。

4.2 框架回调功能函数:

 CAppBBDB.Init():PH7 框架完全启动后的的初始化函数。用户初始化代码可位于该函数中实现。

 CAppBBDB.FastThread_1000Hz():框架快速线程函数,调用频率在400---500hz之间波动。

 CAppBBDB.NormalThread_250Hz():框架普通线程函数,调用频率固定为250hz。

 CAppBBDB.SlowThread_50Hz():框架慢速线程函数,调用频率固定为50hz。

用户的算法及逻辑实现代码,根据更新频率,以及实际运行时间,合理置于上述三个函数中予以实现即可。相关高级功能使用,见PH47框架二次开发教程。

4.3 PH47框架全局函数、数据总线、参数系统、遥测通讯系统等等的使用示例,见CController_Demo示例类文件:\DevStudio\Algorithms\Controller_Demo.cpp(.h)。

cpp 复制代码
// Demo_12. 算法层创建自定义控制器类(3/7) - 通用控制器抽象类派生类 CController_Demo 实现 
#include "./Controller_Demo.h"

/*
	CController_Demo 类为 PH47 控制代码框架 BBDB 固件特意设计的二次开发 Demo 代码集合.
		本代码对以下二次开发功能进行了示例(使用 IDE 查找功能在整个项目范围内查找如 "Demo_12." 即可找到该示例的全部操作步骤)
	
		Demo_1. 二次开发代码中基本文件包含
		Demo_2. TRACE/gPrintf 调试信息输出语句的使用
		Demo_3. 全局时间函数的基本使用
		Demo_4. 获取代码运行时间
		Demo_5. PH47 代码框架启动完成标志
		Demo_6. 数据总线基本操作
		Demo_7. 机载参数基本操作
		Demo_8. 使用 FreqDiv 类进行循环频率控制
		Demo_9. 如何实现调试串口自定义控制命令( 位于 CAppBBDB.HandleConsoleCmd() 函数实现)
		Demo_10.下行发送 mavlink 控制命令及状态信息
		Demo_11. 解析及响应 GCS 上行控制命令
		Demo_12. 算法层创建自定义控制器类
		Demo_13. 对自定义控制器类内部运行实施控制
		Demo_14. 自定义总线数据项 ( 分别位于DevStudio\BoardConfig\Board_BBDB\DataBus_BBDB.h, BusItemDef4User.h 中)
		Demo_15. 自定义全局状态变量 OptiFlowTof_MTF01 (位于 DevStudio\BoardConfig\Board_BBDB\StateVarDef_BBDB.h 文件中)
		Demo_16. 自定义机载控制参数 P_TEST_USER_1 (位于 \DevStudio\BoardConfig\Board_BBDB\ParamDict_BBDB.h / .cpp 文件中)
		Demo_17. PostMessage 机制使用
		Demo_18. Pwm 输入输出控制

	在调试串口当中输入命令 debug; 即可对上述示例代码运行效果进行切换
*/

// Demo_1. 二次开发文件包含 - 在 .cpp 文件中对 UseGlobalObject.h 进行文件包含
#include "../Frame/UseGlobalObject.h"	

void CController_Demo::Init() 
{
	// Demo_2. TRACE()/gPrintf() 调试信息输出语句使用:
	//   1. TRACE 为使用邮件队列方式显示格式化字符串, 特点是具有线程安全性,且连续调用能够确保顺序正确.
	//      依赖于 PH47 框架的启动完成, 框架启动前不能正常工作. 
	//      显示时刻会晚于函数调用时刻(滞后不高于 10ms 级别)	
	//   2. gPrintf() 为使用中断方式显示格式化字符串, 不依赖于框架的启动完成
	//      连续密集调用时显示的先后顺序可能会发生改变
	
	TRACE("\r\ndemo> 1. TRACE at %.3fms", gGetMills_f());	
	gPrintf("\r\ndemo> 2. gPrintf at %.3fms", gGetMills_f());	

	// Demo_3. 全局时间函数的基本使用 
	//   1. HAL_Delay() 或 osDelay() 函数可用于 ms 级延时, 区别在于 osDelay() 依赖于 FreeRTOS 启动完成(可以简单理解为PH47代码框架启动完成), HAL_Delay 则不依赖
	//      osDelay() 在延时期间可以将系统资源轮换给其他线程使用, HAL_Delay()则不能
	//   2. gDelay_us() 可用于 us 级别延时, 延时时间不要超过 1000us
	//   3. gGetMills()   获取调用时刻距离 mcu 加电启动时刻的 ms 时间间隔, 获取数据范围为 4294967296ms,即 1193 hour
	//   4. gGetMills_f() 可以获取精确到小数点后3位的 ms 时间, 即可以精确到 us. 但是 gGetMills_f() 在运行时间超过4800s后会溢出重置为 0 后重新开始计时
	//   5. gGetMicros()  获取 mcu 加电以来的 us 时间, 注意: us 计时器溢出时间为 600s
	
	osDelay(1);
	TRACE("\r\ndemo> 3. TRACE at %dms %.3fms %dus", gGetMills(), gGetMills_f(), gGetMicros());

	// Demo_4. 获取代码运行时间 (Step1/2) - 记录代码段执行开始时间
	uint32_t uPrevUs = gGetMicros();
	// 代码执行...
	gDelay_us(200);
	// Demo_4. 获取代码运行时间 (Step2/2) - 获取代码段实际执行时间
	TRACE("\r\ndemo> 4. Time usage:%dus", gGetTmUsage_us(uPrevUs));

	// Demo_5. PH47 代码框架启动完成标志: 
	//   core.blSysInitCompleted 为 true 表示 PH47 代码框架初始化已经全部完成, 此后框架提供的各种功能, 机制均可正常使用
	TRACE("\r\ndemo> 5. PH47 init status:%d", core.blSysInitCompleted);		
	

	
	// Demo_8. FreqDiv 类进行循环频率控制(2/3) - 设定循环控制间隔为 1000ms, 即 1Hz
	_FreqDemo_1Hz.SetRunInv_ms(1000);
		
	//osDelay(2);
}

void CController_Demo::Reset()
{

}

void CController_Demo::Update(float fDt)
{
	// Demo_8. FreqDiv 类进行循环频率控制(2/3) 
	//  1Hz 循环控制代码实现, 此处注意,_FreqDemo_1Hz 对象的 IsSetRunInv_ms() 函数调用不能在同一循环的多个不同位置重复出现
	if(_FreqDemo_1Hz.IsSetRunInv_ms())
	{
		// Demo_13. 对自定义控制器类内部运行实施控制(2/3)
		if(_uUpdateType == DEMO_READ_AHRS)
			Demo_Read_Ahrs();
		else if(_uUpdateType == DEMO_READ_BARO)
			Demo_Read_Baro();
		else if(_uUpdateType == DEMO_MAVLINK_SEND)
			Demo_Mavlink_Send();
		else if(_uUpdateType == DEMO_GET_SET_PWM)
			Demo_Get_Set_Pwm();
	}

	// Demo_17. PostMessage 机制使用 (3/3) - 在循环中队是否有 postmessage 进行检查
	if(frm.CheckPostMsg(P_MSG_USER_DEF_DEMO))		// frm.CheckPostMsg() 必须位于一个持续循环中被调用
	{
		gShowInfo(true, true, MAV_SEVERITY_WARNING, "Get message P_MSG_USER_DEF_DEMO at %.3f", gGetMills_f());
	}
}

bool CController_Demo::Function(uint8_t uCallType, void *pData)
{
	// 控制器通用操作执行函数
	// uCallType 执行操作类型, pData 数据交换指针
	bool blRet = false;

	// Demo_13. 对自定义控制器类内部运行实施控制(2/3)
	_uUpdateType = uCallType;

	if(_uUpdateType == DEMO_READ_AHRS) 
		TRACE("\r\n\r\ndemo> Output ahrs data.");
	else if(_uUpdateType == DEMO_READ_BARO)
		TRACE("\r\n\r\ndemo> Output baro data.");
	else if(_uUpdateType == DEMO_WRITE)
		Demo_Write();
	else if(_uUpdateType == DEMO_MAVLINK_SEND)
		TRACE("\r\n\r\ndemo> Send string and short cmd frame to gcs.");
	else if(_uUpdateType == DEMO_GET_SET_PWM)
		TRACE("\r\n\r\ndemo> Get pwm in value and set pwm out.");
	
	return blRet;
}

void CController_Demo::Demo_Read_Ahrs()
{
	// Demo_7. 机载参数基本操作 - 读取参数
	TRACE("\r\n\r\n> %.3f - Firmware:%.0f Tele baud:%.0f", 
		gGetMills_f(), 
		core.para.Get(P_FIRMWARE_TYPE), 
		core.para.Get(P_TELE_USART_BAUD_RATE) );	// 遥测串口通讯速率

	// Demo_6. 数据总线基本操作 - 读取数据总线数值示例
	Vector3f vAcc = bus.sImu.AccRaw.Get();
	Vector3f vGyr = bus.sImu.GyrRaw.Get();
	TRACE("\r\n> Acc %.2f %.2f %.2f; Gyr %.2f %.2f %.2f", vAcc.x, vAcc.y, vAcc.z, vGyr.x, vGyr.y, vGyr.z);

	// Demo_6. 数据总线基本操作 - 获取总线数据 AccRaw 的时间戳
	TRACE("\r\n> AccRaw timeTag:%d.%dms dt:%dus", 
		bus.sImu.AccRaw.GetTimeStamp_ms(),			// 获取 AccRaw 以 ms 为单位时间戳的整数部分
		bus.sImu.AccRaw.GetTmStampDec_us(),			// 获取 AccRaw 以 ms 为单位时间戳的 3 位小数部分(即精确到 us)
		bus.sImu.AccRaw.GetDt2Prev_us());			// 获取 AccRaw 数据项最近两次被设置(Set)之间的时间间隔(us)

	/*
	   此处需要对前后两次调用 bus.sImu.AccRaw.GetTmStampDec_us() 计算得出的时间间隔与 bus.sImu.AccFilted.GetDt2Prev_us() 
	   得出的总线数据 AccRaw 被设置的时间间隔不一致问题进行解释:
	   bus.sImu. 数据是 idle 线程被设置,该线程循环频率大概是 1500-2000hz 之间波动
	   GetTimeStamp_ms及GetTmStampDec_us() 函数获取的总线数据最近一一次被设置的时间,由于上述代码被调用频率为 1hz,故计算出的时间间隔
	   大概在1000ms左右
	   GetDt2Prev_us() 函数获取的是总线数据最近两次被设置的时间间隔,所以该时间间隔为 idle 线程运行时间间隔
	*/

	TRACE("\r\n> Roll %.1f Pitch %.1f Yaw %.1f", 
		degrees(bus.sAhrs.Roll.Get()),				// degrees() 弧度 --> 角度
		degrees(bus.sAhrs.Pitch.Get()), 
		degrees(bus.sAhrs.Yaw.Get()));
}

void CController_Demo::Demo_Read_Baro()
{
	// Demo_6. 数据总线基本操作 - 读取数据总线数值示例
	BARO_RAW sBaroRaw = bus.sBaro.Raw.Get();
	BARO_EXT sBaroExt = bus.sBaro.Ext.Get();
	TRACE("\r\n> Press:%.1f Temp:%.1f Alt:%.2f", sBaroRaw.fAbsPress, sBaroRaw.fRawTemp, sBaroExt.altitude);
	TRACE("\r\n> arDbg[0]=%.1f", bus.arDbg_2[0].Get());

	// Demo_6. 数据总线基本操作 - 全局状态变量读取, gGetStatus() 等同于 core.GetStatus()
	TRACE("\r\n> Global status - ImuStill:%d RcAlive:%d", gGetStatus(S_IMU_STILL), core.GetStatus(S_RC_ALIVE));		
}

void CController_Demo::Demo_Write()
{
	// Demo_6. 数据总线基本操作 - 写入数据总线数值
	TRACE("\r\n\r\n>>> Write bus arDbg[0] to 2.234f, and set para to change pilot placement.");
	bus.arDbg_2[0].Set(2.234);

	// Demo_7. 机载参数基本操作 - 设置存储机载数据参数 P_PILOT_PLACEMENT(飞控板安装方向)
	// Set pilot placement to head forward and right side down)
	core.para.Set(P_PILOT_PLACEMENT, ROTATE_ROLL_90);				// 仅仅更新内存中的参数设置
	// core.para.Set(P_PILOT_PLACEMENT, ROTATE_ROLL_90, true);		// 更新内存中的参数设置, 并保存至存储器 
}

void CController_Demo::Demo_Mavlink_Send()
{
	// Demo_10.下行发送 mavlink 控制命令及状态信息 - 状态信息发送
	char szSend[32];
	gFormat(szSend, "String to GCS at %d", gGetMills());
	core.mavlink.AddMsg_StatusText(szSend, MAV_SEVERITY_INFO);
	// 上述代码实际效果与如下语句相同: 
	// gShowInfo(false, true, MAV_SEVERITY_INFO, "String to GCS at %d", gGetMills());

	// Demo_10.下行发送 mavlink 控制命令及状态信息 - 自定义短控制命令发送
	Vector3f vTmp = bus.sImu.MagRaw.Get();
	mavlink_user_def_data_t packet;
	packet.ID = 0xA5;
	packet.Para_16 = (int16_t) vTmp.x;
	packet.Para_32 = gGetMills();
	core.mavlink.AddMsg_UserDefData(&packet);
}

void CController_Demo::Demo_Get_Set_Pwm()
{
	// Demo_18. Pwm 输入输出控制
	
	int16_t uPwmIn_1 = drv._pPwm->GetPwmIn(PWM_IN_CH1);
	int16_t uPwmIn_2 = drv._pPwm->GetPwmIn(PWM_IN_CH2);
	int16_t uPwmIn_3 = drv._pPwm->GetPwmIn(PWM_IN_CH3);
	int16_t uPwmIn_4 = drv._pPwm->GetPwmIn(PWM_IN_CH4);

	TRACE("\r\n> Pmw in: %d %d %d %d", uPwmIn_1, uPwmIn_2, uPwmIn_3, uPwmIn_4);

	drv._pPwm->SetPwmWidth(PWM_IN_CH1, constrain_int16(uPwmIn_1, 1000, 2000)); 
	drv._pPwm->SetPwmWidth(PWM_IN_CH2, constrain_int16(uPwmIn_2, 1000, 2000));
	drv._pPwm->SetPwmWidth(PWM_IN_CH3, constrain_int16(uPwmIn_3, 1000, 2000));
	drv._pPwm->SetPwmWidth(PWM_IN_CH4, constrain_int16(uPwmIn_4, 1000, 2000));
}

4.4 编译、连接、烧写。

使用 css 打开遥测串口。若电路连接及配置正确,则 css 显示飞控工作时间及接收帧计数等相关信息。同时,调试串口输出相关信息。

由于当前硬件配置为最小系统配置,未连接任何传感器,故相关数据尾0或无效。但 ph47框架所有功能均已齐备,可以开展一定范围内的二次开发工作。

5 完全功能的二次开发

5.1 准备一块BBP v2或BBP FEI或BBP mini飞控板,然后重复步骤2---4。

更多内容见CSDN博客专栏:无人机飞控
相关资源:https://gitee.com/ss15/ph47

相关推荐
yufengxinpian21 分钟前
集成了高性能ARM Cortex-M0+处理器的一款SimpleLink 2.4 GHz无线模块-RF-BM-2340B1
单片机·嵌入式硬件·音视频·智能硬件
__基本操作__1 小时前
历遍单片机下的IIC设备[ESP--0]
单片机·嵌入式硬件
网易独家音乐人Mike Zhou8 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
PegasusYu10 小时前
STM32CUBEIDE FreeRTOS操作教程(九):eventgroup事件标志组
stm32·教程·rtos·stm32cubeide·free-rtos·eventgroup·时间标志组
lantiandianzi15 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
文弱书生65615 小时前
输出比较简介
stm32
哔哥哔特商务网15 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式15 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
LittroInno15 小时前
无人机侦察打击方案(3)
人工智能·无人机
云卓SKYDROID16 小时前
无人机的激光雷达避障系统阐述!
科技·安全·无人机·云卓科技·激光雷达避障系统