本节主要分析ardupilot3.6版本的log固件代码,欢迎批评指正!!!
DataFlash日志存储在飞行控制器的DataFlash内存中,可以在飞行后下载。这些日志提供了关于每次飞行的详细信息,这可能非常重要,尤其是在试图诊断出故障原因时
这个页面解释了如何编写一个新的dataflash日志消息。
用:ardupilot/libraries/DataFlash/DataFlash.h 的DataFlash_Class:instance()->Log_Write函数写消息
void Log_Write(const char *name, const char *labels, const char *fmt, ...);
void Log_Write(const char *name, const char *labels, const char *units, const char *mults, const char *fmt, ...);
在Compass_learn.cpp中可以找到正在使用的顶层函数的一个例子:
if (sample_available)
{
DataFlash_Class::instance()->Log_Write("COFS", "TimeUS,OfsX,OfsY,OfsZ,Var,Yaw,WVar,N", "QffffffI",
AP_HAL::micros64(),
(double)best_offsets.x,
(double)best_offsets.y,
(double)best_offsets.z,
(double)best_error,
(double)best_yaw_deg,
(double)worst_error,
num_samples);
}
我们分析下:"QffffffI"是什么意思?Q:uint64_t,可以看出AP_HAL::micros64()就是这种类型:uint64_t micros64(),因此Q表示一种类型的简写;f:float表示浮点类型,(double)best_offsets.x,(double)best_offsets.y, (double)best_offsets.z,(double)best_error, (double)best_yaw_deg,
(double)worst_error,num_samples)本来都是浮点类型,到这里知道什么意思了吧。
/*
二进制日志消息格式字符串中的格式字符:Format characters in the format string for binary log messages
a : int16_t[32]
b : int8_t
B : uint8_t
h : int16_t
H : uint16_t
i : int32_t
I : uint32_t
f : float
d : double
n : char[4]
N : char[16]
Z : char[64]
c : int16_t * 100
C : uint16_t * 100
e : int32_t * 100
E : uint32_t * 100
L : int32_t latitude/longitude
M : uint8_t flight mode
q : int64_t
Q : uint64_t
*/
AP_HAL::micros64(), //第二个参数是一个逗号分隔的字段名列表
(double)best_offsets.x, //第三个参数是一个格式字符串,每个字母表示对应字段的格式。
(double)best_offsets.y,
(double)best_offsets.z,
(double)best_error,
(double)best_yaw_deg,
(double)worst_error,
num_samples);
第二个Log_Write()函数与第一个函数相同,只是它接受另外两个字符串参数“units”和“mults”。 与“format”参数类似,这些参数中的每个字符指定以下字段的单位(即“d”代表度数)或乘数(即“2”代表* 100,“B”代表* 0.01)。 这些帮助图形工具在向用户显示时正确缩放输出
例如,下面是一个“测试”日志消息,它输出当前的系统时间和高度。
DataFlash_Class::instance()->Log_Write("TEST", "TimeUS,Alt",
"sm", // units: seconds, meters,这个是单位s和米
"FB", // mult: 1e-6, 1e-2,表示的数量级
"Qf", // format: uint64_t, float ,数据类型,和上面的讲解一样
AP_HAL::micros64(), //显示的时间单位
(double)alt_in_cm); //显示的高度信息
上面的要是不明白,可以具体看下面的表示
//这里所有的单元都应该是基本单元。这意味着电池容量在这里是“amp*.”。请保持名称与Tools/autotest/param_metadata/param.py:33一致。
const struct UnitStructure log_Units[] = {
{ '-', "" }, // no units e.g. Pi, or a string
{ '?', "UNKNOWN" }, // Units which haven't been worked out yet....
{ 'A', "A" }, // Ampere
{ 'd', "deg" }, // of the angular variety, -180 to 180
{ 'b', "B" }, // bytes
{ 'k', "deg/s" }, // degrees per second. Degrees are NOT SI, but is some situations more user-friendly than radians
{ 'D', "deglatitude" }, // degrees of latitude
{ 'e', "deg/s/s" }, // degrees per second per second. Degrees are NOT SI, but is some situations more user-friendly than radians
{ 'E', "rad/s" }, // radians per second
{ 'G', "Gauss" }, // Gauss is not an SI unit, but 1 tesla = 10000 gauss so a simple replacement is not possible here
{ 'h', "degheading" }, // 0.? to 359.?
{ 'i', "A.s" }, // Ampere second
{ 'J', "W.s" }, // Joule (Watt second)
// { 'l', "l" }, // litres
{ 'L', "rad/s/s" }, // radians per second per second
{ 'm', "m" }, // metres
{ 'n', "m/s" }, // metres per second
// { 'N', "N" }, // Newton
{ 'o', "m/s/s" }, // metres per second per second
{ 'O', "degC" }, // degrees Celsius. Not SI, but Kelvin is too cumbersome for most users
{ '%', "%" }, // percent
{ 'S', "satellites" }, // number of satellites
{ 's', "s" }, // seconds
{ 'q', "rpm" }, // rounds per minute. Not SI, but sometimes more intuitive than Hertz
{ 'r', "rad" }, // radians
{ 'U', "deglongitude" }, // degrees of longitude
{ 'u', "ppm" }, // pulses per minute
{ 'U', "us" }, // pulse width modulation in microseconds
{ 'v', "V" }, // Volt
{ 'P', "Pa" }, // Pascal
{ 'w', "Ohm" }, // Ohm
{ 'z', "Hz" } // Hertz
};
// this multiplier information applies to the raw value present in the
// log. Any adjustment implied by the format field (e.g. the "centi"
// in "centidegrees" is *IGNORED* for the purposes of scaling.
// Essentially "format" simply tells you the C-type, and format-type h
// (int16_t) is equivalent to format-type c (int16_t*100)
// tl;dr a GCS shouldn't/mustn't infer any scaling from the unit name
const struct MultiplierStructure log_Multipliers[] = {
{ '-', 0 }, // no multiplier e.g. a string
{ '?', 1 }, // multipliers which haven't been worked out yet....
//
{ '2', 1e2 },
{ '1', 1e1 },
{ '0', 1e0 },
{ 'A', 1e-1 },
{ 'B', 1e-2 },
{ 'C', 1e-3 },
{ 'D', 1e-4 },
{ 'E', 1e-5 },
{ 'F', 1e-6 },
{ 'G', 1e-7 },
//
{ '!', 3.6 }, // (ampere*second => milliampere*hour) and (km/h => m/s)
{ '/', 3600 }, // (ampere*second => ampere*hour)
};
对于通常使用的消息,特别是以相对高的速率(50hz或更高)输出的消息,可以使用稍微更高效的日志记录方法。。
struct PACKED log_Test {
LOG_PACKET_HEADER;
uint64_t time_us; //时间
float a_value; //测量值
}
//下面这个是自己重新拷贝的,跟上面的一样
struct PACKED log_Control_Tuning {
LOG_PACKET_HEADER;
uint64_t time_us;
float throttle_in;
float angle_boost;
float throttle_out;
float throttle_hover;
float desired_alt;
float inav_alt;
int32_t baro_alt;
float desired_rangefinder_alt;
int16_t rangefinder_alt;
float terr_alt;
int16_t target_climb_rate;
int16_t climb_rate;
};
//写一个控制曲线包----- Write a control tuning packet
void Copter::Log_Write_Control_Tuning()
{
// get terrain altitude
float terr_alt = 0.0f;
#if AP_TERRAIN_AVAILABLE && AC_TERRAIN
if (!terrain.height_above_terrain(terr_alt, true)) {
terr_alt = DataFlash.quiet_nan();
}
#endif
float _target_rangefinder_alt;
if (target_rangefinder_alt_used) {
_target_rangefinder_alt = target_rangefinder_alt * 0.01f; // cm->m
} else {
_target_rangefinder_alt = DataFlash.quiet_nan();
}
struct log_Control_Tuning pkt = {
LOG_PACKET_HEADER_INIT(LOG_CONTROL_TUNING_MSG),
time_us : AP_HAL::micros64(),
throttle_in : attitude_control->get_throttle_in(),
angle_boost : attitude_control->angle_boost(),
throttle_out : motors->get_throttle(),
throttle_hover : motors->get_throttle_hover(),
desired_alt : pos_control->get_alt_target() / 100.0f,
inav_alt : inertial_nav.get_altitude() / 100.0f,
baro_alt : baro_alt,
desired_rangefinder_alt : _target_rangefinder_alt,
rangefinder_alt : rangefinder_state.alt_cm,
terr_alt : terr_alt,
target_climb_rate : (int16_t)pos_control->get_vel_target_z(),
climb_rate : climb_rate
};
DataFlash.WriteBlock(&pkt, sizeof(pkt));
}
void Copter::Log_Write_Control_Tuning() {}
void Copter::Log_Write_Performance() {}
void Copter::Log_Write_Attitude(void) {}
void Copter::Log_Write_EKF_POS() {}
void Copter::Log_Write_MotBatt() {}
void Copter::Log_Write_Event(uint8_t id) {}
void Copter::Log_Write_Data(uint8_t id, int32_t value) {}
void Copter::Log_Write_Data(uint8_t id, uint32_t value) {}
void Copter::Log_Write_Data(uint8_t id, int16_t value) {}
void Copter::Log_Write_Data(uint8_t id, uint16_t value) {}
void Copter::Log_Write_Data(uint8_t id, float value) {}
void Copter::Log_Write_Error(uint8_t sub_system, uint8_t error_code) {}
void Copter::Log_Write_Parameter_Tuning(uint8_t param, float tuning_val, int16_t control_in, int16_t tune_low, int16_t tune_high) {}
void Copter::Log_Sensor_Health() {}
void Copter::Log_Write_Precland() {}
void Copter::Log_Write_GuidedTarget(uint8_t target_type, const Vector3f& pos_target, const Vector3f& vel_target) {}
void Copter::Log_Write_Vehicle_Startup_Messages() {}
//添加自己的测试代码
void Copter::Log_Write_Test()
//添加自己的测试代码
void Copter::Log_Write_Test()
{
struct log_Test pkt =
{
LOG_PACKET_HEADER_INIT(LOG_TEST_MSG),
time_us : AP_HAL::micros64(),
a_value : 1234
};
DataFlash.WriteBlock(&pkt, sizeof(pkt));
}
**ardupilot的数据闪存日志记录在Mincard上,最后保存格式为.bin文件
自动驾驶仪需要的另一种存储方式就是飞行日志存储,ardupilot基于DataFlash library库来实现。这个库的名字看上去有些怪怪的,实际上这个库最开始是为APM1的DataFlash芯片设计的,它原本是一个硬件驱动库,后来慢慢演变为一个通用日志系统,这个可以在代码中找到蛛丝马迹(这些都是以前的痕迹,不是最好的代码实现方式)。
现在,DataFlash API主要用于实现日志存储模型设计。它允许您自定义日志消息的数据结构。例如GPS消息,用于记录GPS传感器的日志数据。它能够非常有效存储这些数据,它同时也对其他库提供相应的APIs,用来进行日志回传、下载。
如果您最近下载日志时已经在ArduPilot看到了“*.bin”文件,那么您会看到ArduPilot用于存储日志消息的格式。这个时“自我描述”,意思是地面站可以计算出日志文件中消息的格式,而不必有一些共同的方案。在每个日志文件的前端是一组FMT消息,它们具有众所周知的格式,并且描述按照消息的格式。
去看看库/DATAFLIS/Stask/DATAFLASHYTest/DATAFLASHASTest.CPP。您将在顶部看到一个小表,它定义了我们将要编写的日志消息,在本例中是一个“TEST”消息,其中包含4个无符号16位整数和两个带符号32位整数(这就是“HHHHii”的意思)。它还给出了这6个变量的名称(巧妙标记的V1到V4和L1和L2)。
在loop()函数中,你会看到这样一个相当奇怪的调用:
DataFlash是用来存储日志的。日志有固定的格式,固定的日志头和日志包头
DataFlash.get_log_boundaries(log_num, start, end);
DataFlash API隐藏了底层如何存储log文件的细节。另外,对于Pixhawk or Linux这样的支持 Posix IO的系统,日志文件是存储在microSD卡的“LOGS”目录中的。用户可以直接抽出SD卡,直接拷贝到电脑中。
在像APM2这样的板上并不是那么简单。APM2在数据流芯片上有4M字节的存储,可以通过SPI接口访问。接口本身是面向页面的,因此需要填充一个512字节(或者可能是528字节!)页,然后告诉芯片将该页复制到持久存储,同时填充下一页。在这个DataFlash上执行随机IO是不好的——它是为需要连续写入的代码设计的,这在日志记录时发生。与自动驾驶仪喜欢记录的数据量相比,4M字节的大小实际上不是很大,因此当数据填充时,我们还需要处理包装。
所有这些复杂性都隐藏在一个API后面,该API提出了“日志编号”的概念,这只是一组在自动驾驶仪的一次飞行中写入的字节。APM1和APM2上的DataFlash实现在每个页面的前端使用少量标记字节来表示正在写入哪个日志编号。这些日志号对应于当用户要求检索其日志时下载的日志号。
DataFlash是用来存储日志的。日志有固定的格式,固定的日志头和日志包头
// 用于定义日志格式的结构----structure used to define logging format
struct LogStructure {
uint8_t msg_type;
uint8_t msg_len;
const char name[5];
const char format[16];
const char labels[64];
};
//所有车辆类型常见的日志结构-----log structures common to all vehicle types
struct PACKED log_Format
{
LOG_PACKET_HEADER;
uint8_t type;
uint8_t length;
char name[4];
char format[16];
char labels[64];
};
#define LOG_PACKET_HEADER uint8_t head1, head2, msgid; //日志包头
#define LOG_PACKET_HEADER_INIT(id) head1 : HEAD_BYTE1, head2 : HEAD_BYTE2, msgid : id
#define LOG_PACKET_HEADER_LEN 3 // bytes required for LOG_PACKET_HEADER
// once the logging code is all converted we will remove these from
// this header
#define HEAD_BYTE1 0xA3 // Decimal 163
#define HEAD_BYTE2 0x95 // Decimal 149
libraries/DataFlash/examples/DataFlash_test/DataFlash_test.cpp
struct PACKED log_Test {
LOG_PACKET_HEADER;
uint16_t v1, v2, v3, v4;
int32_t l1, l2;
};
static const struct LogStructure log_structure[] = {
LOG_COMMON_STRUCTURES,
{ LOG_TEST_MSG, sizeof(log_Test),
"TEST", "HHHHii", "V1,V2,V3,V4,L1,L2" }
};
日志是以文件的形式存储到microSD card,可以直接拔出SD卡拷贝到PC
每个页首都有“日志文件的编号”和“日志文件的页号”。当用户下载日志时,非常有用
void DataFlashTest::setup(void)
{
dataflash.Init(log_structure, ARRAY_SIZE(log_structure));
dataflash.set_vehicle_armed(true);
hal.console->printf("Dataflash Log Test 1.0\n");
// Test
hal.scheduler->delay(20);
dataflash.ShowDeviceInfo(hal.console);
if (dataflash.NeedPrep()) {
hal.console->printf("Preparing dataflash...\n");
dataflash.Prep();
}
// We start to write some info (sequentialy) starting from page 1
// This is similar to what we will do...
dataflash.StartUnstartedLogging();
log_num = dataflash.find_last_log();
hal.console->printf("Using log number %u\n", log_num);
hal.console->printf("After testing perform erase before using DataFlash for logging!\n");
hal.console->printf("\n");
hal.console->printf("Writing to flash... wait...\n");
uint32_t total_micros = 0;
uint16_t i;
for (i = 0; i < NUM_PACKETS; i++) {
uint32_t start = AP_HAL::micros();
// note that we use g++ style initialisers to make larger
// structures easier to follow
struct log_Test pkt = {
LOG_PACKET_HEADER_INIT(LOG_TEST_MSG),
v1 : (uint16_t)(2000 + i),
v2 : (uint16_t)(2001 + i),
v3 : (uint16_t)(2002 + i),
v4 : (uint16_t)(2003 + i),
l1 : (int32_t)(i * 5000),
l2 : (int32_t)(i * 16268)
};
dataflash.WriteBlock(&pkt, sizeof(pkt));
total_micros += AP_HAL::micros() - start;
hal.scheduler->delay(20);
}
hal.console->printf("Average write time %.1f usec/byte\n",
(double)total_micros/((double)i*sizeof(struct log_Test)));
#if CONFIG_HAL_BOARD == HAL_BOARD_SITL || CONFIG_HAL_BOARD == HAL_BOARD_LINUX
dataflash.flush();
#endif
hal.scheduler->delay(100);
}
void DataFlashTest::loop(void)
{
uint16_t start, end;
hal.console->printf("Start read of log %u\n", log_num);
dataflash.get_log_boundaries(log_num, start, end);
dataflash.LogReadProcess(log_num, start, end,
FUNCTOR_BIND_MEMBER(&DataFlashTest::print_mode, void, AP_HAL::BetterStream *, uint8_t),//print_mode,
hal.console);
hal.console->printf("\nTest complete. Test will repeat in 20 seconds\n");
hal.scheduler->delay(20000);
}
用户需要设置page大小512 byte,当写满一页以后告诉芯片复制一页
日志本身是通过DataFlash_File.cpp写到SD卡,还提供了DataFlash_Empty.cpp的块设备读写接口,下面是应用铁电的读写实例
缓冲区buffer
static uint8_t buffer[2][DF_PAGE_SIZE];
void DataFlash_Flash::Init(const struct LogStructure *structure, uint8_t num_types)
{
DataFlash_Backend::Init(structure, num_types);
if (flash_fd == 0) {
flash_fd = open(MTD_LOG_FILE, O_RDWR, 0777);
if (flash_fd == -1) {
printf("DataFlash_Flash init failed\n");
}
}
df_PageSize = DF_PAGE_SIZE; //页大小
df_NumPages = DF_NUM_PAGES - 1; //页数量
}
读取flash数据到buffer
void DataFlash_Flash::PageToBuffer(unsigned char BufferNum, uint16_t PageAdr)
{
PageAdr -= 1;
uint16_t ofs = PageAdr * DF_PAGE_SIZE;
memset(buffer[BufferNum], 0, DF_PAGE_SIZE);
if (lseek(flash_fd, ofs, SEEK_SET) != ofs) {
printf("PageToBuffer lseek err.\n");
return;
}
if (read(flash_fd, buffer[BufferNum], DF_PAGE_SIZE) != DF_PAGE_SIZE)
{
printf("PageToBuffer read err.\n");
return;
}
}
记录需要写入flash的页地址和缓冲区编号
void DataFlash_Flash::BufferToPage (unsigned char BufferNum, uint16_t PageAdr, unsigned char wait)
{
PageAdr -= 1;
uint16_t ofs = PageAdr * DF_PAGE_SIZE;
if(flash_fd < 0) return;
if (lseek(flash_fd, ofs, SEEK_SET) != ofs) {
printf("BufferToPage lseek err.\n");
return;
}
if (::write(flash_fd, &buffer[BufferNum], DF_PAGE_SIZE) != DF_PAGE_SIZE)
{
printf("BufferToPage write err.\n");
return;
}
}
**数据写入缓冲区**
void DataFlash_Flash::BlockWrite(uint8_t BufferNum, uint16_t IntPageAdr,
const void *pHeader, uint8_t hdr_size,
const void *pBuffer, uint16_t size)
{
if (!_writes_enabled) {
return;
}
memset(&buffer[BufferNum][IntPageAdr], 0, size+hdr_size);
if (hdr_size) {
memcpy(&buffer[BufferNum][IntPageAdr],
pHeader,
hdr_size);
}
memcpy(&buffer[BufferNum][IntPageAdr+hdr_size],
pBuffer,
size);
}
**从缓冲区读取数据**
bool DataFlash_Flash::BlockRead(uint8_t BufferNum, uint16_t IntPageAdr, void *pBuffer, uint16_t size)
{
memset(pBuffer, 0, size);
memcpy(pBuffer, &buffer[BufferNum][IntPageAdr], size);
return true;
}
void Copter::setup()
{
//从参数表中加载默认参数----------Load the default values of variables listed in var_info[]s
AP_Param::setup_sketch_defaults();
//初始化储存的多旋翼布局-----------setup storage layout for copter
StorageManager::set_layout_copter();
//传感器初始化,注册
init_ardupilot();
//初始化整个主loop任务调度-------initialise the main loop scheduler
scheduler.init(&scheduler_tasks[0], ARRAY_SIZE(scheduler_tasks), MASK_LOG_PM);
}
需要注意的地方:
用于定义日志格式的结构
struct LogStructure {
uint8_t msg_type; //消息类型
uint8_t msg_len; //消息长度
const char *name; //消息名字
const char *format; //数据格式类型
const char *labels; //名字标签
const char *units; //单位
const char *multipliers; //数量级
};
const struct LogStructure Copter::log_structure[] =
{
LOG_COMMON_STRUCTURES,
#if AUTOTUNE_ENABLED == ENABLED
{ LOG_AUTOTUNE_MSG, sizeof(log_AutoTune),
"ATUN", "QBBfffffff", "TimeUS,Axis,TuneStep,Targ,Min,Max,RP,RD,SP,ddt", "s--ddd---o", "F--BBB---0" },
{ LOG_AUTOTUNEDETAILS_MSG, sizeof(log_AutoTuneDetails),
"ATDE", "Qff", "TimeUS,Angle,Rate", "sdk", "FBB" },
#endif
{ LOG_PARAMTUNE_MSG, sizeof(log_ParameterTuning),
"PTUN", "QBfHHH", "TimeUS,Param,TunVal,CtrlIn,TunLo,TunHi", "s-----", "F-----" },
#if OPTFLOW == ENABLED
{ LOG_OPTFLOW_MSG, sizeof(log_Optflow),
"OF", "QBffff", "TimeUS,Qual,flowX,flowY,bodyX,bodyY", "s-EEEE", "F-0000" },
#endif
{ LOG_CONTROL_TUNING_MSG, sizeof(log_Control_Tuning),
"CTUN", "Qffffffefcfhh", "TimeUS,ThI,ABst,ThO,ThH,DAlt,Alt,BAlt,DSAlt,SAlt,TAlt,DCRt,CRt", "s----mmmmmmnn", "F----00B0BBBB" },
//类型,长度,名字,格式,标签,单位,乘法
{ LOG_MOTBATT_MSG, sizeof(log_MotBatt),
"MOTB", "Qffff", "TimeUS,LiftMax,BatVolt,BatRes,ThLimit", "s-vw-", "F-00-" },
{ LOG_EVENT_MSG, sizeof(log_Event),
"EV", "QB", "TimeUS,Id", "s-", "F-" },
{ LOG_DATA_INT16_MSG, sizeof(log_Data_Int16t),
"D16", "QBh", "TimeUS,Id,Value", "s--", "F--" },
{ LOG_DATA_UINT16_MSG, sizeof(log_Data_UInt16t),
"DU16", "QBH", "TimeUS,Id,Value", "s--", "F--" },
{ LOG_DATA_INT32_MSG, sizeof(log_Data_Int32t),
"D32", "QBi", "TimeUS,Id,Value", "s--", "F--" },
{ LOG_DATA_UINT32_MSG, sizeof(log_Data_UInt32t),
"DU32", "QBI", "TimeUS,Id,Value", "s--", "F--" },
{ LOG_DATA_FLOAT_MSG, sizeof(log_Data_Float),
"DFLT", "QBf", "TimeUS,Id,Value", "s--", "F--" },
{ LOG_ERROR_MSG, sizeof(log_Error),
"ERR", "QBB", "TimeUS,Subsys,ECode", "s--", "F--" },
#if FRAME_CONFIG == HELI_FRAME
{ LOG_HELI_MSG, sizeof(log_Heli),
"HELI", "Qff", "TimeUS,DRRPM,ERRPM", "s--", "F--" },
#endif
#if PRECISION_LANDING == ENABLED
{ LOG_PRECLAND_MSG, sizeof(log_Precland),
"PL", "QBBfffffffIIB", "TimeUS,Heal,TAcq,pX,pY,vX,vY,mX,mY,mZ,LastMeasUS,EKFOutl,Est", "s--ddmmddms--","F--00BB00BC--" },
#endif
{ LOG_GUIDEDTARGET_MSG, sizeof(log_GuidedTarget),
"GUID", "QBffffff", "TimeUS,Type,pX,pY,pZ,vX,vY,vZ", "s-mmmnnn", "F-000000" },
{ LOG_CONTROL_TUNING1_MSG, sizeof(log_Control_Tuning1),
"CTUN1", "Qffffffefcfhh", "TimeUS1,ThI1,ABst1,ThO1,ThH1,DAlt1,Alt1,BAlt1,DSAlt1,SAlt1,TAlt1,DCRt1,CRt1", "s----mmmmmmnn", "F----00B0BBBA" },
};
void Copter::init_ardupilot()
{
.....................
#if LOGGING_ENABLED == ENABLED
log_init(); //闪存日志初始化
#endif
.....................
}
void Copter::log_init(void)
{
DataFlash.Init(log_structure, ARRAY_SIZE(log_structure)); //初始化Dataflash
}
void DataFlash_Class::Init(const struct LogStructure *structures, uint8_t num_types)
{
gcs().send_text(MAV_SEVERITY_INFO, "Preparing log system");
#if CONFIG_HAL_BOARD == HAL_BOARD_SITL
validate_structures(structures, num_types);
dump_structures(structures, num_types);
#endif
if (_next_backend == DATAFLASH_MAX_BACKENDS)
{
AP_HAL::panic("Too many backends");
return;
}
_num_types = num_types;
_structures = structures;
#if defined(HAL_BOARD_LOG_DIRECTORY)
#if HAL_OS_POSIX_IO || HAL_OS_FATFS_IO
if (_params.backend_types == DATAFLASH_BACKEND_FILE ||
_params.backend_types == DATAFLASH_BACKEND_BOTH) {
DFMessageWriter_DFLogStart *message_writer =
new DFMessageWriter_DFLogStart();
if (message_writer != nullptr) {
backends[_next_backend] = new DataFlash_File(*this,
message_writer,
HAL_BOARD_LOG_DIRECTORY);
}
if (backends[_next_backend] == nullptr) {
hal.console->printf("Unable to open DataFlash_File");
} else {
_next_backend++;
}
}
#elif CONFIG_HAL_BOARD == HAL_BOARD_F4LIGHT
if (_params.backend_types == DATAFLASH_BACKEND_FILE ||
_params.backend_types == DATAFLASH_BACKEND_BOTH) {
DFMessageWriter_DFLogStart *message_writer =
new DFMessageWriter_DFLogStart();
if (message_writer != nullptr) {
#if defined(BOARD_SDCARD_NAME) || defined(BOARD_DATAFLASH_FATFS)
backends[_next_backend] = new DataFlash_File(*this, message_writer, HAL_BOARD_LOG_DIRECTORY);
#else
backends[_next_backend] = new DataFlash_Revo(*this, message_writer); // restore dataflash logs
#endif
}
if (backends[_next_backend] == nullptr) {
printf("Unable to open DataFlash_Revo");
} else {
_next_backend++;
}
}
#endif
#endif // HAL_BOARD_LOG_DIRECTORY
#if DATAFLASH_MAVLINK_SUPPORT
if (_params.backend_types == DATAFLASH_BACKEND_MAVLINK ||
_params.backend_types == DATAFLASH_BACKEND_BOTH) {
if (_next_backend == DATAFLASH_MAX_BACKENDS) {
AP_HAL::panic("Too many backends");
return;
}
DFMessageWriter_DFLogStart *message_writer =
new DFMessageWriter_DFLogStart();
if (message_writer != nullptr) {
backends[_next_backend] = new DataFlash_MAVLink(*this,
message_writer);
}
if (backends[_next_backend] == nullptr) {
hal.console->printf("Unable to open DataFlash_MAVLink");
} else {
_next_backend++;
}
}
#endif
for (uint8_t i=0; i<_next_backend; i++)
{
backends[i]->Init();//后端初始化
}
Prep();
EnableWrites(true);
gcs().send_text(MAV_SEVERITY_INFO, "Prepared log system");
}
由于ardupilot默认记录log到SD卡,需要解锁才能进行,如果想不解锁就进行记录,需要修改参数设置LOG_DISARMED
SCHED_TASK(update_altitude, 10, 100), //更新高度信息
void Copter::update_altitude()
{
//读取气压高度值--------read in baro altitude
read_barometer();
//写高度信息到dataflash logs中---------write altitude info to dataflash logs
if (should_log(MASK_LOG_CTUN))
{
Log_Write_Control_Tuning();
}
}
void Copter::Log_Write_Control_Tuning()
{
//获取地形高度----get terrain altitude
float terr_alt = 0.0f;
#if AP_TERRAIN_AVAILABLE && AC_TERRAIN
if (!terrain.height_above_terrain(terr_alt, true))
{
terr_alt = DataFlash.quiet_nan();
}
#endif
float _target_rangefinder_alt;
if (target_rangefinder_alt_used)
{
_target_rangefinder_alt = target_rangefinder_alt * 0.01f; // cm->m
} else
{
_target_rangefinder_alt = DataFlash.quiet_nan();
}
struct log_Control_Tuning pkt =
{
LOG_PACKET_HEADER_INIT(LOG_CONTROL_TUNING_MSG), //LOG_CONTROL_TUNING_MSG=0x04
time_us : AP_HAL::micros64(), //运行时间
throttle_in : attitude_control->get_throttle_in(), //throttle_in为油门输入,0到1000的整数值。
angle_boost : attitude_control->angle_boost(), //用于倾斜补偿的油门增加量
throttle_out : motors->get_throttle(), //throttle_out油门输出,0到1000的float数值。
throttle_hover : motors->get_throttle_hover(), //评估油门值需要的油门滑动条范围,修改这个值可以修正摇杆的中立点
desired_alt : pos_control->get_alt_target() / 100.0f, //desired_alt目标高度, _pos_target.z的值除以100,单位为米。
inav_alt : inertial_nav.get_altitude() / 100.0f, //飞机目前高度,_relpos_cm.z的值除以100,单位为米。
baro_alt : baro_alt, // 气压计的高度,单位为厘米
desired_rangefinder_alt : (int16_t)target_rangefinder_alt, //目标测距仪高度
rangefinder_alt : rangefinder_state.alt_cm, //测距仪高度
terr_alt : terr_alt, //地形高度
target_climb_rate : (int16_t)pos_control->get_vel_target_z(), //目标爬升率
climb_rate : climb_rate //climb_rate为当前的爬升速率,此值在inertia.cpp中的read_inertial_altitude函数中更新,实际上就是读取的inertial_nav的_velocity_cm.z的值
};
DataFlash.WriteBlock(&pkt, sizeof(pkt)); //把包写进flash中
}
void Copter::setup()
{
//从参数表中加载默认参数----------Load the default values of variables listed in var_info[]s
AP_Param::setup_sketch_defaults();
//初始化储存的多旋翼布局-----------setup storage layout for copter
StorageManager::set_layout_copter();
//传感器初始化,注册
/******************需要修改的地方**************************/
/******************需要修改的地方**************************/
/******************需要修改的地方**************************/
init_ardupilot();
/******************需要修改的地方**************************/
/******************需要修改的地方**************************/
/******************需要修改的地方**************************/
//初始化整个主loop任务调度-------initialise the main loop scheduler
scheduler.init(&scheduler_tasks[0], ARRAY_SIZE(scheduler_tasks), MASK_LOG_PM);
}
#if LOGGING_ENABLED == ENABLED
log_init(); //闪存日志初始化,进入修改
#endif
void Copter::log_init(void)
{
DataFlash.Init(log_structure, ARRAY_SIZE(log_structure));
}
const struct LogStructure Copter::log_structure[] =
{
LOG_COMMON_STRUCTURES,
#if AUTOTUNE_ENABLED == ENABLED
{ LOG_AUTOTUNE_MSG, sizeof(log_AutoTune),
"ATUN", "QBBfffffff", "TimeUS,Axis,TuneStep,Targ,Min,Max,RP,RD,SP,ddt", "s--ddd---o", "F--BBB---0" },
{ LOG_AUTOTUNEDETAILS_MSG, sizeof(log_AutoTuneDetails),
"ATDE", "Qff", "TimeUS,Angle,Rate", "sdk", "FBB" },
#endif
{ LOG_PARAMTUNE_MSG, sizeof(log_ParameterTuning),
"PTUN", "QBfHHH", "TimeUS,Param,TunVal,CtrlIn,TunLo,TunHi", "s-----", "F-----" },
#if OPTFLOW == ENABLED
{ LOG_OPTFLOW_MSG, sizeof(log_Optflow),
"OF", "QBffff", "TimeUS,Qual,flowX,flowY,bodyX,bodyY", "s-EEEE", "F-0000" },
#endif
{ LOG_CONTROL_TUNING_MSG, sizeof(log_Control_Tuning),
"CTUN", "Qffffffefcfhh", "TimeUS,ThI,ABst,ThO,ThH,DAlt,Alt,BAlt,DSAlt,SAlt,TAlt,DCRt,CRt", "s----mmmmmmnn", "F----00B0BBBB" },
//类型,长度,名字,格式,标签,单位,乘法
{ LOG_MOTBATT_MSG, sizeof(log_MotBatt),
"MOTB", "Qffff", "TimeUS,LiftMax,BatVolt,BatRes,ThLimit", "s-vw-", "F-00-" },
{ LOG_EVENT_MSG, sizeof(log_Event),
"EV", "QB", "TimeUS,Id", "s-", "F-" },
{ LOG_DATA_INT16_MSG, sizeof(log_Data_Int16t),
"D16", "QBh", "TimeUS,Id,Value", "s--", "F--" },
{ LOG_DATA_UINT16_MSG, sizeof(log_Data_UInt16t),
"DU16", "QBH", "TimeUS,Id,Value", "s--", "F--" },
{ LOG_DATA_INT32_MSG, sizeof(log_Data_Int32t),
"D32", "QBi", "TimeUS,Id,Value", "s--", "F--" },
{ LOG_DATA_UINT32_MSG, sizeof(log_Data_UInt32t),
"DU32", "QBI", "TimeUS,Id,Value", "s--", "F--" },
{ LOG_DATA_FLOAT_MSG, sizeof(log_Data_Float),
"DFLT", "QBf", "TimeUS,Id,Value", "s--", "F--" },
{ LOG_ERROR_MSG, sizeof(log_Error),
"ERR", "QBB", "TimeUS,Subsys,ECode", "s--", "F--" },
#if FRAME_CONFIG == HELI_FRAME
{ LOG_HELI_MSG, sizeof(log_Heli),
"HELI", "Qff", "TimeUS,DRRPM,ERRPM", "s--", "F--" },
#endif
#if PRECISION_LANDING == ENABLED
{ LOG_PRECLAND_MSG, sizeof(log_Precland),
"PL", "QBBfffffffIIB", "TimeUS,Heal,TAcq,pX,pY,vX,vY,mX,mY,mZ,LastMeasUS,EKFOutl,Est", "s--ddmmddms--","F--00BB00BC--" },
#endif
{ LOG_GUIDEDTARGET_MSG, sizeof(log_GuidedTarget),
"GUID", "QBffffff", "TimeUS,Type,pX,pY,pZ,vX,vY,vZ", "s-mmmnnn", "F-000000" },
/******************需要修改的地方**************************/
/******************需要修改的地方**************************/
/******************需要修改的地方**************************/
{ LOG_CONTROL_CT1_MSG,sizeof(log_Control_CT1),
"CT1", "Q", "TimeUS", "s", "F" },//这里一定要注意书写方法,特别是CT1,你不能随便写这个长度,长度不能超过四个字母,我这里折腾很久,需要注意
};
添加log枚举变量的地方
enum LoggingParameters {
TYPE_AIRSTART_MSG,
TYPE_GROUNDSTART_MSG,
LOG_CONTROL_TUNING_MSG,
LOG_OPTFLOW_MSG,
LOG_EVENT_MSG,
LOG_ERROR_MSG,
LOG_DATA_INT16_MSG,
LOG_DATA_UINT16_MSG,
LOG_DATA_INT32_MSG,
LOG_DATA_UINT32_MSG,
LOG_DATA_FLOAT_MSG,
LOG_AUTOTUNE_MSG,
LOG_AUTOTUNEDETAILS_MSG,
LOG_MOTBATT_MSG,
LOG_PARAMTUNE_MSG,
LOG_HELI_MSG,
LOG_PRECLAND_MSG,
LOG_GUIDEDTARGET_MSG,
LOG_CONTROL_CT1_MSG,
};
定义结构体
struct PACKED log_Control_CT1 {
LOG_PACKET_HEADER;
uint64_t time_us;
};
SCHED_TASK(update_mylog, 10, 100), //更新测试参数
需要注意需要在Copter.h中声明update_mylog()这个函数
void ten_hz_logging_loop();
void twentyfive_hz_logging();
void three_hz_loop();
void one_hz_loop();
void update_GPS(void);
void init_simple_bearing();
void update_simple_mode(void);
void update_super_simple_bearing(bool force_update);
void read_AHRS(void);
void update_altitude();
void update_mylog();//声明函数
函数实现
void Copter::update_mylog()
{
if (should_log(MASK_LOG_CT1))
{
Log_Write_Control_CT1();
}
}
其中:需要注意
(1)这里要定义
#define MASK_LOG_CT1 (1<<20)
#define MASK_LOG_ANY 0x1FFFFF
(2) if (should_log(MASK_LOG_CT1))这里需要设置的参数特别重要,不然if函数是进不去的
void Log_Write_Control_Tuning();
void Log_Write_Control_CT1(); //添加参数
void Log_Write_Performance();
实现函数
void Copter::Log_Write_Control_CT1()
{
struct log_Control_CT1 pkt =
{
LOG_PACKET_HEADER_INIT(LOG_CONTROL_CT1_MSG),
time_us : AP_HAL::micros64()
};
DataFlash.WriteBlock(&pkt, sizeof(pkt));
}