上面已经实现了字节和数组如何从EEPROM中读回和写入,我们控制器需要将常用的参数存入到EEPROM中,包括了机械结构的参数,那如何将结构体整体和部分读回和写入呢?
存入的结构体
typedef struct {
float steps_per_mm[3];
uint8_t microsteps;
uint8_t pulse_microseconds;
float default_feed_rate;
float default_seek_rate;
uint8_t invert_mask;
float mm_per_arc_segment;
float acceleration;
float junction_deviation;
} settings_v4_t;
其中各个参数的意义在下文中有描述:
GRBL的配置参数文件
具体来说,存入的参数如下:
#define DEFAULT_X_STEPS_PER_MM 600.0
#define DEFAULT_Y_STEPS_PER_MM 600.0
#define DEFAULT_Z_STEPS_PER_MM 600.0
#define DEFAULT_STEP_PULSE_MICROSECONDS 60
#define DEFAULT_MM_PER_ARC_SEGMENT 0.1
#define DEFAULT_RAPID_FEEDRATE 100.0 // mm/min
#define DEFAULT_FEEDRATE 300.0
#define DEFAULT_ACCELERATION (10.0*60*60) // 10 mm/min^2
#define DEFAULT_JUNCTION_DEVIATION 0.05 // mm
#define DEFAULT_STEPPING_INVERT_MASK 0x00//((1<<Y_DIRECTION_BIT)|(1<<Z_DIRECTION_BIT))
#define DEFAULT_REPORT_INCHES 0 // false
#define DEFAULT_AUTO_START 1 // true
#define DEFAULT_INVERT_ST_ENABLE 0 // false
#define DEFAULT_HARD_LIMIT_ENABLE 0 // false
#define DEFAULT_HOMING_ENABLE 0 // false
#define DEFAULT_HOMING_DIR_MASK 0 // move positive dir
#define DEFAULT_HOMING_RAPID_FEEDRATE 150.0 // mm/min
#define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min
#define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k)
#define DEFAULT_HOMING_PULLOFF 1.0 // mm
#define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-255)
#define DEFAULT_DECIMAL_PLACES 3
#define DEFAULT_N_ARC_CORRECTION 25
如此,我们存入的参数将不以字节为单元,该如何存入?
memcpy_to_eeprom_with_checksum
我们的函数用到了checksum,看如何通过算法进行checksum
//入口参数:destination为写入的首地址,因为0位置保存的是版本号,全局变量我们从位置1开始存入
//入口参数:*source 指针,指向的是结构体,通过&settings获取结构体的地址
//并且强制转换成(char*)指针
//入口参数:结构体的大小,通过函数sizeof来计算,这样,此函数的调用示范如下
//调用示例:memcpy_to_eeprom_with_checksum(EEPROM_ADDR_GLOBAL,
//(char*)&settings, sizeof(settings_t));
void memcpy_to_eeprom_with_checksum
(unsigned int destination, char *source, unsigned int size)
{
unsigned char checksum = 0;
for(; size > 0; size--)
{
checksum = (checksum << 1) || (checksum >> 7);
//第一次循环,checksum=0;将第一个字节赋给checksum
checksum += *source;
//写入后地址++,写入后结构体指针++,这里结构体强制转换为了8位的数组
eeprom_put_char(destination++, *(source++));
}
//最后将checksum写入到最后的一个字节中。
eeprom_put_char(destination, checksum);
}
此函数实现了将结构体中的数据写入到EEPROM中,我们调用循环读出函数,将设置好的参数进行回读。首先,我们需要将结构体进行赋值,如下:
settings.steps_per_mm[X_AXIS] = DEFAULT_X_STEPS_PER_MM;
settings.steps_per_mm[Y_AXIS] = DEFAULT_Y_STEPS_PER_MM;
settings.steps_per_mm[Z_AXIS] = DEFAULT_Z_STEPS_PER_MM;
……
设置完成以后,调用:write_global_settings,而此函数只是将版本和全局设置分开写入而已。
void write_global_settings()
{
eeprom_put_char(0, SETTINGS_VERSION);
memcpy_to_eeprom_with_checksum(EEPROM_ADDR_GLOBAL,
(char*)&settings, sizeof(settings_t));
}
设置完成以后,我们用测试程序进行查看EEPROM的写入是否成功:
settings_reset(1);
for(i=0;i<80;i++)
{
if(i%10==0) { DebugPf("\n"); }
DebugPf("0X%02X ",I2C_EE_ByteRead(i));
}
用串口输出一行10字节的数据,显示如下:
memcpy_to_eeprom_with_checksum
uint8_t read_global_settings() {
//先读取版本号,如果版本号是我们写入过的5
uint8_t version = eeprom_get_char(0);
if (version == SETTINGS_VERSION) {
//
if (!(memcpy_from_eeprom_with_checksum((char*)&settings, EEPROM_ADDR_GLOBAL, sizeof(settings_t)))) {
return(false);
}
}
else {
if (version <= 4)
{
// Migrate from settings version 4 to current version.
if (!(memcpy_from_eeprom_with_checksum((char*)&settings, 1, sizeof(settings_v4_t)))) {
return(false);
}
settings_reset(false); // Old settings ok. Write new settings only.
} else {
return(false);
}
}
return(true);
}
用到了下面的函数:此函数实现从EEPROM中读取到结构体中,并进行checksum校验。
int memcpy_from_eeprom_with_checksum(char *destination, unsigned int source, unsigned int size) {
unsigned char data, checksum = 0;
for(; size > 0; size--) {
data = eeprom_get_char(source++);
checksum = (checksum << 1) || (checksum >> 7);
checksum += data;
*(destination++) = data;
}
return(checksum == eeprom_get_char(source));
}
此函数我们用写入和读取,判断校验位的方式,看读写是否正确:通过测试,回读正确。
void write_global_settings()
{
//u8 a;
eeprom_put_char(0, SETTINGS_VERSION);
memcpy_to_eeprom_with_checksum(EEPROM_ADDR_GLOBAL, (char*)&settings, sizeof(settings_t));
//测试回读
// a=memcpy_from_eeprom_with_checksum((char*)&settings, EEPROM_ADDR_GLOBAL, sizeof(settings_t));
// if(a==1)
// DebugPf("EEPROM READBACK RIGHT\n");
// else
// DebugPf("!!!!!!!!!!!!!!EEPROM READBACK WRONG!!!!!!!!!!!!!\n");
}
读全局参数配置
uint8_t read_global_settings() {
//检测版本信息
uint8_t version = eeprom_get_char(0);
if (version == SETTINGS_VERSION)
{
//如果校验错误,则返回0,否则将结构体读出
if (!(memcpy_from_eeprom_with_checksum((char*)&settings, EEPROM_ADDR_GLOBAL, sizeof(settings_t)))) {
return(false);
}
}
else
{
if (version <= 4)
{
if (!(memcpy_from_eeprom_with_checksum((char*)&settings, 1, sizeof(settings_v4_t)))) {
return(false);
}
settings_reset(false); // Old settings ok. Write new settings only.
} else {
return(false);
}
}
return(true);
}
函数确认OK以后,继续看settings_init,函数还可以保存故障发生时候XYZ的坐标值,我们这里没有保存,所以可以略过。
程序测试:
- 程序上电时,一定需要设置的参数不设置,而默认的参数,我们设置为一个固定值。
- 程序返回设置完成的参数表,之后我们进行一次设置到默认值
- 重新上电以后,程序将使用EEPROM中的参数进行打印输出
void settings_init() {
float coord_data[N_AXIS];
uint8_t i;
if(!read_global_settings()) {
report_status_message(STATUS_SETTING_READ_FAIL);
settings_reset(true);
report_grbl_settings();
}
// Read all parameter data into a dummy variable. If error, reset to zero, otherwise do nothing.
for (i=0; i<=SETTING_INDEX_NCOORD; i++) {
if (!settings_read_coord_data(i, coord_data)) {
report_status_message(STATUS_SETTING_READ_FAIL);
}
}
// NOTE: Startup lines are handled and called by main.c at the end of initialization.
}
串口打印输出的文本如下:
$0=0.000 (x, step/mm)
$1=0.000 (y, step/mm)
$2=0.000 (z, step/mm)
$3=0 (step pulse, usec)
$4=0.000 (default feed, mm/min)
$5=0.000 (default seek, mm/min)
$6=0 (step port invert mask, int:)
$7=25 (step idle delay, msec)
$8=0.000 (acceleration, mm/sec^2)
$9=0.000 (junction deviation, mm)
$10=0.000 (arc, mm/segment)
$11=25 (n-arc correction, int)
$12=3 (n-decimals, int)
$13=0 (report inches, bool)
$14=1 (auto start, bool)
$15=0 (invert step enable, bool)
$16=0 (hard limits, bool)
$17=0 (homing cycle, bool)
$18=0 (homing dir invert mask, int:)
$19=25.000 (homing feed, mm/min)
$20=150.000 (homing seek, mm/min)
$21=100 (homing debounce, msec)
$22=1.000 (homing pull-off, mm)
System initialization finished
初始化结构体参数设置完成,开始读取坐标值...
error: EEPROM read fail. Using defaults
坐标轴读取失败,系统将从逻辑零点开始运行
error: EEPROM read fail. Using defaults
坐标轴读取失败,系统将从逻辑零点开始运行
error: EEPROM read fail. Using defaults
坐标轴读取失败,系统将从逻辑零点开始运行
error: EEPROM read fail. Using defaults
坐标轴读取失败,系统将从逻辑零点开始运行
error: EEPROM read fail. Using defaults
坐标轴读取失败,系统将从逻辑零点开始运行
error: EEPROM read fail. Using defaults
坐标轴读取失败,系统将从逻辑零点开始运行
初始化设置完毕
$0=600.000 (x, step/mm)
$1=600.000 (y, step/mm)
$2=600.000 (z, step/mm)
$3=60 (step pulse, usec)
$4=300.000 (default feed, mm/min)
$5=100.000 (default seek, mm/min)
$6=0 (step port invert mask, int:)
$7=25 (step idle delay, msec)
$8=10.000 (acceleration, mm/sec^2)
$9=0.050 (junction deviation, mm)
$10=0.100 (arc, mm/segment)
$11=25 (n-arc correction, int)
$12=3 (n-decimals, int)
$13=0 (report inches, bool)
$14=1 (auto start, bool)
$15=0 (invert step enable, bool)
$16=0 (hard limits, bool)
$17=0 (homing cycle, bool)
$18=0 (homing dir invert mask, int:)
$19=25.000 (homing feed, mm/min)
$20=150.000 (homing seek, mm/min)
$21=100 (homing debounce, msec)
$22=1.000 (homing pull-off, mm)
总结
如何将一个结构体和特殊文件保存到EEPROM中。
在调试程序时候,善用串口调试助手进行程序的过程输出。