Am I doing something wrong or is Arduino just this slow? - Page 1
Forum section: electronics / general

author: Red Squirrel
author_rank: Super Contributor
timestamp: November 04, 2017, 06:10:02 am
content: I want to make a basic push-pull controller, cheating by using an MCU as I'm sure there is a better way, but here is my code:Code:#define ontime 100#define deadtime 5int cycletime=0;int fetnum=0;void setup(){pinMode(10,OUTPUT);pinMode(11,OUTPUT);PORTB=0b00000000;cycletime=0;fetnum=0;}void loop(){if(cycletime==0){PORTB = fetnum==1 ? 0b00000100 : 0b00001000;}else if(cycletime>=ontime){PORTB = 0b00000000;}if(cycletime>(ontime+deadtime)){cycletime=0;fetnum = fetnum==0 ? 1 : 0;}else{cycletime++;}}Some values are still open to tweaking. One thing though I noticed that this only runs at about 1Khz! I was able to speed it up slightly by setting the registers directly, but I would have expected to get way more, like 100khz+ at least given the actual clock is 16Mhz. I kinda cheated by doing a single set which sets all 8 pins on that register, I did not quite understand what was going on with some of the code I found that was using odd functions like _BV which I am not sure what it does. Am I doing something wrong, or is this behavior normal? I'd like to eventually learn the proper way of accessing all the registers directly and setting pins and doing other stuff (just harder to find tutorials on that) so if I was to write code without using Arduino would it be much faster?

author: ebclr
author_rank: Super Contributor
timestamp: November 04, 2017, 06:26:59 am
content: Dissambly your code, and check how many instructions have there you will never get something near 16Mhz, With huge assembly optimization 100Khz will be enough, Wana something much faster change to ESP32

author: sleemanj
author_rank: Super Contributor
timestamp: November 04, 2017, 06:29:09 am
content: Are you sure the "arduino" is running at 16Mhz?Is it an actual arduino, or something else.

