Files
grblHAL/nvs_buffer.c
Terje Io 64c35930c7 Refactored offset handling, for improved readability and in preparation for rotation support.
Added experimental support for G66 (modal macro call) and G67 (end modal macro call).
Made axis letter to axis/motor assignment for axes ABCUVW freely changeable at compile time.
Fix for some G65 arguments being incorrectly validated for normal use (sign, range).
Added repeat support to G65 macro call via the optional L parameter word.
Changed default setting for ABC-axes to rotary.
Changed defaults for jerk settings to 10x acceleration settings.
Disabled jerk for jog, probe and spindle synchronized motion.
Added _active_probe system parameter, returns -1 if no probe inputs available.
Minor bug fix, G5.1 and G33.1 motion commands were not coverted to the correct string equivalent in $G output.
2026-01-25 07:51:44 +01:00

467 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"
settings_dirty_t settings_dirty;
static nvs_io_t physical_nvs;
static bool dirty;
static struct {
uint8_t *addr;
uint32_t size;
} nvsbuffer = { .size = NVS_SIZE };
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_system_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 offset)
{
return nvsbuffer.addr[offset];
}
inline static void ram_put_byte (uint32_t offset, uint8_t new_value)
{
if(offset == 0)
settings_dirty.version = true;
dirty = dirty || nvsbuffer.addr[offset] != new_value || offset == 0;
nvsbuffer.addr[offset] = 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.addr[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)
{
if(nvsbuffer.addr == NULL) {
if(hal.nvs.size_max > NVS_SIZE)
nvsbuffer.size = min(NVS_SIZE_MAX, hal.nvs.size_max);
if(nvsbuffer.size >= GRBL_NVS_SIZE && (nvsbuffer.addr = malloc(nvsbuffer.size)))
memset(nvsbuffer.addr, 0xFF, nvsbuffer.size);
}
return nvsbuffer.addr != NULL;
}
void nvs_buffer_free (void)
{
if(nvsbuffer.addr) {
nvs_buffer_sync_physical();
free(nvsbuffer.addr);
}
}
//
// 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.addr) {
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.addr);
else if(physical_nvs.type != NVS_None)
physical_nvs.memcpy_from_nvs(nvsbuffer.addr, 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.addr);
else if(physical_nvs.memcpy_to_nvs)
physical_nvs.memcpy_to_nvs(0, nvsbuffer.addr, 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.addr != 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;
uint8_t *start_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.addr == NULL && !nvs_buffer_alloc()))
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.addr + hal.nvs.driver_area.address;
}
size += NVS_CRC_BYTES; // add room for checksum.
start_address = (uint8_t *)((uintptr_t)(mem_address - 1) | 0x03) + 1; // Align to word boundary
if(start_address + size < nvsbuffer.addr + nvsbuffer.size) {
addr = start_address - nvsbuffer.addr;
mem_address = start_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.addr, 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.addr + 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.addr + 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.addr + 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_system_data_t) + NVS_CRC_BYTES);
if(physical_nvs.memcpy_to_nvs(offset, (uint8_t *)(nvsbuffer.addr + offset), sizeof(coord_system_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.addr + 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.addr + 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.addr))
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(NVS_SIZE_PARAMETERS));
strcat(buf, " ");
strcat(buf, uitoa(NVS_ADDR_PARAMETERS + N_CoordinateSystems * (sizeof(coord_system_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(NVS_SIZE_STARTUP_BLOCK));
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(NVS_SIZE_BUILD_INFO));
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