-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Not an Issue] Hint on I2S Parallel DMA and shift register stepper driving #4
Comments
Hi @Aggebitter I've actually been wondering the same and I ordered some sn74hc595 couple of hours before you posted this :), I should get them tomorrow. Hopefully i will get some time over christmas to look into it. I think if we want to go that way there is no need for the parallel mode of I2S, serial will be fast enough and use less IOs. From the technical reference and if i did my calculations right we should be able to achieve a bit clock of over 1MHz through I2S (40MHz word clock) for 32 outputs which should be plenty. If we go this way there won't be a need for a GPIO expander as this will free the majority of pins on the ESP32 that we are currently using for step/dir, I just hope it won't be too much hassle getting it to work (especially because i don't have a scope handy) |
I am trying to understand this conversation since I am not really a very hardware type of person. Basically, this I2S thing, it converts a serial input into 8 parallel bits, is my understanding correct? So what @simon-jouet is saying is that potentially, these 8 parallel bits can be used as the STEP and DIR for the four motors? How many pins on the ESP32 will be needed? Looking at the datasheet, it should be able to connect Output Enable to the same pin driving Motor_Enable, which means they will both be driven by the same pin on the ESP32. I don't understand how RCLK, SRCLK, and inverse SRCLK work, though. But I guess they will each need their own pins from the ESP32, right? So five pins on the ESP32 to make this work, replacing the original 9 pins needed to drive 4 stepper drivers? |
@vivian-ng basically the i2s is very similar to SPI, you have some data that is sent serially (and in the case of the ESP32 can also be parallel of configurable width). So for instance you send 32bits of data 1 bit at a time or for instance 4x8 in parallel with 8 outputs. The idea (not mine) is to use the i2s bus with 595 shift registers to allow sending 32bits of data from the ESP32 to the shift registers using only the 3 i2s pins. Using this we should be able to offload the STEP/DIR/EN pins and free up many io pins on the ESP32 while still being fast enough. So only 3 pins on the ESP32 should be needed for i2s, the data output, the bit clock and the word clock (left/right channel selection in i2s). So the shift registers are cascaded (so it becomes equivalent to a 32 bit shift register if you use 4, QH' is chained into the next register's SER). The ESP32 i2s output becomes the first shift register SER input and the i2s bitclock and wordclock become the SRCLK and RCLK of all the registers. The output enable pin, is an input on the shift register and it's just used to enable or disable the output of the shift register, we won't need that for this so we should just tie it to ground so the output is always enabled. Using this technique we should be able to have 32 outputs for only 3 pins used, 15 of them will be used for STEP/DIR/EN for 5 drivers (I guess adding a 5th driver make sense if we have enough outputs) and the 17 remaining can be used for other things (maybe PWM, bang bang ...) or we just populate 2 shift register as it's enough but giving additional 16 pins for ~$0.30 is probably a smarter approach |
I am not very good with how Marlin works, but scanning through the files in Marlin, it seems that For example, in
Then, in
A quick search on Google shows a few Arduino libraries for shift registers. The question is how to implement them for use with stepper drivers. Is Marlin going to send out one pulse to one motor in each "cycle", or will pulses be sent in parallel to all motors being moved in each "cycle"? Meanwhile, I have zero understanding of |
My understanding is that a buffer in memory is set and I2S transfers would
be handled by a DMA channel that will be updating the serial register all
the time.
…On Thu, Dec 13, 2018 at 9:34 AM vivian-ng ***@***.***> wrote:
I am not very good with how Marlin works, but scanning through the files
in Marlin, it seems that stepper.cpp under the modules subdirectory
should be the place to implement the use of shift registers, right? To make
it easily configurable, maybe a #define statement should be used.
For example, in pins_ESP32.h, something like:
#ifdef USE_SHIFT_REGISTER
#define X_STEP S1
#define X_DIR S2
#define Y_STEP S3
#define Y_DIR S4
...
Then, in stepper.cpp,
#ifdef USE_SHIFT_REGISTER
blah blah blah code for handling movement using shift registers
...
A quick search on Google shows a few Arduino libraries for shift
registers. The question is how to implement them for use with stepper
drivers. Is Marlin going to send out one pulse to one motor in each
"cycle", or will pulses be sent in parallel to all motors being moved in
each "cycle"? Meanwhile, I have zero understanding of stepper.cpp so I
guess I will need to try and find time to understand the code if I am ever
going to be able to meaningfully contribute.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#4 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAccyIngfpltffciYEVaYKrLDyFWKCGVks5u4hEOgaJpZM4ZLfcp>
.
|
Yeah @misan is right this will have to be handled quite differently from the normal pins as we want to use I2s' DMA for this to be fast enough. Not too sure if we will have to update the serial continuously (which I know is how the ESP8266 examples work) or if we can actually do one-off word send. The i2s support in the ESP32 is much more mature and part of the esp-idf so I hope things are going to be a bit smoother. Pretty sure the way to do it, is the same way as I did the GPIO expander in the old code (for the first ever board early last year). I want to keep this specific piece of code into the HAL of the ESP32 so we don't pollute Marlin's source code. The way I did it before was to use the MSB of the pin to indicate whether it is a "normal" pin or a pin on the GPIO expander. So pins lower than 128 are normal esp32 pins and pin 128 is pin 0 of the GPIO expander, pin 129 is pin 1 of the GPIO expander... Doing it this way allows us to implement the entire logic in the HAL (https://github.com/MarlinFirmware/Marlin/blob/bugfix-2.0.x/Marlin/src/HAL/HAL_ESP32/fastio_ESP32.h) and we can abstract from Marlin the fact that these pins are not normal pins. I received the 74HC595 yesterday, next step is going to be to solder a prototype board to try this out :) |
Love it! @simon-jouet as just one sn74hc595 would be able to handle 3 steppers with common stepper enable pin. I have been thinking alot on this when I started to look into what error values we actually have in the mechanics matched to what resolution we would need for example making an arc. As shift registers does not change there "parallel state" on outputs if the same serial data it latched. The resolution is just a matter of "time" (As in Sample rate) instead of timing between the steps of each stepper. If we use a round buffer and the update fq is high enough, timing of the steps should make the timing error between steppers lower than the mechanical error. Does the Nyquist Theorem apply here as well ? When I looked in to EPS32 documents I noticed that combined with the I2S standard that 16bit words would take the same time as a 8bit word. And if we use more than 2 shift (aka 4) registers, a inverted word clock on the second half of the shift register bank would enable us to use both channels (Left/Right) for 32 outputs at a generated 192KHz 16bit stereo data in the ringbuffer. An other part of using shift registers and I2S is PWM !! a common RC servo works at 50Hz frame rate and pulse width of 1ms to 2ms. That is 1KHz to 500Hz. Hmm Data latched at 192KHz, the resolution the PWM generated signal for servos will be a lot higher than the mechanics in the servo especially if it uses a varistor for feedback. This concludes me that with four shift registers, all outputs for a RAMPS printer movement could be fixed just by using 4 pins on a ESP32 or even a ESP8266 and that done in DMA reserving the mcu for other nice tasks @simon-jouet could you update me on your proto board as I've also got my 74HC596 and no working duties until January ;-) And I have a scope and 8-channel recording logic analyser |
@simon-jouet @vivian-ng |
Thanks @Aggebitter I've been through this two in the last couple of days been useful to get some information but code-wise I think it will be quite different. The esp8266 example is very rough, it uses a lot of the low level registers to access the i2s peripheral. The esp-idf for the esp32 contains a proper driver so I think (hope) that the implementation will be much more straightforward and easy to follow than the esp8266 exmaples. Regarding the board I'm just going to make a very simple board with 4 shift registers, very similar to the one in the esp8266_reprap repo. Hopefully I can get this done this weekend, designing the PCB will be very very straightforward but I want to try to engrave it using my printer so that might take a bit of time :) Once I get something running I will post it here so you can replicate it if you want (the more we are to try this the better) For the clock of the i2s I won't go in much detail right now but the i2s peripheral works by full 32 bits word at a time even if you only use 16 bits and the maximum rate of it is (if i got it right) 160MHz / 4. So we should be able to achieve a bit clock speed of 40MHz and therefore a word clock of 40MHz/32 = 1.250MHz. What I used as a reference is a bit of a back of the envelope math, but the A4988 requires a minimum of ~4us pulse time (the TMC are faster than this but it was more to check things out). 4us is 250kHz so we've got plenty to spare even if we try to step as fast as we can. Reallistically the 74hc595 won't be able to be clocked at 40MHz but I think it should work out okay even if we go lower than that |
@simon-jouet When you are ready with your prototype schematics can you post it asap ? I've got the time this weekend as well to build a prototype and got the tools for probing and sample the output if you run into problems. I'm more into electromechanics than programming (just novice basic skills and knowledge in C++/C) If I look into the http://www.ti.com/lit/ds/symlink/sn74hc595.pdf I notice that we might not get that high speed serial transfer (rule of thumb from one of my colleagues is at 5V logic max 25MHz serial clock) |
@Aggebitter Here is the basic schematic i2sexpansion.pdf. I've tried to mill the PCB using my printer and it wasn't a great success (and my neighbours probably didn't like me very much) so I just it setup on breadboard for the time being. I've managed to hook up my Saleae clone logic analyzer too and it looks like it's doing the job well enough so that's a good start. (I might etch a board but my 2d printer died :D) For the pins to use on the ESP32 I'm not sure yet and it doesn't matter too much but i'm considering using GPIO17/GPIO16 and GPIO0 so we still have all the rest available (HSPI/VSPI/I2c...) For the 5V operation I think we might be able to get away with it without level shifting, if we want to run at 5V. We already have on the board 5V from either the USB or the buck converter than can be used to supply the shift registers and the 3.3V output of the ESP32 should be high enough for the input of the shift registers. The issue would have been if the shift registers output something to the ESP32 but that's not the case. Now i will have to look a bit more into i2s, from the little time i spent on this yesterday it looks like the bclk and channel selection continue to run even if the transmit buffer is empty. I guess on the esp8266 example that's why they continuously send the same data, so I need to look into that either to keep queuing data, make the buffer cyclic so it resend the same data over and over again or be able to switch off the clocks when the buffer is empty. |
@simon-jouet 74HC595 can work from 2 to 6V http://www.ti.com/lit/ds/symlink/sn74hc595.pdf I do not follow why to use 5V for the shift registers (maybe it was mentioned above and I missed it). A4988 drivers may work at 3.3V too: https://www.pololu.com/product/1182 as that seems to be one of the destination of some of the outputs. Is there other reason for using 5V? (I can see that for powering a MOSFET gate there may be interesting to use 5V, or even higher voltage depending on the model). |
@misan I believe the issue @Aggebitter is mentioning is this table from the datasheet: I think the 74lv595a would solve these issues - http://www.ti.com/lit/ds/symlink/sn74lv595a.pdf - it has more favorable timings for 3.3V But given the actual speeds needed for most printers as @simon-jouet pointed out - the 74hc595 can be used at lower speeds and probably be sufficient - the sn74lv595a is over 10x more expensive in bulk @simon-jouet - again I'd like to offer to help sponsor your work on this in any way, from PCBs to sending you a oscilloscope, or whatever you need - feel free to email me at erik@digistump.com if you're interested |
@ekettenburg I can see now that SN74HC595 might be a problem at any voltage for 40Mhz clock frequency. It is unclear to me what would be the max frequency @3v3 but for sure lower than the 25Mhz @5v. But if we need 40Mhz another serial to parallel register will be needed. |
@simon-jouet, @misan, I have used the common 2N2222 transistor as a level shifter before at high frequencies (rated to handle 250MHz). If we will go for fail safe @25mhz three 2N2222 and four SN74HC595 is a good easy available option with common IC's @simon-jouet, I just starter to solder and wire up a new proto/breakout/wire-wrap board. Think that's easier than design a new PCB and put it into a router (well My goal is to get my chinese 7040 router the run again with ESP32 on Marlin/GRBL and of course my 3D printer) Then I can easy change the pin used without changing definitions in source. I like to keep it simple (stupid) as a goal for designing as all users aren't skilled enough to do things outside default settings later on. Now It's time to wire-wrap some shift registers, I think I will try to bench test SN74HC595 @3.3V and with 2N2222 @5v. |
@misan - since the 74lv595 and 74hv595 are completely interchangeable and direct drop in replacements I guess it probably doesn't matter as long as the clock can be set in configuration - for instance looking at one of my printers (coreXY belt driven RAMPS printer) even at 500 mm/sec the step speed would be 25,000 steps/sec or 50,000 pin changes/sec (on and then off) or 50khz Now my math might be totally wrong here but it takes more or less 40 ticks of the clock per pin change, and at 2V the max clock 5Mhz (worst case - no value given for 3.3V) - so at 2V you could get 125khz in pin changes (across 32 bits) or 75khz in steps? still far beyond your average printers need for even crazy fast speeds. The only printer I've worked on that wouldn't be satisfied by 5Mhz clock would be a heavily geared very large format printer. Unless my math is totally wrong.... Also worth noting that Marlin running on a standard ATMEGA2560 board starts using double and quad steps above 10khz and 20khz respectively - so if say even 75khz was supported by an ESP32 and a shift register it would be far beyond ATMEGA2560 capabilities - and smoothieboard/duet/etc are in the 100-200khz range (https://reprap.org/wiki/Step_rates). If say 10MHZ is acceptable clock at 3.3V (data sheet doesn't say but based on the jump from 2V to 4.5V) that'd be 150khz max steps? Again my math might be wrong - but if it is at least close then I think there should be no concern about using the SN74HC595 other than allowing the clock frequency to be set low enough for it to be reliable across temperatures at 3.3v (whether that's 5MHZ the safe 2V value or something like 10MHZ I don't know) because the step rates will be plenty high |
@ekettenburg, I think You are right about the rates used. But in that case we are talking about real time functions, What I'm gaining at is that with high resolution aka word clock on the shift register, we will get resolution higher than the mechanics and therefore can leave the real time domain ;-) This without using any hard interrupts. The firmware can still use timer interrupts internally to just feed the DMA and by doing so we will free up a lot of cpu cycles. If the skill code writers get this to work in HAL well the step for getting it to work on RPI is short. And we are skipping the Real Time co MCU's on Beagle Bone Black. When I have looked into some of the hardware used with linux CNC and USB Stepper controllers, This has been one approach and USB is far away from real-time |
@simon-jouet Not to fill you totally with code examples but this one I will reuse and modify for bench testing the shift register @3.3V and @5v |
@simon-jouet I did some benching on a single SN74HC595 at 3.3V (will build one with four soon). Found out that ESP32 API for I2S has no way to get more than a int in samplerate. But using the earlier ESP32AMRadioTransmitter code there was some register bit setting that got me to around 25MHz Bit clock. My modded bench code is here: https://github.com/Aggebitter/ESP32ShiftRegisterBenchTestI2S |
Hi guys, sorry for the radio silence over that last few days, been busy with work :). @Aggebitter Well done with that, I'm looking into getting the I2s peripheral to behave like I want at the moment. What i'm trying to do is to send one word (32bits) at a time an stop both clocks so we only push and clock when there is data to be sent. The thing is that the documentation for the I2S is not super great, but I guess (hope) I'm making some progress.... If i get anything kind of working I will post it here. Once we get the I2s working as it should standalone, adding it to the HAL should be very straightforward. @ekettenburg Thanks for the support, that's great to hear that people are keen on this. I've been working on and off (mostly off) on the ESP32 Marlin support for about a year and a bit now and it's finally getting some traction so that a good thing! For the support what I really need is free time but I guess that's a bit harder to get :D. I think for now I'm good we can test and prototype the hardware and software relatively easily. Once we get the I2S working I will design the R2 of the board, which will be quite a significant redesign. At this point I think it might be nice to get some people with more routing /PCB layout experience than I have and maybe get a few people to chip in to get a small batch of boards made. The R1 prototype was useful to make and it's now the main board in one of my printers, but hand soldering the boards takes a while that's why only 5 people in the world have one :D |
Okay progress (surprisingly faster than expected) I finally got it sending one word at a time. The code definitely needs to be cleaned up (I think I won't use the The remaining issue in this is that the WS clock should go high at the end of the word transmission to latch the shift registers. I tried to add some delay to the WS signal but from the looks of it it's limited to 3 cycles. One approach would be to send the right channel first (so it's high) and be able to set the idle state of the WS line to high. I tried to enable the pull up on the pin bug that didn't work and I'm not finding a way to configure the idle state of the WS signal in the ESP32 TRM. Here is the code #include <Arduino.h>
#include "driver/i2s.h"
#include "rom/lldesc.h"
static volatile lldesc_t dmaDesc[1];
uint32_t data = 0xdeadbeef;
void setup() {
Serial.begin(115200);
static const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = 44100,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = 0, // default interrupt priority
.dma_buf_count = 8,
.dma_buf_len = 8,
.use_apll = false
};
static const i2s_pin_config_t pin_config = {
.bck_io_num = 26,
.ws_io_num = 25,
.data_out_num = 22,
.data_in_num = I2S_PIN_NO_CHANGE
};
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); //install and start i2s driver
i2s_set_pin(I2S_NUM_0, &pin_config);
i2s_stop(I2S_NUM_0);
//
// gpio_pullup_en((gpio_num_t)pin_config.ws_io_num);
// Use normal clock format, (WS is aligned with the last bit)
I2S0.conf.tx_msb_shift = 0;
I2S0.conf.rx_msb_shift = 0;
// I2S0.conf.tx_right_first = 1;
// auto stop when the fifo is empty
I2S0.conf1.tx_stop_en = 1;
// Disable TX interrupts
I2S0.int_ena.out_eof = 0;
I2S0.int_ena.out_dscr_err = 0;
// Create the dma buffer
volatile lldesc_t *dma = dmaDesc;
dma->owner = 1; // Owner is the DMA controller
dma->eof = 1; // Eof of file, last item in the linked list
dma->length = 4; // 4 bytes of data in the buffer, 32 bits
dma->size = 4;
dma->offset = 0;
dma->sosf = 0;
dma->buf = (uint8_t *)&data;
dma->empty = 0;
I2S0.out_link.addr = ((uint32_t)(&dmaDesc)) & I2S_OUTLINK_ADDR;
}
void loop() {
// Send the data
// Do we actually need to reset the full transmitter module?
// TODO: try out_link restart?
I2S0.conf.tx_reset = 1;
I2S0.conf.tx_reset = 0;
I2S0.out_link.start = 1;
I2S0.conf.tx_start = 1;
delay(100);
} EDIT: Using the gpio matrix we can invert the logic but it's still not right, as the data will be latched at the beginning and at the end of the transmission... |
@simon-jouet good job! I have some ideas and you are much more skilled in programing than I. so I will keep on feeding you with ideas About the word clock. About one word transfer: About delaying a direction pin: Some goodies: |
More progress on this end, and it's getting about close enough to be added to the HAL! So the plan for now is to keep it quite simple and use I2S pretty much as a GPIO expander simply allowing to toggle extra outputs. In the long term it would be very nice to use the stream of data to do high frequency and highly accurate stepping but this will require quite a rework of the stepper interrupt (well we will have to replace it pretty much entirely). The good thing though is that it will be the exact same hardware so once we have something working we can improve it over time. I spent some time going through the datasheet of the ESP32 and I think I found one of the cleanest way to use the I2s peripheral as a GPIO expander. The i2s peripheral allows for one of the 2 channel to be constant base on a register, therefore we can toggle outputs simply by writing to that register, no need for circular buffers, DMA transactions and interrupts. The only downside is that only one channel contains data so the effective bitrate is half the clock speed but that really shouldn't be an issue. Current code looks like this and it looks like it's working very well. I still need to bring the #include <Arduino.h>
#include "driver/i2s.h"
#include "rom/lldesc.h"
inline static void gpio_matrix_out_check(uint32_t gpio, uint32_t signal_idx, bool out_inv, bool oen_inv)
{
//if pin = -1, do not need to configure
if (gpio != -1) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO);
gpio_set_direction((gpio_num_t)gpio, (gpio_mode_t)GPIO_MODE_DEF_OUTPUT);
gpio_matrix_out(gpio, signal_idx, out_inv, oen_inv);
}
}
void setup() {
// Serial.begin(115200);
static const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = 44100,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ALL_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = 0, // default interrupt priority
.dma_buf_count = 8,
.dma_buf_len = 8,
.use_apll = false
};
// install and start i2s driver
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
// i2s_set_pin
gpio_matrix_out_check(22, I2S0O_DATA_OUT23_IDX, 0, 0);
gpio_matrix_out_check(25, I2S0O_WS_OUT_IDX, 0, 0);
gpio_matrix_out_check(26, I2S0O_BCK_OUT_IDX, 0, 0);
// clock configuration
// fi2s = fpll / (N + b/a)
// N >= 2 REG_CLKM_DIV_NUM
// b I2S_CLKM_DIV_B
// a I2S_CLKM_DIV_A
// fbck = fi2s/M
// M >= 2 I2S_TX_BCK_DIV_NUM
I2S0.clkm_conf.clka_en = 0; // Use PLL/2 as reference
I2S0.clkm_conf.clkm_div_num = 4; // reset value of 4
I2S0.clkm_conf.clkm_div_a = 1; // 0 at reset, what about divide by 0?
I2S0.clkm_conf.clkm_div_b = 0; // 0 at reset
//
I2S0.fifo_conf.tx_fifo_mod = 3; // 32 bits single channel data
I2S0.conf_chan.tx_chan_mod = 3; //
I2S0.sample_rate_conf.tx_bits_mod = 32;
I2S0.conf_single_data = 0;
// Use normal clock format, (WS is aligned with the last bit)
I2S0.conf.tx_msb_shift = 0;
I2S0.conf.rx_msb_shift = 0;
// Disable TX interrupts
I2S0.int_ena.out_eof = 0;
I2S0.int_ena.out_dscr_err = 0;
}
void loop() {
I2S0.conf_single_data++;
} |
@simon-jouet Can writing to the registers be handled by the stepper interrupt routine in Marlin ? I was thinking complete the other way around. By giving FreeRTOS a Core0 task with decent priority just to feed the DMA ring buffer (The bigger size of buffer the lower priority). The stepper interrupt would just write to that buffer instead of pulling GPIO's. As we can toggle the stepper pins at more than 400kHz the limit is in what time resolution we would need. And the time resolution in not an issue. As an example. If we use a GT5 pulley with 20 tooths and 256 microsteps. The step rate will be 256 000 steps/sec and give 100mm/sec on a 200 step/rev stepper motor. If we put in the numbers that I've got (probably not maxed out yet) on the skift register that is 416 000 steps/sec and with the same hardware setup it gives us 162.5 mm/sec in movement So what if we "miss" aka linear jitter, a step in the time domain. One micro step is 0,000390625 mm that's maximum error at all speeds for all axis! At slower rates this linear jitter will reduce more but still be there. Conclusion: We are way below the mechanical error in the stepper motor itself. The size of the ring buffer is only a matter when the stepper movement has to interact with other interrupts as when homing and probing. But let us look at the linear jitter again. With a ring buffer at 1024 we will get a limit interrupt somewhere in the +0.4 mm range. That sounds like a bad numbers. Well that was at 162.5mm/sec probing ;-). At feed rate 60mm/min we will move 1mm/s. The ring buffer will be have a round trip of less than 0.0025 sec (ring buffer size / word clock), that is again much lower than the stepper motor error. My thinking with the ring buffer is that we would not need any semaphores or other task write handlers for that buffer in FreeRTOS. Just do a write as it is a buffer. Add a new data flag to the function and the ring buffer could loop the same data as long as no new data has arrived. No interrupts just a loop that feeds the DMA with the same data as long as it has not change. And if changed update the ring buffer and keep filling with the latest word. There is a bug in the sample rate settings in ESP32 idf. It uses an int! This code made my bench test go at a much higher sampling rate: // Dirty modded for bench testing Shift registers by Agge
#include <soc/rtc.h>
#include "driver/i2s.h"
static const i2s_port_t i2s_num = (i2s_port_t)I2S_NUM_0; // i2s port number
//static i2s_config_t i2s_config;
static const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX ),
.sample_rate = 1000000, //not really used
.bits_per_sample = (i2s_bits_per_sample_t)I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2,
.dma_buf_len = 64 //Small buffer
};
static const i2s_pin_config_t pin_config = {
.bck_io_num = 26, //this is BCK pin
.ws_io_num = 25, // this is LRCK pin
.data_out_num = 22, // this is DATA output pin
.data_in_num = -1 //Not used
};
void setup()
{
Serial.begin(115200);
rtc_clk_cpu_freq_set(RTC_CPU_FREQ_240M); //highest cpu frequency
i2s_driver_install(i2s_num, &i2s_config, 0, NULL); //start i2s driver
i2s_set_pin(i2s_num, &pin_config); //use external shift register
i2s_set_sample_rates(i2s_num, 1000000); //dummy sample rate, since the function fails at high values
// the API states that this value is an int!!! Agge
//this is the hack that enables the highest sampling rate possible ~13MHz, have fun
// Gives BCK @ ~25Mhz , WCK @ ~833KHz and Step Rate @ ~416KHz, Can we go higher ?
SET_PERI_REG_BITS(I2S_CLKM_CONF_REG(0), I2S_CLKM_DIV_A_V, 1, I2S_CLKM_DIV_A_S);
SET_PERI_REG_BITS(I2S_CLKM_CONF_REG(0), I2S_CLKM_DIV_B_V, 1, I2S_CLKM_DIV_B_S);
SET_PERI_REG_BITS(I2S_CLKM_CONF_REG(0), I2S_CLKM_DIV_NUM_V, 2, I2S_CLKM_DIV_NUM_S);
SET_PERI_REG_BITS(I2S_SAMPLE_RATE_CONF_REG(0), I2S_TX_BCK_DIV_NUM_V, 2, I2S_TX_BCK_DIV_NUM_S);
} Have a nice Xmas |
Just a few questions to clarify. Also, are the outputs from the shift register going to be usable for LCD? To allow the ESP32 to hook up to a LCD2004 or LCD12864. |
@vivian-ng If @simon-jouet get this working all outputs could be handled so making a parallel interfaced LCD working would work as long that you don't read any data from it. |
So for the first point yes writing the registers should work from the interrupt, it's just writing to a memory location so there shouldn't be anything special about it. Regarding the CPU cycles there can't be anything that's much more efficient that the way I'm proposing here. It's a single memory write and that's all, the transfer of the data to the shift register is then entirely managed by the I2S peripheral. Even if you want to use DMA for larger transfer you also need multiple registers to be modified.
Yeah that's in line with what I was talking about previously of using the I2S stream to actually queue operations. This approach would significantly reduce the number of interrupts on the ESP32 and might potentially give a higher stepping rate. The reason why I'm not doing it this way to start with, is that's it's significantly more effort as it requires a very big rewrite of the stepper ISR (to actually not be an ISR but just queue the operations in a buffer), Anyway that's clearly something I'm interested in doing at a later point. If you want to see how it could done have a look at the LCD display examples it's very similar, took me some time to understand how the DMA controller works on the ESP32 but I think I'm getting it now :), just hope I will remember when I try to do that!
So the issue here is that the ESP32 idf I2S driver is very much designed to stream audio (which is the main purpose of I2S) so you give a sample rate and they calculate the prescalers for the clock. In the current code, I've extracted only the necessary from the i2s driver and I set the clock manually. You can look at 12.3 (page 305) of the technical reference manual and you will see the equation used to calculate the clock speed. It's
|
@simon-jouet I've got a real Saleae pro 8 channel on USB3 so I can sample at high speeds. What state are your code in at the moment? Would love to try it out. Cheers // Agge |
@Aggebitter Super slow reply! So I worked quite a bit on that over the holidays but I was travelling and on my laptop so the code is mostly in place for an experiment but I haven't tried it yet on hardware. I'm back to work now so I will try this out probably this weekend. Regardless on how the testing goes I will probably create a new branch on my Marlin repo with the code so you can check it out if you are interested. I will post here once that's done |
Hey, Some very good success here :), I just flashed my ESP32 with the Marlin edited for I2s and it does work! Not too sure if it works well yet but that's a detail... I will push the code soonish Here is the video of my dodgey setup and my stepper stepping over I2S |
Here is the code ! https://github.com/simon-jouet/Marlin/commits/esp32-i2s |
I just pushed a bit more code and I got my first 3D print through the i2s port :). I had to increase the minimum_stepper_pulse for now as I can't really run the i2s at fullspeed with my breadboard setup but it doesn't really matter. Also at full speed I can't really debug anything at my saleae clone can't follow up, I will have to invest in a scope at some point (any suggestions? I'm thinking of a Rigol MSO5000 but the price is a bit steep especially just for hobby work... Next step will be to design the R2 board with a better layout and shorten the tracks as much as possible to reduce the noise. After that I might start looking at replacing the stepper interrupt with an I2S stream through DMA that would be great (but probably not straightforward at all) |
@simon-jouet - that looks great! I like the way you've handled the pin numbers (127 + shift register pin) rather than adding additional functions or such, since that keeps it much more compatible with the rest of Marlin. For the logic analyzer how about something like this: https://www.dreamsourcelab.com/product/dslogic-plus/ I use one similar to that and the much cheaper Rigol DS1102E and between the two I can do just about anything I need to (plus a good bench supply and multimeter of course) short of RF work. My previous offer still stands - if you'd like me to have something like the DSLogic Plus sent to you so you can continue your work on this I'd be happy to provide that. (erik@digistump.com) I'm going to have 5 or 10 printer controller boards made soon for this setup with two shift registers - happy to provide a few of those as well when I do. And share the files of course. |
Hi @ekettenburg Thanks, yeah the approach with the MSB set is a bit of a hack but it allows us to keep everything within the ESP32 HAL which is perfect in the short term (and is much more likely to get merged into upstream Marlin). Until someone else gives it a shot I will keep it here and I will open a new MR on Marlin at some point soon to add support for this, the improved ADC (which I need to cleanup) and maybe few more things. The DSLogic Plus does look very nice indeed and it's reasonably priced. For this work it should be good enough. I just had a quick look on aliexpress and they have some that are about ~£50 do you know if they are proper ones or cheap clones? Thanks for the offer and I might take you on it - just that asking for stuff when I just do that as a hobby somehow doesn't feel right :) For the printer controller board, I'm revising the R2 but if you are planning to get one sorted too it might be worth splitting resources so we don't end up with many different but similar boards and each time we need to revise the hardware we are all having to redesign it. (Plus having less hardware design on my end will give me more time to focus on the software :D). I started modifying the schematic for the R2 and I will push that soon. Just as an FYI I would recommend sticking 4 shift registers and not 2 for a few reasons:
|
@simon-jouet I am also interested to incorporate 74HC595s into the board I am working on right now. But first, I need to get the current version to work. 😀 |
@simon-jouet More than happy to supply the DSLogic even if it's just a hobby, my company @digistump has benefited greatly from (and contributed to as well) open source software and hardware so I like to do what I can to support people working on code that we may find useful. The Chinese sold ones are what I have used and they have worked fine (DSLogic is based in Shenzhen so they very well may be the real deal, or from the same factory) - I can get one shipped DHL/UPS to you - just send me an email with your address. The board I'm designing to test out just using the ESP32/shift registers is aimed at testing to see if we can eventually replace our more traditional ATMEGA2560+ESP8266 board with it for a printer we are working on. That's why we only plan to have two shift registers - it meets our needs. That said I will make an easy way to add two more as no doubt 4 is better than 2 for hobbyist use or for power users if we end up putting it into a printer. Have you pushed the improved ADC code anywhere? I'm interested to see how you've approached that. I've tested a few different approaches from general calibration formulas to reading the calibration value on newer chips to manual calibration - all have worked "well enough" but the later of course isn't usable for production boards. |
@vivian-ng Yeah this should work for PWM without too much problems. It kind of depend how the shift registers are used. Like i mentionned in my previous message, in the long run my goal is to use the ESP32's DMA to actually queue stepper operation. That would be perfect for stepping but it's not great for closed loop control like the PWM for the heater/bed but it would be fine for the part cooling fan. What I have in mind is if 4 shift registers are used is to use the first two with DMA control so it's very fast and very accurate but computed beforehand and use the other 2 as a GPIO expander so we can actually control more outputs using the static channel system that I'm using now. For now I think if you want to control the bed/heater just use the ESP32's GPIO it will make things simpler and a bit more future proof and with the steppers on the shift registers you should have quite a few spare pins. If you use the right mosfets you shouldn't need to feed them 5V quite a few mosfets can have a very low Ron even with a 3.3V gate voltage. @ekettenburg Thanks for that I will write you an email tomorrow probably. In the short term my answer to replace your mega2560+esp8266 board with an esp32 is probably yes, I've been running one of my printer with an ESP32 for about 6 months now and it's been working well (even though for testing I have very few Marlin options enabled so things might get a bit more dodgy with more options enabled). With a few more people working on software and hardware I think we can end up with a very very nice system :) I haven't pushed the ADC code anywhere just yet, it's quite nasty at the moment but I can post a diff at some point when i'm on the machine with the code. My current approach includes a few workarounds but the result is quite satisfactory except at low temps (below 30degrees) but I don't care much about that for now.
Using this approach I've been able to measure the temperature between 30 and 300 (upper bound on the thermistor table I use). I haven't really checked how accurately the readings are (I don't really have an easy way to do that that's accurate enough) but it has been good enough to print in PLA and PETG (I couldn't really print in PETG before with the temperature code that I pushed to Marlin) |
@simon-jouet ... Just love it! or at last! Is there a way to get one of your R2 prototype boards when you have the design ready? As an hobbyist army employee both time and finances are mostly going to the family ;-) Will of course pay for the making and postage. |
@Aggebitter sorry for the slow reply! For the time being I'm stepping a bit off in designing the R2 prototype as both @vivian-ng and @ekettenburg are working on one, I think it's better for me to focus on the software. I've just pushed a new branch esp32-i2s-dma that adds support for proper step scheduling over I2s using the DMA controller and removing the constant calls to the stepper isr. I think this will provide a huge performance boost! I've opened a MR for discussion here MarlinFirmware/Marlin#12959 Anyway I'm super happy with the result, just finished my first print using that. The next step might be to stop using bresenham and schedule each step independently but my lack of knowledge in the current code will probably make this a bit tricky :). After that I think I will remove the temperature ISR too and just use a higher priority task. At this point there won't be any more long interrupts and we can start playing a bit with the ESP32's capabilities :) |
No problem @simon-jouet , I't was more to speed up testing, With I2S up and running we have to redesign the GPIO mapping for a more common 3D-printer setup and open up future new hardware as TMC2130 with SPI. This also makes a lot off "standard" extensions as display, PWM and servo control a possibility. All "left" over GPIO on the ESP32 should as much as possible be used as INPUT, As you now has fixed 32 GPO's ;-) |
Hi, great job! I was thinking if the esp32 needs more gpIos for normal stuff couldn't you use mcp23s17 spi or mcp23017 i2c? So you would have additional 16+ GPIO. Trinamic has now some TMC BOB boards including all needed parts, like the TMC 5160-BOB or 2160-BOB which would make it quite modular. |
@Aggebitter yeah the mapping needs to be redefined as it also opens up the second SPI port which could potentially be useful (especially for debugging as it's the same as the JTAG port). I think @ejtagle is working on support for the ILI9341 tft displays, I haven't seen any code for that just yet but when something is available I will have a look, it would be a nice addition. Regarding the 32 outputs ports I think there might be some limitation on what they can be used for especially if we use the DMA channel to control them but I think in most cases it will work (just not sure for things like CS lines for TMCs). I was thinking last night that it might be possible use i2s for inputs too, and therefore have 32 outputs and 32 inputs both at the same frequency. I might have a look when I get some free time. @3d-gussner The first ever board I made used a MCP23S17 https://user-images.githubusercontent.com/832495/32343083-53fb4bd0-bffa-11e7-8323-661b429bfd14.png but it wasn't that great and any GPIO expander will be too slow to actually do stepping (which the i2s stream solves + more). The R1 of the board used a SX1509 as a GPIO expander which is I think superior (for instance it does support PWM) but I never added support for it in Marlin and again it's not fast enough for stepping and SPI chip select but could be useful for endstops. Although with the stepper pins now freed from the ESP32 GPIOs there is much more room to do stuff without any GPIO expander. The TMC breakout boards are quite nice though quite expensive (£15 a piece) I think for the moment I will stick with my TMC2130 with a pololu format to do the tests. At the moment there is marlin+esp32 in the main repository though not too sure how many people are running that (properly not more than a handful) and @luc-github forked my repo to have his esp3d interface supported directly in Marlin, I haven't had a time to give it a shot it yet though. @grownseed also has made some work to add Websocket + UI interface which is pending in a MR on Marlin. |
@simon-jouet the extra I2S GPO we got could be used as "slow" serial interfaces and "slow" PWM. They might work as CS on slow SPI devices. I would not use any I2C extender for endstops as they are as time resolution critical as the stepper movements. So endstops should be interrupt driven. I2S as input! we are thinking the same again... but can ESP32 do full duplex I2S ? |
I2C I/O extenders can cause an interrupt on pin change though.
For example http://ww1.microchip.com/downloads/en/DeviceDoc/20090C.pdf
The datasheet gives the number of 200usec for interrupt latency (not sure if too high).
|
The problem with that is mostly the latency, if we use DMA to control the steppers then we will start filling it with data as soon as it's empty. In the current HAL I've configured this to 8 buffers and each buffer takes 4092us to empty so between the time you fill the buffer and it's send there might be up to 7*4092us (~28ms) of delay. For some stuff it doesn't really matter, but for the CS lines it will probably be an issue because we will have to wait that long before the ESP can start doing the transfers.
Like @misan said most GPIO expanders have an interrupt line so the steppers can be paused until we figure out what caused the interrupt. The problem with that like any other GPIO expander is to get the integration nicely in Marlin.
From the i2s driver in the esp-idf and the TRM it does look like it, the sample rate for the input will be far too fast (as fast as the output) but we can just ignore some samples. I should get some 74HC165 soon to test that out. I think it's less important to get that working than the steppers but it might be good to have nonetheless.
The SX1509 looks to have much better timing characteristics. But even 200us shouldn't be much an issue I think, I should double check but with a normal homing rate that might be max a step or two |
Is there a schematic any where? I like to work on PCB. |
Hey @simon-jouet , First of all - great work! I wanted to ask if R2 board on your branch already have these changes related to new shift-registers? Thanks! |
Hi @kkszysiu, Thanks, glad people are starting to use it, shows that the many days spend in the soft and hardware are finally being worthwhile. Yeah the R2 branch has been updated last weekend iirc with more fixes and Tweaks and it contains the shift registers for the i2s stream. The only step left is to do the pcb design, got some feedback from a friend but I find pcb routing very painful so I haven't worked on it much yet... |
@simon-jouet , looking at the PRs I see somebody already prepared small design of PCB: #6 I do not know how valid it is, I'm a pretty newbie in case of hardware. Trying to get more experience thanks to projects as this one :) |
I think i2s stream is a brilliant idea, and really the way to go - to get the hands on some testboards i've made the routing from PR #6 without major modifications in the schematic. Should be pretty valid - if you have the parts. For my flavour there aren't enough FET outputs - for 2 Extruders there should be at least Heater1, Heater2, Bed, Part-Fan, E0-Fan, E1-Fan, Controller-Fan - best used from the shift registers. I might also adapt the parts to my inventory - i'm better hand placing 0805 R/C instead of 0603. A Header to replace the TPS560430 by the ultra cheap chinese LM2596 Modules wont hurt. And a display port would be the icing on the cake ;) Is the pin-mapping already fixed? Shuffling the shift-register pins (and splitting J6) could reduce some pressure from the routing. |
@Ringel Just ordered some PCB of your design and will test it out on my holiday. I'm thinking RAMPS 1.4! |
@Aggebitter 5V tolerance might be not needed, if we drive the end stops with 3V3. To debounce it even better you can rise the c9-c11 value. I've started r3-branch for some more improvements: The last 2 shift registers are supplied with 5V to have enough voltage for some logic-level fets. By using 5V shift-register outputs for all fets, the remaining io's are enough for a REPRAP_DISCOUNT_SMART_CONTROLLER. There are some more fan fet's and wild shuffled pinout for cleaner routing. I'll order R2+R3 the next days to test this out. |
I totally agree with that but with the ESP it's a bit of a tricky one. If you use the GPIO pins of the ESP32 then it will work but you will very quickly run out, and if you want to do things like SPI,I2C then you won't have enough GPIO to do both, so the current version was the bare minimum to get stuff working. The shift registers could be an option but then you enter a different problem. If you use the I2S stream like for the steppers then the issue is the latency, when you queue the operations for the steps it's not time critical but do to closed loop PID control it will likely be an issue. It might be possibly to tweak/improve Marlin to do the PWM over I2S (I don't think it's too complicated) but for the heater/bed it might be trickier (I haven't tried, it might be good enough or the current PID might be slow enough that the latency doesn't matter). The other alternative which is a bit of a mix between the two is the way the I2S is currently configured, the lower 16 bits are part of the I2S stream and can be used to schedule the steps while the higher 16bits are based on a register so you can just set a value and it will show on the pins shortly after. This allow to get a very rough GPO expander, it works fine but it's not very accurate timing wise. The other issue with that is as soon as the 16 higher bits are used with the register instead of the DMA stream you limit the number of drivers you can run.
There are few IO ports that are not used for anything so you can just use that, or use the second SPI port if it's not used or even the I2S port if you're not planning on using it. If we could move the FET outputs to the I2S then we would free 3 pins which would be nice (but then back to point 1).
I think I tried to run an SSD1306 screen at some point over I2S and it was working fine. My original idea was to use something like an ILI9341 or something similar over SPI to have a fairly large TFT screen. But i'm a bit reconsidering that, with WiFi in the ESP32 it might be just better to use your smartphone as a screen... You will get a better resolution better control and less wires :)
The pin mapping can be changed without much issues, the ESP32 can be rerouted pretty much however we want so we can change pins to make things simpler. The only thing to consider are the pullups and pulldowns on the pins that can limit what we can attach to it (for instance we don't want a pullup for the heaters)
The first board I made quite a while ago before this was for ramps, the problem is that there is so much difference in pin capabilities that half your RAMPs pins won't be connected to anything. Considering how basic the RAMPS board is I decided to just have an all-in-one and designed this controller. (I can probably send a photo of the old board if you're curious)
I haven't tried but I'm (pretty) sure you can't use the I2S for the CS pins. The ESP32 has a built-in SPI controller with DMA that will assert the CS line and do the transfers. Like I said in the first point with I2S you have some latency and very rough timing when you use the fixed channel which very likely will end up with the CS lines not being asserted when it's needed (not even close). The option I had in mind which I haven't tried would be to use a demux on the CS lines, reading the ESP32 datasheet it seems possible and it would allow 8 CS lines with only 3 pins. If i've missed anything let me know :) |
Nice to be in the lopp again. I am a nog fan of open hardware. And ease of use and integration. The ramps kits are dirty sheap. And if a controller card ordered from a pcb manufacturer plus components gets down to the pricing of a mega the esp32 hardware Will spread fast. Regardin spi cs. See it as bit bang spi! The speed would not be an issue. //Agge (on a Swedish mobile, 3 large beers, and someware in Netherlands doing afterwork. Just hate spell checking) |
I have thinking a lot about this I2S to a shift register. The time resolution should be good and look at this driver for RGB displays:
https://github.com/mrfaptastic/ESP32-RGB64x32MatrixPanel-I2S-DMA
https://github.com/ESP32DE/I2S_parallel_example_drive_a_64x32_display
from thread:
https://esp32.com/viewtopic.php?f=17&t=3188
The text was updated successfully, but these errors were encountered: