From 92c17dede233eef78edc433a6756899d483220bb Mon Sep 17 00:00:00 2001 From: David Crocker Date: Mon, 24 Feb 2014 14:17:11 +0000 Subject: [PATCH] Various changes relating to zprobe and non-volatile data 1. Z-probing is now done in two stages: a fast stage at the configured home feed rate until within 10% of the target value, then a slow stage at 20% of that feed rate. 2. Provisional support for ultrasonic Z-probe. 3. Added calibration temperature and height temperature coefficient to Z probe parameters. 4. Z-probe parameters and Z-probe type are now saved to flash memory so that they survive power-off and reset cycles. Separate parameters are retained for IR and ultrasonic probes in case both are fitted. 5. Fixed issue with doing slow Z-moves immediately after Z-homing or probing. --- Configuration.h | 4 +- Flash/DueFlashStorage.cpp | 83 + Flash/DueFlashStorage.h | 52 + Flash/efc.cpp | 340 ++++ Flash/efc.h | 139 ++ Flash/flash_efc.cpp | 916 ++++++++++ Flash/flash_efc.h | 151 ++ GCodes.cpp | 2388 +++++++++++++------------- GCodes.h | 11 +- Move.cpp | 58 +- Move.h | 114 +- Platform.cpp | 290 +++- Platform.h | 224 +-- Release/RepRapFirmware-057o-dc52.bin | Bin 0 -> 173048 bytes Webserver.cpp | 22 +- 15 files changed, 3384 insertions(+), 1408 deletions(-) create mode 100644 Flash/DueFlashStorage.cpp create mode 100644 Flash/DueFlashStorage.h create mode 100644 Flash/efc.cpp create mode 100644 Flash/efc.h create mode 100644 Flash/flash_efc.cpp create mode 100644 Flash/flash_efc.h create mode 100644 Release/RepRapFirmware-057o-dc52.bin diff --git a/Configuration.h b/Configuration.h index 050be90..9e1d746 100644 --- a/Configuration.h +++ b/Configuration.h @@ -24,8 +24,8 @@ Licence: GPL #define CONFIGURATION_H #define NAME "RepRapFirmware" -#define VERSION "0.57n-dc42" -#define DATE "2014-02-18" +#define VERSION "0.57o-dc42" +#define DATE "2014-02-24" #define LAST_AUTHOR "dc42" // Other firmware that we might switch to be compatible with. diff --git a/Flash/DueFlashStorage.cpp b/Flash/DueFlashStorage.cpp new file mode 100644 index 0000000..2314e19 --- /dev/null +++ b/Flash/DueFlashStorage.cpp @@ -0,0 +1,83 @@ +#include "DueFlashStorage.h" + +void DueFlashStorage::init() { + /* Initialize flash: 6 wait states for flash writing. */ + uint32_t retCode = flash_init(FLASH_ACCESS_MODE_128, 6); + if (retCode != FLASH_RC_OK) { + _FLASH_DEBUG("Flash init failed\n"); + } +} + +byte DueFlashStorage::read(uint32_t address) { + return FLASH_START[address]; +} + +void DueFlashStorage::read(uint32_t address, void *data, uint32_t dataLength) { + memcpy(data, FLASH_START+address, dataLength); +} + +bool DueFlashStorage::write(uint32_t address, byte value) { + uint32_t byteLength = 1; + uint32_t retCode = flash_unlock((uint32_t)FLASH_START+address, (uint32_t)FLASH_START+address + byteLength - 1, 0, 0); + if (retCode != FLASH_RC_OK) { + _FLASH_DEBUG("Failed to unlock flash for write\n"); + return false; + } + + // write data + retCode = flash_write((uint32_t)FLASH_START+address, &value, byteLength, 1); + + if (retCode != FLASH_RC_OK) { + _FLASH_DEBUG("Flash write failed\n"); + return false; + } + + // Lock page + retCode = flash_lock((uint32_t)FLASH_START+address, (uint32_t)FLASH_START+address + byteLength - 1, 0, 0); + if (retCode != FLASH_RC_OK) { + _FLASH_DEBUG("Failed to lock flash page\n"); + return false; + } + return true; +} + +bool DueFlashStorage::write(uint32_t address, const void *data, uint32_t dataLength) { + if ((uint32_t)FLASH_START+address < IFLASH1_ADDR) { + _FLASH_DEBUG("Flash write address too low\n"); + return false; + } + + if ((uint32_t)FLASH_START+address >= (IFLASH1_ADDR + IFLASH1_SIZE)) { + _FLASH_DEBUG("Flash write address too high\n"); + return false; + } + + if (((uint32_t)FLASH_START+address & 3) != 0) { + _FLASH_DEBUG("Flash start address must be on four byte boundary\n"); + return false; + } + + // Unlock page + uint32_t retCode = flash_unlock((uint32_t)FLASH_START+address, (uint32_t)FLASH_START+address + dataLength - 1, 0, 0); + if (retCode != FLASH_RC_OK) { + _FLASH_DEBUG("Failed to unlock flash for write\n"); + return false; + } + + // write data + retCode = flash_write((uint32_t)FLASH_START+address, data, dataLength, 1); + + if (retCode != FLASH_RC_OK) { + _FLASH_DEBUG("Flash write failed\n"); + return false; + } + + // Lock page + retCode = flash_lock((uint32_t)FLASH_START+address, (uint32_t)FLASH_START+address + dataLength - 1, 0, 0); + if (retCode != FLASH_RC_OK) { + _FLASH_DEBUG("Failed to lock flash page\n"); + return false; + } + return true; +} + diff --git a/Flash/DueFlashStorage.h b/Flash/DueFlashStorage.h new file mode 100644 index 0000000..283a457 --- /dev/null +++ b/Flash/DueFlashStorage.h @@ -0,0 +1,52 @@ +/* +DueFlashStorage saves non-volatile data for Arduino Due. +The library is made to be similar to EEPROM library +Uses flash block 1 per default. + +Note: uploading new software will erase all flash so data written to flash +using this library will not survive a new software upload. + +Inspiration from Pansenti at https://github.com/Pansenti/DueFlash +Rewritten and modified by Sebastian Nilsson +Further modified up by David Crocker +*/ + + +#ifndef DUEFLASHSTORAGE_H +#define DUEFLASHSTORAGE_H + +#include +#include "flash_efc.h" +#include "efc.h" + +// 1Kb of data +#define DATA_LENGTH ((IFLASH1_PAGE_SIZE/sizeof(byte))*4) + +// Choose a start address close to the top of the Flash 1 memory space +#define FLASH_START ((byte *)(IFLASH1_ADDR + IFLASH1_SIZE - DATA_LENGTH)) + +// FLASH_DEBUG can be enabled to get debugging information displayed. +//#define FLASH_DEBUG + +#ifdef FLASH_DEBUG +#define _FLASH_DEBUG(x) Serial.print(x); +#else +#define _FLASH_DEBUG(x) +#endif + +// DueFlash is the main namespace for flash functions +namespace DueFlashStorage { + void init(); + + // write() writes the specified amount of data into flash. + // flashStart is the address in memory where the write should start + // data is a pointer to the data to be written + // dataLength is length of data in bytes + + byte read(uint32_t address); + void read(uint32_t address, void *data, uint32_t dataLength); + bool write(uint32_t address, byte value); + bool write(uint32_t address, const void *data, uint32_t dataLength); +}; + +#endif diff --git a/Flash/efc.cpp b/Flash/efc.cpp new file mode 100644 index 0000000..74f0c8a --- /dev/null +++ b/Flash/efc.cpp @@ -0,0 +1,340 @@ +/** + * \file + * + * \brief Enhanced Embedded Flash Controller (EEFC) driver for SAM. + * + * Copyright (c) 2011-2012 Atmel Corporation. All rights reserved. + * + * \asf_license_start + * + * \page License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of Atmel may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 4. This software may only be redistributed and used in connection with an + * Atmel microcontroller product. + * + * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \asf_license_stop + * + */ + +#include "efc.h" + +/// @cond 0 +/**INDENT-OFF**/ +#ifdef __cplusplus +extern "C" { +#endif +/**INDENT-ON**/ +/// @endcond + +/** + * \defgroup sam_drivers_efc_group Enhanced Embedded Flash Controller (EEFC) + * + * The Enhanced Embedded Flash Controller ensures the interface of the Flash + * block with the 32-bit internal bus. + * + * @{ + */ + +/* Address definition for read operation */ +# define READ_BUFF_ADDR0 IFLASH0_ADDR +# define READ_BUFF_ADDR1 IFLASH1_ADDR + +/* Flash Writing Protection Key */ +#define FWP_KEY 0x5Au + +#if (SAM4S || SAM4E) +#define EEFC_FCR_FCMD(value) \ + ((EEFC_FCR_FCMD_Msk & ((value) << EEFC_FCR_FCMD_Pos))) +#define EEFC_ERROR_FLAGS (EEFC_FSR_FLOCKE | EEFC_FSR_FCMDE | EEFC_FSR_FLERR) +#else +#define EEFC_ERROR_FLAGS (EEFC_FSR_FLOCKE | EEFC_FSR_FCMDE) +#endif + + + +/* + * Local function declaration. + * Because they are RAM functions, they need 'extern' declaration. + */ +extern void efc_write_fmr(Efc *p_efc, uint32_t ul_fmr); +extern uint32_t efc_perform_fcr(Efc *p_efc, uint32_t ul_fcr); + +/** + * \brief Initialize the EFC controller. + * + * \param ul_access_mode 0 for 128-bit, EEFC_FMR_FAM for 64-bit. + * \param ul_fws The number of wait states in cycle (no shift). + * + * \return 0 if successful. + */ +uint32_t efc_init(Efc *p_efc, uint32_t ul_access_mode, uint32_t ul_fws) +{ + efc_write_fmr(p_efc, ul_access_mode | EEFC_FMR_FWS(ul_fws)); + return EFC_RC_OK; +} + +/** + * \brief Enable the flash ready interrupt. + * + * \param p_efc Pointer to an EFC instance. + */ +void efc_enable_frdy_interrupt(Efc *p_efc) +{ + uint32_t ul_fmr = p_efc->EEFC_FMR; + + efc_write_fmr(p_efc, ul_fmr | EEFC_FMR_FRDY); +} + +/** + * \brief Disable the flash ready interrupt. + * + * \param p_efc Pointer to an EFC instance. + */ +void efc_disable_frdy_interrupt(Efc *p_efc) +{ + uint32_t ul_fmr = p_efc->EEFC_FMR; + + efc_write_fmr(p_efc, ul_fmr & (~EEFC_FMR_FRDY)); +} + +/** + * \brief Set flash access mode. + * + * \param p_efc Pointer to an EFC instance. + * \param ul_mode 0 for 128-bit, EEFC_FMR_FAM for 64-bit. + */ +void efc_set_flash_access_mode(Efc *p_efc, uint32_t ul_mode) +{ + uint32_t ul_fmr = p_efc->EEFC_FMR & (~EEFC_FMR_FAM); + + efc_write_fmr(p_efc, ul_fmr | ul_mode); +} + +/** + * \brief Get flash access mode. + * + * \param p_efc Pointer to an EFC instance. + * + * \return 0 for 128-bit or EEFC_FMR_FAM for 64-bit. + */ +uint32_t efc_get_flash_access_mode(Efc *p_efc) +{ + return (p_efc->EEFC_FMR & EEFC_FMR_FAM); +} + +/** + * \brief Set flash wait state. + * + * \param p_efc Pointer to an EFC instance. + * \param ul_fws The number of wait states in cycle (no shift). + */ +void efc_set_wait_state(Efc *p_efc, uint32_t ul_fws) +{ + uint32_t ul_fmr = p_efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk); + + efc_write_fmr(p_efc, ul_fmr | EEFC_FMR_FWS(ul_fws)); +} + +/** + * \brief Get flash wait state. + * + * \param p_efc Pointer to an EFC instance. + * + * \return The number of wait states in cycle (no shift). + */ +uint32_t efc_get_wait_state(Efc *p_efc) +{ + return ((p_efc->EEFC_FMR & EEFC_FMR_FWS_Msk) >> EEFC_FMR_FWS_Pos); +} + +/** + * \brief Perform the given command and wait until its completion (or an error). + * + * \note Unique ID commands are not supported, use efc_read_unique_id. + * + * \param p_efc Pointer to an EFC instance. + * \param ul_command Command to perform. + * \param ul_argument Optional command argument. + * + * \note This function will automatically choose to use IAP function. + * + * \return 0 if successful, otherwise returns an error code. + */ +uint32_t efc_perform_command(Efc *p_efc, uint32_t ul_command, + uint32_t ul_argument) +{ + /* Unique ID commands are not supported. */ + if (ul_command == EFC_FCMD_STUI || ul_command == EFC_FCMD_SPUI) { + return EFC_RC_NOT_SUPPORT; + } + + /* Use IAP function with 2 parameters in ROM. */ + static uint32_t(*iap_perform_command) (uint32_t, uint32_t); + uint32_t ul_efc_nb = (p_efc == EFC0) ? 0 : 1; + + iap_perform_command = + (uint32_t(*)(uint32_t, uint32_t)) + *((uint32_t *) CHIP_FLASH_IAP_ADDRESS); + iap_perform_command(ul_efc_nb, + EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(ul_argument) | + EEFC_FCR_FCMD(ul_command)); + return (p_efc->EEFC_FSR & EEFC_ERROR_FLAGS); +} + +/** + * \brief Get the current status of the EEFC. + * + * \note This function clears the value of some status bits (FLOCKE, FCMDE). + * + * \param p_efc Pointer to an EFC instance. + * + * \return The current status. + */ +uint32_t efc_get_status(Efc *p_efc) +{ + return p_efc->EEFC_FSR; +} + +/** + * \brief Get the result of the last executed command. + * + * \param p_efc Pointer to an EFC instance. + * + * \return The result of the last executed command. + */ +uint32_t efc_get_result(Efc *p_efc) +{ + return p_efc->EEFC_FRR; +} + +/** + * \brief Perform read sequence. Supported sequences are read Unique ID and + * read User Signature + * + * \param p_efc Pointer to an EFC instance. + * \param ul_cmd_st Start command to perform. + * \param ul_cmd_sp Stop command to perform. + * \param p_ul_buf Pointer to an data buffer. + * \param ul_size Buffer size. + * + * \return 0 if successful, otherwise returns an error code. + */ +RAMFUNC +uint32_t efc_perform_read_sequence(Efc *p_efc, + uint32_t ul_cmd_st, uint32_t ul_cmd_sp, + uint32_t *p_ul_buf, uint32_t ul_size) +{ + volatile uint32_t ul_status; + uint32_t ul_cnt; + + uint32_t *p_ul_data = + (uint32_t *) ((p_efc == EFC0) ? + READ_BUFF_ADDR0 : READ_BUFF_ADDR1); + + if (p_ul_buf == NULL) { + return EFC_RC_INVALID; + } + + p_efc->EEFC_FMR |= (0x1u << 16); + + /* Send the Start Read command */ + + p_efc->EEFC_FCR = EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(0) + | EEFC_FCR_FCMD(ul_cmd_st); + /* Wait for the FRDY bit in the Flash Programming Status Register + * (EEFC_FSR) falls. + */ + do { + ul_status = p_efc->EEFC_FSR; + } while ((ul_status & EEFC_FSR_FRDY) == EEFC_FSR_FRDY); + + /* The data is located in the first address of the Flash + * memory mapping. + */ + for (ul_cnt = 0; ul_cnt < ul_size; ul_cnt++) { + p_ul_buf[ul_cnt] = p_ul_data[ul_cnt]; + } + + /* To stop the read mode */ + p_efc->EEFC_FCR = + EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(0) | + EEFC_FCR_FCMD(ul_cmd_sp); + /* Wait for the FRDY bit in the Flash Programming Status Register (EEFC_FSR) + * rises. + */ + do { + ul_status = p_efc->EEFC_FSR; + } while ((ul_status & EEFC_FSR_FRDY) != EEFC_FSR_FRDY); + + p_efc->EEFC_FMR &= ~(0x1u << 16); + + return EFC_RC_OK; +} + +/** + * \brief Set mode register. + * + * \param p_efc Pointer to an EFC instance. + * \param ul_fmr Value of mode register + */ +RAMFUNC +void efc_write_fmr(Efc *p_efc, uint32_t ul_fmr) +{ + p_efc->EEFC_FMR = ul_fmr; +} + +/** + * \brief Perform command. + * + * \param p_efc Pointer to an EFC instance. + * \param ul_fcr Flash command. + * + * \return The current status. + */ +RAMFUNC +uint32_t efc_perform_fcr(Efc *p_efc, uint32_t ul_fcr) +{ + volatile uint32_t ul_status; + + p_efc->EEFC_FCR = ul_fcr; + do { + ul_status = p_efc->EEFC_FSR; + } while ((ul_status & EEFC_FSR_FRDY) != EEFC_FSR_FRDY); + + return (ul_status & EEFC_ERROR_FLAGS); +} + +//@} + +/// @cond 0 +/**INDENT-OFF**/ +#ifdef __cplusplus +} +#endif +/**INDENT-ON**/ +/// @endcond diff --git a/Flash/efc.h b/Flash/efc.h new file mode 100644 index 0000000..4481b98 --- /dev/null +++ b/Flash/efc.h @@ -0,0 +1,139 @@ +/** + * \file + * + * \brief Embedded Flash Controller (EFC) driver for SAM. + * + * Copyright (c) 2011-2012 Atmel Corporation. All rights reserved. + * + * \asf_license_start + * + * \page License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of Atmel may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 4. This software may only be redistributed and used in connection with an + * Atmel microcontroller product. + * + * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \asf_license_stop + * + */ + +#ifndef EFC_H_INCLUDED +#define EFC_H_INCLUDED + +#include +#include + +#define SAM3XA +#define RAMFUNC __attribute__ ((section(".ramfunc"))) + +/// @cond 0 +/**INDENT-OFF**/ +#ifdef __cplusplus +extern "C" { +#endif +/**INDENT-ON**/ +/// @endcond + +/*! \name EFC return codes */ +//! @{ +typedef enum efc_rc { + EFC_RC_OK = 0, //!< Operation OK + EFC_RC_YES = 0, //!< Yes + EFC_RC_NO = 1, //!< No + EFC_RC_ERROR = 1, //!< General error + EFC_RC_INVALID, //!< Invalid argument input + EFC_RC_NOT_SUPPORT = 0xFFFFFFFF //!< Operation is not supported +} efc_rc_t; +//! @} + +/*! \name EFC command */ +//! @{ +#define EFC_FCMD_GETD 0x00 //!< Get Flash Descriptor +#define EFC_FCMD_WP 0x01 //!< Write page +#define EFC_FCMD_WPL 0x02 //!< Write page and lock +#define EFC_FCMD_EWP 0x03 //!< Erase page and write page +#define EFC_FCMD_EWPL 0x04 //!< Erase page and write page then lock +#define EFC_FCMD_EA 0x05 //!< Erase all +#if (SAM3SD8) +#define EFC_FCMD_EPL 0x06 //!< Erase plane +#endif +#if (SAM4S || SAM4E) +#define EFC_FCMD_EPA 0x07 //!< Erase pages +#endif +#define EFC_FCMD_SLB 0x08 //!< Set Lock Bit +#define EFC_FCMD_CLB 0x09 //!< Clear Lock Bit +#define EFC_FCMD_GLB 0x0A //!< Get Lock Bit +#define EFC_FCMD_SGPB 0x0B //!< Set GPNVM Bit +#define EFC_FCMD_CGPB 0x0C //!< Clear GPNVM Bit +#define EFC_FCMD_GGPB 0x0D //!< Get GPNVM Bit +#define EFC_FCMD_STUI 0x0E //!< Start unique ID +#define EFC_FCMD_SPUI 0x0F //!< Stop unique ID +#if (!SAM3U && !SAM3SD8 && !SAM3S8) +#define EFC_FCMD_GCALB 0x10 //!< Get CALIB Bit +#endif +#if (SAM4S || SAM4E) +#define EFC_FCMD_ES 0x11 //!< Erase sector +#define EFC_FCMD_WUS 0x12 //!< Write user signature +#define EFC_FCMD_EUS 0x13 //!< Erase user signature +#define EFC_FCMD_STUS 0x14 //!< Start read user signature +#define EFC_FCMD_SPUS 0x15 //!< Stop read user signature +#endif +//! @} + +/*! The IAP function entry address */ +#define CHIP_FLASH_IAP_ADDRESS (IROM_ADDR + 8) + +/*! \name EFC access mode */ +//! @{ +#define EFC_ACCESS_MODE_128 0 +#define EFC_ACCESS_MODE_64 EEFC_FMR_FAM +//! @} + +uint32_t efc_init(Efc *p_efc, uint32_t ul_access_mode, uint32_t ul_fws); +void efc_enable_frdy_interrupt(Efc *p_efc); +void efc_disable_frdy_interrupt(Efc *p_efc); +void efc_set_flash_access_mode(Efc *p_efc, uint32_t ul_mode); +uint32_t efc_get_flash_access_mode(Efc *p_efc); +void efc_set_wait_state(Efc *p_efc, uint32_t ul_fws); +uint32_t efc_get_wait_state(Efc *p_efc); +uint32_t efc_perform_command(Efc *p_efc, uint32_t ul_command, + uint32_t ul_argument); +uint32_t efc_get_status(Efc *p_efc); +uint32_t efc_get_result(Efc *p_efc); +uint32_t efc_perform_read_sequence(Efc *p_efc, + uint32_t ul_cmd_st, uint32_t ul_cmd_sp, + uint32_t *p_ul_buf, uint32_t ul_size); + +/// @cond 0 +/**INDENT-OFF**/ +#ifdef __cplusplus +} +#endif +/**INDENT-ON**/ +/// @endcond + +#endif /* EFC_H_INCLUDED */ diff --git a/Flash/flash_efc.cpp b/Flash/flash_efc.cpp new file mode 100644 index 0000000..c7b3aa8 --- /dev/null +++ b/Flash/flash_efc.cpp @@ -0,0 +1,916 @@ +/** + * \file + * + * \brief Embedded Flash service for SAM. + * + * Copyright (c) 2011-2013 Atmel Corporation. All rights reserved. + * + * \asf_license_start + * + * \page License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of Atmel may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 4. This software may only be redistributed and used in connection with an + * Atmel microcontroller product. + * + * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \asf_license_stop + * + */ + +#include +#include +#include "flash_efc.h" + +/// @cond 0 +/**INDENT-OFF**/ +#ifdef __cplusplus +extern "C" { +#endif +/**INDENT-ON**/ +/// @endcond + +/** + * \defgroup sam_services_flash_efc_group Embedded Flash Service + * + * The Embedded Flash service provides functions for internal flash operations. + * + * @{ + */ + +#if SAM4E +/* User signature size */ +# define FLASH_USER_SIG_SIZE (512) +#endif + +#if SAM4S +/* Internal Flash Controller 0. */ +# define EFC EFC0 +/* User signature size */ +# define FLASH_USER_SIG_SIZE (512) +/* Internal Flash 0 base address. */ +# define IFLASH_ADDR IFLASH0_ADDR +/* Internal flash page size. */ +# define IFLASH_PAGE_SIZE IFLASH0_PAGE_SIZE +/* Internal flash lock region size. */ +# define IFLASH_LOCK_REGION_SIZE IFLASH0_LOCK_REGION_SIZE +#endif + +/* Internal Flash Controller 0. */ +# define EFC EFC0 +/* The max GPNVM number. */ +# define GPNVM_NUM_MAX 3 +/* Internal Flash 0 base address. */ +# define IFLASH_ADDR IFLASH0_ADDR +/* Internal flash page size. */ +# define IFLASH_PAGE_SIZE IFLASH0_PAGE_SIZE +/* Internal flash lock region size. */ +# define IFLASH_LOCK_REGION_SIZE IFLASH0_LOCK_REGION_SIZE + +/* Flash page buffer for alignment */ +static uint32_t gs_ul_page_buffer[IFLASH_PAGE_SIZE / sizeof(uint32_t)]; + +/** + * \brief Translate the given flash address to page and offset values. + * \note pus_page and pus_offset must not be null in order to store the + * corresponding values. + * + * \param pp_efc Pointer to an EFC pointer. + * \param ul_addr Address to translate. + * \param pus_page The first page accessed. + * \param pus_offset Byte offset in the first page. + */ +static void translate_address(Efc **pp_efc, uint32_t ul_addr, + uint16_t *pus_page, uint16_t *pus_offset) +{ + Efc *p_efc; + uint16_t us_page; + uint16_t us_offset; + + if (ul_addr >= IFLASH1_ADDR) { + p_efc = EFC1; + us_page = (ul_addr - IFLASH1_ADDR) / IFLASH1_PAGE_SIZE; + us_offset = (ul_addr - IFLASH1_ADDR) % IFLASH1_PAGE_SIZE; + } else { + p_efc = EFC0; + us_page = (ul_addr - IFLASH0_ADDR) / IFLASH0_PAGE_SIZE; + us_offset = (ul_addr - IFLASH0_ADDR) % IFLASH0_PAGE_SIZE; + } + + /* Store values */ + if (pp_efc) { + *pp_efc = p_efc; + } + + if (pus_page) { + *pus_page = us_page; + } + + if (pus_offset) { + *pus_offset = us_offset; + } +} + +/** + * \brief Compute the address of a flash by the given page and offset. + * + * \param p_efc Pointer to an EFC instance. + * \param us_page Page number. + * \param us_offset Byte offset inside page. + * \param pul_addr Computed address (optional). + */ +static void compute_address(Efc *p_efc, uint16_t us_page, uint16_t us_offset, + uint32_t *pul_addr) +{ + uint32_t ul_addr; + +/* Dual bank flash */ +#ifdef EFC1 + /* Compute address */ + ul_addr = (p_efc == EFC0) ? + IFLASH0_ADDR + us_page * IFLASH_PAGE_SIZE + us_offset : + IFLASH1_ADDR + us_page * IFLASH_PAGE_SIZE + us_offset; + +/* One bank flash */ +#else + /* avoid Cppcheck Warning */ + UNUSED(p_efc); + /* Compute address */ + ul_addr = IFLASH_ADDR + us_page * IFLASH_PAGE_SIZE + us_offset; +#endif + + /* Store result */ + if (pul_addr != NULL) { + *pul_addr = ul_addr; + } +} + +/** + * \brief Compute the lock range associated with the given address range. + * + * \param ul_start Start address of lock range. + * \param ul_end End address of lock range. + * \param pul_actual_start Actual start address of lock range. + * \param pul_actual_end Actual end address of lock range. + */ +static void compute_lock_range(uint32_t ul_start, uint32_t ul_end, + uint32_t *pul_actual_start, uint32_t *pul_actual_end) +{ + uint32_t ul_actual_start, ul_actual_end; + + ul_actual_start = ul_start - (ul_start % IFLASH_LOCK_REGION_SIZE); + ul_actual_end = ul_end - (ul_end % IFLASH_LOCK_REGION_SIZE) + + IFLASH_LOCK_REGION_SIZE - 1; + + if (pul_actual_start) { + *pul_actual_start = ul_actual_start; + } + + if (pul_actual_end) { + *pul_actual_end = ul_actual_end; + } +} + +/** + * \brief Initialize the flash service. + * + * \param ul_mode FLASH_ACCESS_MODE_128 or FLASH_ACCESS_MODE_64. + * \param ul_fws The number of wait states in cycle (no shift). + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_init(uint32_t ul_mode, uint32_t ul_fws) +{ + efc_init(EFC, ul_mode, ul_fws); + +#ifdef EFC1 + efc_init(EFC1, ul_mode, ul_fws); +#endif + + return FLASH_RC_OK; +} + +/** + * \brief Set flash wait state. + * + * \param ul_address Flash bank start address. + * \param ul_fws The number of wait states in cycle (no shift). + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_set_wait_state(uint32_t ul_address, uint32_t ul_fws) +{ + Efc *p_efc; + + translate_address(&p_efc, ul_address, NULL, NULL); + efc_set_wait_state(p_efc, ul_fws); + + return FLASH_RC_OK; +} + +/** + * \brief Set flash wait state. + * + * \param ul_address Flash bank start address. + * \param ul_fws The number of wait states in cycle (no shift). + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_set_wait_state_adaptively(uint32_t ul_address) +{ + Efc *p_efc; + uint32_t clock = SystemCoreClock; + + translate_address(&p_efc, ul_address, NULL, NULL); + + /* Set FWS for embedded Flash access according to operating frequency */ + if (clock < CHIP_FREQ_FWS_0) { + efc_set_wait_state(p_efc, 0); + } else if (clock < CHIP_FREQ_FWS_1) { + efc_set_wait_state(p_efc, 1); + } else if (clock < CHIP_FREQ_FWS_2) { + efc_set_wait_state(p_efc, 2); + } else if (clock < CHIP_FREQ_FWS_3) { + efc_set_wait_state(p_efc, 3); + } else { + efc_set_wait_state(p_efc, 4); + } + return FLASH_RC_OK; +} + +/** + * \brief Get flash wait state. + * + * \param ul_address Flash bank start address. + * + * \return The number of wait states in cycle (no shift). + */ +uint32_t flash_get_wait_state(uint32_t ul_address) +{ + Efc *p_efc; + + translate_address(&p_efc, ul_address, NULL, NULL); + return efc_get_wait_state(p_efc); +} + +/** + * \brief Get flash descriptor. + * + * \param ul_address Flash bank start address. + * \param pul_flash_descriptor Pointer to a data buffer to store flash descriptor. + * \param ul_size Data buffer size in DWORD. + * + * \return The actual descriptor length. + */ +uint32_t flash_get_descriptor(uint32_t ul_address, + uint32_t *pul_flash_descriptor, uint32_t ul_size) +{ + Efc *p_efc; + uint32_t ul_tmp; + uint32_t ul_cnt; + + translate_address(&p_efc, ul_address, NULL, NULL); + + /* Command fails */ + if (FLASH_RC_OK != efc_perform_command(p_efc, EFC_FCMD_GETD, 0)) { + return 0; + } else { + /* Read until no result */ + for (ul_cnt = 0;; ul_cnt++) { + ul_tmp = efc_get_result(p_efc); + if ((ul_size > ul_cnt) && (ul_tmp != 0)) { + *pul_flash_descriptor++ = ul_tmp; + } else { + break; + } + } + } + + return ul_cnt; +} + +/** + * \brief Get flash total page count for the specified bank. + * + * \note The flash descriptor must be fetched from flash_get_descriptor + * function first. + * + * \param pul_flash_descriptor Pointer to a flash descriptor. + * + * \return The flash total page count. + */ +uint32_t flash_get_page_count(const uint32_t *pul_flash_descriptor) +{ + return (pul_flash_descriptor[1] / pul_flash_descriptor[2]); +} + +/** + * \brief Get flash page count per region (plane) for the specified bank. + * + * \note The flash descriptor must be fetched from flash_get_descriptor + * function first. + * + * \param pul_flash_descriptor Pointer to a flash descriptor. + * + * \return The flash page count per region (plane). + */ +uint32_t flash_get_page_count_per_region(const uint32_t *pul_flash_descriptor) +{ + return (pul_flash_descriptor[4] / pul_flash_descriptor[2]); +} + +/** + * \brief Get flash region (plane) count for the specified bank. + * + * \note The flash descriptor must be fetched from flash_get_descriptor + * function first. + * + * \param pul_flash_descriptor Pointer to a flash descriptor. + * + * \return The flash region (plane) count. + */ +uint32_t flash_get_region_count(const uint32_t *pul_flash_descriptor) +{ + return (pul_flash_descriptor[3]); +} + +/** + * \brief Erase the entire flash. + * + * \note Only the flash bank including ul_address will be erased. If there are + * two flash banks, we need to call this function twice with each bank start + * address. + * + * \param ul_address Flash bank start address. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_erase_all(uint32_t ul_address) +{ + Efc *p_efc; + + translate_address(&p_efc, ul_address, NULL, NULL); + + if (EFC_RC_OK != efc_perform_command(p_efc, EFC_FCMD_EA, 0)) { + return FLASH_RC_ERROR; + } + + return FLASH_RC_OK; +} + +#if SAM3SD8 +/** + * \brief Erase the flash by plane. + * + * \param ul_address Flash plane start address. + * + * \note Erase plane command needs a page number parameter which belongs to + * the plane to be erased. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_erase_plane(uint32_t ul_address) +{ + Efc *p_efc; + uint16_t us_page; + + translate_address(&p_efc, ul_address, &us_page, NULL); + + if (EFC_RC_OK != efc_perform_command(p_efc, EFC_FCMD_EPL, us_page)) { + return FLASH_RC_ERROR; + } + + return FLASH_RC_OK; +} +#endif + +#if (SAM4S || SAM4E) +/** + * \brief Erase the specified pages of flash. + * + * \param ul_address Flash bank start address. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_erase_page(uint32_t ul_address, uint8_t uc_page_num) +{ + Efc *p_efc; + uint16_t us_page; + + if (uc_page_num >= IFLASH_ERASE_PAGES_INVALID) { + return FLASH_RC_INVALID; + } + + if (ul_address & (IFLASH_PAGE_SIZE - 1)) { + return FLASH_RC_INVALID; + } + + translate_address(&p_efc, ul_address, &us_page, NULL); + + if (EFC_RC_OK != efc_perform_command(p_efc, EFC_FCMD_EPA, + (us_page | uc_page_num))) { + return FLASH_RC_ERROR; + } + + return FLASH_RC_OK; +} + +/** + * \brief Erase the flash sector. + * + * \note Erase sector command needs a page number parameter which belongs to + * the sector to be erased. + * + * \param ul_address Flash sector start address. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_erase_sector(uint32_t ul_address) +{ + Efc *p_efc; + uint16_t us_page; + + translate_address(&p_efc, ul_address, &us_page, NULL); + + if (EFC_RC_OK != efc_perform_command(p_efc, EFC_FCMD_ES, us_page)) { + return FLASH_RC_ERROR; + } + + return FLASH_RC_OK; +} +#endif + +/** + * \brief Write a data buffer on flash. + * + * \note This function works in polling mode, and thus only returns when the + * data has been effectively written. + * \note For dual bank flash, this function doesn't support cross write from + * bank 0 to bank 1. In this case, flash_write must be called twice (ie for + * each bank). + * + * \param ul_address Write address. + * \param p_buffer Data buffer. + * \param ul_size Size of data buffer in bytes. + * \param ul_erase_flag Flag to set if erase first. + * + * \return 0 if successful, otherwise returns an error code. + */ +uint32_t flash_write(uint32_t ul_address, const void *p_buffer, + uint32_t ul_size, uint32_t ul_erase_flag) +{ + Efc *p_efc; + uint32_t ul_fws_temp; + uint16_t us_page; + uint16_t us_offset; + uint32_t writeSize; + uint32_t ul_page_addr; + uint16_t us_padding; + uint32_t ul_error; + uint32_t ul_idx; + uint32_t *p_aligned_dest; + uint8_t *puc_page_buffer = (uint8_t *) gs_ul_page_buffer; + + translate_address(&p_efc, ul_address, &us_page, &us_offset); + + /* According to the errata, set the wait state value to 6. */ + ul_fws_temp = efc_get_wait_state(p_efc); + efc_set_wait_state(p_efc, 6); + + /* Write all pages */ + while (ul_size > 0) { + /* Copy data in temporary buffer to avoid alignment problems. */ + writeSize = Min((uint32_t) IFLASH_PAGE_SIZE - us_offset, + ul_size); + compute_address(p_efc, us_page, 0, &ul_page_addr); + us_padding = IFLASH_PAGE_SIZE - us_offset - writeSize; + + /* Pre-buffer data */ + memcpy(puc_page_buffer, (void *)ul_page_addr, us_offset); + + /* Buffer data */ + memcpy(puc_page_buffer + us_offset, p_buffer, writeSize); + + /* Post-buffer data */ + memcpy(puc_page_buffer + us_offset + writeSize, + (void *)(ul_page_addr + us_offset + writeSize), + us_padding); + + /* Write page. + * Writing 8-bit and 16-bit data is not allowed and may lead to + * unpredictable data corruption. + */ + p_aligned_dest = (uint32_t *) ul_page_addr; + for (ul_idx = 0; ul_idx < (IFLASH_PAGE_SIZE / sizeof(uint32_t)); + ++ul_idx) { + *p_aligned_dest++ = gs_ul_page_buffer[ul_idx]; + } + + if (ul_erase_flag) { + ul_error = efc_perform_command(p_efc, EFC_FCMD_EWP, + us_page); + } else { + ul_error = efc_perform_command(p_efc, EFC_FCMD_WP, + us_page); + } + + if (ul_error) { + return ul_error; + } + + /* Progression */ + p_buffer = (void *)((uint32_t) p_buffer + writeSize); + ul_size -= writeSize; + us_page++; + us_offset = 0; + } + + /* According to the errata, restore the wait state value. */ + efc_set_wait_state(p_efc, ul_fws_temp); + + return FLASH_RC_OK; +} + + +/** + * \brief Lock all the regions in the given address range. The actual lock + * range is reported through two output parameters. + * + * \param ul_start Start address of lock range. + * \param ul_end End address of lock range. + * \param pul_actual_start Start address of the actual lock range (optional). + * \param pul_actual_end End address of the actual lock range (optional). + * + * \return 0 if successful, otherwise returns an error code. + */ +uint32_t flash_lock(uint32_t ul_start, uint32_t ul_end, + uint32_t *pul_actual_start, uint32_t *pul_actual_end) +{ + Efc *p_efc; + uint32_t ul_actual_start, ul_actual_end; + uint16_t us_start_page, us_end_page; + uint32_t ul_error; + uint16_t us_num_pages_in_region = + IFLASH_LOCK_REGION_SIZE / IFLASH_PAGE_SIZE; + + /* Compute actual lock range and store it */ + compute_lock_range(ul_start, ul_end, &ul_actual_start, &ul_actual_end); + + if (pul_actual_start != NULL) { + *pul_actual_start = ul_actual_start; + } + + if (pul_actual_end != NULL) { + *pul_actual_end = ul_actual_end; + } + + /* Compute page numbers */ + translate_address(&p_efc, ul_actual_start, &us_start_page, 0); + translate_address(0, ul_actual_end, &us_end_page, 0); + + /* Lock all pages */ + while (us_start_page < us_end_page) { + ul_error = efc_perform_command(p_efc, EFC_FCMD_SLB, us_start_page); + + if (ul_error) { + return ul_error; + } + us_start_page += us_num_pages_in_region; + } + + return FLASH_RC_OK; +} + +/** + * \brief Unlock all the regions in the given address range. The actual unlock + * range is reported through two output parameters. + * + * \param ul_start Start address of unlock range. + * \param ul_end End address of unlock range. + * \param pul_actual_start Start address of the actual unlock range (optional). + * \param pul_actual_end End address of the actual unlock range (optional). + * + * \return 0 if successful, otherwise returns an error code. + */ +uint32_t flash_unlock(uint32_t ul_start, uint32_t ul_end, + uint32_t *pul_actual_start, uint32_t *pul_actual_end) +{ + Efc *p_efc; + uint32_t ul_actual_start, ul_actual_end; + uint16_t us_start_page, us_end_page; + uint32_t ul_error; + uint16_t us_num_pages_in_region = + IFLASH_LOCK_REGION_SIZE / IFLASH_PAGE_SIZE; + + /* Compute actual unlock range and store it */ + compute_lock_range(ul_start, ul_end, &ul_actual_start, &ul_actual_end); + if (pul_actual_start != NULL) { + *pul_actual_start = ul_actual_start; + } + if (pul_actual_end != NULL) { + *pul_actual_end = ul_actual_end; + } + + /* Compute page numbers */ + translate_address(&p_efc, ul_actual_start, &us_start_page, 0); + translate_address(0, ul_actual_end, &us_end_page, 0); + + /* Unlock all pages */ + while (us_start_page < us_end_page) { + ul_error = efc_perform_command(p_efc, EFC_FCMD_CLB, + us_start_page); + if (ul_error) { + return ul_error; + } + us_start_page += us_num_pages_in_region; + } + + return FLASH_RC_OK; +} + +/** + * \brief Get the number of locked regions inside the given address range. + * + * \param ul_start Start address of range + * \param ul_end End address of range. + * + * \return The number of locked regions inside the given address range. + */ +uint32_t flash_is_locked(uint32_t ul_start, uint32_t ul_end) +{ + Efc *p_efc; + uint16_t us_start_page, us_end_page; + uint8_t uc_start_region, uc_end_region; + uint16_t us_num_pages_in_region; + uint32_t ul_status; + uint32_t ul_error; + uint32_t ul_num_locked_regions = 0; + uint32_t ul_count = 0; + uint32_t ul_bit = 0; + + /* Compute page numbers */ + translate_address(&p_efc, ul_start, &us_start_page, 0); + translate_address(0, ul_end, &us_end_page, 0); + + /* Compute region numbers */ + us_num_pages_in_region = IFLASH_LOCK_REGION_SIZE / IFLASH_PAGE_SIZE; + uc_start_region = us_start_page / us_num_pages_in_region; + uc_end_region = us_end_page / us_num_pages_in_region; + + /* Retrieve lock status */ + ul_error = efc_perform_command(p_efc, EFC_FCMD_GLB, 0); + + /* Skip unrequested regions (if necessary) */ + ul_status = efc_get_result(p_efc); + while (!(ul_count <= uc_start_region && + uc_start_region < (ul_count + 32))) { + ul_status = efc_get_result(p_efc); + ul_count += 32; + } + + /* Check status of each involved region */ + ul_bit = uc_start_region - ul_count; + + /* Number of region to check (must be > 0) */ + ul_count = uc_end_region - uc_start_region + 1; + + while (ul_count > 0) { + if (ul_status & (1 << (ul_bit))) { + ul_num_locked_regions++; + } + + ul_count -= 1; + ul_bit += 1; + if (ul_bit == 32) { + ul_status = efc_get_result(p_efc); + ul_bit = 0; + } + } + + return ul_num_locked_regions; +} + +/** + * \brief Set the given GPNVM bit. + * + * \param ul_gpnvm GPNVM bit index. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_set_gpnvm(uint32_t ul_gpnvm) +{ + if (ul_gpnvm >= GPNVM_NUM_MAX) { + return FLASH_RC_INVALID; + } + + if (FLASH_RC_YES == flash_is_gpnvm_set(ul_gpnvm)) { + return FLASH_RC_OK; + } + + if (EFC_RC_OK == efc_perform_command(EFC, EFC_FCMD_SGPB, ul_gpnvm)) { + return FLASH_RC_OK; + } + + return FLASH_RC_ERROR; +} + +/** + * \brief Clear the given GPNVM bit. + * + * \param ul_gpnvm GPNVM bit index. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_clear_gpnvm(uint32_t ul_gpnvm) +{ + if (ul_gpnvm >= GPNVM_NUM_MAX) { + return FLASH_RC_INVALID; + } + + if (FLASH_RC_NO == flash_is_gpnvm_set(ul_gpnvm)) { + return FLASH_RC_OK; + } + + if (EFC_RC_OK == efc_perform_command(EFC, EFC_FCMD_CGPB, ul_gpnvm)) { + return FLASH_RC_OK; + } + + return FLASH_RC_ERROR; +} + +/** + * \brief Check if the given GPNVM bit is set or not. + * + * \param ul_gpnvm GPNVM bit index. + * + * \retval 1 If the given GPNVM bit is currently set. + * \retval 0 If the given GPNVM bit is currently cleared. + */ +uint32_t flash_is_gpnvm_set(uint32_t ul_gpnvm) +{ + uint32_t ul_gpnvm_bits; + + if (ul_gpnvm >= GPNVM_NUM_MAX) { + return FLASH_RC_INVALID; + } + + if (EFC_RC_OK != efc_perform_command(EFC, EFC_FCMD_GGPB, 0)) { + return FLASH_RC_ERROR; + } + + ul_gpnvm_bits = efc_get_result(EFC); + if (ul_gpnvm_bits & (1 << ul_gpnvm)) { + return FLASH_RC_YES; + } + + return FLASH_RC_NO; +} + +/** + * \brief Set security bit. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_enable_security_bit(void) +{ + return flash_set_gpnvm(0); +} + +/** + * \brief Check if the security bit is set or not. + * + * \retval 1 If the security bit is currently set. + * \retval 0 If the security bit is currently cleared. + */ +uint32_t flash_is_security_bit_enabled(void) +{ + return flash_is_gpnvm_set(0); +} + +/** + * \brief Read the flash unique ID. + * + * \param pul_data Pointer to a data buffer to store 128-bit unique ID. + * \param ul_size Data buffer size in DWORD. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_read_unique_id(uint32_t *pul_data, uint32_t ul_size) +{ + uint32_t uid_buf[4]; + uint32_t ul_idx; + + if (FLASH_RC_OK != efc_perform_read_sequence(EFC, EFC_FCMD_STUI, + EFC_FCMD_SPUI, uid_buf, 4)) { + return FLASH_RC_ERROR; + } + + if (ul_size > 4) { + /* Only 4 dword to store unique ID */ + ul_size = 4; + } + + for (ul_idx = 0; ul_idx < ul_size; ul_idx++) { + pul_data[ul_idx] = uid_buf[ul_idx]; + } + + return FLASH_RC_OK; +} + +#if (SAM4S || SAM4E) +/** + * \brief Read the flash user signature. + * + * \param p_data Pointer to a data buffer to store 512 bytes of user signature. + * \param ul_size Data buffer size. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_read_user_signature(uint32_t *p_data, uint32_t ul_size) +{ + if (ul_size > FLASH_USER_SIG_SIZE) { + /* Only 512 byte to store unique ID */ + ul_size = FLASH_USER_SIG_SIZE; + } + + /* Send the read user signature commands */ + if (FLASH_RC_OK != efc_perform_read_sequence(EFC, EFC_FCMD_STUS, + EFC_FCMD_SPUS, p_data, ul_size)) { + return FLASH_RC_ERROR; + } + + return FLASH_RC_OK; +} + +/** + * \brief Write the flash user signature. + * + * \param ul_address Write address. + * \param p_data Pointer to a data buffer to store 512 bytes of user signature. + * \param ul_size Data buffer size. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_write_user_signature(uint32_t ul_address, const void *p_buffer, + uint32_t ul_size) +{ + /* The user signature should be no longer than 512 bytes */ + if (ul_size > FLASH_USER_SIG_SIZE) { + return FLASH_RC_INVALID; + } + + /* Write the full page */ + flash_write(ul_address, p_buffer, ul_size, 0); + + /* Send the write signature command */ + if (FLASH_RC_OK != efc_perform_command(EFC, EFC_FCMD_WUS, 0)) { + return FLASH_RC_ERROR; + } + + return FLASH_RC_OK; +} + +/** + * \brief Erase the flash user signature. + * + * \return 0 if successful; otherwise returns an error code. + */ +uint32_t flash_erase_user_signature(void) +{ + /* Perform the erase user signature command */ + return efc_perform_command(EFC, EFC_FCMD_EUS, 0); +} +#endif + +//@} + +/// @cond 0 +/**INDENT-OFF**/ +#ifdef __cplusplus +} +#endif +/**INDENT-ON**/ +/// @endcond diff --git a/Flash/flash_efc.h b/Flash/flash_efc.h new file mode 100644 index 0000000..bd52033 --- /dev/null +++ b/Flash/flash_efc.h @@ -0,0 +1,151 @@ +/** + * \file + * + * \brief Embedded Flash service for SAM. + * + * Copyright (c) 2011-2013 Atmel Corporation. All rights reserved. + * + * \asf_license_start + * + * \page License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of Atmel may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 4. This software may only be redistributed and used in connection with an + * Atmel microcontroller product. + * + * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \asf_license_stop + * + */ + +#ifndef FLASH_H_INCLUDED +#define FLASH_H_INCLUDED + +#include +#include "efc.h" + +/* Internal Flash 0 base address. */ +#define IFLASH_ADDR IFLASH0_ADDR + /* Internal flash page size. */ +#define IFLASH_PAGE_SIZE IFLASH0_PAGE_SIZE + +/* Last page start address. */ +#define IFLASH_LAST_PAGE_ADDRESS (IFLASH1_ADDR + IFLASH1_SIZE - IFLASH1_PAGE_SIZE) + +/// @cond 0 +/**INDENT-OFF**/ +#ifdef __cplusplus + extern "C" { +#endif +/**INDENT-ON**/ +/// @endcond + +/*! \name Flash driver return codes */ +//! @{ +typedef enum flash_rc { + FLASH_RC_OK = 0, //!< Operation OK + FLASH_RC_YES = 0, //!< Yes + FLASH_RC_NO = 1, //!< No + FLASH_RC_ERROR = 0x10, //!< General error + FLASH_RC_INVALID, //!< Invalid argument input + FLASH_RC_NOT_SUPPORT = 0xFFFFFFFF //!< Operation is not supported +} flash_rc_t; +//! @} + +/*! \name Flash erase page num in FARG[1:0] + \note The erase page commands should be cautiouly used as EPA4/EPA32 will not + take effect according to errata and EPA8/EPA16 must module 8/16 page addresses.*/ +//! @{ +typedef enum flash_farg_page_num { + /* 4 of pages to be erased with EPA command*/ + IFLASH_ERASE_PAGES_4=0, + /* 8 of pages to be erased with EPA command*/ + IFLASH_ERASE_PAGES_8, + /* 16 of pages to be erased with EPA command*/ + IFLASH_ERASE_PAGES_16, + /* 32 of pages to be erased with EPA command*/ + IFLASH_ERASE_PAGES_32, + /* Parameter is not support */ + IFLASH_ERASE_PAGES_INVALID, +}flash_farg_page_num_t; +//! @} + +/*! \name Flash access mode */ +//! @{ +#define FLASH_ACCESS_MODE_128 EFC_ACCESS_MODE_128 +#define FLASH_ACCESS_MODE_64 EFC_ACCESS_MODE_64 +//! @} + +uint32_t flash_init(uint32_t ul_mode, uint32_t ul_fws); +uint32_t flash_set_wait_state(uint32_t ul_address, uint32_t ul_fws); +uint32_t flash_set_wait_state_adaptively(uint32_t ul_address); +uint32_t flash_get_wait_state(uint32_t ul_address); +uint32_t flash_get_descriptor(uint32_t ul_address, + uint32_t *pul_flash_descriptor, uint32_t ul_size); +uint32_t flash_get_page_count(const uint32_t *pul_flash_descriptor); +uint32_t flash_get_page_count_per_region(const uint32_t *pul_flash_descriptor); +uint32_t flash_get_region_count(const uint32_t *pul_flash_descriptor); +uint32_t flash_erase_all(uint32_t ul_address); + +#if SAM3SD8 +uint32_t flash_erase_plane(uint32_t ul_address); +#endif + +#if (SAM4S || SAM4E) +uint32_t flash_erase_page(uint32_t ul_address, uint8_t uc_page_num); +uint32_t flash_erase_sector(uint32_t ul_address); +#endif + +uint32_t flash_write(uint32_t ul_address, const void *p_buffer, + uint32_t ul_size, uint32_t ul_erase_flag); +uint32_t flash_lock(uint32_t ul_start, uint32_t ul_end, + uint32_t *pul_actual_start, uint32_t *pul_actual_end); +uint32_t flash_unlock(uint32_t ul_start, uint32_t ul_end, + uint32_t *pul_actual_start, uint32_t *pul_actual_end); +uint32_t flash_is_locked(uint32_t ul_start, uint32_t ul_end); +uint32_t flash_set_gpnvm(uint32_t ul_gpnvm); +uint32_t flash_clear_gpnvm(uint32_t ul_gpnvm); +uint32_t flash_is_gpnvm_set(uint32_t ul_gpnvm); +uint32_t flash_enable_security_bit(void); +uint32_t flash_is_security_bit_enabled(void); +uint32_t flash_read_unique_id(uint32_t *pul_data, uint32_t ul_size); + +#if (SAM4S || SAM4E) +uint32_t flash_read_user_signature(uint32_t *p_data, uint32_t ul_size); +uint32_t flash_write_user_signature(uint32_t ul_address, const void *p_buffer, + uint32_t ul_size); +uint32_t flash_erase_user_signature(void); +#endif + +/// @cond 0 +/**INDENT-OFF**/ +#ifdef __cplusplus +} +#endif +/**INDENT-ON**/ +/// @endcond + +#endif /* FLASH_H_INCLUDED */ diff --git a/GCodes.cpp b/GCodes.cpp index 841ca26..5008bdc 100644 --- a/GCodes.cpp +++ b/GCodes.cpp @@ -1,105 +1,106 @@ /**************************************************************************************************** -RepRapFirmware - G Codes + RepRapFirmware - G Codes -This class interprets G Codes from one or more sources, and calls the functions in Move, Heat etc -that drive the machine to do what the G Codes command. + This class interprets G Codes from one or more sources, and calls the functions in Move, Heat etc + that drive the machine to do what the G Codes command. -Most of the functions in here are designed not to wait, and they return a boolean. When you want them to do -something, you call them. If they return false, the machine can't do what you want yet. So you go away -and do something else. Then you try again. If they return true, the thing you wanted done has been done. + Most of the functions in here are designed not to wait, and they return a boolean. When you want them to do + something, you call them. If they return false, the machine can't do what you want yet. So you go away + and do something else. Then you try again. If they return true, the thing you wanted done has been done. ------------------------------------------------------------------------------------------------------ + ----------------------------------------------------------------------------------------------------- -Version 0.1 + Version 0.1 -13 February 2013 + 13 February 2013 -Adrian Bowyer -RepRap Professional Ltd -http://reprappro.com + Adrian Bowyer + RepRap Professional Ltd + http://reprappro.com -Licence: GPL + Licence: GPL -****************************************************************************************************/ + ****************************************************************************************************/ #include "RepRapFirmware.h" GCodes::GCodes(Platform* p, Webserver* w) { - active = false; - platform = p; - webserver = w; - webGCode = new GCodeBuffer(platform, "web: "); - fileGCode = new GCodeBuffer(platform, "file: "); - serialGCode = new GCodeBuffer(platform, "serial: "); - cannedCycleGCode = new GCodeBuffer(platform, "macro: "); + active = false; + platform = p; + webserver = w; + webGCode = new GCodeBuffer(platform, "web: "); + fileGCode = new GCodeBuffer(platform, "file: "); + serialGCode = new GCodeBuffer(platform, "serial: "); + cannedCycleGCode = new GCodeBuffer(platform, "macro: "); } void GCodes::Exit() { - platform->Message(HOST_MESSAGE, "GCodes class exited.\n"); - active = false; + platform->Message(HOST_MESSAGE, "GCodes class exited.\n"); + active = false; } void GCodes::Init() { - webGCode->Init(); - fileGCode->Init(); - serialGCode->Init(); - cannedCycleGCode->Init(); - webGCode->SetFinished(true); - fileGCode->SetFinished(true); - serialGCode->SetFinished(true); - cannedCycleGCode->SetFinished(true); - moveAvailable = false; - drivesRelative = true; - axesRelative = false; - checkEndStops = false; - gCodeLetters = GCODE_LETTERS; - distanceScale = 1.0; - for(int8_t i = 0; i < DRIVES - AXES; i++) - lastPos[i] = 0.0; - fileBeingPrinted = NULL; - fileToPrint = NULL; - fileBeingWritten = NULL; - configFile = NULL; - doingCannedCycleFile = false; - eofString = EOF_STRING; - eofStringCounter = 0; - eofStringLength = strlen(eofString); - homeX = false; - homeY = false; - homeZ = false; - homeAxisMoveCount = 0; - offSetSet = false; - dwellWaiting = false; - stackPointer = 0; - selectedHead = -1; - gFeedRate = platform->MaxFeedrate(Z_AXIS); // Typically the slowest - zProbesSet = false; - probeCount = 0; - cannedCycleMoveCount = 0; - cannedCycleMoveQueued = false; - active = true; - longWait = platform->Time(); - dwellTime = longWait; - axisIsHomed[X_AXIS] = axisIsHomed[Y_AXIS] = axisIsHomed[Z_AXIS] = false; + webGCode->Init(); + fileGCode->Init(); + serialGCode->Init(); + cannedCycleGCode->Init(); + webGCode->SetFinished(true); + fileGCode->SetFinished(true); + serialGCode->SetFinished(true); + cannedCycleGCode->SetFinished(true); + moveAvailable = false; + drivesRelative = true; + axesRelative = false; + checkEndStops = noEndstopCheck; + gCodeLetters = GCODE_LETTERS; + distanceScale = 1.0; + for (int8_t i = 0; i < DRIVES - AXES; i++) + lastPos[i] = 0.0; + fileBeingPrinted = NULL; + fileToPrint = NULL; + fileBeingWritten = NULL; + configFile = NULL; + doingCannedCycleFile = false; + eofString = EOF_STRING; + eofStringCounter = 0; + eofStringLength = strlen(eofString); + homeX = false; + homeY = false; + homeZ = false; + homeAxisMoveCount = 0; + offSetSet = false; + dwellWaiting = false; + stackPointer = 0; + selectedHead = -1; + gFeedRate = platform->MaxFeedrate(Z_AXIS); // Typically the slowest + zProbesSet = false; + probeCount = 0; + cannedCycleMoveCount = 0; + cannedCycleMoveQueued = false; + active = true; + longWait = platform->Time(); + dwellTime = longWait; + axisIsHomed[X_AXIS] = axisIsHomed[Y_AXIS] = axisIsHomed[Z_AXIS] = false; } void GCodes::doFilePrint(GCodeBuffer* gb) { char b; - if(fileBeingPrinted != NULL) + if (fileBeingPrinted != NULL) { - if(fileBeingPrinted->Read(b)) + if (fileBeingPrinted->Read(b)) { - if(gb->Put(b)) + if (gb->Put(b)) gb->SetFinished(ActOnGcode(gb)); - } else + } + else { - if(gb->Put('\n')) // In case there wasn't one ending the file + if (gb->Put('\n')) // In case there wasn't one ending the file gb->SetFinished(ActOnGcode(gb)); fileBeingPrinted->Close(); fileBeingPrinted = NULL; @@ -109,117 +110,118 @@ void GCodes::doFilePrint(GCodeBuffer* gb) void GCodes::Spin() { - if(!active) - return; - - // Check each of the sources of G Codes (web, serial, and file) to - // see if what they are doing has been done. If it hasn't, return without - // looking at anything else. - // - // Note the order establishes a priority: web first, then serial, and file - // last. If file weren't last, then the others would never get a look in when - // a file was being printed. + if (!active) + return; - if(!webGCode->Finished()) - { - webGCode->SetFinished(ActOnGcode(webGCode)); - platform->ClassReport("GCodes", longWait); - return; - } - - if(!serialGCode->Finished()) - { - serialGCode->SetFinished(ActOnGcode(serialGCode)); - platform->ClassReport("GCodes", longWait); - return; - } + // Check each of the sources of G Codes (web, serial, and file) to + // see if what they are doing has been done. If it hasn't, return without + // looking at anything else. + // + // Note the order establishes a priority: web first, then serial, and file + // last. If file weren't last, then the others would never get a look in when + // a file was being printed. - if(!fileGCode->Finished()) - { - fileGCode->SetFinished(ActOnGcode(fileGCode)); - platform->ClassReport("GCodes", longWait); - return; - } + if (!webGCode->Finished()) + { + webGCode->SetFinished(ActOnGcode(webGCode)); + platform->ClassReport("GCodes", longWait); + return; + } - // Now check if a G Code byte is available from each of the sources - // in the same order for the same reason. + if (!serialGCode->Finished()) + { + serialGCode->SetFinished(ActOnGcode(serialGCode)); + platform->ClassReport("GCodes", longWait); + return; + } - if(webserver->GCodeAvailable()) - { - int8_t i = 0; - do - { - char b = webserver->ReadGCode(); - if(webGCode->Put(b)) - { - // we have a complete gcode - if(webGCode->WritingFileDirectory() != NULL) - { - WriteGCodeToFile(webGCode); - } - else - { - webGCode->SetFinished(ActOnGcode(webGCode)); - } - break; // stop after receiving a complete gcode in case we haven't finished processing it - } - ++i; - } while ( i < 16 && webserver->GCodeAvailable()); - platform->ClassReport("GCodes", longWait); - return; - } - - // Now the serial interface. First check the special case of our - // uploading the reprap.htm file + if (!fileGCode->Finished()) + { + fileGCode->SetFinished(ActOnGcode(fileGCode)); + platform->ClassReport("GCodes", longWait); + return; + } - if(serialGCode->WritingFileDirectory() == platform->GetWebDir()) - { - if(platform->GetLine()->Status() & byteAvailable) - { - char b; - platform->GetLine()->Read(b); - WriteHTMLToFile(b, serialGCode); - } - } else - { - // Otherwise just deal in general with incoming bytes from the serial interface + // Now check if a G Code byte is available from each of the sources + // in the same order for the same reason. - if(platform->GetLine()->Status() & byteAvailable) - { - // Read several bytes instead of just one. This approximately doubles the speed of file uploading. - int8_t i = 0; - do - { - char b; - platform->GetLine()->Read(b); - if(serialGCode->Put(b)) // add char to buffer and test whether the gcode is complete - { - // we have a complete gcode - if(serialGCode->WritingFileDirectory() != NULL) - { - WriteGCodeToFile(serialGCode); - } - else - { - serialGCode->SetFinished(ActOnGcode(serialGCode)); - } - break; // stop after receiving a complete gcode in case we haven't finished processing it - } - ++i; - } while (i < 16 && (platform->GetLine()->Status() & byteAvailable)); - platform->ClassReport("GCodes", longWait); - return; - } - } + if (webserver->GCodeAvailable()) + { + int8_t i = 0; + do + { + char b = webserver->ReadGCode(); + if (webGCode->Put(b)) + { + // we have a complete gcode + if (webGCode->WritingFileDirectory() != NULL) + { + WriteGCodeToFile(webGCode); + } + else + { + webGCode->SetFinished(ActOnGcode(webGCode)); + } + break; // stop after receiving a complete gcode in case we haven't finished processing it + } + ++i; + } while (i < 16 && webserver->GCodeAvailable()); + platform->ClassReport("GCodes", longWait); + return; + } - doFilePrint(fileGCode); + // Now the serial interface. First check the special case of our + // uploading the reprap.htm file - platform->ClassReport("GCodes", longWait); + if (serialGCode->WritingFileDirectory() == platform->GetWebDir()) + { + if (platform->GetLine()->Status() & byteAvailable) + { + char b; + platform->GetLine()->Read(b); + WriteHTMLToFile(b, serialGCode); + } + } + else + { + // Otherwise just deal in general with incoming bytes from the serial interface + + if (platform->GetLine()->Status() & byteAvailable) + { + // Read several bytes instead of just one. This approximately doubles the speed of file uploading. + int8_t i = 0; + do + { + char b; + platform->GetLine()->Read(b); + if (serialGCode->Put(b)) // add char to buffer and test whether the gcode is complete + { + // we have a complete gcode + if (serialGCode->WritingFileDirectory() != NULL) + { + WriteGCodeToFile(serialGCode); + } + else + { + serialGCode->SetFinished(ActOnGcode(serialGCode)); + } + break; // stop after receiving a complete gcode in case we haven't finished processing it + } + ++i; + } while (i < 16 && (platform->GetLine()->Status() & byteAvailable)); + platform->ClassReport("GCodes", longWait); + return; + } + } + + doFilePrint(fileGCode); + + platform->ClassReport("GCodes", longWait); } -void GCodes::Diagnostics() +void GCodes::Diagnostics() { - platform->Message(HOST_MESSAGE, "GCodes Diagnostics:\n"); + platform->Message(HOST_MESSAGE, "GCodes Diagnostics:\n"); } // The wait till everything's done function. If you need the machine to @@ -229,23 +231,23 @@ void GCodes::Diagnostics() bool GCodes::AllMovesAreFinishedAndMoveBufferIsLoaded() { - // Last one gone? - - if(moveAvailable) - return false; - - // Wait for all the queued moves to stop so we get the actual last position and feedrate - - if(!reprap.GetMove()->AllMovesAreFinished()) - return false; - reprap.GetMove()->ResumeMoving(); - - // Load the last position; If Move can't accept more, return false - should never happen - - if(!reprap.GetMove()->GetCurrentState(moveBuffer)) - return false; - - return true; + // Last one gone? + + if (moveAvailable) + return false; + + // Wait for all the queued moves to stop so we get the actual last position and feedrate + + if (!reprap.GetMove()->AllMovesAreFinished()) + return false; + reprap.GetMove()->ResumeMoving(); + + // Load the last position; If Move can't accept more, return false - should never happen + + if (!reprap.GetMove()->GetCurrentState(moveBuffer)) + return false; + + return true; } // Save (some of) the state of the machine for recovery in the future. @@ -253,56 +255,56 @@ bool GCodes::AllMovesAreFinishedAndMoveBufferIsLoaded() bool GCodes::Push() { - if(stackPointer >= STACK) - { - platform->Message(HOST_MESSAGE, "Push(): stack overflow!\n"); - return true; - } - - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) - return false; - - drivesRelativeStack[stackPointer] = drivesRelative; - axesRelativeStack[stackPointer] = axesRelative; - feedrateStack[stackPointer] = gFeedRate; - fileStack[stackPointer] = fileBeingPrinted; - stackPointer++; - platform->PushMessageIndent(); - return true; + if (stackPointer >= STACK) + { + platform->Message(HOST_MESSAGE, "Push(): stack overflow!\n"); + return true; + } + + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) + return false; + + drivesRelativeStack[stackPointer] = drivesRelative; + axesRelativeStack[stackPointer] = axesRelative; + feedrateStack[stackPointer] = gFeedRate; + fileStack[stackPointer] = fileBeingPrinted; + stackPointer++; + platform->PushMessageIndent(); + return true; } // Recover a saved state. Call repeatedly till it returns true. bool GCodes::Pop() { - if(stackPointer <= 0) - { - platform->Message(HOST_MESSAGE, "Pop(): stack underflow!\n"); - return true; - } - - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) - return false; - - stackPointer--; - drivesRelative = drivesRelativeStack[stackPointer]; - axesRelative = axesRelativeStack[stackPointer]; - fileBeingPrinted = fileStack[stackPointer]; - platform->PopMessageIndent(); - // Remember for next time if we have just been switched - // to absolute drive moves - - for(int8_t i = AXES; i < DRIVES; i++) - lastPos[i - AXES] = moveBuffer[i]; - - // Do a null move to set the correct feedrate - - gFeedRate = feedrateStack[stackPointer]; - moveBuffer[DRIVES] = gFeedRate; - - checkEndStops = false; - moveAvailable = true; - return true; + if (stackPointer <= 0) + { + platform->Message(HOST_MESSAGE, "Pop(): stack underflow!\n"); + return true; + } + + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) + return false; + + stackPointer--; + drivesRelative = drivesRelativeStack[stackPointer]; + axesRelative = axesRelativeStack[stackPointer]; + fileBeingPrinted = fileStack[stackPointer]; + platform->PopMessageIndent(); + // Remember for next time if we have just been switched + // to absolute drive moves + + for (int8_t i = AXES; i < DRIVES; i++) + lastPos[i - AXES] = moveBuffer[i]; + + // Do a null move to set the correct feedrate + + gFeedRate = feedrateStack[stackPointer]; + moveBuffer[DRIVES] = gFeedRate; + + checkEndStops = noEndstopCheck; + moveAvailable = true; + return true; } // Move expects all axis movements to be absolute, and all @@ -311,122 +313,121 @@ bool GCodes::Pop() void GCodes::LoadMoveBufferFromGCode(GCodeBuffer *gb, bool doingG92, bool applyLimits) { - for(uint8_t i = 0; i < DRIVES; i++) + for (uint8_t i = 0; i < DRIVES; i++) { - if(i < AXES) - { - if(gb->Seen(gCodeLetters[i])) - { - float moveArg = gb->GetFValue()*distanceScale; - if (axesRelative && !doingG92) - { - moveArg += moveBuffer[i]; - } - if (applyLimits && i < 2 && axisIsHomed[i] && !doingG92) // limit X and Y moves unless doing G92 - { - if (moveArg < 0.0) - { - moveArg = 0.0; - } - else if (moveArg > platform->AxisLength(i)) - { - moveArg = platform->AxisLength(i); - } - } - moveBuffer[i] = moveArg; - if (doingG92) - { - axisIsHomed[i] = true; // doing a G92 is equivalent to homing the axis - } - } - } else - { - if(gb->Seen(gCodeLetters[i])) - { - float moveArg = gb->GetFValue()*distanceScale; - if(drivesRelative || doingG92) - moveBuffer[i] = moveArg; - else - { - float absE = moveArg; - moveBuffer[i] = absE - lastPos[i - AXES]; - lastPos[i - AXES] = absE; - } - } - } + if (i < AXES) + { + if (gb->Seen(gCodeLetters[i])) + { + float moveArg = gb->GetFValue() * distanceScale; + if (axesRelative && !doingG92) + { + moveArg += moveBuffer[i]; + } + if (applyLimits && i < 2 && axisIsHomed[i] && !doingG92) // limit X and Y moves unless doing G92 + { + if (moveArg < 0.0) + { + moveArg = 0.0; + } + else if (moveArg > platform->AxisLength(i)) + { + moveArg = platform->AxisLength(i); + } + } + moveBuffer[i] = moveArg; + if (doingG92) + { + axisIsHomed[i] = true; // doing a G92 is equivalent to homing the axis + } + } + } + else + { + if (gb->Seen(gCodeLetters[i])) + { + float moveArg = gb->GetFValue() * distanceScale; + if (drivesRelative || doingG92) + moveBuffer[i] = moveArg; + else + { + float absE = moveArg; + moveBuffer[i] = absE - lastPos[i - AXES]; + lastPos[i - AXES] = absE; + } + } + } } // Deal with feedrate - if(gb->Seen(gCodeLetters[DRIVES])) - gFeedRate = gb->GetFValue()*distanceScale*0.016666667; // G Code feedrates are in mm/minute; we need mm/sec + if (gb->Seen(gCodeLetters[DRIVES])) + gFeedRate = gb->GetFValue() * distanceScale * 0.016666667; // G Code feedrates are in mm/minute; we need mm/sec - moveBuffer[DRIVES] = gFeedRate; // We always set it, as Move may have modified the last one. + moveBuffer[DRIVES] = gFeedRate; // We always set it, as Move may have modified the last one. } - // This function is called for a G Code that makes a move. // If the Move class can't receive the move (i.e. things have to wait) // this returns false, otherwise true. bool GCodes::SetUpMove(GCodeBuffer *gb) { - // Last one gone yet? - - if(moveAvailable) - return false; - - // Load the last position; If Move can't accept more, return false - - if(!reprap.GetMove()->GetCurrentState(moveBuffer)) - return false; - - checkEndStops = false; - if(gb->Seen('S')) - { - if(gb->GetIValue() == 1) - checkEndStops = true; - } + // Last one gone yet? - LoadMoveBufferFromGCode(gb, false, !checkEndStops); + if (moveAvailable) + return false; - moveAvailable = true; - return true; + // Load the last position; If Move can't accept more, return false + + if (!reprap.GetMove()->GetCurrentState(moveBuffer)) + return false; + + checkEndStops = noEndstopCheck; + if (gb->Seen('S')) + { + if (gb->GetIValue() == 1) + checkEndStops = checkAtEndstop; + } + + LoadMoveBufferFromGCode(gb, false, checkEndStops == noEndstopCheck); + + moveAvailable = true; + return true; } // The Move class calls this function to find what to do next. -bool GCodes::ReadMove(float m[], bool& ce) +bool GCodes::ReadMove(float m[], EndstopMode& ce) { - if(!moveAvailable) - return false; - for(int8_t i = 0; i <= DRIVES; i++) // 1 more for feedrate - m[i] = moveBuffer[i]; - ce = checkEndStops; - moveAvailable = false; - checkEndStops = false; - return true; + if (!moveAvailable) + return false; + for (int8_t i = 0; i <= DRIVES; i++) // 1 more for feedrate + m[i] = moveBuffer[i]; + ce = checkEndStops; + moveAvailable = false; + checkEndStops = noEndstopCheck; + return true; } - bool GCodes::DoFileCannedCycles(const char* fileName) { // Have we started the file? - if(!doingCannedCycleFile) + if (!doingCannedCycleFile) { // No - if(!Push()) + if (!Push()) return false; fileBeingPrinted = platform->GetFileStore(platform->GetSysDir(), fileName, false); - if(fileBeingPrinted == NULL) + if (fileBeingPrinted == NULL) { platform->Message(HOST_MESSAGE, "Canned cycle GCode file not found - "); platform->Message(HOST_MESSAGE, fileName); platform->Message(HOST_MESSAGE, "\n"); - if(!Pop()) + if (!Pop()) platform->Message(HOST_MESSAGE, "Cannot pop the stack.\n"); return true; } @@ -437,11 +438,11 @@ bool GCodes::DoFileCannedCycles(const char* fileName) // Have we finished the file? - if(fileBeingPrinted == NULL) + if (fileBeingPrinted == NULL) { // Yes - if(!Pop()) + if (!Pop()) return false; doingCannedCycleFile = false; cannedCycleGCode->Init(); @@ -450,10 +451,10 @@ bool GCodes::DoFileCannedCycles(const char* fileName) // No - Do more of the file - if(!cannedCycleGCode->Finished()) + if (!cannedCycleGCode->Finished()) { cannedCycleGCode->SetFinished(ActOnGcode(cannedCycleGCode)); - return false; + return false; } doFilePrint(cannedCycleGCode); @@ -463,16 +464,16 @@ bool GCodes::DoFileCannedCycles(const char* fileName) bool GCodes::FileCannedCyclesReturn() { - if(!doingCannedCycleFile) + if (!doingCannedCycleFile) return true; - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) return false; doingCannedCycleFile = false; cannedCycleGCode->Init(); - if(fileBeingPrinted != NULL) + if (fileBeingPrinted != NULL) fileBeingPrinted->Close(); fileBeingPrinted = NULL; @@ -484,23 +485,24 @@ bool GCodes::FileCannedCyclesReturn() // be ignored. Recall that moveToDo[DRIVES] should contain the feedrate // you want (if action[DRIVES] is true). -bool GCodes::DoCannedCycleMove(bool ce) +bool GCodes::DoCannedCycleMove(EndstopMode ce) { // Is the move already running? - if(cannedCycleMoveQueued) + if (cannedCycleMoveQueued) { // Yes. - if(!Pop()) // Wait for the move to finish then restore the state + if (!Pop()) // Wait for the move to finish then restore the state return false; cannedCycleMoveQueued = false; return true; - } else + } + else { // No. - if(!Push()) // Wait for the RepRap to finish whatever it was doing, save it's state, and load moveBuffer[] with the current position. + if (!Push()) // Wait for the RepRap to finish whatever it was doing, save it's state, and load moveBuffer[] with the current position. return false; - for(int8_t drive = 0; drive <= DRIVES; drive++) + for (int8_t drive = 0; drive <= DRIVES; drive++) { - if(activeDrive[drive]) + if (activeDrive[drive]) moveBuffer[drive] = moveToDo[drive]; } checkEndStops = ce; @@ -514,7 +516,7 @@ bool GCodes::DoCannedCycleMove(bool ce) bool GCodes::SetPositions(GCodeBuffer *gb) { - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) return false; LoadMoveBufferFromGCode(gb, true, false); @@ -533,17 +535,18 @@ bool GCodes::SetPositions(GCodeBuffer *gb) bool GCodes::OffsetAxes(GCodeBuffer* gb) { - if(!offSetSet) + if (!offSetSet) { - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) - return false; - for(int8_t drive = 0; drive <= DRIVES; drive++) + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) + return false; + for (int8_t drive = 0; drive <= DRIVES; drive++) { - if(drive < AXES || drive == DRIVES) + if (drive < AXES || drive == DRIVES) { record[drive] = moveBuffer[drive]; moveToDo[drive] = moveBuffer[drive]; - } else + } + else { record[drive] = 0.0; moveToDo[drive] = 0.0; @@ -551,16 +554,16 @@ bool GCodes::OffsetAxes(GCodeBuffer* gb) activeDrive[drive] = false; } - for(int8_t axis = 0; axis < AXES; axis++) + for (int8_t axis = 0; axis < AXES; axis++) { - if(gb->Seen(gCodeLetters[axis])) + if (gb->Seen(gCodeLetters[axis])) { moveToDo[axis] += gb->GetFValue(); activeDrive[axis] = true; } } - if(gb->Seen(gCodeLetters[DRIVES])) // Has the user specified a feedrate? + if (gb->Seen(gCodeLetters[DRIVES])) // Has the user specified a feedrate? { moveToDo[DRIVES] = gb->GetFValue(); activeDrive[DRIVES] = true; @@ -569,13 +572,12 @@ bool GCodes::OffsetAxes(GCodeBuffer* gb) offSetSet = true; } - - if(DoCannedCycleMove(false)) + if (DoCannedCycleMove(noEndstopCheck)) { //LoadMoveBufferFromArray(record); - for(int drive = 0; drive <= DRIVES; drive++) + for (int drive = 0; drive <= DRIVES; drive++) moveBuffer[drive] = record[drive]; - reprap.GetMove()->SetLiveCoordinates(record); // This doesn't transform record + reprap.GetMove()->SetLiveCoordinates(record); // This doesn't transform record reprap.GetMove()->SetPositions(record); // This does offSetSet = false; return true; @@ -592,9 +594,9 @@ bool GCodes::OffsetAxes(GCodeBuffer* gb) bool GCodes::DoHome(char* reply, bool& error) //pre(reply.upb == STRING_LENGTH) { - if(homeX && homeY && homeZ) + if (homeX && homeY && homeZ) { - if(DoFileCannedCycles(HOME_ALL_G)) + if (DoFileCannedCycles(HOME_ALL_G)) { homeAxisMoveCount = 0; homeX = false; @@ -606,9 +608,9 @@ bool GCodes::DoHome(char* reply, bool& error) return false; } - if(homeX) + if (homeX) { - if(DoFileCannedCycles(HOME_X_G)) + if (DoFileCannedCycles(HOME_X_G)) { homeAxisMoveCount = 0; homeX = false; @@ -618,10 +620,9 @@ bool GCodes::DoHome(char* reply, bool& error) return false; } - - if(homeY) + if (homeY) { - if(DoFileCannedCycles(HOME_Y_G)) + if (DoFileCannedCycles(HOME_Y_G)) { homeAxisMoveCount = 0; homeY = false; @@ -631,18 +632,17 @@ bool GCodes::DoHome(char* reply, bool& error) return false; } - - if(homeZ) + if (homeZ) { - if (!(axisIsHomed[X_AXIS] && axisIsHomed[Y_AXIS])) + if (platform->MustHomeXYBeforeZ() && (!axisIsHomed[X_AXIS] || !axisIsHomed[Y_AXIS])) { - // We can only home Z if X and Y have already been homed. Possibly this should only be if we are using an IR probe. + // We can only home Z if X and Y have already been homed strncpy(reply, "Must home X and Y before homing Z", STRING_LENGTH); error = true; homeZ = false; return true; } - if(DoFileCannedCycles(HOME_Z_G)) + if (DoFileCannedCycles(HOME_Z_G)) { homeAxisMoveCount = 0; homeZ = false; @@ -654,7 +654,7 @@ bool GCodes::DoHome(char* reply, bool& error) // Should never get here - checkEndStops = false; + checkEndStops = noEndstopCheck; moveAvailable = false; homeAxisMoveCount = 0; @@ -667,23 +667,25 @@ bool GCodes::DoHome(char* reply, bool& error) bool GCodes::DoSingleZProbeAtPoint() { - float x, y, z; + reprap.GetMove()->SetIdentityTransform(); // It doesn't matter if these are called repeatedly - reprap.GetMove()->SetIdentityTransform(); // It doesn't matter if these are called repeatedly - - for(int8_t drive = 0; drive <= DRIVES; drive++) - activeDrive[drive] = false; - - switch(cannedCycleMoveCount) + for (int8_t drive = 0; drive <= DRIVES; drive++) { - case 0: // This only does anything on the first move; on all the others Z is already there + activeDrive[drive] = false; + } + + switch (cannedCycleMoveCount) + { + case 0: // This only does anything on the first move; on all the others Z is already there moveToDo[Z_AXIS] = Z_DIVE; activeDrive[Z_AXIS] = true; moveToDo[DRIVES] = platform->HomeFeedRate(Z_AXIS); activeDrive[DRIVES] = true; - reprap.GetMove()->SetZProbing(false); - if(DoCannedCycleMove(false)) + if (DoCannedCycleMove(noEndstopCheck)) + { cannedCycleMoveCount++; + reprap.GetMove()->SetZProbing(true); // we only want to call this once + } return false; case 1: @@ -694,31 +696,52 @@ bool GCodes::DoSingleZProbeAtPoint() moveToDo[DRIVES] = platform->HomeFeedRate(X_AXIS); activeDrive[DRIVES] = true; reprap.GetMove()->SetZProbing(false); - if(DoCannedCycleMove(false)) + if (DoCannedCycleMove(noEndstopCheck)) + { cannedCycleMoveCount++; + } return false; case 2: - moveToDo[Z_AXIS] = -2.0*platform->AxisLength(Z_AXIS); + moveToDo[Z_AXIS] = -2.0 * platform->AxisLength(Z_AXIS); activeDrive[Z_AXIS] = true; moveToDo[DRIVES] = platform->HomeFeedRate(Z_AXIS); activeDrive[DRIVES] = true; reprap.GetMove()->SetZProbing(true); - if(DoCannedCycleMove(true)) + if (DoCannedCycleMove(checkApproachingEndstop)) { cannedCycleMoveCount++; - axisIsHomed[Z_AXIS] = true; // we now home the Z-axis in Move.cpp it is wasn't already } return false; case 3: + { + float liveCoordinates[DRIVES + 1]; + reprap.GetMove()->LiveCoordinates(liveCoordinates); + moveToDo[Z_AXIS] = liveCoordinates[Z_AXIS] - 1.0; // move down at most another 1mm + } + activeDrive[Z_AXIS] = true; + moveToDo[DRIVES] = platform->HomeFeedRate(Z_AXIS) * 0.2; + activeDrive[DRIVES] = true; + reprap.GetMove()->SetZProbing(true); + if (DoCannedCycleMove(checkAtEndstop)) + { + cannedCycleMoveCount++; + axisIsHomed[Z_AXIS] = true; // we now home the Z-axis in Move.cpp it is wasn't already + platform->SetZProbing(false); + } + return false; + + case 4: moveToDo[Z_AXIS] = Z_DIVE; activeDrive[Z_AXIS] = true; moveToDo[DRIVES] = platform->HomeFeedRate(Z_AXIS); activeDrive[DRIVES] = true; reprap.GetMove()->SetZProbing(false); - if(DoCannedCycleMove(false)) + if (DoCannedCycleMove(noEndstopCheck)) + { cannedCycleMoveCount++; + } return false; default: @@ -728,29 +751,55 @@ bool GCodes::DoSingleZProbeAtPoint() } } - // This simply moves down till the Z probe/switch is triggered. bool GCodes::DoSingleZProbe() { - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) + for (int8_t drive = 0; drive <= DRIVES; drive++) + { + activeDrive[drive] = false; + } + + switch (cannedCycleMoveCount) + { + case 0: + platform->SetZProbing(true); // we only want to call this once + ++cannedCycleMoveCount; return false; - for(int8_t drive = 0; drive <= DRIVES; drive++) - activeDrive[drive] = false; + case 1: + moveToDo[Z_AXIS] = -1.1 * platform->AxisLength(Z_AXIS); + activeDrive[Z_AXIS] = true; + moveToDo[DRIVES] = platform->HomeFeedRate(Z_AXIS); + activeDrive[DRIVES] = true; + if (DoCannedCycleMove(checkApproachingEndstop)) + { + cannedCycleMoveCount++; + } + return false; - moveToDo[Z_AXIS] = -1.1*platform->AxisLength(Z_AXIS); - activeDrive[Z_AXIS] = true; - moveToDo[DRIVES] = platform->HomeFeedRate(Z_AXIS); - activeDrive[DRIVES] = true; - if(DoCannedCycleMove(true)) - { + case 2: + { + float liveCoordinates[DRIVES + 1]; + reprap.GetMove()->LiveCoordinates(liveCoordinates); + moveToDo[Z_AXIS] = liveCoordinates[Z_AXIS] - 1.0; // move down at most another 1mm + } + activeDrive[Z_AXIS] = true; + moveToDo[DRIVES] = platform->HomeFeedRate(Z_AXIS) * 0.2; + activeDrive[DRIVES] = true; + if (DoCannedCycleMove(checkAtEndstop)) + { + cannedCycleMoveCount++; + probeCount = 0; + axisIsHomed[Z_AXIS] = true; // we have homed the Z axis + platform->SetZProbing(false); + } + return false; + + default: cannedCycleMoveCount = 0; - probeCount = 0; - axisIsHomed[Z_AXIS] = true; // we have homed the Z axis return true; } - return false; } // This sets wherever we are as the probe point P (probePointIndex) @@ -763,24 +812,24 @@ bool GCodes::DoSingleZProbe() bool GCodes::SetSingleZProbeAtAPosition(GCodeBuffer *gb) { - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) return false; - if(!gb->Seen('P')) + if (!gb->Seen('P')) return DoSingleZProbe(); int probePointIndex = gb->GetIValue(); float x, y, z; - if(gb->Seen(gCodeLetters[X_AXIS])) + if (gb->Seen(gCodeLetters[X_AXIS])) x = gb->GetFValue(); else x = moveBuffer[X_AXIS]; - if(gb->Seen(gCodeLetters[Y_AXIS])) + if (gb->Seen(gCodeLetters[Y_AXIS])) y = gb->GetFValue(); else y = moveBuffer[Y_AXIS]; - if(gb->Seen(gCodeLetters[Z_AXIS])) + if (gb->Seen(gCodeLetters[Z_AXIS])) z = gb->GetFValue(); else z = moveBuffer[Z_AXIS]; @@ -789,24 +838,25 @@ bool GCodes::SetSingleZProbeAtAPosition(GCodeBuffer *gb) reprap.GetMove()->SetXBedProbePoint(probeCount, x); reprap.GetMove()->SetYBedProbePoint(probeCount, y); - if(z > SILLY_Z_VALUE) + if (z > SILLY_Z_VALUE) { reprap.GetMove()->SetZBedProbePoint(probeCount, z); reprap.GetMove()->SetZProbing(false); // Not really needed, but let's be safe probeCount = 0; - if(gb->Seen('S')) + if (gb->Seen('S')) { zProbesSet = true; reprap.GetMove()->SetProbedBedEquation(); } return true; - } else + } + else { - if(DoSingleZProbeAtPoint()) + if (DoSingleZProbeAtPoint()) { probeCount = 0; reprap.GetMove()->SetZProbing(false); - if(gb->Seen('S')) + if (gb->Seen('S')) { zProbesSet = true; reprap.GetMove()->SetProbedBedEquation(); @@ -824,15 +874,15 @@ bool GCodes::SetSingleZProbeAtAPosition(GCodeBuffer *gb) bool GCodes::DoMultipleZProbe() { - if(reprap.GetMove()->NumberOfXYProbePoints() < 3) + if (reprap.GetMove()->NumberOfXYProbePoints() < 3) { platform->Message(HOST_MESSAGE, "Bed probing: there needs to be 3 or more points set.\n"); return true; } - if(DoSingleZProbeAtPoint()) + if (DoSingleZProbeAtPoint()) probeCount++; - if(probeCount >= reprap.GetMove()->NumberOfXYProbePoints()) + if (probeCount >= reprap.GetMove()->NumberOfXYProbePoints()) { probeCount = 0; zProbesSet = true; @@ -857,22 +907,49 @@ bool GCodes::GetProbeCoordinates(int count, float& x, float& y, float& z) bool GCodes::SetPrintZProbe(GCodeBuffer* gb, char* reply) { - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) return false; - if(gb->Seen(gCodeLetters[Z_AXIS])) + + if (gb->Seen(gCodeLetters[Z_AXIS])) { - platform->SetZProbeStopHeight(gb->GetFValue()); - if(gb->Seen('P')) + ZProbeParameters params; + platform->GetZProbeParameters(params); + params.height = gb->GetFValue(); + if (gb->Seen('P')) { - platform->SetZProbe(gb->GetIValue()); + params.adcValue = gb->GetIValue(); } - } else if (platform->GetZProbeType() == 2) - { - snprintf(reply, STRING_LENGTH, "%d (%d)", platform->ZProbe(), platform->ZProbeOnVal()); + if (gb->Seen('T')) + { + params.calibTemperature = gb->GetFValue(); + } + else if (!PrintingAFile()) + { + // Use the current bed temperature as the calibration temperature if no value was provided + params.calibTemperature = platform->GetTemperature(0); + } + if (gb->Seen('C')) + { + params.temperatureCoefficient = gb->GetFValue(); + } + platform->SetZProbeParameters(params); } else { - snprintf(reply, STRING_LENGTH, "%d", platform->ZProbe()); + int v0 = platform->ZProbe(); + int v1, v2; + switch(platform->GetZProbeSecondaryValues(v1, v2)) + { + case 1: + snprintf(reply, STRING_LENGTH, "%d (%d)", v0, v1); + break; + case 2: + snprintf(reply, STRING_LENGTH, "%d (%d, %d)", v0, v1, v2); + break; + default: + snprintf(reply, STRING_LENGTH, "%d", v0); + break; + } } return true; } @@ -885,17 +962,18 @@ bool GCodes::SetPrintZProbe(GCodeBuffer* gb, char* reply) char* GCodes::GetCurrentCoordinates() { - float liveCoordinates[DRIVES+1]; + float liveCoordinates[DRIVES + 1]; reprap.GetMove()->LiveCoordinates(liveCoordinates); - snprintf(scratchString, STRING_LENGTH, "X:%f Y:%f Z:%f E:%f", liveCoordinates[X_AXIS], liveCoordinates[Y_AXIS], liveCoordinates[Z_AXIS], liveCoordinates[AXES]); + snprintf(scratchString, STRING_LENGTH, "X:%f Y:%f Z:%f E:%f", liveCoordinates[X_AXIS], liveCoordinates[Y_AXIS], + liveCoordinates[Z_AXIS], liveCoordinates[AXES]); return scratchString; } void GCodes::OpenFileToWrite(const char* directory, const char* fileName, GCodeBuffer *gb) { fileBeingWritten = platform->GetFileStore(directory, fileName, true); - if(fileBeingWritten == NULL) + if (fileBeingWritten == NULL) { platform->Message(HOST_MESSAGE, "Can't open GCode file for writing.\n"); } @@ -911,7 +989,7 @@ void GCodes::WriteHTMLToFile(char b, GCodeBuffer *gb) char reply[1]; reply[0] = 0; - if(fileBeingWritten == NULL) + if (fileBeingWritten == NULL) { platform->Message(HOST_MESSAGE, "Attempt to write to a null file.\n"); return; @@ -919,21 +997,22 @@ void GCodes::WriteHTMLToFile(char b, GCodeBuffer *gb) fileBeingWritten->Write(b); - if(b == eofString[eofStringCounter]) + if (b == eofString[eofStringCounter]) { eofStringCounter++; - if(eofStringCounter >= eofStringLength) + if (eofStringCounter >= eofStringLength) { fileBeingWritten->Close(); fileBeingWritten = NULL; gb->SetWritingFileDirectory(NULL); char* r = reply; - if(platform->Emulating() == marlin) + if (platform->Emulating() == marlin) r = "Done saving file."; - HandleReply(false, gb == serialGCode , r, 'M', 560, false); + HandleReply(false, gb == serialGCode, r, 'M', 560, false); return; } - } else + } + else eofStringCounter = 0; } @@ -942,7 +1021,7 @@ void GCodes::WriteGCodeToFile(GCodeBuffer *gb) char reply[1]; reply[0] = 0; - if(fileBeingWritten == NULL) + if (fileBeingWritten == NULL) { platform->Message(HOST_MESSAGE, "Attempt to write to a null file.\n"); return; @@ -950,31 +1029,31 @@ void GCodes::WriteGCodeToFile(GCodeBuffer *gb) // End of file? - if(gb->Seen('M')) + if (gb->Seen('M')) { - if(gb->GetIValue() == 29) + if (gb->GetIValue() == 29) { fileBeingWritten->Close(); fileBeingWritten = NULL; gb->SetWritingFileDirectory(NULL); char* r = reply; - if(platform->Emulating() == marlin) + if (platform->Emulating() == marlin) r = "Done saving file."; - HandleReply(false, gb == serialGCode , r, 'M', 29, false); + HandleReply(false, gb == serialGCode, r, 'M', 29, false); return; } } // Resend request? - if(gb->Seen('G')) + if (gb->Seen('G')) { - if(gb->GetIValue() == 998) + if (gb->GetIValue() == 998) { - if(gb->Seen('P')) + if (gb->Seen('P')) { snprintf(scratchString, STRING_LENGTH, "%s", gb->GetIValue()); - HandleReply(false, gb == serialGCode , scratchString, 'G', 998, true); + HandleReply(false, gb == serialGCode, scratchString, 'G', 998, true); return; } } @@ -982,40 +1061,40 @@ void GCodes::WriteGCodeToFile(GCodeBuffer *gb) fileBeingWritten->Write(gb->Buffer()); fileBeingWritten->Write('\n'); - HandleReply(false, gb == serialGCode , reply, 'G', 1, false); + HandleReply(false, gb == serialGCode, reply, 'G', 1, false); } // Set up a file to print, but don't print it yet. void GCodes::QueueFileToPrint(const char* fileName) { - fileToPrint = platform->GetFileStore(platform->GetGCodeDir(), fileName, false); - if(fileToPrint == NULL) - { - webserver->HandleReply("GCode file not found", true); - platform->Message(HOST_MESSAGE, "GCode file not found\n"); - } + fileToPrint = platform->GetFileStore(platform->GetGCodeDir(), fileName, false); + if (fileToPrint == NULL) + { + webserver->HandleReply("GCode file not found", true); + platform->Message(HOST_MESSAGE, "GCode file not found\n"); + } } void GCodes::DeleteFile(const char* fileName) { - if(!platform->GetMassStorage()->Delete(platform->GetGCodeDir(), fileName)) - { - platform->Message(HOST_MESSAGE, "Unsuccessful attempt to delete: "); - platform->Message(HOST_MESSAGE, fileName); - platform->Message(HOST_MESSAGE, "\n"); - webserver->HandleReply("Failed to delete file", true); - } + if (!platform->GetMassStorage()->Delete(platform->GetGCodeDir(), fileName)) + { + platform->Message(HOST_MESSAGE, "Unsuccessful attempt to delete: "); + platform->Message(HOST_MESSAGE, fileName); + platform->Message(HOST_MESSAGE, "\n"); + webserver->HandleReply("Failed to delete file", true); + } } // Send the config file to USB in response to an M503 command. // This is not used for processing M503 requests received via the webserver. bool GCodes::SendConfigToLine() { - if(configFile == NULL) + if (configFile == NULL) { configFile = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false); - if(configFile == NULL) + if (configFile == NULL) { platform->Message(HOST_MESSAGE, "Configuration file not found\n"); return true; @@ -1024,10 +1103,10 @@ bool GCodes::SendConfigToLine() } char b; - while(configFile->Read(b)) + while (configFile->Read(b)) { platform->GetLine()->Write(b); - if(b == '\n') + if (b == '\n') return false; } @@ -1037,40 +1116,39 @@ bool GCodes::SendConfigToLine() return true; } - // Function to handle dwell delays. Return true for // dwell finished, false otherwise. bool GCodes::DoDwell(GCodeBuffer *gb) { - if(!gb->Seen('P')) - return true; // No time given - throw it away - - float dwell = 0.001*(float)gb->GetLValue(); // P values are in milliseconds; we need seconds - - // Wait for all the queued moves to stop - - if(!reprap.GetMove()->AllMovesAreFinished()) - return false; - - // Are we already in the dwell? - - if(dwellWaiting) - { - if(platform->Time() - dwellTime >= 0.0) - { - dwellWaiting = false; - reprap.GetMove()->ResumeMoving(); - return true; - } - return false; - } - - // New dwell - set it up - - dwellWaiting = true; - dwellTime = platform->Time() + dwell; - return false; + if (!gb->Seen('P')) + return true; // No time given - throw it away + + float dwell = 0.001 * (float) gb->GetLValue(); // P values are in milliseconds; we need seconds + + // Wait for all the queued moves to stop + + if (!reprap.GetMove()->AllMovesAreFinished()) + return false; + + // Are we already in the dwell? + + if (dwellWaiting) + { + if (platform->Time() - dwellTime >= 0.0) + { + dwellWaiting = false; + reprap.GetMove()->ResumeMoving(); + return true; + } + return false; + } + + // New dwell - set it up + + dwellWaiting = true; + dwellTime = platform->Time() + dwell; + return false; } // Set distance offsets and working and standby temperatures for @@ -1078,27 +1156,27 @@ bool GCodes::DoDwell(GCodeBuffer *gb) bool GCodes::SetOffsets(GCodeBuffer *gb) { - int8_t head; - if(gb->Seen('P')) - { - head = gb->GetIValue() + 1; // 0 is the Bed - if(gb->Seen('R')) - reprap.GetHeat()->SetStandbyTemperature(head, gb->GetFValue()); - - if(gb->Seen('S')) - reprap.GetHeat()->SetActiveTemperature(head, gb->GetFValue()); - // FIXME - do X, Y and Z - } - return true; + int8_t head; + if (gb->Seen('P')) + { + head = gb->GetIValue() + 1; // 0 is the Bed + if (gb->Seen('R')) + reprap.GetHeat()->SetStandbyTemperature(head, gb->GetFValue()); + + if (gb->Seen('S')) + reprap.GetHeat()->SetActiveTemperature(head, gb->GetFValue()); + // FIXME - do X, Y and Z + } + return true; } // Does what it says. bool GCodes::DisableDrives() { - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) - return false; - for(int8_t drive = 0; drive < DRIVES; drive++) + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) + return false; + for (int8_t drive = 0; drive < DRIVES; drive++) platform->Disable(drive); return true; } @@ -1107,9 +1185,9 @@ bool GCodes::DisableDrives() bool GCodes::StandbyHeaters() { - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) return false; - for(int8_t heater = 0; heater < HEATERS; heater++) + for (int8_t heater = 0; heater < HEATERS; heater++) reprap.GetHeat()->Standby(heater); selectedHead = -1; return true; @@ -1122,13 +1200,13 @@ void GCodes::SetEthernetAddress(GCodeBuffer *gb, int mCode) uint8_t sp = 0; uint8_t spp = 0; uint8_t ipp = 0; - while(ipString[sp]) + while (ipString[sp]) { - if(ipString[sp] == '.') + if (ipString[sp] == '.') { eth[ipp] = atoi(&ipString[spp]); ipp++; - if(ipp > 3) + if (ipp > 3) { platform->Message(HOST_MESSAGE, "Dud IP address: "); platform->Message(HOST_MESSAGE, gb->Buffer()); @@ -1137,13 +1215,14 @@ void GCodes::SetEthernetAddress(GCodeBuffer *gb, int mCode) } sp++; spp = sp; - }else + } + else sp++; } eth[ipp] = atoi(&ipString[spp]); - if(ipp == 3) + if (ipp == 3) { - switch(mCode) + switch (mCode) { case 552: platform->SetIPAddress(eth); @@ -1158,7 +1237,8 @@ void GCodes::SetEthernetAddress(GCodeBuffer *gb, int mCode) default: platform->Message(HOST_MESSAGE, "Setting ether parameter - dud code."); } - } else + } + else { platform->Message(HOST_MESSAGE, "Dud IP address: "); platform->Message(HOST_MESSAGE, gb->Buffer()); @@ -1168,28 +1248,28 @@ void GCodes::SetEthernetAddress(GCodeBuffer *gb, int mCode) void GCodes::HandleReply(bool error, bool fromLine, const char* reply, char gMOrT, int code, bool resend) { - if (gMOrT != 'M' || code != 111) // web server reply for M111 is handled before we get here + if (gMOrT != 'M' || code != 111) // web server reply for M111 is handled before we get here { webserver->HandleReply(reply, error); } Compatibility c = platform->Emulating(); - if(!fromLine) + if (!fromLine) c = me; const char* response = "ok"; - if(resend) + if (resend) response = "rs "; const char* s = 0; - switch(c) + switch (c) { case me: case reprapFirmware: - if(!reply[0]) + if (!reply[0]) return; - if(error) + if (error) platform->GetLine()->Write("Error: "); platform->GetLine()->Write(reply); platform->GetLine()->Write("\n"); @@ -1197,7 +1277,7 @@ void GCodes::HandleReply(bool error, bool fromLine, const char* reply, char gMOr case marlin: - if(gMOrT == 'M' && code == 20) + if (gMOrT == 'M' && code == 20) { platform->GetLine()->Write("Begin file list\n"); platform->GetLine()->Write(reply); @@ -1207,7 +1287,7 @@ void GCodes::HandleReply(bool error, bool fromLine, const char* reply, char gMOr return; } - if(gMOrT == 'M' && code == 28) + if (gMOrT == 'M' && code == 28) { platform->GetLine()->Write(response); platform->GetLine()->Write("\n"); @@ -1216,7 +1296,7 @@ void GCodes::HandleReply(bool error, bool fromLine, const char* reply, char gMOr return; } - if( (gMOrT == 'M' && code == 105) || (gMOrT == 'G' && code == 998)) + if ((gMOrT == 'M' && code == 105) || (gMOrT == 'G' && code == 998)) { platform->GetLine()->Write(response); platform->GetLine()->Write(" "); @@ -1225,7 +1305,7 @@ void GCodes::HandleReply(bool error, bool fromLine, const char* reply, char gMOr return; } - if(reply[0]) + if (reply[0]) { platform->GetLine()->Write(reply); platform->GetLine()->Write("\n"); @@ -1247,7 +1327,7 @@ void GCodes::HandleReply(bool error, bool fromLine, const char* reply, char gMOr s = "unknown"; } - if(s != 0) + if (s != 0) { snprintf(scratchString, STRING_LENGTH, "Emulation of %s is not yet supported.\n", s); platform->Message(HOST_MESSAGE, scratchString); @@ -1260,538 +1340,543 @@ void GCodes::HandleReply(bool error, bool fromLine, const char* reply, char gMOr bool GCodes::ActOnGcode(GCodeBuffer *gb) { - int code; - bool result = true; - bool error = false; - bool resend = false; - bool seen; - char reply[STRING_LENGTH]; + int code; + bool result = true; + bool error = false; + bool resend = false; + bool seen; + char reply[STRING_LENGTH]; - reply[0] = 0; + reply[0] = 0; - if(gb->Seen('G')) - { - code = gb->GetIValue(); - switch(code) - { - case 0: // There are no rapid moves... - case 1: // Ordinary move - result = SetUpMove(gb); - break; - - case 4: // Dwell - result = DoDwell(gb); - break; - - case 10: // Set offsets - result = SetOffsets(gb); - break; - - case 20: // Inches (which century are we living in, here?) - distanceScale = INCH_TO_MM; - break; - - case 21: // mm - distanceScale = 1.0; - break; - - case 28: // Home - if(NoHome()) - { - homeAxisMoveCount = 0; - homeX = gb->Seen(gCodeLetters[X_AXIS]); - homeY = gb->Seen(gCodeLetters[Y_AXIS]); - homeZ = gb->Seen(gCodeLetters[Z_AXIS]); - if(NoHome()) - { - homeX = true; - homeY = true; - homeZ = true; - } - } - result = DoHome(reply, error); - break; - - case 30: // Z probe/manually set at a position and set that as point P - result = SetSingleZProbeAtAPosition(gb); - break; - - case 31: // Return the probe value, or set probe variables - result = SetPrintZProbe(gb, reply); - break; - - case 32: // Probe Z at multiple positions and generate the bed transform - if (!(axisIsHomed[X_AXIS] && axisIsHomed[Y_AXIS])) + if (gb->Seen('G')) + { + code = gb->GetIValue(); + switch (code) { - // We can only do a Z probe if X and Y have already been homed - strncpy(reply, "Must home X and Y before bed probing", STRING_LENGTH); + case 0: // There are no rapid moves... + case 1: // Ordinary move + result = SetUpMove(gb); + break; + + case 4: // Dwell + result = DoDwell(gb); + break; + + case 10: // Set offsets + result = SetOffsets(gb); + break; + + case 20: // Inches (which century are we living in, here?) + distanceScale = INCH_TO_MM; + break; + + case 21: // mm + distanceScale = 1.0; + break; + + case 28: // Home + if (NoHome()) + { + homeAxisMoveCount = 0; + homeX = gb->Seen(gCodeLetters[X_AXIS]); + homeY = gb->Seen(gCodeLetters[Y_AXIS]); + homeZ = gb->Seen(gCodeLetters[Z_AXIS]); + if (NoHome()) + { + homeX = true; + homeY = true; + homeZ = true; + } + } + result = DoHome(reply, error); + break; + + case 30: // Z probe/manually set at a position and set that as point P + result = SetSingleZProbeAtAPosition(gb); + break; + + case 31: // Return the probe value, or set probe variables + result = SetPrintZProbe(gb, reply); + break; + + case 32: // Probe Z at multiple positions and generate the bed transform + if (!(axisIsHomed[X_AXIS] && axisIsHomed[Y_AXIS])) + { + // We can only do bed levelling if X and Y have already been homed + strncpy(reply, "Must home X and Y before bed probing", STRING_LENGTH); + error = true; + result = true; + } + else + { + result = DoMultipleZProbe(); + } + break; + + case 90: // Absolute coordinates + drivesRelative = false; + axesRelative = false; + break; + + case 91: // Relative coordinates + drivesRelative = true; // Non-axis movements (i.e. extruders) + axesRelative = true; // Axis movements (i.e. X, Y and Z) + break; + + case 92: // Set position + result = SetPositions(gb); + break; + + default: error = true; - result = true; + snprintf(reply, STRING_LENGTH, "invalid G Code: %s", gb->Buffer()); } - else + if (result) + HandleReply(error, gb == serialGCode, reply, 'G', code, resend); + return result; + } + + if (gb->Seen('M')) + { + code = gb->GetIValue(); + switch (code) { - result = DoMultipleZProbe(); - } - break; + case 0: // Stop + case 1: // Sleep + if (fileBeingPrinted != NULL) + { + fileToPrint = fileBeingPrinted; + fileBeingPrinted = NULL; + } + if (!DisableDrives()) + return false; + if (!StandbyHeaters()) + return false; // Should never happen + break; - case 90: // Absolute coordinates - drivesRelative = false; - axesRelative = false; - break; - - case 91: // Relative coordinates - drivesRelative = true; // Non-axis movements (i.e. extruders) - axesRelative = true; // Axis movements (i.e. X, Y and Z) - break; - - case 92: // Set position - result = SetPositions(gb); - break; - - default: - error = true; - snprintf(reply, STRING_LENGTH, "invalid G Code: %s", gb->Buffer()); - } - if(result) - HandleReply(error, gb == serialGCode, reply, 'G', code, resend); - return result; - } - - if(gb->Seen('M')) - { - code = gb->GetIValue(); - switch(code) - { - case 0: // Stop - case 1: // Sleep - if(fileBeingPrinted != NULL) - { - fileToPrint = fileBeingPrinted; - fileBeingPrinted = NULL; - } - if(!DisableDrives()) - return false; - if(!StandbyHeaters()) - return false; // Should never happen - break; - - case 18: // Motors off - result = DisableDrives(); - break; - - case 20: // Deprecated... - if(platform->Emulating() == me || platform->Emulating() == reprapFirmware) - snprintf(reply, STRING_LENGTH, "GCode files:\n%s", platform->GetMassStorage()->FileList(platform->GetGCodeDir(), gb == serialGCode)); - else - snprintf(reply, STRING_LENGTH, "%s", platform->GetMassStorage()->FileList(platform->GetGCodeDir(), gb == serialGCode)); - break; + case 18: // Motors off + result = DisableDrives(); + break; - case 21: // Initialise SD - ignore - break; + case 20: // Deprecated... + if (platform->Emulating() == me || platform->Emulating() == reprapFirmware) + snprintf(reply, STRING_LENGTH, "GCode files:\n%s", + platform->GetMassStorage()->FileList(platform->GetGCodeDir(), gb == serialGCode)); + else + snprintf(reply, STRING_LENGTH, "%s", + platform->GetMassStorage()->FileList(platform->GetGCodeDir(), gb == serialGCode)); + break; - case 23: // Set file to print - QueueFileToPrint(gb->GetUnprecedentedString()); - if(platform->Emulating() == marlin) - snprintf(reply, STRING_LENGTH, "%s", "File opened\nFile selected\n"); - break; - - case 24: // Print/resume-printing the selected file - if(fileBeingPrinted != NULL) - break; - fileBeingPrinted = fileToPrint; - fileToPrint = NULL; - break; - - case 25: // Pause the print - fileToPrint = fileBeingPrinted; - fileBeingPrinted = NULL; - break; + case 21: // Initialise SD - ignore + break; - case 27: // Report print status - Deprecated - if(this->PrintingAFile()) - strncpy(reply, "SD printing.", STRING_LENGTH); - else - strncpy(reply, "Not SD printing.", STRING_LENGTH); - break; + case 23: // Set file to print + QueueFileToPrint(gb->GetUnprecedentedString()); + if (platform->Emulating() == marlin) + snprintf(reply, STRING_LENGTH, "%s", "File opened\nFile selected\n"); + break; - case 28: // Write to file - { + case 24: // Print/resume-printing the selected file + if (fileBeingPrinted != NULL) + break; + fileBeingPrinted = fileToPrint; + fileToPrint = NULL; + break; + + case 25: // Pause the print + fileToPrint = fileBeingPrinted; + fileBeingPrinted = NULL; + break; + + case 27: // Report print status - Deprecated + if (this->PrintingAFile()) + strncpy(reply, "SD printing.", STRING_LENGTH); + else + strncpy(reply, "Not SD printing.", STRING_LENGTH); + break; + + case 28: // Write to file + { const char* str = gb->GetUnprecedentedString(); OpenFileToWrite(platform->GetGCodeDir(), str, gb); snprintf(reply, STRING_LENGTH, "Writing to file: %s", str); - } - break; + } + break; - case 29: // End of file being written; should be intercepted before getting here - platform->Message(HOST_MESSAGE, "GCode end-of-file being interpreted.\n"); - break; + case 29: // End of file being written; should be intercepted before getting here + platform->Message(HOST_MESSAGE, "GCode end-of-file being interpreted.\n"); + break; - case 30: // Delete file - DeleteFile(gb->GetUnprecedentedString()); - break; + case 30: // Delete file + DeleteFile(gb->GetUnprecedentedString()); + break; - case 82: - for(int8_t extruder = AXES; extruder < DRIVES; extruder++) - lastPos[extruder - AXES] = 0.0; - drivesRelative = false; - break; + case 82: + for (int8_t extruder = AXES; extruder < DRIVES; extruder++) + lastPos[extruder - AXES] = 0.0; + drivesRelative = false; + break; - case 83: - for(int8_t extruder = AXES; extruder < DRIVES; extruder++) - lastPos[extruder - AXES] = 0.0; - drivesRelative = true; + case 83: + for (int8_t extruder = AXES; extruder < DRIVES; extruder++) + lastPos[extruder - AXES] = 0.0; + drivesRelative = true; - break; + break; - case 84: // Motors off - deprecated, use M18 - result = DisableDrives(); - break; + case 84: // Motors off - deprecated, use M18 + result = DisableDrives(); + break; - case 85: // Set inactive time - break; + case 85: // Set inactive time + break; - case 92: // Set/report steps/mm for some axes - seen = false; - for(int8_t drive = 0; drive < DRIVES; drive++) - if(gb->Seen(gCodeLetters[drive])) - { - platform->SetDriveStepsPerUnit(drive, gb->GetFValue()); - seen = true; - } - reprap.GetMove()->SetStepHypotenuse(); - if(!seen) - snprintf(reply, STRING_LENGTH, "Steps/mm: X: %d, Y: %d, Z: %d, E: %d", - (int)platform->DriveStepsPerUnit(X_AXIS), (int)platform->DriveStepsPerUnit(Y_AXIS), - (int)platform->DriveStepsPerUnit(Z_AXIS), (int)platform->DriveStepsPerUnit(AXES)); // FIXME - needs to do multiple extruders - break; + case 92: // Set/report steps/mm for some axes + seen = false; + for (int8_t drive = 0; drive < DRIVES; drive++) + if (gb->Seen(gCodeLetters[drive])) + { + platform->SetDriveStepsPerUnit(drive, gb->GetFValue()); + seen = true; + } + reprap.GetMove()->SetStepHypotenuse(); + if (!seen) + snprintf(reply, STRING_LENGTH, "Steps/mm: X: %d, Y: %d, Z: %d, E: %d", + (int) platform->DriveStepsPerUnit(X_AXIS), (int) platform->DriveStepsPerUnit(Y_AXIS), + (int) platform->DriveStepsPerUnit(Z_AXIS), (int) platform->DriveStepsPerUnit(AXES)); // FIXME - needs to do multiple extruders + break; + case 98: + if (gb->Seen('P')) + result = DoFileCannedCycles(gb->GetString()); + break; - case 98: - if(gb->Seen('P')) - result = DoFileCannedCycles(gb->GetString()); - break; + case 99: + result = FileCannedCyclesReturn(); + break; - case 99: - result = FileCannedCyclesReturn(); - break; + case 104: // Deprecated + if (gb->Seen('S')) + { + reprap.GetHeat()->SetActiveTemperature(1, gb->GetFValue()); // 0 is the bed + reprap.GetHeat()->Activate(1); + } + break; - case 104: // Deprecated - if(gb->Seen('S')) - { - reprap.GetHeat()->SetActiveTemperature(1, gb->GetFValue()); // 0 is the bed - reprap.GetHeat()->Activate(1); - } - break; + case 105: // Deprecated... + strncpy(reply, "T:", STRING_LENGTH); + for (int8_t heater = HEATERS - 1; heater > 0; heater--) + { + strncat(reply, ftoa(0, reprap.GetHeat()->GetTemperature(heater), 1), STRING_LENGTH); + strncat(reply, " ", STRING_LENGTH); + } + strncat(reply, "B:", STRING_LENGTH); + strncat(reply, ftoa(0, reprap.GetHeat()->GetTemperature(0), 1), STRING_LENGTH); + break; - case 105: // Deprecated... - strncpy(reply, "T:", STRING_LENGTH); - for(int8_t heater = HEATERS - 1; heater > 0; heater--) - { - strncat(reply, ftoa(0, reprap.GetHeat()->GetTemperature(heater), 1), STRING_LENGTH); - strncat(reply, " ", STRING_LENGTH); - } - strncat(reply, "B:", STRING_LENGTH); - strncat(reply, ftoa(0, reprap.GetHeat()->GetTemperature(0), 1), STRING_LENGTH); - break; - - case 106: // Fan on or off - if(gb->Seen('S')) - platform->CoolingFan(gb->GetFValue()); - break; - - case 107: // Fan off - deprecated - platform->CoolingFan(0.0); - break; - - case 110: // Set line numbers - line numbers are dealt with in the GCodeBuffer class - break; + case 106: // Fan on or off + if (gb->Seen('S')) + platform->CoolingFan(gb->GetFValue()); + break; - case 111: // Debug level - if(gb->Seen('S')) - reprap.SetDebug(gb->GetIValue()); - break; + case 107: // Fan off - deprecated + platform->CoolingFan(0.0); + break; - case 112: // Emergency stop - acted upon in Webserver - break; + case 110: // Set line numbers - line numbers are dealt with in the GCodeBuffer class + break; - case 114: // Deprecated - { - const char* str = GetCurrentCoordinates(); - if(str != 0) + case 111: // Debug level + if (gb->Seen('S')) + reprap.SetDebug(gb->GetIValue()); + break; + + case 112: // Emergency stop - acted upon in Webserver + break; + + case 114: // Deprecated + { + const char* str = GetCurrentCoordinates(); + if (str != 0) { strncpy(reply, str, STRING_LENGTH); - } else - result = false; - } - break; + } + else + result = false; + } + break; - case 115: // Print firmware version - snprintf(reply, STRING_LENGTH, "FIRMWARE_NAME:%s FIRMWARE_VERSION:%s ELECTRONICS:%s DATE:%s", NAME, VERSION, ELECTRONICS, DATE); - break; + case 115: // Print firmware version + snprintf(reply, STRING_LENGTH, "FIRMWARE_NAME:%s FIRMWARE_VERSION:%s ELECTRONICS:%s DATE:%s", NAME, VERSION, + ELECTRONICS, DATE); + break; - case 109: // Deprecated - if(gb->Seen('S')) - { - reprap.GetHeat()->SetActiveTemperature(1, gb->GetFValue()); // 0 is the bed - reprap.GetHeat()->Activate(1); - } - /* no break */ - case 116: // Wait for everything, especially set temperatures - if(!AllMovesAreFinishedAndMoveBufferIsLoaded()) - return false; - result = reprap.GetHeat()->AllHeatersAtSetTemperatures(); - break; + case 109: // Deprecated + if (gb->Seen('S')) + { + reprap.GetHeat()->SetActiveTemperature(1, gb->GetFValue()); // 0 is the bed + reprap.GetHeat()->Activate(1); + } + /* no break */ + case 116: // Wait for everything, especially set temperatures + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) + return false; + result = reprap.GetHeat()->AllHeatersAtSetTemperatures(); + break; - case 120: - result = Push(); - break; + case 120: + result = Push(); + break; - case 121: - result = Pop(); - break; - - case 122: - reprap.Diagnostics(); - break; - - case 126: // Valve open - platform->Message(HOST_MESSAGE, "M126 - valves not yet implemented\n"); - break; - - case 127: // Valve closed - platform->Message(HOST_MESSAGE, "M127 - valves not yet implemented\n"); - break; - - case 135: // Set PID sample interval - break; + case 121: + result = Pop(); + break; - case 140: // Set bed temperature - if(gb->Seen('S')) - { - reprap.GetHeat()->SetActiveTemperature(0, gb->GetFValue()); - reprap.GetHeat()->Activate(0); - } - break; - - case 141: // Chamber temperature - platform->Message(HOST_MESSAGE, "M141 - heated chamber not yet implemented\n"); - break; + case 122: + reprap.Diagnostics(); + break; - case 201: // Set axis accelerations - for(int8_t drive = 0; drive < DRIVES; drive++) - { - float value; - if(gb->Seen(gCodeLetters[drive])) - { - value = gb->GetFValue(); - }else{ - value = -1; - } - platform->SetAcceleration(drive, value); - } - break; + case 126: // Valve open + platform->Message(HOST_MESSAGE, "M126 - valves not yet implemented\n"); + break; - case 203: // Set maximum feedrates - for(int8_t drive = 0; drive < DRIVES; drive++) - { - if(gb->Seen(gCodeLetters[drive])) - { - float value = gb->GetFValue()*distanceScale*0.016666667; // G Code feedrates are in mm/minute; we need mm/sec; - platform->SetMaxFeedrate(drive, value); - } - } - break; + case 127: // Valve closed + platform->Message(HOST_MESSAGE, "M127 - valves not yet implemented\n"); + break; - case 205: //M205 advanced settings: minimum travel speed S=while printing T=travel only, B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk - break; + case 135: // Set PID sample interval + break; - case 206: // Offset axes - result = OffsetAxes(gb); - break; + case 140: // Set bed temperature + if (gb->Seen('S')) + { + reprap.GetHeat()->SetActiveTemperature(0, gb->GetFValue()); + reprap.GetHeat()->Activate(0); + } + break; - case 208: // Set maximum axis lengths - for(int8_t axis = 0; axis < AXES; axis++) - { - if(gb->Seen(gCodeLetters[axis])) - { - float value = gb->GetFValue()*distanceScale; - platform->SetAxisLength(axis, value); - } - } - break; + case 141: // Chamber temperature + platform->Message(HOST_MESSAGE, "M141 - heated chamber not yet implemented\n"); + break; - case 210: // Set homing feedrates - for(int8_t axis = 0; axis < AXES; axis++) - { - if(gb->Seen(gCodeLetters[axis])) - { - float value = gb->GetFValue()*distanceScale*0.016666667; - platform->SetHomeFeedRate(axis, value); - } - } - break; + case 201: // Set axis accelerations + for (int8_t drive = 0; drive < DRIVES; drive++) + { + float value; + if (gb->Seen(gCodeLetters[drive])) + { + value = gb->GetFValue(); + } + else + { + value = -1; + } + platform->SetAcceleration(drive, value); + } + break; - case 301: // Set hot end PID values - { - float pValue, iValue, dValue; - bool seen = false; - if (gb->Seen('P')) - { - pValue = gb->GetFValue(); - seen = true; - } - else - { - pValue = platform->PidKp(1); - } - if (gb->Seen('I')) - { - iValue = gb->GetFValue(); - seen = true; - } - else - { - iValue = platform->PidKi(1); - } - if (gb->Seen('D')) - { - dValue = gb->GetFValue(); - seen = true; - } - else - { - dValue = platform->PidKd(1); - } + case 203: // Set maximum feedrates + for (int8_t drive = 0; drive < DRIVES; drive++) + { + if (gb->Seen(gCodeLetters[drive])) + { + float value = gb->GetFValue() * distanceScale * 0.016666667; // G Code feedrates are in mm/minute; we need mm/sec; + platform->SetMaxFeedrate(drive, value); + } + } + break; - if (seen) - { - platform->SetPidValues(1, pValue, iValue, dValue); - } - else - { - snprintf(reply, STRING_LENGTH, "P:%f I:%f D: %f\n", pValue, iValue, dValue); - } - } - break; + case 205: //M205 advanced settings: minimum travel speed S=while printing T=travel only, B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk + break; - case 302: // Allow cold extrudes - break; + case 206: // Offset axes + result = OffsetAxes(gb); + break; - case 304: // Set thermistor parameters - break; + case 208: // Set maximum axis lengths + for (int8_t axis = 0; axis < AXES; axis++) + { + if (gb->Seen(gCodeLetters[axis])) + { + float value = gb->GetFValue() * distanceScale; + platform->SetAxisLength(axis, value); + } + } + break; - case 503: // list variable settings - result = SendConfigToLine(); - break; + case 210: // Set homing feedrates + for (int8_t axis = 0; axis < AXES; axis++) + { + if (gb->Seen(gCodeLetters[axis])) + { + float value = gb->GetFValue() * distanceScale * 0.016666667; + platform->SetHomeFeedRate(axis, value); + } + } + break; - case 550: // Set machine name - if(gb->Seen('P')) - reprap.GetWebserver()->SetName(gb->GetString()); - break; + case 301: // Set hot end PID values + { + float pValue, iValue, dValue; + bool seen = false; + if (gb->Seen('P')) + { + pValue = gb->GetFValue(); + seen = true; + } + else + { + pValue = platform->PidKp(1); + } + if (gb->Seen('I')) + { + iValue = gb->GetFValue(); + seen = true; + } + else + { + iValue = platform->PidKi(1); + } + if (gb->Seen('D')) + { + dValue = gb->GetFValue(); + seen = true; + } + else + { + dValue = platform->PidKd(1); + } - case 551: // Set password - if(gb->Seen('P')) - reprap.GetWebserver()->SetPassword(gb->GetString()); - break; + if (seen) + { + platform->SetPidValues(1, pValue, iValue, dValue); + } + else + { + snprintf(reply, STRING_LENGTH, "P:%f I:%f D: %f\n", pValue, iValue, dValue); + } + } + break; - case 552: // Set/Get IP address - if(gb->Seen('P')) - SetEthernetAddress(gb, code); - else - { - const byte *ip = platform->IPAddress(); - snprintf(reply, STRING_LENGTH, "IP address: %d.%d.%d.%d\n ", ip[0], ip[1], ip[2], ip[3]); - } - break; + case 302: // Allow cold extrudes + break; - case 553: // Set/Get netmask - if(gb->Seen('P')) - SetEthernetAddress(gb, code); - else - { - const byte *nm = platform->NetMask(); - snprintf(reply, STRING_LENGTH, "Net mask: %d.%d.%d.%d\n ", nm[0], nm[1], nm[2], nm[3]); - } - break; + case 304: // Set thermistor parameters + break; - case 554: // Set/Get gateway - if(gb->Seen('P')) - SetEthernetAddress(gb, code); - else - { - const byte *gw = platform->GateWay(); - snprintf(reply, STRING_LENGTH, "Gateway: %d.%d.%d.%d\n ", gw[0], gw[1], gw[2], gw[3]); - } - break; + case 503: // list variable settings + result = SendConfigToLine(); + break; - case 555: // Set firmware type to emulate - if(gb->Seen('P')) - platform->SetEmulating((Compatibility)gb->GetIValue()); - break; + case 550: // Set machine name + if (gb->Seen('P')) + reprap.GetWebserver()->SetName(gb->GetString()); + break; - case 556: // Axis compensation - if(gb->Seen('S')) - { - float value = gb->GetFValue(); - for(int8_t axis = 0; axis < AXES; axis++) - if(gb->Seen(gCodeLetters[axis])) - reprap.GetMove()->SetAxisCompensation(axis, gb->GetFValue()/value); - } - break; + case 551: // Set password + if (gb->Seen('P')) + reprap.GetWebserver()->SetPassword(gb->GetString()); + break; - case 557: // Set Z probe point coordinates - if(gb->Seen('P')) - { - int iValue = gb->GetIValue(); - if(gb->Seen(gCodeLetters[X_AXIS])) - reprap.GetMove()->SetXBedProbePoint(iValue, gb->GetFValue()); - if(gb->Seen(gCodeLetters[Y_AXIS])) - reprap.GetMove()->SetYBedProbePoint(iValue, gb->GetFValue()); - } - break; + case 552: // Set/Get IP address + if (gb->Seen('P')) + SetEthernetAddress(gb, code); + else + { + const byte *ip = platform->IPAddress(); + snprintf(reply, STRING_LENGTH, "IP address: %d.%d.%d.%d\n ", ip[0], ip[1], ip[2], ip[3]); + } + break; - case 558: // Set Z probe type - if(gb->Seen('P')) - { - platform->SetZProbeType(gb->GetIValue()); - } - else - { - snprintf(reply, STRING_LENGTH, "%d", platform->GetZProbeType()); - } - break; + case 553: // Set/Get netmask + if (gb->Seen('P')) + SetEthernetAddress(gb, code); + else + { + const byte *nm = platform->NetMask(); + snprintf(reply, STRING_LENGTH, "Net mask: %d.%d.%d.%d\n ", nm[0], nm[1], nm[2], nm[3]); + } + break; - case 559: // Upload config.g - { - const char* str; - if(gb->Seen('P')) + case 554: // Set/Get gateway + if (gb->Seen('P')) + SetEthernetAddress(gb, code); + else + { + const byte *gw = platform->GateWay(); + snprintf(reply, STRING_LENGTH, "Gateway: %d.%d.%d.%d\n ", gw[0], gw[1], gw[2], gw[3]); + } + break; + + case 555: // Set firmware type to emulate + if (gb->Seen('P')) + platform->SetEmulating((Compatibility) gb->GetIValue()); + break; + + case 556: // Axis compensation + if (gb->Seen('S')) + { + float value = gb->GetFValue(); + for (int8_t axis = 0; axis < AXES; axis++) + if (gb->Seen(gCodeLetters[axis])) + reprap.GetMove()->SetAxisCompensation(axis, gb->GetFValue() / value); + } + break; + + case 557: // Set Z probe point coordinates + if (gb->Seen('P')) + { + int iValue = gb->GetIValue(); + if (gb->Seen(gCodeLetters[X_AXIS])) + reprap.GetMove()->SetXBedProbePoint(iValue, gb->GetFValue()); + if (gb->Seen(gCodeLetters[Y_AXIS])) + reprap.GetMove()->SetYBedProbePoint(iValue, gb->GetFValue()); + } + break; + + case 558: // Set Z probe type + if (gb->Seen('P')) + { + platform->SetZProbeType(gb->GetIValue()); + } + else + { + snprintf(reply, STRING_LENGTH, "%d", platform->GetZProbeType()); + } + break; + + case 559: // Upload config.g + { + const char* str; + if (gb->Seen('P')) str = gb->GetString(); else str = platform->GetConfigFile(); OpenFileToWrite(platform->GetSysDir(), str, gb); snprintf(reply, STRING_LENGTH, "Writing to file: %s", str); - } - break; + } + break; - case 560: // Upload reprap.htm - { - const char* str = INDEX_PAGE; - OpenFileToWrite(platform->GetWebDir(), str, gb); - snprintf(reply, STRING_LENGTH, "Writing to file: %s", str); - } - break; + case 560: // Upload reprap.htm + { + const char* str = INDEX_PAGE; + OpenFileToWrite(platform->GetWebDir(), str, gb); + snprintf(reply, STRING_LENGTH, "Writing to file: %s", str); + } + break; - case 561: - reprap.GetMove()->SetIdentityTransform(); - break; + case 561: + reprap.GetMove()->SetIdentityTransform(); + break; - case 562: // Reset temperature fault - use with great caution - if(gb->Seen('P')) - { - int iValue = gb->GetIValue(); - reprap.GetHeat()->ResetFault(iValue); - } - break; + case 562: // Reset temperature fault - use with great caution + if (gb->Seen('P')) + { + int iValue = gb->GetIValue(); + reprap.GetHeat()->ResetFault(iValue); + } + break; // case 876: // TEMPORARY - this will go away... // if(gb->Seen('P')) @@ -1804,111 +1889,107 @@ bool GCodes::ActOnGcode(GCodeBuffer *gb) // } // break; - case 900: - result = DoFileCannedCycles("homex.g"); - break; + case 900: + result = DoFileCannedCycles("homex.g"); + break; - case 901: - result = DoFileCannedCycles("homey.g"); - break; + case 901: + result = DoFileCannedCycles("homey.g"); + break; + case 906: // Set Motor currents + for (uint8_t i = 0; i < DRIVES; i++) + { + if (gb->Seen(gCodeLetters[i])) + { + float value = gb->GetFValue(); // mA + platform->SetMotorCurrent(i, value); + } + } + break; + case 998: + if (gb->Seen('P')) + { + snprintf(reply, STRING_LENGTH, "%s", gb->GetIValue()); + resend = true; + } + break; - case 906: // Set Motor currents - for(uint8_t i = 0; i < DRIVES; i++) - { - if(gb->Seen(gCodeLetters[i])) - { - float value = gb->GetFValue(); // mA - platform->SetMotorCurrent(i, value); - } - } - break; + default: + error = true; + snprintf(reply, STRING_LENGTH, "invalid M Code: %s", gb->Buffer()); + } + if (result) + HandleReply(error, gb == serialGCode, reply, 'M', code, resend); + return result; + } - case 998: - if(gb->Seen('P')) - { - snprintf(reply, STRING_LENGTH, "%s", gb->GetIValue()); - resend = true; - } - break; - - default: - error = true; - snprintf(reply, STRING_LENGTH, "invalid M Code: %s", gb->Buffer()); - } - if(result) - HandleReply(error, gb == serialGCode, reply, 'M', code, resend); - return result; - } - - if(gb->Seen('T')) - { - code = gb->GetIValue(); - if(code == selectedHead) - { - if(result) - HandleReply(error, gb == serialGCode, reply, 'T', code, resend); - return result; - } + if (gb->Seen('T')) + { + code = gb->GetIValue(); + if (code == selectedHead) + { + if (result) + HandleReply(error, gb == serialGCode, reply, 'T', code, resend); + return result; + } - error = true; - for(int8_t i = AXES; i < DRIVES; i++) - { - if(selectedHead == i - AXES) - reprap.GetHeat()->Standby(selectedHead + 1); // + 1 because 0 is the Bed - } - for(int8_t i = AXES; i < DRIVES; i++) - { - if(code == i - AXES) - { - selectedHead = code; - reprap.GetHeat()->Activate(selectedHead + 1); // 0 is the Bed - error = false; - } - } + error = true; + for (int8_t i = AXES; i < DRIVES; i++) + { + if (selectedHead == i - AXES) + reprap.GetHeat()->Standby(selectedHead + 1); // + 1 because 0 is the Bed + } + for (int8_t i = AXES; i < DRIVES; i++) + { + if (code == i - AXES) + { + selectedHead = code; + reprap.GetHeat()->Activate(selectedHead + 1); // 0 is the Bed + error = false; + } + } - if(error) - snprintf(reply, STRING_LENGTH, "Invalid T Code: %s", gb->Buffer()); + if (error) + snprintf(reply, STRING_LENGTH, "Invalid T Code: %s", gb->Buffer()); - if(result) - HandleReply(error, gb == serialGCode, reply, 'T', code, resend); - return result; - } - - // An empty buffer jumps to here and gets discarded + if (result) + HandleReply(error, gb == serialGCode, reply, 'T', code, resend); + return result; + } - if(result) - HandleReply(error, gb == serialGCode, reply, 'X', code, resend); + // An empty buffer jumps to here and gets discarded - return result; + if (result) + HandleReply(error, gb == serialGCode, reply, 'X', code, resend); + + return result; } - - //************************************************************************************* // This class stores a single G Code and provides functions to allow it to be parsed GCodeBuffer::GCodeBuffer(Platform* p, const char* id) -{ - platform = p; - identity = id; - writingFileDirectory = NULL; // Has to be done here as Init() is called every line. +{ + platform = p; + identity = id; + writingFileDirectory = NULL; // Has to be done here as Init() is called every line. } void GCodeBuffer::Init() { - gcodePointer = 0; - readPointer = -1; - inComment = false; + gcodePointer = 0; + readPointer = -1; + inComment = false; } int GCodeBuffer::CheckSum() { int cs = 0; - for(int i = 0; gcodeBuffer[i] != '*' && gcodeBuffer[i] != 0; i++) - cs = cs ^ gcodeBuffer[i]; + for (int i = 0; gcodeBuffer[i] != '*' && gcodeBuffer[i] != 0; i++) + cs = cs ^ gcodeBuffer[i]; cs &= 0xff; // Defensive programming... return cs; } @@ -1918,112 +1999,113 @@ int GCodeBuffer::CheckSum() bool GCodeBuffer::Put(char c) { - bool result = false; - gcodeBuffer[gcodePointer] = c; - - if(c == ';') - inComment = true; - - if(c == '\n' || !c) - { - gcodeBuffer[gcodePointer] = 0; - Init(); - if(reprap.Debug() && gcodeBuffer[0] && !writingFileDirectory) // Don't bother with blank/comment lines - { - platform->Message(HOST_MESSAGE, identity); - platform->Message(HOST_MESSAGE, gcodeBuffer); - platform->Message(HOST_MESSAGE, "\n"); - } + bool result = false; + gcodeBuffer[gcodePointer] = c; - // Deal with line numbers and checksums + if (c == ';') + inComment = true; - if(Seen('*')) - { - int csSent = GetIValue(); - int csHere = CheckSum(); - Seen('N'); - if(csSent != csHere) - { - snprintf(gcodeBuffer, GCODE_LENGTH, "M998 P%d", GetIValue()); - Init(); - result = true; - return result; - } + if (c == '\n' || !c) + { + gcodeBuffer[gcodePointer] = 0; + Init(); + if (reprap.Debug() && gcodeBuffer[0] && !writingFileDirectory) // Don't bother with blank/comment lines + { + platform->Message(HOST_MESSAGE, identity); + platform->Message(HOST_MESSAGE, gcodeBuffer); + platform->Message(HOST_MESSAGE, "\n"); + } - // Strip out the line number and checksum + // Deal with line numbers and checksums - while(gcodeBuffer[gcodePointer] != ' ' && gcodeBuffer[gcodePointer]) - gcodePointer++; + if (Seen('*')) + { + int csSent = GetIValue(); + int csHere = CheckSum(); + Seen('N'); + if (csSent != csHere) + { + snprintf(gcodeBuffer, GCODE_LENGTH, "M998 P%d", GetIValue()); + Init(); + result = true; + return result; + } - // Anything there? + // Strip out the line number and checksum - if(!gcodeBuffer[gcodePointer]) - { - // No... - gcodeBuffer[0] = 0; - Init(); - result = true; - return result; - } + while (gcodeBuffer[gcodePointer] != ' ' && gcodeBuffer[gcodePointer]) + gcodePointer++; - // Yes... + // Anything there? - gcodePointer++; - int gp2 = 0; - while(gcodeBuffer[gcodePointer] != '*' && gcodeBuffer[gcodePointer]) - { - gcodeBuffer[gp2] = gcodeBuffer[gcodePointer++]; - gp2++; - } - gcodeBuffer[gp2] = 0; - Init(); - } + if (!gcodeBuffer[gcodePointer]) + { + // No... + gcodeBuffer[0] = 0; + Init(); + result = true; + return result; + } - result = true; - } else - { - if(!inComment || writingFileDirectory) - gcodePointer++; - } - - if(gcodePointer >= GCODE_LENGTH) - { - platform->Message(HOST_MESSAGE, "G Code buffer length overflow.\n"); - gcodePointer = 0; - gcodeBuffer[0] = 0; - } - - return result; -} + // Yes... + + gcodePointer++; + int gp2 = 0; + while (gcodeBuffer[gcodePointer] != '*' && gcodeBuffer[gcodePointer]) + { + gcodeBuffer[gp2] = gcodeBuffer[gcodePointer++]; + gp2++; + } + gcodeBuffer[gp2] = 0; + Init(); + } + + result = true; + } + else + { + if (!inComment || writingFileDirectory) + gcodePointer++; + } + + if (gcodePointer >= GCODE_LENGTH) + { + platform->Message(HOST_MESSAGE, "G Code buffer length overflow.\n"); + gcodePointer = 0; + gcodeBuffer[0] = 0; + } + + return result; +} // Is 'c' in the G Code string? // Leave the pointer there for a subsequent read. bool GCodeBuffer::Seen(char c) { - readPointer = 0; - while(gcodeBuffer[readPointer]) - { - if(gcodeBuffer[readPointer] == c) - return true; - readPointer++; - } - readPointer = -1; - return false; + readPointer = 0; + while (gcodeBuffer[readPointer]) + { + if (gcodeBuffer[readPointer] == c) + return true; + readPointer++; + } + readPointer = -1; + return false; } // Get a float after a G Code letter found by a call to Seen() float GCodeBuffer::GetFValue() { - if(readPointer < 0) - { - platform->Message(HOST_MESSAGE, "GCodes: Attempt to read a GCode float before a search.\n"); - return 0.0; - } - float result = (float)strtod(&gcodeBuffer[readPointer + 1], 0); - readPointer = -1; - return result; + if (readPointer < 0) + { + platform->Message(HOST_MESSAGE, "GCodes: Attempt to read a GCode float before a search.\n"); + return 0.0; + } + float result = (float) strtod(&gcodeBuffer[readPointer + 1], 0); + readPointer = -1; + return result; } // Get a string after a G Code letter found by a call to Seen(). @@ -2032,12 +2114,12 @@ float GCodeBuffer::GetFValue() const char* GCodeBuffer::GetString() { - if(readPointer < 0) + if (readPointer < 0) { platform->Message(HOST_MESSAGE, "GCodes: Attempt to read a GCode string before a search.\n"); return ""; } - const char* result = &gcodeBuffer[readPointer+1]; + const char* result = &gcodeBuffer[readPointer + 1]; readPointer = -1; return result; } @@ -2055,36 +2137,32 @@ const char* GCodeBuffer::GetString() const char* GCodeBuffer::GetUnprecedentedString() { - readPointer = 0; - while(gcodeBuffer[readPointer] && gcodeBuffer[readPointer] != ' ') - readPointer++; + readPointer = 0; + while (gcodeBuffer[readPointer] && gcodeBuffer[readPointer] != ' ') + readPointer++; - if(!gcodeBuffer[readPointer]) - { - platform->Message(HOST_MESSAGE, "GCodes: String expected but not seen.\n"); - return gcodeBuffer; // Good idea? - } + if (!gcodeBuffer[readPointer]) + { + platform->Message(HOST_MESSAGE, "GCodes: String expected but not seen.\n"); + return gcodeBuffer; // Good idea? + } - char* result = &gcodeBuffer[readPointer+1]; - readPointer = -1; - return result; + char* result = &gcodeBuffer[readPointer + 1]; + readPointer = -1; + return result; } - // Get an long after a G Code letter long GCodeBuffer::GetLValue() { - if(readPointer < 0) - { - platform->Message(HOST_MESSAGE, "GCodes: Attempt to read a GCode int before a search.\n"); - return 0; - } - long result = strtol(&gcodeBuffer[readPointer + 1], 0, 0); - readPointer = -1; - return result; + if (readPointer < 0) + { + platform->Message(HOST_MESSAGE, "GCodes: Attempt to read a GCode int before a search.\n"); + return 0; + } + long result = strtol(&gcodeBuffer[readPointer + 1], 0, 0); + readPointer = -1; + return result; } - - - diff --git a/GCodes.h b/GCodes.h index 30507ee..3419b9f 100644 --- a/GCodes.h +++ b/GCodes.h @@ -27,6 +27,11 @@ Licence: GPL #define GCODE_LETTERS { 'X', 'Y', 'Z', 'E', 'F' } // The drives and feedrate in a GCode +// Enumeration to define the mode in which we check endstops + +enum EndstopMode { noEndstopCheck, checkApproachingEndstop, checkAtEndstop}; + + // Small class to hold an individual GCode and provide functions to allow it to be parsed class GCodeBuffer @@ -72,7 +77,7 @@ class GCodes void Init(); void Exit(); bool RunConfigurationGCodes(); - bool ReadMove(float* m, bool& ce); + bool ReadMove(float* m, EndstopMode& ce); void QueueFileToPrint(const char* fileName); void DeleteFile(const char* fileName); bool GetProbeCoordinates(int count, float& x, float& y, float& z); @@ -86,7 +91,7 @@ class GCodes void doFilePrint(GCodeBuffer* gb); bool AllMovesAreFinishedAndMoveBufferIsLoaded(); - bool DoCannedCycleMove(bool ce); + bool DoCannedCycleMove(EndstopMode ce); bool DoFileCannedCycles(const char* fileName); bool FileCannedCyclesReturn(); bool ActOnGcode(GCodeBuffer* gb); @@ -126,7 +131,7 @@ class GCodes GCodeBuffer* cannedCycleGCode; bool moveAvailable; float moveBuffer[DRIVES+1]; // Last is feed rate - bool checkEndStops; + EndstopMode checkEndStops; bool drivesRelative; // All except X, Y and Z bool axesRelative; // X, Y and Z bool drivesRelativeStack[STACK]; diff --git a/Move.cpp b/Move.cpp index af456a1..26ad6d1 100644 --- a/Move.cpp +++ b/Move.cpp @@ -94,7 +94,7 @@ void Move::Init() liveCoordinates[i] = 0.0; } - lastMove->Init(ep, platform->HomeFeedRate(Z_AXIS), platform->InstantDv(Z_AXIS), false, zMove); // Typically Z is the slowest Axis + lastMove->Init(ep, platform->HomeFeedRate(Z_AXIS), platform->InstantDv(Z_AXIS), noEndstopCheck, zMove); // Typically Z is the slowest Axis lastMove->Release(); liveCoordinates[DRIVES] = platform->HomeFeedRate(Z_AXIS); @@ -167,13 +167,11 @@ void Move::Spin() // If there's a G Code move available, add it to the look-ahead // ring for processing. - bool checkEndStopsOnNextMove; + EndstopMode checkEndStopsOnNextMove; if(gCodes->ReadMove(nextMove, checkEndStopsOnNextMove)) { Transform(nextMove); - currentFeedrate = nextMove[DRIVES]; // Might be G1 with just an F field - for(int8_t drive = 0; drive < DRIVES; drive++) nextMachineEndPoints[drive] = LookAhead::EndPointToMachine(drive, nextMove[drive]); @@ -183,12 +181,12 @@ void Move::Spin() if(movementType == noMove) { + currentFeedrate = nextMove[DRIVES]; // Might be G1 with just an F field platform->ClassReport("Move", longWait); return; } // Real move - record its feedrate with it, not here. - currentFeedrate = -1.0; // Promote minimum feedrates @@ -294,7 +292,7 @@ bool Move::GetCurrentState(float m[]) // for the bed's plane, which means that a move is MAINLY and XY move, or MAINLY a Z move. It // is the main type of move that is returned. -int8_t Move::GetMovementType(long p0[], long p1[]) +int8_t Move::GetMovementType(const long p0[], const long p1[]) const { int8_t result = noMove; long dxy = 0; @@ -568,7 +566,7 @@ void Move::Interrupt() } -bool Move::LookAheadRingAdd(long ep[], float feedRate, float vv, bool ce, int8_t mt) +bool Move::LookAheadRingAdd(const long ep[], float feedRate, float vv, EndstopMode ce, int8_t mt) { if(LookAheadRingFull()) return false; @@ -898,9 +896,9 @@ MovementProfile DDA::Init(LookAhead* lookAhead, float& u, float& v) distance = 0.0; // X+Y+Z float eDistance = 0.0; float d; - long* targetPosition = myLookAheadEntry->MachineEndPoints(); + const long* targetPosition = myLookAheadEntry->MachineEndPoints(); v = myLookAheadEntry->V(); - long* positionNow = myLookAheadEntry->Previous()->MachineEndPoints(); + const long* positionNow = myLookAheadEntry->Previous()->MachineEndPoints(); u = myLookAheadEntry->Previous()->V(); checkEndStops = myLookAheadEntry->CheckEndStops(); @@ -1004,10 +1002,17 @@ MovementProfile DDA::Init(LookAhead* lookAhead, float& u, float& v) // If velocity requested is (almost) zero, set it to instantDv - if(v < instantDv) // Set change here? + if(v < instantDv) { - v = instantDv; - result = change; + v = instantDv; + result = change; + } + + // u here may be zero if we recently hit an endstop, in which case we need to set to to instantDv + if (u < instantDv) + { + u = instantDv; + result = change; } if(myLookAheadEntry->FeedRate() < instantDv) @@ -1019,11 +1024,11 @@ MovementProfile DDA::Init(LookAhead* lookAhead, float& u, float& v) velocity = u; - // Sanity check +// Sanity check if(velocity <= 0.0) { - velocity = 1.0; + velocity = 1.0; // if(reprap.Debug()) // platform->Message(HOST_MESSAGE, "DDA.Init(): Zero or negative initial velocity!\n"); } @@ -1076,18 +1081,27 @@ void DDA::Step() // Hit anything? - if(checkEndStops) + if(checkEndStops != noEndstopCheck) { - EndStopHit esh = platform->Stopped(drive); - if(esh == lowHit) + switch(platform->Stopped(drive)) { + case lowHit: move->HitLowStop(drive, myLookAheadEntry, this); active = false; - } - if(esh == highHit) - { + break; + case highHit: move->HitHighStop(drive, myLookAheadEntry, this); active = false; + break; + case lowNear: + if (checkEndStops == checkApproachingEndstop) + { + move->NearLowStop(drive, myLookAheadEntry, this); + active = false; + } + break; + default: + break; } } } @@ -1095,7 +1109,7 @@ void DDA::Step() // May have hit a stop, so test active here - if(active) + if(active) { if(axesMoving) timeStep = move->stepDistances[axesMoving]/velocity; @@ -1140,7 +1154,7 @@ LookAhead::LookAhead(Move* m, Platform* p, LookAhead* n) next = n; } -void LookAhead::Init(long ep[], float f, float vv, bool ce, int8_t mt) +void LookAhead::Init(const long ep[], float f, float vv, EndstopMode ce, int8_t mt) { v = vv; movementType = mt; diff --git a/Move.h b/Move.h index f4126a6..2b7c3e3 100644 --- a/Move.h +++ b/Move.h @@ -70,22 +70,22 @@ public: protected: LookAhead(Move* m, Platform* p, LookAhead* n); - void Init(long ep[], float feedRate, float vv, bool ce, int8_t mt); + void Init(const long ep[], float feedRate, float vv, EndstopMode ce, int8_t mt); LookAhead* Next(); LookAhead* Previous(); - long* MachineEndPoints(); + const long* MachineEndPoints() const; float MachineToEndPoint(int8_t drive); static float MachineToEndPoint(int8_t drive, long coord); static long EndPointToMachine(int8_t drive, float coord); - int8_t GetMovementType(); - float FeedRate(); - float V(); + int8_t GetMovementType() const; + float FeedRate() const; + float V() const; void SetV(float vv); void SetFeedRate(float f); - int8_t Processed(); + int8_t Processed() const; void SetProcessed(MovementState ms); void SetDriveCoordinateAndZeroEndSpeed(float a, int8_t drive); - bool CheckEndStops(); + EndstopMode CheckEndStops() const; void Release(); private: @@ -97,7 +97,7 @@ private: long endPoint[DRIVES+1]; // Should never use the +1, but safety first int8_t movementType; float Cosine(); - bool checkEndStops; + EndstopMode checkEndStops; float cosine; float v; // The feedrate we can actually do float feedRate; // The requested feedrate @@ -118,9 +118,9 @@ protected: MovementProfile Init(LookAhead* lookAhead, float& u, float& v); void Start(bool noTest); void Step(); - bool Active(); + bool Active() const; DDA* Next(); - float InstantDv(); + float InstantDv() const; private: MovementProfile AccelerationCalculation(float& u, float& v, MovementProfile result); @@ -135,7 +135,7 @@ private: bool directions[DRIVES]; long totalSteps; long stepCount; - bool checkEndStops; + EndstopMode checkEndStops; float timeStep; float velocity; long stopAStep; @@ -147,8 +147,6 @@ private: }; - - class Move { public: @@ -165,23 +163,24 @@ class Move void ResumeMoving(); void DoLookAhead(); void HitLowStop(int8_t drive, LookAhead* la, DDA* hitDDA); + void NearLowStop(int8_t drive, LookAhead* la, DDA* hitDDA); void HitHighStop(int8_t drive, LookAhead* la, DDA* hitDDA); void SetPositions(float move[]); void SetLiveCoordinates(float coords[]); void SetXBedProbePoint(int index, float x); void SetYBedProbePoint(int index, float y); void SetZBedProbePoint(int index, float z); - float xBedProbePoint(int index); - float yBedProbePoint(int index); - float zBedProbePoint(int index); - int NumberOfProbePoints(); - int NumberOfXYProbePoints(); - bool AllProbeCoordinatesSet(int index); - bool XYProbeCoordinatesSet(int index); + float xBedProbePoint(int index) const; + float yBedProbePoint(int index) const; + float zBedProbePoint(int index) const; + int NumberOfProbePoints() const; + int NumberOfXYProbePoints() const; + bool AllProbeCoordinatesSet(int index) const; + bool XYProbeCoordinatesSet(int index) const; void SetZProbing(bool probing); void SetProbedBedEquation(); - float SecondDegreeTransformZ(float x, float y); - float GetLastProbedZ(); + float SecondDegreeTransformZ(float x, float y) const; + float GetLastProbedZ() const; void SetAxisCompensation(int8_t axis, float tangent); void SetIdentityTransform(); void Transform(float move[]); @@ -197,16 +196,16 @@ class Move bool DDARingAdd(LookAhead* lookAhead); DDA* DDARingGet(); - bool DDARingEmpty(); - bool NoLiveMovement(); - bool DDARingFull(); + bool DDARingEmpty() const; + bool NoLiveMovement() const; + bool DDARingFull() const; bool GetDDARingLock(); void ReleaseDDARingLock(); - bool LookAheadRingEmpty(); - bool LookAheadRingFull(); - bool LookAheadRingAdd(long ep[], float feedRate, float vv, bool ce, int8_t movementType); + bool LookAheadRingEmpty() const; + bool LookAheadRingFull() const; + bool LookAheadRingAdd(const long ep[], float feedRate, float vv, EndstopMode ce, int8_t movementType); LookAhead* LookAheadRingGet(); - int8_t GetMovementType(long sp[], long ep[]); + int8_t GetMovementType(const long sp[], const long ep[]) const; float liveCoordinates[DRIVES + 1]; @@ -263,7 +262,7 @@ inline void LookAhead::SetV(float vv) v = vv; } -inline float LookAhead::V() +inline float LookAhead::V() const { return v; } @@ -276,7 +275,7 @@ inline float LookAhead::MachineToEndPoint(int8_t drive) } -inline float LookAhead::FeedRate() +inline float LookAhead::FeedRate() const { return feedRate; } @@ -286,7 +285,7 @@ inline void LookAhead::SetFeedRate(float f) feedRate = f; } -inline int8_t LookAhead::Processed() +inline int8_t LookAhead::Processed() const { return processed; } @@ -304,7 +303,7 @@ inline void LookAhead::Release() processed = released; } -inline bool LookAhead::CheckEndStops() +inline EndstopMode LookAhead::CheckEndStops() const { return checkEndStops; } @@ -316,19 +315,19 @@ inline void LookAhead::SetDriveCoordinateAndZeroEndSpeed(float a, int8_t drive) v = 0.0; } -inline long* LookAhead::MachineEndPoints() +inline const long* LookAhead::MachineEndPoints() const { return endPoint; } -inline int8_t LookAhead::GetMovementType() +inline int8_t LookAhead::GetMovementType() const { return movementType; } //****************************************************************************************************** -inline bool DDA::Active() +inline bool DDA::Active() const { return active; } @@ -338,7 +337,7 @@ inline DDA* DDA::Next() return next; } -inline float DDA::InstantDv() +inline float DDA::InstantDv() const { return instantDv; } @@ -346,12 +345,12 @@ inline float DDA::InstantDv() //*************************************************************************************** -inline bool Move::DDARingEmpty() +inline bool Move::DDARingEmpty() const { return ddaRingGetPointer == ddaRingAddPointer; } -inline bool Move::NoLiveMovement() +inline bool Move::NoLiveMovement() const { if(dda != NULL) return false; @@ -360,19 +359,19 @@ inline bool Move::NoLiveMovement() // Leave a gap of 2 as the last Get result may still be being processed -inline bool Move::DDARingFull() +inline bool Move::DDARingFull() const { return ddaRingAddPointer->Next()->Next() == ddaRingGetPointer; } -inline bool Move::LookAheadRingEmpty() +inline bool Move::LookAheadRingEmpty() const { return lookAheadRingCount == 0; } // Leave a gap of 2 as the last Get result may still be being processed -inline bool Move::LookAheadRingFull() +inline bool Move::LookAheadRingFull() const { if(!(lookAheadRingAddPointer->Processed() & released)) return true; @@ -459,17 +458,17 @@ inline void Move::SetZBedProbePoint(int index, float z) probePointSet[index] |= zSet; } -inline float Move::xBedProbePoint(int index) +inline float Move::xBedProbePoint(int index) const { return xBedProbePoints[index]; } -inline float Move::yBedProbePoint(int index) +inline float Move::yBedProbePoint(int index) const { return yBedProbePoints[index]; } -inline float Move::zBedProbePoint(int index) +inline float Move::zBedProbePoint(int index) const { return zBedProbePoints[index]; } @@ -479,22 +478,22 @@ inline void Move::SetZProbing(bool probing) zProbing = probing; } -inline float Move::GetLastProbedZ() +inline float Move::GetLastProbedZ() const { return lastZHit; } -inline bool Move::AllProbeCoordinatesSet(int index) +inline bool Move::AllProbeCoordinatesSet(int index) const { return probePointSet[index] == (xSet | ySet | zSet); } -inline bool Move::XYProbeCoordinatesSet(int index) +inline bool Move::XYProbeCoordinatesSet(int index) const { return (probePointSet[index] & xSet) && (probePointSet[index] & ySet); } -inline int Move::NumberOfProbePoints() +inline int Move::NumberOfProbePoints() const { if(AllProbeCoordinatesSet(0) && AllProbeCoordinatesSet(1) && AllProbeCoordinatesSet(2)) { @@ -506,7 +505,7 @@ inline int Move::NumberOfProbePoints() return 0; } -inline int Move::NumberOfXYProbePoints() +inline int Move::NumberOfXYProbePoints() const { if(XYProbeCoordinatesSet(0) && XYProbeCoordinatesSet(1) && XYProbeCoordinatesSet(2)) { @@ -530,7 +529,7 @@ inline int Move::NumberOfXYProbePoints() * * The values of x and y are transformed to put them in the interval [0, 1]. */ -inline float Move::SecondDegreeTransformZ(float x, float y) +inline float Move::SecondDegreeTransformZ(float x, float y) const { x = (x - xBedProbePoints[0])*xRectangle; y = (y - yBedProbePoints[0])*yRectangle; @@ -564,8 +563,12 @@ inline void Move::HitLowStop(int8_t drive, LookAhead* la, DDA* hitDDA) } else { // Executing G30, so set the current Z height to the value at which the end stop is triggered - lastZHit = platform->ZProbeStopHeight(); - hitPoint = lastZHit; + // Transform it first so that the height is correct in user coordinates + float xyzPoint[3]; + LiveCoordinates(xyzPoint); + xyzPoint[Z_AXIS] = lastZHit = platform->ZProbeStopHeight(); + Transform(xyzPoint); + hitPoint = xyzPoint[Z_AXIS]; } } la->SetDriveCoordinateAndZeroEndSpeed(hitPoint, drive); @@ -576,6 +579,11 @@ inline void Move::HitHighStop(int8_t drive, LookAhead* la, DDA* hitDDA) la->SetDriveCoordinateAndZeroEndSpeed(platform->AxisLength(drive), drive); } +inline void Move::NearLowStop(int8_t drive, LookAhead* la, DDA* hitDDA) +{ + la->SetDriveCoordinateAndZeroEndSpeed(1.0, drive); // say we are at 1mm +} + inline float Move::ComputeCurrentCoordinate(int8_t drive, LookAhead* la, DDA* runningDDA) { float previous = la->Previous()->MachineToEndPoint(drive); diff --git a/Platform.cpp b/Platform.cpp index 7fb1956..54c162d 100644 --- a/Platform.cpp +++ b/Platform.cpp @@ -20,6 +20,7 @@ Licence: GPL ****************************************************************************************************/ #include "RepRapFirmware.h" +#include "DueFlashStorage.h" #define WINDOWED_SEND_PACKETS (2) @@ -74,16 +75,29 @@ Platform::Platform() void Platform::Init() { - byte i; + DueFlashStorage::init(); + DueFlashStorage::read(nvAddress, &nvData, sizeof(nvData)); + if (nvData.magic != FlashData::magicValue) + { + // Nonvolatile data has not been initialized since the firmware was last written, so set up default values + nvData.compatibility = me; + nvData.ipAddress = IP_ADDRESS; + nvData.netMask = NET_MASK; + nvData.gateWay = GATE_WAY; - compatibility = me; + nvData.zProbeType = 0; // Default is to use the switch + nvData.irZProbeParameters.Init(false); + nvData.ultrasonicZProbeParameters.Init(true); + + nvData.magic = FlashData::magicValue; + } line->Init(); messageIndent = 0; massStorage->Init(); - for(i=0; i < MAX_FILES; i++) + for(size_t i=0; i < MAX_FILES; i++) files[i]->Init(); fileStructureInitialised = true; @@ -93,10 +107,6 @@ void Platform::Init() sysDir = SYS_DIR; configFile = CONFIG_FILE; - ipAddress = IP_ADDRESS; - netMask = NET_MASK; - gateWay = GATE_WAY; - // DRIVES stepPins = STEP_PINS; @@ -117,9 +127,6 @@ void Platform::Init() zProbePin = Z_PROBE_PIN; zProbeModulationPin = Z_PROBE_MOD_PIN; - zProbeType = 0; // Default is to use the switch - zProbeADValue = Z_PROBE_AD_VALUE; - zProbeStopHeight = Z_PROBE_STOP_HEIGHT; InitZProbe(); // AXES @@ -153,7 +160,7 @@ void Platform::Init() gcodeDir = GCODE_DIR; tempDir = TEMP_DIR; - for(i = 0; i < DRIVES; i++) + for(size_t i = 0; i < DRIVES; i++) { if(stepPins[i] >= 0) @@ -181,7 +188,7 @@ void Platform::Init() driveEnabled[i] = false; } - for(i = 0; i < AXES; i++) + for(size_t i = 0; i < AXES; i++) { if(lowStopPins[i] >= 0) { @@ -199,7 +206,7 @@ void Platform::Init() pinMode(heatOnPins[0], OUTPUT); thermistorInfRs[0] = ( thermistorInfRs[0]*exp(-thermistorBetas[0]/(25.0 - ABS_ZERO)) ); - for(i = 1; i < HEATERS; i++) + for(size_t i = 1; i < HEATERS; i++) { if(heatOnPins[i] >= 0) pinModeNonDue(heatOnPins[i], OUTPUT); @@ -227,18 +234,261 @@ void Platform::InitZProbe() zProbeCount = 0; zProbeOnSum = 0; zProbeOffSum = 0; + zProbeMinSum = 0; + zProbeWorking = false; for (uint8_t i = 0; i < NumZProbeReadingsAveraged; ++i) { zProbeReadings[i] = 0; } - if (zProbeType != 0) + if (nvData.zProbeType == 1 || nvData.zProbeType == 2) { pinMode(zProbeModulationPin, OUTPUT); digitalWrite(zProbeModulationPin, HIGH); // enable the IR LED + SetZProbing(false); } } +int Platform::GetRawZHeight() const +{ + return (nvData.zProbeType != 0) ? analogRead(zProbePin) : 0; +} + +int Platform::ZProbe() const +{ + switch(nvData.zProbeType) + { + case 1: // simple IR sensor + return (int)((zProbeOnSum + zProbeOffSum)/NumZProbeReadingsAveraged); + case 2: // modulated IR sensor + return (int)((zProbeOnSum - zProbeOffSum)/(NumZProbeReadingsAveraged/2)); + case 3: // ultrasonic sensor + return (int)((zProbeOnSum + zProbeOffSum - zProbeMinSum)/(NumZProbeReadingsAveraged/2)); + default: + return 0; + } +} + +int Platform::GetZProbeSecondaryValues(int& v1, int& v2) const +{ + switch(nvData.zProbeType) + { + case 2: // modulated IR sensor + v1 = zProbeOnSum/(NumZProbeReadingsAveraged/2); // pass back the reading with IR turned on + return 1; + case 3: // ultrasonic + v1 = (zProbeOnSum + zProbeOffSum)/NumZProbeReadingsAveraged; // pass back the raw reading + v2 = zProbeMinSum/NumZProbeReadingsAveraged; // pass back the minimum found + return 2; + default: + return 0; + } +} + +int Platform::GetZProbeType() const +{ + return nvData.zProbeType; +} + +void Platform::PollZHeight() +{ + uint16_t currentReading = GetRawZHeight(); + if (nvData.zProbeType == 2) + { + // Reverse the modulation, ready for next time + digitalWrite(zProbeModulationPin, (zProbeCount & 1) ? HIGH : LOW); + } + if (zProbeCount & 1) + { + zProbeOffSum = zProbeOffSum - zProbeReadings[zProbeCount] + currentReading; + } + else + { + zProbeOnSum = zProbeOnSum - zProbeReadings[zProbeCount] + currentReading; + } + zProbeReadings[zProbeCount] = currentReading; + ++zProbeCount; + if (zProbeCount == NumZProbeReadingsAveraged) + { + zProbeCount = 0; + if (!zProbeWorking) + { + zProbeWorking = true; + if (nvData.zProbeType == 3) + { + zProbeMinSum = zProbeOnSum + zProbeOffSum; + } + } + } + if (nvData.zProbeType == 3 && zProbeWorking) + { + // Update the minimum value seen from the ultrasonic sensor + long temp = zProbeOnSum + zProbeOffSum; + if (temp < zProbeMinSum) + { + zProbeMinSum = temp; + } + } +} + +float Platform::ZProbeStopHeight() const +{ + switch(nvData.zProbeType) + { + case 1: + case 2: + return nvData.irZProbeParameters.GetStopHeight(GetTemperature(0)); + case 3: + return nvData.ultrasonicZProbeParameters.GetStopHeight(GetTemperature(0)); + default: + return 0; + } +} + +void Platform::SetZProbeType(int pt) +{ + int newZProbeType = (pt >= 0 && pt <= 3) ? pt : 0; + if (newZProbeType != nvData.zProbeType) + { + nvData.zProbeType = newZProbeType; + WriteNvData(); + } + InitZProbe(); +} + +bool Platform::GetZProbeParameters(struct ZProbeParameters& params) const +{ + switch (nvData.zProbeType) + { + case 1: + case 2: + params = nvData.irZProbeParameters; + return true; + case 3: + params = nvData.ultrasonicZProbeParameters; + return true; + default: + return false; + } +} + +bool Platform::SetZProbeParameters(const struct ZProbeParameters& params) +{ + switch (nvData.zProbeType) + { + case 1: + case 2: + if (nvData.irZProbeParameters != params) + { + nvData.irZProbeParameters = params; + WriteNvData(); + } + return true; + case 3: + if (nvData.ultrasonicZProbeParameters != params) + { + nvData.ultrasonicZProbeParameters = params; + WriteNvData(); + } + return true; + default: + return false; + } +} + +// Return true if we must hoe X and U before we home Z (i.e. we are using an IR probe) +bool Platform::MustHomeXYBeforeZ() const +{ + return nvData.zProbeType == 1 || nvData.zProbeType == 2; +} + +void Platform::WriteNvData() +{ + DueFlashStorage::write(nvAddress, &nvData, sizeof(nvData)); +} + +void Platform::SetZProbing(bool starting) +{ + if (starting && nvData.zProbeType == 3) + { + zProbeMinSum = zProbeOnSum + zProbeOffSum; //look for a new minimum + // Tell the ultrasonic sensor we are starting or have completed a z-probe + //digitalWrite(zProbeModulationPin, (starting) ? LOW : HIGH); + } +} + +float Platform::Time() +{ + unsigned long now = micros(); + if(now < lastTimeCall) // Has timer overflowed? + addToTime += ((float)ULONG_MAX)*TIME_FROM_REPRAP; + lastTimeCall = now; + return addToTime + TIME_FROM_REPRAP*(float)now; +} + +void Platform::Exit() +{ + Message(HOST_MESSAGE, "Platform class exited.\n"); + active = false; +} + +Compatibility Platform::Emulating() const +{ + if(nvData.compatibility == reprapFirmware) + return me; + return nvData.compatibility; +} + +void Platform::SetEmulating(Compatibility c) +{ + if(c != me && c != reprapFirmware && c != marlin) + { + Message(HOST_MESSAGE, "Attempt to emulate unsupported firmware.\n"); + return; + } + if(c == reprapFirmware) + { + c = me; + } + if (nvData.compatibility != c) + { + nvData.compatibility = c; + WriteNvData(); + } +} + +void Platform::UpdateNetworkAddress(byte dst[4], const byte src[4]) +{ + bool changed = false; + for(uint8_t i = 0; i < 4; i++) + { + if(dst[i] != src[i]) + { + dst[i] = src[i]; + changed = true; + } + } + if (changed) + { + WriteNvData(); + } +} + +void Platform::SetIPAddress(byte ip[]) +{ + UpdateNetworkAddress(nvData.ipAddress, ip); +} + +void Platform::SetGateWay(byte gw[]) +{ + UpdateNetworkAddress(nvData.gateWay, gw); +} + +void Platform::SetNetMask(byte nm[]) +{ + UpdateNetworkAddress(nvData.netMask, nm); +} + void Platform::StartNetwork() { network->Init(); @@ -360,7 +610,7 @@ void Platform::ClassReport(char* className, float &lastTime) // Result is in degrees celsius -float Platform::GetTemperature(int8_t heater) +float Platform::GetTemperature(int8_t heater) const { // If the ADC reading is N then for an ideal ADC, the input voltage is at least N/(AD_RANGE + 1) and less than (N + 1)/(AD_RANGE + 1), times the analog reference. // So we add 0.5 to to the reading to get a better estimate of the input. But first, recognise the special case of thermistor disconnected. @@ -394,12 +644,18 @@ void Platform::SetHeater(int8_t heater, const float& power) EndStopHit Platform::Stopped(int8_t drive) { - if(zProbeType > 0) + if(nvData.zProbeType > 0) { // Z probe is used for both X and Z. if(drive != Y_AXIS) { - if(ZProbe() > zProbeADValue) + int zProbeVal = ZProbe(); + int zProbeADValue = (nvData.zProbeType == 3) + ? nvData.ultrasonicZProbeParameters.adcValue + : nvData.irZProbeParameters.adcValue; + if(zProbeVal >= zProbeADValue) return lowHit; + else if (zProbeVal * 10 >= zProbeADValue * 9) // if we are at/above 90% of the target value + return lowNear; else return noStop; } diff --git a/Platform.h b/Platform.h index 59778a3..f40a7d6 100644 --- a/Platform.h +++ b/Platform.h @@ -203,7 +203,8 @@ enum EndStopHit { noStop = 0, // no enstop hit lowHit = 1, // low switch hit, or Z-probe in use and above threshold - highHit = 2 // high stop hit + highHit = 2, // high stop hit + lowNear = 3 // approaching Z-probe threshold }; /***************************************************************************************************/ @@ -410,6 +411,44 @@ private: /***************************************************************************************************************/ +// Struct for holding Z probe parameters + +struct ZProbeParameters +{ + int adcValue; // the target ADC value + float height; // the nozzle height at which the target ADC value is returned + float calibTemperature; // the temperature at which we did the calibration + float temperatureCoefficient; // the variation of height with bed temperature + + void Init(bool isUltrasonic) + { + adcValue = Z_PROBE_AD_VALUE; + height = Z_PROBE_STOP_HEIGHT; + calibTemperature = 20.0; + temperatureCoefficient = (isUltrasonic) + ? 0.007575 // speed of sound correction assuming half wavelength between sensor and bed + : 0.0; // no default temperature correction for IR sensor + } + + float GetStopHeight(float temperature) const + { + return ((temperature - calibTemperature) * temperatureCoefficient) + height; + } + + bool operator==(const ZProbeParameters& other) const + { + return adcValue == other.adcValue + && height == other.height + && calibTemperature == other.calibTemperature + && temperatureCoefficient == other.temperatureCoefficient; + } + + bool operator!=(const ZProbeParameters& other) const + { + return !operator==(other); + } +}; + // The main class that defines the RepRap machine for the benefit of the other classes class Platform @@ -495,17 +534,21 @@ class Platform void SetAxisLength(int8_t axis, float value); bool HighStopButNotLow(int8_t axis) const; + // Z probe + float ZProbeStopHeight() const; - void SetZProbeStopHeight(float z); int ZProbe() const; - int ZProbeOnVal() const; - void SetZProbe(int iZ); + int GetZProbeSecondaryValues(int& v1, int& v2) const; void SetZProbeType(int iZ); int GetZProbeType() const; + void SetZProbing(bool starting); + bool GetZProbeParameters(struct ZProbeParameters& params) const; + bool SetZProbeParameters(const struct ZProbeParameters& params); + bool MustHomeXYBeforeZ() const; // Heat and temperature - float GetTemperature(int8_t heater); // Result is in degrees celsius + float GetTemperature(int8_t heater) const; // Result is in degrees Celsius void SetHeater(int8_t heater, const float& power); // power is a fraction in [0,1] float PidKp(int8_t heater) const; float PidKi(int8_t heater) const; @@ -526,6 +569,27 @@ class Platform private: + // This is the structure used to hold out non-volatile data. + // The SAM3X doesn't have EEPROM so we save the data to flash. This unfortunately means that it gets cleared + // every time we reprogram the firmware. So there is no need to cater for writing one version of this + // struct and reading back another. + + struct FlashData + { + static const uint16_t magicValue = 0x59B2; // value we use to recognise that he flash data has been written + uint16_t magic; + ZProbeParameters irZProbeParameters; // Z probe values for the IR sensor + ZProbeParameters ultrasonicZProbeParameters; // Z probe values for the IR sensor + int zProbeType; // the type of Z probe we are using + byte ipAddress[4]; + byte netMask[4]; + byte gateWay[4]; + Compatibility compatibility; + }; + + static const uint32_t nvAddress = 0; // address in flash where we store the nonvolatile data + FlashData nvData; + float lastTime; float longWait; float addToTime; @@ -533,8 +597,6 @@ class Platform bool active; - Compatibility compatibility; - void InitialiseInterrupts(); int GetRawZHeight() const; @@ -555,29 +617,25 @@ class Platform int8_t potWipes[DRIVES]; float senseResistor; float maxStepperDigipotVoltage; -// float zProbeGradient; -// float zProbeConstant; int8_t zProbePin; int8_t zProbeModulationPin; - int8_t zProbeType; uint8_t zProbeCount; long zProbeOnSum; // sum of readings taken when IR led is on long zProbeOffSum; // sum of readings taken when IR led is on + long zProbeMinSum; // minimum zprobe value seen, used with ultrasonic probe uint16_t zProbeReadings[NumZProbeReadingsAveraged]; - int zProbeADValue; - float zProbeStopHeight; + bool zProbeWorking; // AXES void InitZProbe(); void PollZHeight(); + void UpdateNetworkAddress(byte dst[4], const byte src[4]); + void WriteNvData(); float axisLengths[AXES]; float homeFeedrates[AXES]; float headOffsets[AXES]; // FIXME - needs a 2D array -// bool zProbeStarting; -// float zProbeHigh; -// float zProbeLow; // HEATERS - Bed is assumed to be the first @@ -612,61 +670,19 @@ class Platform MassStorage* massStorage; FileStore* files[MAX_FILES]; bool fileStructureInitialised; - //bool* inUse; char* webDir; char* gcodeDir; char* sysDir; char* tempDir; char* configFile; - //byte* buf[MAX_FILES]; - //int bPointer[MAX_FILES]; - //char fileList[FILE_LIST_LENGTH]; - //char scratchString[STRING_LENGTH]; // Network connection Network* network; - byte ipAddress[4]; - byte netMask[4]; - byte gateWay[4]; }; // Seconds -inline float Platform::Time() -{ - unsigned long now = micros(); - if(now < lastTimeCall) // Has timer overflowed? - addToTime += ((float)ULONG_MAX)*TIME_FROM_REPRAP; - lastTimeCall = now; - return addToTime + TIME_FROM_REPRAP*(float)now; -} - -inline void Platform::Exit() -{ - Message(HOST_MESSAGE, "Platform class exited.\n"); - active = false; -} - -inline Compatibility Platform::Emulating() const -{ - if(compatibility == reprapFirmware) - return me; - return compatibility; -} - -inline void Platform::SetEmulating(Compatibility c) -{ - if(c != me && c != reprapFirmware && c != marlin) - { - Message(HOST_MESSAGE, "Attempt to emulate unsupported firmware.\n"); - return; - } - if(c == reprapFirmware) - c = me; - compatibility = c; -} - // Where the htm etc files are inline const char* Platform::GetWebDir() const @@ -824,76 +840,6 @@ inline void Platform::SetMaxFeedrate(int8_t drive, float value) maxFeedrates[drive] = value; } -inline int Platform::GetRawZHeight() const -{ - return (zProbeType != 0) ? analogRead(zProbePin) : 0; -} - -inline int Platform::ZProbe() const -{ - return (zProbeType == 1) - ? (zProbeOnSum + zProbeOffSum)/NumZProbeReadingsAveraged // non-modulated mode - : (zProbeType == 2) - ? (zProbeOnSum - zProbeOffSum)/(NumZProbeReadingsAveraged/2) // modulated mode - : 0; // z-probe disabled -} - -inline int Platform::ZProbeOnVal() const -{ - return (zProbeType == 1) - ? (zProbeOnSum + zProbeOffSum)/NumZProbeReadingsAveraged - : (zProbeType == 2) - ? zProbeOnSum/(NumZProbeReadingsAveraged/2) - : 0; -} - -inline float Platform::ZProbeStopHeight() const -{ - return zProbeStopHeight; -} - -inline void Platform::SetZProbeStopHeight(float z) -{ - zProbeStopHeight = z; -} - -inline void Platform::SetZProbe(int iZ) -{ - zProbeADValue = iZ; -} - -inline void Platform::SetZProbeType(int pt) -{ - zProbeType = (pt >= 0 && pt <= 2) ? pt : 0; - InitZProbe(); -} - -inline int Platform::GetZProbeType() const -{ - return zProbeType; -} - -inline void Platform::PollZHeight() -{ - uint16_t currentReading = GetRawZHeight(); - if (zProbeType == 2) - { - // Reverse the modulation, ready for next time - digitalWrite(zProbeModulationPin, (zProbeCount & 1) ? HIGH : LOW); - } - if (zProbeCount & 1) - { - zProbeOffSum = zProbeOffSum - zProbeReadings[zProbeCount] + currentReading; - } - else - { - zProbeOnSum = zProbeOnSum - zProbeReadings[zProbeCount] + currentReading; - } - zProbeReadings[zProbeCount] = currentReading; - zProbeCount = (zProbeCount + 1) % NumZProbeReadingsAveraged; -} - - //******************************************************************************************************** // Drive the RepRap machine - Heat and temperature @@ -990,37 +936,19 @@ inline Network* Platform::GetNetwork() return network; } -inline void Platform::SetIPAddress(byte ip[]) -{ - for(uint8_t i = 0; i < 4; i++) - ipAddress[i] = ip[i]; -} - inline const byte* Platform::IPAddress() const { - return ipAddress; -} - -inline void Platform::SetNetMask(byte nm[]) -{ - for(uint8_t i = 0; i < 4; i++) - netMask[i] = nm[i]; + return nvData.ipAddress; } inline const byte* Platform::NetMask() const { - return netMask; -} - -inline void Platform::SetGateWay(byte gw[]) -{ - for(uint8_t i = 0; i < 4; i++) - gateWay[i] = gw[i]; + return nvData.netMask; } inline const byte* Platform::GateWay() const { - return gateWay; + return nvData.gateWay; } inline Line* Platform::GetLine() const diff --git a/Release/RepRapFirmware-057o-dc52.bin b/Release/RepRapFirmware-057o-dc52.bin new file mode 100644 index 0000000000000000000000000000000000000000..0a2f8119da9859e07549d4e4cf00402c58de62c6 GIT binary patch literal 173048 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∈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 literal 0 HcmV?d00001 diff --git a/Webserver.cpp b/Webserver.cpp index 87b9cd3..5e73c51 100644 --- a/Webserver.cpp +++ b/Webserver.cpp @@ -440,14 +440,20 @@ void Webserver::GetJsonResponse(const char* request) // Send the Z probe value char scratch[SHORT_STRING_LENGTH+1]; scratch[SHORT_STRING_LENGTH] = 0; - if (platform->GetZProbeType() == 2) - { - snprintf(scratch, SHORT_STRING_LENGTH, ",\"probe\":\"%d (%d)\"", (int)platform->ZProbe(), platform->ZProbeOnVal()); - } - else - { - snprintf(scratch, SHORT_STRING_LENGTH, ",\"probe\":\"%d\"", (int)platform->ZProbe()); - } + int v0 = platform->ZProbe(); + int v1, v2; + switch(platform->GetZProbeSecondaryValues(v1, v2)) + { + case 1: + snprintf(scratch, SHORT_STRING_LENGTH, ",\"probe\":\"%d (%d)\"", v0, v1); + break; + case 2: + snprintf(scratch, SHORT_STRING_LENGTH, ",\"probe\":\"%d (%d, %d)\"", v0, v1, v2); + break; + default: + snprintf(scratch, SHORT_STRING_LENGTH, ",\"probe\":\"%d\"", v0); + break; + } strncat(jsonResponse, scratch, STRING_LENGTH); // Send the amount of buffer space available for gcodes