[arduino] can't subtract two time stamps - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: October 20, 2021, 08:00:12 am
content: I am trying to calculate a speed based on how many pulse's a proximity sensor next to a toothed wheel has generated in an amount of time.Code:uint32_t varLastSpeedTime;volatile int16_t vTachoSpeedCounts;void lcdSpeedPrint() {uint32_t tempvarTime = millis();uint32_t vtSpeed = (vTachoSpeedCounts * tachoStep * 600) / (millis() - varLastSpeedTime);int32_t tvTimeDifference = ((int32_t)tempvarTime - (int32_t)varLastSpeedTime);char text = {0, 0, 0, 0, 0, 0};char counter = 0;char temp;lcd.print(millis());lcd.setCursor(0, 2);lcd.print(vTachoSpeedCounts);lcd.setCursor(0, 3);lcd.print(tvTimeDifference, DEC);varLastSpeedTime = millis();vTachoSpeedCounts = 0;The difference between the current time and the last time always results in some randomly low number lik 6 or 9 or 7.Initially "vTachoSpeedCounts" was called "varTachoSpeedCounts" and it failed to update. I don't know if anything starting with "var" has some other meaning in Arduino but the colour of the text never changed. Unfortunately colour highlighting on the arduino IDE is very limited and you can't tell the difference between things like variables and defined text.

author: Nusa
author_rank: Super Contributor
timestamp: October 20, 2021, 08:58:46 am
content: First thing I'd do is replace all but the first millis() call with tempvarTime. I guarantee the last millis() call isn't returning the same number as the first.

author: Simon
author_rank: Unknown
timestamp: October 20, 2021, 09:24:18 am
content: Nope, not that the calculation the only other call to millis is in is being used. I gave up displaying the speed and just wanted to see the time difference come out right. As the function is called around every 2s I should get something just over 2000.vTachoSpeedCounts will count up if I don't reset it, but when I do and put a signal in at 20Hz I get 0,this is absurd and annoying because it's not like I can't write a program to do this, I'm trying to get a hitachi controller based display to work on a SAMD so that I can for now ditch this arduino crap but converting peter fleury's library from AVR to ARM is a good bit harder than converting from one AVR to another.

author: brucehoult
author_rank: Super Contributor
timestamp: October 20, 2021, 09:33:29 am
content: Of course it doesn't matter what the variable names are.And it is evident that your function is being called about every 10 ms not every 2 seconds as you state. Which is why the count is zero for a 20 Hz signal.That's your problem.

author: Simon
author_rank: Unknown
timestamp: October 20, 2021, 09:37:58 am
content: why is it evident that it is being called every 10ms? This is just one function in a program. As you can see I am putting the current millis() on the display, this does indeed every 2s update (very visibly) by another 2000 and a bit).If I increase my signal to 1kHz I get "8" as an answer.Code:void tachoUpdate() {vTachoSpeedCounts ++;tachoDistanceCounts ++;}And yes so long as the name started with var it never updated but will sort of now. It's crazy

author: Simon
author_rank: Unknown
timestamp: October 20, 2021, 10:12:29 am
content: Complete project attached. I have attempted to use multiple files in the same way you would in any C/C++ IDE.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 20, 2021, 10:22:14 am
content: Signed int wraparound is implementation defined behavior and may or may not work with luck.Always cast timestamp to unsigned types, do unsigned subtraction. You can then cast the result to signed type.Didn't look the code further, there may be other issues as well.

author: brucehoult
author_rank: Super Contributor
timestamp: October 20, 2021, 10:51:28 am
content: Quote from: Simon on October 20, 2021, 09:37:58 amImpossible. Just simply impossible.

author: Simon
author_rank: Unknown
timestamp: October 20, 2021, 10:52:07 am
content: It makes no difference. The value is always positive so the issue will only occur if I exceed 32s which I do not plan to. The lcd.print() fucntion apparently will want signed integers. I guess that providing I do not exceed half of the storage limit of the variable type it will work with signed or unsigned as it does in their own example of printing millis() to the screen.I have tried with various variable types but the result is always the same. Just the same as the variable counts on the interrupt routine never come out right, but the distance travelled calculation works fine.Distance travelled has a variable that is updated on every interrupt and speed calculation also has a variable of the same type that is updated in the same interrupt function. So one variable will update every time but the other not? if I just update both it works, as soon as I zero the speed calculation variable on the exit of the function called every loop it suddenly stops working.

author: Psi
author_rank: Super Contributor
timestamp: October 20, 2021, 12:13:26 pm
content: I dunno how that lcd library works, but if it does hard delays to get the LCD timing right it will cause you major problems because you are disabling interrupts before any LCD stuff starts then re-enabling interrupts after the LCD stuff is finished.Think of CPU execution time inside void loop() as a pie chart showing the percentage of cpu cycles with interrupts on vs off.Hard delays uses many 10000 of cpu cycles. So if the total cycles for each loop of void main() is 10000 of pointless delay cycles + 100 cycles running your code that means your interrupts are off for like 99% of the time which would make you miss lots of tacho edge interrupts.I cant see why you should need to disable interrupts to write to the LCD.Except maybe if the library is using hard delays and doesn't want anything else messing with the timing. But if that's the case that is also the problem.Try take out your interrupt enable and disable lines and see what happens.Any LCD library that uses hard delays is badly written.

author: Simon
author_rank: Unknown
timestamp: October 20, 2021, 12:44:29 pm
content: I put the disable and enable interrupts in to make sure nothing was happening in the middle, turned out it's the same as with interrupts on all the time. Something is going really screwy. I am about to look up the math stuff like subtraction. Every time I do a subtraction the result is near 0.

author: Psi
author_rank: Super Contributor
timestamp: October 20, 2021, 01:28:55 pm
content: Go back to basics. Check output of the mills() function firstDisplay it on your LCD and confirm it makes sense.Sure it will be changing super fast and the lower digits will jump and miss some values, but you can see if it appears to be changing at the right speed.Maybe your clock or maybe the freq #define is not right and mills function thinks the cpu is running slower/faster than it really is.basic add/sub calcs not working is usually an indication you are doing something wrong with signed vs unsigned variables. Or that something is not set 'volatile' and it should be.

author: Simon
author_rank: Unknown
timestamp: October 20, 2021, 01:40:57 pm
content: Quote from: Psi on October 20, 2021, 01:28:55 pmYes I have done that already, if I:lcd.print(millis());I see the time on the dispaly, in this case it increments by a little over 2000 at each update as the function is called every 2s. It's the moment I try to subtract 2 numbers.I have the sketch in multiple files, if a lack of a volatile is the problem then every damn variable needs to be volatile. When I call a variable not declared in the function what is supposed to happen? I have written this as I would any C project and never had all this before when I am bare metalling it.I am currently investigating ripping the libraries I need out of the arduino and rewriting all the low level stuff myself like pinMode and digitalWrihe ect. Yes the LCD library is full of delays. Actually doing delays of a few µs with interrups I guess does not leave much time for anything else to happen anyway and if they deem that having the exact delay is more important they have made a choice to make a mess of things. If an interrupt occurs during a delay surely the interrupt is serviced?If I am using a delay I would happily accept that the delay may be longer if I know something useful is being done.

