0
\$\begingroup\$

I have a nema17 stepper motor and a WS2812B based LED strip (60 leds) and an Arduino Nano. The goal is to update the brightness of the led strip based on the motor position as frequently as possible. The problem is because of the CPU time the LED update needs, the motor will slow down and stutter.

The motor drives is A4988 based driver with step and dir pins. The lib is AccelStepper. The motor has a max speed of 2000 steps per second and the step resolution is set dynamically based on speed (lower speed 1/16 steps). I need to know the exact position of the motor at all times and the position can't drift. I only have Nanos and try to do it with them.

I now moved the LED update code to another Arduino Nano and tried to send the the ratio 0-1 (position on a circle) as a byte 0-255 with I2C. It's better but seems like I2C still blocks the motor update. Best would be if I had 16 digital pins I would just send the number with setting them binary to that number and when the other one finishes updating could just read in the current state and go into another LED update cycle. That would not block the main code at all but I don't have that many pins.

I'm currently thinking of using analogWrite and mesuring the pulse width with pulseIn on the other but that's just 255 resolution.

Is there any better solution that I might be overlooking?

Thank You

\$\endgroup\$
5
  • \$\begingroup\$ Multi-processor designs can easily get quite complex. A better solution might be a processor with DMA which could feed the LEDs. Or perhaps a step+direction motor driver IC you could feed even from a timer. If you do go multi-processor, it's best to stay digital. It would help if you could edit your question to clarify the sort of movements the motor makes - do you set a speed and let it spin, or are you executing acceleration-profiled movements like a CNC machine or 3d printer would? \$\endgroup\$ Dec 6, 2020 at 19:17
  • 1
    \$\begingroup\$ The bloated Arduino firmware is getting in the way. If you split the task between two Arduinos, the LED Arduino has to listen or independently sense what is going on without any intervention or knowledge by the motor Arduino. Or it's time to move one from an Arduino to bare metal coding. Bare metal coding should have no problem with this. \$\endgroup\$
    – DKNguyen
    Dec 6, 2020 at 19:18
  • \$\begingroup\$ I edited the question, unfortunately I'm not experienced in bare metal coding and don't really have the time to do it like that. \$\endgroup\$
    – Reider
    Dec 6, 2020 at 19:24
  • 1
    \$\begingroup\$ Do you vary the speed of the motor? How often? The "don't have the time to do that" is not a comment likely to go over very well here. Depending on precise details of your need you may be able to replace the stepper library with simple usage of a timer, or have a second Arduino spy on the step pulses using its counter and read (accounting for overflow) and translate that into a new LED command at every opportunity. Or maybe you can use interrupts and do it all in one chip. IMHO the problem still remains underspecified and missing details I'd need if I were asked to design this. \$\endgroup\$ Dec 6, 2020 at 19:40
  • \$\begingroup\$ The led is mounted on a big arm and rotated by the motor. Similar to this i.ytimg.com/vi/WBd1cfgoZHM/maxresdefault.jpg but much slower and bigger. The motor needs to vary in speed and accelerate to handle the heavy arm. The position is important because of the patterns. I just need to get over a position as frequently as possible. Maybe I'll try a 9 pin connection that's 512 resolution . I could live with the current I2C solution but am looking for the solutions I might have overlooked. The step counting could work if no simpler would. Thank you for your time. \$\endgroup\$
    – Reider
    Dec 6, 2020 at 20:01

1 Answer 1

3
\$\begingroup\$

Maybe not an answer that you want to hear but totally an answer that fits the problem.

The better solution is proper programming. The Arduino suite and its HAL is easy to use for one task, but sometimes quite bad for multiple tasks. The solution would be to do things on your own and skip the HAL(hence some libaries). I will show some basic analysis and a possible solution to communicate some basic concepts, but no C-Code implementation.

Basic analysis

  1. The Arduino Nano is nothing more than a ATMega328 on a blue PCB with an USB Interface and a bootloader. This controller has multiple Timers and other peripherials like SPI - controlling an Stepper Motor and some LEDs is a task that this controller can handle easily.
  2. You have two tasks, that you want to have in "parallel":
  • Stepping your Motor
  • Pulsing your WS2812B LEDs
  1. The way you control your Stepper Motor is through a A4988 driver. This driver has a Step/Dir Interface. Means that with one pin(Dir) the rotation direction is controlled(CW/CCW), the other pin(Step) tells the controller to do one step.
  2. According to the Datasheet the WS2812B LED are controlled by a 24 Bit value, divided in 3 times 8 bit, each 8 Bit(=one Byte) for in color(RGB). A dataframe is indicated by a reset period(50µs) before these 24 Bit. If one bit is an 1 or a 0 is indicated by the pulsewidth of a squarewave of a period of 1,25µs.
  3. According to the datasheet the ATMega328 has 2 8 Bit timers and one 16 Bit timer and several PWM Channels.

