From e5c7ae470640ea20fe372b8da8185abf72123dbe Mon Sep 17 00:00:00 2001 From: Freddie Chopin Date: Fri, 30 Nov 2012 11:04:35 +0100 Subject: [PATCH 01/26] Make cpuload correct and more efficient for all configurations of NuttX Currently cpuload assumes there are only 2 static threads - idle and init, this is true only for "basic" config of NuttX, as there can be 3 more static threads: paging, work0 and work1 - depending on config. In such cases cpuload would mistake one of them for init (which in fact is always last), giving incorrect results to "top" Signed-off-by: Freddie Chopin --- apps/systemlib/cpuload.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/systemlib/cpuload.c b/apps/systemlib/cpuload.c index 20b711fa66..e35bc56c5d 100644 --- a/apps/systemlib/cpuload.c +++ b/apps/systemlib/cpuload.c @@ -77,8 +77,6 @@ extern FAR _TCB *sched_gettcb(pid_t pid); void cpuload_initialize_once() { -// if (!system_load.initialized) -// { system_load.start_time = hrt_absolute_time(); int i; @@ -86,27 +84,29 @@ void cpuload_initialize_once() system_load.tasks[i].valid = false; } - system_load.total_count = 0; - uint64_t now = hrt_absolute_time(); - /* initialize idle thread statically */ - system_load.tasks[0].start_time = now; - system_load.tasks[0].total_runtime = 0; - system_load.tasks[0].curr_start_time = 0; - system_load.tasks[0].tcb = sched_gettcb(0); - system_load.tasks[0].valid = true; - system_load.total_count++; + int static_tasks_count = 2; // there are at least 2 threads that should be initialized statically - "idle" and "init" - /* initialize init thread statically */ - system_load.tasks[1].start_time = now; - system_load.tasks[1].total_runtime = 0; - system_load.tasks[1].curr_start_time = 0; - system_load.tasks[1].tcb = sched_gettcb(1); - system_load.tasks[1].valid = true; - /* count init thread */ - system_load.total_count++; - // } +#ifdef CONFIG_PAGING + static_tasks_count++; // include paging thread in initialization +#endif /* CONFIG_PAGING */ +#if CONFIG_SCHED_WORKQUEUE + static_tasks_count++; // include high priority work0 thread in initialization +#endif /* CONFIG_SCHED_WORKQUEUE */ +#if CONFIG_SCHED_LPWORK + static_tasks_count++; // include low priority work1 thread in initialization +#endif /* CONFIG_SCHED_WORKQUEUE */ + + // perform static initialization of "system" threads + for (system_load.total_count = 0; system_load.total_count < static_tasks_count; system_load.total_count++) + { + system_load.tasks[system_load.total_count].start_time = now; + system_load.tasks[system_load.total_count].total_runtime = 0; + system_load.tasks[system_load.total_count].curr_start_time = 0; + system_load.tasks[system_load.total_count].tcb = sched_gettcb(system_load.total_count); // it is assumed that these static threads have consecutive PIDs + system_load.tasks[system_load.total_count].valid = true; + } } void sched_note_start(FAR _TCB *tcb) From 5b93ab0372dd1208112156850908b87143a0c0dd Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 18 Mar 2013 22:36:47 +0100 Subject: [PATCH 02/26] Clean up and compact the output to fit inside a 80 column display. Bug fix: - running/sleeping count Plus: - added task state - show the idle task (to make the number of tasks match the reported number) - convert some calc to floating point where it doesn't hurt performance (for clarity) - accept 'q' (standard) and escape to exit the program --- apps/systemcmds/top/top.c | 267 +++++++++++++++++++++----------------- 1 file changed, 148 insertions(+), 119 deletions(-) diff --git a/apps/systemcmds/top/top.c b/apps/systemcmds/top/top.c index 59d2bc8f16..d49cf2f5b5 100644 --- a/apps/systemcmds/top/top.c +++ b/apps/systemcmds/top/top.c @@ -49,19 +49,46 @@ #include #include +#define CL "\033[K" // clear line + /** * Start the top application. */ -__EXPORT int top_main(int argc, char *argv[]); +__EXPORT int top_main(void); extern struct system_load_s system_load; -bool top_sigusr1_rcvd = false; - -int top_main(int argc, char *argv[]) +static const char * +tstate_name(const tstate_t s) { - int t; + switch (s) { + case TSTATE_TASK_INVALID: return "init"; + case TSTATE_TASK_PENDING: return "PEND"; + case TSTATE_TASK_READYTORUN: return "READY"; + case TSTATE_TASK_RUNNING: return "RUN"; + + case TSTATE_TASK_INACTIVE: return "inact"; + case TSTATE_WAIT_SEM: return "w:sem"; +#ifndef CONFIG_DISABLE_SIGNALS + case TSTATE_WAIT_SIG: return "w:sig"; +#endif +#ifndef CONFIG_DISABLE_MQUEUE + case TSTATE_WAIT_MQNOTEMPTY: return "w:mqe"; + case TSTATE_WAIT_MQNOTFULL: return "w:mqf"; +#endif +#ifdef CONFIG_PAGING + case TSTATE_WAIT_PAGEFILL: return "w:pgf"; +#endif + + default: + return "ERROR"; + } +} + +int +top_main(void) +{ uint64_t total_user_time = 0; int running_count = 0; @@ -73,7 +100,7 @@ int top_main(int argc, char *argv[]) uint64_t last_times[CONFIG_MAX_TASKS]; float curr_loads[CONFIG_MAX_TASKS]; - for (t = 0; t < CONFIG_MAX_TASKS; t++) + for (int t = 0; t < CONFIG_MAX_TASKS; t++) last_times[t] = 0; float interval_time_ms_inv = 0.f; @@ -81,16 +108,16 @@ int top_main(int argc, char *argv[]) /* Open console directly to grab CTRL-C signal */ int console = open("/dev/console", O_NONBLOCK | O_RDONLY | O_NOCTTY); - while (true) -// for (t = 0; t < 10; t++) - { - int i; + /* clear screen */ + printf("\033[2J"); - uint64_t curr_time_ms = (hrt_absolute_time() / 1000LLU); - unsigned int curr_time_s = curr_time_ms / 1000LLU; + for (;;) { + int i; + uint64_t curr_time_us; + uint64_t idle_time_us; - uint64_t idle_time_total_ms = (system_load.tasks[0].total_runtime / 1000LLU); - unsigned int idle_time_total_s = idle_time_total_ms / 1000LLU; + curr_time_us = hrt_absolute_time(); + idle_time_us = system_load.tasks[0].total_runtime; if (new_time > interval_start_time) interval_time_ms_inv = 1.f / ((float)((new_time - interval_start_time) / 1000)); @@ -100,7 +127,38 @@ int top_main(int argc, char *argv[]) total_user_time = 0; for (i = 0; i < CONFIG_MAX_TASKS; i++) { - uint64_t interval_runtime = (system_load.tasks[i].valid && last_times[i] > 0 && system_load.tasks[i].total_runtime > last_times[i]) ? (system_load.tasks[i].total_runtime - last_times[i]) / 1000 : 0; + uint64_t interval_runtime; + + if (system_load.tasks[i].valid) { + switch (system_load.tasks[i].tcb->task_state) { + case TSTATE_TASK_PENDING: + case TSTATE_TASK_READYTORUN: + case TSTATE_TASK_RUNNING: + running_count++; + break; + + case TSTATE_TASK_INVALID: + case TSTATE_TASK_INACTIVE: + case TSTATE_WAIT_SEM: +#ifndef CONFIG_DISABLE_SIGNALS + case TSTATE_WAIT_SIG: +#endif +#ifndef CONFIG_DISABLE_MQUEUE + case TSTATE_WAIT_MQNOTEMPTY: + case TSTATE_WAIT_MQNOTFULL: +#endif +#ifdef CONFIG_PAGING + case TSTATE_WAIT_PAGEFILL: +#endif + blocked_count++; + break; + } + } + + interval_runtime = (system_load.tasks[i].valid && last_times[i] > 0 && + system_load.tasks[i].total_runtime > last_times[i]) + ? (system_load.tasks[i].total_runtime - last_times[i]) / 1000 + : 0; last_times[i] = system_load.tasks[i].total_runtime; @@ -109,7 +167,6 @@ int top_main(int argc, char *argv[]) if (i > 0) total_user_time += interval_runtime; - } else curr_loads[i] = 0; } @@ -117,127 +174,99 @@ int top_main(int argc, char *argv[]) for (i = 0; i < CONFIG_MAX_TASKS; i++) { if (system_load.tasks[i].valid && (new_time > interval_start_time)) { if (system_load.tasks[i].tcb->pid == 0) { - float idle = curr_loads[0]; - float task_load = (float)(total_user_time) * interval_time_ms_inv; + float idle; + float task_load; + float sched_load; - if (task_load > (1.f - idle)) task_load = (1.f - idle); /* this can happen if one tasks total runtime was not computed correctly by the scheduler instrumentation TODO */ + idle = curr_loads[0]; + task_load = (float)(total_user_time) * interval_time_ms_inv; - float sched_load = 1.f - idle - task_load; + /* this can happen if one tasks total runtime was not computed + correctly by the scheduler instrumentation TODO */ + if (task_load > (1.f - idle)) + task_load = (1.f - idle); + + sched_load = 1.f - idle - task_load; /* print system information */ - printf("\033[H"); /* cursor home */ - printf("\033[KProcesses: %d total, %d running, %d sleeping\n", system_load.total_count, running_count, blocked_count); - printf("\033[KCPU usage: %d.%02d%% tasks, %d.%02d%% sched, %d.%02d%% idle\n", (int)(task_load * 100), (int)((task_load * 10000.0f) - (int)(task_load * 100.0f) * 100), (int)(sched_load * 100), (int)((sched_load * 10000.0f) - (int)(sched_load * 100.0f) * 100), (int)(idle * 100), (int)((idle * 10000.0f) - ((int)(idle * 100)) * 100)); - printf("\033[KUptime: %u.%03u s total, %d.%03d s idle\n\033[K\n", curr_time_s, (unsigned int)(curr_time_ms - curr_time_s * 1000LLU), idle_time_total_s, (int)(idle_time_total_ms - idle_time_total_s * 1000)); + printf("\033[H"); /* move cursor home and clear screen */ + printf(CL "Processes: %d total, %d running, %d sleeping\n", + system_load.total_count, + running_count, + blocked_count); + printf(CL "CPU usage: %.2f%% tasks, %.2f%% sched, %.2f%% idle\n", + (double)(task_load * 100.f), + (double)(sched_load * 100.f), + (double)(idle * 100.f)); + printf(CL "Uptime: %.3fs total, %.3fs idle\n\n", + (double)curr_time_us / 1000000.d, + (double)idle_time_us / 1000000.d); - /* 34 chars command name length (32 chars plus two spaces) */ - char header_spaces[CONFIG_TASK_NAME_SIZE + 1]; - memset(header_spaces, ' ', CONFIG_TASK_NAME_SIZE); - header_spaces[CONFIG_TASK_NAME_SIZE] = '\0'; + /* header for task list */ + printf(CL "%4s %*-s %8s %6s %11s %10s %-6s\n", + "PID", + CONFIG_TASK_NAME_SIZE, "COMMAND", + "CPU(ms)", + "CPU(%)", + "USED/STACK", + "PRIO(BASE)", #if CONFIG_RR_INTERVAL > 0 - printf("\033[KPID\tCOMMAND%s CPU TOTAL \t%%CPU CURR \tSTACK USE\tCURR (BASE) PRIO\tRR SLICE\n", header_spaces); + "TSLICE" #else - printf("\033[KPID\tCOMMAND%s CPU TOTAL \t%%CPU CURR \tSTACK USE\tCURR (BASE) PRIO\n", header_spaces); -#endif - - } else { - enum tstate_e task_state = (enum tstate_e)system_load.tasks[i].tcb->task_state; - - if (task_state == TSTATE_TASK_PENDING || - task_state == TSTATE_TASK_READYTORUN || - task_state == TSTATE_TASK_RUNNING) { - running_count++; - } - - if (task_state == TSTATE_TASK_INACTIVE || /* BLOCKED - Initialized but not yet activated */ - task_state == TSTATE_WAIT_SEM /* BLOCKED - Waiting for a semaphore */ -#ifndef CONFIG_DISABLE_SIGNALS - || task_state == TSTATE_WAIT_SIG /* BLOCKED - Waiting for a signal */ -#endif -#ifndef CONFIG_DISABLE_MQUEUE - || task_state == TSTATE_WAIT_MQNOTEMPTY /* BLOCKED - Waiting for a MQ to become not empty. */ - || task_state == TSTATE_WAIT_MQNOTFULL /* BLOCKED - Waiting for a MQ to become not full. */ -#endif -#ifdef CONFIG_PAGING - || task_state == TSTATE_WAIT_PAGEFILL /* BLOCKED - Waiting for page fill */ -#endif - ) { - blocked_count++; - } - - char spaces[CONFIG_TASK_NAME_SIZE + 2]; - - /* count name len */ - int namelen = 0; - - while (namelen < CONFIG_TASK_NAME_SIZE) { - if (system_load.tasks[i].tcb->name[namelen] == '\0') break; - - namelen++; - } - - int s = 0; - - for (s = 0; s < CONFIG_TASK_NAME_SIZE + 2 - namelen; s++) { - spaces[s] = ' '; - } - - spaces[s] = '\0'; - - char *runtime_spaces = " "; - - if ((system_load.tasks[i].total_runtime / 1000) < 99) { - runtime_spaces = ""; - } - - unsigned stack_size = (uintptr_t)system_load.tasks[i].tcb->adj_stack_ptr - - (uintptr_t)system_load.tasks[i].tcb->stack_alloc_ptr; - unsigned stack_free = 0; - uint8_t *stack_sweeper = (uint8_t *)system_load.tasks[i].tcb->stack_alloc_ptr; - - while (stack_free < stack_size) { - if (*stack_sweeper++ != 0xff) - break; - - stack_free++; - } - - printf("\033[K % 2d\t%s%s % 8lld ms%s \t % 2d.%03d \t % 4u / % 4u", - (int)system_load.tasks[i].tcb->pid, - system_load.tasks[i].tcb->name, - spaces, - (system_load.tasks[i].total_runtime / 1000), - runtime_spaces, - (int)(curr_loads[i] * 100), - (int)(curr_loads[i] * 100000.0f - (int)(curr_loads[i] * 1000.0f) * 100), - stack_size - stack_free, - stack_size); - /* Print scheduling info with RR time slice */ -#if CONFIG_RR_INTERVAL > 0 - printf("\t%d\t(%d)\t\t%d\n", (int)system_load.tasks[i].tcb->sched_priority, (int)system_load.tasks[i].tcb->base_priority, (int)system_load.tasks[i].tcb->timeslice); -#else - /* Print scheduling info without time slice*/ - printf("\t%d (%d)\n", (int)system_load.tasks[i].tcb->sched_priority, (int)system_load.tasks[i].tcb->base_priority); + "STATE" #endif + ); } + + unsigned stack_size = (uintptr_t)system_load.tasks[i].tcb->adj_stack_ptr - + (uintptr_t)system_load.tasks[i].tcb->stack_alloc_ptr; + unsigned stack_free = 0; + uint8_t *stack_sweeper = (uint8_t *)system_load.tasks[i].tcb->stack_alloc_ptr; + + while (stack_free < stack_size) { + if (*stack_sweeper++ != 0xff) + break; + + stack_free++; + } + + printf(CL "%4d %*-s %8lld %2d.%03d %5u/%5u %3u (%3u) ", + system_load.tasks[i].tcb->pid, + CONFIG_TASK_NAME_SIZE, system_load.tasks[i].tcb->name, + (system_load.tasks[i].total_runtime / 1000), + (int)(curr_loads[i] * 100), + (int)(curr_loads[i] * 100000.0f - (int)(curr_loads[i] * 1000.0f) * 100), + stack_size - stack_free, + stack_size, + system_load.tasks[i].tcb->sched_priority, + system_load.tasks[i].tcb->base_priority); + +#if CONFIG_RR_INTERVAL > 0 + /* print scheduling info with RR time slice */ + printf(" %6d\n", system_load.tasks[i].tcb->timeslice); +#else + // print task state instead + printf(" %-6s\n", tstate_name(system_load.tasks[i].tcb->task_state)); +#endif } } - printf("\033[K[ Hit Ctrl-C to quit. ]\n\033[J"); - fflush(stdout); - interval_start_time = new_time; - char c; - - /* Sleep 200 ms waiting for user input four times */ + /* Sleep 200 ms waiting for user input five times ~ 1s */ /* XXX use poll ... */ - for (int k = 0; k < 4; k++) { + for (int k = 0; k < 5; k++) { + char c; + if (read(console, &c, 1) == 1) { - if (c == 0x03 || c == 0x63) { - printf("Abort\n"); + switch (c) { + case 0x03: // ctrl-c + case 0x1b: // esc + case 'c': + case 'q': close(console); return OK; + /* not reached */ } } From 496127ca459c603e5de3f8bc83c6113bcf9cbead Mon Sep 17 00:00:00 2001 From: sergeil Date: Fri, 31 May 2013 11:44:20 +0200 Subject: [PATCH 03/26] mpu6000 driver support for setting rate --- src/drivers/mpu6000/mpu6000.cpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/drivers/mpu6000/mpu6000.cpp b/src/drivers/mpu6000/mpu6000.cpp index df1958186f..2208425369 100644 --- a/src/drivers/mpu6000/mpu6000.cpp +++ b/src/drivers/mpu6000/mpu6000.cpp @@ -1,6 +1,6 @@ /**************************************************************************** * - * Copyright (c) 2012, 2013 PX4 Development Team. All rights reserved. + * Copyright (C) 2012 PX4 Development Team. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -272,6 +272,11 @@ private: */ void _set_dlpf_filter(uint16_t frequency_hz); + /* + set sample rate (approximate) - 1kHz to 5Hz + */ + void _set_sample_rate(uint16_t desired_sample_rate_hz); + }; /** @@ -378,7 +383,8 @@ MPU6000::init() up_udelay(1000); // SAMPLE RATE - write_reg(MPUREG_SMPLRT_DIV, 0x04); // Sample rate = 200Hz Fsample= 1Khz/(4+1) = 200Hz + //write_reg(MPUREG_SMPLRT_DIV, 0x04); // Sample rate = 200Hz Fsample= 1Khz/(4+1) = 200Hz + _set_sample_rate(200); // default sample rate = 200Hz usleep(1000); // FS & DLPF FS=2000 deg/s, DLPF = 20Hz (low pass filter) @@ -493,6 +499,18 @@ MPU6000::probe() return -EIO; } +/* + set sample rate (approximate) - 1kHz to 5Hz, for both accel and gyro +*/ +void +MPU6000::_set_sample_rate(uint16_t desired_sample_rate_hz) +{ + uint8_t div = 1000 / desired_sample_rate_hz; + if(div>200) div=200; + if(div<1) div=1; + write_reg(MPUREG_SMPLRT_DIV, div-1); +} + /* set the DLPF filter frequency. This affects both accel and gyro. */ @@ -644,8 +662,8 @@ MPU6000::ioctl(struct file *filp, int cmd, unsigned long arg) case ACCELIOCSSAMPLERATE: case ACCELIOCGSAMPLERATE: - /* XXX not implemented */ - return -EINVAL; + _set_sample_rate(arg); + return OK; case ACCELIOCSLOWPASS: case ACCELIOCGLOWPASS: @@ -702,8 +720,8 @@ MPU6000::gyro_ioctl(struct file *filp, int cmd, unsigned long arg) case GYROIOCSSAMPLERATE: case GYROIOCGSAMPLERATE: - /* XXX not implemented */ - return -EINVAL; + _set_sample_rate(arg); + return OK; case GYROIOCSLOWPASS: case GYROIOCGLOWPASS: From 91e1680c1bb45bceb1d1dd675782111713f76a8a Mon Sep 17 00:00:00 2001 From: Lorenz Meier Date: Mon, 17 Jun 2013 17:13:34 +0200 Subject: [PATCH 04/26] fixed attitude estimator params --- .../attitude_estimator_ekf_params.c | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/modules/attitude_estimator_ekf/attitude_estimator_ekf_params.c b/src/modules/attitude_estimator_ekf/attitude_estimator_ekf_params.c index 7d3812abdb..52dac652be 100755 --- a/src/modules/attitude_estimator_ekf/attitude_estimator_ekf_params.c +++ b/src/modules/attitude_estimator_ekf/attitude_estimator_ekf_params.c @@ -44,42 +44,42 @@ /* Extended Kalman Filter covariances */ /* gyro process noise */ -PARAM_DEFINE_FLOAT(EKF_ATT_V2_Q0, 1e-4f); -PARAM_DEFINE_FLOAT(EKF_ATT_V2_Q1, 0.08f); -PARAM_DEFINE_FLOAT(EKF_ATT_V2_Q2, 0.009f); +PARAM_DEFINE_FLOAT(EKF_ATT_V3_Q0, 1e-4f); +PARAM_DEFINE_FLOAT(EKF_ATT_V3_Q1, 0.08f); +PARAM_DEFINE_FLOAT(EKF_ATT_V3_Q2, 0.009f); /* gyro offsets process noise */ -PARAM_DEFINE_FLOAT(EKF_ATT_V2_Q3, 0.005f); -PARAM_DEFINE_FLOAT(EKF_ATT_V2_Q4, 0.0f); +PARAM_DEFINE_FLOAT(EKF_ATT_V3_Q3, 0.005f); +PARAM_DEFINE_FLOAT(EKF_ATT_V3_Q4, 0.0f); /* gyro measurement noise */ -PARAM_DEFINE_FLOAT(EKF_ATT_V2_R0, 0.0008f); -PARAM_DEFINE_FLOAT(EKF_ATT_V2_R1, 0.8f); -PARAM_DEFINE_FLOAT(EKF_ATT_V2_R2, 1.0f); +PARAM_DEFINE_FLOAT(EKF_ATT_V3_R0, 0.0008f); +PARAM_DEFINE_FLOAT(EKF_ATT_V3_R1, 10000.0f); +PARAM_DEFINE_FLOAT(EKF_ATT_V3_R2, 1.0f); /* accelerometer measurement noise */ -PARAM_DEFINE_FLOAT(EKF_ATT_V2_R3, 0.0f); +PARAM_DEFINE_FLOAT(EKF_ATT_V3_R3, 0.0f); /* offsets in roll, pitch and yaw of sensor plane and body */ -PARAM_DEFINE_FLOAT(ATT_ROLL_OFFS, 0.0f); -PARAM_DEFINE_FLOAT(ATT_PITCH_OFFS, 0.0f); -PARAM_DEFINE_FLOAT(ATT_YAW_OFFS, 0.0f); +PARAM_DEFINE_FLOAT(ATT_ROLL_OFF3, 0.0f); +PARAM_DEFINE_FLOAT(ATT_PITCH_OFF3, 0.0f); +PARAM_DEFINE_FLOAT(ATT_YAW_OFF3, 0.0f); int parameters_init(struct attitude_estimator_ekf_param_handles *h) { /* PID parameters */ - h->q0 = param_find("EKF_ATT_V2_Q0"); - h->q1 = param_find("EKF_ATT_V2_Q1"); - h->q2 = param_find("EKF_ATT_V2_Q2"); - h->q3 = param_find("EKF_ATT_V2_Q3"); - h->q4 = param_find("EKF_ATT_V2_Q4"); + h->q0 = param_find("EKF_ATT_V3_Q0"); + h->q1 = param_find("EKF_ATT_V3_Q1"); + h->q2 = param_find("EKF_ATT_V3_Q2"); + h->q3 = param_find("EKF_ATT_V3_Q3"); + h->q4 = param_find("EKF_ATT_V3_Q4"); - h->r0 = param_find("EKF_ATT_V2_R0"); - h->r1 = param_find("EKF_ATT_V2_R1"); - h->r2 = param_find("EKF_ATT_V2_R2"); - h->r3 = param_find("EKF_ATT_V2_R3"); + h->r0 = param_find("EKF_ATT_V3_R0"); + h->r1 = param_find("EKF_ATT_V3_R1"); + h->r2 = param_find("EKF_ATT_V3_R2"); + h->r3 = param_find("EKF_ATT_V3_R3"); - h->roll_off = param_find("ATT_ROLL_OFFS"); - h->pitch_off = param_find("ATT_PITCH_OFFS"); - h->yaw_off = param_find("ATT_YAW_OFFS"); + h->roll_off = param_find("ATT_ROLL_OFF3"); + h->pitch_off = param_find("ATT_PITCH_OFF3"); + h->yaw_off = param_find("ATT_YAW_OFF3"); return OK; } From d3eb86d0ea000add6e2747fda58f77a88b05314c Mon Sep 17 00:00:00 2001 From: Anton Babushkin Date: Sat, 20 Apr 2013 09:14:26 +0400 Subject: [PATCH 05/26] Publish manual_sas_mode immediately, SAS modes switch order changed to more safe --- src/modules/commander/commander.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/modules/commander/commander.c b/src/modules/commander/commander.c index aab8f3e04d..bb8580328f 100644 --- a/src/modules/commander/commander.c +++ b/src/modules/commander/commander.c @@ -1389,6 +1389,7 @@ int commander_thread_main(int argc, char *argv[]) uint64_t start_time = hrt_absolute_time(); uint64_t failsave_ll_start_time = 0; + enum VEHICLE_MANUAL_SAS_MODE manual_sas_mode; bool state_changed = true; bool param_init_forced = true; @@ -1828,8 +1829,9 @@ int commander_thread_main(int argc, char *argv[]) } else if (sp_man.manual_sas_switch < -STICK_ON_OFF_LIMIT) { - /* bottom stick position, set altitude hold */ - current_status.manual_sas_mode = VEHICLE_MANUAL_SAS_MODE_ALTITUDE; + /* bottom stick position, set default */ + /* this MUST be mapped to extremal position to switch easy in case of emergency */ + current_status.manual_sas_mode = VEHICLE_MANUAL_SAS_MODE_ROLL_PITCH_ABS_YAW_ABS; } else if (sp_man.manual_sas_switch > STICK_ON_OFF_LIMIT) { @@ -1837,8 +1839,14 @@ int commander_thread_main(int argc, char *argv[]) current_status.manual_sas_mode = VEHICLE_MANUAL_SAS_MODE_SIMPLE; } else { - /* center stick position, set default */ - current_status.manual_sas_mode = VEHICLE_MANUAL_SAS_MODE_ROLL_PITCH_ABS_YAW_ABS; + /* center stick position, set altitude hold */ + current_status.manual_sas_mode = VEHICLE_MANUAL_SAS_MODE_ALTITUDE; + } + + if (current_status.manual_sas_mode != manual_sas_mode) { + /* publish SAS mode changes immediately */ + manual_sas_mode = current_status.manual_sas_mode; + state_changed = true; } /* From 2f1de6621b34f76ddf3a0ff00ac8e9fcb8e60bea Mon Sep 17 00:00:00 2001 From: Anton Babushkin Date: Sat, 29 Jun 2013 20:49:54 +0400 Subject: [PATCH 06/26] More strict conditions for arm/disarm --- src/modules/commander/commander.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/modules/commander/commander.c b/src/modules/commander/commander.c index bb8580328f..bb3aac0ff1 100644 --- a/src/modules/commander/commander.c +++ b/src/modules/commander/commander.c @@ -1857,8 +1857,10 @@ int commander_thread_main(int argc, char *argv[]) (current_status.system_type == VEHICLE_TYPE_HEXAROTOR) || (current_status.system_type == VEHICLE_TYPE_OCTOROTOR) ) && - ((sp_man.yaw < -STICK_ON_OFF_LIMIT)) && - (sp_man.throttle < STICK_THRUST_RANGE * 0.2f)) { + current_status.flag_control_manual_enabled && + current_status.manual_sas_mode == VEHICLE_MANUAL_SAS_MODE_ROLL_PITCH_ABS_YAW_ABS && + sp_man.yaw < -STICK_ON_OFF_LIMIT && + sp_man.throttle < STICK_THRUST_RANGE * 0.1f) { if (stick_off_counter > STICK_ON_OFF_COUNTER_LIMIT) { update_state_machine_disarm(stat_pub, ¤t_status, mavlink_fd); stick_on_counter = 0; @@ -1870,7 +1872,10 @@ int commander_thread_main(int argc, char *argv[]) } /* check if left stick is in lower right position --> arm */ - if (sp_man.yaw > STICK_ON_OFF_LIMIT && sp_man.throttle < STICK_THRUST_RANGE * 0.2f) { + if (current_status.flag_control_manual_enabled && + current_status.manual_sas_mode == VEHICLE_MANUAL_SAS_MODE_ROLL_PITCH_ABS_YAW_ABS && + sp_man.yaw > STICK_ON_OFF_LIMIT && + sp_man.throttle < STICK_THRUST_RANGE * 0.1f) { if (stick_on_counter > STICK_ON_OFF_COUNTER_LIMIT) { update_state_machine_arm(stat_pub, ¤t_status, mavlink_fd); stick_on_counter = 0; From 3f9f2018e20f4be23e7d62571ec0a3678d960108 Mon Sep 17 00:00:00 2001 From: Jean Cyr Date: Fri, 5 Jul 2013 20:51:29 -0400 Subject: [PATCH 07/26] Support binding DSM2 and DSMX satellite receivers The px4io bind command allows you to put a DSM satellite receiver into bind mode. Since this feature requires that the dsm VCC line (red wire) be cut and routed through relay one, it is not enabled by default in order not to affect those not using a DSM satellite receiver or wising to use relay one for other purposes. NOTE: Binding DSM2 satellites in 11-bit mode is not supported due to potential bug in some DSM2 receiver streams when in 11-bit mode. Furthermore the px4io software folds 11 bit data down to 10 bits so there is no resolution advantage to to 11-bit mode. To enable the feature the RC_RL1_DSM_VCC parameter must be set to a non zero value from the console, or using QGroundControl: param set RC_RL1_DSM_VCC 1 From the console you can initiate DSM bind mode with: uorb start param set RC_RL1_DSM_VCC 1 px4io start px4io bind dsm2 For binding a DSMX satellite to a DSMX transmitter you would instead use: px4io bind dsmx Your receiver module should start a rapid flash and you can follow the normal binding sequence of your transmitter. Note: The value of parameter RC_RL1_DSM_VCC defaults to 0, so none of this will have any effect on an unmodified DSM receiver connection. For this feature to work, the power wire (red) must be cut and each side connected to a terminal on relay1 of the px4io board. This has been tested using Spektrum as well as Hobby King 'Orange' DSM satellite receivers. Both px4fmu and px4io images are updated. --- src/drivers/drv_pwm_output.h | 9 +++ src/drivers/px4io/px4io.cpp | 96 +++++++++++++++++++++++++-- src/modules/px4iofirmware/controls.c | 13 +++- src/modules/px4iofirmware/dsm.c | 43 +++++++++++- src/modules/px4iofirmware/protocol.h | 9 +++ src/modules/px4iofirmware/px4io.h | 1 + src/modules/px4iofirmware/registers.c | 4 ++ src/modules/sensors/sensor_params.c | 1 + src/modules/sensors/sensors.cpp | 12 ++++ 9 files changed, 178 insertions(+), 10 deletions(-) diff --git a/src/drivers/drv_pwm_output.h b/src/drivers/drv_pwm_output.h index 56af710592..52a6674031 100644 --- a/src/drivers/drv_pwm_output.h +++ b/src/drivers/drv_pwm_output.h @@ -115,6 +115,15 @@ ORB_DECLARE(output_pwm); /** clear the 'ARM ok' bit, which deactivates the safety switch */ #define PWM_SERVO_CLEAR_ARM_OK _IOC(_PWM_SERVO_BASE, 6) +/** start DSM bind */ +#define DSM_BIND_START _IOC(_PWM_SERVO_BASE, 7) + +/** stop DSM bind */ +#define DSM_BIND_STOP _IOC(_PWM_SERVO_BASE, 8) + +/** Power up DSM receiver */ +#define DSM_BIND_POWER_UP _IOC(_PWM_SERVO_BASE, 9) + /** set a single servo to a specific value */ #define PWM_SERVO_SET(_servo) _IOC(_PWM_SERVO_BASE, 0x20 + _servo) diff --git a/src/drivers/px4io/px4io.cpp b/src/drivers/px4io/px4io.cpp index 19163cebe3..54b9d50e47 100644 --- a/src/drivers/px4io/px4io.cpp +++ b/src/drivers/px4io/px4io.cpp @@ -89,6 +89,8 @@ #define PX4IO_SET_DEBUG _IOC(0xff00, 0) #define PX4IO_INAIR_RESTART_ENABLE _IOC(0xff00, 1) +static int dsm_vcc_ctl; + class PX4IO : public device::I2C { public: @@ -700,8 +702,6 @@ PX4IO::io_set_control_state() int PX4IO::set_failsafe_values(const uint16_t *vals, unsigned len) { - uint16_t regs[_max_actuators]; - if (len > _max_actuators) /* fail with error */ return E2BIG; @@ -1271,13 +1271,14 @@ PX4IO::print_status() printf("%u bytes free\n", io_reg_get(PX4IO_PAGE_STATUS, PX4IO_P_STATUS_FREEMEM)); uint16_t flags = io_reg_get(PX4IO_PAGE_STATUS, PX4IO_P_STATUS_FLAGS); - printf("status 0x%04x%s%s%s%s%s%s%s%s%s%s%s%s\n", + printf("status 0x%04x%s%s%s%s%s%s%s%s%s%s%s%s%s\n", flags, ((flags & PX4IO_P_STATUS_FLAGS_ARMED) ? " ARMED" : ""), ((flags & PX4IO_P_STATUS_FLAGS_OVERRIDE) ? " OVERRIDE" : ""), ((flags & PX4IO_P_STATUS_FLAGS_RC_OK) ? " RC_OK" : " RC_FAIL"), ((flags & PX4IO_P_STATUS_FLAGS_RC_PPM) ? " PPM" : ""), - ((flags & PX4IO_P_STATUS_FLAGS_RC_DSM) ? " DSM" : ""), + (((flags & PX4IO_P_STATUS_FLAGS_RC_DSM) && (!(flags & PX4IO_P_STATUS_FLAGS_RC_DSM11))) ? " DSM10" : ""), + (((flags & PX4IO_P_STATUS_FLAGS_RC_DSM) && (flags & PX4IO_P_STATUS_FLAGS_RC_DSM11)) ? " DSM11" : ""), ((flags & PX4IO_P_STATUS_FLAGS_RC_SBUS) ? " SBUS" : ""), ((flags & PX4IO_P_STATUS_FLAGS_FMU_OK) ? " FMU_OK" : " FMU_FAIL"), ((flags & PX4IO_P_STATUS_FLAGS_RAW_PWM) ? " RAW_PPM" : ""), @@ -1424,6 +1425,26 @@ PX4IO::ioctl(file *filep, int cmd, unsigned long arg) *(unsigned *)arg = _max_actuators; break; + case DSM_BIND_START: + io_reg_set(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_DSM, dsm_bind_power_down); + usleep(500000); + io_reg_set(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_DSM, dsm_bind_set_rx_out); + usleep(1000); + io_reg_set(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_DSM, dsm_bind_power_up); + usleep(100000); + io_reg_set(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_DSM, dsm_bind_send_pulses | (arg << 4)); + break; + + case DSM_BIND_STOP: + io_reg_set(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_DSM, dsm_bind_power_down); + io_reg_set(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_DSM, dsm_bind_reinit_uart); + usleep(500000); + break; + + case DSM_BIND_POWER_UP: + io_reg_set(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_DSM, dsm_bind_power_up); + break; + case PWM_SERVO_SET(0) ... PWM_SERVO_SET(PWM_OUTPUT_MAX_CHANNELS - 1): { /* TODO: we could go lower for e.g. TurboPWM */ @@ -1614,9 +1635,71 @@ start(int argc, char *argv[]) errx(1, "driver init failed"); } + if (param_get(param_find("RC_RL1_DSM_VCC"), &dsm_vcc_ctl) == OK) { + if (dsm_vcc_ctl) { + int fd = open("/dev/px4io", O_WRONLY); + if (fd < 0) + errx(1, "failed to open device"); + ioctl(fd, DSM_BIND_POWER_UP, 0); + close(fd); + } + } exit(0); } +void +bind(int argc, char *argv[]) +{ + int fd, pulses; + + if (g_dev == nullptr) + errx(1, "px4io must be started first"); + + if (dsm_vcc_ctl == 0) + errx(1, "DSM bind feature not enabled"); + + if (argc < 3) + errx(0, "needs argument, use dsm2 or dsmx"); + + if (!strcmp(argv[2], "dsm2")) + pulses = 3; + else if (!strcmp(argv[2], "dsmx")) + pulses = 7; + else + errx(1, "unknown parameter %s, use dsm2 or dsmx", argv[2]); + + fd = open("/dev/px4io", O_WRONLY); + + if (fd < 0) + errx(1, "failed to open device"); + + ioctl(fd, DSM_BIND_START, pulses); + + /* Open console directly to grab CTRL-C signal */ + int console = open("/dev/console", O_NONBLOCK | O_RDONLY | O_NOCTTY); + if (!console) + errx(1, "failed opening console"); + + warnx("This command will only bind DSM if satellite VCC (red wire) is controlled by relay 1."); + warnx("Press CTRL-C or 'c' when done."); + + for (;;) { + usleep(500000L); + /* Check if user wants to quit */ + char c; + if (read(console, &c, 1) == 1) { + if (c == 0x03 || c == 0x63) { + warnx("Done\n"); + ioctl(fd, DSM_BIND_STOP, 0); + ioctl(fd, DSM_BIND_POWER_UP, 0); + close(fd); + close(console); + exit(0); + } + } + } +} + void test(void) { @@ -1918,6 +2001,9 @@ px4io_main(int argc, char *argv[]) if (!strcmp(argv[1], "monitor")) monitor(); + if (!strcmp(argv[1], "bind")) + bind(argc, argv); + out: - errx(1, "need a command, try 'start', 'stop', 'status', 'test', 'monitor', 'debug', 'recovery', 'limit', 'current', 'failsafe' or 'update'"); + errx(1, "need a command, try 'start', 'stop', 'status', 'test', 'monitor', 'debug', 'recovery', 'limit', 'current', 'failsafe', 'bind', or 'update'"); } diff --git a/src/modules/px4iofirmware/controls.c b/src/modules/px4iofirmware/controls.c index 3cf9ca149b..9561c9b1e5 100644 --- a/src/modules/px4iofirmware/controls.c +++ b/src/modules/px4iofirmware/controls.c @@ -95,9 +95,16 @@ controls_tick() { */ perf_begin(c_gather_dsm); - bool dsm_updated = dsm_input(r_raw_rc_values, &r_raw_rc_count); - if (dsm_updated) + uint16_t temp_count = r_raw_rc_count; + bool dsm_updated = dsm_input(r_raw_rc_values, &temp_count); + if (dsm_updated) { r_status_flags |= PX4IO_P_STATUS_FLAGS_RC_DSM; + r_raw_rc_count = temp_count & 0x7fff; + if (temp_count & 0x8000) + r_status_flags |= PX4IO_P_STATUS_FLAGS_RC_DSM11; + else + r_status_flags &= ~PX4IO_P_STATUS_FLAGS_RC_DSM11; + } perf_end(c_gather_dsm); perf_begin(c_gather_sbus); @@ -138,7 +145,7 @@ controls_tick() { /* map raw inputs to mapped inputs */ /* XXX mapping should be atomic relative to protocol */ - for (unsigned i = 0; i < r_raw_rc_count; i++) { + for (unsigned i = 0; i < (r_raw_rc_count & 0x7fff); i++) { /* map the input channel */ uint16_t *conf = &r_page_rc_input_config[i * PX4IO_P_RC_CONFIG_STRIDE]; diff --git a/src/modules/px4iofirmware/dsm.c b/src/modules/px4iofirmware/dsm.c index ea35e55135..79e8925032 100644 --- a/src/modules/px4iofirmware/dsm.c +++ b/src/modules/px4iofirmware/dsm.c @@ -40,6 +40,7 @@ */ #include +#include #include #include @@ -101,6 +102,41 @@ dsm_init(const char *device) return dsm_fd; } +void +dsm_bind(uint16_t cmd, int pulses) +{ + const uint32_t usart1RxAsOutp = GPIO_OUTPUT|GPIO_CNF_OUTPP|GPIO_MODE_50MHz|GPIO_OUTPUT_SET|GPIO_PORTA|GPIO_PIN10; + + if (dsm_fd < 0) + return; + + switch (cmd) { + case dsm_bind_power_down: + // power down DSM satellite + POWER_RELAY1(0); + break; + case dsm_bind_power_up: + POWER_RELAY1(1); + dsm_guess_format(true); + break; + case dsm_bind_set_rx_out: + stm32_configgpio(usart1RxAsOutp); + break; + case dsm_bind_send_pulses: + for (int i = 0; i < pulses; i++) { + stm32_gpiowrite(usart1RxAsOutp, false); + up_udelay(50); + stm32_gpiowrite(usart1RxAsOutp, true); + up_udelay(50); + } + break; + case dsm_bind_reinit_uart: + // Restore USART rx pin + stm32_configgpio(GPIO_USART1_RX); + break; + } +} + bool dsm_input(uint16_t *values, uint16_t *num_values) { @@ -218,7 +254,7 @@ dsm_guess_format(bool reset) /* * Iterate the set of sensible sniffed channel sets and see whether - * decoding in 10 or 11-bit mode has yielded anything we recognise. + * decoding in 10 or 11-bit mode has yielded anything we recognize. * * XXX Note that due to what seem to be bugs in the DSM2 high-resolution * stream, we may want to sniff for longer in some cases when we think we @@ -303,7 +339,7 @@ dsm_decode(hrt_abstime frame_time, uint16_t *values, uint16_t *num_values) for (unsigned i = 0; i < DSM_FRAME_CHANNELS; i++) { uint8_t *dp = &frame[2 + (2 * i)]; - uint16_t raw = (dp[0] << 8) | dp[1]; + uint16_t raw = ((uint16_t)dp[0] << 8) | dp[1]; unsigned channel, value; if (!dsm_decode_channel(raw, channel_shift, &channel, &value)) @@ -349,6 +385,9 @@ dsm_decode(hrt_abstime frame_time, uint16_t *values, uint16_t *num_values) values[channel] = value; } + if (channel_shift == 11) + *num_values |= 0x8000; + /* * XXX Note that we may be in failsafe here; we need to work out how to detect that. */ diff --git a/src/modules/px4iofirmware/protocol.h b/src/modules/px4iofirmware/protocol.h index 674f9dddd6..6ee5c28342 100644 --- a/src/modules/px4iofirmware/protocol.h +++ b/src/modules/px4iofirmware/protocol.h @@ -105,6 +105,7 @@ #define PX4IO_P_STATUS_FLAGS_ARM_SYNC (1 << 9) /* the arming state between IO and FMU is in sync */ #define PX4IO_P_STATUS_FLAGS_INIT_OK (1 << 10) /* initialisation of the IO completed without error */ #define PX4IO_P_STATUS_FLAGS_FAILSAFE (1 << 11) /* failsafe is active */ +#define PX4IO_P_STATUS_FLAGS_RC_DSM11 (1 << 12) /* DSM input is 11 bit data */ #define PX4IO_P_STATUS_ALARMS 3 /* alarm flags - alarms latch, write 1 to a bit to clear it */ #define PX4IO_P_STATUS_ALARMS_VBATT_LOW (1 << 0) /* VBatt is very close to regulator dropout */ @@ -157,6 +158,14 @@ #define PX4IO_P_SETUP_PWM_ALTRATE 4 /* 'high' PWM frame output rate in Hz */ #define PX4IO_P_SETUP_RELAYS 5 /* bitmask of relay/switch outputs, 0 = off, 1 = on */ #define PX4IO_P_SETUP_VBATT_SCALE 6 /* battery voltage correction factor (float) */ +#define PX4IO_P_SETUP_DSM 7 /* DSM bind state */ +enum { /* DSM bind states */ + dsm_bind_power_down = 0, + dsm_bind_power_up, + dsm_bind_set_rx_out, + dsm_bind_send_pulses, + dsm_bind_reinit_uart +}; #define PX4IO_P_SETUP_SET_DEBUG 9 /* debug level for IO board */ /* autopilot control values, -10000..10000 */ diff --git a/src/modules/px4iofirmware/px4io.h b/src/modules/px4iofirmware/px4io.h index 272cdb7bf4..83feeb9b61 100644 --- a/src/modules/px4iofirmware/px4io.h +++ b/src/modules/px4iofirmware/px4io.h @@ -184,6 +184,7 @@ extern void controls_init(void); extern void controls_tick(void); extern int dsm_init(const char *device); extern bool dsm_input(uint16_t *values, uint16_t *num_values); +extern void dsm_bind(uint16_t cmd, int pulses); extern int sbus_init(const char *device); extern bool sbus_input(uint16_t *values, uint16_t *num_values); diff --git a/src/modules/px4iofirmware/registers.c b/src/modules/px4iofirmware/registers.c index df7d6dcd35..805eb7eccd 100644 --- a/src/modules/px4iofirmware/registers.c +++ b/src/modules/px4iofirmware/registers.c @@ -360,6 +360,10 @@ registers_set_one(uint8_t page, uint8_t offset, uint16_t value) isr_debug(0, "set debug %u\n", (unsigned)r_page_setup[PX4IO_P_SETUP_SET_DEBUG]); break; + case PX4IO_P_SETUP_DSM: + dsm_bind(value & 0x0f, (value >> 4) & 7); + break; + default: return -1; } diff --git a/src/modules/sensors/sensor_params.c b/src/modules/sensors/sensor_params.c index 2300601484..a11c135683 100644 --- a/src/modules/sensors/sensor_params.c +++ b/src/modules/sensors/sensor_params.c @@ -151,6 +151,7 @@ PARAM_DEFINE_FLOAT(RC14_REV, 1.0f); PARAM_DEFINE_FLOAT(RC14_DZ, 0.0f); PARAM_DEFINE_INT32(RC_TYPE, 1); /** 1 = FUTABA, 2 = Spektrum, 3 = Graupner HoTT, 4 = Turnigy 9x */ +PARAM_DEFINE_INT32(RC_RL1_DSM_VCC, 0); /* Relay 1 controls DSM VCC */ /* default is conversion factor for the PX4IO / PX4IOAR board, the factor for PX4FMU standalone is different */ PARAM_DEFINE_FLOAT(BAT_V_SCALING, (3.3f * 52.0f / 5.0f / 4095.0f)); diff --git a/src/modules/sensors/sensors.cpp b/src/modules/sensors/sensors.cpp index 6b6aeedee5..626cbade49 100644 --- a/src/modules/sensors/sensors.cpp +++ b/src/modules/sensors/sensors.cpp @@ -229,6 +229,8 @@ private: float rc_scale_flaps; float battery_voltage_scaling; + + int rc_rl1_DSM_VCC_control; } _parameters; /**< local copies of interesting parameters */ struct { @@ -276,6 +278,8 @@ private: param_t rc_scale_flaps; param_t battery_voltage_scaling; + + param_t rc_rl1_DSM_VCC_control; } _parameter_handles; /**< handles for interesting parameters */ @@ -509,6 +513,9 @@ Sensors::Sensors() : _parameter_handles.battery_voltage_scaling = param_find("BAT_V_SCALING"); + /* DSM VCC relay control */ + _parameter_handles.rc_rl1_DSM_VCC_control = param_find("RC_RL1_DSM_VCC"); + /* fetch initial parameter values */ parameters_update(); } @@ -722,6 +729,11 @@ Sensors::parameters_update() warnx("Failed updating voltage scaling param"); } + /* relay 1 DSM VCC control */ + if (param_get(_parameter_handles.rc_rl1_DSM_VCC_control, &(_parameters.rc_rl1_DSM_VCC_control)) != OK) { + warnx("Failed updating relay 1 DSM VCC control"); + } + return OK; } From 8d0784af61536302cc6a2a1d65f847528658ba09 Mon Sep 17 00:00:00 2001 From: Anton Babushkin Date: Sat, 6 Jul 2013 18:30:09 +0400 Subject: [PATCH 08/26] gpio_led: PX4IO RELAY and ACC outputs support, some fixes --- src/modules/gpio_led/gpio_led.c | 110 ++++++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 21 deletions(-) diff --git a/src/modules/gpio_led/gpio_led.c b/src/modules/gpio_led/gpio_led.c index 43cbe74c74..632630b54a 100644 --- a/src/modules/gpio_led/gpio_led.c +++ b/src/modules/gpio_led/gpio_led.c @@ -54,9 +54,15 @@ #include #include +#define PX4IO_RELAY1 (1<<0) +#define PX4IO_RELAY2 (1<<1) +#define PX4IO_ACC1 (1<<2) +#define PX4IO_ACC2 (1<<3) + struct gpio_led_s { struct work_s work; int gpio_fd; + bool use_io; int pin; struct vehicle_status_s status; int vehicle_status_sub; @@ -75,51 +81,97 @@ void gpio_led_cycle(FAR void *arg); int gpio_led_main(int argc, char *argv[]) { - int pin = GPIO_EXT_1; - if (argc < 2) { - errx(1, "no argument provided. Try 'start' or 'stop' [-p 1/2]"); + errx(1, "usage: gpio_led {start|stop} [-p <1|2|a1|a2|r1|r2>]\n" + "\t-p\tUse pin:\n" + "\t\t1\tPX4FMU GPIO_EXT1 (default)\n" + "\t\t2\tPX4FMU GPIO_EXT2\n" + "\t\ta1\tPX4IO GPIO_ACC1\n" + "\t\ta2\tPX4IO GPIO_ACC2\n" + "\t\tr1\tPX4IO GPIO_RELAY1\n" + "\t\tr2\tPX4IO GPIO_RELAY2"); } else { - /* START COMMAND HANDLING */ if (!strcmp(argv[1], "start")) { + if (gpio_led_started) { + errx(1, "already running"); + } + + bool use_io = false; + int pin = GPIO_EXT_1; if (argc > 2) { - if (!strcmp(argv[1], "-p")) { - if (!strcmp(argv[2], "1")) { + if (!strcmp(argv[2], "-p")) { + if (!strcmp(argv[3], "1")) { + use_io = false; pin = GPIO_EXT_1; - } else if (!strcmp(argv[2], "2")) { + } else if (!strcmp(argv[3], "2")) { + use_io = false; pin = GPIO_EXT_2; + } else if (!strcmp(argv[3], "a1")) { + use_io = true; + pin = PX4IO_ACC1; + + } else if (!strcmp(argv[3], "a2")) { + use_io = true; + pin = PX4IO_ACC2; + + } else if (!strcmp(argv[3], "r1")) { + use_io = true; + pin = PX4IO_RELAY1; + + } else if (!strcmp(argv[3], "r2")) { + use_io = true; + pin = PX4IO_RELAY2; + } else { - warnx("[gpio_led] Unsupported pin: %s\n", argv[2]); - exit(1); + errx(1, "unsupported pin: %s", argv[3]); } } } memset(&gpio_led_data, 0, sizeof(gpio_led_data)); + gpio_led_data.use_io = use_io; gpio_led_data.pin = pin; int ret = work_queue(LPWORK, &gpio_led_data.work, gpio_led_start, &gpio_led_data, 0); if (ret != 0) { - warnx("[gpio_led] Failed to queue work: %d\n", ret); - exit(1); + errx(1, "failed to queue work: %d", ret); } else { gpio_led_started = true; + char pin_name[24]; + + if (use_io) { + if (pin & (PX4IO_ACC1 | PX4IO_ACC2)) { + sprintf(pin_name, "PX4IO ACC%i", (pin >> 3)); + + } else { + sprintf(pin_name, "PX4IO RELAY%i", pin); + } + + } else { + sprintf(pin_name, "PX4FMU GPIO_EXT%i", pin); + + } + + warnx("start, using pin: %s", pin_name); } exit(0); - /* STOP COMMAND HANDLING */ } else if (!strcmp(argv[1], "stop")) { - gpio_led_started = false; + if (gpio_led_started) { + gpio_led_started = false; + warnx("stop"); - /* INVALID COMMAND */ + } else { + errx(1, "not running"); + } } else { errx(1, "unrecognized command '%s', only supporting 'start' or 'stop'", argv[1]); @@ -131,11 +183,22 @@ void gpio_led_start(FAR void *arg) { FAR struct gpio_led_s *priv = (FAR struct gpio_led_s *)arg; + char *gpio_dev; + + if (priv->use_io) { + gpio_dev = "/dev/px4io"; + + } else { + gpio_dev = "/dev/px4fmu"; + } + /* open GPIO device */ - priv->gpio_fd = open(GPIO_DEVICE_PATH, 0); + priv->gpio_fd = open(gpio_dev, 0); if (priv->gpio_fd < 0) { - warnx("[gpio_led] GPIO: open fail\n"); + // TODO find way to print errors + //printf("gpio_led: GPIO device \"%s\" open fail\n", gpio_dev); + gpio_led_started = false; return; } @@ -150,11 +213,11 @@ void gpio_led_start(FAR void *arg) int ret = work_queue(LPWORK, &priv->work, gpio_led_cycle, priv, 0); if (ret != 0) { - warnx("[gpio_led] Failed to queue work: %d\n", ret); + // TODO find way to print errors + //printf("gpio_led: failed to queue work: %d\n", ret); + gpio_led_started = false; return; } - - warnx("[gpio_led] Started, using pin GPIO_EXT%i\n", priv->pin); } void gpio_led_cycle(FAR void *arg) @@ -211,7 +274,12 @@ void gpio_led_cycle(FAR void *arg) if (priv->counter > 5) priv->counter = 0; - /* repeat cycle at 5 Hz*/ - if (gpio_led_started) + /* repeat cycle at 5 Hz */ + if (gpio_led_started) { work_queue(LPWORK, &priv->work, gpio_led_cycle, priv, USEC2TICK(200000)); + + } else { + /* switch off LED on stop */ + ioctl(priv->gpio_fd, GPIO_CLEAR, priv->pin); + } } From 369e6d1eeab8478f3ce81a7803e55047c221d15b Mon Sep 17 00:00:00 2001 From: Anton Babushkin Date: Sat, 6 Jul 2013 18:37:02 +0400 Subject: [PATCH 09/26] gpio_led: minor usage fix --- src/modules/gpio_led/gpio_led.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/gpio_led/gpio_led.c b/src/modules/gpio_led/gpio_led.c index 632630b54a..8b4c0cb308 100644 --- a/src/modules/gpio_led/gpio_led.c +++ b/src/modules/gpio_led/gpio_led.c @@ -86,10 +86,10 @@ int gpio_led_main(int argc, char *argv[]) "\t-p\tUse pin:\n" "\t\t1\tPX4FMU GPIO_EXT1 (default)\n" "\t\t2\tPX4FMU GPIO_EXT2\n" - "\t\ta1\tPX4IO GPIO_ACC1\n" - "\t\ta2\tPX4IO GPIO_ACC2\n" - "\t\tr1\tPX4IO GPIO_RELAY1\n" - "\t\tr2\tPX4IO GPIO_RELAY2"); + "\t\ta1\tPX4IO ACC1\n" + "\t\ta2\tPX4IO ACC2\n" + "\t\tr1\tPX4IO RELAY1\n" + "\t\tr2\tPX4IO RELAY2"); } else { From dab652faf68931a2b1fa07609d63518237c9c8b7 Mon Sep 17 00:00:00 2001 From: Jean Cyr Date: Sun, 7 Jul 2013 19:04:30 -0400 Subject: [PATCH 10/26] Prevent RELAY1 control via IOCTL if DSM bind feature is enabled --- src/drivers/px4io/px4io.cpp | 59 +++++++++++++++++++++------- src/modules/px4iofirmware/controls.c | 2 +- src/modules/px4iofirmware/dsm.c | 2 +- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/drivers/px4io/px4io.cpp b/src/drivers/px4io/px4io.cpp index 54b9d50e47..ad0112b9bf 100644 --- a/src/drivers/px4io/px4io.cpp +++ b/src/drivers/px4io/px4io.cpp @@ -89,8 +89,6 @@ #define PX4IO_SET_DEBUG _IOC(0xff00, 0) #define PX4IO_INAIR_RESTART_ENABLE _IOC(0xff00, 1) -static int dsm_vcc_ctl; - class PX4IO : public device::I2C { public: @@ -131,6 +129,16 @@ public: */ void print_status(); + inline void set_dsm_vcc_ctl(bool enable) + { + _dsm_vcc_ctl = enable; + }; + + inline bool get_dsm_vcc_ctl() + { + return _dsm_vcc_ctl; + }; + private: // XXX unsigned _max_actuators; @@ -174,6 +182,12 @@ private: float _battery_mamphour_total; uint64_t _battery_last_timestamp; + /** + * Relay1 is dedicated to controlling DSM receiver power + */ + + bool _dsm_vcc_ctl; + /** * Trampoline to the worker task */ @@ -315,7 +329,7 @@ PX4IO *g_dev; } PX4IO::PX4IO() : - I2C("px4io", "/dev/px4io", PX4_I2C_BUS_ONBOARD, PX4_I2C_OBDEV_PX4IO, 320000), + I2C("px4io", GPIO_DEVICE_PATH, PX4_I2C_BUS_ONBOARD, PX4_I2C_OBDEV_PX4IO, 320000), _max_actuators(0), _max_controls(0), _max_rc_input(0), @@ -340,7 +354,8 @@ PX4IO::PX4IO() : _battery_amp_per_volt(90.0f/5.0f), // this matches the 3DR current sensor _battery_amp_bias(0), _battery_mamphour_total(0), - _battery_last_timestamp(0) + _battery_last_timestamp(0), + _dsm_vcc_ctl(false) { /* we need this potentially before it could be set in task_main */ g_dev = this; @@ -1487,18 +1502,31 @@ PX4IO::ioctl(file *filep, int cmd, unsigned long arg) break; } - case GPIO_RESET: - ret = io_reg_set(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_RELAYS, 0); + case GPIO_RESET: { + uint32_t bits = (1 << _max_relays) - 1; + /* don't touch relay1 if it's controlling RX vcc */ + if (_dsm_vcc_ctl) + bits &= ~1; + ret = io_reg_modify(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_RELAYS, bits, 0); break; + } case GPIO_SET: arg &= ((1 << _max_relays) - 1); - ret = io_reg_modify(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_RELAYS, 0, arg); + /* don't touch relay1 if it's controlling RX vcc */ + if (_dsm_vcc_ctl & (arg & 1)) + ret = -EINVAL; + else + ret = io_reg_modify(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_RELAYS, 0, arg); break; case GPIO_CLEAR: arg &= ((1 << _max_relays) - 1); - ret = io_reg_modify(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_RELAYS, arg, 0); + /* don't touch relay1 if it's controlling RX vcc */ + if (_dsm_vcc_ctl & (arg & 1)) + ret = -EINVAL; + else + ret = io_reg_modify(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_RELAYS, arg, 0); break; case GPIO_GET: @@ -1635,9 +1663,12 @@ start(int argc, char *argv[]) errx(1, "driver init failed"); } + int dsm_vcc_ctl; + if (param_get(param_find("RC_RL1_DSM_VCC"), &dsm_vcc_ctl) == OK) { if (dsm_vcc_ctl) { - int fd = open("/dev/px4io", O_WRONLY); + g_dev->set_dsm_vcc_ctl(true); + int fd = open(GPIO_DEVICE_PATH, O_WRONLY); if (fd < 0) errx(1, "failed to open device"); ioctl(fd, DSM_BIND_POWER_UP, 0); @@ -1655,7 +1686,7 @@ bind(int argc, char *argv[]) if (g_dev == nullptr) errx(1, "px4io must be started first"); - if (dsm_vcc_ctl == 0) + if (!g_dev->get_dsm_vcc_ctl()) errx(1, "DSM bind feature not enabled"); if (argc < 3) @@ -1668,7 +1699,7 @@ bind(int argc, char *argv[]) else errx(1, "unknown parameter %s, use dsm2 or dsmx", argv[2]); - fd = open("/dev/px4io", O_WRONLY); + fd = open(GPIO_DEVICE_PATH, O_WRONLY); if (fd < 0) errx(1, "failed to open device"); @@ -1694,8 +1725,8 @@ bind(int argc, char *argv[]) ioctl(fd, DSM_BIND_POWER_UP, 0); close(fd); close(console); - exit(0); - } + exit(0); + } } } } @@ -1709,7 +1740,7 @@ test(void) int direction = 1; int ret; - fd = open("/dev/px4io", O_WRONLY); + fd = open(GPIO_DEVICE_PATH, O_WRONLY); if (fd < 0) err(1, "failed to open device"); diff --git a/src/modules/px4iofirmware/controls.c b/src/modules/px4iofirmware/controls.c index 9561c9b1e5..43d96fb067 100644 --- a/src/modules/px4iofirmware/controls.c +++ b/src/modules/px4iofirmware/controls.c @@ -145,7 +145,7 @@ controls_tick() { /* map raw inputs to mapped inputs */ /* XXX mapping should be atomic relative to protocol */ - for (unsigned i = 0; i < (r_raw_rc_count & 0x7fff); i++) { + for (unsigned i = 0; i < r_raw_rc_count; i++) { /* map the input channel */ uint16_t *conf = &r_page_rc_input_config[i * PX4IO_P_RC_CONFIG_STRIDE]; diff --git a/src/modules/px4iofirmware/dsm.c b/src/modules/px4iofirmware/dsm.c index 79e8925032..ab6e3fec43 100644 --- a/src/modules/px4iofirmware/dsm.c +++ b/src/modules/px4iofirmware/dsm.c @@ -339,7 +339,7 @@ dsm_decode(hrt_abstime frame_time, uint16_t *values, uint16_t *num_values) for (unsigned i = 0; i < DSM_FRAME_CHANNELS; i++) { uint8_t *dp = &frame[2 + (2 * i)]; - uint16_t raw = ((uint16_t)dp[0] << 8) | dp[1]; + uint16_t raw = (dp[0] << 8) | dp[1]; unsigned channel, value; if (!dsm_decode_channel(raw, channel_shift, &channel, &value)) From 20103f572fcf1451b6625209f02b7fde70dd3f04 Mon Sep 17 00:00:00 2001 From: Jean Cyr Date: Sun, 7 Jul 2013 20:19:27 -0400 Subject: [PATCH 11/26] Minor px4io optimization Since this module creates the PX4IO object and that the IOCTL function doesn't use the file descriptor parameter, there is no need to invoke IOCTL via the filesystem since we can call it directly. --- src/drivers/px4io/px4io.cpp | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/drivers/px4io/px4io.cpp b/src/drivers/px4io/px4io.cpp index ad0112b9bf..1adefdea5b 100644 --- a/src/drivers/px4io/px4io.cpp +++ b/src/drivers/px4io/px4io.cpp @@ -1388,7 +1388,8 @@ PX4IO::print_status() } int -PX4IO::ioctl(file *filep, int cmd, unsigned long arg) +PX4IO::ioctl(file * /*filep*/, int cmd, unsigned long arg) +/* Make it obvious that file * isn't used here */ { int ret = OK; @@ -1668,11 +1669,7 @@ start(int argc, char *argv[]) if (param_get(param_find("RC_RL1_DSM_VCC"), &dsm_vcc_ctl) == OK) { if (dsm_vcc_ctl) { g_dev->set_dsm_vcc_ctl(true); - int fd = open(GPIO_DEVICE_PATH, O_WRONLY); - if (fd < 0) - errx(1, "failed to open device"); - ioctl(fd, DSM_BIND_POWER_UP, 0); - close(fd); + g_dev->ioctl(nullptr, DSM_BIND_POWER_UP, 0); } } exit(0); @@ -1681,7 +1678,7 @@ start(int argc, char *argv[]) void bind(int argc, char *argv[]) { - int fd, pulses; + int pulses; if (g_dev == nullptr) errx(1, "px4io must be started first"); @@ -1699,12 +1696,7 @@ bind(int argc, char *argv[]) else errx(1, "unknown parameter %s, use dsm2 or dsmx", argv[2]); - fd = open(GPIO_DEVICE_PATH, O_WRONLY); - - if (fd < 0) - errx(1, "failed to open device"); - - ioctl(fd, DSM_BIND_START, pulses); + g_dev->ioctl(nullptr, DSM_BIND_START, pulses); /* Open console directly to grab CTRL-C signal */ int console = open("/dev/console", O_NONBLOCK | O_RDONLY | O_NOCTTY); @@ -1721,9 +1713,8 @@ bind(int argc, char *argv[]) if (read(console, &c, 1) == 1) { if (c == 0x03 || c == 0x63) { warnx("Done\n"); - ioctl(fd, DSM_BIND_STOP, 0); - ioctl(fd, DSM_BIND_POWER_UP, 0); - close(fd); + g_dev->ioctl(nullptr, DSM_BIND_STOP, 0); + g_dev->ioctl(nullptr, DSM_BIND_POWER_UP, 0); close(console); exit(0); } @@ -1914,7 +1905,7 @@ px4io_main(int argc, char *argv[]) * We can cheat and call the driver directly, as it * doesn't reference filp in ioctl() */ - g_dev->ioctl(NULL, PX4IO_INAIR_RESTART_ENABLE, 1); + g_dev->ioctl(nullptr, PX4IO_INAIR_RESTART_ENABLE, 1); } else { errx(1, "not loaded"); } @@ -1958,7 +1949,7 @@ px4io_main(int argc, char *argv[]) /* we can cheat and call the driver directly, as it * doesn't reference filp in ioctl() */ - int ret = g_dev->ioctl(NULL, PX4IO_SET_DEBUG, level); + int ret = g_dev->ioctl(nullptr, PX4IO_SET_DEBUG, level); if (ret != 0) { printf("SET_DEBUG failed - %d\n", ret); exit(1); From a7d5248f05f22e0e546df092ef507a4100151238 Mon Sep 17 00:00:00 2001 From: Jean Cyr Date: Mon, 8 Jul 2013 00:07:00 -0400 Subject: [PATCH 12/26] Feature documentation --- Documentation/dsm_bind.odt | Bin 0 -> 26760 bytes Documentation/dsm_bind.pdf | Bin 0 -> 30593 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Documentation/dsm_bind.odt create mode 100644 Documentation/dsm_bind.pdf diff --git a/Documentation/dsm_bind.odt b/Documentation/dsm_bind.odt new file mode 100644 index 0000000000000000000000000000000000000000..793980d4cedd8fa4b244f68ac98f3055b3e54b18 GIT binary patch literal 26760 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≷DLK$# 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 K3=Fr@Kq>)J(ZQeq literal 0 HcmV?d00001 diff --git a/Documentation/dsm_bind.pdf b/Documentation/dsm_bind.pdf new file mode 100644 index 0000000000000000000000000000000000000000..daaad7b965109498124b0887502d665ab64b38ac GIT binary patch literal 30593 zcmcHAbyyW$|1f%y4k<+%wuCgh_olnMbJJ|PTab`aT3WiKrAtsiKtX8)qyz~C36&11 zbHOcmKfmXF&-IctcL6$q&$~B4cumaftmitvs`WAZtd z(){6m*Pfs3ZoZM?s6eYoi}z&1p#DhOChA2D%D#Adt}>2+Az&IcjW^X&={<0O=&YA$ zUOo;O_EtdxpN}l&*%P105qH8jttABqqqpBnkddCvEnnOgs;u=LE{hu$n{u8eyIY0(dNzvU%4s^&8? zFq|%H8hCQGd@`_ppFg+qF+R)~ude%7@`*&j;P>VEZ7W4`nLVAvFk9|dl{<(wat1wH z8$Z6Hie=H0H(Vc8VC5kt&}Xy2uQpMZvKs6ymA^7VSo}CRNk977vqX$ zWAD17)K(Simgms^MJwYyl{^e=7nta~m?vyBF%%x}F&y=|gV$)?4ecKG#wBE1naG+#J%``h7Q( zbR+=ZGJjNvP%;$t%_REKD0NcnO})-7POLUJO!_mNn?HY)f6s`J9fp#c&PRJ7sw_V@ z{h-k2$1w!rEVxT)>6t!+wdoXgxGLibjy-02hvQ>Uzlqz@CM1p55OR}joGt#PQUsW; z{TbyAa*QRTQeF@L=RwAez5BPZU!-uqx?!D%PLo*Br)ToID?%N z#6AxNYkJj=dPV1`OIw=F4#_hKkjgJM?+CvJ^Af&=qo$^?`K=`ejHwBV`^(XOboGk;8uUbQI|69aJHSmCi0)L+~O z{EFzI-kz|J0HxRpjtS>d)niOX31f^G-p*ajkytxV6P?NprRLU8D#CU2Cj`Ts zjRuBe?lbrb(zD$-y`i3C7o|jC;b6f3jQ?waF6lN`s1VBmTKLXr;AT5*ThvBe^_vqS zd**CTZUehaz=1%bL|T~DxH*WUQr{w|{PSr+DCID=rFh9EV5at+R|j752?1rELUL<4 zUzPOFA*fJwO6#NTcB+obTVZ-Fw=4^O>atQ&8P2_5?!m@?&J^VK&T@jE;wZejOr3b` zWq4&Df+w;%Y=ROa=;@wH>Zd#UbRp!C!TBuM)so{Hb{u>Ffj)OJQ2_^Q`JpjxW2zbv zR^kJSPq|86&)>;Hx_y2xki#QP;x)B&6}mzCW?4M^|ITLtn{`FB>)k>hayJ9i94< zp0cfu(~4(4ykF7YT|^YB`{Rg*S==LqVCVy)F1*OQzwKaPHhJ?7rO#HTC6`@+qp*bW zSBu+*whf98SRbUs(kl8-aHi5?V2h*Y7H#<6YjJSQ{HhxWFAPEP+MkPK#`DcEY`4 zfySIT@n~wAPYI!DuCGkWCk^7Sl~vzQDpvRAljB)^t+jTlVsgK|U_UtUpffBVudiKCwrOLa z9Pt6#Btpt(E_hh+??aO zk*0Gf(C*2Y#M7j5bH%5MuRHWNnMveY>UWSTn9MlAjcDzZ1^WIMa;B{?N&F76t9#L0he+=g980x9Qy{Z5wf?Zdf8|O(z1)a&%&yG`+G|7$Kejh2^$62DROw=PO zw5d#1Ad;?b_y|$6zB!~(8G>Dl!71G>AMHV#bz?p&l|zo>4Ko-kNWOZQkGZYP6Q`O` z@?~x0m)n(_QVY>+4EykR;|a_UQYW_9oxgK@cCFnk5LQcDDazMc|9H1Q+0_`Z6Tv(? zXon59d5L_-(2r&^H^WINeAR6dNK}6h_fG8)&x=kMA*q9W;-5-B>ps;{q+SN6p(Lz7 zy^<+hqGiUIpZF;uwaOA$e{fpAb@FXae1GqYncmN-0rHW)H{_GG@4162BAiW$W(i+F zeKNZWt-aU3RvlSw9z4YU&b4>*%ZjN})so|rr3_qYG{{I>|fzUmcf5?*^|4Tn^ zG@csE@wsZYSW;h6tg3BIGx&QW1hv?wme!`w(?0C$EM5FI z)u5{rd`uK~?3qg4veDCVcdF@7n3|A+dNO@dlbb(nbdKM!hSMAf{}+p2*Hlysom90AqOaO z$F?~9TU3WIDxt}?>m2@i;&*G{uGJgPU~Xiwu&&!=*yT*_mIKge&cFU&_jEz3wY~+>0A>wAb{}?iJ?89{gj# z!+vrJ7miH~oie>ikv^eEt&Ix!hQ|hMrfub7rQ6aZdO@+`ff!-}?(l3^fk#*u2THjewcG?6- z83m2brd+NCT1?hm=|iDC?L&^J5ovE%@1<6p_N6e@ib}{F{^OxQ_W|@iEmdXT&S*&H z^d^$*q}cwD3T#rzopBEt%NzXprEZ+nh$nt6-Vyf7#JzJ~_PJ9IJl4UJl1Ip$JTLe; zyUBp)q{AsmS$(WYicJ@~)3=2v2)p#=_RwrxS*n3%PN#}+{QK^uz#lQUUOn(!;j+N# zc_?2KH1rzm#%p)mzx=y~i?LP4^u04##F@RZB_fZ$%VFh&;)bzGLPvBa5yF_}JF(uX zbFG!H2#Wi4d)-~mJBlqI34}9G?Movxiljobw%^LRl0<#+(5?)8=txi}NTWgN-VDFunB%O({5M1~*f`a*56*s&Ba}dms zY%7(IuZj4NRXnIml2|Qt2;7-Jbldt4;5KP9@`y{t{X!g)ks5`JVplpI6@Ig|^=-S) zGyPf8^Y*C2%GNo8%$sl*ybR!k>^_@Y)7VuFt;3KYC{&!%@rkLyi;v-ue5`#cmsZaX^w z6L!iJWW+gjk^uv-<5b^x@=@fpR}>73KNfpA+_g@%Xxc!`G#y>p@cccWO1Hx!lkP2U?+b-0KpI$KMYb33?c zi3D}SJF#&N0rPdIwR;MB-g{R^_<>w*qhm6&O_+_zc-dz1G*#3I){f?}koU{&@e6Y+zOEg) z#)l%~qP!G0eYqGtK$Zzda4MG?LGi&_9J%`%h^?Ecg}2E+I0>vKehGi8D!?YHG#!O2 zLl}q8BZRJ*Y0iFJJWE5QzBsEgEo2@4q|=6ct*um7+^0DD-jk0qZva|eZ)La2Zu|&- zzcIPt;k=og`k@vnS$L=Q!=vxrEb&XU$C*E}M`q9!SsM{fc*kWx<(Xk-)y#WHk03lN ztkAjVNlFoEfBOxkxXLGUOz}Logepu z{#^>hc3S-fjTw2hvhfT{RCwO5rVO9i>9pXmB6ck29nL0UWH+GR34$aSAl&HEipY8r zx{}l7E;6fVpwRExa7$`dDM_SY+LO8cVef$qaBx8 z-V*txo^C+|(eDrhB9UNw+DdjX$DR?Q-{q8}Lv{3T>YKur0#rdy*j? z>LB7*;jr@ih@tee;Y{k=n@Q!*#JFyL4ZrV---e4zK?7&^JDUzE6e$_&EGSj$t`S~E z1lZE*SEm(6P-^RRM`qs;Ga0M@DaRnW`$(w!`CFXGRzuFchbLPB{R~oq8BrRKB0f&C zK|gyLKaaX zR$jEzX)bn87n>WSAxUbU=9Yb`hV^(i!(@opqv#;?+f$Vy_Rnn+PMWm*rDxVAx0eS- zT&Bjq(4-pB>3Q6Z8kD$`-7~Nu8B@&NMe);8mD<5!>1hQ8m7<@T-=^-2ny6Xt@JFAb zU`H+OyjvMx{Puf;yvHbOxHU@Nc$fN1*nu-*?=PKgPvQfYPPVBUgwe;L34X5x#>>}6 zcZYGy9ru{^yG){o_TG?5`p$dVTkn*vluYH)HQLQkV4bCB7n?0V{2r%h&ErC{IdST9 zDp@7VV|i5nnl{ZIkF^g~2o!%tta?b8Qn31ReI_$avT$@ON#uAVKBBo$yd-)@pXT$e z6w9a@ga*$91s~&)-*_MaWuvzJ-e>m#in0EXEuEHyu_usN#s)@u_GT>wImMjM7P-zp zKYfSgoqjj=*Qh#2J!~~TNeVc}SPV(~{$H)uWpi}VY=IFF#8tBe``c=1J#aw-)y%BX zKxMS0t(k|y4C27rKSW^QOF4*>L{eIo#BpxvE4-7L`V zLP9__H)jh?w1**3O-cp;ly@|J0QlnVss;g2 z+Q&my^D=et<=a&Ppq{F^9ooVJ07m|Egp9M32LQ}>xgjEQk%*HA+R4Km@J|XNB3HKr z=Kufop8nj!-wTF7{&j(nfAZu%F7UEzpz*IOyg25cH_Cv^TnAicyE_1=Xlv;XFuW`j zjeqV#gTM#?lyvrV@&G{omBK~O;-3`$Bp_+#Vdmg$eU;Lml2LU*JBeF(*g87_3@=Jr z0|1nFH51Q0l!fE@(_fv!Q3$bW&Mpx2-%=rsfidJTkvUW22c*B~h9H82W# z1%X8Ug+M{CK~d0aa1`_!3EG*dA*icXi_=>!|JPHZ|U4tNz*Pt-u zUvMPq4?{yk6bK5t20?=Ug1}LK*xA`RJ3E`0n4q9w@HO^}MPFYZ1p+~?7#ka-pm6XN zX=!N`2#mZ20YR=oA<%0OB;pzr2EHOJER2G}Kv%fAxls@Z=o%CO{tE;}U4uhWS0D(; zH7E%D7aWB8LrhEz1%kk?NJ>hYnVF%?FBd2T@)rVx`s3o+0V9!D{QdnoIXT_j+)xl0 z=!%Pr3jhF+kdQz@VX$iu5b_!b1iiw;!vlxIQ6Mnl8Wak?A}%hDf*?RwbaZsAt*up6 zRZ(si3j_i|L16GJA|fIv5EODnKtKQm1%s|Z5Qu9a2;?s?6m<;(MqL9TQCA>f#1%zF zMHB=KzXpOL{sMtfS0J#9i~9ovLtO)dP}e|E)HOI9^~b}94^dDs>>30KzG7ozgww4?Tvy!Ay-62MNuFK=!&4ApscJc3IYNDg@B>{KwMNp&^0+ZITQp2yMja_`T6-# zP%!cu1O&R`=jUf&V1R?GiFzPQj3UUPsLjA$a%#4D7q1QkV7DJdy6 zH8l+l4JZ_fMx#wlO;I2y^coZa`3nR=T?0c=e>gZeprBCrUoZ&j8U%^D28W@pK@g~G zP#Eej1Pb~G1d99%0Y_bfAyC(#P}DU981)Ae6O*p4uCK2z3IYaS2?z*4K_JM#z(~{| zDk>^MLP97A9C-x{1}iHoTUlA5AaKxMFfi&5BO@ae1PQ;Qt*woMAR$-0yu7%$xHL62 zQI}snK0fB=<|qgP`4=34y25d}KtPCVP$={o2nxCe1;PJDa2{|f>~U4g<7 z*Q_p=%Xobevmp?7cXt#N0>8pxbk%?P*SNk082V3tM&n=I$g8(JLP9_d z=d+zAVihtcavn1^p7{{ndXw2elr|%1Mu5(u#nMOFwy^XFQ! zj+hj;=)BA}I#iY!Kd9Yi`-unxyDc_a%yA*Jh7W0MsZXqe7 z^0Cp!^V-k=?yc>B8>cuP$}LPEip@`Pf?MJIEx(ev(WW1@Lu}T@3B(Kp!g5Rs!h1~C zCFM&xqtB^?EN#X-skpKJu&$jitWu7lYIy3yp@pG@61q0 zvhXIJN%VvGZ5b22+Rg2wLUikjqzW&0)>5eWZQkv?=XckfTFs{DLsCS~`Z~D65=;G)E^j{MK9SfurlP)iN)ckc7#NOmf63SafLSuc z)9_?)6Sy}~sr>nu3={tMljQdU?GLyq19*z8%>{Uz^zc0NH^4Mn&A-9m(ys?6Ep#rA z)jUS}JV0aWxKRpn?=~#^-1!SWF)2En>?EUv9#xDRF6uhCk2%QJn6JGE3$hRPk{+F3 z+qMl0U$7v4lHE5jSO)~<#iet0N^;isPby1}Zz+3t%6fSRWDq2}2fW%@G;(o#=qRP~ zdxIYs+wXG{d((&G8#;IJ>3pn)Q^o4fd*C{1q9g4)ttZ|e>JGlv=_&*Sj+fipFBuzV z5c`>+NDsEdJU@3DGQwS+(C63%Bm%d1!I#vEb^sf};sW__0W){}Q3i>-h}=;_?BdsM=-zxd>{ zGq7zS|4V0A#tEJD42+o5DqBoyNraDHjN6V!mss1(Ly zaijKen@*@1ketBGf^L}D|0SS`ftbW|dUsAavt)X2X8xl-=)*nk{;_yKV85XRj19+U&rX#?zP}zIDL~) z5)@Q7V*d=HzZGQGm6TzD)KkD(uzEDSRLW2_>O#u1u53?4 zh@2-aoxkp^mlYe*u=Z=LoEfTUVQJjG_tWGhCqpFbrfJ977=H1SmJIeYLdhX)lWuCY z%_92!Vq(~!hGtv8hlS-5XcW>DJuUHGO@%?|B`D!r2o)yY?D$!M9y#RNT(k*?;> zR{H+Z%G1)Nxx>RW)DnU?+8%EvX`5dvUYM~kIY!~L%P22)X9es7jYN<@(1dSGH)~*? zl2DuV!q0_0g6GXnuMeYWe+`^fkoSWgot7>I$eknvKf~WiH0w%|_|YvDw%pP!LY3J6 z#a<88HN9UPE6CcgI>IPI;SUrWA8bmYICXP>Fg@8Oza@E}LG8JTcXxwmQ6>4>L^M}8 zbFLuTNUte(~TV z44`@FW+rEj=#z0T&IC2_xayBA_SK@E`7%K@{HbQ-N?0Yt%&{^{ z6WI~$t$@B(U-1wsC1Q-V=;hM%_f;fI=N<<|)?U9;#UGuPGON@uqby%Eye5HqttaFz z2TaEa+V$udQtH;bWoVPn3as{px5sCOk%5Mtk5q1vk{m+ez!q6Q6i`c zjE6gw*`Wl=pMSOaFv6e>7q+t+;A?qa)4SB0{>8N(H)p^{Tpr*^U+mt7gqANX=6mW)FWwHl>6zac z?6k4}R1urJoxycUTXGkEtu^SI*JyP6e}p0+19L@76bu zEqynO53|o@ZmMRfqrL6aW4GyApdrrW15vm+r^O)H`IcKwDfYogb@6&)S_wq$E8n;nX?XXZNgb@$JVQ&h2vi zl<|GwZBNs+Aky(xi?E0v4V)iW11w)Fd@iReru3a*%Pr6-W|GI!eE4>bePwm?(T>KF z7+qHWO#$BV95{ht6D57BfQW?6MxOc|f;jPsnqJ`hqrRjcpMS49x2CzDSfiiuaISq- zZrcss>Q^EyU(#TErtkWi>*0eyiGZb+xV6CZVOsfQA!?8kr7{5qaSzJV4-51PF9-C z^wOxRW07Zm(CJ&FTRQIkv_<3NpJt&T&MN+@iTUlwAxK6+?=M^SxC)!fif_)wx~{5C z%iGH@r?+1_rPbu*6pEXTe;JAxcp=D~`VzsZiUEvGCVKH{?FN zp3m1&J5E`YuEZ{VT&s5VF@vJjZb@0$gVpa1bqa{R5{{$SYf(M2^{bO)20mXZ(-VrI zU)p602Ey^j1U%0w-OBB8?bp(|(${GqJG})s2wlsR)CnsDjIR8BRJsm>p>8U5Ly@lI ztTkrePW9&@OiUwd+mVjMs(Rxb72(3^UVgHP-)~v>$qz^Ntuns_FZ%l#8U|PADH0{L zKvOR4}U%gWAQAyR|>r)pJgkzrX(rAcDXfWrYxypO|xH2 z3@T8@h_fe_(3#>&k!q+jS}wsvk?q)h%Scvks$XCjO55dBh>i%=Y&3W}rt++im*`1; zNVm|9osVTzHNJLC=vHAaUX7lzV%$pZVEHvIwJEbTe%d4x_2!3$&%a|o=OL|e_YrkTh+v}SgUZMHyTw#8HD6NnfS42jLaXimL zpt8^+0z9stRq6*;DiKrYd-24MQOQg1%(ets%(ftwznb#0OG=?ZcN9bQm8pVVS^9GO ziOYQBL>m45C$7JfWVK0;ICT`KN%`WZ=3O!1z&i6yD(Bj&hggYY;De4ThCCV;Zz;Xa z^?XB{xn>X4QjfgbC!xvS$78c2P;}RtGD=aJ9egM93D(WU z4Qv-NTVI@dYUd>b-RL@w@SW0Fel8o}6LsNq_x!oDsTYW1Etb#U6r#k!qCm~ZbzP6< zvs)-?io-d2tILzR9J^)Sm;7ruL&-75o1TeL@XONNRS&`|1 za+2>VER!-3du)Fxdv!-FLneBDb;Vpw9qM*Z$?fs{n6?GLnEwCr@PKC zR*fHTN)oD678qz-XsxH#rPX=hbb$9C0&%PT5wfpRdhso>T+C*F{<-h}d_**?X)ZG1 zpDg+Ru#4aspLkhRYx^Ajapb_5-LIfGiwiSVwmpnbI~^flvPW!q0t^q1ysF|{$_xTF z%+idl#g?+1*WkB|naa#S*_}LsFKUE!AMZPC#Uw>U;85{x0`62*^({Iw1Zk;U#SV6G0?);dc3dD_6as;ozVV75conElT@AM3ZhFF8>_#y8p_4qD6*lP5 zM3mFBrsOxSIJ2KXdS3)59qXX%3-^BvZ}~lr4*Hdp9lyC27Iu6?r4$5B$Y@>aaLR7& zIR5%Ga093I>_ajoBmb}=e_=m>ASj`CvIx7wDKo3X#U_TjFV|*;g}p6Xb&$YK;Fs`g z9`ot)D3X)aP&LJfPkDkEyd0Q%TM^w1$D4T7;&l^4U(!!ipVe-AXNc1Umf3xTJ7M?x zdvX7amOkgD3*k@!4_KQI(RbLHK~yF1z6;tNdGcqJmyi9k=!;&~XFQP>*2f-4nmJ-;97+h9z&G0@AQ zPx-v!2FZgtVVI{WM9iELexG62d!jQCHu?U8g{URPGXd`dHjHi_I$;>)K3DzX0Gmjh zJn@&KAMd`L+V`FP63mlnQ78@P5=&@>ic@(Dky|ehy`b6h>I(I~t($#9+AT7=t39nC z@&=+|03dKvFq|N$7Oj;WqSs6}uVvt!t`=->p&nc%>`NLD%eoV(*o?0wXg4KkPx^c8 z)WUHe$#&+ucYD-aqb=iU6}SAr9EKTZiSpy*BD~>DH<=L{yr^MQ(qZOYV9Q&K^G9S6 zsj{6F6Tn_sKHNYSO@Wqsbi&Y2#_;TR= z?>pDaf%vO){_BnH<;eWi-v2{F|9bWN&wqLSuim#p;P8LFZZ(SUaOwgOh5vd$FbBA= zY?{nzGOb9GQRD~|d!xml1W0Gt-|zC7GJ8z2+J#66{Jpd1rrcNb`=Iu>>a-S0gMC*o z=Urm@YU6Vi5>D=b;|8zaulkGv13Zt`1ry6|-Ff%rbJp*;j(S=iXuz%A_k)_Qt1`3r zH&nsqvK%K|wp_Q}KO4dcK*V~UVi^7LdqFp;%<0u{Kxdx*r6k*r1 zb?Cc(_v`KzRm`hz$g%z4zNnbrKGbKw%%Qy+aD(17JoUfJ@1nr|WVVEvJNjbs22e{^ zO;=opOG^tR=?ns0PUw(AyIZ)~x_CId0WM~3XaKdGwViA){*?}JSqK{c%u2a9{o>WP zo4bdkjhPz&3{*0^T1Ecz_T1eN0Ea>W5HLRg4gp_+A@~6h2p<3eLI7ZJ2!Ia;1wi?b zm%oQXkN_Az{PK7x3`PKhAOJ8ZpAi92+R4J%($>iu0J)raa{c!HVtNetUk}^A1^_N+ z&Rm^$dB4{iy0%W@PVTnX+fuexR%kc0lLgw{kN^y{1|xyiP#8bZ8j6Ggts#66pfwbV z09r$V)({xbnjZ$VM(_cxVIUCD8Unvq17EE1T^xeAIF}y*w1yxd1VC#TjL+y_*Y~dy z_}@RnP=5Y@Kf{UQu?1p8(g&Hrv~Lk=$#Oi-Ey&jIpKPpn#Uk_HF=549jx;eI`$3}g zg`Sh3d*dLFCk~npJe!*yGafgblpS~(f3mG=TDJf;2v1a@9rdPdIS$(KC)}u;mN$Qc zo!Z-*rl*4iWY6e;dz+fEFT2z=D>YjkElQ7H28=I<$(bzcXSArC3$LlDyJqJo>SK~? zMCD`bwf3lB(g?)0;*6;>;ND_ivi%rD?$=MF)?FS;qedf?4a?OMiltE#cpzjihEGdz zi@m_>V^D(63mUa_;XxX;vb$4lVHP7ZrtauNs{^z<`tZK)#d^`#5fpXX6w>@+y+HP2 z@2|9hy7_Yt^L2Qo&VHqD9{=PMPAfXFwh#xck%5{Z6~;>!566^AwX-$MP0tE&`=lxXbpqI zFDfp??yW2WhJD~q{N&mB~E}qxF4aViBikTz&f7Px?NI3f#f&e^V zBp(0?hXN3MAft;m+}a%gBLIrKTU?B$BKSZAKuI%~KSQpUn-}R{?7KLV04VQa=3r|f zesNs@5CKrr1MR2-0D~a}z>8)`6YatNPXxaR)tAxvpK`p2;{Sx`t7v}t99+ihi-Nx_ zoZzJH=TKaAt@u4HT-&`<#6e+(&KZvV;7|BmPc|5*x>SEIsE1cL8hA-+}5 z)`4#2!E*1I8#!0)*YHZzs3#_+4F0G%{#;{{H9)it;uz6MFn= zNbK-klrT^AkF76$4_m#@e|s|+1_qdn24p!Q8?C=gkeqanw(o8H5;{1RjXhsb3@?y1 z0nrIK@wx3vcf~Ks-g<1p=fLOo_1VIcg-!LYDA`**#vnRjpJA6nlHr|uyC*k&*QtK* zQN~ddGm3s58>!9$5puh6+W+WByWh*>8?rU9E(rI42Hm^(8@O0R5~4(6bc;fR zi9!_l5+2XQAIG>bHJ;X0%%ZUH04FSz;bx@4f^40#5({cr{*SSYzp@%HQ&4?ldqhBy zdn4jDpVEA%!?Rm8_wK)aF%)PZN}4a(>FZw?>l7KzTT7*n*jDH+I7vLNay^SRuYMI%Dp~Qm1jD|$qKd4xUA|5 zrG(WJc{^Dv5VXagfM~;exAapM^kNG!x-^1rU^R_?2CztCscx0j2+=NgmK2z=n;NP% zy#d*=*rEBNX{gE7*;qsfv_)|8)o>uB&3J2AYxiia0BhL$o2w}vhC2;9d{Q$fkHk!a zA=~#&DKYH;3f~rpUPL|6jw$7@>75Wu(HWr)z7-2I@Nr+~;8A+x#;z3F1?+q)N|$>c{t?TcNbV@uDCo>Gp|Mq#cPS}}x zCWh^`^Bd7eZ01>ogQacyqmi~#jmxioyi%SE5e;YLI!HXwIEb`|rq-nODb`I8P7wZj zCo=y9yZnsnQ@I5fCKvr=_qIxG%ma!}GKL2JT`tFEDaUSgeYHCd%sq`2hCDNIP5}jb z{aNn1g2jSGL-&Vr?GCWQ9l9FTM?=jz=U2O51si#=ZG`^lifS- zqStvmCT@0PzpLqqig#7H-@{=Wa60aNUY{1>tgqBWA{O8$nyc#q` zhy0p6pCo=ek4RMfy=OF^Bg9B6iU@F7SpLwENLZzOIx^O@)H9E4Fh3C(CkP}zA?%6E z6os|TxVIkI9e*b2bocj_1pu$768ZO0Prwp#xFhOf!3x+KXRoU^x_alRzdSVUXqTwijxrU` z)V3a2B}-rjR6_~#2-!Qp z?q;j$Qv^;lM)|}-z2NJ{TsWUhoH*fz4$f#%QYlEM#!P!A+J6vU6}B!F7yID&^V{eK z^U`!eN%jVjBS#;Zb%~5Ks|xLPg0r37ozXSu14kFDIp)9=WY%1XdzD{SJV(mkFX|>(1H~&4}0jErE&j;v_*5F zGu}Ex)V!qb=zN}TZX>n09Lfq#sYN^LTaV~&Y0KUE1g~Kz@3CK1D1DMlFgm3ep_n-F z9L|FEfOqKbGw!S+sAWR5rmIGwT7Qz*FDfa9ygMJtKZ(dmRAj|8+TD`jL#t1aDo|(R zNbv!1S*td*K&g;>hOFfd#3Va1rdAbi`D{siA37CI_w zYu`*38;`3L(>k%<{xcm*tq_Yyk+1^w|RlX#&jmhowL z^vhE0X6+8Et7|ku&;YL&!Y(vkZwSpzUH}o93_Q_yH_!va@jEW~b85P^JIzN1j%}el zcdD$M-hGLfB?}DI`_*(VLqhpXKVL@6pVj(P8hs8-T6si#;pP|2->YU1S!{^)PIcOa zgAVzra^)E$yCbY048#MRgI_>`lWwq-*xVI(9%?C`LLr?Uy*!?2pwXxPB7f|wZ7FYm zXz`JsY0T@5=8fzucEcj2G>itePdr|4yf!*y`u4M9QVp6+#&R$zpAH~MWpc)amp%AT zmIAook)g{TYn{=qR)^^;{$~O&$qhMq?&>aARhq4b;Jtc`2sldA`Hq1RYwDWv+%{dJ zY;ayFXsc#=>n3m|%-DxvAD_8qVbW&WxRXv=l|HA3@;imk?U|Q#r&@%>@9_$UA+*PI z{U^i=`4t$CsKxcg!u$vxtO5OF!aZtg$=5c!!K@6 zOfT{E$zizE#)9Q3OniYQgjpV6RBxn9H@_H)Id#UrLKC+va)+d+%Z?>7mYl3hLyssz z=r_xgmpC@WWTQbR)Zr002`?+G5eXG8(H_UHj6aM0h4$z37}_`*?M1+|hBVnU={3A9 zDVgPm^a0e|QdZCPOUv$ze2`Cc)6ifhkAvU*w6QQ)ZNnl9dr8Ny_+n~|f6c3xIPWXPC z?K|x|eOo^H*8DAbwV~7CtnoL-`MsC)FGT|UACBn#DnFoTA-5*~vCZK_Uf1aG>(wO9 z@3;HvD;0x=RVi_sM!B?%9wY+Fi1G)YH|Vx$PW&wyXQCWY=O(|{9bdK2Z0Ec>G2!N* znJ6gSgC6)|c5IC;FI31Hz~wRSumoAPk|ny(74tCAByQ5|Eyn*pt$lY?Q(e<9O`1}r z*HAQol!Oo<^xmX{6hSnQP(uqKMUWy$2bJCtlrGXi>C%xdO_1IM1Ox%;+?71t=e^&3 z*ScT+n6=OBnLTr6pR=>(xAvYRC(dxF>I?UuYSiLI=UwrKe%0e(iLPSe--{}2(Ah^ z^z-8M5%d(CxW{~#?QYeq`;2R2>|Cg9$Yu89_*3|y#n$TC;+Mshsp!7weaYdUUL}M@ z<|#$G$~kH@&$?fo+4*zwTqhOOm5iga*JQ5XTpkFRgzo5E=T_VkCk~rU7FyNR= z48eju*h>s*gMoQ4Adoo5p`}(r;k&17nAvOgMkl&6j#@q8MOx-p$uxo)R( zB`}!B_egN(hs@5826o3Z(YcBq>q_{b?B&qXb|{jo{=t=YO{@xSMJD2A>#=siXNov2*&qB#<^z7;9LI2%b6c>@9 z55GPBHXSRwI|>9}zUkUYql5^WXZiGIjA)d!pvvw0Zr)+O+3(z{}}{gWOWq zY0p+qhK5b}#No_$UmAr=bkTp-$pwdbs*q3k}kwGKZ^Grn6N#ok+Oh6DN< znJr`+J{^3o3GL5%_wbvq?Wa7g`}I3Db8~yJX@Od&(n`rP9lzC26lbkF(q5+%+jT!m z>ob0SF>5}kB+I(|K;P_snbf>DrX||hIVH~QtSH)gbBf#S%gt$;H2Y5|60?!KY5kh2 z{xT%W)NCNJmTZc&Cg%-;xq@uEw}xuE;Eg47W%Tq-R9p@3bd_VLX#M&oFwsX*x zOX1&Vj9FN?HCTq)Iw)ozuOv{_zoz&c6$PB+VOHRf3GgkTxT2><99}1<1`bc;NePFL z7Q?G2jAal=io|K96h^PGz9vxQcY#@`N@Oh~=nUS8 zlkMbjt5_CIjmi13B<&YfQ9pZ7@(PupWNM^zXQX7|=`;FD^9Y>}F0`vJOjD>#!{xuX zJUbs)i5vU*QCkU3(^Y=0O`pI2g>P`%F(oNLmXyo9MOh*H!7<1&ofdYbaB`zDW-~Hj zsp6GdYW8gjjjOQa{(z1=6WUe%NpY@nPOj9-d-W6G$+F|9>(IEFR$ZRun`XguO=e__ z99*J7tHUVEQIK$Ffw0hk8!aEJsDizi1YFSVpm$>+ElItcAhu1ot+`y&?Ik~doLJI3 zsb#sO$t#w^qA{_Ap+Sm%raDu(^W z!(M@UF@ctBvn9@D-i08_n}C_fqQ2jCeJp9vg30gQS%s^*Z(MzF*82VLTAm5hy}%#+ zC-6o+Z(Qk+HyLipX0z5#jC%ZC`eppH=0@?6d*XNj94yLLEd4&>p-ewdq7Ov7TK$O9 z?z``J?vQQC&nXV(QzSBf?8?{9kgWdt=*Iri_t+k>Vacyg+I1>rdm8mFhL>R|pWVLE zkmr%FZxzGA9;muJ%@SXS7^kGyUj8FDE!=hd+qgNmm%7XN%B zk>1@#*v%8&m7E+fwp~u~sA4-<@-w4sh{RBniB)!oe@XtEPcsWLvz{WOG`8G7!v#z4 zIDImM>b#eIqWqy;mtXB91&2wFtmS;BSHZNs%^NCN5zY_J{E<9h)Lv8Asy|m#LVPoRDIed*z zU71ldhkWCTw9T^HXLG@O#EFc!vv{1eam?j*E`D59mb@FoN-6u-b@*) zNiWvWHf}bdc|g8NC1hz`vZ?R@P>_W7aWJ!W=sRlZ?gwLf^#cy^K_BauRb|wYQ!Uyu zzIt4S)TGoD;veU?lb1$rhab+L%lrG6$u4sF5L3)VOW6+2zbqVIAHRAraRn8@9!s&_ z)+5g)nf!56`ArGgZKPo!#aXN|h2*er*@(pk4~teKVC;i^qjSqns1&(Mn^H2d9$d8j zX&tfL50}F8bkCmCn->sw5`p&o7oWn1-NB)>kk1W9qY=B*d~f0gGsx2Xhs#VYj+3+v zi?904G=6N{oXpzsgYV1&Q=2~@B%e}nRreDYM^ABFfBlS2g;Df-)v*w&SHsGN`IzBg|-`C6{G zTQ2pvtu(gHsXJaTpIVye-pHE1O!XH!wqLJqy&9KKzedj|%j8@9J@F9t#4OVH!VCHC z!~y6->fbv3)$yD7_?Ov{heJPt{If0@(G(I9G3r2S;Hc$?of||s)x80P7qoJb4=3wIB ztU8rd@2gl|RGGCSadsFKc`fgiase6WhK=^;vUiJeT;V~_dtae7wA*DWlIr9IKd$mT z8MpG`4|iBlQX}iVSdlE1He#<85-X7@qM?QfG(zFr`v`>z_ZzHY+={^qV_FY0Ztsy8 zS&4K;J=5$~Vxi~c34)FuN^YB*-nAR>OAIMkOv>BhT*Ylin3gxXozy=2@S-eUe^^;M2W@blD{+=zfvd%6L8X zcD&@+>-tYlxgWpwA13;S$iYPDN}oY&akUB!I;+5@1yPCRy+iH`n%1p#*^PXO@8C{h z0tU)?a%3-Dyvm+&zZT}Z!W0H=yQVxATp&k!Q;mdYTrYE8KDL}jwfx#gd=+n%?4yEU zhdQPaLh8wYRRig}u^Tb9kxr#a>#t9{FH9IHlyf+Q

#Me|o^zjBaqI&)gO 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 I>!^_aAD`SYbpQYW literal 0 HcmV?d00001 From 3188250634abaaa825a5066dadca91db92c343c9 Mon Sep 17 00:00:00 2001 From: Jean Cyr Date: Mon, 8 Jul 2013 00:13:17 -0400 Subject: [PATCH 13/26] Fix documentation fonts --- Documentation/dsm_bind.odt | Bin 26760 -> 27043 bytes Documentation/dsm_bind.pdf | Bin 30593 -> 34300 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Documentation/dsm_bind.odt b/Documentation/dsm_bind.odt index 793980d4cedd8fa4b244f68ac98f3055b3e54b18..66ea1f1bee22eba9943f6077e7fcec6c6ef6e182 100644 GIT binary patch delta 5297 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 F`ad5*$V&hK diff --git a/Documentation/dsm_bind.pdf b/Documentation/dsm_bind.pdf index daaad7b965109498124b0887502d665ab64b38ac..e62d1ed830316bd893f05c269a7f97384f94c502 100644 GIT binary patch delta 22349 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 Date: Tue, 9 Jul 2013 14:25:47 +0800 Subject: [PATCH 14/26] Ammended UBlox driver to record SV Info, satelites_visible == satelites used. Info is recorded for all SVs, used or not. Might be useful for GPS debugging. --- src/drivers/gps/ubx.cpp | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/drivers/gps/ubx.cpp b/src/drivers/gps/ubx.cpp index f2e7ca67dc..b136dfc0ad 100644 --- a/src/drivers/gps/ubx.cpp +++ b/src/drivers/gps/ubx.cpp @@ -196,7 +196,7 @@ UBX::configure(unsigned &baudrate) // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) // continue; configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_SVINFO, - 0); + 1); // /* insist of receiving the ACK for this packet */ // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) // continue; @@ -539,7 +539,7 @@ UBX::handle_message() } case NAV_SVINFO: { -// printf("GOT NAV_SVINFO MESSAGE\n"); + // printf("GOT NAV_SVINFO MESSAGE\n"); if (!_waiting_for_ack) { //this is a more complicated message: the length depends on the number of satellites. This number is extracted from the first part of the message @@ -560,7 +560,7 @@ UBX::handle_message() uint8_t satellites_used = 0; int i; - + // printf("Number of Channels: %d\n", packet_part1->numCh); for (i = 0; i < packet_part1->numCh; i++) { //for each channel /* Get satellite information from the buffer */ @@ -572,27 +572,22 @@ UBX::handle_message() _gps_position->satellite_prn[i] = packet_part2->svid; //if satellite information is healthy store the data - uint8_t unhealthy = packet_part2->flags & 1 << 4; //flags is a bitfield + //DT uint8_t unhealthy = packet_part2->flags & 1 << 4; //flags is a bitfield + //DT Above is broken due to operator precedence. should be ... & (1<<4) or ... & 0x10. + //DT If an SV is unhealthy then it won't be used. + + uint8_t sv_used = packet_part2->flags & 0x01; - if (!unhealthy) { - if ((packet_part2->flags) & 1) { //flags is a bitfield - _gps_position->satellite_used[i] = 1; - satellites_used++; - - } else { - _gps_position->satellite_used[i] = 0; - } - - _gps_position->satellite_snr[i] = packet_part2->cno; - _gps_position->satellite_elevation[i] = (uint8_t)(packet_part2->elev); - _gps_position->satellite_azimuth[i] = (uint8_t)((float)packet_part2->azim * 255.0f / 360.0f); - - } else { - _gps_position->satellite_used[i] = 0; - _gps_position->satellite_snr[i] = 0; - _gps_position->satellite_elevation[i] = 0; - _gps_position->satellite_azimuth[i] = 0; + if (sv_used) { + // Update count of SVs used for NAV. + satellites_used++; } + + // Record info for all used channels, whether or not the SV is used for NAV, + _gps_position->satellite_used[i] = sv_used; + _gps_position->satellite_snr[i] = packet_part2->cno; + _gps_position->satellite_elevation[i] = (uint8_t)(packet_part2->elev); + _gps_position->satellite_azimuth[i] = (uint8_t)((float)packet_part2->azim * 255.0f / 360.0f); } From dc2ef7b3c6e1ee90ba2130ed0574987d5c99a35b Mon Sep 17 00:00:00 2001 From: Darryl Taylor Date: Tue, 9 Jul 2013 17:07:11 +0800 Subject: [PATCH 15/26] Some cleanup of NAV_SVINFO message handler --- src/drivers/gps/ubx.cpp | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/drivers/gps/ubx.cpp b/src/drivers/gps/ubx.cpp index b136dfc0ad..ff8945da11 100644 --- a/src/drivers/gps/ubx.cpp +++ b/src/drivers/gps/ubx.cpp @@ -567,28 +567,20 @@ UBX::handle_message() memcpy(_rx_buffer_part2, &(_rx_buffer[length_part1 + i * length_part2]), length_part2); packet_part2 = (gps_bin_nav_svinfo_part2_packet_t *) _rx_buffer_part2; + /* Write satellite information to global storage */ + uint8_t sv_used = packet_part2->flags & 0x01; - /* Write satellite information in the global storage */ - _gps_position->satellite_prn[i] = packet_part2->svid; - - //if satellite information is healthy store the data - //DT uint8_t unhealthy = packet_part2->flags & 1 << 4; //flags is a bitfield - //DT Above is broken due to operator precedence. should be ... & (1<<4) or ... & 0x10. - //DT If an SV is unhealthy then it won't be used. - - uint8_t sv_used = packet_part2->flags & 0x01; - - if (sv_used) { - // Update count of SVs used for NAV. + if ( sv_used ) { + // Count SVs used for NAV. satellites_used++; } - // Record info for all used channels, whether or not the SV is used for NAV, + // Record info for all channels, whether or not the SV is used for NAV. _gps_position->satellite_used[i] = sv_used; _gps_position->satellite_snr[i] = packet_part2->cno; _gps_position->satellite_elevation[i] = (uint8_t)(packet_part2->elev); _gps_position->satellite_azimuth[i] = (uint8_t)((float)packet_part2->azim * 255.0f / 360.0f); - + _gps_position->satellite_prn[i] = packet_part2->svid; } for (i = packet_part1->numCh; i < 20; i++) { //these channels are unused From e3bb9e87e2af2f0f70e486405e2c6df5d3e23667 Mon Sep 17 00:00:00 2001 From: Lorenz Meier Date: Tue, 9 Jul 2013 17:36:24 +0200 Subject: [PATCH 16/26] Hotfix for GPS: Disable unknown message classes --- src/drivers/gps/ubx.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/drivers/gps/ubx.cpp b/src/drivers/gps/ubx.cpp index f2e7ca67dc..b460890734 100644 --- a/src/drivers/gps/ubx.cpp +++ b/src/drivers/gps/ubx.cpp @@ -176,7 +176,7 @@ UBX::configure(unsigned &baudrate) // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) // continue; configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_TIMEUTC, - 1); + 0); // /* insist of receiving the ACK for this packet */ // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) // continue; @@ -327,6 +327,7 @@ UBX::parse_char(uint8_t b) } break; case UBX_DECODE_GOT_CLASS: + { add_byte_to_checksum(b); switch (_message_class) { case NAV: @@ -413,6 +414,14 @@ UBX::parse_char(uint8_t b) // config_needed = true; break; } + // Evaluate state machine - if the state changed, + // the state machine was reset via decode_init() + // and we want to tell the module to stop sending this message + + // disable unknown message + warnx("disabled class %d, msg %d", (int)_message_class, (int)b); + configure_message_rate(_message_class, b, 0); + } break; case UBX_DECODE_GOT_MESSAGEID: add_byte_to_checksum(b); From c7f372916c5f74a3f9500a87d757abbaec3e1cc7 Mon Sep 17 00:00:00 2001 From: Lorenz Meier Date: Tue, 9 Jul 2013 18:25:42 +0200 Subject: [PATCH 17/26] Hotfix: Updated a MAVLink header --- mavlink/include/mavlink/v1.0/mavlink_conversions.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mavlink/include/mavlink/v1.0/mavlink_conversions.h b/mavlink/include/mavlink/v1.0/mavlink_conversions.h index 1dc9088b7a..d893635770 100644 --- a/mavlink/include/mavlink/v1.0/mavlink_conversions.h +++ b/mavlink/include/mavlink/v1.0/mavlink_conversions.h @@ -1,6 +1,12 @@ #ifndef _MAVLINK_CONVERSIONS_H_ #define _MAVLINK_CONVERSIONS_H_ +/* enable math defines on Windows */ +#ifdef _MSC_VER +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif +#endif #include /** From 6cbbb9b99fbeddc2da00f67bb209d33971a30041 Mon Sep 17 00:00:00 2001 From: Lorenz Meier Date: Tue, 9 Jul 2013 22:46:24 +0200 Subject: [PATCH 18/26] Hotfix for GPS driver --- src/drivers/gps/ubx.cpp | 51 +++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/drivers/gps/ubx.cpp b/src/drivers/gps/ubx.cpp index b460890734..3e790499be 100644 --- a/src/drivers/gps/ubx.cpp +++ b/src/drivers/gps/ubx.cpp @@ -176,17 +176,17 @@ UBX::configure(unsigned &baudrate) // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) // continue; configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_TIMEUTC, - 0); + UBX_CFG_MSG_PAYLOAD_RATE1_1HZ); // /* insist of receiving the ACK for this packet */ // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) // continue; configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_SOL, - 1); + UBX_CFG_MSG_PAYLOAD_RATE1_5HZ); // /* insist of receiving the ACK for this packet */ // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) // continue; configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_VELNED, - 1); + UBX_CFG_MSG_PAYLOAD_RATE1_5HZ); // /* insist of receiving the ACK for this packet */ // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) // continue; @@ -196,7 +196,7 @@ UBX::configure(unsigned &baudrate) // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) // continue; configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_SVINFO, - 0); + UBX_CFG_MSG_PAYLOAD_RATE1_05HZ); // /* insist of receiving the ACK for this packet */ // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) // continue; @@ -224,35 +224,18 @@ UBX::receive(unsigned timeout) fds[0].fd = _fd; fds[0].events = POLLIN; - uint8_t buf[32]; + uint8_t buf[128]; /* timeout additional to poll */ uint64_t time_started = hrt_absolute_time(); - int j = 0; ssize_t count = 0; + bool message_found = false; + while (true) { - /* pass received bytes to the packet decoder */ - while (j < count) { - if (parse_char(buf[j]) > 0) { - /* return to configure during configuration or to the gps driver during normal work - * if a packet has arrived */ - if (handle_message() > 0) - return 1; - } - /* in case we keep trying but only get crap from GPS */ - if (time_started + timeout*1000 < hrt_absolute_time() ) { - return -1; - } - j++; - } - - /* everything is read */ - j = count = 0; - - /* then poll for new data */ + /* poll for new data */ int ret = ::poll(fds, sizeof(fds) / sizeof(fds[0]), timeout); if (ret < 0) { @@ -272,8 +255,26 @@ UBX::receive(unsigned timeout) * available, we'll go back to poll() again... */ count = ::read(_fd, buf, sizeof(buf)); + /* pass received bytes to the packet decoder */ + for (int i = 0; i < count; i++) { + if (parse_char(buf[i])) { + /* return to configure during configuration or to the gps driver during normal work + * if a packet has arrived */ + handle_message(); + message_found = true; + } + } } } + + /* return success after receiving a packet */ + if (message_found) + return 1; + + /* abort after timeout if no packet parsed successfully */ + if (time_started + timeout*1000 < hrt_absolute_time() ) { + return -1; + } } } From c3d07030dd1882c626ed027cfc5870f42b1cd33e Mon Sep 17 00:00:00 2001 From: Lorenz Meier Date: Tue, 9 Jul 2013 22:56:02 +0200 Subject: [PATCH 19/26] Minor additions to fix, pushing --- src/drivers/gps/ubx.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/drivers/gps/ubx.cpp b/src/drivers/gps/ubx.cpp index 3e790499be..ad219cd259 100644 --- a/src/drivers/gps/ubx.cpp +++ b/src/drivers/gps/ubx.cpp @@ -231,7 +231,7 @@ UBX::receive(unsigned timeout) ssize_t count = 0; - bool message_found = false; + bool position_updated = false; while (true) { @@ -260,15 +260,15 @@ UBX::receive(unsigned timeout) if (parse_char(buf[i])) { /* return to configure during configuration or to the gps driver during normal work * if a packet has arrived */ - handle_message(); - message_found = true; + if (handle_message()) + position_updated = true; } } } } /* return success after receiving a packet */ - if (message_found) + if (position_updated) return 1; /* abort after timeout if no packet parsed successfully */ @@ -420,8 +420,8 @@ UBX::parse_char(uint8_t b) // and we want to tell the module to stop sending this message // disable unknown message - warnx("disabled class %d, msg %d", (int)_message_class, (int)b); - configure_message_rate(_message_class, b, 0); + //warnx("disabled class %d, msg %d", (int)_message_class, (int)b); + //configure_message_rate(_message_class, b, 0); } break; case UBX_DECODE_GOT_MESSAGEID: From 897b541b12d5782af51ce0a78658cc153bd13544 Mon Sep 17 00:00:00 2001 From: Jean Cyr Date: Tue, 9 Jul 2013 20:37:00 -0400 Subject: [PATCH 20/26] General cleanup of /dev/px4io and /dev/px4fmu - Use distinct common symbols for px4io and px4fmu device files, and use instead of hardcoded filenames - Use common symbols defining px4io bits consistently between px4fmu and px4io builds. --- .../ardrone_interface/ardrone_motor_control.c | 2 +- src/drivers/drv_gpio.h | 37 ++----------------- src/drivers/px4fmu/fmu.cpp | 2 +- src/drivers/px4io/px4io.cpp | 10 ++--- src/modules/gpio_led/gpio_led.c | 13 ++----- src/modules/px4iofirmware/protocol.h | 7 ++++ src/modules/px4iofirmware/registers.c | 8 ++-- src/systemcmds/tests/test_gpio.c | 2 +- 8 files changed, 26 insertions(+), 55 deletions(-) diff --git a/src/drivers/ardrone_interface/ardrone_motor_control.c b/src/drivers/ardrone_interface/ardrone_motor_control.c index ecd31a073d..be8968a4ea 100644 --- a/src/drivers/ardrone_interface/ardrone_motor_control.c +++ b/src/drivers/ardrone_interface/ardrone_motor_control.c @@ -109,7 +109,7 @@ int ar_multiplexing_init() { int fd; - fd = open(GPIO_DEVICE_PATH, 0); + fd = open(PX4FMU_DEVICE_PATH, 0); if (fd < 0) { warn("GPIO: open fail"); diff --git a/src/drivers/drv_gpio.h b/src/drivers/drv_gpio.h index 7e3998fca1..510983d4bd 100644 --- a/src/drivers/drv_gpio.h +++ b/src/drivers/drv_gpio.h @@ -63,43 +63,12 @@ * they also export GPIO-like things. This is always the GPIOs on the * main board. */ -# define GPIO_DEVICE_PATH "/dev/px4fmu" +# define PX4FMU_DEVICE_PATH "/dev/px4fmu" +# define PX4IO_DEVICE_PATH "/dev/px4io" #endif -#ifdef CONFIG_ARCH_BOARD_PX4IO_V1 -/* - * PX4IO GPIO numbers. - * - * XXX note that these are here for reference/future use; currently - * there is no good way to wire these up without a common STM32 GPIO - * driver, which isn't implemented yet. - */ -/* outputs */ -# define GPIO_ACC1_POWER (1<<0) /**< accessory power 1 */ -# define GPIO_ACC2_POWER (1<<1) /**< accessory power 2 */ -# define GPIO_SERVO_POWER (1<<2) /**< servo power */ -# define GPIO_RELAY1 (1<<3) /**< relay 1 */ -# define GPIO_RELAY2 (1<<4) /**< relay 2 */ -# define GPIO_LED_BLUE (1<<5) /**< blue LED */ -# define GPIO_LED_AMBER (1<<6) /**< amber/red LED */ -# define GPIO_LED_SAFETY (1<<7) /**< safety LED */ - -/* inputs */ -# define GPIO_ACC_OVERCURRENT (1<<8) /**< accessory 1/2 overcurrent detect */ -# define GPIO_SERVO_OVERCURRENT (1<<9) /**< servo overcurrent detect */ -# define GPIO_SAFETY_BUTTON (1<<10) /**< safety button pressed */ - -/** - * Default GPIO device - other devices may also support this protocol if - * they also export GPIO-like things. This is always the GPIOs on the - * main board. - */ -# define GPIO_DEVICE_PATH "/dev/px4io" - -#endif - -#ifndef GPIO_DEVICE_PATH +#ifndef PX4IO_DEVICE_PATH # error No GPIO support for this board. #endif diff --git a/src/drivers/px4fmu/fmu.cpp b/src/drivers/px4fmu/fmu.cpp index 5147ac500d..a98b527b1b 100644 --- a/src/drivers/px4fmu/fmu.cpp +++ b/src/drivers/px4fmu/fmu.cpp @@ -166,7 +166,7 @@ PX4FMU *g_fmu; } // namespace PX4FMU::PX4FMU() : - CDev("fmuservo", "/dev/px4fmu"), + CDev("fmuservo", PX4FMU_DEVICE_PATH), _mode(MODE_NONE), _pwm_default_rate(50), _pwm_alt_rate(50), diff --git a/src/drivers/px4io/px4io.cpp b/src/drivers/px4io/px4io.cpp index 1adefdea5b..08aef70692 100644 --- a/src/drivers/px4io/px4io.cpp +++ b/src/drivers/px4io/px4io.cpp @@ -329,7 +329,7 @@ PX4IO *g_dev; } PX4IO::PX4IO() : - I2C("px4io", GPIO_DEVICE_PATH, PX4_I2C_BUS_ONBOARD, PX4_I2C_OBDEV_PX4IO, 320000), + I2C("px4io", PX4IO_DEVICE_PATH, PX4_I2C_BUS_ONBOARD, PX4_I2C_OBDEV_PX4IO, 320000), _max_actuators(0), _max_controls(0), _max_rc_input(0), @@ -1507,7 +1507,7 @@ PX4IO::ioctl(file * /*filep*/, int cmd, unsigned long arg) uint32_t bits = (1 << _max_relays) - 1; /* don't touch relay1 if it's controlling RX vcc */ if (_dsm_vcc_ctl) - bits &= ~1; + bits &= ~PX4IO_RELAY1; ret = io_reg_modify(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_RELAYS, bits, 0); break; } @@ -1515,7 +1515,7 @@ PX4IO::ioctl(file * /*filep*/, int cmd, unsigned long arg) case GPIO_SET: arg &= ((1 << _max_relays) - 1); /* don't touch relay1 if it's controlling RX vcc */ - if (_dsm_vcc_ctl & (arg & 1)) + if (_dsm_vcc_ctl & (arg & PX4IO_RELAY1)) ret = -EINVAL; else ret = io_reg_modify(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_RELAYS, 0, arg); @@ -1524,7 +1524,7 @@ PX4IO::ioctl(file * /*filep*/, int cmd, unsigned long arg) case GPIO_CLEAR: arg &= ((1 << _max_relays) - 1); /* don't touch relay1 if it's controlling RX vcc */ - if (_dsm_vcc_ctl & (arg & 1)) + if (_dsm_vcc_ctl & (arg & PX4IO_RELAY1)) ret = -EINVAL; else ret = io_reg_modify(PX4IO_PAGE_SETUP, PX4IO_P_SETUP_RELAYS, arg, 0); @@ -1731,7 +1731,7 @@ test(void) int direction = 1; int ret; - fd = open(GPIO_DEVICE_PATH, O_WRONLY); + fd = open(PX4IO_DEVICE_PATH, O_WRONLY); if (fd < 0) err(1, "failed to open device"); diff --git a/src/modules/gpio_led/gpio_led.c b/src/modules/gpio_led/gpio_led.c index 8b4c0cb308..1aef739c7b 100644 --- a/src/modules/gpio_led/gpio_led.c +++ b/src/modules/gpio_led/gpio_led.c @@ -53,11 +53,7 @@ #include #include #include - -#define PX4IO_RELAY1 (1<<0) -#define PX4IO_RELAY2 (1<<1) -#define PX4IO_ACC1 (1<<2) -#define PX4IO_ACC2 (1<<3) +#include struct gpio_led_s { struct work_s work; @@ -186,10 +182,9 @@ void gpio_led_start(FAR void *arg) char *gpio_dev; if (priv->use_io) { - gpio_dev = "/dev/px4io"; - + gpio_dev = PX4IO_DEVICE_PATH; } else { - gpio_dev = "/dev/px4fmu"; + gpio_dev = PX4FMU_DEVICE_PATH; } /* open GPIO device */ @@ -203,6 +198,7 @@ void gpio_led_start(FAR void *arg) } /* configure GPIO pin */ + /* px4fmu only, px4io doesn't support GPIO_SET_OUTPUT and will ignore */ ioctl(priv->gpio_fd, GPIO_SET_OUTPUT, priv->pin); /* subscribe to vehicle status topic */ @@ -263,7 +259,6 @@ void gpio_led_cycle(FAR void *arg) if (led_state_new) { ioctl(priv->gpio_fd, GPIO_SET, priv->pin); - } else { ioctl(priv->gpio_fd, GPIO_CLEAR, priv->pin); } diff --git a/src/modules/px4iofirmware/protocol.h b/src/modules/px4iofirmware/protocol.h index 6ee5c28342..88d8cc87c1 100644 --- a/src/modules/px4iofirmware/protocol.h +++ b/src/modules/px4iofirmware/protocol.h @@ -157,6 +157,13 @@ #define PX4IO_P_SETUP_PWM_DEFAULTRATE 3 /* 'low' PWM frame output rate in Hz */ #define PX4IO_P_SETUP_PWM_ALTRATE 4 /* 'high' PWM frame output rate in Hz */ #define PX4IO_P_SETUP_RELAYS 5 /* bitmask of relay/switch outputs, 0 = off, 1 = on */ + +/* px4io relay bit definitions */ +#define PX4IO_RELAY1 (1<<0) +#define PX4IO_RELAY2 (1<<1) +#define PX4IO_ACC1 (1<<2) +#define PX4IO_ACC2 (1<<3) + #define PX4IO_P_SETUP_VBATT_SCALE 6 /* battery voltage correction factor (float) */ #define PX4IO_P_SETUP_DSM 7 /* DSM bind state */ enum { /* DSM bind states */ diff --git a/src/modules/px4iofirmware/registers.c b/src/modules/px4iofirmware/registers.c index 805eb7eccd..a922362b69 100644 --- a/src/modules/px4iofirmware/registers.c +++ b/src/modules/px4iofirmware/registers.c @@ -349,10 +349,10 @@ registers_set_one(uint8_t page, uint8_t offset, uint16_t value) case PX4IO_P_SETUP_RELAYS: value &= PX4IO_P_SETUP_RELAYS_VALID; r_setup_relays = value; - POWER_RELAY1(value & (1 << 0) ? 1 : 0); - POWER_RELAY2(value & (1 << 1) ? 1 : 0); - POWER_ACC1(value & (1 << 2) ? 1 : 0); - POWER_ACC2(value & (1 << 3) ? 1 : 0); + POWER_RELAY1(value & PX4IO_RELAY1 ? 1 : 0); + POWER_RELAY2(value & PX4IO_RELAY2 ? 1 : 0); + POWER_ACC1(value & PX4IO_ACC1 ? 1 : 0); + POWER_ACC2(value & PX4IO_ACC2 ? 1 : 0); break; case PX4IO_P_SETUP_SET_DEBUG: diff --git a/src/systemcmds/tests/test_gpio.c b/src/systemcmds/tests/test_gpio.c index ab536d9560..62a8732702 100644 --- a/src/systemcmds/tests/test_gpio.c +++ b/src/systemcmds/tests/test_gpio.c @@ -92,7 +92,7 @@ int test_gpio(int argc, char *argv[]) int fd; int ret = 0; - fd = open(GPIO_DEVICE_PATH, 0); + fd = open(PX4IO_DEVICE_PATH, 0); if (fd < 0) { printf("GPIO: open fail\n"); From 4685871c83e5175e58da08a68b49642daf79267d Mon Sep 17 00:00:00 2001 From: Anton Babushkin Date: Thu, 11 Jul 2013 15:28:56 +0400 Subject: [PATCH 21/26] Major ubx driver cleanup: few pages of code removed, send update only when full navigation solution received --- src/drivers/gps/ubx.cpp | 901 ++++++++++++++++++---------------------- src/drivers/gps/ubx.h | 61 +-- 2 files changed, 422 insertions(+), 540 deletions(-) diff --git a/src/drivers/gps/ubx.cpp b/src/drivers/gps/ubx.cpp index 762c257aaa..895d164377 100644 --- a/src/drivers/gps/ubx.cpp +++ b/src/drivers/gps/ubx.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2013 PX4 Development Team. All rights reserved. * Author: Thomas Gubler * Julian Oes + * Anton Babushkin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -56,12 +57,14 @@ #include "ubx.h" #define UBX_CONFIG_TIMEOUT 100 +#define UBX_PACKET_TIMEOUT 2 UBX::UBX(const int &fd, struct vehicle_gps_position_s *gps_position) : -_fd(fd), -_gps_position(gps_position), -_waiting_for_ack(false), -_disable_cmd_counter(0) + _fd(fd), + _gps_position(gps_position), + _configured(false), + _waiting_for_ack(false), + _disable_cmd_counter(0) { decode_init(); } @@ -73,12 +76,13 @@ UBX::~UBX() int UBX::configure(unsigned &baudrate) { - _waiting_for_ack = true; - + _configured = false; /* try different baudrates */ const unsigned baudrates_to_try[] = {9600, 38400, 19200, 57600, 115200}; - for (int baud_i = 0; baud_i < 5; baud_i++) { + int baud_i; + + for (baud_i = 0; baud_i < 5; baud_i++) { baudrate = baudrates_to_try[baud_i]; set_baudrate(_fd, baudrate); @@ -89,8 +93,8 @@ UBX::configure(unsigned &baudrate) /* Set everything else of the packet to 0, otherwise the module wont accept it */ memset(&cfg_prt_packet, 0, sizeof(cfg_prt_packet)); - _clsID_needed = UBX_CLASS_CFG; - _msgID_needed = UBX_MESSAGE_CFG_PRT; + _message_class_needed = UBX_CLASS_CFG; + _message_id_needed = UBX_MESSAGE_CFG_PRT; /* Define the package contents, don't change the baudrate */ cfg_prt_packet.clsID = UBX_CLASS_CFG; @@ -102,9 +106,9 @@ UBX::configure(unsigned &baudrate) cfg_prt_packet.inProtoMask = UBX_CFG_PRT_PAYLOAD_INPROTOMASK; cfg_prt_packet.outProtoMask = UBX_CFG_PRT_PAYLOAD_OUTPROTOMASK; - send_config_packet(_fd, (uint8_t*)&cfg_prt_packet, sizeof(cfg_prt_packet)); + send_config_packet(_fd, (uint8_t *)&cfg_prt_packet, sizeof(cfg_prt_packet)); - if (receive(UBX_CONFIG_TIMEOUT) < 0) { + if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { /* try next baudrate */ continue; } @@ -120,100 +124,125 @@ UBX::configure(unsigned &baudrate) cfg_prt_packet.inProtoMask = UBX_CFG_PRT_PAYLOAD_INPROTOMASK; cfg_prt_packet.outProtoMask = UBX_CFG_PRT_PAYLOAD_OUTPROTOMASK; - send_config_packet(_fd, (uint8_t*)&cfg_prt_packet, sizeof(cfg_prt_packet)); - + send_config_packet(_fd, (uint8_t *)&cfg_prt_packet, sizeof(cfg_prt_packet)); + /* no ACK is expected here, but read the buffer anyway in case we actually get an ACK */ - receive(UBX_CONFIG_TIMEOUT); - + wait_for_ack(UBX_CONFIG_TIMEOUT); + if (UBX_CFG_PRT_PAYLOAD_BAUDRATE != baudrate) { set_baudrate(_fd, UBX_CFG_PRT_PAYLOAD_BAUDRATE); baudrate = UBX_CFG_PRT_PAYLOAD_BAUDRATE; } - /* send a CFT-RATE message to define update rate */ - type_gps_bin_cfg_rate_packet_t cfg_rate_packet; - memset(&cfg_rate_packet, 0, sizeof(cfg_rate_packet)); - - _clsID_needed = UBX_CLASS_CFG; - _msgID_needed = UBX_MESSAGE_CFG_RATE; - - cfg_rate_packet.clsID = UBX_CLASS_CFG; - cfg_rate_packet.msgID = UBX_MESSAGE_CFG_RATE; - cfg_rate_packet.length = UBX_CFG_RATE_LENGTH; - cfg_rate_packet.measRate = UBX_CFG_RATE_PAYLOAD_MEASINTERVAL; - cfg_rate_packet.navRate = UBX_CFG_RATE_PAYLOAD_NAVRATE; - cfg_rate_packet.timeRef = UBX_CFG_RATE_PAYLOAD_TIMEREF; - - send_config_packet(_fd, (uint8_t*)&cfg_rate_packet, sizeof(cfg_rate_packet)); - if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { - /* try next baudrate */ - continue; - } - - /* send a NAV5 message to set the options for the internal filter */ - type_gps_bin_cfg_nav5_packet_t cfg_nav5_packet; - memset(&cfg_nav5_packet, 0, sizeof(cfg_nav5_packet)); - - _clsID_needed = UBX_CLASS_CFG; - _msgID_needed = UBX_MESSAGE_CFG_NAV5; - - cfg_nav5_packet.clsID = UBX_CLASS_CFG; - cfg_nav5_packet.msgID = UBX_MESSAGE_CFG_NAV5; - cfg_nav5_packet.length = UBX_CFG_NAV5_LENGTH; - cfg_nav5_packet.mask = UBX_CFG_NAV5_PAYLOAD_MASK; - cfg_nav5_packet.dynModel = UBX_CFG_NAV5_PAYLOAD_DYNMODEL; - cfg_nav5_packet.fixMode = UBX_CFG_NAV5_PAYLOAD_FIXMODE; - - send_config_packet(_fd, (uint8_t*)&cfg_nav5_packet, sizeof(cfg_nav5_packet)); - if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { - /* try next baudrate */ - continue; - } - - configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_POSLLH, - UBX_CFG_MSG_PAYLOAD_RATE1_5HZ); - /* insist of receiving the ACK for this packet */ - // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) - // continue; - configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_TIMEUTC, - UBX_CFG_MSG_PAYLOAD_RATE1_1HZ); - // /* insist of receiving the ACK for this packet */ - // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) - // continue; - configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_SOL, - UBX_CFG_MSG_PAYLOAD_RATE1_5HZ); - // /* insist of receiving the ACK for this packet */ - // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) - // continue; - configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_VELNED, - UBX_CFG_MSG_PAYLOAD_RATE1_5HZ); - // /* insist of receiving the ACK for this packet */ - // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) - // continue; - // configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_DOP, - // 0); - // /* insist of receiving the ACK for this packet */ - // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) - // continue; - configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_SVINFO, - UBX_CFG_MSG_PAYLOAD_RATE1_05HZ); - // /* insist of receiving the ACK for this packet */ - // if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) - // continue; - - _waiting_for_ack = false; - return 0; + /* at this point we have correct baudrate on both ends */ + break; } - return -1; + + if (baud_i >= 5) { + return 1; + } + + /* send a CFG-RATE message to define update rate */ + type_gps_bin_cfg_rate_packet_t cfg_rate_packet; + memset(&cfg_rate_packet, 0, sizeof(cfg_rate_packet)); + + _message_class_needed = UBX_CLASS_CFG; + _message_id_needed = UBX_MESSAGE_CFG_RATE; + + cfg_rate_packet.clsID = UBX_CLASS_CFG; + cfg_rate_packet.msgID = UBX_MESSAGE_CFG_RATE; + cfg_rate_packet.length = UBX_CFG_RATE_LENGTH; + cfg_rate_packet.measRate = UBX_CFG_RATE_PAYLOAD_MEASINTERVAL; + cfg_rate_packet.navRate = UBX_CFG_RATE_PAYLOAD_NAVRATE; + cfg_rate_packet.timeRef = UBX_CFG_RATE_PAYLOAD_TIMEREF; + + send_config_packet(_fd, (uint8_t *)&cfg_rate_packet, sizeof(cfg_rate_packet)); + + if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { + warnx("ubx: configuration failed: RATE"); + return 1; + } + + /* send a NAV5 message to set the options for the internal filter */ + type_gps_bin_cfg_nav5_packet_t cfg_nav5_packet; + memset(&cfg_nav5_packet, 0, sizeof(cfg_nav5_packet)); + + _message_class_needed = UBX_CLASS_CFG; + _message_id_needed = UBX_MESSAGE_CFG_NAV5; + + cfg_nav5_packet.clsID = UBX_CLASS_CFG; + cfg_nav5_packet.msgID = UBX_MESSAGE_CFG_NAV5; + cfg_nav5_packet.length = UBX_CFG_NAV5_LENGTH; + cfg_nav5_packet.mask = UBX_CFG_NAV5_PAYLOAD_MASK; + cfg_nav5_packet.dynModel = UBX_CFG_NAV5_PAYLOAD_DYNMODEL; + cfg_nav5_packet.fixMode = UBX_CFG_NAV5_PAYLOAD_FIXMODE; + + send_config_packet(_fd, (uint8_t *)&cfg_nav5_packet, sizeof(cfg_nav5_packet)); + + if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { + warnx("ubx: configuration failed: NAV5"); + return 1; + } + + /* configure message rates */ + /* the last argument is divisor for measurement rate (set by CFG RATE), i.e. 1 means 5Hz */ + configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_POSLLH, 1); + + if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { + warnx("ubx: msg rate configuration failed: NAV POSLLH\n"); + return 1; + } + + configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_TIMEUTC, 1); + + if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { + warnx("ubx: msg rate configuration failed: NAV TIMEUTC\n"); + return 1; + } + + configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_SOL, 1); + + if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { + warnx("ubx: msg rate configuration failed: NAV SOL\n"); + return 1; + } + + configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_VELNED, 1); + + if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { + warnx("ubx: msg rate configuration failed: NAV VELNED\n"); + return 1; + } + + configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_SVINFO, 10); + + if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { + warnx("ubx: msg rate configuration failed: NAV SVINFO\n"); + return 1; + } + + _configured = true; + return 0; } int UBX::wait_for_ack(unsigned timeout) { _waiting_for_ack = true; - int ret = receive(timeout); - _waiting_for_ack = false; - return ret; + uint64_t time_started = hrt_absolute_time(); + + while (hrt_absolute_time() < time_started + timeout * 1000) { + if (receive(timeout) > 0) { + if (!_waiting_for_ack) { + return 1; + } + + } else { + return -1; // timeout or error receiving, or NAK + } + } + + return -1; // timeout } int @@ -231,20 +260,20 @@ UBX::receive(unsigned timeout) ssize_t count = 0; - bool position_updated = false; + bool handled = false; while (true) { - /* poll for new data */ - int ret = ::poll(fds, sizeof(fds) / sizeof(fds[0]), timeout); + /* poll for new data, wait for only UBX_PACKET_TIMEOUT (2ms) if something already received */ + int ret = poll(fds, sizeof(fds) / sizeof(fds[0]), handled ? UBX_PACKET_TIMEOUT : timeout); if (ret < 0) { /* something went wrong when polling */ return -1; } else if (ret == 0) { - /* Timeout */ - return -1; + /* return success after short delay after receiving a packet or timeout after long delay */ + return handled ? 1 : -1; } else if (ret > 0) { /* if we have new data from GPS, go handle it */ @@ -254,25 +283,22 @@ UBX::receive(unsigned timeout) * won't block even on a blocking device. If more bytes are * available, we'll go back to poll() again... */ - count = ::read(_fd, buf, sizeof(buf)); - /* pass received bytes to the packet decoder */ + count = read(_fd, buf, sizeof(buf)); + + /* pass received bytes to the packet decoder */ for (int i = 0; i < count; i++) { - if (parse_char(buf[i])) { + if (parse_char(buf[i]) > 0) { /* return to configure during configuration or to the gps driver during normal work * if a packet has arrived */ - if (handle_message()) - position_updated = true; + if (handle_message() > 0) + handled = true; } } } } - /* return success after receiving a packet */ - if (position_updated) - return 1; - - /* abort after timeout if no packet parsed successfully */ - if (time_started + timeout*1000 < hrt_absolute_time() ) { + /* abort after timeout if no useful packets received */ + if (time_started + timeout * 1000 < hrt_absolute_time()) { return -1; } } @@ -283,406 +309,299 @@ UBX::parse_char(uint8_t b) { switch (_decode_state) { /* First, look for sync1 */ - case UBX_DECODE_UNINIT: - if (b == UBX_SYNC1) { - _decode_state = UBX_DECODE_GOT_SYNC1; - } - break; + case UBX_DECODE_UNINIT: + if (b == UBX_SYNC1) { + _decode_state = UBX_DECODE_GOT_SYNC1; + } + + break; + /* Second, look for sync2 */ - case UBX_DECODE_GOT_SYNC1: - if (b == UBX_SYNC2) { - _decode_state = UBX_DECODE_GOT_SYNC2; - } else { - /* Second start symbol was wrong, reset state machine */ - decode_init(); - } - break; + case UBX_DECODE_GOT_SYNC1: + if (b == UBX_SYNC2) { + _decode_state = UBX_DECODE_GOT_SYNC2; + + } else { + /* Second start symbol was wrong, reset state machine */ + decode_init(); + /* don't return error, it can be just false sync1 */ + } + + break; + /* Now look for class */ - case UBX_DECODE_GOT_SYNC2: - /* everything except sync1 and sync2 needs to be added to the checksum */ + case UBX_DECODE_GOT_SYNC2: + /* everything except sync1 and sync2 needs to be added to the checksum */ + add_byte_to_checksum(b); + _message_class = b; + _decode_state = UBX_DECODE_GOT_CLASS; + break; + + case UBX_DECODE_GOT_CLASS: + add_byte_to_checksum(b); + _message_id = b; + _decode_state = UBX_DECODE_GOT_MESSAGEID; + break; + + case UBX_DECODE_GOT_MESSAGEID: + add_byte_to_checksum(b); + _payload_size = b; //this is the first length byte + _decode_state = UBX_DECODE_GOT_LENGTH1; + break; + + case UBX_DECODE_GOT_LENGTH1: + add_byte_to_checksum(b); + _payload_size += b << 8; // here comes the second byte of length + _decode_state = UBX_DECODE_GOT_LENGTH2; + break; + + case UBX_DECODE_GOT_LENGTH2: + + /* Add to checksum if not yet at checksum byte */ + if (_rx_count < _payload_size) add_byte_to_checksum(b); - /* check for known class */ - switch (b) { - case UBX_CLASS_ACK: - _decode_state = UBX_DECODE_GOT_CLASS; - _message_class = ACK; - break; - case UBX_CLASS_NAV: - _decode_state = UBX_DECODE_GOT_CLASS; - _message_class = NAV; - break; + _rx_buffer[_rx_count] = b; -// case UBX_CLASS_RXM: -// _decode_state = UBX_DECODE_GOT_CLASS; -// _message_class = RXM; -// break; + /* once the payload has arrived, we can process the information */ + if (_rx_count >= _payload_size + 1) { //+1 because of 2 checksum bytes + /* compare checksum */ + if (_rx_ck_a == _rx_buffer[_rx_count - 1] && _rx_ck_b == _rx_buffer[_rx_count]) { + decode_init(); + return 1; // message received successfully - case UBX_CLASS_CFG: - _decode_state = UBX_DECODE_GOT_CLASS; - _message_class = CFG; - break; - default: //unknown class: reset state machine - decode_init(); - break; + } else { + warnx("ubx: checksum wrong"); + decode_init(); + return -1; } - break; - case UBX_DECODE_GOT_CLASS: - { - add_byte_to_checksum(b); - switch (_message_class) { - case NAV: - switch (b) { - case UBX_MESSAGE_NAV_POSLLH: - _decode_state = UBX_DECODE_GOT_MESSAGEID; - _message_id = NAV_POSLLH; - break; - case UBX_MESSAGE_NAV_SOL: - _decode_state = UBX_DECODE_GOT_MESSAGEID; - _message_id = NAV_SOL; - break; + } else if (_rx_count < RECV_BUFFER_SIZE) { + _rx_count++; - case UBX_MESSAGE_NAV_TIMEUTC: - _decode_state = UBX_DECODE_GOT_MESSAGEID; - _message_id = NAV_TIMEUTC; - break; + } else { + warnx("ubx: buffer full"); + decode_init(); + return -1; + } -// case UBX_MESSAGE_NAV_DOP: -// _decode_state = UBX_DECODE_GOT_MESSAGEID; -// _message_id = NAV_DOP; -// break; + break; - case UBX_MESSAGE_NAV_SVINFO: - _decode_state = UBX_DECODE_GOT_MESSAGEID; - _message_id = NAV_SVINFO; - break; - - case UBX_MESSAGE_NAV_VELNED: - _decode_state = UBX_DECODE_GOT_MESSAGEID; - _message_id = NAV_VELNED; - break; - - default: //unknown class: reset state machine, should not happen - decode_init(); - break; - } - break; -// case RXM: -// switch (b) { -// case UBX_MESSAGE_RXM_SVSI: -// _decode_state = UBX_DECODE_GOT_MESSAGEID; -// _message_id = RXM_SVSI; -// break; -// -// default: //unknown class: reset state machine, should not happen -// decode_init(); -// break; -// } -// break; - - case CFG: - switch (b) { - case UBX_MESSAGE_CFG_NAV5: - _decode_state = UBX_DECODE_GOT_MESSAGEID; - _message_id = CFG_NAV5; - break; - - default: //unknown class: reset state machine, should not happen - decode_init(); - break; - } - break; - - case ACK: - switch (b) { - case UBX_MESSAGE_ACK_ACK: - _decode_state = UBX_DECODE_GOT_MESSAGEID; - _message_id = ACK_ACK; - break; - case UBX_MESSAGE_ACK_NAK: - _decode_state = UBX_DECODE_GOT_MESSAGEID; - _message_id = ACK_NAK; - break; - default: //unknown class: reset state machine, should not happen - decode_init(); - break; - } - break; - default: //should not happen because we set the class - warnx("UBX Error, we set a class that we don't know"); - decode_init(); -// config_needed = true; - break; - } - // Evaluate state machine - if the state changed, - // the state machine was reset via decode_init() - // and we want to tell the module to stop sending this message - - // disable unknown message - //warnx("disabled class %d, msg %d", (int)_message_class, (int)b); - //configure_message_rate(_message_class, b, 0); - } - break; - case UBX_DECODE_GOT_MESSAGEID: - add_byte_to_checksum(b); - _payload_size = b; //this is the first length byte - _decode_state = UBX_DECODE_GOT_LENGTH1; - break; - case UBX_DECODE_GOT_LENGTH1: - add_byte_to_checksum(b); - _payload_size += b << 8; // here comes the second byte of length - _decode_state = UBX_DECODE_GOT_LENGTH2; - break; - case UBX_DECODE_GOT_LENGTH2: - /* Add to checksum if not yet at checksum byte */ - if (_rx_count < _payload_size) - add_byte_to_checksum(b); - _rx_buffer[_rx_count] = b; - /* once the payload has arrived, we can process the information */ - if (_rx_count >= _payload_size + 1) { //+1 because of 2 checksum bytes - - /* compare checksum */ - if (_rx_ck_a == _rx_buffer[_rx_count-1] && _rx_ck_b == _rx_buffer[_rx_count]) { - return 1; - } else { - decode_init(); - return -1; - warnx("ubx: Checksum wrong"); - } - - return 1; - } else if (_rx_count < RECV_BUFFER_SIZE) { - _rx_count++; - } else { - warnx("ubx: buffer full"); - decode_init(); - return -1; - } - break; - default: - break; + default: + break; } - return 0; //XXX ? + + return 0; // message decoding in progress } + int UBX::handle_message() { int ret = 0; - switch (_message_id) { //this enum is unique for all ids --> no need to check the class - case NAV_POSLLH: { -// printf("GOT NAV_POSLLH MESSAGE\n"); - if (!_waiting_for_ack) { - gps_bin_nav_posllh_packet_t *packet = (gps_bin_nav_posllh_packet_t *) _rx_buffer; + if (_configured) { + /* handle only info messages when configured */ + switch (_message_class) { + case UBX_CLASS_NAV: + switch (_message_id) { + case UBX_MESSAGE_NAV_POSLLH: { + // printf("GOT NAV_POSLLH\n"); + gps_bin_nav_posllh_packet_t *packet = (gps_bin_nav_posllh_packet_t *) _rx_buffer; - _gps_position->lat = packet->lat; - _gps_position->lon = packet->lon; - _gps_position->alt = packet->height_msl; + _gps_position->lat = packet->lat; + _gps_position->lon = packet->lon; + _gps_position->alt = packet->height_msl; + _gps_position->eph_m = (float)packet->hAcc * 1e-3f; // from mm to m + _gps_position->epv_m = (float)packet->vAcc * 1e-3f; // from mm to m + _gps_position->timestamp_position = hrt_absolute_time(); - _gps_position->eph_m = (float)packet->hAcc * 1e-3f; // from mm to m - _gps_position->epv_m = (float)packet->vAcc * 1e-3f; // from mm to m + _rate_count_lat_lon++; - _rate_count_lat_lon++; - - /* Add timestamp to finish the report */ - _gps_position->timestamp_position = hrt_absolute_time(); - /* only return 1 when new position is available */ - ret = 1; - } - break; - } - - case NAV_SOL: { -// printf("GOT NAV_SOL MESSAGE\n"); - if (!_waiting_for_ack) { - gps_bin_nav_sol_packet_t *packet = (gps_bin_nav_sol_packet_t *) _rx_buffer; - - _gps_position->fix_type = packet->gpsFix; - _gps_position->s_variance_m_s = packet->sAcc; - _gps_position->p_variance_m = packet->pAcc; - - _gps_position->timestamp_variance = hrt_absolute_time(); - } - break; - } - -// case NAV_DOP: { -//// printf("GOT NAV_DOP MESSAGE\n"); -// gps_bin_nav_dop_packet_t *packet = (gps_bin_nav_dop_packet_t *) _rx_buffer; -// -// _gps_position->eph_m = packet->hDOP; -// _gps_position->epv = packet->vDOP; -// -// _gps_position->timestamp_posdilution = hrt_absolute_time(); -// -// _new_nav_dop = true; -// -// break; -// } - - case NAV_TIMEUTC: { -// printf("GOT NAV_TIMEUTC MESSAGE\n"); - - if (!_waiting_for_ack) { - gps_bin_nav_timeutc_packet_t *packet = (gps_bin_nav_timeutc_packet_t *) _rx_buffer; - - //convert to unix timestamp - struct tm timeinfo; - timeinfo.tm_year = packet->year - 1900; - timeinfo.tm_mon = packet->month - 1; - timeinfo.tm_mday = packet->day; - timeinfo.tm_hour = packet->hour; - timeinfo.tm_min = packet->min; - timeinfo.tm_sec = packet->sec; - - time_t epoch = mktime(&timeinfo); - - _gps_position->time_gps_usec = (uint64_t)epoch * 1000000; //TODO: test this - _gps_position->time_gps_usec += (uint64_t)(packet->time_nanoseconds * 1e-3f); - - _gps_position->timestamp_time = hrt_absolute_time(); - } - break; - } - - case NAV_SVINFO: { - // printf("GOT NAV_SVINFO MESSAGE\n"); - - if (!_waiting_for_ack) { - //this is a more complicated message: the length depends on the number of satellites. This number is extracted from the first part of the message - const int length_part1 = 8; - char _rx_buffer_part1[length_part1]; - memcpy(_rx_buffer_part1, _rx_buffer, length_part1); - gps_bin_nav_svinfo_part1_packet_t *packet_part1 = (gps_bin_nav_svinfo_part1_packet_t *) _rx_buffer_part1; - - //read checksum - const int length_part3 = 2; - char _rx_buffer_part3[length_part3]; - memcpy(_rx_buffer_part3, &(_rx_buffer[_rx_count - 1]), length_part3); - - //definitions needed to read numCh elements from the buffer: - const int length_part2 = 12; - gps_bin_nav_svinfo_part2_packet_t *packet_part2; - char _rx_buffer_part2[length_part2]; //for temporal storage - - uint8_t satellites_used = 0; - int i; - // printf("Number of Channels: %d\n", packet_part1->numCh); - for (i = 0; i < packet_part1->numCh; i++) { //for each channel - - /* Get satellite information from the buffer */ - memcpy(_rx_buffer_part2, &(_rx_buffer[length_part1 + i * length_part2]), length_part2); - packet_part2 = (gps_bin_nav_svinfo_part2_packet_t *) _rx_buffer_part2; - - /* Write satellite information to global storage */ - uint8_t sv_used = packet_part2->flags & 0x01; - - if ( sv_used ) { - // Count SVs used for NAV. - satellites_used++; - } - - // Record info for all channels, whether or not the SV is used for NAV. - _gps_position->satellite_used[i] = sv_used; - _gps_position->satellite_snr[i] = packet_part2->cno; - _gps_position->satellite_elevation[i] = (uint8_t)(packet_part2->elev); - _gps_position->satellite_azimuth[i] = (uint8_t)((float)packet_part2->azim * 255.0f / 360.0f); - _gps_position->satellite_prn[i] = packet_part2->svid; - } - - for (i = packet_part1->numCh; i < 20; i++) { //these channels are unused - /* Unused channels have to be set to zero for e.g. MAVLink */ - _gps_position->satellite_prn[i] = 0; - _gps_position->satellite_used[i] = 0; - _gps_position->satellite_snr[i] = 0; - _gps_position->satellite_elevation[i] = 0; - _gps_position->satellite_azimuth[i] = 0; - } - _gps_position->satellites_visible = satellites_used; // visible ~= used but we are interested in the used ones - - /* set timestamp if any sat info is available */ - if (packet_part1->numCh > 0) { - _gps_position->satellite_info_available = true; - } else { - _gps_position->satellite_info_available = false; - } - _gps_position->timestamp_satellites = hrt_absolute_time(); - } - - break; - } - - case NAV_VELNED: { - - if (!_waiting_for_ack) { - /* 35.15 NAV-VELNED (0x01 0x12) message (page 181 / 210 of reference manual */ - gps_bin_nav_velned_packet_t *packet = (gps_bin_nav_velned_packet_t *) _rx_buffer; - - _gps_position->vel_m_s = (float)packet->speed * 1e-2f; - _gps_position->vel_n_m_s = (float)packet->velN * 1e-2f; /* NED NORTH velocity */ - _gps_position->vel_e_m_s = (float)packet->velE * 1e-2f; /* NED EAST velocity */ - _gps_position->vel_d_m_s = (float)packet->velD * 1e-2f; /* NED DOWN velocity */ - _gps_position->cog_rad = (float)packet->heading * M_DEG_TO_RAD_F * 1e-5f; - _gps_position->c_variance_rad = (float)packet->cAcc * M_DEG_TO_RAD_F * 1e-5f; - _gps_position->vel_ned_valid = true; - _gps_position->timestamp_velocity = hrt_absolute_time(); - - _rate_count_vel++; - } - - break; - } - -// case RXM_SVSI: { -// printf("GOT RXM_SVSI MESSAGE\n"); -// const int length_part1 = 7; -// char _rx_buffer_part1[length_part1]; -// memcpy(_rx_buffer_part1, _rx_buffer, length_part1); -// gps_bin_rxm_svsi_packet_t *packet = (gps_bin_rxm_svsi_packet_t *) _rx_buffer_part1; -// -// _gps_position->satellites_visible = packet->numVis; -// _gps_position->counter++; -// _last_message_timestamps[RXM_SVSI - 1] = hrt_absolute_time(); -// -// break; -// } - case ACK_ACK: { -// printf("GOT ACK_ACK\n"); - gps_bin_ack_ack_packet_t *packet = (gps_bin_ack_ack_packet_t *) _rx_buffer; - - if (_waiting_for_ack) { - if (packet->clsID == _clsID_needed && packet->msgID == _msgID_needed) { ret = 1; + break; } - } - } - break; - case ACK_NAK: { -// printf("GOT ACK_NAK\n"); - warnx("UBX: Received: Not Acknowledged"); - /* configuration obviously not successful */ - ret = -1; + case UBX_MESSAGE_NAV_SOL: { + // printf("GOT NAV_SOL\n"); + gps_bin_nav_sol_packet_t *packet = (gps_bin_nav_sol_packet_t *) _rx_buffer; + + _gps_position->fix_type = packet->gpsFix; + _gps_position->s_variance_m_s = packet->sAcc; + _gps_position->p_variance_m = packet->pAcc; + _gps_position->timestamp_variance = hrt_absolute_time(); + + ret = 1; + break; + } + + case UBX_MESSAGE_NAV_TIMEUTC: { + // printf("GOT NAV_TIMEUTC\n"); + gps_bin_nav_timeutc_packet_t *packet = (gps_bin_nav_timeutc_packet_t *) _rx_buffer; + + /* convert to unix timestamp */ + struct tm timeinfo; + timeinfo.tm_year = packet->year - 1900; + timeinfo.tm_mon = packet->month - 1; + timeinfo.tm_mday = packet->day; + timeinfo.tm_hour = packet->hour; + timeinfo.tm_min = packet->min; + timeinfo.tm_sec = packet->sec; + time_t epoch = mktime(&timeinfo); + + _gps_position->time_gps_usec = (uint64_t)epoch * 1000000; //TODO: test this + _gps_position->time_gps_usec += (uint64_t)(packet->time_nanoseconds * 1e-3f); + _gps_position->timestamp_time = hrt_absolute_time(); + + ret = 1; + break; + } + + case UBX_MESSAGE_NAV_SVINFO: { + // printf("GOT NAV_SVINFO\n"); + // TODO avoid memcpy + const int length_part1 = 8; + char _rx_buffer_part1[length_part1]; + memcpy(_rx_buffer_part1, _rx_buffer, length_part1); + gps_bin_nav_svinfo_part1_packet_t *packet_part1 = (gps_bin_nav_svinfo_part1_packet_t *) _rx_buffer_part1; + + const int length_part2 = 12; + gps_bin_nav_svinfo_part2_packet_t *packet_part2; + char _rx_buffer_part2[length_part2]; // for temporal storage + + uint8_t satellites_used = 0; + int i; + + // printf("Number of Channels: %d\n", packet_part1->numCh); + for (i = 0; i < packet_part1->numCh; i++) { //for each channel + + /* get satellite information from the buffer */ + memcpy(_rx_buffer_part2, &(_rx_buffer[length_part1 + i * length_part2]), length_part2); + packet_part2 = (gps_bin_nav_svinfo_part2_packet_t *) _rx_buffer_part2; + + /* write satellite information to global storage */ + uint8_t sv_used = packet_part2->flags & 0x01; + + if (sv_used) { + // Count SVs used for NAV. + satellites_used++; + } + + // Record info for all channels, whether or not the SV is used for NAV. + _gps_position->satellite_used[i] = sv_used; + _gps_position->satellite_snr[i] = packet_part2->cno; + _gps_position->satellite_elevation[i] = (uint8_t)(packet_part2->elev); + _gps_position->satellite_azimuth[i] = (uint8_t)((float)packet_part2->azim * 255.0f / 360.0f); + _gps_position->satellite_prn[i] = packet_part2->svid; + } + + for (i = packet_part1->numCh; i < 20; i++) { //these channels are unused + /* Unused channels have to be set to zero for e.g. MAVLink */ + _gps_position->satellite_prn[i] = 0; + _gps_position->satellite_used[i] = 0; + _gps_position->satellite_snr[i] = 0; + _gps_position->satellite_elevation[i] = 0; + _gps_position->satellite_azimuth[i] = 0; + } + + _gps_position->satellites_visible = satellites_used; // visible ~= used but we are interested in the used ones + + /* set timestamp if any sat info is available */ + if (packet_part1->numCh > 0) { + _gps_position->satellite_info_available = true; + + } else { + _gps_position->satellite_info_available = false; + } + + _gps_position->timestamp_satellites = hrt_absolute_time(); + + ret = 1; + break; + } + + case UBX_MESSAGE_NAV_VELNED: { + // printf("GOT NAV_VELNED\n"); + gps_bin_nav_velned_packet_t *packet = (gps_bin_nav_velned_packet_t *) _rx_buffer; + + _gps_position->vel_m_s = (float)packet->speed * 1e-2f; + _gps_position->vel_n_m_s = (float)packet->velN * 1e-2f; /* NED NORTH velocity */ + _gps_position->vel_e_m_s = (float)packet->velE * 1e-2f; /* NED EAST velocity */ + _gps_position->vel_d_m_s = (float)packet->velD * 1e-2f; /* NED DOWN velocity */ + _gps_position->cog_rad = (float)packet->heading * M_DEG_TO_RAD_F * 1e-5f; + _gps_position->c_variance_rad = (float)packet->cAcc * M_DEG_TO_RAD_F * 1e-5f; + _gps_position->vel_ned_valid = true; + _gps_position->timestamp_velocity = hrt_absolute_time(); + + _rate_count_vel++; + + ret = 1; + break; + } + + default: + break; + } + + break; + + case UBX_CLASS_ACK: { + /* ignore ACK when already configured */ + ret = 1; + break; + } + + default: break; } - default: //we don't know the message - warnx("UBX: Unknown message received: %d-%d\n",_message_class,_message_id); - if (_disable_cmd_counter++ == 0) { - // Don't attempt for every message to disable, some might not be disabled */ - warnx("Disabling message 0x%02x 0x%02x", (unsigned)_message_class, (unsigned)_message_id); + if (ret == 0) { + /* message not handled */ + warnx("ubx: unknown message received: 0x%02x-0x%02x\n", (unsigned)_message_class, (unsigned)_message_id); + + if ((_disable_cmd_counter = _disable_cmd_counter++ % 10) == 0) { + /* don't attempt for every message to disable, some might not be disabled */ + warnx("ubx: disabling message 0x%02x-0x%02x", (unsigned)_message_class, (unsigned)_message_id); configure_message_rate(_message_class, _message_id, 0); } - return ret; - ret = -1; - break; } - // end if _rx_count high enough + + } else { + /* handle only ACK while configuring */ + if (_message_class == UBX_CLASS_ACK) { + switch (_message_id) { + case UBX_MESSAGE_ACK_ACK: { + // printf("GOT ACK_ACK\n"); + gps_bin_ack_ack_packet_t *packet = (gps_bin_ack_ack_packet_t *) _rx_buffer; + + if (_waiting_for_ack) { + if (packet->clsID == _message_class_needed && packet->msgID == _message_id_needed) { + _waiting_for_ack = false; + ret = 1; + } + } + + break; + } + + case UBX_MESSAGE_ACK_NAK: { + // printf("GOT ACK_NAK\n"); + warnx("ubx: not acknowledged"); + /* configuration obviously not successful */ + _waiting_for_ack = false; + ret = -1; + break; + } + + default: + break; + } + } + } + decode_init(); - return ret; //XXX? + return ret; } void @@ -692,9 +611,8 @@ UBX::decode_init(void) _rx_ck_b = 0; _rx_count = 0; _decode_state = UBX_DECODE_UNINIT; - _message_class = CLASS_UNKNOWN; - _message_id = ID_UNKNOWN; _payload_size = 0; + /* don't reset _message_class, _message_id, _rx_buffer leave it for message handler */ } void @@ -705,23 +623,24 @@ UBX::add_byte_to_checksum(uint8_t b) } void -UBX::add_checksum_to_message(uint8_t* message, const unsigned length) +UBX::add_checksum_to_message(uint8_t *message, const unsigned length) { uint8_t ck_a = 0; uint8_t ck_b = 0; unsigned i; - for (i = 0; i < length-2; i++) { + for (i = 0; i < length - 2; i++) { ck_a = ck_a + message[i]; ck_b = ck_b + ck_a; } + /* The checksum is written to the last to bytes of a message */ - message[length-2] = ck_a; - message[length-1] = ck_b; + message[length - 2] = ck_a; + message[length - 1] = ck_b; } void -UBX::add_checksum(uint8_t* message, const unsigned length, uint8_t &ck_a, uint8_t &ck_b) +UBX::add_checksum(uint8_t *message, const unsigned length, uint8_t &ck_a, uint8_t &ck_b) { for (unsigned i = 0; i < length; i++) { ck_a = ck_a + message[i]; @@ -732,11 +651,11 @@ UBX::add_checksum(uint8_t* message, const unsigned length, uint8_t &ck_a, uint8_ void UBX::configure_message_rate(uint8_t msg_class, uint8_t msg_id, uint8_t rate) { - struct ubx_cfg_msg_rate msg; - msg.msg_class = msg_class; - msg.msg_id = msg_id; - msg.rate = rate; - send_message(UBX_CLASS_CFG, UBX_MESSAGE_CFG_MSG, &msg, sizeof(msg)); + struct ubx_cfg_msg_rate msg; + msg.msg_class = msg_class; + msg.msg_id = msg_id; + msg.rate = rate; + send_message(UBX_CLASS_CFG, UBX_MESSAGE_CFG_MSG, &msg, sizeof(msg)); } void @@ -761,19 +680,19 @@ void UBX::send_message(uint8_t msg_class, uint8_t msg_id, void *msg, uint8_t size) { struct ubx_header header; - uint8_t ck_a=0, ck_b=0; + uint8_t ck_a = 0, ck_b = 0; header.sync1 = UBX_SYNC1; header.sync2 = UBX_SYNC2; header.msg_class = msg_class; header.msg_id = msg_id; header.length = size; - add_checksum((uint8_t *)&header.msg_class, sizeof(header)-2, ck_a, ck_b); + add_checksum((uint8_t *)&header.msg_class, sizeof(header) - 2, ck_a, ck_b); add_checksum((uint8_t *)msg, size, ck_a, ck_b); // Configure receive check - _clsID_needed = msg_class; - _msgID_needed = msg_id; + _message_class_needed = msg_class; + _message_id_needed = msg_id; write(_fd, (const char *)&header, sizeof(header)); write(_fd, (const char *)msg, size); diff --git a/src/drivers/gps/ubx.h b/src/drivers/gps/ubx.h index 5a433642ce..4fc2769750 100644 --- a/src/drivers/gps/ubx.h +++ b/src/drivers/gps/ubx.h @@ -3,6 +3,7 @@ * Copyright (C) 2008-2013 PX4 Development Team. All rights reserved. * Author: Thomas Gubler * Julian Oes + * Anton Babushkin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -51,23 +52,23 @@ /* MessageIDs (the ones that are used) */ #define UBX_MESSAGE_NAV_POSLLH 0x02 -#define UBX_MESSAGE_NAV_SOL 0x06 -#define UBX_MESSAGE_NAV_TIMEUTC 0x21 //#define UBX_MESSAGE_NAV_DOP 0x04 -#define UBX_MESSAGE_NAV_SVINFO 0x30 +#define UBX_MESSAGE_NAV_SOL 0x06 #define UBX_MESSAGE_NAV_VELNED 0x12 //#define UBX_MESSAGE_RXM_SVSI 0x20 -#define UBX_MESSAGE_ACK_ACK 0x01 +#define UBX_MESSAGE_NAV_TIMEUTC 0x21 +#define UBX_MESSAGE_NAV_SVINFO 0x30 #define UBX_MESSAGE_ACK_NAK 0x00 +#define UBX_MESSAGE_ACK_ACK 0x01 #define UBX_MESSAGE_CFG_PRT 0x00 -#define UBX_MESSAGE_CFG_NAV5 0x24 #define UBX_MESSAGE_CFG_MSG 0x01 #define UBX_MESSAGE_CFG_RATE 0x08 +#define UBX_MESSAGE_CFG_NAV5 0x24 #define UBX_CFG_PRT_LENGTH 20 #define UBX_CFG_PRT_PAYLOAD_PORTID 0x01 /**< UART1 */ #define UBX_CFG_PRT_PAYLOAD_MODE 0x000008D0 /**< 0b0000100011010000: 8N1 */ -#define UBX_CFG_PRT_PAYLOAD_BAUDRATE 38400 /**< always choose 38400 as GPS baudrate */ +#define UBX_CFG_PRT_PAYLOAD_BAUDRATE 38400 /**< choose 38400 as GPS baudrate */ #define UBX_CFG_PRT_PAYLOAD_INPROTOMASK 0x01 /**< UBX in */ #define UBX_CFG_PRT_PAYLOAD_OUTPROTOMASK 0x01 /**< UBX out */ @@ -298,44 +299,6 @@ struct ubx_cfg_msg_rate { // END the structures of the binary packets // ************ -typedef enum { - UBX_CONFIG_STATE_PRT = 0, - UBX_CONFIG_STATE_PRT_NEW_BAUDRATE, - UBX_CONFIG_STATE_RATE, - UBX_CONFIG_STATE_NAV5, - UBX_CONFIG_STATE_MSG_NAV_POSLLH, - UBX_CONFIG_STATE_MSG_NAV_TIMEUTC, - UBX_CONFIG_STATE_MSG_NAV_DOP, - UBX_CONFIG_STATE_MSG_NAV_SVINFO, - UBX_CONFIG_STATE_MSG_NAV_SOL, - UBX_CONFIG_STATE_MSG_NAV_VELNED, -// UBX_CONFIG_STATE_MSG_RXM_SVSI, - UBX_CONFIG_STATE_CONFIGURED -} ubx_config_state_t; - -typedef enum { - CLASS_UNKNOWN = 0, - NAV = 1, - RXM = 2, - ACK = 3, - CFG = 4 -} ubx_message_class_t; - -typedef enum { - //these numbers do NOT correspond to the message id numbers of the ubx protocol - ID_UNKNOWN = 0, - NAV_POSLLH, - NAV_SOL, - NAV_TIMEUTC, -// NAV_DOP, - NAV_SVINFO, - NAV_VELNED, -// RXM_SVSI, - CFG_NAV5, - ACK_ACK, - ACK_NAK, -} ubx_message_id_t; - typedef enum { UBX_DECODE_UNINIT = 0, UBX_DECODE_GOT_SYNC1, @@ -401,17 +364,17 @@ private: int _fd; struct vehicle_gps_position_s *_gps_position; - ubx_config_state_t _config_state; + bool _configured; bool _waiting_for_ack; - uint8_t _clsID_needed; - uint8_t _msgID_needed; + uint8_t _message_class_needed; + uint8_t _message_id_needed; ubx_decode_state_t _decode_state; uint8_t _rx_buffer[RECV_BUFFER_SIZE]; unsigned _rx_count; uint8_t _rx_ck_a; uint8_t _rx_ck_b; - ubx_message_class_t _message_class; - ubx_message_id_t _message_id; + uint8_t _message_class; + uint8_t _message_id; unsigned _payload_size; uint8_t _disable_cmd_counter; }; From 0ccf50bca367d39f75a7a4f76f98dec7352ef3d5 Mon Sep 17 00:00:00 2001 From: Anton Babushkin Date: Thu, 11 Jul 2013 18:17:36 +0400 Subject: [PATCH 22/26] ubx: SVINFO parsing optimized and message rate increased, CPU consumption reduced in 6 times, ~0.3% now. --- src/drivers/gps/ubx.cpp | 42 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/drivers/gps/ubx.cpp b/src/drivers/gps/ubx.cpp index 895d164377..b579db7150 100644 --- a/src/drivers/gps/ubx.cpp +++ b/src/drivers/gps/ubx.cpp @@ -56,8 +56,9 @@ #include "ubx.h" -#define UBX_CONFIG_TIMEOUT 100 -#define UBX_PACKET_TIMEOUT 2 +#define UBX_CONFIG_TIMEOUT 200 // ms, timeout for waiting ACK +#define UBX_PACKET_TIMEOUT 2 // ms, if now data during this delay assume that full update received +#define UBX_WAIT_BEFORE_READ 20 // ms, wait before reading to save read() calls UBX::UBX(const int &fd, struct vehicle_gps_position_s *gps_position) : _fd(fd), @@ -214,7 +215,7 @@ UBX::configure(unsigned &baudrate) return 1; } - configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_SVINFO, 10); + configure_message_rate(UBX_CLASS_NAV, UBX_MESSAGE_NAV_SVINFO, 5); if (wait_for_ack(UBX_CONFIG_TIMEOUT) < 0) { warnx("ubx: msg rate configuration failed: NAV SVINFO\n"); @@ -280,9 +281,11 @@ UBX::receive(unsigned timeout) if (fds[0].revents & POLLIN) { /* * We are here because poll says there is some data, so this - * won't block even on a blocking device. If more bytes are - * available, we'll go back to poll() again... + * won't block even on a blocking device. But don't read immediately + * by 1-2 bytes, wait for some more data to save expensive read() calls. + * If more bytes are available, we'll go back to poll() again. */ + usleep(UBX_WAIT_BEFORE_READ * 1000); count = read(_fd, buf, sizeof(buf)); /* pass received bytes to the packet decoder */ @@ -459,45 +462,39 @@ UBX::handle_message() } case UBX_MESSAGE_NAV_SVINFO: { - // printf("GOT NAV_SVINFO\n"); - // TODO avoid memcpy + //printf("GOT NAV_SVINFO\n"); const int length_part1 = 8; - char _rx_buffer_part1[length_part1]; - memcpy(_rx_buffer_part1, _rx_buffer, length_part1); - gps_bin_nav_svinfo_part1_packet_t *packet_part1 = (gps_bin_nav_svinfo_part1_packet_t *) _rx_buffer_part1; - + gps_bin_nav_svinfo_part1_packet_t *packet_part1 = (gps_bin_nav_svinfo_part1_packet_t *) _rx_buffer; const int length_part2 = 12; gps_bin_nav_svinfo_part2_packet_t *packet_part2; - char _rx_buffer_part2[length_part2]; // for temporal storage uint8_t satellites_used = 0; int i; - // printf("Number of Channels: %d\n", packet_part1->numCh); - for (i = 0; i < packet_part1->numCh; i++) { //for each channel - - /* get satellite information from the buffer */ - memcpy(_rx_buffer_part2, &(_rx_buffer[length_part1 + i * length_part2]), length_part2); - packet_part2 = (gps_bin_nav_svinfo_part2_packet_t *) _rx_buffer_part2; + //printf("Number of Channels: %d\n", packet_part1->numCh); + for (i = 0; i < packet_part1->numCh; i++) { + /* set pointer to sattelite_i information */ + packet_part2 = (gps_bin_nav_svinfo_part2_packet_t *) & (_rx_buffer[length_part1 + i * length_part2]); /* write satellite information to global storage */ uint8_t sv_used = packet_part2->flags & 0x01; if (sv_used) { - // Count SVs used for NAV. + /* count SVs used for NAV */ satellites_used++; } - // Record info for all channels, whether or not the SV is used for NAV. + /* record info for all channels, whether or not the SV is used for NAV */ _gps_position->satellite_used[i] = sv_used; _gps_position->satellite_snr[i] = packet_part2->cno; _gps_position->satellite_elevation[i] = (uint8_t)(packet_part2->elev); _gps_position->satellite_azimuth[i] = (uint8_t)((float)packet_part2->azim * 255.0f / 360.0f); _gps_position->satellite_prn[i] = packet_part2->svid; + //printf("SAT %d: %d %d %d %d\n", i, (int)sv_used, (int)packet_part2->cno, (int)(uint8_t)(packet_part2->elev), (int)packet_part2->svid); } - for (i = packet_part1->numCh; i < 20; i++) { //these channels are unused - /* Unused channels have to be set to zero for e.g. MAVLink */ + for (i = packet_part1->numCh; i < 20; i++) { + /* unused channels have to be set to zero for e.g. MAVLink */ _gps_position->satellite_prn[i] = 0; _gps_position->satellite_used[i] = 0; _gps_position->satellite_snr[i] = 0; @@ -507,7 +504,6 @@ UBX::handle_message() _gps_position->satellites_visible = satellites_used; // visible ~= used but we are interested in the used ones - /* set timestamp if any sat info is available */ if (packet_part1->numCh > 0) { _gps_position->satellite_info_available = true; From 39b3fc8d32ebaa0a9a01e73399884b007e97b378 Mon Sep 17 00:00:00 2001 From: Jean Cyr Date: Thu, 11 Jul 2013 10:36:17 -0400 Subject: [PATCH 23/26] Don't leave RX in bind mode on console open fail Don't leave RX in bind mode in the unlikely eventuality that console open fails --- src/drivers/px4io/px4io.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/drivers/px4io/px4io.cpp b/src/drivers/px4io/px4io.cpp index 08aef70692..ae56b70b36 100644 --- a/src/drivers/px4io/px4io.cpp +++ b/src/drivers/px4io/px4io.cpp @@ -1696,8 +1696,6 @@ bind(int argc, char *argv[]) else errx(1, "unknown parameter %s, use dsm2 or dsmx", argv[2]); - g_dev->ioctl(nullptr, DSM_BIND_START, pulses); - /* Open console directly to grab CTRL-C signal */ int console = open("/dev/console", O_NONBLOCK | O_RDONLY | O_NOCTTY); if (!console) @@ -1706,6 +1704,8 @@ bind(int argc, char *argv[]) warnx("This command will only bind DSM if satellite VCC (red wire) is controlled by relay 1."); warnx("Press CTRL-C or 'c' when done."); + g_dev->ioctl(nullptr, DSM_BIND_START, pulses); + for (;;) { usleep(500000L); /* Check if user wants to quit */ From 28f536dc4478e5536b568093ec62cf16e8a1687d Mon Sep 17 00:00:00 2001 From: Lorenz Meier Date: Thu, 11 Jul 2013 23:25:08 +0200 Subject: [PATCH 24/26] Hotfix: fixed compile warnings --- src/modules/att_pos_estimator_ekf/KalmanNav.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/att_pos_estimator_ekf/KalmanNav.cpp b/src/modules/att_pos_estimator_ekf/KalmanNav.cpp index 97d7fdd757..191d20f306 100644 --- a/src/modules/att_pos_estimator_ekf/KalmanNav.cpp +++ b/src/modules/att_pos_estimator_ekf/KalmanNav.cpp @@ -661,10 +661,10 @@ int KalmanNav::correctPos() Vector y(6); y(0) = _gps.vel_n_m_s - vN; y(1) = _gps.vel_e_m_s - vE; - y(2) = double(_gps.lat) - lat * 1.0e7 * M_RAD_TO_DEG; - y(3) = double(_gps.lon) - lon * 1.0e7 * M_RAD_TO_DEG; - y(4) = double(_gps.alt) / 1.0e3 - alt; - y(5) = double(_sensors.baro_alt_meter) - alt; + y(2) = double(_gps.lat) - double(lat) * 1.0e7 * M_RAD_TO_DEG; + y(3) = double(_gps.lon) - double(lon) * 1.0e7 * M_RAD_TO_DEG; + y(4) = _gps.alt / 1.0e3f - alt; + y(5) = _sensors.baro_alt_meter - alt; // compute correction // http://en.wikipedia.org/wiki/Extended_Kalman_filter @@ -698,7 +698,7 @@ int KalmanNav::correctPos() vD += xCorrect(VD); lat += double(xCorrect(LAT)); lon += double(xCorrect(LON)); - alt += double(xCorrect(ALT)); + alt += xCorrect(ALT); // update state covariance // http://en.wikipedia.org/wiki/Extended_Kalman_filter @@ -710,7 +710,7 @@ int KalmanNav::correctPos() static int counter = 0; if (beta > _faultPos.get() && (counter % 10 == 0)) { warnx("fault in gps: beta = %8.4f", (double)beta); - warnx("Y/N: vN: %8.4f, vE: %8.4f, lat: %8.4f, lon: %8.4f, alt: %8.4f", + warnx("Y/N: vN: %8.4f, vE: %8.4f, lat: %8.4f, lon: %8.4f, alt: %8.4f, baro: %8.4f", double(y(0) / sqrtf(RPos(0, 0))), double(y(1) / sqrtf(RPos(1, 1))), double(y(2) / sqrtf(RPos(2, 2))), From 1d986d6c040ed0123bdb8cccad1e444f9d0113f3 Mon Sep 17 00:00:00 2001 From: Anton Babushkin Date: Fri, 12 Jul 2013 10:09:38 +0400 Subject: [PATCH 25/26] sdlog2: Global Position Set Point message added, vehicle_global_position_setpoint topic fixed --- src/modules/sdlog2/sdlog2.c | 31 ++++++++++++++++- src/modules/sdlog2/sdlog2_messages.h | 34 ++++++++++++++----- .../topics/vehicle_global_position_setpoint.h | 2 +- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/modules/sdlog2/sdlog2.c b/src/modules/sdlog2/sdlog2.c index 3e6b20472c..3713e0b306 100644 --- a/src/modules/sdlog2/sdlog2.c +++ b/src/modules/sdlog2/sdlog2.c @@ -72,6 +72,7 @@ #include #include #include +#include #include #include #include @@ -615,7 +616,7 @@ int sdlog2_thread_main(int argc, char *argv[]) /* --- IMPORTANT: DEFINE NUMBER OF ORB STRUCTS TO WAIT FOR HERE --- */ /* number of messages */ - const ssize_t fdsc = 18; + const ssize_t fdsc = 19; /* Sanity check variable and index */ ssize_t fdsc_count = 0; /* file descriptors to wait for */ @@ -637,6 +638,7 @@ int sdlog2_thread_main(int argc, char *argv[]) struct vehicle_local_position_s local_pos; struct vehicle_local_position_setpoint_s local_pos_sp; struct vehicle_global_position_s global_pos; + struct vehicle_global_position_setpoint_s global_pos_sp; struct vehicle_gps_position_s gps_pos; struct vehicle_vicon_position_s vicon_pos; struct optical_flow_s flow; @@ -660,6 +662,7 @@ int sdlog2_thread_main(int argc, char *argv[]) int local_pos_sub; int local_pos_sp_sub; int global_pos_sub; + int global_pos_sp_sub; int gps_pos_sub; int vicon_pos_sub; int flow_sub; @@ -689,6 +692,7 @@ int sdlog2_thread_main(int argc, char *argv[]) struct log_ARSP_s log_ARSP; struct log_FLOW_s log_FLOW; struct log_GPOS_s log_GPOS; + struct log_GPSP_s log_GPSP; struct log_ESC_s log_ESC; } body; } log_msg = { @@ -775,6 +779,12 @@ int sdlog2_thread_main(int argc, char *argv[]) fds[fdsc_count].events = POLLIN; fdsc_count++; + /* --- GLOBAL POSITION SETPOINT--- */ + subs.global_pos_sp_sub = orb_subscribe(ORB_ID(vehicle_global_position_setpoint)); + fds[fdsc_count].fd = subs.global_pos_sp_sub; + fds[fdsc_count].events = POLLIN; + fdsc_count++; + /* --- VICON POSITION --- */ subs.vicon_pos_sub = orb_subscribe(ORB_ID(vehicle_vicon_position)); fds[fdsc_count].fd = subs.vicon_pos_sub; @@ -1077,6 +1087,25 @@ int sdlog2_thread_main(int argc, char *argv[]) LOGBUFFER_WRITE_AND_COUNT(GPOS); } + /* --- GLOBAL POSITION SETPOINT --- */ + if (fds[ifds++].revents & POLLIN) { + orb_copy(ORB_ID(vehicle_global_position_setpoint), subs.global_pos_sp_sub, &buf.global_pos_sp); + log_msg.msg_type = LOG_GPSP_MSG; + log_msg.body.log_GPSP.altitude_is_relative = buf.global_pos_sp.altitude_is_relative; + log_msg.body.log_GPSP.lat = buf.global_pos_sp.lat; + log_msg.body.log_GPSP.lon = buf.global_pos_sp.lon; + log_msg.body.log_GPSP.altitude = buf.global_pos_sp.altitude; + log_msg.body.log_GPSP.yaw = buf.global_pos_sp.yaw; + log_msg.body.log_GPSP.loiter_radius = buf.global_pos_sp.loiter_radius; + log_msg.body.log_GPSP.loiter_direction = buf.global_pos_sp.loiter_direction; + log_msg.body.log_GPSP.nav_cmd = buf.global_pos_sp.nav_cmd; + log_msg.body.log_GPSP.param1 = buf.global_pos_sp.param1; + log_msg.body.log_GPSP.param2 = buf.global_pos_sp.param2; + log_msg.body.log_GPSP.param3 = buf.global_pos_sp.param3; + log_msg.body.log_GPSP.param4 = buf.global_pos_sp.param4; + LOGBUFFER_WRITE_AND_COUNT(GPSP); + } + /* --- VICON POSITION --- */ if (fds[ifds++].revents & POLLIN) { orb_copy(ORB_ID(vehicle_vicon_position), subs.vicon_pos_sub, &buf.vicon_pos); diff --git a/src/modules/sdlog2/sdlog2_messages.h b/src/modules/sdlog2/sdlog2_messages.h index abc882d23c..934e4dec89 100644 --- a/src/modules/sdlog2/sdlog2_messages.h +++ b/src/modules/sdlog2/sdlog2_messages.h @@ -149,15 +149,15 @@ struct log_ATTC_s { /* --- STAT - VEHICLE STATE --- */ #define LOG_STAT_MSG 10 struct log_STAT_s { - unsigned char state; - unsigned char flight_mode; - unsigned char manual_control_mode; - unsigned char manual_sas_mode; - unsigned char armed; + uint8_t state; + uint8_t flight_mode; + uint8_t manual_control_mode; + uint8_t manual_sas_mode; + uint8_t armed; float battery_voltage; float battery_current; float battery_remaining; - unsigned char battery_warning; + uint8_t battery_warning; }; /* --- RC - RC INPUT CHANNELS --- */ @@ -210,13 +210,29 @@ struct log_GPOS_s { float vel_d; }; +/* --- GPSP - GLOBAL POSITION SETPOINT --- */ +#define LOG_GPSP_MSG 17 +struct log_GPSP_s { + uint8_t altitude_is_relative; + int32_t lat; + int32_t lon; + float altitude; + float yaw; + float loiter_radius; + int8_t loiter_direction; + uint8_t nav_cmd; + float param1; + float param2; + float param3; + float param4; +}; + /* --- ESC - ESC STATE --- */ -#define LOG_ESC_MSG 64 +#define LOG_ESC_MSG 18 struct log_ESC_s { uint16_t counter; uint8_t esc_count; uint8_t esc_connectiontype; - uint8_t esc_num; uint16_t esc_address; uint16_t esc_version; @@ -227,6 +243,7 @@ struct log_ESC_s { float esc_setpoint; uint16_t esc_setpoint_raw; }; + #pragma pack(pop) /* construct list of all message formats */ @@ -248,6 +265,7 @@ static const struct log_format_s log_formats[] = { LOG_FORMAT(ARSP, "fff", "RollRateSP,PitchRateSP,YawRateSP"), LOG_FORMAT(FLOW, "hhfffBB", "RawX,RawY,CompX,CompY,Dist,Q,SensID"), LOG_FORMAT(GPOS, "LLffff", "Lat,Lon,Alt,VelN,VelE,VelD"), + LOG_FORMAT(GPSP, "BLLfffbBffff", "AltRel,Lat,Lon,Alt,Yaw,LoiterR,LoiterDir,NavCmd,P1,P2,P3,P4"), LOG_FORMAT(ESC, "HBBBHHHHHHfH", "Counter,NumESC,Conn,No,Version,Adr,Volt,Amp,RPM,Temp,SetP,SetPRAW"), }; diff --git a/src/modules/uORB/topics/vehicle_global_position_setpoint.h b/src/modules/uORB/topics/vehicle_global_position_setpoint.h index 3ae3ff28ca..5c8ce1e4d6 100644 --- a/src/modules/uORB/topics/vehicle_global_position_setpoint.h +++ b/src/modules/uORB/topics/vehicle_global_position_setpoint.h @@ -66,7 +66,7 @@ struct vehicle_global_position_setpoint_s float altitude; /**< altitude in meters */ float yaw; /**< in radians NED -PI..+PI */ float loiter_radius; /**< loiter radius in meters, 0 for a VTOL to hover */ - uint8_t loiter_direction; /**< 1: positive / clockwise, -1, negative. */ + int8_t loiter_direction; /**< 1: positive / clockwise, -1, negative. */ enum NAV_CMD nav_cmd; /**< true if loitering is enabled */ float param1; float param2; From 3b9c306d64acdebddbf9de1d518777f11c066cbe Mon Sep 17 00:00:00 2001 From: Lorenz Meier Date: Fri, 12 Jul 2013 11:11:26 +0200 Subject: [PATCH 26/26] Hotfix for relative altitude waypoints --- src/modules/mavlink/waypoints.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/mavlink/waypoints.c b/src/modules/mavlink/waypoints.c index 405046750b..eea928a170 100644 --- a/src/modules/mavlink/waypoints.c +++ b/src/modules/mavlink/waypoints.c @@ -373,7 +373,7 @@ void check_waypoints_reached(uint64_t now, const struct vehicle_global_position_ dist = mavlink_wpm_distance_to_point_global_wgs84(wpm->current_active_wp_id, (float)global_pos->lat * 1e-7f, (float)global_pos->lon * 1e-7f, global_pos->alt); } else if (coordinate_frame == (int)MAV_FRAME_GLOBAL_RELATIVE_ALT) { - dist = mavlink_wpm_distance_to_point_global_wgs84(wpm->current_active_wp_id, global_pos->lat, global_pos->lon, global_pos->relative_alt); + dist = mavlink_wpm_distance_to_point_global_wgs84(wpm->current_active_wp_id, (float)global_pos->lat * 1e-7f, (float)global_pos->lon * 1e-7f, global_pos->relative_alt); } else if (coordinate_frame == (int)MAV_FRAME_LOCAL_ENU || coordinate_frame == (int)MAV_FRAME_LOCAL_NED) { dist = mavlink_wpm_distance_to_point_local(wpm->current_active_wp_id, local_pos->x, local_pos->y, local_pos->z);