Files
grblHAL/nvs_buffer.c
Terje Io 2c58f0de09 Spindle handling refactoring for improved management and configuration of multiple spindles.
NOTE: this is a relatively large change and may have introduced bugs and/or unintended side-effects. Please report any issues!

Added setting $519 for binding spindle encoder to given spindle in multi spindle configurations.

Added machine readable spindle enumeration report, $SPINDLESH.

Increased default value for setting $398 (number of planner blocs) from 35 to 100 for faster laser engraving.
NOTE: the $398 setting value will not change on an upgrade!
NOTE: STM32F103 builds for the 128K flash variants does not have enough free RAM and will keep 35 as the default value.

Increased allowed number of decimal places from 3 to 5 for $10x stepper step/mm settings. Ref. ioSender issue 346.

Added setting $650 for filing system options. Ref. issue 397.
Currently the following bits are available (depending on the configuration):
0 - Auto mount SD card on startup (1).
1 - Do not add littlefs files when listing the root directory (2).

Added build option for lathe UVW mode.
When enabled UVW words can be used to command relative moves for XYZ without switching to relative mode with G91.
NOTE: This permanently sets lathe mode and disables the $32 mode setting.

There are signature changes to some spindle, ioports enumeration and VFS filing system mount functions.

Added events to allow plugin code to handle tool table data, possibly stored on a SD card.
2023-12-12 09:51:59 +01:00

444 lines
15 KiB
C

/*
nvs_buffer.c - RAM based non-volatile storage buffer/emulation
Part of grblHAL
Copyright (c) 2017-2023 Terje Io
Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC
Copyright (c) 2009-2011 Simen Svale Skogsrud
Grbl 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.
Grbl 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 Grbl. 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 "nvs.h"
static uint8_t *nvsbuffer = NULL;
static nvs_io_t physical_nvs;
static bool dirty;
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;
uint8_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);
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);
uint8_t checksum = with_checksum ? calc_checksum(&nvsbuffer[source], size) : 0;
for(; size > 0; size--)
*(destination++) = ram_get_byte(source++);
return with_checksum ? (checksum == ram_get_byte(source) ? NVS_TransferResult_OK : NVS_TransferResult_Failed) : NVS_TransferResult_OK;
}
static void nvs_warning (sys_state_t state)
{
report_message("Not enough heap for NVS buffer!", Message_Warning);
}
// Try to allocate RAM from heap for buffer/emulation.
bool nvs_buffer_alloc (void)
{
assert(NVS_SIZE >= GRBL_NVS_SIZE);
if((nvsbuffer = malloc(NVS_SIZE)))
memset(nvsbuffer, 0xFF, NVS_SIZE);
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
protocol_enqueue_rt_command(nvs_warning);
// 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 - 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 DEBUGOUT
#include "report.h"
void nvs_memmap (void)
{
char buf[30];
report_message("NVS Area: addr size", Message_Plain);
strcpy(buf, "Global: ");
strcat(buf, uitoa(NVS_ADDR_GLOBAL));
strcat(buf, " ");
strcat(buf, uitoa(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)));
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)));
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));
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)));
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));
report_message(buf, Message_Plain);
}
#endif