author: Psi
author_rank: Super Contributor
timestamp: October 20, 2021, 09:39:47 pm
content: I don't think your issue is a volatile issue, i had a look and it seemed to be set correctly. Only the two variables in the interrupt need to be volatile and yours both are.

author: Psi
author_rank: Super Contributor
timestamp: October 20, 2021, 09:43:46 pm
content: Quote from: Simon on October 20, 2021, 01:40:57 pmI wouldn't bother looking at pinMode /digitalWrite.Those are core functionality and will be fine.The LCD module could be part of the issue as that is not a core module.Maybe add a single LED you can flash and comment out all the LCD stuff temporarily.Just to see if things seem to work with it not there.

author: Psi
author_rank: Super Contributor
timestamp: October 20, 2021, 09:47:18 pm
content: Quote from: Simon on October 20, 2021, 01:40:57 pmOnly if you have interrupts enabled, which your original code didn't. It had them off for all the LCD stuff. but you said you removing that, so that's all good now. Unless of cause there are calls to turn interrupts off hidden inside the LCD module. There should not be, but you can never be sure with Arduino libraries.One thing, I see in some places you have a space between the variable name and ++That might work, but i'm not sure, ive never seen anyone put a space there before. Obviously it does compile but maybe remove those spaces just to be sure.

author: brucehoult
author_rank: Super Contributor
timestamp: October 21, 2021, 12:11:51 am
content: Quote from: Psi on October 20, 2021, 09:47:18 pmffsWhitespace changes nothing in C, except (obviously) between two adjacent identifiers such as a type and variable name in a declaration e.g. "int foo" but not "intfoo". Or inside a string literal. Or the newline at the end of a // comment or #define #if etc (which are all preprocessor, not C)There is a scary amount of uninformed superstition in this thread.

author: Psi
author_rank: Super Contributor
timestamp: October 21, 2021, 02:09:19 am
content: Quote from: brucehoult on October 21, 2021, 12:11:51 amWhitespace is "supposed" to change nothing. Doesn't mean it always does, for all we know he is using some experimental compiler with a bug in it. Or he copied that line from a webpage and it put some unicode blank char in there, maybe it's not a space char at all. etc..When searching for code problems and you see something that's very unlikely to be the cause of the problem but it does look a bit odd, you might as well fix it if doing so takes 2 seconds.There is always a tradeoff between likeliness to be the cause of the problem verses how easy it is to fix.And before you start going on about me saying "only variables inside the interrupt need to be volatile" Yes I know that is not strictly true and they only need to be volatile if accessed both inside and outside the interrupt. There is no point making things overly complicated when trying to explain basic things.

author: brucehoult
author_rank: Super Contributor
timestamp: October 21, 2021, 02:56:32 am
content: Quote from: Psi on October 21, 2021, 02:09:19 amHe's using the Arduino IDE with AVR. That means gcc, which is the most heavily used and debugged compiler that has ever existed.

author: Simon
author_rank: Unknown
timestamp: October 21, 2021, 05:45:10 am
content: No it is a SAD21G18A (MKR zero) The spaces are typed spaces by me. The liquidcrystal files don't have any disabling of interrupts in them. If there is a better place to get libraries from I'd happily give those a go short of writing my own in C for the hassle that these are to work out.So this is a lashup for the customer, it just needs the display to work counting the pulses. Further down the line I need to work with an SD card, RTC and Bluetooth module so I guess that if I can make the arduino LCD library work without the arduino I have half a chance of adding all of the other functionality.

author: Psi
author_rank: Super Contributor
timestamp: October 21, 2021, 01:33:05 pm
content: I have zero experience running Arduino on a ATSAMD21 processor.An arduino compatibility issue is possible, how stable is arduino considered with that core?

author: Simon
author_rank: Unknown
timestamp: October 21, 2021, 05:39:08 pm
content: no idea, I don't consider the arduino stable full stop, not the first time I have an issue, the last was on the UNO of all boards!!!

author: boffin
author_rank: Unknown
timestamp: October 21, 2021, 07:54:07 pm
content: I did some tach work with a pin change interrupt just a month ago and had no issues, I can post the code if you want later today..

author: Simon
author_rank: Unknown
timestamp: October 21, 2021, 08:54:31 pm
content: Sure, but my problem here is that this should work, if I can display one time or the other on their own why is it that the moment I subtract them the answer is less than 9? I am sick of the arduino. Why can I make that function run every 2s reliably by doing newmillis-oldmillis and then have the exact same thing fail in another instance. How can I have two identical type variables that should both update in the interrupt but only one works? I'm done with this shit!

author: Psi
author_rank: Super Contributor
timestamp: October 21, 2021, 11:24:25 pm
content: Make a copy of your project, then start pulling out everything except lines needed to demonstrates the bug.

author: Simon
author_rank: Unknown
timestamp: October 22, 2021, 05:48:21 am
content: Yea will do, I'll try putting it in the main loop. but this sort of flakyness will not do. The only thing is that the guy that designed this based it on a MKR Zero but took the 32.768 kHz crystal out. He had to do his own board file for it as it was looking for the clock to stabilize and would not start. I don't know if there is more in there that may be a problem. But then this works to control the main loop timing just fine but not the timing of the tacho signals so it's still very odd.

