Question about Arduino ms implementation - Page 1
Forum section: electronics / beginners

author: _Vlad_
author_rank: Newbie
timestamp: July 03, 2020, 09:49:43 am
content: Hello boys and girls, geeks and weebs!Small question:So we have Arduino (UNO, NANO, don't care) with ATMEGA328p on board.We have 16Mhz oscillator on board.Arduino library uses TIMER0 in this case with prescale of 64 AND overflow interruption. TIMER0 is 8-bit timer..and here my question is.. WHY? I don't get it. WHY OVERFLOW? Why not COMPARE mode?So we have 16 000 000 / 64 = 250 000 ticks per second.Please! I just cant understand why you don't want to set OCR to (250-1) and get compare interrupt every 250 ticks.. or 250 000/250 = 1000 interrupts per second. Which is called, you know, milliseconds. For a milliseconds counting. Or I apocalyptically missing something?Instead it's implemented as clear overflow, so our timer generate interrupt when it get's over 255..So it's actually counting slower and you get 1000 interrupts in actual 1024ms.Code:void init(){// this needs to be called before setup() or some functions won't// work theresei();/***** some code for ATMEGA8 and other *****/// set timer 0 prescale factor to 64#elif defined(TCCR0B) && defined(CS01) && defined(CS00)// this combination is for the standard 168/328/1280/2560sbi(TCCR0B, CS01);sbi(TCCR0B, CS00);// enable timer 0 overflow interrupt#if defined(TIMSK) && defined(TOIE0)sbi(TIMSK, TOIE0);#elif defined(TIMSK0) && defined(TOIE0)sbi(TIMSK0, TOIE0);#else#errorTimer 0 overflow interrupt not set correctly#endif/***** other timer's initialization *****/And they know it. So They get kinda "correction" in ISR:Code:ISR(TIMER0_OVF_vect){// copy these to local variables so they can be stored in registers// (volatile variables must be read from memory on every access)unsigned long m = timer0_millis;unsigned char f = timer0_fract;m += MILLIS_INC;f += FRACT_INC;if (f >= FRACT_MAX) {f -= FRACT_MAX;m += 1;}timer0_fract = f;timer0_millis = m;timer0_overflow_count++;}So can somebody explain this to me? I just don't understand why you want to do this not in straight way? Thank you!

author: Siwastaja
author_rank: Super Contributor
timestamp: July 03, 2020, 09:55:49 am
content: There is absolutely no technical reason for about anything in Arduino. It's a total hobby project made within days by total noobs. (Nothing wrong in that, just stating this fact.) The reason for most design decision is, "just popped in mind".Hence, it's mostly a waste of time to look at how Arduino implements things. It's for you to write some super simple projects with trivial requirements, utilizing existing shields and their libraries as widely as possible. It's literally designed for artists, not programmers nor electronic designers, professional or hobbyist. This is what the Arduino company themselves define their role as.Once you start asking "why", it's time to just go forward. Then you can begin doing what you want and need.Otherwise, it's like having lengthy discussions about the bearing grades of training wheels.

author: mikerj
author_rank: Super Contributor
timestamp: July 03, 2020, 10:18:40 am
content: The CCP modules on all three timers are used for the PWM outputs.

author: Peabody
author_rank: Super Contributor
timestamp: July 03, 2020, 01:43:07 pm
content: Yes I think mikerj is right. Rolling over allows them to use the compare registers for PWM.But I agree that the method they chose can cause problems. Periodically the millis value increases by two. So if you're using millis for something like 7-segment multiplexing, you can get what appears to be flicker caused by variability in the segment ON time.However, you're welcome to write your own millis ISR and do the compare at 249. Here's my version:Code:/*This is a replacement for the ISR(TIMER0_OVF_vect)interrupt that drives millis(). It disables the OVFinterrupt, enables the COMPA interrupt, sets OCR0A to 249,and changes Timer0 to CTC mode. This results in aninterrupt every 250 timer clock cycles, which is exactly 1msfor a 16MHz crystal, or 2ms for an 8MHz crystal. The new ISRincrements millis, but since the interrupt rate is exactlycorrect, no periodic double increment of millis is needed.Using this code probably means you can't do any analogwrites that use Timer0, which would include pins 5 and 6.*/extern volatile unsigned long timer0_millis; //these defined in wiring.cextern volatile unsigned long timer0_overflow_count;volatile unsigned long MILLIS_INCB = (64 * 250) / (F_CPU / 1000); // ms between timer overflowsconst int cycleTime = 500; // flash LED every secondint LEDcount = cycleTime;unsigned long oldMillis = millis();unsigned long newMillis = 0;void setup() { //Set up alternate interrupt// at 249 on timer0cli(); // disable interrupts while doing thisTCCR0A = 0; // set entire TCCR0A register to 0TCCR0B = 0; // same for TCCR0BTCNT0 = 0; // initialize timer0 count to 0OCR0A = 249; // set top of counterTIMSK0 &= ~bit(TOIE0); // disable overflow interruptTCCR0A |= bit(WGM01); // turn on CTC modeTCCR0B |= (bit(CS01)+bit(CS00)); // Set CS00&CS01 bits for prescaler = 64TIMSK0 |= bit(OCIE0A); // enable timer compare interruptsei(); // enable interruptspinMode(13,OUTPUT);digitalWrite(13,HIGH);}void loop() { // flashes LED for 1/2 second// every secondnewMillis = millis();if ((newMillis - oldMillis) > 0) {oldMillis = newMillis;LEDcount -= 1;if (LEDcount == 0) {digitalWrite(13,!digitalRead(13)); // invert pin 13 stateLEDcount = cycleTime;}}}ISR(TIMER0_COMPA_vect) { // this is the new ISR - much// simpler than originaltimer0_millis += MILLIS_INCB;timer0_overflow_count++;}

author: _Vlad_
author_rank: Newbie
timestamp: July 03, 2020, 03:30:09 pm
content: Thank you!Yep, looks like it's really like mikerj said. Well at least it's sounds reasonable.And at least I'm not missing something, so good to get a small fight to kinda Impostor syndromeThank you guys for response!

author: brucehoult
author_rank: Super Contributor
timestamp: July 04, 2020, 03:15:48 am
content: Привіт Влад,While I agree somewhat with what Siwastaja said I think a lot of people overlook that the Arduino people simply had different priorities to professional engineers. They emphasize getting the maximum functionality out of the chip for somewhat low performance (human timescale) applications rather than getting maximum performance -- and also to get an API which is reasonably implementable over a wide range of different microcontrollers.In this case the trade-off of getting three PWM outputs and a little bit of jitter on the ms counter seems like a good one for most users, compared to having two PWM outputs and a "perfect" ms. They could also have chosen to leave the ms counter at slightly the wrong speed, but jitter-free, which also would have been fine for many uses. However I think, again, introducing a tiny bit of jitter in order to get good long-term timing accuracy (at least to the limits of the crystal) is also the right decision.The same goes for all the "extra" work digitalWrite() does to map the virtual pin number to a physical port and pin, set up the pin mode etc. Yes, it limits the maximum frequency at which you can toggle a pin to (I don't remember exactly) a few hundred kHz instead of the several MHz you can get with low level code, but for the intended applications the intended user isn't going to need over (or anywhere near) 1 kHz anyway. When you need more, for example to do fast SPI transfers or drive WS2812 LED strips (which need *very* precise timing), you use a specialized library for that anyway.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 04, 2020, 08:35:56 am
content: There is absolutely no excuse for the digitalWrite() farce. You can speculate whether it's "good enough" "for the job" or not, but this is all meaningless, it doesn't need to be that way, there is no benefit for it to be that way, and it wouldn't have been any harder to do it properly. (Similarly, there would have been no extra work alingning the 0.1" pin headers to 0.1" grid so you could connect them to a breadboard.)For a beginner artist not interested in low-level stuff, it would bemuch easierif such basic building block worked without exposing the user to said low level ("why the heck is this taking so long??") The entire conundrum just wouldn't exist, it would "just work".They do run the entire code through their own preprocessor anyway - could have just made a simple implementation to replace digitalWrite(123) with PORTx = xyz; operation. Basically run the same digitalWrite() logic during the preprocessing. Heck, just auto-including theexact currentdigitalWrite() implementation defined as a static function to the project would likely make the compiler just optimize it away completely.The fact they couldn't do that and ever fix this trivial thing shows perfectly how it was a "designed in a few days" project with absolutely zero interest in maintaining it or making it any better, just generate as much money with large-scale guerilla marketing as possible.Note these are just the cold facts, not opinions about whether all this is "good" or "bad". On that matter, I think Arduino has done much more good than bad, especially being the "stepping stone" on way to start designing electronics and writing embedded software, hobby or professional. On the other hand, people who insist, no matter what, how Arduino somehow "must" be the way electronics and embedded systems are designed, despite that even the Arduino companyitselfunderstands it's just a stepping stone for non-programmers, non-electronic-designers, are severely hindering people who started with Arduino, from going forward.Because when you really start hitting the limits, and that happens pretty soon, you are wasting colossal amount of time trying to figure out why every single Arduino feature is the way it is, and how to work around it. Looking at "how others do things" is educational, but it pays off to use a bit more professionally designed projects as references. There is not much design behind Arduino.

author: mikerj
author_rank: Super Contributor
timestamp: July 04, 2020, 01:18:48 pm
content: Quote from: Siwastaja on July 04, 2020, 08:35:56 amSorry but there absolutely is a good excuse; enumerating a pin to a simple integer value simplifies things at a high level and that is ultimately the aim of the language. If this gets in the way for your use then use plain C/C++ rather than the Arduino language.Out of interest how would you do it "properly"?

author: sleemanj
author_rank: Super Contributor
timestamp: July 04, 2020, 01:50:50 pm
content: Quote from: mikerj on July 04, 2020, 01:18:48 pmAt compile time wherever possible, specifically, when the pin is a compile time constantI did it that way in my ATTinyCore forkhttps://github.com/sleemanj/ATTinyCore/blob/master/avr/cores/tiny/Arduino.h#L275If the pin is compile time constant, then the work of figuring out what bit to set in what register is all done compile time.NB: it is in practical terms almost always the case that the pin is constant, especially in arduino land.

author: ledtester
author_rank: Super Contributor
timestamp: July 04, 2020, 01:59:29 pm
content: Quote from: Siwastaja on July 04, 2020, 08:35:56 amThere are alternate implementations of digitalRead/Write which generate optimal code, e.g.:https://github.com/NicksonYap/digitalWriteFast

author: langwadt
author_rank: Super Contributor
timestamp: July 04, 2020, 02:18:57 pm
content: Quote from: Siwastaja on July 04, 2020, 08:35:56 amthe misaligned pins were a mistake on first batch of boards, they can't really change it now since it would breakcompatibility with all the shields done since then

author: brucehoult
author_rank: Super Contributor
timestamp: July 05, 2020, 12:05:39 am
content: Quote from: sleemanj on July 04, 2020, 01:50:50 pmI strongly disagree with that.While it is of course true that in any given hardware project configuration the pin assignments don't change, it is not true for the library writer. Most Arduino projects use libraries that provide things such as Object-Oriented interfaces to bit-banged facilities such as SPI, UART, WS2812 LEDs or whatever. The user instantiates objects with particular pin assignments which can vary from project to project, and there can even be more than one object in a given project. For the code inside the library the pin numbers are not at all literal constants.An Object-Oriented interface is potentially better than a function interface in this regard because with functions the user has to pass the pin number on every call, which is necessarily inefficient (unless the function is so trivial it can be inlined). With an OO interface you can choose to have the constructor do a logical pin to port address + mask mapping (if ports are memory-mapped) and just use those in the other functions. But most library writers don't bother and just save the logical pin number. Which is absolutely fine in most cases.

author: sleemanj
author_rank: Super Contributor
timestamp: July 05, 2020, 04:20:01 am
content: Quote from: brucehoult on July 05, 2020, 12:05:39 amFair point, but when ever hardware interface libraries (and libraries that use those libraries) are concerned that's of course moot, ie, the (typical) Wire library doesn't bit-bang with digitalWrite or otherwise, it uses theSPII2C hardware.Unfortunately, AFAIK, __builtin_constant_p is considered before Link-Time Optimization (LTO) happens so the constant-folding etc of LTO as the linked whole doesn't really help a lot.It is irksome that the Arduino ecosystem does notallowany way for the "sketch writer" to edit a header that can be included by libraries (ie, you can't have Configuration.h included by libraries). The Arduino powers that be refuse to implement it.But, it certainly is possible to write libraries such that they still deal with user specified compile time constants by using C++ templates, although it's unusual in the Arduino world to use templates. However for example FastLED (WS2811 etc) is a notable exception with very very heavy reliance on templates internally.This thread seems to have got derailed a bit, it was originally about millisecond counting!

author: Siwastaja
author_rank: Super Contributor
timestamp: July 05, 2020, 06:10:00 am
content: Library writer does not need to use digitalWrite() because library writers are not artists with zero programming experience.The artist, however, tries to bitbang an interface with digitalWrite() sooner than later. Now the artist is exposed to the low level because the solution is not working and the artist is wondering why. The only reason for digitalWrite()s existence was to avoid this.

author: sleemanj
author_rank: Super Contributor
timestamp: July 05, 2020, 08:14:35 am
content: Quote from: Siwastaja on July 05, 2020, 06:10:00 amYou make the assumption that the library is only intended to run on one specific architecture or that the library author should need to care about the specific vagaries of every archicture, indeed every chip within every architecture.The Arduino collection of abstractions such as digitalWrite have been ported to many architectures.digitalWrite (and co) were not brilliantly implemented abstractions, but the abstraction itself is good.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 05, 2020, 08:22:04 am
content: No, not making such assumption. I'm asserting library writers must be able to create a portable library for different platformswithouthavingto use digitalWrite(), exactly this digitalWrite() and how it is implemented. They haveoptionsbecause they are not (or should not be) artists without any programming experience. This is what many libraries do exactly because of the crappiness of digitalWrite().Heck, it's just the opposite, if the librarydoesuse digitalWrite() instead of rolling a better-working abstraction, they haveliterallylimited the portability to the Arduino ecosystem only.Claims that "abstraction itself is good" are meaningless and lead to poor abstractions for the sake of abstraction. Measuring theresultsdirectly, instead of the means, leads to better, eh, results. In this case, what youreallywant and are referring to, is "portability", which isn't very good with Arduino libraries, so digitalWrite() clearly isn't solving any portability problems.

author: mikerj
author_rank: Super Contributor
timestamp: July 05, 2020, 08:58:01 am
content: Quote from: Siwastaja on July 05, 2020, 08:22:04 amDo you consider Arduino as some kind of professional development system? It wasliterallydesigned as a "my first microcontroller" for people that wanted to make interactive art projects who had little programming experience. It includes a certain set of compromises to achieve that, if you don't like those compromises why are you using it when other alternative exist?

author: brucehoult
author_rank: Super Contributor
timestamp: July 05, 2020, 09:45:46 am
content: Quote from: Siwastaja on July 05, 2020, 06:10:00 amThe solution *does* work, in the vast majority of cases.The readme for the digitalWriteFast library you pointed to says that theirs takes 0.125 us on an Uno (i.e. ATMega328 @16 MHz), while the standard library function takes 6.28 us.Most users, doing the usual things of controlling LEDs or motors or steppers etc would not even notice the difference, let alone care about it. Most Arduino users aren't interested in doing things below the ms time grain level.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 05, 2020, 10:06:11 am
content: Quote from: mikerj on July 05, 2020, 08:58:01 amEx-act-ly!