author: sleemanj
author_rank: Super Contributor
timestamp: November 04, 2017, 06:44:45 am
content: FWIW, on a 16MHz Nano on my desk with your code unmodified (watched with a Saleae knockoff with PulseViewHigh time ~187uS, Low time ~216uSMakes it about 2.4kHz I thinkOf course, that is dominated by your choice of "ontime" and "deadtime".Naturally you would be better to use a PWM output if all you are wanting to do is generate some square wave at the fastest speed you can.

author: sleemanj
author_rank: Super Contributor
timestamp: November 04, 2017, 06:52:03 am
content: NB: something as simple as changing those two variables to a more appropriate data type can have a significant effect, doing 16 bit math on an 8 bit processor requires significant legwork.Do you really need a signed 16 bit integer for both of those.

author: Bruce Abbott
author_rank: Frequent Contributor
timestamp: November 04, 2017, 08:03:45 am
content: 100kHz is a PWM period of 10us. At 16MHz the MCU can executeat most160 machine code instructions in 10us (some instructions take 2 or more clock cycles). Your on time plus dead time count is 105. That means to get 100kHz you can't have more than 1 machine code instruction in the loop. How do you expect it to:- 1. compare cycletime to 0, 2. compare cycletime to ontime + deadtime, 3. increment cycletime, 4. go back to the start of the loop - using only one instruction?C source code that looks terse and simple may translate into hundreds or thousands of machine code instructions. However the biggest problem with your code is the algorithm, which does several math operations for every PWM count. This is unnecessary and very inefficient.There are two ways you can dramatically speed up the PWM frequency:-1. Instead of incrementing a counter in a loop, use a time delay function that only has to be called twice during the PWM period. Arduino has delayMicroseconds(), which should get you about 9.5kHz (1/105us). To go faster you would need a custom delay function which has higher resolution. The theoretical limit is 150kHz, but to get this you would probably need highly optimized machine code.2. Use the hardware PWM module. On the Arduino this is normally set to ~490Hz , but it can be increased to62.5kHzby programming the PWM registers directly, or even higher if you don't need 8 bits. Hardware PWM is also generally more stable and predictable than 'bit-banging', and allows the MCU do do other stuff while the PWM is being generated.

author: Nusa
author_rank: Super Contributor
timestamp: November 04, 2017, 08:39:12 am
content: Also, if you do your own loop instead of exiting the loop function, you'll save some cycles per iteration.

author: donotdespisethesnake
author_rank: Super Contributor
timestamp: November 04, 2017, 11:27:48 am
content: There are some things better down in hardware, generating PWM signals is one of them.The regular Arduino AVR does not have PWM with deadtime compensation, but you can do it with 16-bit Timer1. Seehttp://www.avrfreaks.net/comment/2225216#comment-2225216The following works on ATmega32 (uno etc).Code:void setup() {pinMode (9, OUTPUT);pinMode (10, OUTPUT);// TCCR1A=(1<<COM1A1) | (0<<COM1A0) | (1<<COM1B1) | (1<<COM1B0) | (0<<COM1C1) | (0<<COM1C0) | (0<<WGM11) | (0<<WGM10);TCCR1A=(1<<COM1A1) | (0<<COM1A0) | (1<<COM1B1) | (1<<COM1B0) | (0<<WGM11) | (0<<WGM10);TCCR1B=(0<<ICNC1) | (0<<ICES1) | (1<<WGM13) | (0<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10);TCNT1H=0x00;TCNT1L=0x00;ICR1H=0x00;ICR1L=0x28;OCR1AH=0x00;OCR1AL=0x12;OCR1BH=0x00;OCR1BL=0x16;//OCR1CH=0x00;//OCR1CL=0x00;}void loop() {// put your main code here, to run repeatedly:}

author: Red Squirrel
author_rank: Super Contributor
timestamp: November 04, 2017, 04:22:18 pm
content: Oh never considered data type that's something that has an effect but guess it would make sense. But really, expecting 100khz is probably mostly unrealistic right? I guess I am just underestimating the amount of instructions that happen under the hood. Was mostly just thinking maybe there is something completely wrong I'm doing, but it sounds like even some micro optimizing probably won't easily get me as much as I expected. I'll keep playing with it and try some of the tips mentioned like making my own loop to see how close I can get.For this particular purpose (push pull converter) I'm probably going to end up just getting a discrete component.

author: Red Squirrel
author_rank: Super Contributor
timestamp: November 04, 2017, 06:00:46 pm
content: I guess the if statements take way more instructions than I had thought. Got rid of all that and just hard coded some values and I got it as high as 3Mhz, which is actually way higher than I need unless I want to start using litz wire as this will be driving a transformer. Though my timing is all weird and wave forms arn't very clean, so still need to tweak stuff.

author: Red Squirrel
author_rank: Super Contributor
timestamp: November 04, 2017, 06:57:13 pm
content: I decided to simplify things and managed to hit 105Khz! I guess I was really underestimating the number of instructions for doing certain operations like if statements, setting increment values etc. This is my code now:Code:bool fetnum=0;void setup(){pinMode(10,OUTPUT);pinMode(11,OUTPUT);PORTB=0b00000000;}void loop(){while(true){PORTB = fetnum ? 0b00000100 : 0b00001000;for(byte i=0;i<20;i++);fetnum = !fetnum;PORTB=0b00000000; //dead time//for(byte i=0;i<2;i++);}}If I try to go any faster then the dead time becomes too big in proportion with on time so this is as good as I'm going to get I think, and about what I was aiming for anyway. This will drive a transformer, so I need two primary windings of 75 turns if I'm going to attempt to drive this with 170vdc.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 04, 2017, 07:18:40 pm
content: You're not doing any control at all? Just use a MAX253, or a pair of transistors (multivibrator)!FYI, you can further eliminate a conditional by changing the question-mark operator (a compact if statement) to: PORTB ^= 0b00001100; (place a PORTB = 0b00000100; above the while() to initialize it).Tim

author: hans
author_rank: Super Contributor
timestamp: November 04, 2017, 07:43:36 pm
content: This kind of free running software 'PWM' is quite tricky. Depending on the orientation of the moon, earth, our sun and the milky way the code may be compiled differently tomorrow, thus run at a different frequency.The toggle (^= operator) of T3sl4co1l won't work because the port is written to zero as a dead-time. I suppose you're driving a H-bridge with this.In fact, writing whole ports to a particular value could be dangerous. If you got other I/O connected to it will/may be reset.You could probably get a bit more speed by doing a little bit of loop unrolling. Namely you're toggling a boolean just for the sake of outputting a different port value for the driver. You could also expand that :Code:while(true){// High timePORTB = 0b00001000;for(byte i=0;i<20;i++);PORTB=0b00000000; //dead time// Perhaps a small loop necessary here for deadtime.// Low timePORTB = 0b00000100;for(byte i=0;i<20;i++);PORTB=0b00000000; //dead time// Perhaps a small loop necessary here for deadtime..}Also watch out with the byte i loops. Ideally you should make variable i volatile because in many GCC configurations it sees that i is never really used, and so it will remove the delay loop completely. This is why the 'best' solution would be to use a hardware PWM module.

author: Nusa
author_rank: Super Contributor
timestamp: November 04, 2017, 09:00:17 pm
content: Another refinement for something like this would be to disable interrupts before the loop. It'll break timer features to leave it off, but you aren't using them. The idea is to eliminate small blips in your timing as background interrupts occurs.Code:cli();If you want really precise control of delays, you can get down to a single cycle precision if you want. You could unroll the for loop into a list of nops if you wanted (within reason).Code:__asm__("nop\n\t"); // delay 1 cycle (62.5ns @ 16MHz)__asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t"); // delay 5 cycles

author: Red Squirrel
author_rank: Super Contributor
timestamp: November 04, 2017, 10:35:24 pm
content: Yeah this is more of a test, after seeing how tricky it really is (at least with a slower MCU) I'm probably going to use a discrete component as I was actually planing to have a separate chip after the transformer to do the actual buck control. The transformer is only really there for isolation and to step down to a relatively safe voltage for the post isolation circuitry.That __asm__ function is interesting, I found that while searching to see if there was a nop function, only thing, does calling the function itself end up costing a lot more instructions?This all got me thinking, what does it take to code these using assembler, any good books/resources that would cover all the various methods, etc? I've always been interested in assembler at the computer level but an MCU is probably an easier place to start.

author: daybyter
author_rank: Frequent Contributor
timestamp: November 04, 2017, 10:44:33 pm
content: 478 kHz with an stm32f103 ?https://stackoverflow.com/questions/25707982/are-there-a-limitation-for-frequency-in-timer-stm32That's a 2$ board and there is stm32duino...

author: Nusa
author_rank: Super Contributor
timestamp: November 04, 2017, 11:24:23 pm
content: Quote from: Red Squirrel on November 04, 2017, 10:35:24 pm__asm__ isn't a function, it's a preprocessor directive that says insert this inline assembly code during the compile. The string being passed is handed off to the assembler to interpret. There's no function call at all during execution, so no overhead to worry about.

author: Red Squirrel
author_rank: Super Contributor
timestamp: November 05, 2017, 01:18:23 am
content: Quote from: Nusa on November 04, 2017, 09:00:17 pmOh I did notice oddball blips on the scope, was wondering what that was.But yeah it sounds like at this level things could get unpredicable from system to system depending on how the compiler does it, so probably not the style of code I'd be doing in production. I did manage to refine it even more, got my loop to 330khz. Probably as high as I want to go before I have to worry about skin effect in my transformer. Leaves me leg room if I want to add some kind of control like short circuit protection though probably leave that up to the buck converter stage on the other side of the transformer. I'm not even sure how well it will work, like if my dead time is too long or too short or if the back emf will vary based on various conditions like load etc, I'll find out.I'm probably better off using a discrete push-pull gate driver that is designed for this, but I'll see how it goes anyway.Code:void setup(){pinMode(10,OUTPUT);pinMode(11,OUTPUT);PORTB=0b00000000;}void loop(){cli();while(true){PORTB = 0b00000100; //fet 1__asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\tnop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");__asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\tnop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");PORTB = 0b00000000; //dead time__asm__("nop\n\tnop\n\t");PORTB = 0b00001000; //fet 2__asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\tnop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");__asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\tnop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");PORTB = 0b00000000; //dead time}}The STM32 looks interesting too, it may be more suitable if I want to be doing "high speed" stuff like this. I'll have to read up further on it. IMO the hardest part of a new platform is figuring out the toolchain etc and getting it to work in Linux, so I tend to stick to what I know but as a newbie I'm definitely open to trying new things so I don't just restrict myself to one platform.I'm also curious about simply going right under the hood, and actually learning how to use raw C or assembly. Any good resources that would show me how to do that? I imagine that knowledge will translate to other platforms too right? Like even how to interpret datasheets and such so that I can know that when the datasheet talks about something I know what the variable names are in code and so on. Arduino is a great stepping stone but I do eventually want to go off it, whether I keep using the Atmega or something else.