author: semir-t
author_rank: Regular Contributor
timestamp: October 22, 2021, 07:13:52 am
content: Code:if ((millis() - varLastLoopTime) > constLoopIntervalMs) { // check time since last loop counter updateloopCount ++; // update loop countif (loopCount > 10) loopCount = 0; // if loop count has eceeded 10 resetvarLastLoopTime = millis();}if (loopCount == 10){task2s();}Suggestions:1. Don't be lazy, improve the code readability . Use the brackets2. You are not taking in the account the overflow when subtracting the varLastLoopTime from the millis(). millis() will overflow at one point and your program will not behave like expected3. Try to print out the values for the tempvarTime and v_LastSpeedTime using the Serial output.

author: Simon
author_rank: Unknown
timestamp: October 22, 2021, 07:26:45 am
content: Millis will overflow after some days, this will never run for days. It's a machine that is carried around and used for an hour at a time. I'll have another stab at it.

author: semir-t
author_rank: Regular Contributor
timestamp: October 22, 2021, 07:42:20 am
content: Yes, I understand that the product won't work long enough for the overflow to occur and I saw that you mentioned this in this thread. I was just making a suggestion because you don't won't to reinvent the wheel each time. In my projects (rarely use Arduino platform) I use my custom library based on the millis() where I have a function that checks if the timeout happened, something similar as yours but I have included the overflow check. I wrote this library once and benefit is that I know that it is working and it is easy to use.But, nevertheless there is something odd with the code or Arduino that you are using. I am interested to see what the Serial.print will tell you, and it would be great if you could get lot of debugging messages out just to see what is happening.

author: Simon
author_rank: Unknown
timestamp: October 22, 2021, 08:13:23 am
content: Well I now have:Code:void lcdSpeedPrint() {uint32_t varCurrentSpeedTime = millis();int32_t vtSpeed = (vTachoSpeedCounts * tachoStep * 600) / (varCurrentSpeedTime - vLastSpeedTime);char text = {0, 0, 0, 0, 0, 0};char counter = 0;char temp;lcd.setCursor(0, 2);lcd.print(varCurrentSpeedTime);lcd.setCursor(0, 3);lcd.print(vLastSpeedTime);vLastSpeedTime = millis();}the two time stamps are indeed just a few ms apart...... This is getting weirderso in I just put in my main loop in the what should happen every 2sCode:void loop() {if ((millis() - varLastLoopTime) > constLoopIntervalMs){ // check time since last loop counter updateloopCount ++; // update loop countvarLastLoopTime = millis();if (loopCount > 10) loopCount = 0; // if loop count has exceeded 10 reset}if (loopCount == 10){if (backlightstate == 1){digitalWrite(lcdBackLightPin, LOW);backlightstate = 0;}else{digitalWrite(lcdBackLightPin, HIGH);backlightstate = 1;}//task2s();}}That turns the lcd backlight on and off for 2s each. but if I make the else and else if it does not work properly it flickers like that bit runs all the time, is this a C++ versus C thing? in C "else if" means that only if the first thing was not satisfied will I test the second, rather than do the second regardless.

author: semir-t
author_rank: Regular Contributor
timestamp: October 22, 2021, 08:29:53 am
content: You should try to define varLastLoopTime as volatile.Code:volatile unsigned long varLastLoopTime;Also, I would reset this variable in the part of the code where we check if current value is equal to 10. SoCode:void loop() {if ((millis() - varLastLoopTime) > constLoopIntervalMs) { // check time since last loop counter updateloopCount ++; // update loop countvarLastLoopTime = millis();}if (loopCount == 10){loopCount = 0;task2s();}}There shouldn't be any difference between the C and C++ for the else if statement.

author: retiredfeline
author_rank: Frequent Contributor
timestamp: October 22, 2021, 08:54:14 am
content: Code:void loop(){if ((millis() - varLastLoopTime) > constLoopIntervalMs) { // check time since last loop counter updateloopCount++; // update loop countvarLastLoopTime = millis();if (loopCount > 10)loopCount = 0; // if loop count has exceeded 10 reset}if (loopCount == 10) {if (backlightstate == 1) {digitalWrite(lcdBackLightPin, LOW);backlightstate = 0;} else {digitalWrite(lcdBackLightPin, HIGH);backlightstate = 1;}//task2s();}}I reformatted the code to be more readable.Consider what happens when loopcount reaches 10, but ((millis() - varLastLoopTime) > constLoopIntervalMs) is not true. Then this section of the code will be executed as fast as loop() can be called repeatedly.Code:if (loopCount == 10) {if (backlightstate == 1) {digitalWrite(lcdBackLightPin, LOW);backlightstate = 0;} else {digitalWrite(lcdBackLightPin, HIGH);backlightstate = 1;}//task2s();}semir-t's suggestion of resetting loopcount in that part of the code would also fix it.

author: Simon
author_rank: Unknown
timestamp: October 22, 2021, 09:04:58 am
content: The idea is that the loops will be counted up to 10 but other timed functions may run more often, so all I have to do is do a "loopcount % 'a number' " and run at any multiple of 200ms any event I want. I will probably be letting loopcount overflow in the end and check the modulus as if i want something to run every 4 counts on a 10 top out that will cause a delay every 2 cycles.

author: Simon
author_rank: Unknown
timestamp: October 22, 2021, 09:20:04 am
content: So now i have:Code:#define lcd_rs_pin 6#define lcd_enable_pin 10#define lcd_d4_pin 9#define lcd_d5_pin 8#define lcd_d6_pin 19#define lcd_d7_pin 15#define lcdBackLightPin LED_BUILTIN#define tachoSensor 5#include <LiquidCrystal.h>// configuring LCD data pinsLiquidCrystal lcd(lcd_rs_pin, lcd_enable_pin, lcd_d4_pin, lcd_d5_pin, lcd_d6_pin, lcd_d7_pin);//#include "2s_tasks.h"//#include "tacho.h"const unsigned int constLoopIntervalMs = 200;uint8_t backlightstate;unsigned int loopCount = 0; // countsunsigned long varLastLoopTime = 0;uint32_t vLastSpeedTime;void setup() {// set up the LCD's number of columns and rows:lcd.begin(20, 4);pinMode(lcdBackLightPin, OUTPUT);digitalWrite(lcdBackLightPin, HIGH);pinMode(tachoSensor, INPUT);//attachInterrupt(digitalPinToInterrupt(tachoSensor), tachoUpdate, FALLING);lcd.clear();}void loop() {if ((millis() - varLastLoopTime) > constLoopIntervalMs){ // check time since last loop counter updateloopCount ++; // update loop countvarLastLoopTime = millis();}if (loopCount % 10 == 0){uint32_t varCurrentSpeedTime = millis();// int32_t vtSpeed = (vTachoSpeedCounts * tachoStep * 600) / (varCurrentSpeedTime - vLastSpeedTime);char text = {0, 0, 0, 0, 0, 0};char counter = 0;char temp;lcd.setCursor(0, 2);lcd.print(varCurrentSpeedTime);lcd.setCursor(0, 3);lcd.print(vLastSpeedTime);vLastSpeedTime = millis();}}So now I just get identical numbers on the display.

author: semir-t
author_rank: Regular Contributor
timestamp: October 22, 2021, 10:03:31 am
content: Nice one retiredfeline. At first sight that part of the code cached my attention, but I didn't saw this bug. I just suggested what I always do and what made sense to make the code more understandable.I can't understand now why would you use loopCount % 10 == 0 ? And please, use brackets. Don't let the compiler chose the operation order. So, if you really want this line of the code it should be like this (loopCount % 10) == 0Even some compilers (depending on the flags you set) would warn you about this statement.

author: Simon
author_rank: Unknown
timestamp: October 22, 2021, 10:17:46 am
content: Yea I did put those brackets in, but it made no difference. You are all looking at the wrong thing here. this code works:Code:void loop() {uint32_t timenow = millis();if ((timenow - varLastLoopTime) > constLoopIntervalMs){ // check time since last loop counter updateloopCount++; // update loop countvarLastLoopTime = millis();// int32_t vtSpeed = (vTachoSpeedCounts * tachoStep * 600) / (varCurrentSpeedTime - vLastSpeedTime);if (backlightstate == 1) {digitalWrite(lcdBackLightPin, LOW);backlightstate = 0;} else {digitalWrite(lcdBackLightPin, HIGH);backlightstate = 1;}lcd.setCursor(0, 2);lcd.print(timenow);lcd.setCursor(0, 3);lcd.print(vLastSpeedTime);vLastSpeedTime = millis();}}The difference is that I took the statement out that gives me a multiple of the basic loop tic, so I can take the time stamps at the start and end of the main loop, but I can't in other code in a seperate block. So while I can make the lastup work, I am not prepared to do a production project like this. Basically i have to work all in one file? and in one function.

author: Nusa
author_rank: Super Contributor
timestamp: October 22, 2021, 11:11:48 am
content: Quote from: semir-t on October 22, 2021, 08:29:53 amOf course, when there are no "else if" statements in the code, it hardly matters.

author: Psi
author_rank: Super Contributor
timestamp: October 22, 2021, 11:51:56 am
content: Quote from: Simon on October 22, 2021, 10:17:46 amCan you create two versions of your code.One that works and one that does not work using functions instead of all in void loop

author: Simon
author_rank: Unknown
timestamp: October 23, 2021, 12:29:33 am
content: yes, basically if you call millis() from a function that is too deeply nested things go wrong. I vaguely remember reading something about this a long time ago, naturally because arduino is perfect this information is no longer on their website under the milis() page. I think it was a maximum of 2 levels, even an if block stops it from working. I's a joke.

author: Psi
author_rank: Super Contributor
timestamp: October 23, 2021, 01:06:35 am
content: hm. This is sounding like a bug in the setup of the compiler for the ARM core in the ATSAMD.That is well outside my experience so I have no idea how to check that.If you simplify it to two examples one that works and one that doesnt and post them to the arduino forum someone might be able to help debug that.Oh, one minor thing you could try.Change all your unsigned long variables to uint32_t variables.It's probably not that, but worth a try. I'm not sure what unsigned long is actually being converted to by your compiler. uint32_t is more fixed, an unsigned int of 32bits long.It could also be a bug with the millis function itself.ARM cores do actually have a dedicated systick timer for getting millisecond ticks. I dunno if millis is trying to use that or doing the tick some other way, but you should be able to use the systick timer directly. Check for some examples of using systick on arduino with AT-SAMD

author: brucehoult
author_rank: Super Contributor
timestamp: October 23, 2021, 02:18:57 am
content: Quote from: Simon on October 23, 2021, 12:29:33 amNo. That is ridiculously unlikely to be true.

author: semir-t
author_rank: Regular Contributor
timestamp: October 23, 2021, 06:17:41 am
content: And I agree fully with this. There is no way that nesting can affect the functionality of the code. Most likely, by adding the code you add some bugs that produce undesired behavior. This thread is the perfect example, because the problem was that the time between two calls was to small ( we didn't want this functionality but by writing our code we created this bug) but someone pointed out that there is a bug that when the counter is equal to 10 and the timeout didn't happen, function will be called as fast as possible.So, if you want us to help you best way is to share the example that it doesn't work and one that is working. Basically, more info you give it will be easier to find the bug because most certainly bug is somewhere in your code. As bad as some Arduino libraries are, millis() is present for a long time and has been heavily tested and functions should be really stable.

author: ledtester
author_rank: Super Contributor
timestamp: October 23, 2021, 06:55:11 am
content: Quote from: Simon on October 23, 2021, 12:29:33 amI believe this is the code for millis() for AVR devices:https://github.com/arduino/ArduinoCore-avr/blob/44dc454b9382298fa8be542c8c92e7944d9aa21e/cores/arduino/wiring.c#L65and for the SAMD21:https://github.com/arduino/ArduinoCore-samd/blob/39f483e901a6c7c10cbacc67df2048502913cf8b/cores/arduino/delay.c#L28

author: Simon
author_rank: Unknown
timestamp: October 23, 2021, 10:15:35 am
content: Quote from: semir-t on October 23, 2021, 06:17:41 amYes the arduino is of course perfect which is why someone that I beleive is a contributor had to put on his own forum not the arduino website haw to avoid issues caused by the arduino IDE itself:https://www.gammon.com.au/forum/?id=12625i also do very well remember there once being a mention of not calling millis() from a function too deeply nested, but again the arduino is perfect so that information has been removed. maybe they thought they had it fixed. On monday I will have a look at taking a copy of my working code and hammer it back into what did not work.

author: semir-t
author_rank: Regular Contributor
timestamp: October 23, 2021, 10:43:04 am
content: Quote from: Simon on October 23, 2021, 10:15:35 amI think you are missing a point here. I can see that you are frustrated with the Arduino and you should be, because when it comes to doing some serious stuff Arduino platform is pain in the a**. Most people are tempted with how easily you can program the board but whenever you start doing some serious stuff it fails, and it fails miserably.That is way a had 2 suggestion, either you get familiar with the libraries or you write your own stuff. Either way, then you will know that is really happening.That being said, by the code that you provided previously we can't say that the problem is in fact with the millis() function. I worked a lot with the millis() function (I had to because it was required to use Arduino) and I have to say that I didn't have this problem, and this function was in other .cpp files and not in the .ino file.So, if you do have that problem. Create the example, share it with us and tell us which board you are using. Maybe someone will try to recreate that problem and will be able to help you. At this point it is just guessing game if the millis() is correct or not.

author: Simon
author_rank: Unknown
timestamp: October 23, 2021, 12:30:37 pm
content: The first full sketch I uploaded in reply #5 does not work, I have posted here code that does work and code that does not. I can post more on Monday. The idea of the arduino is a good one but I have no idea what the arduino does to my code before it is compiled and it seems this can be an issue. When I first started in programming it was with the arduino but as I needed access to the hardware I converted my first commercial project from arduino to C, from there I was comfortable working with the hardware directly. But these days I do have the need for functionality that either I spend a long time learning for no reason other than to reinvent the wheel and write by own libraries or I use libraries. The arduino is again attractive in this sense but compared to a real no nonsense IDE feels like a toy that itself is playing too much with my code and does not give me any tools.

author: brucehoult
author_rank: Super Contributor
timestamp: October 24, 2021, 02:54:15 am
content: Quote from: Simon on October 23, 2021, 10:15:35 am"the Arduino". WTF even is that? There are several independent factors at play here- the ATMega328 or other CPU- the C compiler, usually gccNeither of those depends on it being "Arduino" instead of Atmel Studio in any way.- the Arduino libraries, including millis()- the Arduino IDEWhat the above link is referring to is a custom preprocessor run by the Arduino IDE before running gcc. The aim is to simplify programming for beginners and to make the "Wiring" language compatible between C++ and Java by automatically adding prototypes for all declared functions at the start of the program, so that you can declare your functions in any order (like in Java).QuoteNothing unique to millis() there. That applies to calling ANY function on a tiny CPU with very limited RAM. The ATMega328 only has 2k of RAM so if you have a lot of global variables (or use malloc() carelessly) and have deeply nested functions then there is a risk of them colliding.That is completely independent of using the Arduino IDE vs programming in any other language or environment.I had a look at millis() and on avr gcc 5.4.0 and earlier the stack space used by millis() is ... precisely 2 bytes for the return address (3 bytes on Mega2560). I don't know about Arduino on ARM. The code does look more complex there, but nothing extreme. But you probably also have more RAM to play with.QuoteDo you seriously not use a code repository?

author: Psi
author_rank: Super Contributor
timestamp: October 24, 2021, 06:03:46 am
content: Quote from: brucehoult on October 24, 2021, 02:54:15 amHe is using Arduino on a ATSamD21G18A (ARM core)

author: westfw
author_rank: Super Contributor
timestamp: October 24, 2021, 08:52:57 am
content: (paraphrasing)QuoteNone of those are true. You sound like a conspiracy theorist.I ran your code from #5 through Arduino with all the warnings turned on, and took a general look through. I don't have your hardware setup, so I can't duplicate all the testing. I changed the LCD output to Serial, and it prints stuff about every 2 seconds.These look like real problems (use of boolean "!" operator where there should be a bitwise "~"? I'm not real clear on what it's trying to do.)Code:/Volumes/MacOS/HD-Downloads/Downloads/lashup/button_inputs.ino: In function 'void buttonRead(unsigned char)':/Volumes/MacOS/HD-Downloads/Downloads/lashup/button_inputs.ino:11:46: warning: '<<' in boolean context, did you mean '<' ? [-Wint-in-bool-context]11 | varButtonState = (varButtonState & !(0x1 << button)) | (0x1 << button);| ~~~~~^~~~~~~~~~/Volumes/MacOS/HD-Downloads/Downloads/lashup/button_inputs.ino:15:50: warning: '<<' in boolean context, did you mean '<' ? [-Wint-in-bool-context]15 | varButtonPressed = (varButtonPressed & !(0x1 << button)) | (0x1 << button);| ~~~~~^~~~~~~~~~And here, it bothers me that tempstring[] can apparently be up to 14 characters, while the text[] string that it's being copied to can only have 5. (whether that actually happens is a separate question, I guess.)Code:void asciiDistance(long vartoconvert, char *string) {unsigned char tempstring;:while (tempcounter != 0) {string = tempstring;tempcounter--;stringcounter++;}}void lcdDistancePrint() {noInterrupts();long distance = tachoDistanceCounts * tachoStep / 100;char text = {0, 0, 0, 0, 0, 0};asciiDistance(distance, text);:There is a bunch of use of "uint8_t" and "uint16_t" and similar that would be a good idea on an AVR, but is un-useful to harmful on an ARM.The stack pointer for a SAMD21 image is initialized to 0x20008000 (the end of the 32k of RAM), so stack-overflow - the only reason I could imagine that "millis() fails if call from "too deeply nested", seems extremely unlikely. (And I've never heard of any such thing.)-----There are annoying things about "Arduino", but in the end it's a very shallow framework on top of standard well-trusted tools, and it has a LOT of users. If you're going to make extraordinary claims about serious and should-be-obvious bugs and misbehaviors, we (well, I) will need extraordinary proof. That means code and instructions for a (hopefully very minimal; ie no LCD, no actual tach hardware) hardware setup that demonstrates the problem.Nick Gammon was never an official Arduino employee, though he was a significant contributor to the forums for quite a while. His personal forums and web pages pre-date the Arduino by a long time, so it makes sense that he'd post info of long-term interest there. He hasn't been around the Arduino forums for a while, and I don't recall him posting a reason for leaving. I suspect he got tired of rude users, rather than of the administration.) I've never seen the forums censor people for technical info critical of Arduino code/etc; only for ... rudeness to other people. (And I would have, having been a main player in the "Freeduino" project, back when it looked like the HW might go "closed source.")

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 08:53:41 am
content: Quote from: brucehoult on October 24, 2021, 02:54:15 amYes but this is a dumbed down system for beginners meant to work on several different processors with no regard for the hardware detail. What wiring program? I here this mentioned a lot is relation to the arduino but then I am told it's C/C++.A code repository, um yea, how do I get that to work with the arduino, at what point do I decide "this code won't work and never has but hey I should keep a copy for posterity, oh hang on over 2 days I have like 100 mods to this and the result of all 100 mods is the same - so how do I evaluate what to keep".

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 09:11:51 am
content: Quote from: westfw on October 24, 2021, 08:52:57 amYou have just gone through code that I have not tested yet and pointed at things I have not even tested yet. As you can see all of the button pressing stuff is not in use yet so has no baring. How about you comment on the code that will not work? Oh you just have a point to make, sorry next time I will throttle information to help me control the discussion so that I can be artificially right and make you wrong despite the fact it won.t help me....All I know is that things are not working and so far no one can say why. Your conspiracy theorist claims are stupid at best! soAs we are into conspiracy theories let's put you right:Arduino doesn't allow direct access to the hardware. - I never said that, I know very well that i can wright directly to registers but knowing that this could compromise existing code that libraries already depend on I would not be so silly as to do so without knowing what else I am effecting and with something that is already not working not the time to do it.Basic functions (millis(), subtraction, etc) in arduino don't work, perhaps because the ARM compiler is misconfigured. - No it does work, just look at the code, millis(), the 2s main ticker is working, but when millis() is used in deeply nested functions it stops working thus a subtraction seems to no longer work.The Arduino forums hide this by censoring their forums, forcing expert contributors off onto their own forums. - I have been at the receiving end of the over protectiveness of the arduino forum. It does just strike me as odd that there is all this information about how to get so much more done with the arduino making it truly powerful - not on their forums or website. And yes, I can assure you that I have read in the past that the millis() function cannot be called by functions deeply nested. As someone else has said this could be normal, if there is nothing about this in the instructions well what am i to assume? if I write a function in C I can call it from anywhere.The Arduino code is full of clever and complex (and thus indecipherable) C++ code. - I never ever said that it was complex and undecipherable. I have said from the start that I am being realistic about the fact that I not not yet understand C++ so pretending to will just make more problems for myself. I prefer to make work what I can now and get clever when I know what I am doing.I don't know who Nick Gammon is, I just know that his name is all over the arduino forum including a discussion about how he left for a bit and from my limited experience I can see why and that when he had something truly useful to say ho chose not to put it on their forum.Now, did you have anything useful to add?

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 09:22:37 am
content: Quote from: westfw on October 24, 2021, 08:52:57 amYes there is a problem there, thank you on that one. This code is untested and that is the first writing. As you can see it's not been called yet as I stopped work on that when i could not get the speed calculation to work.QuoteYes my conversion functions are a bit messy, they have been evolving and were just pulled out of something else. Why are uint8_t type stuff a problem? I like to know that things will be what I said I wanted them to be. They are also quicker to write.I have no idea what the stack overflow stuff is about. This is the arduino, I'm supposed to be an experienced programmer....

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 09:31:22 am
content: https://forum.arduino.cc/t/functions-limit/282065Nesting limits, as you can see it's not like i was going that deep. Never had a problem like this on straight C.

author: Psi
author_rank: Super Contributor
timestamp: October 24, 2021, 09:55:25 am
content: If anyone of your string functions try to write a value into an array at a position bigger than the size of the array you will overwrite memory and cause all sorts of unpredictable behavior.Have a look at this code belowIn lcdDistancePrint() This line calculates a numberCode:long distance = tachoDistanceCounts * tachoStep / 100;Then in asciiDistance()That number is used in this while loop as var to control how many iterations through the loop.The only thing that ends the loop is the div by 10 getting var to zero. So if var is huge you will overwrite lots of memory from your writes to tempstring[] which can only take 15 bytes.Code:while (var > 0){tempcounter++;if (tempcounter == 2){tempstring = '.';tempcounter ++;}tempstring = (var % 10) + 48;var = var / 10;}Maybe there is a variable not initialized somewhere, or maybe the timing is producing bigger numbers than you expected etc.The one thing you can be sure on, when your overwrite memory the result is chaos and nothing makes sense. Which appears to be what you are getting. As you change the code trying to find the problem that causes different code to be put in different locations so different things get overwritten. eg chaos. Maybe you're overwriting the stack and that is why you cannot go very deep in function callsGo through all your loops dealing with arrays and put an if statement in there that will do a 'break' statement if the array index gets to the max value for the array size.

author: brucehoult
author_rank: Super Contributor
timestamp: October 24, 2021, 10:31:45 am
content: Quote from: Simon on October 24, 2021, 09:31:22 am*Absolutely* nothing to do with the Arduino IDE or libraries or language.EVERYTHING to do with programming a tiny machine with almost no RAM, no matter what the language.

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 11:39:53 am
content: Quote from: Psi on October 24, 2021, 09:55:25 amYes I need to refine that code, I know in this case that the number will not exceed a certain limit. One day it will be a function in my own library file that I use as standard. As I have already explained the lower call to millis() does not give what I would expect and as soon as I made sure the call to millis was not too deeply nested it worked.

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 11:42:07 am
content: Quote from: brucehoult on October 24, 2021, 10:31:45 amAnd who decides how much RAM to allocate to the stack?

author: brucehoult
author_rank: Super Contributor
timestamp: October 24, 2021, 11:47:02 am
content: You do. The stack is all of RAM that has not been used by global variables or malloc().

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 11:53:27 am
content: Quote from: brucehoult on October 24, 2021, 11:47:02 amAnd how would I do that on an arduino based system? it's not like there is not RAM to spare, I have 32kB, that little program uses a fraction.

author: brucehoult
author_rank: Super Contributor
timestamp: October 24, 2021, 12:27:17 pm
content: You don't have to do anything. It just is.

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 12:28:47 pm
content: Quote from: brucehoult on October 24, 2021, 12:27:17 pmso there is plenty of ram available but the stack does not get enough to run the program, do you see my logic?

author: bingo600
author_rank: Super Contributor
timestamp: October 24, 2021, 12:32:43 pm
content: As Bruce saysStack is "normally" pointing to "End of Ram" , and grow downwards towards Zero.This happens "automatically by HW or the init routine".It's the programmers responsibility to make sure the stack doesn't grow down to "hit the "ram" variables".That creates weird results and erratic (often fatal) behaviour./Bingo

author: bingo600
author_rank: Super Contributor
timestamp: October 24, 2021, 12:47:17 pm
content: If you have 32K Ram , and no huge "local variable" definitions , they shouldn't normally "clash".Code:int func1(int x){char array ;int j;memset(array , 0x00, sizeof(array));j = func2(x);}int func2(int x){char array ;int j = 0;memset(array , 0x00, sizeof(array));if(x == 42)j = 1;return j;}main(){while(1){func1(42);}}This would prob. "explode" w 32k Ram.Both func allocates 20K on the stack , and func1 calls func2 , now 40k is allocated.Thought/constructed example , prob not realistic.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 24, 2021, 12:52:26 pm
content: Don't get fixated into the idea of stack running out due to too much function nesting. It doesn't happen easily unless you are writing recursive functions, or are on a device with say 128 bytes of RAM (some TINY series AVR).Instead, I'm almost sure you are just over/underindexing somewhere and messing up the RAM content. Such behavior is difficult to debug; I suggest you add some sort ofassertaround all suspect array accesses. You don't have too many. Use this pattern for example:Code:int array;access: instead of:arraywrite:if(i < 0 || i >= ARRAY_LEN)error(5); // different number in each placearrayvoid error(int n){// blink LED n times, print n to uart, whateverwhile(1);}This saves you a LOT of time, and do extend that to other types of error/sanity checks as well, not just array indexing.

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 01:43:47 pm
content: Quote from: Siwastaja on October 24, 2021, 12:52:26 pmBut I have just been told I control this or at least can....

author: langwadt
author_rank: Super Contributor
timestamp: October 24, 2021, 02:08:18 pm
content: Quote from: Simon on October 24, 2021, 11:42:07 amit set to the top of ram so it is all the ram that is used for anything elsehttps://github.com/arduino/ArduinoCore-samd/blob/master/variants/mkrzero/linker_scripts/gcc/flash_with_bootloader.ld

author: Siwastaja
author_rank: Super Contributor
timestamp: October 24, 2021, 02:25:09 pm
content: Quote from: Simon on October 24, 2021, 01:43:47 pmUsually this is configured so that stack space is all that remains after your static/global variables. Statics grow upward, stack grows downwards. If they touch, kaboom, you have corrupted something.So you can ontrol available stack by not wasting too much in static storage.Yes, stack overflow is a real problem which happens easily if yourun out of memory.It's much more likely you accidentally have some large like 16KB buffer and then run out of memory, than to run out of memory by calling nesting functions,each call wasting just 2 bytes(+ whatever variables they need on stack) or so. But it was posted earlier millis() only needs that 2 bytes.But of course, that 2 bytes can be the final straw if you have wasted all the memory! But you likely didn't waste it in 2-byte things, you wasted it on something bigger.So assuming we really have RAM corruption here, the two probable causes are,* Out of memory causing stack colliding with static storage,* Over/underindexing or faulty pointer somewhereDoes the Arduino IDE print out static memory usage information? If not, command line programavr-sizeshould tell you sizes of .data and .bss sections, their sum would be the static usage.Adding -fstack-usage on the GCC command line makes it output .su files which are quite human readable and tell you how many bytes each function needs. Add that 2-4 bytes per call for return address (depending on your architecture, I already forgot) and you have an idea how each function call chain contributes to stack usage.General advice is to carefully consider any buffers that are larger than a few % of the RAM. On any ATMega, for example, I wouldn't think too much about creating a char buffer, but if you need int, that's going to need some thinking. Making it static is the simplest way to make it appear in the RAM usage as shown by even simplest of tools.

author: retiredfeline
author_rank: Frequent Contributor
timestamp: October 24, 2021, 02:25:44 pm
content: Assuming lashup.zip still contains the code that you can't get to work, then the bug is right there in loop(). Here's loop():Code:void loop(){if ((millis() - varLastLoopTime) > constLoopIntervalMs) { // check time since last loop counter updateloopCount++; // update loop countif (loopCount > 10)loopCount = 0; // if loop count has eceeded 10 resetvarLastLoopTime = millis();}if (loopCount == 10) {task2s();}}Let's annotate that:Code:void loop(){if ((millis() - varLastLoopTime) > constLoopIntervalMs) { // check time since last loop counter updateloopCount++; // update loop countif (loopCount > 10)loopCount = 0; // if loop count has eceeded 10 reset// execution reaches here every 200msvarLastLoopTime = millis();}// execution reaches here very often whenever waiting for timeout// i.e. as fast as loop can be called, the if tested, found false, and loop returnsif (loopCount == 10) {// as above but during the 200ms when loopCount is 10task2s();}}To fix this you could add an else to the if:Code:void loop(){if ((millis() - varLastLoopTime) > constLoopIntervalMs) { // check time since last loop counter updateloopCount++; // update loop countif (loopCount > 10)loopCount = 0; // if loop count has eceeded 10 reset// execution reaches here every 200msvarLastLoopTime = millis();} else {return;}// execution reaches here once every 200msif (loopCount == 10) {// execution reaches here once every 2stask2s();}}Or put the code inside the body of the if:Code:void loop(){if ((millis() - varLastLoopTime) > constLoopIntervalMs) { // check time since last loop counter updateloopCount++; // update loop countif (loopCount > 10)loopCount = 0; // if loop count has eceeded 10 reset// execution reaches here every 200msvarLastLoopTime = millis();// execution reaches here once every 200msif (loopCount == 10) {// execution reaches here once every 2stask2s();}}}Or if you are not doing anything in the 200ms, might as well wait inside loop instead of exiting and reentering loop:Code:void loop(){while ((millis() - varLastLoopTime) <= constLoopIntervalMs) // wait for timeout;loopCount++; // update loop countif (loopCount > 10)loopCount = 0; // if loop count has eceeded 10 reset// execution reaches here every 200msvarLastLoopTime = millis();// execution reaches here once every 200msif (loopCount == 10) {// execution reaches here once every 2stask2s();}}Better still, as your loopCount actually divides by 11, not 10:Code:void loop(){while ((millis() - varLastLoopTime) <= constLoopIntervalMs) // wait for timeout;// execution reaches here every 200msvarLastLoopTime = millis();loopCount++; // update loop countif (loopCount >= 10) {loopCount = 0; // if loop count has eceeded 10 reset// execution reaches here once every 2stask2s();}}I pointed this out in post #31 but it seems you didn't understand.

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 02:46:52 pm
content: what difference does it make? yes the code will run around and around the loop constantly until a 200ms period has passed, every 200ms it has to make a note of the current time so that it can track the time from there to see when the next 200mms is up, when it has the counter is updated. When the counter gets to 10 the code executes, what did you think the program was to do when it has nothing to do other than run around in circles?The problem with millis is in the speed calculation which in nested a couple more levels down. When I take that code and put it directly in the if count == 10 it works and displays the speed rather than 0.In time more code will be added to the loop. This will be timed in the same way.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 24, 2021, 03:13:19 pm
content: Arduino Zero, i.e. ATSAMD21? That's a Cortex-M0+, and darnit, that one does not have a cycle counter in the DWT. On Cortex-M4's and others, DWT_CYCCNT is a monotonic cycle counter one can read and use to accurately measure intervals of up to 2**32 cycles (minute and a half at 48 MHz, minute at 72 MHz, and so on); all one needs to do is ensure the DWT is enabled.The core Arduino support for SAMD21 ishere, with linker scriptshere, pin definitionshere, and the Arduino main library stuffhere.If we look atSAMD core delay.c, we'll find a note that indicatesmillis()is not safe in an interrupt context, butmicros()is.(micros()calculates microseconds, and uses the same systick counter. It uses the counter value in addition to the number of cycles of the counter, thus getting somewhat better resolution. Since a 32-bit microsecond counter rolls over every 4294 seconds or so, it suffices for intervals of up to about an hour and eleven minutes.)My very first suggestion would be to replace every instance ofmillis()with(micros()/1000), and see if anything changes. Better yet,Code:void lcdSpeedPrint() {uint32_t tempvarTime = micros();uint32_t tempvarCounts = vTachoSpeedCounts;vTachoSpeedCounts = 0;uint32_t tvTimeDifference = (tempvarTime - varLastSpeedTime);varLastSpeedTime = tempvarTime;uint32_t vtSpeed = (vTachoSpeedCounts * tachoStep * 6) / (tvTimeDifference * 10); // *600/1000 = *6/10lcd.clear();lcd.print((int)tvTimeDifference, DEC);lcd.setCursor(0, 1);lcd.print((int)vtSpeed, DEC);lcd.setCursor(0, 2);lcd.print((int)vTachoSpeedCounts, DEC);lcd.setCursor(0, 3);lcd.print((int)tempvarTime, DEC);}With this, looking at the LCD output will tell us crucial information. I assume it has four lines, with at least 10 digits visible on each line.The first lcd.print prints the tachometer counting duration in microseconds: 1000 is one millisecond, 1000000 one second.The second lcd.print prints the calculated (RPM?) value. I didn't check the math, just copied it and adjusted to microseconds from the initial post.(If you asked me, I'd say the correct formula is(pulses* 60000) / (interval_in_microseconds*pulses_per_revolution/ 1000), but you can move the zeros from the multiplier to the divisor or vice versa to ensure both subexpressions can be properly represented as a 32-bit unsigned integer. As it stands, at less than second intervals, it works for up to 71582 pulses per second and 4294 pulses per revolution, before it craps out.)The third lcd.print prints, I assume, the number of tachometer pulses. If this stays zero, it means either that the variable is not being updated, or that it is updated in an interrupt but vTachoSpeedCounts is not marked volatile, and thus the compiler assumes it does not change (unlikely, though).The fourth lcd.print prints the microsecond timer value. If it does not change at all, it means the systick counter (hardware) is disabled. If only the three or four lowest/rightmost digits change, it means the systick interrupt is not firing correctly (perhaps because interrupts are disabled).This should be a relatively simple test, but provide much needed additional information (by observing what the LCD shows).

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 03:23:00 pm
content: I will give that a go tomorrow, this is why I am looking at ways to write code to deal with the hardware directly and use things like the LCD library that do not have much to do with the hardware, so basically put my own hardware code between the high level libraries and the hardware.

author: langwadt
author_rank: Super Contributor
timestamp: October 24, 2021, 03:36:51 pm
content: Quote from: Nominal Animal on October 24, 2021, 03:13:19 pmthat must be comment left over from some other processor, there is nothing unsafe about reading a 32 bit int on an M0

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 04:01:50 pm
content: M0+

author: langwadt
author_rank: Super Contributor
timestamp: October 24, 2021, 04:10:56 pm
content: Quote from: Simon on October 24, 2021, 04:01:50 pmstill safe, it is a 32 bit processor reading from 32 bit memory and it can't be unaligned

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 24, 2021, 04:25:57 pm
content: Yeah, readingstatic volatile uint32_t _ulTickCount;is atomic on Cortex-M0+; but if you look at the code, itcouldbe one millisec behind in certain corner cases. I likemicros()much better, even if it may iterate a couple of times in those corner cases, because it doesn't just count the interrupts, but uses the counter value that generates the interrupt for better resolution.Quote from: retiredfeline on October 24, 2021, 02:25:44 pmYes: during loopCount==10, every iteration of loop() causes task2s() to be called; several times each millisecond, most definitely.I would write loop() slightly differently, and again usingmicros()instead of millis() (due to SAMD21 implementation of them).Code:static unsigned long last_lcd_us; // Add 'last_lcd_us = micros();' at end of setup()static unsigned long last_other_us; // Add 'last_other_us = micros();' at end of setup()static volatile unsigned long tacho_pulses; // Set 'tacho_pulses = 0;' in setup()void loop(void){const unsigned long curr_us = micros();if ((unsigned long)(curr_us - last_lcd_us) >= LCD_UPDATE_INTERVAL_US) {// Read 'tacho_pulses' and clear it to zero, while interrupts are disabled.noInterrupts();const unsigned long pulses = tacho_pulses;tacho_pulses = 0;interrupts();update_lcd(tacho_pulses, curr_us - last_lcd_us);last_lcd_us = curr_us;} elseif ((unsigned long)(curr_us - last_other_us) >= OTHER_UPDATE_INTERVAL_US) {do_other_thing(curr_us - last_other_us);last_other_us = curr_us;}}Here,update_lcd(pulses,microseconds)will be called at intervals of at leastLCD_UPDATE_INTERVAL_USmicroseconds; the actual interval may be slightly larger, depending on what else the MCU is using. Note the use ofnoInterrupts(); ... interrupts();to disable interrupts while accessing thetacho_pulsesvariable. I assume there is an ISR or something to update it (doingtacho_pulses++in an ISR is safe, AFAICS). If you have a HW pulse counter, then disabling interrupts may or may not be needed to read and reset the counter value; it depends on the hardware peripheral.Similarly,do_other_thing(microseconds)will be called at intervals of at leastOTHER_UPDATE_INTERVAL_USmicroseconds, i.e. up to1000000/OTHER_UPDATE_INTERVAL_UStimes per second, if the MCU is not too busy doing other stuff.For both functions,microsecondsrepresents the time elapsed since the previous call. In all cases summing consecutive values yields the correct run time; there are no blank intervals. However, because the time is only measured once per loop() iteration, each loop() call only runs one of the functions.The run priority is fixed, in the order listed in the code. To add additional tasks, just add a newlast_foo_usvariable, and append it as a newelse if () { }block, as the others. I like to use a preprocessor macro for the intervals, but you can just as well use a variable for those if you like.This is a simple way to do stuff at various intervals, and although not optimal in any way, it is a robust, easy-to-maintain approach.

author: langwadt
author_rank: Super Contributor
timestamp: October 24, 2021, 04:44:17 pm
content: Quote from: Nominal Animal on October 24, 2021, 04:25:57 pmyou always risk being one behind. You can spin around all you want, the tick could happen right after you decide it's not happend

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 24, 2021, 04:47:36 pm
content: The difference is that withmillis()you can be a millisecond behind, withmicros()a few microseconds behind, because the latter examines the counter used for the systick interrupt.

author: Simon
author_rank: Unknown
timestamp: October 24, 2021, 04:54:00 pm
content: Right got it. yes that would explain why it didn't work. normally I'd just run a counter on an interrupt..... I'll need ta add a flag variable to say that the tasks have been don already.

author: langwadt
author_rank: Super Contributor
timestamp: October 24, 2021, 05:12:52 pm
content: Quote from: Nominal Animal on October 24, 2021, 04:47:36 pmso when do you stop? when it is atmost 1us? it is a 1ms timer that is -0,+1ms you could mess about and make it -.5,+.5ms but does that make a difference?

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 24, 2021, 06:01:23 pm
content: Quote from: langwadt on October 24, 2021, 05:12:52 pmAtmicros(), which uses both the systickcounter, and the number of systick interrupts (one every 1ms), of course. Consider the scale we're working with.(micros()normally does the loop body only once. Only when it detects a pending systick interrupt or counter change, does it loop; and then, almost always only one more time. Additional iterations only occur if interrupted during the looping. It's quite clever, actually.)The run-time cost (instructions, median run time, etc.) ofmicros()is not much more thanmillis(), and I think the added resolution and smaller jitter is worth it on ATSAMD21 (on all Cortex-M0, M0+, M4, and M7, to be more precise; and on Teensyduino AVRs (AT90USB1280 and ATmega32U4) as they do something very similar, written in efficient assembly). Don't know about other architectures supported in Arduino, since I haven't looked at them.Granted, I am assuming sub-second intervals here, used to measure pulses to estimate RPMs, and similar tasks. For human-scale intervals used for e.g. debouncing buttons and such,millis()is perfectly fine.

author: Psi
author_rank: Super Contributor
timestamp: October 24, 2021, 10:55:20 pm
content: Quote from: Simon on October 24, 2021, 02:46:52 pmDon't get too locked-in to this millis() issue being the problem.Yes, I believe you when you say that nesting millis() cause millis() to not work correctly, but that is just a symptom of some other bigger issue, like overwriting memory somewhere.

author: westfw
author_rank: Super Contributor
timestamp: October 25, 2021, 01:43:19 am
content: What retiredfeline said.Every 2s, your "if (loopcount == 10)" will be true EVERY LOOP until the next time you increment loopcount (ie 200ms), at short intervals.Because of the way you display your data, you'll probably only see the text generated by the LAST of these display updates.Easy fix:Code:void loop() {if ((millis() - varLastLoopTime) > constLoopIntervalMs) { // check time since last loop counter updateloopCount ++; // update loop countif (loopCount == 10) {loopCount = 0; // if loop count has eceeded 10 resettask2s();}varLastLoopTime = millis();}}Although I'd be inclined to have separate timers for the 2s stuff and the 200ms stuff. Much more straightforward, and insignificantly more code:Code:boolean periodically (uint32_t *since, uint32_t time) {uint32_t currentmillis = millis();if (currentmillis - *since >= time) {*since = currentmillis;return true;}return false;}void loop() {static uint32_t timer200ms, timer2s;if (periodically(&timer200ms, 200)) {task200ms();}if (periodically(&timer2s, 2000)) {task2s();}}

author: Simon
author_rank: Unknown
timestamp: October 25, 2021, 05:26:32 am
content: Quote from: Psi on October 24, 2021, 10:55:20 pmWhat retiredfeline said.However the code written by the student should have worked, but did not, so it looks like there may be something else as he did it differently and certainly not with the trap that I have.

author: macboy
author_rank: Super Contributor
timestamp: February 22, 2022, 06:17:16 pm
content: Although I realize this thread is several weeks old and the issue is likely resolved, I want to add a two important tidbits about using the Arduino millis()The millis() is incremented once per rollover/overflow of the system timer (Timer0 on AVR commonly). This usually occurs once per 1024 microseconds or 1.024 milliseconds (on AVR). The 0.024 extra ms are accumulated until there is another >=1 ms to add to the millis counter. This means that in one second, the counter only increments 976 (or 977) times, skipping a count approxmately each 42 counts. If you want to do something every say 100 ms, it isn't a big deal. If you want to do something 1000 times (or 500 times etc.) per second, it is a very big deal. If you need accurate short time intervals, use micros() instead. If you need a counter that is approximately 1 ms but never skips a count, you can use the timer 0 overflow counter (look inside the millis() function itself). Beware that it is incremented inside an ISR so make sure to use some form of atomic access (probably disabling and enabling interrupts).Some have made note of the overflow issue. This can be an issue, but only if you do the comparison in the wrong way. For example, if you directly compare the millis() count to an expected number [e.g. if (millis() > nextCount) ] then overflow will bite you. However, if you cast millis() return value to an unsigned number and subtract the previous timestamp, then compare to some interval, you will not have this issue. For example, if the previous timestamp was 5 less than the maximum (0xFFFFFFFA or 4294967290) and twelve "millis" have elapsed, then millis() returns 6 (0x00000006). Using unsigned 32b numbers, performing (6 - 4294967290) will still give 12, the correct count. This value can then be compared to the desired interval to determine whether to perform some action. Note that if youknow for certainthat you will check the interval regularly, you can save a few processing cycles and RAM on 8 or 16 bit micros, by using 16b signed numbers to store the present and previous millis() counts. Then that 16 bit representation of millis() will overflow once each ~ 65.5 seconds. If you do this, test your code well; different compilers (and optimization settings) can do unexpected things when casting and performing arithmetic on differently-sized numbers (32b/16b/8b).

author: Simon
author_rank: Unknown
timestamp: February 23, 2022, 03:16:02 pm
content: Yea, I solved it, I designed my own !Arduino using a SAMC. I run on a counter firing an interrupt every 100µs. Of course now that I have counter interrupts I can use those instead of convoluted use of a global timer that needs comparing to it's past self to see if it ticked once. For those really short thing I use a flag variable.