From 13e4a3f915491143217296cd64ec960b3b1a2659 Mon Sep 17 00:00:00 2001 From: Arthur Wolf Date: Mon, 5 Dec 2011 20:31:38 +0100 Subject: [PATCH] major bug fixes, way too many to enumerate --- gcc4mbed/build/gcc4mbed.mk | 25 ++-- gcc4mbed/build/mbed.ld | 6 + .../FATFileSystem/LPC1768/FATFileSystem.ar | Bin 40812 -> 40812 bytes gcc4mbed/external/mbed/LPC1768/capi.ar | Bin 74982 -> 74982 bytes gcc4mbed/external/mbed/LPC1768/mbed.ar | Bin 303372 -> 303372 bytes gcc4mbed/samples/CTest/makefile | 1 + gcc4mbed/samples/HelloWorld/main.cpp | 3 + gcc4mbed/samples/HelloWorld/makefile | 1 + gcc4mbed/samples/SDFileSystem/main.cpp | 64 ---------- gcc4mbed/samples/SDFileSystem/makefile | 4 +- gcc4mbed/samples/Ticker/main.cpp | 10 -- gcc4mbed/samples/Ticker/makefile | 4 +- gcc4mbed/src/gcc4mbed.c | 13 ++- gcc4mbed/src/syscalls.c | 6 +- src/libs/Kernel.h | 2 + src/libs/RingBuffer.h | 12 +- src/main.cpp | 14 ++- src/modules/communication/GcodeDispatch.cpp | 6 +- src/modules/communication/SerialConsole.cpp | 53 +++++++-- src/modules/communication/SerialConsole.h | 8 +- src/modules/communication/utils/Gcode.cpp | 46 ++++++-- src/modules/robot/Block.cpp | 65 ++++++++--- src/modules/robot/Block.h | 3 + src/modules/robot/Planner.cpp | 40 ++++++- src/modules/robot/Player.cpp | 99 ++++++++++++++++ src/modules/robot/Player.h | 32 +++++ src/modules/robot/Player.o | Bin 0 -> 131240 bytes src/modules/robot/Robot.cpp | 4 +- src/modules/robot/Stepper.cpp | 22 +++- src/modules/tools/extruder/Extruder.cpp | 109 ++++-------------- 30 files changed, 427 insertions(+), 225 deletions(-) create mode 100644 src/modules/robot/Player.cpp create mode 100644 src/modules/robot/Player.h create mode 100644 src/modules/robot/Player.o diff --git a/gcc4mbed/build/gcc4mbed.mk b/gcc4mbed/build/gcc4mbed.mk index 74becc84..199d7943 100644 --- a/gcc4mbed/build/gcc4mbed.mk +++ b/gcc4mbed/build/gcc4mbed.mk @@ -30,6 +30,10 @@ # of the build directory which contains this gcc4mbed.mk file. # LIBS_PREFIX: List of library/object files to prepend to mbed.ar capi.ar libs. # LIBS_SUFFIX: List of library/object files to append to mbed.ar capi.ar libs. +# GCC4MBED_DELAYED_STDIO_INIT: Set to non-zero value to have intialization of +# stdin/stdout/stderr delayed which will +# shrink the size of the resulting binary if +# APIs like printf(), scanf(), etc. aren't used. # Example makefile: # PROJECT=HelloWorld # SRC=. @@ -43,7 +47,12 @@ # Default project source to be located in current directory. ifndef SRC -SRC=./src +SRC=. +endif + +# Default the init of stdio/stdout/stderr to occur before global constructors. +ifndef GCC4MBED_DELAYED_STDIO_INIT +GCC4MBED_DELAYED_STDIO_INIT=0 endif # List of sources to be compiled/assembled @@ -64,19 +73,19 @@ EXTERNAL_DIR = $(GCC4MBED_DIR)/external # Include path which points to external library headers and to subdirectories of this project which contain headers. SUBDIRS = $(wildcard $(SRC)/* $(SRC)/*/* $(SRC)/*/*/* $(SRC)/*/*/*/* $(SRC)/*/*/*/*/*) PROJINCS = $(sort $(dir $(SUBDIRS))) -INCDIRS += $(PROJINCS) $(EXTERNAL_DIR)/mbed $(EXTERNAL_DIR)/mbed/LPC1768 $(EXTERNAL_DIR)/FATFileSystem +INCDIRS += $(PROJINCS) $(EXTERNAL_DIR)/mbed $(EXTERNAL_DIR)/mbed/LPC1768 $(EXTERNAL_DIR)/FATFileSystem $(GCC4MBED_DIR)/mri # DEFINEs to be used when building C/C++ code -DEFINES = -DTARGET_LPC1768 +DEFINES = -DTARGET_LPC1768 -DGCC4MBED_DELAYED_STDIO_INIT=$(GCC4MBED_DELAYED_STDIO_INIT) # Libraries to be linked into final binary -LIBS = $(LIBS_PREFIX) $(EXTERNAL_DIR)/mbed/LPC1768/mbed.ar $(EXTERNAL_DIR)/mbed/LPC1768/capi.ar $(EXTERNAL_DIR)/FATFileSystem/LPC1768/FATFileSystem.ar $(LIBS_SUFFIX) +LIBS = $(LIBS_PREFIX) $(GCC4MBED_DIR)/mri/mri.ar $(EXTERNAL_DIR)/mbed/LPC1768/mbed.ar $(EXTERNAL_DIR)/mbed/LPC1768/capi.ar $(EXTERNAL_DIR)/FATFileSystem/LPC1768/FATFileSystem.ar $(LIBS_SUFFIX) # Optimization level OPTIMIZATION = 2 # Compiler Options -GPFLAGS = -O$(OPTIMIZATION) -gdwarf-2 -mcpu=cortex-m3 -mthumb -mthumb-interwork -fshort-wchar -ffunction-sections -fdata-sections -fpromote-loop-indices -Wall -Wextra -Wimplicit -Wcast-align -Wpointer-arith -Wredundant-decls -Wshadow -Wcast-qual -Wcast-align -fno-exceptions +GPFLAGS = -O$(OPTIMIZATION) -gstabs+3 -mcpu=cortex-m3 -mthumb -mthumb-interwork -fshort-wchar -ffunction-sections -fdata-sections -fpromote-loop-indices -Wall -Wextra -Wimplicit -Wcast-align -Wpointer-arith -Wredundant-decls -Wshadow -Wcast-qual -Wcast-align -fno-exceptions GPFLAGS += $(patsubst %,-I%,$(INCDIRS)) GPFLAGS += $(DEFINES) @@ -94,12 +103,14 @@ OBJDUMP = arm-none-eabi-objdump SIZE = arm-none-eabi-size REMOVE = rm -# Switch to cs-rm on Windows. +# Switch to cs-rm on Windows and make sure that cmd.exe is used as shell. ifeq "$(MAKE)" "cs-make" REMOVE = cs-rm +SHELL=cmd.exe endif ######################################################################### +.PHONY: all clean deploy all:: $(PROJECT).hex $(PROJECT).bin $(PROJECT).disasm @@ -112,7 +123,7 @@ $(PROJECT).hex: $(PROJECT).elf $(PROJECT).disasm: $(PROJECT).elf $(OBJDUMP) -d $(PROJECT).elf >$(PROJECT).disasm -$(PROJECT).elf: $(LSCRIPT) $(OBJECTS) +$(PROJECT).elf: $(LSCRIPT) $(OBJECTS) $(LIBS) $(LD) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $(PROJECT).elf $(SIZE) $(PROJECT).elf diff --git a/gcc4mbed/build/mbed.ld b/gcc4mbed/build/mbed.ld index 5363eaa2..edaa0c46 100644 --- a/gcc4mbed/build/mbed.ld +++ b/gcc4mbed/build/mbed.ld @@ -127,6 +127,7 @@ SECTIONS _sidata = LOADADDR (.data); . = ALIGN(4); _sdata = .; + Image$$RW_IRAM1$$Base = .; *(.ARM.__AT_0x10000000) *(vtable vtable.*) @@ -150,6 +151,7 @@ SECTIONS . = ALIGN(4); _ebss = . ; + Image$$RW_IRAM1$$ZI$$Limit = . ; } >IRAM0 /**************************************************/ @@ -194,12 +196,16 @@ SECTIONS they will be left uninitialized. */ .AHBSRAM0 (NOLOAD): { + Image$$RW_IRAM2$$Base = . ; *(AHBSRAM0) + Image$$RW_IRAM2$$ZI$$Limit = .; } > IRAM1 .AHBSRAM1 (NOLOAD): { + Image$$RW_IRAM3$$Base = . ; *(AHBSRAM1) + Image$$RW_IRAM3$$ZI$$Limit = .; } > IRAM2 diff --git a/gcc4mbed/external/FATFileSystem/LPC1768/FATFileSystem.ar b/gcc4mbed/external/FATFileSystem/LPC1768/FATFileSystem.ar index 8099d00c1dddc55ea111a505ba5881df6f663772..87d3b125e9e8c10a83758b5499f2fec7d3a43101 100644 GIT binary patch delta 145 zcmaE}kLk@mrU}yQ76vAUrbZSUl~Or`p=<>OQv*W<1qBNO1%=JCICd&wl}OVL6virX U%C3?ht3;UJSuv~OLjwZ?2)TI{$4(`z5^36j!dN9v*;Vpm Ql?d}YD~46VEY-{k07wWTa{vGU diff --git a/gcc4mbed/external/mbed/LPC1768/capi.ar b/gcc4mbed/external/mbed/LPC1768/capi.ar index 2657ac9d2ba29463e37ec7777c1eb25a7e8d792a..7001e3bce7366b21038d7e731de4fef17ad98f7d 100644 GIT binary patch delta 648 zcmaEMlI7V+mI>1A76vAUrbZSUm5%cZL)i)nrUr%z3JMkm3JRP5@ZZ$HD)GW%A{$nT z9{YzZSS1==F->GdZy8h{;)qT|%JrFLr+@)Tjz! zH7cUdPynmMoOu^<_1A=0+CAW~Rm)m5%cZL)i)nh6V-(5OVV${+k+DC0F*ysdO9+(y#qJM<8dV{zMn&`) z3SgC(Gw&h}pD*^r9=dlnP36RDRLh=DVXP7g7j9yAF3bWgl}iQV!e l-+Qs^NclgL8=D>51-Ka>VvUgPEP{+{vD&izh!mp|7XaK~m_Yyl diff --git a/gcc4mbed/external/mbed/LPC1768/mbed.ar b/gcc4mbed/external/mbed/LPC1768/mbed.ar index ed6102075b17d17b578d036b7d9d3d1c8c0a90ab..c5b0be5393cf5afd162e39ecbaaba0d51acfdab0 100644 GIT binary patch delta 848 zcmeAe+x2MdE#nlR{o^u5(FWIoloGNGG#4hu!mPJh& E00lU~e*gdg diff --git a/gcc4mbed/samples/CTest/makefile b/gcc4mbed/samples/CTest/makefile index 678ea613..bc644bf8 100644 --- a/gcc4mbed/samples/CTest/makefile +++ b/gcc4mbed/samples/CTest/makefile @@ -15,5 +15,6 @@ PROJECT=CTest GCC4MBED_DIR=../.. LIBS_PREFIX= LIBS_SUFFIX= +GCC4MBED_DELAYED_STDIO_INIT=1 include ../../build/gcc4mbed.mk diff --git a/gcc4mbed/samples/HelloWorld/main.cpp b/gcc4mbed/samples/HelloWorld/main.cpp index 77b5dec8..d6f4318d 100644 --- a/gcc4mbed/samples/HelloWorld/main.cpp +++ b/gcc4mbed/samples/HelloWorld/main.cpp @@ -21,11 +21,14 @@ DigitalOut myled(LED1); int main() { + volatile int IterationCount = 0; + while(1) { myled = 1; wait(0.2); myled = 0; wait(0.2); + IterationCount++; } } diff --git a/gcc4mbed/samples/HelloWorld/makefile b/gcc4mbed/samples/HelloWorld/makefile index a031c3c3..824e3a92 100644 --- a/gcc4mbed/samples/HelloWorld/makefile +++ b/gcc4mbed/samples/HelloWorld/makefile @@ -15,5 +15,6 @@ PROJECT=HelloWorld GCC4MBED_DIR=../.. LIBS_PREFIX= LIBS_SUFFIX= +GCC4MBED_DELAYED_STDIO_INIT=1 include ../../build/gcc4mbed.mk diff --git a/gcc4mbed/samples/SDFileSystem/main.cpp b/gcc4mbed/samples/SDFileSystem/main.cpp index a0f638c2..22046e26 100644 --- a/gcc4mbed/samples/SDFileSystem/main.cpp +++ b/gcc4mbed/samples/SDFileSystem/main.cpp @@ -15,75 +15,13 @@ /* LocalFileSystem test modified to use SD cards instead. */ #include "mbed.h" #include "SDFileSystem.h" -#include "agutil.h" - - -extern "C" void HardFault_Handler(void) -{ - DebugDumpStack(); - error("\r\nHardFault\r\n"); -} - -extern "C" void MemManage_Handler(void) -{ - DebugDumpStack(); - error("\r\nMemManage\r\n"); -} - -extern "C" void BusFault_Handler(void) -{ - DebugDumpStack(); - error("\r\nBusFault\r\n"); -} - -extern "C" void UsageFault_Handler(void) -{ - DebugDumpStack(); - error("\r\nUsageFault\r\n"); -} - -extern "C" void SVC_Handler(void) -{ - DebugDumpStack(); - error("\r\nSVC Call\r\n"); -} - -extern "C" void DebugMon_Handler(void) -{ - DebugDumpStack(); - error("\r\nDebugMonitor\r\n"); -} - -extern "C" void PendSV_Handler(void) -{ - DebugDumpStack(); - error("\r\nPendSV\r\n"); -} - -extern "C" void SysTick_Handler(void) -{ - DebugDumpStack(); - error("\r\nSysTick"); -} - - -void BreakHandler(void) -{ - DebugDumpStack(); - error("\r\nManual Break\r\n"); -} SDFileSystem sd(p5, p6, p7, p8, "sd"); // the pinout on the mbed Cool Components workshop board -InterruptIn BreakInterrupt(p9); int main() { - // If you pull p9 low, then it should break into a running program and dump the stack. - BreakInterrupt.mode(PullUp); - BreakInterrupt.fall(BreakHandler); - int Result = -1; char Buffer[32]; @@ -163,7 +101,5 @@ int main() printf("Test 10: closedir\r\n"); closedir(d); - - printf("\r\nTest completed\r\n"); } diff --git a/gcc4mbed/samples/SDFileSystem/makefile b/gcc4mbed/samples/SDFileSystem/makefile index dfce4377..d01e8d88 100644 --- a/gcc4mbed/samples/SDFileSystem/makefile +++ b/gcc4mbed/samples/SDFileSystem/makefile @@ -13,8 +13,8 @@ # limitations under the License. PROJECT=SDFileSystem GCC4MBED_DIR=../.. -INCDIRS=../agutil -LIBS_PREFIX=../agutil/agutil.ar +INCDIRS= +LIBS_PREFIX= LIBS_SUFFIX= include ../../build/gcc4mbed.mk diff --git a/gcc4mbed/samples/Ticker/main.cpp b/gcc4mbed/samples/Ticker/main.cpp index 7bc70057..a4e54df0 100644 --- a/gcc4mbed/samples/Ticker/main.cpp +++ b/gcc4mbed/samples/Ticker/main.cpp @@ -13,7 +13,6 @@ limitations under the License. */ #include -#include Ticker flipper; @@ -30,15 +29,6 @@ int main() led2 = 1; flipper.attach(&flip, 5.0); // the address of the function to be attached (flip) and the interval (2 seconds) - // Dump the interrupt vector - printf("\r\n"); - printf("Vector Table Offset Register\r\n"); - DebugDumpMemory((void*)0xE000ED08, 4, 4); - - printf("Vector Table\r\n"); - unsigned int VectorAddress = *((unsigned int*)0xE000ED08); - DebugDumpMemory((void*)VectorAddress, 4 * 32, 4); - // spin in a main loop. flipper will interrupt it to call flip while(1) { led1 = !led1; diff --git a/gcc4mbed/samples/Ticker/makefile b/gcc4mbed/samples/Ticker/makefile index bd681128..38c28d20 100644 --- a/gcc4mbed/samples/Ticker/makefile +++ b/gcc4mbed/samples/Ticker/makefile @@ -13,8 +13,8 @@ # limitations under the License. PROJECT=Ticker GCC4MBED_DIR=../.. -INCDIRS=../agutil -LIBS_PREFIX=../agutil/agutil.ar +INCDIRS= +LIBS_PREFIX= LIBS_SUFFIX= include ../../build/gcc4mbed.mk diff --git a/gcc4mbed/src/gcc4mbed.c b/gcc4mbed/src/gcc4mbed.c index 46969921..7b667834 100644 --- a/gcc4mbed/src/gcc4mbed.c +++ b/gcc4mbed/src/gcc4mbed.c @@ -26,7 +26,7 @@ * Modified by Sagar G V on Mar 11 2011. added __libc_init_array() * Modfied by Adam Green in 2011 to support mbed. ******************************************************************************/ -#include "mbedsys.h" +#include "mri.h" /* Exported constants --------------------------------------------------------*/ @@ -46,6 +46,8 @@ extern unsigned long _ebss; /* end address for the .bss section. defined extern "C" int main(void); extern "C" void __libc_init_array(void); extern "C" void exit(int ErrorCode); +extern "C" void __GCC4MBEDOpenStandardHandles(void); +extern "C" void __MriTestRegisters(void); /* CRT initialization code called from Reset_Handler after it calls SystemInit() */ @@ -81,9 +83,18 @@ extern "C" __attribute__ ((section(".mbed_init"))) void __main(void) *(pulDest++) = 0; } + /* Initialize stdin/stdout/stderr file handles. */ + if (!GCC4MBED_DELAYED_STDIO_INIT) + { + __GCC4MBEDOpenStandardHandles(); + } + /* Initialize static constructors. */ __libc_init_array(); + MriInit(); + //__debugbreak(); + /* Call the application's entry point. */ ExitCode = main(); diff --git a/gcc4mbed/src/syscalls.c b/gcc4mbed/src/syscalls.c index 0221238c..07fb9ae2 100644 --- a/gcc4mbed/src/syscalls.c +++ b/gcc4mbed/src/syscalls.c @@ -47,7 +47,7 @@ static int g_StandardHandlesOpened = 0; /* Open the stdin/stdout/stderr handles */ -static void _OpenStandardHandles(void) +extern "C" void __GCC4MBEDOpenStandardHandles(void) { /* Open stdin/stdout/stderr */ _sys_open("/stdin", OPENMODE_R); @@ -238,7 +238,7 @@ extern "C" int _read(int file, char *ptr, int len) /* Open stdin/stdout/stderr if needed */ if (!g_StandardHandlesOpened && file < 3) { - _OpenStandardHandles(); + __GCC4MBEDOpenStandardHandles(); } /* Call the function in mbed.ar and let it handle the read */ @@ -258,7 +258,7 @@ extern "C" int _write(int file, char *ptr, int len) /* Open stdin/stdout/stderr if needed */ if (!g_StandardHandlesOpened && file < 3) { - _OpenStandardHandles(); + __GCC4MBEDOpenStandardHandles(); } /* Call the function in mbed.ar and let it handle the writes */ diff --git a/src/libs/Kernel.h b/src/libs/Kernel.h index 8ec6b9f0..74933b7a 100644 --- a/src/libs/Kernel.h +++ b/src/libs/Kernel.h @@ -56,6 +56,8 @@ class Kernel { Config* config; Player* player; + int debug; + private: vector hooks[NUMBER_OF_DEFINED_EVENTS]; // When a module asks to be called for a specific event ( a hook ), this is where that request is remembered diff --git a/src/libs/RingBuffer.h b/src/libs/RingBuffer.h index 46f4d26a..34765537 100644 --- a/src/libs/RingBuffer.h +++ b/src/libs/RingBuffer.h @@ -16,6 +16,7 @@ template class RingBuffer { RingBuffer(); int size(); int capacity(); + int next_block_index(int index); void push_back(kind object); void pop_front(kind &object); void get( int index, kind &object); @@ -40,6 +41,12 @@ template int RingBuffer::size(){ return((this->head>this->tail)?length:0)+this->tail-head; } +template int RingBuffer::next_block_index(int index){ + index++; + if (index == length) { index = 0; } + return(index); +} + template void RingBuffer::push_back(kind object){ this->buffer[this->tail] = object; this->tail = (tail+1)&(length-1); @@ -80,8 +87,9 @@ template void RingBuffer::pop_front(kind & } template void RingBuffer::delete_first(){ - kind dummy; - this->pop_front(dummy); + //kind dummy; + //this->pop_front(dummy); + this->head = (this->head+1)&(length-1); } diff --git a/src/main.cpp b/src/main.cpp index 53bc70e6..0575dbd4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,7 @@ #include "libs/Kernel.h" #include "modules/tools/laser/Laser.h" #include "modules/tools/extruder/Extruder.h" +#include "modules/robot/Player.h" #include "modules/utils/simpleshell/SimpleShell.h" #include "modules/utils/pauser/Pauser.h" #include "libs/SDFileSystem.h" @@ -89,6 +90,8 @@ class TemperatureControl : public Module { } double get_temperature(){ + double temp = this->new_thermistor_reading() ; + //this->kernel->serial->printf("adc reading: %f \r\n", temp); return this->adc_value_to_temperature( this->new_thermistor_reading() ); } @@ -162,10 +165,14 @@ class TemperatureControl : public Module { double average_adc_reading(){ double total; + int j=0; for( int i = 0; i <= this->queue.size()-1; i++ ){ + j++; + //this->kernel->serial->printf("value:%f\r\n", *(this->queue.get_ref(i)) ); total += *(this->queue.get_ref(i)); } - return total / this->queue.size(); + //this->kernel->serial->printf("total:%f, size:%d \r\n", total, this->queue.size() ); + return total / j; } AnalogIn* thermistor_pin; @@ -195,7 +202,6 @@ class TemperatureControl : public Module { }; - int main() { Kernel* kernel = new Kernel(); @@ -206,9 +212,11 @@ int main() { kernel->add_module( new Extruder(p26,p27) ); kernel->add_module( new SimpleShell() ); //kernel->add_module( new Pauser(p29,p30) ); - //kernel->add_module( new TemperatureControl() ); + kernel->add_module( new TemperatureControl() ); while(1){ kernel->call_event(ON_MAIN_LOOP); + } } + diff --git a/src/modules/communication/GcodeDispatch.cpp b/src/modules/communication/GcodeDispatch.cpp index d2d38881..969594e6 100644 --- a/src/modules/communication/GcodeDispatch.cpp +++ b/src/modules/communication/GcodeDispatch.cpp @@ -13,6 +13,7 @@ using std::string; #include "utils/Gcode.h" #include "libs/nuts_bolts.h" #include "GcodeDispatch.h" +#include "modules/robot/Player.h" GcodeDispatch::GcodeDispatch(){} @@ -35,10 +36,11 @@ void GcodeDispatch::on_console_line_received(void * line){ Gcode gcode = Gcode(); gcode.command = possible_command; this->kernel->call_event(ON_GCODE_RECEIVED, &gcode ); - this->kernel->serial->printf("ok\r\n"); + //this->kernel->serial->printf("ok %d \r\n", this->kernel->player->queue.size()); //Gcode* test = new Gcode(); - //this->kernel->serial->printf("ok %p\r\n", test); + //this->kernel->serial->printf("ok %d %p\r\n", this->kernel->player->queue.size(), test); //delete test; + this->kernel->serial->printf("ok\r\n"); // Ignore comments }else if( first_char == ';' || first_char == '(' ){ diff --git a/src/modules/communication/SerialConsole.cpp b/src/modules/communication/SerialConsole.cpp index cc054021..09ab0f84 100644 --- a/src/modules/communication/SerialConsole.cpp +++ b/src/modules/communication/SerialConsole.cpp @@ -12,6 +12,7 @@ using std::string; #include "libs/Kernel.h" #include "libs/nuts_bolts.h" #include "SerialConsole.h" +#include "libs/RingBuffer.h" // Serial reading module // Treats every received line as a command and passes it ( via event call ) to the command dispatcher. @@ -35,25 +36,53 @@ void SerialConsole::on_serial_char_received(){ char received = this->getc(); //On newline, we have received a line, else concatenate in buffer if( received == '\r' ){ return; } - if( received == '\n' ){ - this->line_received(); - receive_buffer = ""; - }else{ - this->receive_buffer += received; - } - } + //if( received == '\n' ){ + // this->line_received(); + // receive_buffer = ""; + //}else{ + // this->receive_buffer += received; + //} + this->buffer.push_back(received); + } } // Call event when newline received, for other modules to read the line inline void SerialConsole::line_received(){ - string new_string = this->receive_buffer; - this->received_lines.insert(this->received_lines.begin(),new_string); + //string new_string = this->receive_buffer; + //this->received_lines.insert(this->received_lines.begin(),new_string); } // Actual event calling must happen in the main loop because if it happens in the interrupt we will loose data void SerialConsole::on_main_loop(void * argument){ - if( this->received_lines.size() < 1 ){ return; } - this->kernel->call_event(ON_CONSOLE_LINE_RECEIVED, &this->received_lines.back() ); - this->received_lines.pop_back(); + //if( this->received_lines.size() < 1 ){ + // return; + //} + //this->kernel->call_event(ON_CONSOLE_LINE_RECEIVED, &this->received_lines.back() ); + //this->received_lines.pop_back(); + if( this->has_char('\n') ){ + int index = 0; + string received; + while(1){ + char c; + this->buffer.pop_front(c); + if( c == '\n' ){ + //this->printf("received: <%s> \r\n", received.c_str() ); + this->kernel->call_event(ON_CONSOLE_LINE_RECEIVED, &received ); + return; + }else{ + received += c; + } + } + } } +bool SerialConsole::has_char(char letter){ + int index = this->buffer.head; + while( index != this->buffer.tail ){ + if( this->buffer.buffer[index] == letter ){ + return true; + } + index = this->buffer.next_block_index(index); + } + return false; +} diff --git a/src/modules/communication/SerialConsole.h b/src/modules/communication/SerialConsole.h index 79be4cf9..328776be 100644 --- a/src/modules/communication/SerialConsole.h +++ b/src/modules/communication/SerialConsole.h @@ -15,6 +15,7 @@ #include using std::string; #include "SerialConsole.h" +#include "libs/RingBuffer.h" #define baud_rate_setting_ckeckusm 10922 @@ -26,9 +27,12 @@ class SerialConsole : public Module, public Serial { void on_serial_char_received(); void line_received(); virtual void on_main_loop(void * argument); + bool has_char(char letter); - string receive_buffer; // Received chars are stored here until a newline character is received - vector received_lines; // Received lines are stored here until they are requested + //string receive_buffer; // Received chars are stored here until a newline character is received + //vector received_lines; // Received lines are stored here until they are requested + RingBuffer buffer; // Receive buffer + }; #endif diff --git a/src/modules/communication/utils/Gcode.cpp b/src/modules/communication/utils/Gcode.cpp index 28d24711..a1e7030c 100644 --- a/src/modules/communication/utils/Gcode.cpp +++ b/src/modules/communication/utils/Gcode.cpp @@ -7,15 +7,47 @@ Gcode::Gcode(){} // Whether or not a Gcode has a letter bool Gcode::has_letter( char letter ){ - return ( this->command.find( letter ) != string::npos ); + //return ( this->command->find( letter ) != string::npos ); + for (size_t i=0; i < this->command.length(); i++){ + if( this->command.at(i) == letter ){ + return true; + } + } + return false; } // Retrieve the value for a given letter +//double Gcode::get_value_old( char letter ){ +// size_t start = this->command.find(letter); +// size_t end = this->command.find_first_not_of("1234567890.-", start+1); +// if( end == string::npos ){ end = this->command.length()+1; } +// string extracted = this->command.substr( start+1, end-start-1 ); +// double value = atof(extracted.c_str()); +// return value; +//} + + +// Retrieve the value for a given letter +// We don't use the high-level methods of std::string because they call malloc and it's very bad to do that inside of interrupts double Gcode::get_value( char letter ){ - size_t start = this->command.find(letter); - size_t end = this->command.find_first_not_of("1234567890.-", start+1); - if( end == string::npos ){ end = this->command.length()+1; } - string extracted = this->command.substr( start+1, end-start-1 ); - double value = atof(extracted.c_str()); - return value; + __disable_irq(); + for (size_t i=0; i <= this->command.length()-1; i++){ + if( letter == this->command.at(i) ){ + size_t beginning = i+1; + char buffer[20]; + for(size_t j=beginning; j <= this->command.length(); j++){ + char c; + if( j == this->command.length() ){ c = ';'; }else{ c = this->command.at(j); } + if( c != '.' && c != '-' && ( c < '0' || c > '9' ) ){ + buffer[j-beginning] = '\0'; + __enable_irq(); + return atof(buffer); + }else{ + buffer[j-beginning] = c; + } + } + } + } + __enable_irq(); + return 0; } diff --git a/src/modules/robot/Block.cpp b/src/modules/robot/Block.cpp index 82f98f8c..fe7c7771 100644 --- a/src/modules/robot/Block.cpp +++ b/src/modules/robot/Block.cpp @@ -21,10 +21,13 @@ using std::string; Block::Block(){ clear_vector(this->steps); this->times_taken = 0; // A block can be "taken" by any number of modules, and the next block is not moved to until all the modules have "released" it. This value serves as a tracker. + this->is_ready = false; + this->initial_rate = -1; + this->final_rate = -1; } void Block::debug(Kernel* kernel){ - kernel->serial->printf(" steps:%4d|%4d|%4d(max:%4d) nominal:r%10d/s%6.1f mm:%9.6f rdelta:%8d acc:%5d dec:%5d rates:%10d>%10d \r\n", this->steps[0], this->steps[1], this->steps[2], this->steps_event_count, this->nominal_rate, this->nominal_speed, this->millimeters, this->rate_delta, this->accelerate_until, this->decelerate_after, this->initial_rate, this->final_rate ); + kernel->serial->printf("%p: steps:%4d|%4d|%4d(max:%4d) nominal:r%10d/s%6.1f mm:%9.6f rdelta:%8d acc:%5d dec:%5d rates:%10d>%10d taken:%d ready:%d \r\n", this, this->steps[0], this->steps[1], this->steps[2], this->steps_event_count, this->nominal_rate, this->nominal_speed, this->millimeters, this->rate_delta, this->accelerate_until, this->decelerate_after, this->initial_rate, this->final_rate, this->times_taken, this->is_ready ); } @@ -47,6 +50,7 @@ void Block::calculate_trapezoid( double entryfactor, double exitfactor ){ this->initial_rate = ceil(this->nominal_rate * entryfactor); // (step/min) this->final_rate = ceil(this->nominal_rate * exitfactor); // (step/min) + //this->player->kernel->serial->printf("%p: r:%d \r\n", this, this->initial_rate); double acceleration_per_minute = this->rate_delta * this->planner->kernel->stepper->acceleration_ticks_per_second * 60.0; int accelerate_steps = ceil( this->estimate_acceleration_distance( this->initial_rate, this->nominal_rate, acceleration_per_minute ) ); int decelerate_steps = ceil( this->estimate_acceleration_distance( this->nominal_rate, this->final_rate, -acceleration_per_minute ) ); @@ -153,24 +157,33 @@ void Block::forward_pass(Block* previous, Block* next){ // Gcodes are attached to their respective blocks so that on_gcode_execute can be called with it void Block::append_gcode(Gcode* gcode){ - this->commands.push_back(gcode->command); - this->travel_distances.push_back(gcode->millimeters_of_travel); + //this->commands.push_back(gcode->command); + //this->travel_distances.push_back(gcode->millimeters_of_travel); + __disable_irq(); + this->gcodes.push_back(*gcode); + __enable_irq(); } // The attached gcodes are then poped and the on_gcode_execute event is called with them as a parameter void Block::pop_and_execute_gcode(Kernel* &kernel){ - for(unsigned short index=0; indexcommands.size(); index++){ - string command = this->commands.at(index); - double distance = this->travel_distances.at(index); - Gcode gcode = Gcode(); - gcode.command = command; - gcode.millimeters_of_travel = distance; - kernel->call_event(ON_GCODE_EXECUTE, &gcode ); + Block* block = const_cast(this); + //for(unsigned short index=0; indexcommands.size(); index++){ + // Gcode gcode = Gcode(); + // gcode.command = block->commands.at(index); + // gcode.millimeters_of_travel = block->travel_distances.at(index); + // kernel->call_event(ON_GCODE_EXECUTE, &gcode ); + //} + for(unsigned short index=0; indexgcodes.size(); index++){ + //this->player->kernel->serial->printf("exec: block:%p gcode:%p command:%p \r\n", block, &(block->gcodes[index]), &(block->gcodes[index].command) ); + //this->player->kernel->serial->printf(" str:%s \r\n", block->gcodes[index].command.c_str() ); + //wait(0.1); + kernel->call_event(ON_GCODE_EXECUTE, &(block->gcodes[index])); } } // Signal the player that this block is ready to be injected into the system void Block::ready(){ + this->is_ready = true; this->player->new_block_added(); } @@ -184,14 +197,40 @@ void Block::release(){ this->times_taken--; if( this->times_taken < 1 ){ this->player->kernel->call_event(ON_BLOCK_END, this); - //player->kernel->serial->printf("gcodes: %d, dist: %f \r\n", this->commands.size(), this->millimeters ); this->pop_and_execute_gcode(this->player->kernel); Player* player = this->player; + + //this->player->kernel->serial->printf("a %d\r\n", this->player->queue.size() ); if( player->queue.size() > 0 ){ player->queue.delete_first(); } - player->current_block = NULL; - player->pop_and_process_new_block(); + + //this->player->kernel->serial->printf("b %d %d\r\n", this->player->queue.size(), player->looking_for_new_block ); + + if( player->looking_for_new_block == false ){ + //player->pop_and_process_new_block(123); + if( player->queue.size() > 0 ){ + Block* candidate = player->queue.get_ref(0); + if( candidate->is_ready ){ + //candidate->debug(player->kernel); + //this->player->kernel->serial->printf("c %d %d\r\n", this->player->queue.size(), player->looking_for_new_block ); + player->current_block = candidate; + player->kernel->call_event(ON_BLOCK_BEGIN, player->current_block); + if( player->current_block->times_taken < 1 ){ + player->current_block->release(); + } + }else{ + + player->current_block = NULL; + + } + }else{ + //player->current_block->debug(player->kernel); + //this->player->kernel->serial->printf("d %d %d\r\n", this->player->queue.size(), player->looking_for_new_block ); + //wait(0.1); + player->current_block = NULL; + } + } } } diff --git a/src/modules/robot/Block.h b/src/modules/robot/Block.h index df7cd209..e50f743f 100644 --- a/src/modules/robot/Block.h +++ b/src/modules/robot/Block.h @@ -39,6 +39,7 @@ class Block { vector commands; vector travel_distances; + vector gcodes; unsigned int steps[3]; // Number of steps for each axis for this block unsigned int steps_event_count; // Steps for the longest axis @@ -60,6 +61,8 @@ class Block { double max_entry_speed; Planner* planner; Player* player; + + bool is_ready; short times_taken; // A block can be "taken" by any number of modules, and the next block is not moved to until all the modules have "released" it. This value serves as a tracker. diff --git a/src/modules/robot/Planner.cpp b/src/modules/robot/Planner.cpp index 91394e60..fd17e06d 100644 --- a/src/modules/robot/Planner.cpp +++ b/src/modules/robot/Planner.cpp @@ -43,7 +43,7 @@ void Planner::append_block( int target[], double feed_rate, double distance, dou //if( target[ALPHA_STEPPER] == this->position[ALPHA_STEPPER] && target[BETA_STEPPER] == this->position[BETA_STEPPER] && target[GAMMA_STEPPER] == this->position[GAMMA_STEPPER] ){ this->computing = false; return; } // Stall here if the queue is ful - while( this->kernel->player->queue.size() >= this->kernel->player->queue.capacity() ){ wait_us(100); } + while( this->kernel->player->queue.size() >= this->kernel->player->queue.capacity()-2 ){ wait_us(100); } Block* block = this->kernel->player->new_block(); block->planner = this; @@ -176,6 +176,7 @@ void Planner::append_block( int target[], double feed_rate, double distance, dou // 3. Recalculate trapezoids for all blocks. // void Planner::recalculate() { + //this->kernel->serial->printf("recalculate last: %p, queue size: %d \r\n", this->kernel->player->queue.get_ref( this->kernel->player->queue.size()-1 ), this->kernel->player->queue.size() ); this->reverse_pass(); this->forward_pass(); this->recalculate_trapezoids(); @@ -204,24 +205,55 @@ void Planner::forward_pass() { // planner_recalculate() after updating the blocks. Any recalulate flagged junction will // compute the two adjacent trapezoids to the junction, since the junction speed corresponds // to exit speed and entry speed of one another. +/* void Planner::recalculate_trapezoids() { // For each block - for( int index = 0; index <= this->kernel->player->queue.size()-1; index++ ){ // We skip the first one because we need a previous - - if( this->kernel->player->queue.size()-1 == index ){ //last block + int size = this->kernel->player->queue.size(); + for( int index = 0; index <= size-1; index++ ){ // We skip the first one because we need a previous + if( size-1 == index ){ //last block Block* last = this->kernel->player->queue.get_ref(index); last->calculate_trapezoid( last->entry_speed / last->nominal_speed, MINIMUM_PLANNER_SPEED / last->nominal_speed ); + this->kernel->serial->printf("%p: %d/%d last r:%d \r\n", last, index, size-1, last->initial_rate); }else{ Block* current = this->kernel->player->queue.get_ref(index); Block* next = this->kernel->player->queue.get_ref(index+1); if( current->recalculate_flag || next->recalculate_flag ){ current->calculate_trapezoid( current->entry_speed/current->nominal_speed, next->entry_speed/current->nominal_speed ); current->recalculate_flag = false; // Reset current only to ensure next trapezoid is computed + this->kernel->serial->printf("%p: %d/%d other r:%d \r\n", current, index, size-1, current->initial_rate); + }else{ + this->kernel->serial->printf("%p: %d/%d else r:%d \r\n", current, index, size-1, current->initial_rate); } } } } +*/ +void Planner::recalculate_trapezoids() { + int block_index = this->kernel->player->queue.head; + Block* current; + Block* next = NULL; + //this->kernel->serial->printf("tail:%d head:%d size:%d\r\n", this->kernel->player->queue.tail, this->kernel->player->queue.head, this->kernel->player->queue.size()); + + while(block_index != this->kernel->player->queue.tail){ + current = next; + next = &this->kernel->player->queue.buffer[block_index]; + //this->kernel->serial->printf("index:%d current:%p next:%p \r\n", block_index, current, next ); + if( current ){ + // Recalculate if current block entry or exit junction speed has changed. + if( current->recalculate_flag || next->recalculate_flag ){ + current->calculate_trapezoid( current->entry_speed/current->nominal_speed, next->entry_speed/current->nominal_speed ); + current->recalculate_flag = false; + } + } + block_index = this->kernel->player->queue.next_block_index( block_index ); + } + + // Last/newest block in buffer. Exit speed is set with MINIMUM_PLANNER_SPEED. Always recalculated. + next->calculate_trapezoid( next->entry_speed/next->nominal_speed, MINIMUM_PLANNER_SPEED/next->nominal_speed); //TODO: Make configuration option + next->recalculate_flag = false; + +} // Debug function void Planner::dump_queue(){ diff --git a/src/modules/robot/Player.cpp b/src/modules/robot/Player.cpp new file mode 100644 index 00000000..1681837c --- /dev/null +++ b/src/modules/robot/Player.cpp @@ -0,0 +1,99 @@ +/* + This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl) with additions from Sungeun K. Jeon (https://github.com/chamnit/grbl) + Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + You should have received a copy of the GNU General Public License along with Smoothie. If not, see . +*/ + +using namespace std; +#include +#include "mbed.h" +#include "libs/nuts_bolts.h" +#include "libs/RingBuffer.h" +#include "../communication/utils/Gcode.h" +#include "libs/Module.h" +#include "libs/Kernel.h" +#include "Block.h" +#include "Player.h" +#include "Planner.h" + +Player::Player(){ + this->current_block = NULL; + this->looking_for_new_block = false; +} + +// Append a block to the list +Block* Player::new_block(){ + + // Clean up the vector of commands in the block we are about to replace + // It is quite strange to do this here, we really should do it inside Block->pop_and_execute_gcode + // but that function is called inside an interrupt and thus can break everything if the interrupt was trigerred during a memory access + //Block* block = this->queue.get_ref( this->queue.size()-1 ); + Block* block = this->queue.get_ref( this->queue.size() ); + if( block->player == this ){ + for(short index=0; indexgcodes.size(); index++){ + block->gcodes.pop_back(); + } + //for(short index=0; indexcommands.size(); index++){ + // block->commands.pop_back(); + // block->travel_distances.pop_back(); + //} + } + + // Create a new virgin Block in the queue + this->queue.push_back(Block()); + block = this->queue.get_ref( this->queue.size()-1 ); + block->is_ready = false; + block->initial_rate = -2; + block->final_rate = -2; + block->player = this; + + return block; +} + +// Used by blocks to signal when they are ready to be used by the system +void Player::new_block_added(){ + //this->kernel->serial->printf("new block: %p\r\n", this->current_block); + + //if( this->current_block == 0x00 || this->queue.size() == 0 ){ + //this->kernel->serial->printf("f %p %d\r\n", this->current_block, this->queue.size() ); + //} + + + if( this->current_block == NULL ){ + this->pop_and_process_new_block(33); + } +} + +// Process a new block in the queue +void Player::pop_and_process_new_block(int debug){ + if( this->looking_for_new_block ){ return; } + this->looking_for_new_block = true; + + if( this->current_block != NULL ){ this->looking_for_new_block = false; return; } + + // Return if queue is empty + if( this->queue.size() == 0 ){ + this->current_block = NULL; + // TODO : ON_QUEUE_EMPTY event + this->looking_for_new_block = false; + return; + } + + // Get a new block + this->current_block = this->queue.get_ref(0); + + // Tell all modules about it + this->kernel->call_event(ON_BLOCK_BEGIN, this->current_block); + + // In case the module was not taken + if( this->current_block->times_taken < 1 ){ + //this->kernel->serial->printf("e %p %d %d %d\r\n", this->current_block, this->queue.size(), this->current_block == 0x00, debug ); + //wait(0.1); + this->looking_for_new_block = false; + this->current_block->release(); + } + + this->looking_for_new_block = false; + +} diff --git a/src/modules/robot/Player.h b/src/modules/robot/Player.h new file mode 100644 index 00000000..dd5254a0 --- /dev/null +++ b/src/modules/robot/Player.h @@ -0,0 +1,32 @@ +/* + This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl). + Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + You should have received a copy of the GNU General Public License along with Smoothie. If not, see . +*/ + +#ifndef PLAYER_H +#define PLAYER_H +#include "libs/Module.h" +#include "libs/Kernel.h" +using namespace std; +#include +#include + +class Player : public Module { + public: + Player(); + + Block* new_block(); + void new_block_added(); + void pop_and_process_new_block(int debug); + + RingBuffer queue; // Queue of Blocks + Block* current_block; + bool looking_for_new_block; + +}; + + + +#endif diff --git a/src/modules/robot/Player.o b/src/modules/robot/Player.o new file mode 100644 index 0000000000000000000000000000000000000000..d1037f736b95edcc040d4afb9a0a8ae4fc2b1d08 GIT binary patch literal 131240 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 nO{P86r^(o(sct1>#-;iy8G8)W!((argument); gcode->call_on_gcode_execute_event_immediatly = false; gcode->on_gcode_execute_event_called = false; - //If the queue is empty, execute immediatly, otherwise attach to the last added block if( this->kernel->player->queue.size() == 0 ){ gcode->call_on_gcode_execute_event_immediatly = true; @@ -67,7 +66,6 @@ void Robot::on_gcode_received(void * argument){ block->append_gcode(gcode); } - } @@ -117,6 +115,8 @@ void Robot::execute_gcode(Gcode* gcode){ } break; } + + // As far as the parser is concerned, the position is now == target. In reality the // motion control system might still be processing the action and the real tool position // in any intermediate location. diff --git a/src/modules/robot/Stepper.cpp b/src/modules/robot/Stepper.cpp index 95ff009f..d6bbed55 100644 --- a/src/modules/robot/Stepper.cpp +++ b/src/modules/robot/Stepper.cpp @@ -9,6 +9,7 @@ #include "libs/Kernel.h" #include "Stepper.h" #include "Planner.h" +#include "Player.h" #include "mbed.h" #include using namespace std; @@ -41,7 +42,9 @@ void Stepper::on_module_loaded(){ LPC_TIM0->MR0 = 10000; LPC_TIM0->MCR = 11; // for MR0 and MR1, with no reset at MR1 NVIC_EnableIRQ(TIMER0_IRQn); - NVIC_SetPriority(TIMER3_IRQn, 1); + NVIC_SetPriority(TIMER3_IRQn, 3); + NVIC_SetPriority(TIMER0_IRQn, 2); + NVIC_SetPriority(TIMER1_IRQn, 2); LPC_TIM0->TCR = 1; // Step and Dir pins as outputs @@ -100,7 +103,7 @@ void Stepper::on_block_begin(void* argument){ // The stepper does not care about 0-blocks if( block->millimeters == 0.0 ){ return; } - this->current_block = block; + //this->current_block = block; // Mark the new block as of interrest to us block->take(); @@ -177,6 +180,7 @@ inline void Stepper::main_interrupt(){ }else{ this->out_bits = 0; } + } // We compute this here instead of each time in the interrupt @@ -241,7 +245,19 @@ void Stepper::set_step_events_per_minute( double steps_per_minute ){ // Set the Timer interval LPC_TIM0->MR0 = floor( ( SystemCoreClock/4 ) / ( (steps_per_minute/60L) * speed_factor ) ); - + if( LPC_TIM0->MR0 < 150 ){ + LPC_TIM0->MR0 = 150; + + this->kernel->serial->printf("tim0mr0: %d, steps_per minute: %f \r\n", LPC_TIM0->MR0, steps_per_minute ); + //Block* block = new Block(); + //this->kernel->serial->printf(":l queue:%d debug:%d cb:%p cb=null:%d mem:%p tc:%u mr0:%u mr1:%u \r\n", this->kernel->player->queue.size() , this->kernel->debug, this->current_block, this->current_block == NULL, block, LPC_TIM0->TC , LPC_TIM0->MR0, LPC_TIM0->MR1 ); + //delete block; + //this->kernel->serial->printf("mr0:%u, st/m:%f, sf:%f, bsf:%f \r\n", LPC_TIM0->MR0, steps_per_minute, speed_factor, this->base_stepping_frequency ); + //this->current_block->debug(this->kernel); + //wait(0.1); + } + + // In case we change the Match Register to a value the Timer Counter has past if( LPC_TIM0->TC >= LPC_TIM0->MR0 ){ LPC_TIM0->TCR = 3; LPC_TIM0->TCR = 1; } diff --git a/src/modules/tools/extruder/Extruder.cpp b/src/modules/tools/extruder/Extruder.cpp index ac70dadd..720fd288 100644 --- a/src/modules/tools/extruder/Extruder.cpp +++ b/src/modules/tools/extruder/Extruder.cpp @@ -1,6 +1,7 @@ #include "mbed.h" #include "libs/Module.h" #include "libs/Kernel.h" +#include "modules/robot/Player.h" #include "modules/robot/Block.h" #include "modules/tools/extruder/Extruder.h" @@ -62,7 +63,7 @@ void Extruder::on_config_reload(void* argument){ void Extruder::on_gcode_execute(void* argument){ Gcode* gcode = static_cast(argument); - //this->kernel->serial->printf("gcode_exec: %s \r\n", gcode->command.c_str() ); + //this->kernel->serial->printf("e: %s\r\n", gcode->command.c_str() ); // Absolute/relative mode if( gcode->has_letter('M')){ @@ -86,9 +87,11 @@ void Extruder::on_gcode_execute(void* argument){ // Extrusion length if( gcode->has_letter('E' )){ double extrusion_distance = gcode->get_value('E'); + //this->kernel->serial->printf("a extrusion_distance: %f, target_position: %f, new_extrusion_distance: %f, current_position: %f \r\n", extrusion_distance, this->target_position, extrusion_distance - this->target_position, this->current_position ); if( this->absolute_mode == true ){ extrusion_distance = extrusion_distance - this->target_position; } + //this->kernel->serial->printf("b extrusion_distance: %f, target_position: %f \r\n", extrusion_distance, this->target_position ); if( fabs(gcode->millimeters_of_travel) < 0.0001 ){ this->solo_mode = true; this->travel_distance = extrusion_distance; @@ -115,28 +118,20 @@ void Extruder::on_gcode_execute(void* argument){ void Extruder::on_block_begin(void* argument){ Block* block = static_cast(argument); - //this->kernel->serial->printf("block_begin "); - //block->debug(this->kernel); - if( fabs(this->travel_distance) > 0.001 ){ block->take(); // In solo mode we take the block so we can move even if the stepper has nothing to do this->current_block = block; - //this->kernel->serial->printf("before: start:%f target:%f current:%f\r\n", this->start_position, this->target_position, this->current_position); this->start_position = this->target_position; this->target_position = this->start_position + this->travel_distance ; - //this->kernel->serial->printf("after: start:%f target:%f current:%f\r\n", this->start_position, this->target_position, this->current_position); + //this->kernel->serial->printf("bb: target_position: %f, travel_distance:%f \r\n", this->target_position, this->travel_distance ); this->on_speed_change(this); } if( fabs(this->travel_ratio) > 0.001 ){ // In non-solo mode, we just follow the stepper module this->current_block = block; - //this->kernel->serial->printf("before: start:%f target:%f current:%f\r\n", this->start_position, this->target_position, this->current_position); - //this->kernel->serial->printf("a:direction: %d start:%f current: %f target: %f add:%f close:%f \r\n", this->direction, this->start_position, this->current_position, this->target_position, (double(double(1)/double(1001)))*double(this->direction), fabs( this->current_position - this->target_position ) ); this->start_position = this->target_position; this->target_position = this->start_position + ( this->current_block->millimeters * this->travel_ratio ); this->on_speed_change(this); - //this->kernel->serial->printf("d:direction: %d start:%f current: %f target: %f add:%f close:%f \r\n", this->direction, this->start_position, this->current_position, this->target_position, (double(double(1)/double(1001)))*double(this->direction), fabs( this->current_position - this->target_position ) ); - //this->kernel->serial->printf("after: start:%f target:%f current:%f\r\n", this->start_position, this->target_position, this->current_position); } } @@ -148,9 +143,6 @@ void Extruder::on_block_end(void* argument){ void Extruder::on_speed_change(void* argument){ - - if( this->debug ){ this->kernel->serial->printf("back in event \r\n");wait(0.1); } - // Set direction if( this->target_position > this->current_position ){ this->direction = 1; @@ -159,98 +151,46 @@ void Extruder::on_speed_change(void* argument){ } if( fabs(this->travel_ratio) > 0.001 ){ - - if( this->debug ){ - this->kernel->serial->printf("back in if \r\n"); - this->kernel->stepper->current_block->debug(this->kernel); - wait(0.1); - } - - // Do not change rate if stepper rate is null + if( this->kernel->stepper->current_block == NULL || fabs(this->kernel->stepper->trapezoid_adjusted_rate) < 0.0001 || this->kernel->stepper->current_block->nominal_rate == 0 ){ - return; - } - - - if( this->debug ){ this->kernel->serial->printf("back in if2 \r\n");wait(0.1); } - - // Get a current/nominal rate ratio - if( fabs(this->kernel->stepper->current_block->nominal_rate) < 0.01 ){ - wait(1); + return; } - - if( this->debug ){ this->kernel->serial->printf("back in if3 \r\n");wait(0.1); } - + // Get a current/nominal rate ratio double stepper_rate_ratio = this->kernel->stepper->trapezoid_adjusted_rate / double( this->kernel->stepper->current_block->nominal_rate ) ; // Get a nominal duration for this block - - - if( this->debug ){ this->kernel->serial->printf("back in if4 \r\n");wait(0.1); } - double nominal_duration = this->kernel->stepper->current_block->millimeters / ( this->kernel->stepper->current_block->nominal_speed / 60 ) ; - - bool testo = false; - if( this->debug ){ - this->kernel->serial->printf("back in if5 \r\n");wait(0.1); - this->debug_count--; - if( this->debug_count == 0 ){ - this->debug = false; - } - testo = true; - } - if( testo ){ this->kernel->serial->printf("back in if7 \r\n");wait(0.1); } - // Get extrusion nominal speed - if( fabs(nominal_duration) < 0.001 || this->debug ){ - this->kernel->serial->printf("start debugging, nominal_duration: %f \r\n", double(nominal_duration)); - this->kernel->stepper->current_block->debug(this->kernel); - this->debug = true; - this->debug_count = 5; - wait(0.1); - } - - if( testo ){ this->kernel->serial->printf("back in if8 \r\n");wait(0.1); } double nominal_extrusion_speed = fabs( this->target_position - this->start_position ) / nominal_duration; - - - if( testo ){ this->kernel->serial->printf("back in if9 \r\n");wait(0.1); } - if( this->debug ){ this->kernel->serial->printf("nom_extr_speed: %f \r\n", double(nominal_extrusion_speed));wait(0.1); } // Get adjusted speed double adjusted_speed = nominal_extrusion_speed * stepper_rate_ratio; - - if( testo ){ this->kernel->serial->printf("back in if10 \r\n");wait(0.1); } - - if( this->debug ){ this->kernel->serial->printf("adj_speed a: %f \r\n", double(adjusted_speed));wait(0.1); } - //this->kernel->serial->printf("e:direction: %d start:%f current: %f target: %f add:%f close:%f \r\n", this->direction, this->start_position, this->current_position, this->target_position, (double(double(1)/double(1001)))*double(this->direction), fabs( this->current_position - this->target_position ) ); - //this->kernel->serial->printf("speed change rate:%f/%u=%f dur:%f=%f/%f nom_extr_spd:%f/%f=%f adj_speed:%f \r\n", this->kernel->stepper->trapezoid_adjusted_rate, this->kernel->stepper->current_block->nominal_rate, stepper_rate_ratio, nominal_duration, this->kernel->stepper->current_block->millimeters, this->kernel->stepper->current_block->nominal_speed / 60, ( this->target_position - this->start_position ), nominal_duration, nominal_extrusion_speed, adjusted_speed ); - - - if( testo ){ this->kernel->serial->printf("back in if11 \r\n");wait(0.1); } - if( this->debug ){ this->kernel->serial->printf("adj_speed b: %f \r\n", double(adjusted_speed));wait(0.1); } // Set timer - LPC_TIM1->MR0 = ((SystemCoreClock/4))/int(floor(adjusted_speed*1000)); + if((adjusted_speed*1400) < 1 ){ + return; + } + LPC_TIM1->MR0 = ((SystemCoreClock/4))/(adjusted_speed*1700); + if( LPC_TIM1->MR0 < 300 ){ + this->kernel->serial->printf("tim1mr0 %d, adjusted_speed: %f\r\n", LPC_TIM1->MR0, adjusted_speed ); + LPC_TIM1->MR0 = 300; + } - if( testo ){ this->kernel->serial->printf("back in if12 \r\n");wait(0.1); } - if( this->debug ){ this->kernel->serial->printf("adj_speed c: %f \r\n", double(adjusted_speed));wait(0.1); } // In case we are trying to set the timer to a limit it has already past by if( LPC_TIM1->TC >= LPC_TIM1->MR0 ){ LPC_TIM1->TCR = 3; LPC_TIM1->TCR = 1; } - + // Update Timer1 LPC_TIM1->MR1 = (( SystemCoreClock/4 ) / 1000000 ) * this->microseconds_per_step_pulse; - if( testo ){ this->kernel->serial->printf("back in if13 \r\n");wait(0.1); } - if( this->debug ){ this->kernel->serial->printf("adj_speed d: %f \r\n", double(adjusted_speed));wait(1); } + } if( fabs(this->travel_distance) > 0.001 ){ // Set timer - LPC_TIM1->MR0 = ((SystemCoreClock/4))/int(floor(1000)); + LPC_TIM1->MR0 = ((SystemCoreClock/4))/int(floor(1.2*1700)); // In case we are trying to set the timer to a limit it has already past by if( LPC_TIM1->TC >= LPC_TIM1->MR0 ){ @@ -266,14 +206,11 @@ void Extruder::on_speed_change(void* argument){ } - - - inline void Extruder::stepping_tick(){ - if( fabs( this->current_position - this->target_position ) >= 0.001 ){ - //this->kernel->serial->printf("b:direction: %d start:%f current: %f target: %f add:%f close:%f \r\n", this->direction, this->start_position, this->current_position, this->target_position, (double(double(1)/double(1001)))*double(this->direction), fabs( this->current_position - this->target_position ) ); - this->current_position += (double(double(1)/double(1000)))*double(this->direction); - //this->kernel->serial->printf("c:direction: %d start:%f current: %f target: %f add:%f close:%f \r\n", this->direction, this->start_position, this->current_position, this->target_position, (double(double(1)/double(1001)))*double(this->direction), fabs( this->current_position - this->target_position ) ); + + //if( fabs( this->current_position - this->target_position ) >= 0.001 ){ + if( ( this->current_position < this->target_position && this->direction == 1 ) || ( this->current_position > this->target_position && this->direction == -1 ) ){ + this->current_position += (double(double(1)/double(1700)))*double(this->direction); this->dir_pin = ((this->direction > 0) ? 1 : 0); this->step_pin = 1; }else{ -- 2.20.1