mirror of
https://github.com/grblHAL/core.git
synced 2026-02-06 00:52:35 +08:00
Added setting $680 for configuring delay from stepper enable to first dir/step pulse. Adds to fixed ~2ms delay provided by most drivers.
469 lines
16 KiB
C
469 lines
16 KiB
C
/*
|
|
nvs_buffer.c - RAM based non-volatile storage buffer/emulation
|
|
|
|
Part of grblHAL
|
|
|
|
Copyright (c) 2017-2025 Terje Io
|
|
Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC
|
|
Copyright (c) 2009-2011 Simen Svale Skogsrud
|
|
|
|
grblHAL is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
grblHAL is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License.
|
|
along with grblHAL. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
//
|
|
// Can be used by MCUs with no nonvolatile storage options, be sure to allocate enough heap memory before use
|
|
//
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
|
|
#include "hal.h"
|
|
#include "nvs_buffer.h"
|
|
#include "protocol.h"
|
|
#include "settings.h"
|
|
#include "gcode.h"
|
|
#include "crc.h"
|
|
#include "nvs.h"
|
|
|
|
static uint8_t *nvsbuffer = NULL;
|
|
static nvs_io_t physical_nvs;
|
|
static bool dirty;
|
|
uint32_t nvs_size_max = NVS_SIZE;
|
|
settings_dirty_t settings_dirty;
|
|
|
|
typedef struct {
|
|
uint16_t addr;
|
|
uint8_t type;
|
|
uint8_t offset;
|
|
} emap_t;
|
|
|
|
#define NVS_GROUP_GLOBAL 0
|
|
#define NVS_GROUP_TOOLS 1
|
|
#define NVS_GROUP_PARAMETERS 2
|
|
#define NVS_GROUP_STARTUP 3
|
|
#define NVS_GROUP_BUILD 4
|
|
|
|
#define PARAMETER_ADDR(n) (NVS_ADDR_PARAMETERS + n * (sizeof(coord_data_t) + NVS_CRC_BYTES))
|
|
#define STARTLINE_ADDR(n) (NVS_ADDR_STARTUP_BLOCK + n * (sizeof(stored_line_t) + NVS_CRC_BYTES))
|
|
#if N_TOOLS
|
|
#define TOOL_ADDR(n) (NVS_ADDR_TOOL_TABLE + n * (sizeof(tool_data_t) + NVS_CRC_BYTES))
|
|
#endif
|
|
|
|
static const emap_t target[] = {
|
|
{NVS_ADDR_GLOBAL, NVS_GROUP_GLOBAL, 0},
|
|
|
|
{PARAMETER_ADDR(0), NVS_GROUP_PARAMETERS, 0},
|
|
{PARAMETER_ADDR(1), NVS_GROUP_PARAMETERS, 1},
|
|
{PARAMETER_ADDR(2), NVS_GROUP_PARAMETERS, 2},
|
|
{PARAMETER_ADDR(3), NVS_GROUP_PARAMETERS, 3},
|
|
{PARAMETER_ADDR(4), NVS_GROUP_PARAMETERS, 4},
|
|
{PARAMETER_ADDR(5), NVS_GROUP_PARAMETERS, 5},
|
|
{PARAMETER_ADDR(6), NVS_GROUP_PARAMETERS, 6},
|
|
{PARAMETER_ADDR(7), NVS_GROUP_PARAMETERS, 7},
|
|
{PARAMETER_ADDR(8), NVS_GROUP_PARAMETERS, 8},
|
|
{PARAMETER_ADDR(9), NVS_GROUP_PARAMETERS, 9},
|
|
{PARAMETER_ADDR(10), NVS_GROUP_PARAMETERS, 10},
|
|
{PARAMETER_ADDR(11), NVS_GROUP_PARAMETERS, 11},
|
|
{STARTLINE_ADDR(0), NVS_GROUP_STARTUP, 0},
|
|
{STARTLINE_ADDR(1), NVS_GROUP_STARTUP, 1},
|
|
#if N_STARTUP_LINE > 2
|
|
#error Increase number of startup line entries!
|
|
#endif
|
|
{NVS_ADDR_BUILD_INFO, NVS_GROUP_BUILD, 0},
|
|
#if N_TOOLS
|
|
{TOOL_ADDR(0), NVS_GROUP_TOOLS, 0},
|
|
{TOOL_ADDR(1), NVS_GROUP_TOOLS, 1},
|
|
{TOOL_ADDR(2), NVS_GROUP_TOOLS, 2},
|
|
{TOOL_ADDR(3), NVS_GROUP_TOOLS, 3},
|
|
{TOOL_ADDR(4), NVS_GROUP_TOOLS, 4},
|
|
{TOOL_ADDR(5), NVS_GROUP_TOOLS, 5},
|
|
{TOOL_ADDR(6), NVS_GROUP_TOOLS, 6},
|
|
{TOOL_ADDR(7), NVS_GROUP_TOOLS, 7},
|
|
#if N_TOOLS > 8
|
|
{TOOL_ADDR(8), NVS_GROUP_TOOLS, 8},
|
|
{TOOL_ADDR(9), NVS_GROUP_TOOLS, 9},
|
|
{TOOL_ADDR(10), NVS_GROUP_TOOLS, 10},
|
|
{TOOL_ADDR(11), NVS_GROUP_TOOLS, 11},
|
|
{TOOL_ADDR(12), NVS_GROUP_TOOLS, 12},
|
|
{TOOL_ADDR(13), NVS_GROUP_TOOLS, 13},
|
|
{TOOL_ADDR(14), NVS_GROUP_TOOLS, 14},
|
|
{TOOL_ADDR(15), NVS_GROUP_TOOLS, 15},
|
|
#endif
|
|
#if N_TOOLS > 16
|
|
{TOOL_ADDR(16), NVS_GROUP_TOOLS, 16},
|
|
{TOOL_ADDR(17), NVS_GROUP_TOOLS, 17},
|
|
{TOOL_ADDR(18), NVS_GROUP_TOOLS, 18},
|
|
{TOOL_ADDR(19), NVS_GROUP_TOOLS, 19},
|
|
{TOOL_ADDR(20), NVS_GROUP_TOOLS, 20},
|
|
{TOOL_ADDR(21), NVS_GROUP_TOOLS, 21},
|
|
{TOOL_ADDR(22), NVS_GROUP_TOOLS, 22},
|
|
{TOOL_ADDR(23), NVS_GROUP_TOOLS, 23},
|
|
{TOOL_ADDR(24), NVS_GROUP_TOOLS, 24},
|
|
{TOOL_ADDR(25), NVS_GROUP_TOOLS, 25},
|
|
{TOOL_ADDR(26), NVS_GROUP_TOOLS, 26},
|
|
{TOOL_ADDR(27), NVS_GROUP_TOOLS, 27},
|
|
{TOOL_ADDR(28), NVS_GROUP_TOOLS, 28},
|
|
{TOOL_ADDR(29), NVS_GROUP_TOOLS, 29},
|
|
{TOOL_ADDR(30), NVS_GROUP_TOOLS, 30},
|
|
{TOOL_ADDR(31), NVS_GROUP_TOOLS, 31},
|
|
#endif
|
|
#endif
|
|
{0, 0, 0} // list termination - do not remove
|
|
};
|
|
|
|
inline static uint8_t ram_get_byte (uint32_t addr)
|
|
{
|
|
return nvsbuffer[addr];
|
|
}
|
|
|
|
inline static void ram_put_byte (uint32_t addr, uint8_t new_value)
|
|
{
|
|
if(addr == 0)
|
|
settings_dirty.version = true;
|
|
dirty = dirty || nvsbuffer[addr] != new_value || addr == 0;
|
|
nvsbuffer[addr] = new_value;
|
|
}
|
|
|
|
static nvs_transfer_result_t memcpy_to_ram (uint32_t destination, uint8_t *source, uint32_t size, bool with_checksum)
|
|
{
|
|
if(hal.nvs.driver_area.address && destination > hal.nvs.driver_area.address + hal.nvs.driver_area.size)
|
|
return physical_nvs.memcpy_to_nvs(destination, source, size, with_checksum);
|
|
|
|
uint32_t dest = destination;
|
|
uint16_t checksum = with_checksum ? calc_checksum(source, size) : 0;
|
|
|
|
dirty = false;
|
|
|
|
for(; size > 0; size--)
|
|
ram_put_byte(dest++, *(source++));
|
|
|
|
if(with_checksum) {
|
|
ram_put_byte(dest, checksum & 0xFF);
|
|
#if NVS_CRC_BYTES > 1
|
|
ram_put_byte(++dest, checksum >> 8);
|
|
#endif
|
|
}
|
|
|
|
if(settings_dirty.version || source == hal.nvs.driver_area.mem_address)
|
|
dirty = true;
|
|
|
|
if(dirty && physical_nvs.type != NVS_None) {
|
|
|
|
uint8_t idx = 0;
|
|
|
|
settings_dirty.is_dirty = true;
|
|
|
|
if(hal.nvs.driver_area.address && destination >= hal.nvs.driver_area.address)
|
|
settings_dirty.driver_settings = true;
|
|
|
|
else {
|
|
|
|
do {
|
|
if(target[idx].addr == destination)
|
|
break;
|
|
} while(target[++idx].addr);
|
|
|
|
if(target[idx].addr) switch(target[idx].type) {
|
|
|
|
case NVS_GROUP_GLOBAL:
|
|
settings_dirty.global_settings = true;
|
|
break;
|
|
#if N_TOOLS
|
|
case NVS_GROUP_TOOLS:
|
|
settings_dirty.tool_data |= (1 << target[idx].offset);
|
|
break;
|
|
#endif
|
|
case NVS_GROUP_PARAMETERS:
|
|
settings_dirty.coord_data |= (1 << target[idx].offset);
|
|
break;
|
|
|
|
case NVS_GROUP_STARTUP:
|
|
settings_dirty.startup_lines |= (1 << target[idx].offset);
|
|
break;
|
|
|
|
case NVS_GROUP_BUILD:
|
|
settings_dirty.build_info = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NVS_TransferResult_OK;
|
|
}
|
|
|
|
static nvs_transfer_result_t memcpy_from_ram (uint8_t *destination, uint32_t source, uint32_t size, bool with_checksum)
|
|
{
|
|
if(hal.nvs.driver_area.address && source > hal.nvs.driver_area.address + hal.nvs.driver_area.size)
|
|
return physical_nvs.memcpy_from_nvs(destination, source, size, with_checksum);
|
|
|
|
uint16_t checksum = with_checksum ? calc_checksum(&nvsbuffer[source], size) : 0;
|
|
|
|
for(; size > 0; size--)
|
|
*(destination++) = ram_get_byte(source++);
|
|
|
|
#if NVS_CRC_BYTES == 1
|
|
return !with_checksum || checksum == ram_get_byte(source);
|
|
#else
|
|
return !with_checksum || checksum == (ram_get_byte(source) | (ram_get_byte(source + 1) << 8));
|
|
#endif
|
|
}
|
|
|
|
// Try to allocate RAM from heap for buffer/emulation.
|
|
bool nvs_buffer_alloc (void)
|
|
{
|
|
static uint32_t nvs_size = NVS_SIZE;
|
|
|
|
if(hal.nvs.size_max > nvs_size) {
|
|
nvs_size_max = min(4096, hal.nvs.size_max); // Limit to 4K for now
|
|
if(nvsbuffer)
|
|
free(nvsbuffer);
|
|
}
|
|
|
|
assert(nvs_size_max >= GRBL_NVS_SIZE);
|
|
|
|
if((nvsbuffer = malloc(nvs_size_max))) {
|
|
nvs_size = nvs_size_max;
|
|
memset(nvsbuffer, 0xFF, nvs_size_max);
|
|
}
|
|
|
|
return nvsbuffer != NULL;
|
|
}
|
|
|
|
void nvs_buffer_free (void)
|
|
{
|
|
if(nvsbuffer) {
|
|
nvs_buffer_sync_physical();
|
|
free(nvsbuffer);
|
|
}
|
|
}
|
|
//
|
|
// Switch over to RAM based copy.
|
|
// Changes to RAM based copy will be written to physical storage when grblHAL is in IDLE state.
|
|
bool nvs_buffer_init (void)
|
|
{
|
|
hal.nvs.size = ((hal.nvs.size - 1) | 0x03) + 1; // Ensure NVS area ends on a word boundary
|
|
|
|
if(nvsbuffer) {
|
|
|
|
memcpy(&physical_nvs, &hal.nvs, sizeof(nvs_io_t)); // save pointers to physical storage handler functions
|
|
|
|
// Copy physical storage content to RAM when available
|
|
if(physical_nvs.type == NVS_Flash)
|
|
physical_nvs.memcpy_from_flash(nvsbuffer);
|
|
else if(physical_nvs.type != NVS_None)
|
|
physical_nvs.memcpy_from_nvs(nvsbuffer, 0, GRBL_NVS_SIZE + hal.nvs.driver_area.size, false);
|
|
|
|
// Switch hal to use RAM version of non-volatile storage data
|
|
hal.nvs.type = NVS_Emulated;
|
|
hal.nvs.get_byte = &ram_get_byte;
|
|
hal.nvs.put_byte = &ram_put_byte;
|
|
hal.nvs.memcpy_to_nvs = &memcpy_to_ram;
|
|
hal.nvs.memcpy_from_nvs = &memcpy_from_ram;
|
|
hal.nvs.memcpy_from_flash = NULL;
|
|
hal.nvs.memcpy_to_flash = NULL;
|
|
|
|
// If no physical storage available or if NVS import fails copy default settings to RAM
|
|
// and write out to physical storage when available.
|
|
if(physical_nvs.type == NVS_None || ram_get_byte(0) != SETTINGS_VERSION) {
|
|
settings_restore(settings_all);
|
|
if(physical_nvs.type == NVS_Flash)
|
|
physical_nvs.memcpy_to_flash(nvsbuffer);
|
|
else if(physical_nvs.memcpy_to_nvs)
|
|
physical_nvs.memcpy_to_nvs(0, nvsbuffer, GRBL_NVS_SIZE + hal.nvs.driver_area.size, false);
|
|
if(physical_nvs.type != NVS_None)
|
|
grbl.report.status_message(Status_SettingReadFail);
|
|
}
|
|
} else
|
|
task_run_on_startup(report_warning, "Not enough heap for NVS buffer!");
|
|
|
|
// Clear settings dirty flags
|
|
memset(&settings_dirty, 0, sizeof(settings_dirty_t));
|
|
|
|
return nvsbuffer != NULL;
|
|
}
|
|
|
|
// Allocate NVS block for driver settings.
|
|
// NOTE: allocation has to be done before content is copied from physical storage.
|
|
nvs_address_t nvs_alloc (size_t size)
|
|
{
|
|
static uint8_t *mem_address;
|
|
|
|
nvs_address_t addr = 0;
|
|
|
|
// Check if already switched to emulation or buffer allocation failed, return NULL if so.
|
|
if(hal.nvs.type == NVS_Emulated || nvsbuffer == NULL)
|
|
return 0;
|
|
|
|
if(hal.nvs.driver_area.address == 0) {
|
|
hal.nvs.driver_area.address = GRBL_NVS_SIZE;
|
|
hal.nvs.driver_area.mem_address = mem_address = nvsbuffer + GRBL_NVS_SIZE;
|
|
}
|
|
|
|
size += NVS_CRC_BYTES; // add room for checksum.
|
|
if(hal.nvs.driver_area.size + size < (nvs_size_max - GRBL_NVS_SIZE)) {
|
|
mem_address = (uint8_t *)((uint32_t)(mem_address - 1) | 0x03) + 1; // Align to word boundary
|
|
addr = mem_address - nvsbuffer;
|
|
mem_address += size;
|
|
hal.nvs.driver_area.size = mem_address - hal.nvs.driver_area.mem_address;
|
|
hal.nvs.size = GRBL_NVS_SIZE + hal.nvs.driver_area.size + 1;
|
|
}
|
|
|
|
return addr;
|
|
}
|
|
|
|
// Write RAM changes to physical storage
|
|
void nvs_buffer_sync_physical (void)
|
|
{
|
|
if(!settings_dirty.is_dirty)
|
|
return;
|
|
|
|
if(physical_nvs.memcpy_to_nvs) {
|
|
|
|
if(settings_dirty.version)
|
|
settings_dirty.version = physical_nvs.memcpy_to_nvs(0, nvsbuffer, 1, false) != NVS_TransferResult_OK;
|
|
|
|
if(settings_dirty.global_settings)
|
|
settings_dirty.global_settings = physical_nvs.memcpy_to_nvs(NVS_ADDR_GLOBAL, (uint8_t *)(nvsbuffer + NVS_ADDR_GLOBAL), sizeof(settings_t) + NVS_CRC_BYTES, false) != NVS_TransferResult_OK;
|
|
|
|
if(settings_dirty.build_info)
|
|
settings_dirty.build_info = physical_nvs.memcpy_to_nvs(NVS_ADDR_BUILD_INFO, (uint8_t *)(nvsbuffer + NVS_ADDR_BUILD_INFO), sizeof(stored_line_t) + NVS_CRC_BYTES, false) != NVS_TransferResult_OK;
|
|
|
|
uint_fast8_t idx = N_STARTUP_LINE, offset;
|
|
if(settings_dirty.startup_lines) do {
|
|
idx--;
|
|
if(bit_istrue(settings_dirty.startup_lines, bit(idx))) {
|
|
bit_false(settings_dirty.startup_lines, bit(idx));
|
|
offset = NVS_ADDR_STARTUP_BLOCK + idx * (sizeof(stored_line_t) + NVS_CRC_BYTES);
|
|
if(physical_nvs.memcpy_to_nvs(offset, (uint8_t *)(nvsbuffer + offset), sizeof(stored_line_t) + NVS_CRC_BYTES, false) == NVS_TransferResult_OK)
|
|
bit_false(settings_dirty.startup_lines, bit(idx));
|
|
}
|
|
} while(idx);
|
|
|
|
idx = N_CoordinateSystems;
|
|
if(settings_dirty.coord_data) do {
|
|
if(bit_istrue(settings_dirty.coord_data, bit(idx))) {
|
|
offset = NVS_ADDR_PARAMETERS + idx * (sizeof(coord_data_t) + NVS_CRC_BYTES);
|
|
if(physical_nvs.memcpy_to_nvs(offset, (uint8_t *)(nvsbuffer + offset), sizeof(coord_data_t) + NVS_CRC_BYTES, false) == NVS_TransferResult_OK)
|
|
bit_false(settings_dirty.coord_data, bit(idx));
|
|
}
|
|
} while(idx--);
|
|
|
|
if(settings_dirty.driver_settings) {
|
|
if(hal.nvs.driver_area.size > 0)
|
|
settings_dirty.driver_settings = physical_nvs.memcpy_to_nvs(hal.nvs.driver_area.address, (uint8_t *)(nvsbuffer + hal.nvs.driver_area.address), hal.nvs.driver_area.size, false) != NVS_TransferResult_OK;
|
|
else
|
|
settings_dirty.driver_settings = false;
|
|
}
|
|
|
|
#if N_TOOLS
|
|
idx = N_TOOLS;
|
|
if(settings_dirty.tool_data) do {
|
|
idx--;
|
|
if(bit_istrue(settings_dirty.tool_data, bit(idx))) {
|
|
offset = NVS_ADDR_TOOL_TABLE + idx * (sizeof(tool_data_t) + NVS_CRC_BYTES);
|
|
if(physical_nvs.memcpy_to_nvs(offset, (uint8_t *)(nvsbuffer + offset), sizeof(tool_data_t) + NVS_CRC_BYTES, false) == NVS_TransferResult_OK)
|
|
bit_false(settings_dirty.tool_data, bit(idx));
|
|
}
|
|
} while(idx);
|
|
#endif
|
|
settings_dirty.is_dirty = settings_dirty.coord_data ||
|
|
settings_dirty.global_settings ||
|
|
settings_dirty.driver_settings ||
|
|
settings_dirty.startup_lines ||
|
|
#if N_TOOLS
|
|
settings_dirty.tool_data ||
|
|
#endif
|
|
settings_dirty.build_info;
|
|
|
|
} else if(physical_nvs.memcpy_to_flash) {
|
|
uint_fast8_t retries = 4;
|
|
do {
|
|
if(physical_nvs.memcpy_to_flash(nvsbuffer))
|
|
retries = 0;
|
|
else if(--retries == 0)
|
|
report_message("Settings write failed!", Message_Warning);
|
|
} while(retries);
|
|
memset(&settings_dirty, 0, sizeof(settings_dirty_t));
|
|
}
|
|
}
|
|
|
|
nvs_io_t *nvs_buffer_get_physical (void)
|
|
{
|
|
return hal.nvs.type == NVS_Emulated ? &physical_nvs : &hal.nvs;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
#include "report.h"
|
|
|
|
void nvs_memmap (void)
|
|
{
|
|
char buf[30];
|
|
|
|
report_message("NVS Area: addr size end", Message_Plain);
|
|
|
|
strcpy(buf, "Global: ");
|
|
strcat(buf, uitoa(NVS_ADDR_GLOBAL));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(sizeof(settings_t) + NVS_CRC_BYTES));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(NVS_ADDR_GLOBAL + sizeof(settings_t) + NVS_CRC_BYTES));
|
|
report_message(buf, Message_Plain);
|
|
|
|
strcpy(buf, "Parameters: ");
|
|
strcat(buf, uitoa(NVS_ADDR_PARAMETERS));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(N_CoordinateSystems * (sizeof(coord_data_t) + NVS_CRC_BYTES)));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(NVS_ADDR_PARAMETERS + N_CoordinateSystems * (sizeof(coord_data_t) + NVS_CRC_BYTES)));
|
|
report_message(buf, Message_Plain);
|
|
|
|
strcpy(buf, "Startup block: ");
|
|
strcat(buf, uitoa(NVS_ADDR_STARTUP_BLOCK));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(N_STARTUP_LINE * (sizeof(stored_line_t) + NVS_CRC_BYTES)));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(NVS_ADDR_STARTUP_BLOCK + N_STARTUP_LINE * (sizeof(stored_line_t) + NVS_CRC_BYTES)));
|
|
report_message(buf, Message_Plain);
|
|
|
|
strcpy(buf, "Build info: ");
|
|
strcat(buf, uitoa(NVS_ADDR_BUILD_INFO));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(sizeof(stored_line_t) + NVS_CRC_BYTES));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(NVS_ADDR_BUILD_INFO + sizeof(stored_line_t) + NVS_CRC_BYTES));
|
|
report_message(buf, Message_Plain);
|
|
|
|
#if N_TOOLS
|
|
strcpy(buf, "Tool table: ");
|
|
strcat(buf, uitoa(NVS_ADDR_TOOL_TABLE));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(N_TOOLS * (sizeof(tool_data_t) + NVS_CRC_BYTES)));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(NVS_ADDR_TOOL_TABLE + N_TOOLS * (sizeof(tool_data_t) + NVS_CRC_BYTES)));
|
|
report_message(buf, Message_Plain);
|
|
#endif
|
|
|
|
strcpy(buf, "Driver: ");
|
|
strcat(buf, uitoa(hal.nvs.driver_area.address));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(hal.nvs.driver_area.size));
|
|
strcat(buf, " ");
|
|
strcat(buf, uitoa(hal.nvs.driver_area.address + hal.nvs.driver_area.size));
|
|
report_message(buf, Message_Plain);
|
|
}
|
|
|
|
#endif
|