kopia lustrzana https://github.com/DL7AD/pecanpico10
Multiple changes.
- Update APRS position packet to include timestamp. - Add ability to set air pressure level to control GPS airborne mode - Add capability for time only acquisition from GPS when fixed coord set - Revise GPIO naming to use IO1 - IO3 (2 & only if not external BME)pull/4/head
rodzic
e56eca71e6
commit
061fc953ab
|
@ -247,6 +247,9 @@
|
|||
#define LINE_GPIO_PIN PAL_LINE(GPIOA, 8U)
|
||||
#define LINE_IO_TXD PAL_LINE(GPIOB, 10U)
|
||||
#define LINE_IO_RXD PAL_LINE(GPIOC, 11U)
|
||||
#define LINE_IO1 LINE_GPIO_PIN
|
||||
#define LINE_IO2 LINE_IO_TXD
|
||||
#define LINE_IO3 LINE_IO_RXD
|
||||
|
||||
// LED
|
||||
#define LINE_IO_BLUE PAL_LINE(GPIOC, 1U)
|
||||
|
|
|
@ -131,7 +131,8 @@ const conf_t conf_flash_default = {
|
|||
.rssi = 0x3F
|
||||
},
|
||||
// Node rx identity
|
||||
.call = "VK2GJ-4"
|
||||
.call = "VK2GJ-4",
|
||||
.symbol = SYM_ANTENNA // Use this symbol in message responses
|
||||
},
|
||||
.tx = { // The transmit identity for digipeat transmit and messages responses
|
||||
.radio_conf = {
|
||||
|
@ -170,6 +171,7 @@ const conf_t conf_flash_default = {
|
|||
.gps_on_vbat = 1000,
|
||||
.gps_off_vbat = 1000,
|
||||
.gps_onper_vbat = 1000,
|
||||
.gps_airborne = 90000, // Enable airborne mode below this threshold (Pa)
|
||||
|
||||
.magic = CONFIG_MAGIC_DEFAULT // Do not remove. This is the activation bit.
|
||||
};
|
||||
|
|
|
@ -395,7 +395,7 @@ uint8_t gps_power_save(int on) {
|
|||
return gps_receive_ack(0x06, 0x11, 1000);
|
||||
}
|
||||
|
||||
bool GPS_Init(void) {
|
||||
bool GPS_Init(bool airborne) {
|
||||
// Initialize pins
|
||||
TRACE_INFO("GPS > Init pins");
|
||||
palSetLineMode(LINE_GPS_RESET, PAL_MODE_OUTPUT_PUSHPULL); // GPS reset
|
||||
|
@ -431,15 +431,16 @@ bool GPS_Init(void) {
|
|||
return false;
|
||||
}
|
||||
|
||||
cntr = 3;
|
||||
while((status = gps_set_airborne_model()) == false && cntr--);
|
||||
if(status) {
|
||||
TRACE_INFO("GPS > ... Set airborne model OK");
|
||||
} else {
|
||||
TRACE_ERROR("GPS > Communication Error [set airborne]");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(airborne) {
|
||||
cntr = 3;
|
||||
while((status = gps_set_airborne_model()) == false && cntr--);
|
||||
if(status) {
|
||||
TRACE_INFO("GPS > ... Set airborne model OK");
|
||||
} else {
|
||||
TRACE_ERROR("GPS > Communication Error [set airborne]");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ uint8_t gps_power_save(int on);
|
|||
//uint8_t gps_save_settings(void);
|
||||
bool gps_get_fix(gpsFix_t *fix);
|
||||
|
||||
bool GPS_Init(void);
|
||||
bool GPS_Init(bool airborne);
|
||||
void GPS_Deinit(void);
|
||||
uint32_t GPS_get_mcu_frequency(void);
|
||||
bool gps_calc_ubx_csum(uint8_t *mbuf, uint16_t mlen);
|
||||
|
|
|
@ -12,45 +12,52 @@ dataPoint_t* flash_getLogBuffer(uint16_t id)
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns next free log entry address in memory. Returns 0 if all cells are
|
||||
* filled with data
|
||||
* Returns next free log entry address in memory.
|
||||
* Returns NULL if all entries are used.
|
||||
*/
|
||||
static dataPoint_t* flash_getNextFreeLogAddress(void)
|
||||
{
|
||||
dataPoint_t* tp;
|
||||
for(uint32_t i=0; (tp = flash_getLogBuffer(i)) != NULL; i++)
|
||||
if(LOG_IS_EMPTY(tp))
|
||||
return tp;
|
||||
|
||||
return NULL;
|
||||
static dataPoint_t* flash_getNextFreeLogAddress(void) {
|
||||
dataPoint_t* tp;
|
||||
for(uint32_t i=0; (tp = flash_getLogBuffer(i)) != NULL; i++) {
|
||||
if(LOG_IS_EMPTY(tp))
|
||||
return tp;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dataPoint_t* flash_getNewestLogEntry(void)
|
||||
{
|
||||
dataPoint_t* last_tp = NULL;
|
||||
uint64_t last_id = 0x0;
|
||||
dataPoint_t* tp;
|
||||
for(uint32_t i=0; (tp = flash_getLogBuffer(i)) != NULL; i++) {
|
||||
if(!LOG_IS_EMPTY(tp) && last_id <= LOG_RSTandID(tp)) {
|
||||
last_id = LOG_RSTandID(tp);
|
||||
last_tp = tp;
|
||||
}
|
||||
}
|
||||
return last_tp;
|
||||
/*
|
||||
*
|
||||
*/
|
||||
dataPoint_t* flash_getNewestLogEntry(void) {
|
||||
dataPoint_t* last_tp = NULL;
|
||||
uint64_t last_id = 0x0;
|
||||
dataPoint_t* tp;
|
||||
for(uint32_t i=0; (tp = flash_getLogBuffer(i)) != NULL; i++) {
|
||||
if(!LOG_IS_EMPTY(tp) && last_id <= LOG_RSTandID(tp)) {
|
||||
last_id = LOG_RSTandID(tp);
|
||||
last_tp = tp;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return last_tp;
|
||||
}
|
||||
|
||||
dataPoint_t* flash_getOldestLogEntry(void)
|
||||
{
|
||||
dataPoint_t* first_tp = NULL;
|
||||
uint64_t first_id = 0xFFFFFFFFFFFFFFFF;
|
||||
dataPoint_t* tp;
|
||||
for(uint32_t i=0; (tp = flash_getLogBuffer(i)) != NULL; i++) {
|
||||
if(!LOG_IS_EMPTY(tp) && first_id >= LOG_RSTandID(tp)) {
|
||||
first_id = LOG_RSTandID(tp);
|
||||
first_tp = tp;
|
||||
}
|
||||
}
|
||||
return first_tp;
|
||||
/*
|
||||
*
|
||||
*/
|
||||
dataPoint_t* flash_getOldestLogEntry(void) {
|
||||
dataPoint_t* first_tp = NULL;
|
||||
uint64_t first_id = 0xFFFFFFFFFFFFFFFF;
|
||||
dataPoint_t* tp;
|
||||
for(uint32_t i=0; (tp = flash_getLogBuffer(i)) != NULL; i++) {
|
||||
if(!LOG_IS_EMPTY(tp) && first_id >= LOG_RSTandID(tp)) {
|
||||
first_id = LOG_RSTandID(tp);
|
||||
first_tp = tp;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return first_tp;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,7 +84,7 @@ void flash_writeLogDataPoint(dataPoint_t* tp)
|
|||
flash_eraseOldestLogData();
|
||||
address = flash_getNextFreeLogAddress();
|
||||
}
|
||||
if(address == NULL) // Something went wront at erasing the memory
|
||||
if(address == NULL) // Something went wrong at erasing the memory
|
||||
{
|
||||
TRACE_ERROR("LOG > Erasing flash failed");
|
||||
return;
|
||||
|
|
|
@ -70,7 +70,7 @@ void getTime(ptime_t *date) {
|
|||
RTCDateTime timespec;
|
||||
rtcGetTime(&RTCD1, ×pec);
|
||||
|
||||
date->year = timespec.year + 2000;
|
||||
date->year = timespec.year + RTC_BASE_YEAR;
|
||||
date->month = timespec.month;
|
||||
date->day = timespec.day;
|
||||
date->hour = timespec.millisecond / 3600000;
|
||||
|
@ -85,7 +85,7 @@ void getTime(ptime_t *date) {
|
|||
void setTime(ptime_t *date) {
|
||||
TRACE_INFO("GPS > Calibrate RTC");
|
||||
RTCDateTime timespec;
|
||||
timespec.year = date->year - 2000;
|
||||
timespec.year = date->year - RTC_BASE_YEAR;
|
||||
timespec.month = date->month;
|
||||
timespec.day = date->day;
|
||||
timespec.millisecond = date->hour * 3600000 + date->minute * 60000 + date->second * 1000;
|
||||
|
|
|
@ -609,7 +609,7 @@ static bool Si446x_transmit(radio_unit_t radio,
|
|||
chThdSleep(TIME_MS2I(1));
|
||||
}
|
||||
/* Clear channel timing. */
|
||||
TRACE_INFO( "SI > CCA completed in %d milliseconds",
|
||||
TRACE_INFO( "SI > CCA attained in %d milliseconds",
|
||||
chTimeI2MS(chVTTimeElapsedSinceX(t0)));
|
||||
}
|
||||
|
||||
|
@ -1298,7 +1298,7 @@ int16_t Si446x_getLastTemperature(radio_unit_t radio) {
|
|||
pktAcquireRadio(radio, TIME_INFINITE);
|
||||
// Temperature readout
|
||||
lastTemp = Si446x_getTemperature(radio);
|
||||
TRACE_INFO("SI > Transmitter temperature %d degC\r\n", lastTemp/100);
|
||||
TRACE_INFO("SI > Transmitter temperature %d degC", lastTemp/100);
|
||||
pktReleaseRadio(radio);
|
||||
} else {
|
||||
TRACE_INFO("SI > Transmitter temperature not available");
|
||||
|
|
|
@ -479,7 +479,7 @@ packet_t ax25_from_text (char *monitor, int strict)
|
|||
|
||||
if ( ! ax25_parse_addr (AX25_SOURCE, pa, strict, atemp, &ssid_temp, &heard_temp)) {
|
||||
TRACE_ERROR("PKT > Bad source address in packet");
|
||||
/* Only need single packet release here but linked probably better for consistency. */
|
||||
/* Only need single packet release here but linked would be fine for consistency. */
|
||||
pktReleasePacketBuffer(this_p);
|
||||
return (NULL);
|
||||
}
|
||||
|
@ -2509,10 +2509,12 @@ unsigned short ax25_dedupe_crc (packet_t pp)
|
|||
* There is a very very small probability that two unrelated
|
||||
* packets will result in the same checksum, and the
|
||||
* undesired dropping of the packet.
|
||||
|
||||
*
|
||||
* TODO: Deprecate - not used
|
||||
*
|
||||
*------------------------------------------------------------------------------*/
|
||||
|
||||
unsigned short ax25_m_m_crc (packet_t pp)
|
||||
/*unsigned short ax25_m_m_crc (packet_t pp)
|
||||
{
|
||||
unsigned short crc;
|
||||
unsigned char fbuf[AX25_MAX_PACKET_LEN];
|
||||
|
@ -2524,7 +2526,7 @@ unsigned short ax25_m_m_crc (packet_t pp)
|
|||
crc = crc16(fbuf, flen, crc);
|
||||
|
||||
return (crc);
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
/*------------------------------------------------------------------
|
||||
|
|
|
@ -425,7 +425,7 @@ extern int ax25_get_pid (packet_t this_p);
|
|||
|
||||
extern unsigned short ax25_dedupe_crc (packet_t pp);
|
||||
|
||||
extern unsigned short ax25_m_m_crc (packet_t pp);
|
||||
//extern unsigned short ax25_m_m_crc (packet_t pp);
|
||||
|
||||
extern void ax25_safe_print (char *, int, int ascii_only);
|
||||
|
||||
|
|
|
@ -173,9 +173,8 @@ const conf_command_t command_list[] = {
|
|||
const APRSCommand aprs_commands[] = {
|
||||
{"?aprsd", aprs_send_aprsd_message},
|
||||
{"?aprsh", aprs_send_aprsh_message},
|
||||
{"?aprsp", aprs_send_position_beacon},
|
||||
{"?aprsp", aprs_send_position_response},
|
||||
{"?gpio", aprs_execute_gpio_command},
|
||||
/* {"?gps", aprs_handle_gps_command},*/
|
||||
{"?reset", aprs_execute_system_reset},
|
||||
{"?save", aprs_execute_config_save},
|
||||
{"?img", aprs_execute_img_command},
|
||||
|
@ -270,6 +269,113 @@ void aprs_debug_getPacket(packet_t pp, char* buf, uint32_t len)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transmit APRS position packet.
|
||||
*
|
||||
* @param[in] callsign origination call sign
|
||||
* @param[in] path path to use
|
||||
* @param[in] symbol symbol for originator
|
||||
* @param[in] dataPoint position data object
|
||||
*
|
||||
* @return encoded packet object pointer
|
||||
* @retval NULL if encoding failed
|
||||
*/
|
||||
packet_t aprs_encode_position(const char *callsign,
|
||||
const char *path, aprs_sym_t symbol,
|
||||
dataPoint_t *dataPoint) {
|
||||
ptime_t time;
|
||||
unixTimestamp2Date(&time, dataPoint->gps_time);
|
||||
|
||||
// Latitude
|
||||
uint32_t y = 380926 * (90 - dataPoint->gps_lat/10000000.0);
|
||||
uint32_t y3 = y / 753571;
|
||||
uint32_t y3r = y % 753571;
|
||||
uint32_t y2 = y3r / 8281;
|
||||
uint32_t y2r = y3r % 8281;
|
||||
uint32_t y1 = y2r / 91;
|
||||
uint32_t y1r = y2r % 91;
|
||||
|
||||
// Longitude
|
||||
uint32_t x = 190463 * (180 + dataPoint->gps_lon/10000000.0);
|
||||
uint32_t x3 = x / 753571;
|
||||
uint32_t x3r = x % 753571;
|
||||
uint32_t x2 = x3r / 8281;
|
||||
uint32_t x2r = x3r % 8281;
|
||||
uint32_t x1 = x2r / 91;
|
||||
uint32_t x1r = x2r % 91;
|
||||
|
||||
// Altitude
|
||||
uint32_t a = logf(METER_TO_FEET(dataPoint->gps_alt)) / logf(1.002f);
|
||||
uint32_t a1 = a / 91;
|
||||
uint32_t a1r = a % 91;
|
||||
|
||||
uint8_t gpsFix = dataPoint->gps_state == GPS_LOCKED1
|
||||
|| dataPoint->gps_state == GPS_LOCKED2
|
||||
|| dataPoint->gps_state == GPS_FIXED ? GSP_FIX_CURRENT : GSP_FIX_OLD;
|
||||
uint8_t src = NMEA_SRC_GGA;
|
||||
uint8_t origin = ORIGIN_PICO;
|
||||
|
||||
char xmit[256];
|
||||
uint32_t len = chsnprintf(xmit, sizeof(xmit), "%s>%s,%s:@%02d%02d%02dz",
|
||||
callsign,
|
||||
APRS_DEVICE_CALLSIGN,
|
||||
path,
|
||||
time.hour,
|
||||
time.minute,
|
||||
time.second);
|
||||
|
||||
xmit[len+0] = (symbol >> 8) & 0xFF;
|
||||
xmit[len+1] = y3+33;
|
||||
xmit[len+2] = y2+33;
|
||||
xmit[len+3] = y1+33;
|
||||
xmit[len+4] = y1r+33;
|
||||
xmit[len+5] = x3+33;
|
||||
xmit[len+6] = x2+33;
|
||||
xmit[len+7] = x1+33;
|
||||
xmit[len+8] = x1r+33;
|
||||
xmit[len+9] = symbol & 0xFF;
|
||||
xmit[len+10] = a1+33;
|
||||
xmit[len+11] = a1r+33;
|
||||
xmit[len+12] = ((gpsFix << 5) | (src << 3) | origin) + 33;
|
||||
|
||||
// Comments
|
||||
uint32_t len2 = base91_encode((uint8_t*)dataPoint,
|
||||
(uint8_t*)&xmit[len+13],
|
||||
sizeof(dataPoint_t));
|
||||
|
||||
xmit[len+len2+13] = '|';
|
||||
|
||||
/* APRS base91 encoded telemetry. */
|
||||
// Sequence ID
|
||||
uint32_t t = dataPoint->id & 0x1FFF;
|
||||
xmit[len+len2+14] = t/91 + 33;
|
||||
xmit[len+len2+15] = t%91 + 33;
|
||||
|
||||
// Telemetry analog parameters
|
||||
for(uint8_t i=0; i<5; i++) {
|
||||
switch(i) {
|
||||
case 0: t = dataPoint->adc_vbat; break;
|
||||
case 1: t = dataPoint->adc_vsol; break;
|
||||
case 2: t = dataPoint->pac_pbat+4096; break;
|
||||
case 3: t = dataPoint->sen_i1_temp/10 + 1000; break;
|
||||
case 4: t = dataPoint->sen_i1_press/125 - 40; break;
|
||||
}
|
||||
|
||||
xmit[len+len2+16+i*2] = t/91 + 33;
|
||||
xmit[len+len2+16+i*2+1] = t%91 + 33;
|
||||
}
|
||||
|
||||
// Telemetry digital parameter
|
||||
xmit[len+len2+26] = dataPoint->gpio + 33;
|
||||
|
||||
/* Digital bits second byte - set zero. */
|
||||
xmit[len+len2+27] = 33;
|
||||
xmit[len+len2+28] = '|';
|
||||
xmit[len+len2+29] = 0;
|
||||
|
||||
return ax25_from_text(xmit, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transmit APRS position and telemetry packet.
|
||||
* @notes Base 91 telemetry encoding is used.
|
||||
|
@ -291,8 +397,10 @@ void aprs_debug_getPacket(packet_t pp, char* buf, uint32_t len)
|
|||
* @retval NULL if encoding failed
|
||||
*/
|
||||
packet_t aprs_encode_position_and_telemetry(const char *callsign,
|
||||
const char *path, uint16_t symbol,
|
||||
dataPoint_t *dataPoint) {
|
||||
const char *path, aprs_sym_t symbol,
|
||||
dataPoint_t *dataPoint,
|
||||
bool extended) {
|
||||
(void)extended;
|
||||
// Latitude
|
||||
uint32_t y = 380926 * (90 - dataPoint->gps_lat/10000000.0);
|
||||
uint32_t y3 = y / 753571;
|
||||
|
@ -316,15 +424,24 @@ packet_t aprs_encode_position_and_telemetry(const char *callsign,
|
|||
uint32_t a1 = a / 91;
|
||||
uint32_t a1r = a % 91;
|
||||
|
||||
uint8_t gpsFix = dataPoint->gps_state == GPS_LOCKED1
|
||||
|| dataPoint->gps_state == GPS_LOCKED2
|
||||
|| dataPoint->gps_state == GPS_FIXED ? GSP_FIX_CURRENT : GSP_FIX_OLD;
|
||||
uint8_t src = NMEA_SRC_GGA;
|
||||
uint8_t origin = ORIGIN_PICO;
|
||||
ptime_t time;
|
||||
unixTimestamp2Date(&time, dataPoint->gps_time);
|
||||
|
||||
char xmit[256];
|
||||
uint32_t len = chsnprintf(xmit, sizeof(xmit), "%s>%s,%s:!",
|
||||
callsign, APRS_DEVICE_CALLSIGN, path);
|
||||
uint32_t len = chsnprintf(xmit, sizeof(xmit), "%s>%s,%s:@%02d%02d%02dz",
|
||||
callsign,
|
||||
APRS_DEVICE_CALLSIGN,
|
||||
path,
|
||||
time.hour,
|
||||
time.minute,
|
||||
time.second);
|
||||
|
||||
uint8_t gpsFix = dataPoint->gps_state == GPS_LOCKED1
|
||||
|| dataPoint->gps_state == GPS_LOCKED2
|
||||
|| dataPoint->gps_state == GPS_FIXED ? GSP_FIX_CURRENT : GSP_FIX_OLD;
|
||||
|
||||
uint8_t src = NMEA_SRC_GGA;
|
||||
uint8_t origin = ORIGIN_PICO;
|
||||
|
||||
xmit[len+0] = (symbol >> 8) & 0xFF;
|
||||
xmit[len+1] = y3+33;
|
||||
|
@ -378,6 +495,9 @@ packet_t aprs_encode_position_and_telemetry(const char *callsign,
|
|||
return ax25_from_text(xmit, true);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
packet_t aprs_encode_data_packet(const char *callsign, const char *path,
|
||||
char packetType, uint8_t *data)
|
||||
{
|
||||
|
@ -555,101 +675,120 @@ msg_t aprs_execute_gpio_command(aprs_identity_t *id,
|
|||
if(argc != 1)
|
||||
return MSG_ERROR;
|
||||
|
||||
if(!strcmp(argv[0], "pa8:1")) {
|
||||
TRACE_INFO("RX > Message: GPIO query PA8 HIGH");
|
||||
palSetPadMode(GPIOA, 8, PAL_MODE_OUTPUT_PUSHPULL);
|
||||
palSetPad(GPIOA, 8);
|
||||
return MSG_OK;
|
||||
}
|
||||
|
||||
if(!strcmp(argv[0], "pa8:0")) {
|
||||
TRACE_INFO("RX > Message: GPIO query PA8 LOW");
|
||||
palSetPadMode(GPIOA, 8, PAL_MODE_OUTPUT_PUSHPULL);
|
||||
palClearPad(GPIOA, 8);
|
||||
return MSG_OK;
|
||||
}
|
||||
|
||||
if(!strcmp(argv[0], "pa8:?")) {
|
||||
char buf[AX25_MAX_APRS_MSG_LEN + 1];
|
||||
/* TODO: Need to read mode and if not output then report as "input" etc. */
|
||||
chsnprintf(buf, sizeof(buf),
|
||||
"PA8 is %s ",
|
||||
(palReadPad(GPIOA, 8) == PAL_HIGH) ? "HIGH" : "LOW");
|
||||
|
||||
packet_t pp = aprs_encode_message(id->call, id->path, id->src, buf, false);
|
||||
if(pp == NULL) {
|
||||
TRACE_WARN("RX > No free packet objects or badly formed message");
|
||||
return MSG_ERROR;
|
||||
}
|
||||
if(!transmitOnRadio(pp,
|
||||
id->freq,
|
||||
0,
|
||||
0,
|
||||
id->pwr,
|
||||
id->mod,
|
||||
id->cca)) {
|
||||
TRACE_ERROR("RX > Transmit of GPIO status failed");
|
||||
return MSG_ERROR;
|
||||
}
|
||||
return MSG_OK;
|
||||
}
|
||||
return MSG_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Handle GPS command
|
||||
*
|
||||
* @param[in] id aprs node identity
|
||||
* @param[in] argc number of parameters
|
||||
* @param[in] argv array of pointers to parameter strings
|
||||
*
|
||||
* @return result of command
|
||||
* @retval MSG_OK if the command completed.
|
||||
* @retval MSG_ERROR if there was an error.
|
||||
*/
|
||||
/*msg_t aprs_handle_gps_command(aprs_identity_t *id,
|
||||
int argc, char *argv[]) {
|
||||
if(argc != 1)
|
||||
char *tok = strtok(argv[0], ":");
|
||||
if(tok == NULL)
|
||||
return MSG_ERROR;
|
||||
|
||||
if(!strcmp(argv[0], "fixed")) {
|
||||
TRACE_INFO("RX > Message: GPS set to fixed location");
|
||||
test_gps_enabled = true;
|
||||
/* TODO: WIP to generalize by parsing out the port # and operation. */
|
||||
if(!strcmp(argv[0], "io1:1")) {
|
||||
TRACE_INFO("RX > Message: GPIO set IO1 HIGH");
|
||||
palSetLineMode(LINE_IO1, PAL_MODE_OUTPUT_PUSHPULL);
|
||||
palSetLine(LINE_IO1);
|
||||
return MSG_OK;
|
||||
}
|
||||
|
||||
if(!strcmp(argv[0], "normal")) {
|
||||
TRACE_INFO("RX > Message: GPS set to normal operation");
|
||||
test_gps_enabled = false;
|
||||
if(!strcmp(argv[0], "io1:0")) {
|
||||
TRACE_INFO("RX > Message: GPIO set IO1 LOW");
|
||||
palSetLineMode(LINE_IO1, PAL_MODE_OUTPUT_PUSHPULL);
|
||||
palClearLine(LINE_IO1);
|
||||
return MSG_OK;
|
||||
}
|
||||
|
||||
if(!strcmp(argv[0], "status")) {
|
||||
char buf[AX25_MAX_APRS_MSG_LEN + 1];
|
||||
#if ENABLE_EXTERNAL_I2C != TRUE
|
||||
|
||||
chsnprintf(buf, sizeof(buf),
|
||||
"GPS is %s",
|
||||
test_gps_enabled ? "fixed" : "normal");
|
||||
if(!strcmp(argv[0], "io2:1")) {
|
||||
TRACE_INFO("RX > Message: GPIO set IO2 HIGH");
|
||||
palSetLineMode(LINE_IO2, PAL_MODE_OUTPUT_PUSHPULL);
|
||||
palSetLine(LINE_IO2);
|
||||
return MSG_OK;
|
||||
}
|
||||
|
||||
packet_t pp = aprs_encode_message(id->call, id->path, id->src, buf, false);
|
||||
if(pp == NULL) {
|
||||
TRACE_WARN("RX > No free packet objects or badly formed message");
|
||||
return MSG_ERROR;
|
||||
if(!strcmp(argv[0], "io2:0")) {
|
||||
TRACE_INFO("RX > Message: GPIO set IO2 LOW");
|
||||
palSetLineMode(LINE_IO2, PAL_MODE_OUTPUT_PUSHPULL);
|
||||
palClearLine(LINE_IO2);
|
||||
return MSG_OK;
|
||||
}
|
||||
|
||||
if(!strcmp(argv[0], "io3:1")) {
|
||||
TRACE_INFO("RX > Message: GPIO set IO3 HIGH");
|
||||
palSetLineMode(LINE_IO3, PAL_MODE_OUTPUT_PUSHPULL);
|
||||
palSetLine(LINE_IO3);
|
||||
return MSG_OK;
|
||||
}
|
||||
|
||||
if(!strcmp(argv[0], "io3:0")) {
|
||||
TRACE_INFO("RX > Message: GPIO set IO3 LOW");
|
||||
palSetLineMode(LINE_IO3, PAL_MODE_OUTPUT_PUSHPULL);
|
||||
palClearLine(LINE_IO3);
|
||||
return MSG_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* TODO: Parse out IO number and reduce above and below ugly DRY code. */
|
||||
packet_t pp;
|
||||
do {
|
||||
if(!strcmp(argv[0], "io1:?")) {
|
||||
char buf[AX25_MAX_APRS_MSG_LEN + 1];
|
||||
/* TODO: Need to read mode and if not output then report as "input" etc. */
|
||||
chsnprintf(buf, sizeof(buf),
|
||||
"IO1 is %s ",
|
||||
(palReadLine(LINE_IO1) == PAL_HIGH) ? "HIGH" : "LOW");
|
||||
TRACE_INFO("RX > Message: GPIO query IO1 is %s", (palReadLine(LINE_IO1) == PAL_HIGH) ? "HIGH" : "LOW");
|
||||
pp = aprs_encode_message(id->call, id->path, id->src, buf, false);
|
||||
if(pp == NULL) {
|
||||
TRACE_WARN("RX > No free packet objects or badly formed message");
|
||||
return MSG_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(!transmitOnRadio(pp,
|
||||
id->freq,
|
||||
0,
|
||||
0,
|
||||
id->pwr,
|
||||
id->mod,
|
||||
id->rssi)) {
|
||||
TRACE_ERROR("RX > Transmit of GPS status failed");
|
||||
return MSG_ERROR;
|
||||
|
||||
#if ENABLE_EXTERNAL_I2C != TRUE
|
||||
if(!strcmp(argv[0], "io2:?")) {
|
||||
char buf[AX25_MAX_APRS_MSG_LEN + 1];
|
||||
/* TODO: Need to read mode and if not output then report as "input" etc. */
|
||||
chsnprintf(buf, sizeof(buf),
|
||||
"IO2 is %s ",
|
||||
(palReadLine(LINE_IO2) == PAL_HIGH) ? "HIGH" : "LOW");
|
||||
TRACE_INFO("RX > Message: GPIO query IO2 is %s", (palReadLine(LINE_IO2) == PAL_HIGH) ? "HIGH" : "LOW");
|
||||
pp = aprs_encode_message(id->call, id->path, id->src, buf, false);
|
||||
if(pp == NULL) {
|
||||
TRACE_WARN("RX > No free packet objects or badly formed message");
|
||||
return MSG_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return MSG_OK;
|
||||
|
||||
if(!strcmp(argv[0], "io3:?")) {
|
||||
char buf[AX25_MAX_APRS_MSG_LEN + 1];
|
||||
/* TODO: Need to read mode and if not output then report as "input" etc. */
|
||||
chsnprintf(buf, sizeof(buf),
|
||||
"IO3 is %s ",
|
||||
(palReadLine(LINE_IO3) == PAL_HIGH) ? "HIGH" : "LOW");
|
||||
TRACE_INFO("RX > Message: GPIO query IO3 is %s", (palReadLine(LINE_IO3) == PAL_HIGH) ? "HIGH" : "LOW");
|
||||
pp = aprs_encode_message(id->call, id->path, id->src, buf, false);
|
||||
if(pp == NULL) {
|
||||
TRACE_WARN("RX > No free packet objects or badly formed message");
|
||||
return MSG_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* No known IO port found. */
|
||||
return MSG_ERROR;
|
||||
#endif
|
||||
} while(true);
|
||||
if(!transmitOnRadio(pp,
|
||||
id->freq,
|
||||
0,
|
||||
0,
|
||||
id->pwr,
|
||||
id->mod,
|
||||
id->cca)) {
|
||||
TRACE_ERROR("RX > Transmit of GPIO status failed");
|
||||
return MSG_ERROR;
|
||||
}
|
||||
return MSG_ERROR;
|
||||
}*/
|
||||
return MSG_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Request for position beacon to be sent
|
||||
|
@ -662,16 +801,17 @@ msg_t aprs_execute_gpio_command(aprs_identity_t *id,
|
|||
* @retval MSG_OK if the command completed.
|
||||
* @retval MSG_ERROR if there was an error.
|
||||
*/
|
||||
msg_t aprs_send_position_beacon(aprs_identity_t *id,
|
||||
msg_t aprs_send_position_response(aprs_identity_t *id,
|
||||
int argc, char *argv[]) {
|
||||
(void)argv;
|
||||
|
||||
if(argc != 0)
|
||||
return MSG_ERROR;
|
||||
|
||||
/* TODO: Implement a simple (non base 91) position parameter. */
|
||||
TRACE_INFO("RX > Message: Position query");
|
||||
dataPoint_t* dataPoint = getLastDataPoint();
|
||||
packet_t pp = aprs_encode_position_and_telemetry(id->call,
|
||||
packet_t pp = aprs_encode_position(id->call,
|
||||
id->path,
|
||||
id->symbol,
|
||||
dataPoint);
|
||||
|
@ -686,7 +826,7 @@ msg_t aprs_send_position_beacon(aprs_identity_t *id,
|
|||
id->pwr,
|
||||
id->mod,
|
||||
id->cca)) {
|
||||
TRACE_ERROR("RX > Transmit of APRSD failed");
|
||||
TRACE_ERROR("RX > Transmit of APRSP failed");
|
||||
return MSG_ERROR;
|
||||
}
|
||||
return MSG_OK;
|
||||
|
@ -951,6 +1091,7 @@ static bool aprs_decode_message(packet_t pp) {
|
|||
&& (conf_sram.aprs.thread_conf.active);
|
||||
if(aprs_rx) {
|
||||
strcpy(identity.call, conf_sram.aprs.rx.call);
|
||||
identity.symbol = conf_sram.aprs.rx.symbol;
|
||||
/* Other parameters come from tx identity. */
|
||||
}
|
||||
|
||||
|
|
|
@ -87,8 +87,13 @@ extern bool test_gps_enabled;
|
|||
extern "C" {
|
||||
#endif
|
||||
void aprs_debug_getPacket(packet_t pp, char* buf, uint32_t len);
|
||||
packet_t aprs_encode_position_and_telemetry(const char *callsign, const char *path,
|
||||
uint16_t symbol, dataPoint_t *dataPoint);
|
||||
packet_t aprs_encode_position(const char *callsign,
|
||||
const char *path, aprs_sym_t symbol,
|
||||
dataPoint_t *dataPoint);
|
||||
packet_t aprs_encode_position_and_telemetry(const char *callsign,
|
||||
const char *path,
|
||||
aprs_sym_t symbol,
|
||||
dataPoint_t *dataPoint, bool extended);
|
||||
packet_t aprs_encode_telemetry_configuration(const char *originator,
|
||||
const char *path,
|
||||
const char *destination,
|
||||
|
@ -101,7 +106,7 @@ extern "C" {
|
|||
packet_t aprs_compose_aprsd_message(const char *callsign, const char *path,
|
||||
const char *receiver);
|
||||
void aprs_decode_packet(packet_t pp);
|
||||
msg_t aprs_send_position_beacon(aprs_identity_t *id,
|
||||
msg_t aprs_send_position_response(aprs_identity_t *id,
|
||||
int argc, char *argv[]);
|
||||
msg_t aprs_send_aprsd_message(aprs_identity_t *id,
|
||||
int argc, char *argv[]);
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
static dataPoint_t dataPoints[2];
|
||||
static dataPoint_t* lastDataPoint;
|
||||
static bool threadStarted = false;
|
||||
static uint8_t attached = 0;
|
||||
static uint8_t useGPS = 0;
|
||||
|
||||
/**
|
||||
* Returns most recent data point which is complete.
|
||||
|
@ -46,94 +46,99 @@ void waitForNewDataPoint(void) {
|
|||
*/
|
||||
static void aquirePosition(dataPoint_t* tp, dataPoint_t* ltp,
|
||||
sysinterval_t timeout) {
|
||||
sysinterval_t start = chVTGetSystemTime();
|
||||
sysinterval_t start = chVTGetSystemTime();
|
||||
|
||||
gpsFix_t gpsFix;
|
||||
memset(&gpsFix, 0, sizeof(gpsFix_t));
|
||||
gpsFix_t gpsFix;
|
||||
memset(&gpsFix, 0, sizeof(gpsFix_t));
|
||||
|
||||
// Switch on GPS if enough power is available and GPS is needed by any position thread
|
||||
uint16_t batt = stm32_get_vbat();
|
||||
if(batt < conf_sram.gps_on_vbat) {
|
||||
tp->gps_state = GPS_LOWBATT1;
|
||||
} else {
|
||||
// Switch on GPS if enough power is available and GPS is needed by any position thread
|
||||
uint16_t batt = stm32_get_vbat();
|
||||
if(batt < conf_sram.gps_on_vbat) {
|
||||
tp->gps_state = GPS_LOWBATT1;
|
||||
} else {
|
||||
|
||||
// Switch on GPS
|
||||
bool status = GPS_Init();
|
||||
// Switch on GPS
|
||||
bool airborne = ltp->sen_i1_press/10 < conf_sram.gps_airborne;
|
||||
TRACE_INFO("COLL > GPS %s in airborne mode", airborne ? "is" : "is not");
|
||||
bool status = GPS_Init(airborne);
|
||||
|
||||
if(status) {
|
||||
// Search for lock as long as enough power is available
|
||||
do {
|
||||
batt = stm32_get_vbat();
|
||||
gps_get_fix(&gpsFix);
|
||||
} while(!isGPSLocked(&gpsFix)
|
||||
&& batt >= conf_sram.gps_off_vbat
|
||||
&& chVTIsSystemTimeWithin(start, start + timeout)); // Do as long no GPS lock and within timeout, timeout=cycle-1sec (-3sec in order to keep synchronization)
|
||||
if(status) {
|
||||
// Search for lock as long as enough power is available
|
||||
do {
|
||||
batt = stm32_get_vbat();
|
||||
gps_get_fix(&gpsFix);
|
||||
} while(!isGPSLocked(&gpsFix)
|
||||
&& batt >= conf_sram.gps_off_vbat
|
||||
&& chVTIsSystemTimeWithin(start, start + timeout)); // Do as long no GPS lock and within timeout, timeout=cycle-1sec (-3sec in order to keep synchronization)
|
||||
|
||||
if(batt < conf_sram.gps_off_vbat) { // GPS was switched on but prematurely switched off because the battery is low on power, switch off GPS
|
||||
if(batt < conf_sram.gps_off_vbat) { // GPS was switched on but prematurely switched off because the battery is low on power, switch off GPS
|
||||
|
||||
GPS_Deinit();
|
||||
TRACE_WARN("COLL > GPS sampling finished GPS LOW BATT");
|
||||
tp->gps_state = GPS_LOWBATT2;
|
||||
GPS_Deinit();
|
||||
TRACE_WARN("COLL > GPS sampling finished GPS LOW BATT");
|
||||
tp->gps_state = GPS_LOWBATT2;
|
||||
|
||||
} else if(!isGPSLocked(&gpsFix)) { // GPS was switched on but it failed to get a lock, keep GPS switched on
|
||||
} else if(!isGPSLocked(&gpsFix)) { // GPS was switched on but it failed to get a lock, keep GPS switched on
|
||||
|
||||
TRACE_WARN("COLL > GPS sampling finished GPS LOSS");
|
||||
tp->gps_state = GPS_LOSS;
|
||||
TRACE_WARN("COLL > GPS sampling finished GPS LOSS");
|
||||
tp->gps_state = GPS_LOSS;
|
||||
|
||||
} else { // GPS locked successfully, switch off GPS (unless cycle is less than 60 seconds)
|
||||
} else { // GPS locked successfully, switch off GPS (unless cycle is less than 60 seconds)
|
||||
|
||||
// Switch off GPS (if cycle time is more than 60 seconds)
|
||||
if(timeout < TIME_S2I(60)) {
|
||||
TRACE_INFO("COLL > Keep GPS switched on because cycle < 60sec");
|
||||
tp->gps_state = GPS_LOCKED2;
|
||||
} else if(conf_sram.gps_onper_vbat != 0 && batt >= conf_sram.gps_onper_vbat) {
|
||||
TRACE_INFO("COLL > Keep GPS switched on because VBAT >= %dmV", conf_sram.gps_onper_vbat);
|
||||
tp->gps_state = GPS_LOCKED2;
|
||||
} else {
|
||||
TRACE_INFO("COLL > Switch off GPS");
|
||||
GPS_Deinit();
|
||||
tp->gps_state = GPS_LOCKED1;
|
||||
}
|
||||
// Switch off GPS (if cycle time is more than 60 seconds)
|
||||
if(timeout < TIME_S2I(60)) {
|
||||
TRACE_INFO("COLL > Keep GPS switched on because cycle < 60sec");
|
||||
tp->gps_state = GPS_LOCKED2;
|
||||
} else if(conf_sram.gps_onper_vbat != 0 && batt >= conf_sram.gps_onper_vbat) {
|
||||
TRACE_INFO("COLL > Keep GPS switched on because VBAT >= %dmV", conf_sram.gps_onper_vbat);
|
||||
tp->gps_state = GPS_LOCKED2;
|
||||
} else {
|
||||
TRACE_INFO("COLL > Switch off GPS");
|
||||
GPS_Deinit();
|
||||
tp->gps_state = GPS_LOCKED1;
|
||||
}
|
||||
|
||||
// Debug
|
||||
TRACE_INFO("COLL > GPS sampling finished GPS LOCK");
|
||||
// Debug
|
||||
TRACE_INFO("COLL > GPS sampling finished GPS LOCK");
|
||||
|
||||
// Calibrate RTC
|
||||
setTime(&gpsFix.time);
|
||||
// Calibrate RTC
|
||||
setTime(&gpsFix.time);
|
||||
|
||||
// Take time from GPS
|
||||
tp->gps_time = date2UnixTimestamp(&gpsFix.time);
|
||||
// Take time from GPS
|
||||
tp->gps_time = date2UnixTimestamp(&gpsFix.time);
|
||||
|
||||
// Set new GPS fix
|
||||
tp->gps_lat = gpsFix.lat;
|
||||
tp->gps_lon = gpsFix.lon;
|
||||
tp->gps_alt = gpsFix.alt;
|
||||
// Set new GPS fix
|
||||
tp->gps_lat = gpsFix.lat;
|
||||
tp->gps_lon = gpsFix.lon;
|
||||
tp->gps_alt = gpsFix.alt;
|
||||
|
||||
tp->gps_sats = gpsFix.num_svs;
|
||||
tp->gps_pdop = (gpsFix.pdop+3)/5;
|
||||
}
|
||||
tp->gps_sats = gpsFix.num_svs;
|
||||
tp->gps_pdop = (gpsFix.pdop+3)/5;
|
||||
}
|
||||
|
||||
} else { // GPS communication error
|
||||
} else { // GPS communication error
|
||||
|
||||
GPS_Deinit();
|
||||
tp->gps_state = GPS_ERROR;
|
||||
GPS_Deinit();
|
||||
tp->gps_state = GPS_ERROR;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp->gps_ttff = TIME_I2S(chVTGetSystemTime() - start); // Time to first fix
|
||||
tp->gps_ttff = TIME_I2S(chVTGetSystemTime() - start); // Time to first fix
|
||||
|
||||
if(tp->gps_state != GPS_LOCKED1 && tp->gps_state != GPS_LOCKED2) { // We have no valid GPS fix
|
||||
// Take time from internal RTC
|
||||
ptime_t time;
|
||||
getTime(&time);
|
||||
tp->gps_time = date2UnixTimestamp(&time);
|
||||
if(tp->gps_state != GPS_LOCKED1 && tp->gps_state != GPS_LOCKED2) { // We have no valid GPS fix
|
||||
// Take time from internal RTC
|
||||
ptime_t time;
|
||||
getTime(&time);
|
||||
if(time.year != RTC_BASE_YEAR) {
|
||||
/* RTC has been set so OK to use data. */
|
||||
tp->gps_time = date2UnixTimestamp(&time);
|
||||
|
||||
// Take GPS fix from old lock
|
||||
tp->gps_lat = ltp->gps_lat;
|
||||
tp->gps_lon = ltp->gps_lon;
|
||||
tp->gps_alt = ltp->gps_alt;
|
||||
}
|
||||
// Take GPS fix from old lock
|
||||
tp->gps_lat = ltp->gps_lat;
|
||||
tp->gps_lon = ltp->gps_lon;
|
||||
tp->gps_alt = ltp->gps_alt;
|
||||
} /* Else don't set tp data since RTC is not valid. */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -259,132 +264,179 @@ static void setSystemStatus(dataPoint_t* tp) {
|
|||
* Data Collector (Thread)
|
||||
*/
|
||||
THD_FUNCTION(collectorThread, arg) {
|
||||
uint8_t *attached = arg;
|
||||
uint8_t *useGPS = arg;
|
||||
|
||||
uint32_t id = 0;
|
||||
lastDataPoint = &dataPoints[0];
|
||||
uint32_t id = 0;
|
||||
|
||||
// Read time from RTC
|
||||
ptime_t time;
|
||||
getTime(&time);
|
||||
lastDataPoint->gps_time = date2UnixTimestamp(&time);
|
||||
// Read time from RTC
|
||||
ptime_t time;
|
||||
getTime(&time);
|
||||
dataPoints[0].gps_time = date2UnixTimestamp(&time);
|
||||
dataPoints[1].gps_time = date2UnixTimestamp(&time);
|
||||
|
||||
// Get last data point from memory
|
||||
TRACE_INFO("COLL > Read last data point from flash memory");
|
||||
dataPoint_t* lastLogPoint = flash_getNewestLogEntry();
|
||||
lastDataPoint = &dataPoints[0];
|
||||
|
||||
if(lastLogPoint != NULL) { // If there is stored data point, then get the last know GPS fix
|
||||
dataPoints[0].reset = lastLogPoint->reset+1;
|
||||
dataPoints[1].reset = lastLogPoint->reset+1;
|
||||
lastDataPoint->gps_lat = lastLogPoint->gps_lat;
|
||||
lastDataPoint->gps_lon = lastLogPoint->gps_lon;
|
||||
lastDataPoint->gps_alt = lastLogPoint->gps_alt;
|
||||
lastDataPoint->gps_sats = lastLogPoint->gps_sats;
|
||||
lastDataPoint->gps_ttff = lastLogPoint->gps_ttff;
|
||||
// Get last data point from memory
|
||||
TRACE_INFO("COLL > Read last data point from flash memory");
|
||||
dataPoint_t* lastLogPoint = flash_getNewestLogEntry();
|
||||
|
||||
TRACE_INFO(
|
||||
"COLL > Last data point (from memory)\r\n"
|
||||
"%s Reset %d ID %d\r\n"
|
||||
"%s Latitude: %d.%07ddeg\r\n"
|
||||
"%s Longitude: %d.%07ddeg\r\n"
|
||||
"%s Altitude: %d Meter",
|
||||
TRACE_TAB, lastLogPoint->reset, lastLogPoint->id,
|
||||
TRACE_TAB, lastDataPoint->gps_lat/10000000, (lastDataPoint->gps_lat > 0 ? 1:-1)*lastDataPoint->gps_lat%10000000,
|
||||
TRACE_TAB, lastDataPoint->gps_lon/10000000, (lastDataPoint->gps_lon > 0 ? 1:-1)*lastDataPoint->gps_lon%10000000,
|
||||
TRACE_TAB, lastDataPoint->gps_alt
|
||||
);
|
||||
} else {
|
||||
TRACE_INFO("COLL > No data point found in flash memory");
|
||||
}
|
||||
if(lastLogPoint != NULL) { // If there is stored data point, then get it.
|
||||
dataPoints[0].reset = lastLogPoint->reset+1;
|
||||
dataPoints[1].reset = lastLogPoint->reset+1;
|
||||
unixTimestamp2Date(&time, lastDataPoint->gps_time);
|
||||
lastDataPoint->gps_lat = lastLogPoint->gps_lat;
|
||||
lastDataPoint->gps_lon = lastLogPoint->gps_lon;
|
||||
lastDataPoint->gps_alt = lastLogPoint->gps_alt;
|
||||
lastDataPoint->gps_sats = lastLogPoint->gps_sats;
|
||||
lastDataPoint->gps_ttff = lastLogPoint->gps_ttff;
|
||||
|
||||
lastDataPoint->gps_state = GPS_LOG; // Mark dataPoint as LOG packet
|
||||
TRACE_INFO(
|
||||
"COLL > Last data point (from memory)\r\n"
|
||||
"%s Reset %d ID %d\r\n"
|
||||
"%s Time %04d-%02d-%02d %02d:%02d:%02d\r\n"
|
||||
"%s Latitude: %d.%07ddeg\r\n"
|
||||
"%s Longitude: %d.%07ddeg\r\n"
|
||||
"%s Altitude: %d Meter",
|
||||
TRACE_TAB, lastLogPoint->reset, lastLogPoint->id,
|
||||
TRACE_TAB, time.year, time.month, time.day, time.hour,
|
||||
time.minute, time.day,
|
||||
TRACE_TAB, lastDataPoint->gps_lat/10000000,
|
||||
(lastDataPoint->gps_lat > 0
|
||||
? 1:-1)*lastDataPoint->gps_lat%10000000,
|
||||
TRACE_TAB, lastDataPoint->gps_lon/10000000,
|
||||
(lastDataPoint->gps_lon > 0
|
||||
? 1:-1)*lastDataPoint->gps_lon%10000000,
|
||||
TRACE_TAB, lastDataPoint->gps_alt
|
||||
);
|
||||
lastDataPoint->gps_state = GPS_LOG; // Mark dataPoint as LOG packet
|
||||
} else {
|
||||
TRACE_INFO("COLL > No data point found in flash memory");
|
||||
lastDataPoint->gps_state = GPS_OFF; // Mark dataPoint unset
|
||||
}
|
||||
|
||||
// Measure telemetry
|
||||
measureVoltage(lastDataPoint);
|
||||
getSensors(lastDataPoint);
|
||||
getGPIO(lastDataPoint);
|
||||
setSystemStatus(lastDataPoint);
|
||||
// Measure telemetry
|
||||
measureVoltage(lastDataPoint);
|
||||
getSensors(lastDataPoint);
|
||||
getGPIO(lastDataPoint);
|
||||
setSystemStatus(lastDataPoint);
|
||||
|
||||
// Write data point to Flash memory
|
||||
flash_writeLogDataPoint(lastDataPoint);
|
||||
// Write data point to Flash memory
|
||||
flash_writeLogDataPoint(lastDataPoint);
|
||||
|
||||
// Wait for position threads to start
|
||||
chThdSleep(TIME_MS2I(500));
|
||||
// Wait for position threads to start
|
||||
chThdSleep(TIME_MS2I(500));
|
||||
|
||||
sysinterval_t cycle_time = chVTGetSystemTime();
|
||||
while(true)
|
||||
{
|
||||
TRACE_INFO("COLL > Do module DATA COLLECTOR cycle");
|
||||
sysinterval_t cycle_time = chVTGetSystemTime();
|
||||
while(true)
|
||||
{
|
||||
TRACE_INFO("COLL > Do module DATA COLLECTOR cycle");
|
||||
|
||||
dataPoint_t* tp = &dataPoints[(id+1) % 2]; // Current data point (the one which is processed now)
|
||||
dataPoint_t* ltp = &dataPoints[ id % 2]; // Last data point
|
||||
dataPoint_t* tp = &dataPoints[(id+1) % 2]; // Current data point (the one which is processed now)
|
||||
dataPoint_t* ltp = &dataPoints[ id % 2]; // Last data point
|
||||
|
||||
// Determine cycle time and if GPS should be used.
|
||||
sysinterval_t data_cycle_time = TIME_S2I(600);
|
||||
if(conf_sram.pos_pri.thread_conf.active && conf_sram.pos_sec.thread_conf.active) { // Both position threads are active
|
||||
data_cycle_time = conf_sram.pos_pri.thread_conf.cycle < conf_sram.pos_sec.thread_conf.cycle ? conf_sram.pos_pri.thread_conf.cycle : conf_sram.pos_sec.thread_conf.cycle; // Choose the smallest cycle
|
||||
(*attached)++;
|
||||
} else if(conf_sram.pos_pri.thread_conf.active) { // Only primary position thread is active
|
||||
data_cycle_time = conf_sram.pos_pri.thread_conf.cycle;
|
||||
(*attached)++;
|
||||
} else if(conf_sram.pos_sec.thread_conf.active) { // Only secondary position thread is active
|
||||
data_cycle_time = conf_sram.pos_sec.thread_conf.cycle;
|
||||
(*attached)++;
|
||||
} else if(conf_sram.aprs.thread_conf.active && conf_sram.aprs.tx.beacon) { // APRS beacon is active
|
||||
data_cycle_time = conf_sram.pos_sec.thread_conf.cycle;
|
||||
if(conf_sram.aprs.tx.gps)
|
||||
(*attached)++;
|
||||
} else { // There must be an error
|
||||
TRACE_ERROR("COLL > Data collector started but no position thread is active");
|
||||
}
|
||||
// Determine cycle time and if GPS should be used.
|
||||
sysinterval_t data_cycle_time = TIME_S2I(600);
|
||||
if(conf_sram.pos_pri.thread_conf.active && conf_sram.pos_sec.thread_conf.active) { // Both position threads are active
|
||||
data_cycle_time = conf_sram.pos_pri.thread_conf.cycle < conf_sram.pos_sec.thread_conf.cycle ? conf_sram.pos_pri.thread_conf.cycle : conf_sram.pos_sec.thread_conf.cycle; // Choose the smallest cycle
|
||||
(*useGPS)++;
|
||||
} else if(conf_sram.pos_pri.thread_conf.active) { // Only primary position thread is active
|
||||
data_cycle_time = conf_sram.pos_pri.thread_conf.cycle;
|
||||
(*useGPS)++;
|
||||
} else if(conf_sram.pos_sec.thread_conf.active) { // Only secondary position thread is active
|
||||
data_cycle_time = conf_sram.pos_sec.thread_conf.cycle;
|
||||
(*useGPS)++;
|
||||
} else if(conf_sram.aprs.thread_conf.active && conf_sram.aprs.tx.beacon) { // APRS beacon is active
|
||||
data_cycle_time = conf_sram.aprs.tx.cycle;
|
||||
if(conf_sram.aprs.tx.gps) {
|
||||
(*useGPS)++;
|
||||
}
|
||||
} else { // There must be an error
|
||||
TRACE_ERROR("COLL > Data collector started but no position thread is active");
|
||||
}
|
||||
|
||||
// Get GPS position
|
||||
if(*attached) aquirePosition(tp, ltp, data_cycle_time - TIME_S2I(3));
|
||||
// Gather telemetry and system status data
|
||||
measureVoltage(tp);
|
||||
getSensors(tp);
|
||||
getGPIO(tp);
|
||||
setSystemStatus(tp);
|
||||
|
||||
tp->id = ++id; // Serial ID
|
||||
/*
|
||||
* Enable GPS position acquisition if requested.
|
||||
* a) If the RTC was not set then enable GPS temporarily to set it.
|
||||
* b) If a thread is using GPS for position.
|
||||
*/
|
||||
unixTimestamp2Date(&time, ltp->gps_time);
|
||||
if(*useGPS == 0 && time.year == RTC_BASE_YEAR) {
|
||||
TRACE_INFO("COLL > Acquire time using GPS");
|
||||
aquirePosition(tp, ltp, data_cycle_time - TIME_S2I(3));
|
||||
/* GPS done. */
|
||||
if(!(tp->gps_state == GPS_LOCKED1
|
||||
|| tp->gps_state == GPS_LOCKED2)) {
|
||||
/* Acquisition failed. Wait and then try again. */
|
||||
TRACE_INFO("COLL > Time acquisition from GPS failed");
|
||||
chThdSleep(TIME_S2I(60));
|
||||
continue;
|
||||
}
|
||||
TRACE_INFO("COLL > Time acquired from GPS");
|
||||
/* Switch GPS off. */
|
||||
GPS_Deinit();
|
||||
} else if(*useGPS > 0) {
|
||||
aquirePosition(tp, ltp, data_cycle_time - TIME_S2I(3));
|
||||
} else {
|
||||
/*
|
||||
* No threads using GPS.
|
||||
* RTC valid so set tp & ltp from fixed location data.
|
||||
*/
|
||||
tp->gps_alt = conf_sram.aprs.tx.alt;
|
||||
tp->gps_lat = conf_sram.aprs.tx.lat;
|
||||
tp->gps_lon = conf_sram.aprs.tx.lon;
|
||||
tp->gps_state = GPS_FIXED;
|
||||
|
||||
// Measure telemetry
|
||||
measureVoltage(tp);
|
||||
getSensors(tp);
|
||||
getGPIO(tp);
|
||||
setSystemStatus(tp);
|
||||
ltp->gps_time = tp->gps_time;
|
||||
ltp->gps_alt = tp->gps_alt;
|
||||
ltp->gps_lat = tp->gps_lat;
|
||||
ltp->gps_lon = tp->gps_lon;
|
||||
ltp->gps_state = GPS_FIXED;
|
||||
}
|
||||
|
||||
// Trace data
|
||||
unixTimestamp2Date(&time, tp->gps_time);
|
||||
TRACE_INFO( "COLL > New data point available (ID=%d)\r\n"
|
||||
"%s Time %04d-%02d-%02d %02d:%02d:%02d\r\n"
|
||||
"%s Pos %d.%05d %d.%05d Alt %dm\r\n"
|
||||
"%s Sats %d TTFF %dsec\r\n"
|
||||
"%s ADC Vbat=%d.%03dV Vsol=%d.%03dV Pbat=%dmW\r\n"
|
||||
"%s AIR p=%d.%01dPa T=%d.%02ddegC phi=%d.%01d%%\r\n"
|
||||
tp->id = ++id; // Serial ID
|
||||
|
||||
// Trace data
|
||||
unixTimestamp2Date(&time, tp->gps_time);
|
||||
TRACE_INFO( "COLL > New data point available (ID=%d)\r\n"
|
||||
"%s Time %04d-%02d-%02d %02d:%02d:%02d\r\n"
|
||||
"%s Pos %d.%05d %d.%05d Alt %dm\r\n"
|
||||
"%s Sats %d TTFF %dsec\r\n"
|
||||
"%s ADC Vbat=%d.%03dV Vsol=%d.%03dV Pbat=%dmW\r\n"
|
||||
"%s AIR p=%d.%01dPa T=%d.%02ddegC phi=%d.%01d%%\r\n"
|
||||
#if ENABLE_EXTERNAL_I2C == TRUE
|
||||
"%s IO IO1=%d",
|
||||
"%s IOP IO1=%d",
|
||||
|
||||
#else
|
||||
"%s IO IO1=%d IO2=%d IO3=%d",
|
||||
"%s IOP IO1=%d IO2=%d IO3=%d",
|
||||
#endif
|
||||
tp->id,
|
||||
TRACE_TAB, time.year, time.month, time.day, time.hour, time.minute, time.day,
|
||||
TRACE_TAB, tp->gps_lat/10000000, (tp->gps_lat > 0 ? 1:-1)*(tp->gps_lat/100)%100000, tp->gps_lon/10000000, (tp->gps_lon > 0 ? 1:-1)*(tp->gps_lon/100)%100000, tp->gps_alt,
|
||||
TRACE_TAB, tp->gps_sats, tp->gps_ttff,
|
||||
TRACE_TAB, tp->adc_vbat/1000, (tp->adc_vbat%1000), tp->adc_vsol/1000, (tp->adc_vsol%1000), tp->pac_pbat,
|
||||
TRACE_TAB, tp->sen_i1_press/10, tp->sen_i1_press%10, tp->sen_i1_temp/100, tp->sen_i1_temp%100, tp->sen_i1_hum/10, tp->sen_i1_hum%10,
|
||||
tp->id,
|
||||
TRACE_TAB, time.year, time.month, time.day, time.hour, time.minute, time.day,
|
||||
TRACE_TAB, tp->gps_lat/10000000, (tp->gps_lat > 0 ? 1:-1)*(tp->gps_lat/100)%100000, tp->gps_lon/10000000, (tp->gps_lon > 0 ? 1:-1)*(tp->gps_lon/100)%100000, tp->gps_alt,
|
||||
TRACE_TAB, tp->gps_sats, tp->gps_ttff,
|
||||
TRACE_TAB, tp->adc_vbat/1000, (tp->adc_vbat%1000), tp->adc_vsol/1000, (tp->adc_vsol%1000), tp->pac_pbat,
|
||||
TRACE_TAB, tp->sen_i1_press/10, tp->sen_i1_press%10, tp->sen_i1_temp/100, tp->sen_i1_temp%100, tp->sen_i1_hum/10, tp->sen_i1_hum%10,
|
||||
#if ENABLE_EXTERNAL_I2C == TRUE
|
||||
TRACE_TAB, tp->gpio & 1
|
||||
TRACE_TAB, tp->gpio & 1
|
||||
#else
|
||||
TRACE_TAB, tp->gpio & 1, (tp->gpio >> 1) & 1, (tp->gpio >> 2) & 1
|
||||
TRACE_TAB, tp->gpio & 1, (tp->gpio >> 1) & 1, (tp->gpio >> 2) & 1
|
||||
#endif
|
||||
);
|
||||
);
|
||||
|
||||
// Write data point to Flash memory
|
||||
flash_writeLogDataPoint(tp);
|
||||
// Write data point to Flash memory
|
||||
flash_writeLogDataPoint(tp);
|
||||
|
||||
// Switch last data point
|
||||
lastDataPoint = tp;
|
||||
// Switch last data point
|
||||
lastDataPoint = tp;
|
||||
|
||||
// Wait until cycle
|
||||
cycle_time = chThdSleepUntilWindowed(cycle_time, cycle_time + data_cycle_time);
|
||||
}
|
||||
// Wait until cycle
|
||||
cycle_time = chThdSleepUntilWindowed(cycle_time, cycle_time + data_cycle_time);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -399,7 +451,7 @@ void init_data_collector() {
|
|||
thread_t *th = chThdCreateFromHeap(NULL,
|
||||
THD_WORKING_AREA_SIZE(10*1024),
|
||||
"TRA", LOWPRIO,
|
||||
collectorThread, &attached);
|
||||
collectorThread, &useGPS);
|
||||
if(!th) {
|
||||
// Print startup error, do not start watchdog for this thread
|
||||
TRACE_ERROR("COLL > Could not start"
|
||||
|
|
|
@ -11,8 +11,8 @@ typedef enum {
|
|||
GPS_LOSS, // The GPS was switched on all time but it couln't acquire a fix
|
||||
GPS_LOWBATT1, // The GPS wasn't switched on because the battery has not enough energy
|
||||
GPS_LOWBATT2, // The GPS was switched on but has been switched off prematurely while the battery has not enough energy (or is too cold)
|
||||
GPS_LOG, // The tracker has been just switched on and the position has been taken from the log
|
||||
GPS_OFF, // There is no active position thread so the GPS was never switched on (in oder to save power)
|
||||
GPS_LOG, // The tracker has just been switched on and the position has been taken from the log
|
||||
GPS_OFF, // There was no prior acquisition by GPS
|
||||
GPS_ERROR, // The GPS has a communication error
|
||||
GPS_FIXED // Fixed location data used from APRS location
|
||||
} gpsState_t;
|
||||
|
@ -32,19 +32,20 @@ typedef struct {
|
|||
gpsState_t gps_state; // GPS state
|
||||
uint8_t gps_sats; // Satellites used for solution
|
||||
uint8_t gps_ttff; // Time to first fix in seconds
|
||||
uint8_t gps_pdop; // Position DOP in 0.05 per unit (unitless)
|
||||
uint8_t gps_pdop; // Position DOP in 0.05 per arbitrary unit
|
||||
uint16_t gps_alt; // Altitude in meter
|
||||
int32_t gps_lat; // Latitude in 10^(-7)° per unit
|
||||
int32_t gps_lon; // Longitude in 10^(-7)° per unit
|
||||
int32_t gps_lat; // Latitude in 10^(-7)° per unit
|
||||
int32_t gps_lon; // Longitude in 10^(-7)° per unit
|
||||
|
||||
// BME280 (on board)
|
||||
uint32_t sen_i1_press; // Airpressure in Pa*10 (in 0.1Pa)
|
||||
uint32_t sen_e1_press; // Airpressure in Pa*10 (in 0.1Pa)
|
||||
uint32_t sen_e2_press; // Airpressure in Pa*10 (in 0.1Pa)
|
||||
|
||||
int16_t sen_i1_temp; // Temperature in 0.01°C per unit
|
||||
int16_t sen_e1_temp; // Temperature in 0.01°C per unit
|
||||
int16_t sen_e2_temp; // Temperature in 0.01°C per unit
|
||||
// BME280 (off board)
|
||||
int16_t sen_i1_temp; // Temperature in 0.01°C per unit
|
||||
int16_t sen_e1_temp; // Temperature in 0.01°C per unit
|
||||
int16_t sen_e2_temp; // Temperature in 0.01°C per unit
|
||||
|
||||
uint8_t sen_i1_hum; // Rel. humidity in %
|
||||
uint8_t sen_e1_hum; // Rel. humidity in %
|
||||
|
@ -72,8 +73,8 @@ typedef struct {
|
|||
* - 3:4 pac1720 status
|
||||
* - 5:7 OV5640 status
|
||||
* - 8:9 BMEi1 status (0 = OK, 1 = Fail, 2 = Not fitted)
|
||||
* - 9:10 BMEe1 status (0 = OK, 1 = Fail, 2 = Not fitted)
|
||||
* - 10:11 BMEe2 status (0 = OK, 1 = Fail, 2 = Not fitted)
|
||||
* - 10:11 BMEe1 status (0 = OK, 1 = Fail, 2 = Not fitted)
|
||||
* - 12:13 BMEe2 status (0 = OK, 1 = Fail, 2 = Not fitted)
|
||||
*/
|
||||
} dataPoint_t;
|
||||
|
||||
|
@ -82,5 +83,5 @@ dataPoint_t* getLastDataPoint(void);
|
|||
|
||||
void init_data_collector(void);
|
||||
|
||||
#endif
|
||||
#endif /* __COLLECTOR_H__ */
|
||||
|
||||
|
|
|
@ -36,16 +36,6 @@ THD_FUNCTION(bcnThread, arg) {
|
|||
TRACE_INFO("BCN > Do module BEACON cycle");
|
||||
|
||||
dataPoint_t* dataPoint = getLastDataPoint();
|
||||
if(dataPoint->gps_state != GPS_LOCKED1
|
||||
|| dataPoint->gps_state != GPS_LOCKED2) {
|
||||
TRACE_INFO("BCN > Set location from fixed location data");
|
||||
dataPoint->gps_alt = conf->tx.alt;
|
||||
dataPoint->gps_lat = conf->tx.lat;
|
||||
dataPoint->gps_lon = conf->tx.lon;
|
||||
dataPoint->gps_state = GPS_FIXED;
|
||||
} else {
|
||||
TRACE_INFO("BCN > Set location from GPS derived data");
|
||||
}
|
||||
if(!p_sleep(&conf->thread_conf.sleep_conf)) {
|
||||
|
||||
// Telemetry encoding parameter transmission
|
||||
|
@ -63,7 +53,7 @@ THD_FUNCTION(bcnThread, arg) {
|
|||
type);
|
||||
if(packet == NULL) {
|
||||
TRACE_WARN("BCN > No free packet objects for"
|
||||
" telemetry config transmission");
|
||||
" telemetry config transmission");
|
||||
} else {
|
||||
if(!transmitOnRadio(packet,
|
||||
conf->tx.radio_conf.freq,
|
||||
|
@ -80,62 +70,73 @@ THD_FUNCTION(bcnThread, arg) {
|
|||
last_conf_transmission += conf->tx.tel_enc_cycle;
|
||||
}
|
||||
|
||||
TRACE_INFO("BCN > Transmit position beacon");
|
||||
if(dataPoint->gps_state == GPS_FIXED
|
||||
|| dataPoint->gps_state == GPS_LOCKED1
|
||||
|| dataPoint->gps_state == GPS_LOCKED2) {
|
||||
|
||||
// Encode/Transmit position packet
|
||||
packet_t packet = aprs_encode_position_and_telemetry(conf->tx.call, conf->tx.path,
|
||||
conf->tx.symbol, dataPoint);
|
||||
if(packet == NULL) {
|
||||
TRACE_WARN("BCN > No free packet objects"
|
||||
" for position transmission");
|
||||
} else {
|
||||
if(!transmitOnRadio(packet,
|
||||
conf->tx.radio_conf.freq,
|
||||
0,
|
||||
0,
|
||||
conf->tx.radio_conf.pwr,
|
||||
conf->tx.radio_conf.mod,
|
||||
conf->tx.radio_conf.cca)) {
|
||||
TRACE_ERROR("BCN > failed to transmit beacon data");
|
||||
TRACE_INFO("BCN > Transmit position beacon");
|
||||
|
||||
// Encode/Transmit position packet
|
||||
packet_t packet = aprs_encode_position_and_telemetry(conf->tx.call,
|
||||
conf->tx.path,
|
||||
conf->tx.symbol,
|
||||
dataPoint, true);
|
||||
if(packet == NULL) {
|
||||
TRACE_WARN("BCN > No free packet objects"
|
||||
" for position transmission");
|
||||
} else {
|
||||
if(!transmitOnRadio(packet,
|
||||
conf->tx.radio_conf.freq,
|
||||
0,
|
||||
0,
|
||||
conf->tx.radio_conf.pwr,
|
||||
conf->tx.radio_conf.mod,
|
||||
conf->tx.radio_conf.cca)) {
|
||||
TRACE_ERROR("BCN > failed to transmit beacon data");
|
||||
}
|
||||
chThdSleep(TIME_S2I(5));
|
||||
}
|
||||
chThdSleep(TIME_S2I(5));
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode/Transmit APRSD packet.
|
||||
* This is a tracker originated message (not a reply to a request).
|
||||
* The message will be sent to the base station if set.
|
||||
* Else send it to device identity.
|
||||
*/
|
||||
char *call = conf_sram.aprs.base.enabled
|
||||
? conf_sram.aprs.base.call : APRS_DEVICE_CALLSIGN;
|
||||
/*
|
||||
* Encode/Transmit APRSD packet.
|
||||
* This is a tracker originated message (not a reply to a request).
|
||||
* The message will be sent to the base station if set.
|
||||
* Else send it to device identity.
|
||||
*/
|
||||
char *call = conf_sram.aprs.base.enabled
|
||||
? conf_sram.aprs.base.call : APRS_DEVICE_CALLSIGN;
|
||||
|
||||
/*
|
||||
* Send message from this device.
|
||||
* Use call sign and path as specified in base config.
|
||||
* There is no acknowledgment requested.
|
||||
*/
|
||||
packet = aprs_compose_aprsd_message(
|
||||
conf->tx.call,
|
||||
conf->base.path,
|
||||
call);
|
||||
if(packet == NULL) {
|
||||
TRACE_WARN("BCN > No free packet objects "
|
||||
"or badly formed APRSD message");
|
||||
} else {
|
||||
if(!transmitOnRadio(packet,
|
||||
conf->tx.radio_conf.freq,
|
||||
0,
|
||||
0,
|
||||
conf->tx.radio_conf.pwr,
|
||||
conf->tx.radio_conf.mod,
|
||||
conf->tx.radio_conf.cca
|
||||
)) {
|
||||
TRACE_ERROR("BCN > Failed to transmit APRSD data");
|
||||
/*
|
||||
* Send message from this device.
|
||||
* Use call sign and path as specified in base config.
|
||||
* There is no acknowledgment requested.
|
||||
*/
|
||||
packet = aprs_compose_aprsd_message(
|
||||
conf->tx.call,
|
||||
conf->base.path,
|
||||
call);
|
||||
if(packet == NULL) {
|
||||
TRACE_WARN("BCN > No free packet objects "
|
||||
"or badly formed APRSD message");
|
||||
} else {
|
||||
if(!transmitOnRadio(packet,
|
||||
conf->tx.radio_conf.freq,
|
||||
0,
|
||||
0,
|
||||
conf->tx.radio_conf.pwr,
|
||||
conf->tx.radio_conf.mod,
|
||||
conf->tx.radio_conf.cca
|
||||
)) {
|
||||
TRACE_ERROR("BCN > Failed to transmit APRSD data");
|
||||
}
|
||||
chThdSleep(TIME_S2I(5));
|
||||
}
|
||||
chThdSleep(TIME_S2I(5));
|
||||
} else {
|
||||
TRACE_INFO("BCN > No GPS data available for position beacon");
|
||||
chThdSleep(TIME_S2I(60));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} /* psleep */
|
||||
time = waitForTrigger(time, conf->tx.cycle);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,9 @@ THD_FUNCTION(posThread, arg)
|
|||
// Encode/Transmit position packet
|
||||
packet_t packet = aprs_encode_position_and_telemetry(conf->call,
|
||||
conf->path,
|
||||
conf->symbol, dataPoint);
|
||||
conf->symbol,
|
||||
dataPoint,
|
||||
true);
|
||||
if(packet == NULL) {
|
||||
TRACE_WARN("POS > No free packet objects"
|
||||
" for position transmission");
|
||||
|
|
|
@ -115,7 +115,7 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
radio_rx_conf_t radio_conf;
|
||||
|
||||
aprs_sym_t symbol;
|
||||
// Protocol
|
||||
char call[AX25_MAX_ADDR_LEN];
|
||||
} thd_rx_conf_t;
|
||||
|
@ -165,6 +165,7 @@ typedef struct {
|
|||
volt_level_t gps_off_vbat; // Battery voltage threshold at which GPS is switched off
|
||||
volt_level_t gps_onper_vbat; // Battery voltage threshold at which GPS is kept switched on all time. This value must be larger
|
||||
// When gps_on_vbat and gps_off_vbat otherwise this value has no effect. Value 0 disables this feature
|
||||
uint32_t gps_airborne; // Air pressure below which GPS is switched to airborne mode
|
||||
uint32_t magic; // Key that indicates if the flash is loaded or has been updated
|
||||
uint16_t crc; // CRC to verify content
|
||||
} conf_t;
|
||||
|
|
Ładowanie…
Reference in New Issue