wallacoloo / printipi Goto Github PK
View Code? Open in Web Editor NEW3d printing directly through the Raspberry Pi's GPIO pins
License: MIT License
3d printing directly through the Raspberry Pi's GPIO pins
License: MIT License
Currently, if you load printipi and tell it G1 E100
(regardless of if it's relative or absolute positioning), it will first home all axes before moving the extruder. This is clearly not necessary, as the extruder coordinate system is independant from the cartesian coordinate system.
The Raspberry Pi has 16 DMA channels, some of which can be used by a user-space program. These channels can be pointed to the IO memory, so that a user-defined buffer is continually pouring into the IOs. DMA is resistant to kernel-interrupts, uses less cpu, and has a potentially very high bandwidth (at least the 10mbps used by Ethernet). Thus, it appears to be a suitable replacement for the current sleep-based event timing.
There is very little documentation for the Raspberry Pi's DMA. Some resources include:
http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf (Official documentation - see chapter 4, pg 38)
https://github.com/metachris/RPIO/blob/master/source/c_pwm/pwm.c (a servo-control module for Python).
http://www.raspberrypi.org/forums/viewtopic.php?f=44&t=36572 (forum about the above module)
http://www.tbideas.com/blog/2013/02/controling-a-high-power-rgb-led-with-a-raspberry-pi/ (cmd-line executable to run a pin at a given PWM rate with DMA)
https://github.com/sarfata/pi-blaster/ (code for above example)
https://github.com/PeterLemon/RaspberryPi/blob/master/HelloWorld/DMA/kernel.asm (supposed DMA demo in assembly)
http://sourceforge.net/projects/v3craspi/ (DMA for blinking an led? It's really not clear what this project is supposed to be)
http://www.raspberrypi.org/forums/viewtopic.php?t=55657 (forum for above example)
Git subtree presents a way to share code between repositories that's compatible with the common git clone ...
command because it retains full copies of the code within each repository. A git pull ...
will also update the full tree because of this.
Guide for using git subtree: http://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/
On all Raspberry Pi boards up to and including the Model B+, only gpio pins 0-31 are routed out to the gpio header.
On the broadcom chip's data bus, pins 0-31 are accessed in one word, and pins 32+ occupy the next word in memory. Currently, every data frame, 8 bytes of memory are copied into the 2 GPSET registers to set any of the 54 gpios, and then 8 more bytes are copied into the GPCLR registers. Finally, another 16 bytes are copied into the source buffer to reset it for the next iteration (the buffer is circular) and apply PWM settings.
By only buffering and copying data for pins 0-31, a total of 16 bytes per frame are saved. That amounts to 4 mb/sec of bus traffic if running at 250ksamples/sec, or 8 mb/sec if at 500ksamples/sec.
It seems unlikely that future version of the Raspberry Pi will expose pins > 31 due to the fact that many of the remaining 22 broadcom pins are almost surely routed throughout the board internally (usb, ethernet).
Ideally, we could detect at compile-time which pins will be used & then decide whether to support all 54 or just the lower 32. However, the only somewhat clean way to do that is to have the Machine expose the highest pin value it uses explicitly, which almost certainly has to be hand-coded, and then choose the correct DMA Engine implementation from that.
It may be ok to just make MAX_GPIO_ID a define sent to the makefile & relayed to each compilation unit, defaulting to 32. But this would be annoying on non-RPi platforms, and whenever building a machine that requires more than 32 pins.
This project needs some sort of automated testing. Currently, the only thing tested is that all the machines will compile under various compiler versions/modes.
So I'm thinking of some sort of unit test framework. This StackOverflow post suggests the CATCH framework and another few suggest lest.
Both are contained in a single header. lest appears to be CATCH altered for c++11 use.
Catch appears to me a very natural way to do things, and is almost self-documenting using constructs such as
WHEN( "the size is increased" ) {
v.resize( 10 );
THEN( "the size and capacity change" ) {
REQUIRE( v.size() == 10 );
REQUIRE( v.capacity() >= 10 );
}
}
It's not immediately clear to me how it works across multiple files. It's important that the test suite doesn't add bloat to non-test builds. At the same time, is it important to be able to define tests inline with the source? Printipi uses a lot of templates (source in header). This blog post puts their tests in separate .cpp files, and even in a different directory, although this blog puts the tests in the same source file.
Besides unit testing, another option is to place many assertions in the normal (debug targeted) code and test the binary by throwing gcodes at it. While this approach can test the system as a whole, it doesn't test the logic of each part individually, especially local edge-cases. On the other hand, it might provide more realistic input.
Servos are controlled by periodically sending a pulse of a specified length.
The length of that pulse determines the position at which the servo should be placed, and the servo will attempt to stay at that location until it receives another pulse
Typical pulse lengths vary from 1ms to 2ms for the full control range and the pulses must occur between 40-200 times per second.
There are multiple ways to generate these pulses:
Each has notable downsides. If we use PWM, it requires that the underlying hardware is capable of sustaining PWM cycles of at least about 10 ms (100 Hz) in length. It also requires a slight rework of the HardwareScheduler API (with a fixed-length buffer-based PWM system like PWM via DMA, the user can't just ask for 100Hz and expect to get it - the period must be an exact divisor of the buffer length).
If we do this via scheduling OutputEvents, then we have to deal with concurrent scheduling of motor steps and servo controls. In a way, we already deal with concurrent scheduling, in the form of scheduling multiple AxisSteppers side-by-side, and that turns out to be rather trivial. This could be generalized to concurrently scheduling all IoDrivers - just implement the method, getNextOutputEvent()
.
I think using OutputEvents is the better, and more future-proof choice here. The downside is that would require a great deal more refactoring.
Currently, when one types make MACHINE=rpi::KosselPi
or similar, it's case-sensitive. MACHINE
can be given as xxx::Yyy
, xxx/Yyy.h
or xxx/Yyy
, but it's a bit confusing when using the later two cases becuase you must match the cases to the C++ name instead of the (all-lowercase) filename. I think it would be more clear to force the case of the actual machine file to match its C++ name, and then have MACHINE=rpi::kosselpi match against rpi/KosselPi.h in a case-insensitive manner to obtain the path and actual C++ classname.
This change allows one to type make MACHINE=rpi::KosselPi
or make MACHINE=rpi/KosselPi.h
or make MACHINE=rpi/kosselpi
, make MACHINE=RPI::KOSSELPI
or any crazy case combination of those.
I imagine Make has some case-insensitive file matching that should make this fairly trivial to implement.
That is, instead of returning just the raw pin number, OutputEvent should store all the information about a pin, including its inversion states, etc.
This makes it so only the HardwareScheduler has to worry about pin inversions, instead of higher-level classes like A4988 stepper drivers. It also allows us to completely remove the GpioPinIdType typedef (see #51).
Firstly, the concept of what "drivers" are is a bit vague. But I think it can be solidified be renaming them "iodrivers" or something similar, as a Machine exposes a tuple through getIoDrivers()
, and each member of the tuple is a subclass of IoDriver
.
So, our "drivers" are:
I think we can group the thermistor in there, since it handles IO, even though it's a data member of TempControl. I also think we can leave IOPin in there for now, because it's designed to be used exclusively by drivers (although it is also referenced by the Machine...)
So, nothing else besides those should reside in src/drivers.
I think "src/drivers/auto", "drivers/generic", and "drivers/rpi" should be moved to "src/platforms/", because they represent generic overrides for each platform - some of the overrides are IO based (PrimitiveIoPin), some are optimization-based (Clocks & such).
AxisStepper and its implementations should be moved into the "motion" folder, as they represent motion.
I'm not sure if CoordMap & implementations belong in their own folder or in the motion folder. I would put them in the motion folder for now, especially considering that they might later interact with the AxisSteppers.
I'm told that the FETs often used for controlling a heater dissipate a significant amount of power when switching.
250kHz switching (current PCM over DMA rate) is way faster than is necessary for controlling a heater. I can't find any info on optimal frequencies, but I suspect there's very little discernible difference until you get below ~20Hz.
Most of the functionality needed from the (GPL) bcm2835 library is already present in drivers/rpi/dmascheduler. Currently, IO pins call into the bcm2835 library, while the DmaScheduler does its own thing, causing many parts of system memory to be mmap'd into two different regions - unnecessary!
These common memory regions / routines should be placed into another shared file, and doing so would allow Printipi to lose its dependency upon the GPL bcm2835 library.
Currently, the State passes Event objects (e.g. "step motor 2 forward @t=2.3") for the Scheduler to schedule. The scheduler then immediately breaks these apart into a sequence of OutputEvents (e.g. "set pin 3 high at t=2.301") and schedules those.
The main issue here is that to turn a step event into pin events, the Scheduler must know extra information about the machine, and that complicates the interface between Scheduler and State.
I think it is more logical to have the State do the transformation from Event to OutputEvent instead of the Scheduler. Eventually, it may not even be necessary to use an intermediate Event object.
In src/drivers/a4988.h
, the time between a LOW and a HIGH pulse on the a4988 driver is set to 8 uS. In actuality, this only needs to be 1 uS. But with backends like DMA, that quantize events, two events happening 1 uS apart may actually be scheduled simultaneously. Thus, 8 uS was picked to try to avoid this.
The proper thing to do is to signal somehow that events must occur at least T time apart. There are a few ways to do this - add a quantization
parameter to getEventOutputSequence, but I think the best way is to add another boolean data field to OutputEvent
called forceMinInterval
, which instructs the scheduler to force the interval between this event and the previous event to be at least (event2.time - event1.time).
This new data field is only meaningful in an OutputEvent
stream, so it could help to keep OutputEvent
the way it is and instead introduce a new class, OutputEventStream
, that manages this data. But I think that's overkill.
There's a bunch of template recursion stuff that can be made cleaner by using tupleutil.
Occasionally, the kernel will crash after running printipi a few times. I have only seen this occur three times now, starting on Oct 2.
Conditions: profile build, g++-4.7, usb wifi tethering. It can occur on the first post-boot run. Command: sudo ./printipi ../test.gcode
. Error occurs after an unpredictable time and causes the program to exit (uncleanly - steppers are not deactivated). System is still useable.
compiling with make CXX=g++-4.7
or make release CXX=g++-4.7
. Bug not tested with g++-4.6.
running sudo valgrind ./printipi test.gcode --verbose
causes the following output:
...
command: G28
response: ok
MotionPlanner::nextStep() is: 2 at 0.00399042
Accel::transform: 0.003990
Step transformed time: 0.013317
LeverEndstop is 0
Then the program hangs - no thermistor reads OR read errors, cpu is about 95% (note: running under valgrind)
The current way GPIOs are handled has a few problems:
To do:
areWritesInverted()
from IoPinid()
from IoPinPrimitiveIoPin
)M32 currently takes a full path to whichever file is to be printed. It should instead take a path relative to a virtual root directory, so .e.g. "M32 /testprint.gcode" prints the file "/home/pi/printipi/uploads" + "/testprint.gcode" = "/home/pi/printipi/uploads/testprint.gcode".
This root directory should be configurable via a command-line argument.
As far as implementation goes, the path could either be sent to State upon instantiation. Or stored in a global object that translates local to absolute paths. Perhaps best would be to create a FileSystem type, instantiate it in Main, and pass is to State as a ref. This FileSystem object would have functions like openRelativePath(std::string), etc. This would also come in handy when it comes to doing things like listing directories for certain M commands.
Gert showed that it was possible to dump the value of over 50 million 16-bit values per second into the GPIOs using the Pi's GPU. His application was to have each 16 bits present one pixel (RGB565) and use a resistor DAC to send it to a VGA LCD, along with clock signals generated by the GPU, all with accurate enough timings for video (in order to correctly place each pixel, that requires timing accurate to about 20 ns).
Unfortunately, his code, at the time of writing, is only supplied as a binary blob and the documentation of the GPU features he used is nonexistent.
But there are other efforts to demystify the Pi's GPU. There is, for example, this tutorial for writing GPU code on the Pi. It may be possible to commandeer one GPU core permanently and use it to precisely drive the GPIO pins (Not sure how much control we have over scheduling).
Useful documents:
Herman Hermitage Unofficial Videocore Docs
Official Broadcom Videocore Documentation
You cannot prevent 100% of hotend runaway scenarios (scenarios in which the hotend continues to heat far past its setpoint) in firmware, but there are a few things that firmware can watch for:
There are some other things too, such as using algorithms based upon power delivered to the hotend, rather than simple PID controls.
According to this thread, Marlin has a "Thermal Runaway Protection" feature to reference.
So these thermal runaway protection features need to be researched and then implemented in some form.
Supporting a non-dma schedule system makes it easier to add support for new architectures and test for errors in the DMA engine.
And it shouldn't be very difficult; just need to implement SchedInterfaceHardwareScheduler functionality that directly controls IO pins. PWM would need to be emulated, however.
There are interfaces which have more than one (mutually-exclusive) implementation that must be chosen at compile-time. For example, we have boilerplate/ChronoClockPosix and we have drivers/rpi/ChronoClockRpi. These both implement the same functionality, but the ChronoClockRpi implementation will allow for more efficiency on a Raspberry Pi platform (and won't work on another platform).
Currently, the decision of which clock to use is made by inspecting defines sent to the compiler about the target platform. We get a define of TARGET_PLATFORM_RPI when building on the raspberry pi, TARGET_PLATFORM_XXX when building on something else.
These defines are already determined dynamically by the Makefile, through inspecting the directory that contains our machine definition (eg machines/rpi/kosselpi.h). By standardizing the driver layout, the individual interface could also be determined by the Makefile.
For example, generic implementations would go under drivers/generic. Raspberry Pi implementations would go under drivers/rpi and would have the same filename and classname as the generic drivers (but would use the drv::rpi:: namespace instead of the drv::generic namespace). Therefore, all the Makefile has to do is check whether the platform type contains an implementation-specific version of an interface, and instruct the rest of the system to use that if it does, or tell it to use the generic implementation otherwise.
The advantage to this system is in making it trivial to add support for new platforms without tracking down and modifying the code that controls which interface is chosen - new interfaces, and even entirely new platforms, are detected dynamically.
Currently, each DMA frame looks like this:
In actuality, step 3 consists of copying 8 precomputed bytes from a PWM queue to the current frame so that PWM is more efficient.
Each DMA control block is 32 bytes of overhead. So the current implementation uses 36+40+40=116 bytes / frame in bandwidth.
There are a few ways to optimize this.
1: try to optimize control block 3
By only doing step 3 every, say, 2 frames, and instead making it move 16 bytes, we save an average of 16 bytes of overhead each frame.
(Optionally, we can also halve the PWM buffer period, and use the stride feature to copy each PWM frame into two GPIO frames when performing step 3. The only advantage this gives is decreased memory footprint and perhaps slightly better cache performance.)
This decreased data usage should lead to more consistent timing in the long run by decreasing bus contention. But in no longer having each frame use the same amount of data (36+40=76 bytes on even frames and 36+40+48=124 bytes on odd frames), it may increase local timing variations.
Another option is to put the buffer reseting routine into its own separate DMA channel and run that side-by-side at a lower AXI priority. The downside here is that DMA channels are a precious resource and the more we use them in userland, the more likely we are to interfere with other applications.
The final option is to just replace the buffer reseting with cpu-based code. This only needs to transfer 250000*8 = 2mb of data per second, which is totally doable on the cpu. The downside is that this takes away processor time from the motion planner. We currently have a good amount of cpu to spare though. The upside is that it will be simpler, and will likely have next to zero interference with the DMA operation.
Right now, the Machine exposes a getAxisSteppers()
function, and a getArcSteppers()
function, on top of a getCoordMap()
function.
The AxisSteppers are really completely dependent upon the CoordMap, so these getxxxSteppers()
functions should be member functions of the CoordMap, and not exposed through the Machine.
Machines currently have a mix of public typedefs (like CoordMapT
, IODriverTypes
, etc) and public functions (like clampMoveRate
, doHomeBeforeFirstMovement
). The typedefs were chosen in order to avoid making the Machine manage any data or logic that involves state, but they limit things - template parameters can only be integers and compile-time constants, etc, and it causes some unnecessary templating that also restricts runtime reconfiguration/calibration.
I think a better solution, which still avoids making the Machine manage data accesses, is to replace these typedefs with functions like createCoordMap
and createIODrivers
which, rather than return references to private data members, just create the appropriate object and return that. Then the State can manage the lifetime/access restrictions to the actual data. This should not result in any performance decrease, as all the information used previously is still computable at compile-time. And users of these functions should still be able to compute the type (for storage) via auto
or decltype
.
If Printipi ever makes it into commercial and potentially closed-source machines, it would be nice to at least know which machines are running it. The M115 command could just simply return "ok FIRMWARE_NAME:printipi".
It's often impossible to locate the printhead at the exact cartesian coordinates requested. When this is the case, the MotionPlanner just moves as close as it can linearly to the destination without passing it. This leads to cases where the printhead is located, say, .5mm before the destination when executing just one more step would put it a mere .1mm overshoot. In my opinion, the latter is more ideal.
So, the MotionPlanner behavior should switch from going "as close to the destination without passing it" to "As close to the destination as possible."
As of now, the default HardwareScheduler
is declared in schedulerbase.h (NullSchedulerInterface::HardwareScheduler
). This should be moved into src/drivers/generic/hardwarescheduler.h, and I don't believe the encompassing class (NullSchedulerInterface
) is referenced anywhere, so it can be deleted.
Homing can really be seen as a higher-level function than just stepping motors at precise intervals and watching the endstops. Instead, it should be thought of as a relative linear movement made while watching the endstops (for a delta bot, anyway).
In this case, the CoordMap should have a function called home()
, which delegates the appropriate movements to the State, which in turn directs the LinearAxisSteppers (rather than AxisHomeSteppers). The LinearAxisSteppers behave almost exactly how they usually would, but the movement buffer is shortened significantly and the endstops are checked before each step.
The overview.txt
file in the root of the hierarchy describes < 20% of the source files, and is likely inaccurate by now. It's probably best to just delete this file.
src/drivers/rpi/chronoclockrpi.cpp forces mitpi to init upon executable launch, regardless of build target.
This is due to the initialization of the static mitpi::InitMitpiType _i
variable.
Much of the code is mixed tabs & spaces. With the exception of the Makefile, all tabs should be converted to spaces (width=4).
M109 Sxxx is supposed to set the temperature to xxx and then wait until that temperature is reached before handling any other commands.
I believe we still want to handle a large class of commands - M105 (get extruder temperature), M20-M32 (SD card access), etc, but just not perform any movements until the temperature is reached.
Unfortunately, implementing it in this way will essentially hang serial communications if given M109 and then a G1 command, until the temperature is reached. This makes it so a host like Octoprint cannot track temperature over time during this period.
This may be fixable by intentionally dropping movement commands during this period, and asking for resends. This gives Octoprint an opportunity to periodically inject M105 commands into the stream.
The MotionPlanner works on a step-by-step basis, only ever processing one stepper motor step at a time. However, each step is internally broken down into a series of OutputEvents (set pin x high, then set pin y low)
The result is that if two steps are scheduled at the same time, and each is composed of multiple OutputEvents, then although those OutputEvents will be scheduled for the right times, an OutputEvent set farther in the future might actually be sent to the scheduler before an event set closer to the present.
Consider the following table of 2 steps that each consist of 2 OutputEvents. Both steps are scheduled to occur at t=1.
step#event# | time
1.1 | 1
1.2 | 2
2.1 | 1
2.2 | 2
The current algorithm will pick one of these steps, schedule it completely, and then repeat with the other step. Since they both begin at the same time, either one could be picked. Let's assume step 1 is scheduled first. Then, the order with which the OutputEvents are sent to the scheduler looks like:
Notice that step#1event#2, which occurs at t=2, is scheduled before step#2event#1, which should occur at t=1. Really, the events should be ordered like so (swap the 2nd and 3rd ones):
Although this bug is present everywhere, it only affects the the observable behavior on an unbuffered scheduler. Because an unbuffered scheduler cannot schedule a future event before executing the pending event, scheduling in the first order will cause the 3rd event to occur at t=2 (at best). In the DMA scheduler, which can buffer a few ms in the future, everything works OK.
(mainly for personal reference - expect implementation by Friday)
Something changed in 08bb9ee causes an internal compiler error in gcc-4.7 when using link-time optimization (on x86-64). The bug isn't present on gcc-4.8 or 4.9, and also absent on 4.7 with lto disabled.
Output of make
:
g++-4.7 ../build/debug-g++-4.7/common/common.a ../build/debug-g++-4.7/drivers/drivers.a ../build/debug-g++-4.7/gparse/gparse.a ../build/debug-g++-4.7/rest.a -o ../build/debug-g++-4.7/printipi-rpi-kosselrampsfd -std=c++0x -I/home/colin/proj/3dprinter/printipi-devel/src -Wall -Wextra -Wwrite-strings -Wno-unused-result -Wno-pmf-conversions -DDNO_LOG_M105 -flto -fno-signaling-nans -fno-rounding-math -fno-signed-zeros -freciprocal-math -fno-math-errno -march=native -mtune=native -DMACHINE='rpi::kosselrampsfd' -DMACHINE_PATH='"machines/rpi/kosselrampsfd.h"' -DDTARGET_PLATFORM_rpi -DDTARGET_PLATFORM_LOWER="rpi" -DDUSE_PTHREAD=1 -DPLATFORM_DRIVER_CHRONOCLOCK='"drivers/rpi/chronoclock.h"' -DPLATFORM_DRIVER_HARDWARESCHEDULER='"drivers/rpi/hardwarescheduler.h"' -DPLATFORM_DRIVER_PRIMITIVEIOPIN='"drivers/rpi/primitiveiopin.h"' -DBUILD_TYPE_debug -O0 -ggdb3 -fno-omit-frame-pointer -lrt
lto1: internal compiler error: in lto_output_varpool_node, at lto-cgraph.c:595
Please submit a full bug report,
with preprocessed source if appropriate.
See <file:///usr/share/doc/gcc-4.7/README.Bugs> for instructions.
lto-wrapper: g++-4.7 returned 1 exit status
/usr/bin/ld: lto-wrapper failed
collect2: error: ld returned 1 exit status
Makefile:130: recipe for target '../build/debug-g++-4.7/printipi-rpi-kosselrampsfd' failed
make: *** [../build/debug-g++-4.7/printipi-rpi-kosselrampsfd] Error 1
When printipi is passed the name of a file-like object through the command line, it opens that file using a linux file handle. For portability, it would be better to switch to std file handles (though one must check that those support polling, which is used in gparse).
With linear paths, one must theoretically come to a complete stop at each joint in order to avoid straying from the path. If these joints are turned into curves (essentially an arc of a circle where the start is tangential to the first path and the end is tangential to the second path), then a more gradual acceleration can occur that joins the paths without stopping. This achieves a faster and less jerky print speed at the cost of a small amount of theoretical precision.
Example:
According to uniform circular motion, the radial acceleration required to maintain a circular path is a=v^2/r. Rearranged, v=sqrt(a*r). If our maximum curve radius is set to 1mm (which translates to (1-sqrt(1/2))^2 = .086 mm maximal error when executing a 90-degree turn (maximum distance between a square and a circle inset in the square) and our maximal acceleration is 1000mm/s/s, then the maximum speed we can take that corner at is 31.6 mm/sec. This is far better than decelerating all the way to 0. And if we can tolerate a 0.34mm error at the joints with 1000mm/s/s acceleration, then we can maintain a velocity of 63.2 mm/sec.
As can be seen, the maximal error is proportional to the radius of the turn, which is proportional to the square of the desired velocity. A more sophisticated curve choice could reduce the maximal error.
One nice thing is that implementing this provides all the necessary behavior required to handle G2/G3 arc movements as well.
Currently, an arc is implemented such that its tangential velocity at any time while under constant cartesian acceleration is v=a*t
.
But because of circular motion, there is an acceleration normal to the direction of motion (An = v^2/r
for uniform r). Thus, the total cartesian acceleration experienced at any time is sqrt(An^2 + At^2)
, where At is the tangential acceleration. This value should be kept constant if using a constant acceleration profile.
Furthermore, since there is a limit upon the maximum cartesian acceleration, and An=v^2/r
for constant v, then there is also a limit to the ratio of v^2/r
- we need to ensure that smaller arcs are taken at a slower velocity.
This page has a decent explanation of non-uniform circular motion.
Right now, scheduler.h
includes <pthread>
and uses it to increase the process priority. This should only be done if the platform actually supports pthread. So, this should be checked at compile-time.
Currently, the lowpass filter implementation, which is used to smooth thermister read values, assumes that it is fed 1 sample per second. However, it is really fed data at a variable rate. As such, it is not correct to assume that each sample is 1 second apart (and right now the average thermister sample is actually 3-4 seconds apart).
The current code works with an R/C circuit, like so:
(This is done because the Raspberry Pi lacks an ADC input.)
This implementation has a number of issues:
This thread should be a discussion on how to fix these issues.
(2) can likely be solved by redesigning the R/C circuit. The relationship between resistance and time will likely always be such that t is proportional to e^r, but one could insert a (relatively low) fixed resistance in series with the thermistor to make dt/dr (the timing resolution) wider for low r, and insert a (relatively high) fixed resistance in parallel with the thermistor to make large values of r not take quite as long. This would give something like: t=e^(r_1 + 1/(1/r_2 + 1/r_t )), or, t=ke^[(r_t*r_2)/(r_t+r_2)]
For (1), one could use the on-board DAC at startup and feed successively lower voltages (starting at 3.3v) into the pin until it goes low, to determine its threshold at startup.
For (3), it may be possible to use a hardware interrupt on the pin's falling edge. This is supported by the bcm2835 chip, and the kernel exposes an interface to userland programs, but with up to about 100uS jitter. Given that we have full access to /dev/mem, it may be technically possible to route the interrupt handler directly to our code, but we would have to ensure that we preserve the state of all registers when returning from the interrupt, and that would also require pausing interrupts during our interrupt handler, which could potentially have far-reaching effects (networking, sd card, etc?)
An alternative way to solve (3) is to use DMA in a similar way that we currently are with writing a buffer into the IO pins. Instead we just use DMA to read the pin states into a buffer and look for falling edges every so often. This would further limit DMA throughput, but not by more than 33% (there are currently 3 DMA control blocks per buffer frame, and adding input buffering would require 1 more control block).
Besides this, it may also be worth replacing the R/C circuit with another component. The obvious choice is a serial or i2c ADC chip. These chips can be found for $1-$2 and are easy to use.
Another possibility is to use a 555 timer to turn the thermistor current signal into a digital PWM wave where the duty cycle corresponds to the resistance and then calculate the duty cycle by sampling the wave regularly. Relative to the ADC, this would be less expensive, and actually work with simpler code, but would require a higher component count and a more complex circuit. Additionally, for each fixed resistor or capacitor added, more error is introduced via their tolerances.
I am hesitant to ditch the R/C circuit just for the fact that it works and is virtually free. But an ADC chip would make the circuit easier to understand, and would solve all 3 of the problems listed, whereas it's obvious that the 3 problems are impractical to solve with an R/C circuit.
Printipi is fairly good at catching Unix signals and safely shutting down when it's asked too. However, in the case of the system freezing (or other extreme situations), the I/O might go a dangerously long time before being serviced. The primary source of concern is a runaway heater.
If the program exits crudely or freezes, then the heater pin might get stuck 'high'. One way to counter this issue is by using a Watchdog Timer - if the program goes so long without periodically clearing this timer, then the Raspberry Pi will automatically be reset. Upon reset, all the Pi's pins are floating, so the heater would default to low through its hardware pull-down resistor.
Watchdog timers run as cpu peripherals, so are resistant to freezes virtually everywhere. The bcm2708_wdog kernel module interfaces with the watchdog timer. It may even be possible to access it from userland the same way that DMA is. However, care must be taken to ensure that no other service is clearing our watchdog timer.
Currently, when the raspberry pi is powered off, the heater pin goes into a floating state. If the power supply is still plugged in, and somebody forgot a physical pull-up/down resistor, this can causes a dangerous runaway-heater effect. But since the internal raspberry pi pull resistors retain their state even without power, this can be fixed in software.
So as added security, I propose activating internal pull-up/down resistors for the heater block, and other outputs (like fans) when printipi shuts down.
It would be useful to have actual documentation webpages, which can be automatically generated using tools like Doxygen. These tools also encourage consistency in how documentation is laid out internally (e.g. comments before each function, etc).
I haven't researched the choices, but I imagine there are some documentation generators that integrate well with github.
Occasionally, the printer will stall for a half second or so at path joints after coming to a full stop. The desired behavior is for acceleration to activate immediately after it fully decelerates (no pause).
This behavior occurs even when using NoAcceleration as the AccelerationProfileType.
As of now, the Event object is only ever used as a temporary. And its implementation has a few drawbacks:
The only place Events are actually used is when the State calls motionPlanner.nextStep()
. After that, it immediately performs this line of code:
tupleCallOnIndex(this->ioDrivers, __iterEventOutputSequence(), evt.stepperId(), evt, [this](const OutputEvent &out) { this->scheduler.queue(out); });
_lastMotionPlannedTime = evt.time();
and then lets the event go out of scope. Without Events, this code could look something like:
_lastMotionPlannedTime = motionPlanner.processNextStep([this](const OutputEvent &out) { this->scheduler.queue(out); });
and a whole bunch of complexity could be removed elsewhere (remove AxisStepper.getEvent(), remove AxisStepper._index, let the AxisStepper take ownership of its stepper drivers, and completely remove the Event class).
M.S. via e-mail, reproduced here:
What if we emulated M20-M32 (all of the sdcard functionality) using the filesystem? It would allow the use of the standard octoprint front end, but would allow the firmware to parse, validate, pre-compute acceleration and inverse kinematics, and encode the print as a nice binary stream. All of the IO and trajectory planning overhead should pretty much go away...
The major downside to such a scheme is a somewhat inefficient use of disk space (octoprint already saves things to disk...) but a judicious application of zlib could solve that, at the expense of cpu overhead.
stepForward & stepBackward functions appear to no longer be used with the newer scheduling system. They should be removed for simpler stepper driver implementation.
This function is currently used by the motion planning system whenever it constructs a new path segment, in order for it to join it with the previous one. The information that is really desired is the "completion time of the last path segment". Moving this functionality into the motion planning system fixes visibility issues, simplifies the Scheduler, and improves locality, potentially leading to slightly better performance.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.