Possible solution

Outsource the stepping of the Motor to an 8 Bit Timer and the WS2812 Timing to an 8 Bit or 16 Bit PWM.

  1. As stated before the motor just needs to get something like a clock in the step pin(StepCLK). This can easily be done with a hardware timer. For example with Timer0 in Normal Mode or for even finer frequency resolution in CTC Mode. In this mode you dont have to do anything in code for generating the StepCLK waveform if you enable the pin toggle on compare match - this wont waste any CPU time so your controller can handle other things. Look at the datasheet in section 14.7.2 for further information.
  2. For the LEDs you can use Timer1 in PWM Mode. Here you have to setup the timer first to get the bit period of 1,25µs, after that you just have to set the compare value of the PWM timer. A good way to do this is to set this timer to fastPWM mode. In this case OC1A or OC1B will can set high in the beginning of a PWM period and set low on a compare match. in this case the only thing the CPU has to do is to set the period for each PWM cycle, which can be done in the Timer1 overflow interrupt routine. To see your timing tolerances see this wordpress article.
  3. For you application you can do some things in the main loop. From there the only thing you have to do is write to the Timer0 compare register for acceleration ramps and update a LED-RGB buffer for your LED display. Probably the rest of the time the CPU can go to sleep mode for energy effenciency.

Pseudo Code

For better clarification I will provide some c-ish pseudocode:

volatile  uint8_t LEDBuffer[LEDNum*3]; //each color value has its own array field.    

volatile ledIdx,bitIdx;

Timer1OverflowInterrupt(){
if(bitIdx % 8 == 0) ledIdx++; //increments ledindex after 8 bits are masked

if(LEDBuffer[ledIdx] & (1<<(bitIdx%8) != 0){ //With bitIdx we shift through all bits of the current LEDBuffer value and check if its zero or one
timer1Compare = ONEBitPERIOD
}else{
timer1Compare = ZEROBitPERIOD
}

bitIdx++
if(ledIdx==LedNum){
ledIdx=0; //resets ledindex if array size is reached
timer1Compare = RESETbitPERIOD //after we shifted all LED values out we indicate a break for reset-synchronization
}
}

setup(){
 configureTimer0asCTCwithOutputCompare()
 configureTimer1asFastPWMwithOutputCompareAndOverflowInterruptEnable()
 setStepDirPin()
}

loop(){
for(uint8_t i = 0; i<ledNum;i++){
LEDBuffer[i*3] = redValue;
LEDBuffer[i*3+1] = greenValue;
LEDBuffer[i*3+2] = blueValue;
}
timer0compare = stepperSpeedValue
}

Explanation and further problem analysis

This solution makes use of the hardware timers. This means there are actual hardware registers which are counting with their own clock and do actions based on some configuration registers set by the application program. Only if a special event occurs the CPU is notified with a interrupt(hence the name interrupt) and shortly switches to another piece of code(like Timer1OverflowInterrupt) after that resuming its main program(loop). In contrast and for example The fast LED libary from Adafruit makes heavy use of assembler nops(no operation), which means the CPU is blocked by doing nothing.

I hope that helps you in the understanding and basic concurrent programming on embedded systems. You have to figure out by yourself how to configure the Timers but I think I gave you some good hints how to do it.

\$\endgroup\$
4
  • \$\begingroup\$ Do you know if anyone has actually driven WS2812's from an ATmega timer? Because you are feeding a stream of data it would seem that without DMA you would spend a lot of effort feeding the timer since it would only generate one pulse per bit, not keep repeating as it would for typical raw PWM LEDs. \$\endgroup\$ Dec 7, 2020 at 7:41
  • \$\begingroup\$ This solution utilizes the PWM-timer to read from a static, common buffer. The buffer isnt cleared, so the the buffer cannot underflow. This solution works best for LED-frames that are kind of static. I think this is the most CPU.time efficient solution for the given µC. If this doesnt work my guess would be that this controller isnt suited for the task, Edit: I havent seen anyone doing this , but I have seen high frequency PWM. This is basically the same, but using the PWM hardware for generation of data pulses. The LEDs take care for their own on PWMing their Channels. \$\endgroup\$ Dec 7, 2020 at 7:48
  • \$\begingroup\$ The difference between this and PWM is that PWM is repetitive on a local scale, this isn't. Each pulse compromising a bit in a WS2812 word would need the counter loaded, and with no DMA the only thing that can move information from a buffer to the timer register is the CPU itself. \$\endgroup\$ Dec 7, 2020 at 9:02
  • \$\begingroup\$ Ahh now I understand. So a proper set up SPI would be even better, wouldn't it? \$\endgroup\$ Dec 7, 2020 at 9:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.