Open J-Dunn opened 8 years ago
Development environment for nucleo 411: [http://www.openstm32.org/HomePage]
@J-Dunn I did some documentation for this code, but it is not finished yet. You can find it on this link: https://github.com/IRNAS/grbl_stm32 Mainly the programming environment is based on http://www.openstm32.org/HomePage, like @Andypos said.
@VojislavM Nice work on the documentation, saves me having to do it :)
@VojislavM thanks, nice concise overview. One small typo occurs twice : different .
@langwadt It may be worth copying that readme.md here. Just to have some statement about with this is for.
@VojislavM if You test this version of H_Bot ?
@Andypos I haven't tested on H_Bot, I have tasted it on PreciseXY. You have all documentation on github repository page.
@J-Dunn thanks for noticing "differente", I fixed it
I have decrypted most of the config_steppers() fn. and corrected some minor errors. : OC limit of 0xF is 6A , not 3.5 , also max current settings were not calculated quite right.
Some comment to explain the missing byte of the config param would be nice. There's some stuff like slew-rate, TOFF , TFAST which are worth fiddling with. It's unclear if this is just asserting the defaults or is tweaking these values.
void config_steppers()
{
#ifdef STANDARD_GRBL
;
#else
GPIO_SetBits(STP_RST);
spibytes(0xa8,0xa8,0xa8); // disable : HiZ state; configure L6474 while in HiZ mode
spibytes(0xd0,0xd0,0xd0); // L6474_GET_STATUS=0xd0; dump two ret bytes for each board; this is to clear error flag
spibytes(0x00,0x00,0x00); // flush out 1st status return byte for each board; drop result
spibytes(0x00,0x00,0x00); // flush out 2nd status return byte for each board; drop result
spibytes(0x18,0x18,0x18); // L6474_SET_PARAM | L6474_CONFIG
spibytes(0x2c,0x2c,0x2c); // ???
spibytes(0x88,0x88,0x88); // L6474_CONFIG_OC_SD_ENABLE | L6474_CONFIG_INT_16MHZ_OSCOUT_2MHZ ; = def
spibytes(0x16,0x16,0x16); // L6474_SET_PARAM | L6474_STEP_MODE
// spibytes(0x88|4,0x88|4,0x88|4); // 1/16 step NO_SYNC=F8 (b7 tied up so =0x88); divider MODE is coded in 3 LSBs
spibytes(0x88|1,0x88|1,0x88|1); // 1/2 step
spibytes(0x09,0x09,0x09); // L6474_SET_PARAM | L6474_TVAL ie max current
// spibytes(floor(ZCURRENT/31),floor(XCURRENT/31),floor(YCURRENT/31)); // 31mA per
spibytes((ZCURRENT*32/1000),(XCURRENT*32/1000),(YCURRENT*32/1000)); // 31.25mA per bit
spibytes(0x13,0x13,0x13); // L6474_SET_PARAM | L6474_OCD_TH
spibytes(0xE,0xE,0xE); // overcurrent threshold = L6474_OCD_TH_5625mA // def 0x8 = 3.375A
#endif
}
@J-Dunn yeh that setup was quickly put together to get something working without much thought. afair the config was changed to get a higher slewrate expecting it would lower the temperature of the chip, might as well leave it at default. I think I'll soon make a more "formal" setup for the drivers, I've just got a set of powerstep01 boards running so that'll probably be added too
These L6474 overheat badly with the default config and won't run to spec on the demo board ( which they should with the area and thickness of the copper ). I did get some improvement with other settings and still needed forced ventilation with a 40 mm fan.
There's a whole stack of things getting set by L6474_CONFIG param, IMO it's better to read-modify-write if you need to tweak something in a readable way.
I have a file of named constants that I use to make all this legible. I could post that if you'd like to include it.
@J-Dunn Did you get much improvement in power dissipation? I'd like to see what setup you came up with
This is the config that I used, with the 40mm fan, I was able to push the TVAL to 108 ( 3.375A ) and get it reliably running. Some of this may be dependant on the motor inductance etc. I'm using some sturdy NEMA23 format motors.
Can't recall how much current I gave it without the fan but , while being better than the power-on config., it was not up to the claimed spec.
With a bit of forced ventilation, I'm quite happy.
/**
*
* TOFF_MIN = 21 us ( TON_MIN, TOFF_MIN def=21 )
* TON_MIN = 4 us T = (nibble+1)/2us , long TON_MIN leads to TOFF_FAST/8 switching
*
* TOFF_FAST = 10 us T = (nibble+1)*2 us def = 1 μs / 5 μs
* FAST_STEP = 16 us T_FAST = (TOFF_FAST/2-1)<<4 + (FAST_STEP/2-1 ) = ( 0x07 << 4 | 0x04 )
*
* TSW = 44 us T_OFF in CONFIG ?? def=40
*
*
* TON_MIN is not min since it can be interupted by a T_FAST/8; T_FAST/4 ... limitter: sect. 7.2
* keep TON_MIN short
* TOFF_MIN is a min. def=21us only on cycle is switched.
*/
// configure L6474 while in HiZ mode
// 200 step @ 5mm/rev = 1 thou' per step: L6474_STEP_BY4 give 1/4 thou precision. = 6.25 micron
// I=96 = 3.03A I = (TVAL+1)* 31.25mA
// ### find suitable default TVALS. ###
L6474_SetParam( ALL_SHIELDS, L6474_TVAL, 32 ); // printPgmString(PSTR("set Imax\r\n")); // 127 beeps UPS#
L6474_SetParam( ALL_SHIELDS, L6474_OCD_TH, L6474_OCD_TH_5250mA ); // printPgmString(PSTR("set OCD\r\n")); // 4A; def=8=3.375A
L6474_SetParam( ALL_SHIELDS, L6474_STEP_MODE, L6474_STEP_HALF ); // printPgmString(PSTR("half_step\r\n"));
uint16_t tmp;
// decrease slew-rate and config_toff, per Enrico ??? get defaults from sh #0
tmp = ( L6474_GetParam(0, L6474_CONFIG ) & ~L6474_CONFIG_VOLT_SR ) & ~L6474_CONFIG_TOFF_MASK;
L6474_SetParam( ALL_SHIELDS, L6474_CONFIG, tmp | L6474_CONFIG_SR_320V_us | L6474_CONFIG_TOFF_016us ); // printPgmString(PSTR("SR config\r\n"));
L6474_SetParam( ALL_SHIELDS, L6474_T_FAST, L6474_FAST_STEP_16us | L6474_TOFF_FAST_10us ); printPgmString(PSTR("T_FAST\r\n"));
L6474_SetParam( ALL_SHIELDS, L6474_TON_MIN, L6474_TON_MIN_4us ); // printPgmString(PSTR("TON_MIN\r\n"));
L6474_SetParam( ALL_SHIELDS, L6474_TOFF_MIN, L6474_TON_MIN_17us ); // cf L6474_TOFF_MIN
For the moment I have not been able to get this stm32 setup to produce the same speeds as I was getting on AVR. It seems that you have done the interrupts in a slightly different way.
TIM_TimeBaseStructure.TIM_Period =25000-1; // 1KHz
TIM_TimeBaseStructure.TIM_Prescaler = 3; // 100MHz/4
Prescaler = 3 means div 4 . Huh?
Doesn't this correspond to Timer1 freq on GRBL which is set at 1 MHz?
NVIC_SetPendingIRQ(DMA1_Stream7_IRQn); // this is a hack to get to a lower priority so the CC1 interrupt can get through
Is there a reason why this can't be done in a very similar way to GRBL AVR?
Also FLAG i/p really needs it own h/w interrupt. There are several conditions which drop the FLAG that are not stepper failure conditions: eg. temp warning, NOPERF command error....
@J-Dunn Thanks, I'll have a look at the setup later.
the prescaler starts at 0, so 0,1,2,3 = divide by 4
If I'm not much mistaken timer1 running the stepper interrupt in GRBL runs at 16MHz, in nuts_bolts.h:
#define TICKS_PER_MICROSECOND (F_CPU/1000000)
in the STM32 an interrupt cannot interrupt itself, and there is only one interrupt per timer, the different events like update, compare etc. just have flags you can check in that interrupt So the reset stepper port interrupt (CC1 on timer2 ) cannot interrupt the stepper interrupt (update on timer2)
So what I do is do the timing critical stuff in the update interrupt and then trigger a lower priority interrupt (that isn't used for anything else) to do the rest of the processing so the the CC1 interrupt can get through.
In the case of IC like the L6474 it probably doesn't make much sense to use the extra timer interrupt to do the pins reset the pulse width on the step clock only need to be a few hundred ns
the prescaler starts at 0, so 0,1,2,3 = divide by 4
OMG, this is going to be fun.
The reference manual says:
The counter can count up, down or both up and down. The counter clock can be divided by a prescaler.
So the "prescaler" that it's divided by is not the value they are calling prescaler in the rest of the documentation, it's "prescaler"-1. It seems that you only find out that is not true when you look at the timing diagrams.
I'd already noticed that what their doc calls the "period" of the timer is the counter, not the period, ie it's period-1 . It seems all these parameters need to be double checked to see whether you need to add or subtract one!
ST is a french company and the French think there are 15 days in a fortnight since they count both the first and last days. According to that convention, 'in two days time' means tomorrow. Something to do with latin origins of the language it seems and the Romans not having a zero in their number system. It seems that the same kind of logic applied to "prescalers" and "periods". :?
Thanks for the explanation anyway, I'll be on my guard.
void stepper_pinclr_interrupt()
{
// // Reset stepping pins (leave the direction pins)
// STEP_PORT = (STEP_PORT & ~STEP_MASK) | (step_port_invert_mask & STEP_MASK);
// TCCR0B = 0; // Disable Timer0 to prevent re-entering this interrupt when it's not needed.
set_step_pins(0);
}
I think that last line should be :
set_step_pins(step_port_invert_mask);
I notice that stepper_pinset_interrupt() is never called. It seems that STEP_PULSE_DELAY feature is not implemented. In that case, can't the whole thing be done in one ISR maybe with a little dead time loop to ensure min. pulse width and make it more visible on a 'scope?
Like _delay_us(1); ?
How much testing have you done on this bitbang method of doing the SPI comms?
I am calling it to get status bytes and am not getting meaningful returns. If I step it in the debugger I am. IIRC the L6474 can run at a max of 5Gb/s . I have not done cycle counting on this but it may be running too fast.
Since all the commands here are send only with no read-back verification and no commands are reading stuff like status bytes back from the boards, it is not clear from this code whether it is working reliably.
Have you done any read operations to test this. My read tests have got data but with corrupted bits and sometimes it trips the error flag.
A specific example: once the code is through the set-up stage, I send GETSTATUS to the three boards as is done during initialisation. I get back 7E03FE03FE03 . Now the 3 MSBs of the status register are all supposed to be 1, so the bit of the first board is getting missed. This looks like a timing problem.
Ah, I've found the missing bit:
GPIO_ResetBits(SPI1SCK);
if(data1&(0x80>>i)) GPIO_SetBits(SPI1MOSI); else GPIO_ResetBits(SPI1MOSI);
GPIO_SetBits(SPI1SCK);
res = (res<<1) | (GPIO_ReadInputDataBit(SPI1MISO)==RESET ? 0:1 );
It seems that the data was being set after the clock change, causing one bit to be lost in the three byte transfer.
GPIO_SetBits(SPI1SCK);
GPIO_ResetBits(SPI1NSS);
delay_us(1); // allow time of SS line to wake up chip; tsetCS>=350ns
for(i=0;i<8;i++)
{
if(data0&(0x80>>i)) GPIO_SetBits(SPI1MOSI); else GPIO_ResetBits(SPI1MOSI);
GPIO_ResetBits(SPI1SCK);
GPIO_SetBits(SPI1SCK);
res = (res<<1) | (GPIO_ReadInputDataBit(SPI1MISO)==RESET ? 0:1 );
}
for(i=0;i<8;i++)
{
if(data1&(0x80>>i)) GPIO_SetBits(SPI1MOSI); else GPIO_ResetBits(SPI1MOSI);
GPIO_ResetBits(SPI1SCK);
GPIO_SetBits(SPI1SCK);
res = (res<<1) | (GPIO_ReadInputDataBit(SPI1MISO)==RESET ? 0:1 );
}
for(i=0;i<8;i++)
{
if(data2&(0x80>>i)) GPIO_SetBits(SPI1MOSI); else GPIO_ResetBits(SPI1MOSI);
GPIO_ResetBits(SPI1SCK);
GPIO_SetBits(SPI1SCK);
res = (res<<1) | (GPIO_ReadInputDataBit(SPI1MISO)==RESET ? 0:1 );
}
CMSIS is probably slow enough that resetting and setting the clock line will take at least the 25ns required. ( t_setSDI ) I've also added a delay to ensure that the wake-up time ( t_setCS ) minimum of 350ns in the spec. is respected.
I now seem to have two way communication with the L6474 stack working using this bitbang method.
Now that I have a usable testbed, I can confirm that I'm not getting anything like the step rate I was able to get on AVR, using the settings I posted above. It stalls at about half the rate it used to.
I will have to look into getting the timers and pulse timing closer to std GRBL.
At least I can now set and get L6474 parameters from the GRBL command line and do some experimentation.
If I'm not much mistaken timer1 running the stepper interrupt in GRBL runs at 16MHz, in nuts_bolts.h:
Thanks
TCCR0B = (1<<CS01); // set Timer0 full speed: ie. 1/8 prescaler
GRBL stepper.c says:
NOTE: This interrupt must be as efficient as possible and complete before the next ISR tick, which for Grbl must be less than 33.3usec (@30kHz ISR rate). Oscilloscope measured time in
Speeding up TIMER2 on STM32 gets step rate just above what I had on AVR.
TIM_TimeBaseStructure.TIM_Period =800-1; // 32.5KHz cf std GRBL
@J-Dunn the -1 makes perfect sense from a HW perspective, it is a counter that count from zero not from one.
Yes many drivers are fast enough that you could just do it all in one interrupt, but if you use something with opto couplers you usually need bigger delays
Yes the SPI (and reset) timing was at best marginal, with optimization it didn't work at all, I've made some changes and added the option to use HW SPI
changing TIM_TimeBaseStructure.TIM_Period shouldn't have any effect it is just the init value, the timer period is constantly updated in the stepper interrupt
I've also added a report of the max time to end of interrupt, and max percentage of period to end of interrupt
the -1 makes perfect sense from a HW perspective, it is a counter that count from zero not from one.
Sure, my objection was calling it "period" when it isn't the period. A bit like calling something a pre-scaler, when it isn't the scaling factor.
Thanks for the HW SPI, I'll pull a copy. I've been wasting a lot of time trying to get the couple of dozen parameters that need to be set just right and hadn't got it fully working yet. Very flexible, I'm sure, but a PITA to configure. I'd resigned to sticking with bitbanging it for now, since it was consuming an unwarranted amount of time.
Thanks for the HWSPI update. I see that you've adopted some of comments I posted above about the config of L6474 boards.
I have uploaded a temporary copy of my file containing #defines for the L6474 parameters and parameter values. This is very comprehensive and includes a few notes where this can be helpful. I have adopted a policy of prefixing everything with L6474_ to give a consistent namespace and I tend to be fairly verbose in order that this makes the source self documenting.
eg I use L6474_ENABLE rather than CMDENA
I also define a named bitmask for the critical error bit flags. I may add the reserved L6474_STATUS_BIT1 to that to make it the same as you XOR constant.
https://github.com/J-Dunn/grbl_stm32/blob/master/grbl_L6474.h
grbl_L6474.h implies there is grbl_L6474.c ; this is where I put all the L6474 specific code, to keep it separate from the std GRBL files like stepper.c
Feel free to adopt any of that if you find it useful.
( BTW I don't intend to maintain a separate fork, I just wanted somewhere to dump related files. )
@langwadt : I've been load testing this on my h/w to establish motor torque. I have the gantry motor connected and two heavy duty springs restraining the gantry motion. 5" displacement produces a linear force of just over 100kgf. The motor stalls if I try 5.5".
Firstly, this is pretty much the same result I got using these drivers on AVR, so it's a confirmation that the STM port has not introduced any problems at this level. Nice.
However, this is about 0.8 N.m and these steppers (57BYGH633) are supposed to deliver about 5 N.m at this step speed. Even a generous allowance for drive efficiency and frictional loss in the ball screw, that's barely 1 N.m at the motor output shaft.
While I expect that the claims of the chinese manufacturer should be taken with a pinch of salt, this seems too far off and suggests that I am not optimally driving the motor with the L6474.
Have you done any similar torque tests for motors driven with these L6474 driver boards?
@J-Dunn sure about those numbers? I don't think you can even get 5Nm in less that NEMA34
If I look up 57BYGH633 I get something like 1.35Nm holding torque at 3A so 1Nm when moving doesn't sound that far off
Thanks, you are right of course. I must have picked up some bum info. 5Nm did sound surprising. Cool, that's one less thing to worry about.
Thanks for making this code available. I have these L6474 boards working on top of Arduino but that is just a stepping stone to stm32, for me. This may be useful.
What is the development environment for this code ? Is it intended to build under Mbed, Arduino "IDE" , eclipse... ?
Nucleo STM32F411RE , 'Discovery' ...?
a quick README.md would be helpful to give an overview or what it is and how to build it.
Thx.