Thank Ralph. I've used millis() before but your explanation of "Static" was helpful. When I first saw the subroutine code I said wait, that's not going to work the timer is constantly being reset. Nope! For some reason I only thought static was when you didn't want the program to ever change the value. I can think of several projects I've done where timer and other declarations are littered throughout the program, when they can be neatly stored as a local variable in the subroutine.
I would prefer redMillis += 500; and grnMillis += 100; in the if () statements. If the processor gets busy enough, millis() can count a few times before the assignment and these missed counts will accumulate. By adding a fixed amount, one loop might go long, but the next will be short and restore the timing.
As a retired ex HW engineer I have decided to play with Arduino and after the first exercise I was stuck as I could not “accept” an infinite loop. I started to look for resources till I found your video. It is great, it has answered in a very clear way to all my doubts. Thank you for your clarity. This should become something that the Arduino team should explain to all beginners.
A wonderful video, Ralph. The comments demonstrate its efficacy by the fact that viewers have started thinking out of the blink sketch box. The important thing to take away from the loop you showed is that other tasks can be added to the loop by adding another function into the loop. Of course, just as with intercepts, one can't use too much time in that function. However, the added task could be broken up into smaller bits. An example: say you have an LCD and a real time clock (RTC) chip and you want to change the display of time once a second; but you find that each time that function 'activates' the procedure, the pattern of the LEDs stutters. In that case, you would have the function read the RTC and save the value in a static variable and sets a static flag. The nest time the function is called by the loop it checks the flag (in truth, the function checks the flag every time it is called) and ignores the millis count but changes the display to reflect the value that was saved last time and clears the flag. Thus, the two blink functions get their chance to check the millis count in the middle of your task.
Wouldn't an interrupt be far more superior yo implement that scenario? One that either frequently reads from the RTC or even better if wired properly uses the RTC as the interrupt source.
Of course, the more "work" we do in (or call from) the loop the less time there is to go around. But the next stage of this multi-tasking approach is to do "bits" of each function for a short time but return to the same point on the next invocation - hence the 'state' of each function must be known. But there is nothing to stop us calling functions like this: A ⬅ B C A ⬅ D E A ⬅ F G where function A is time-critical and must run more often. Ultimately, a true multitasking system will only ever allow each function a maximum of (say) 1mS of run-time before switching context to the next function (round-robin approach). But that has its drawbacks too which you alluded to. And running a critical function at a higher priority just starves lower priority tasks. I had to take into account all this with my ESP32 Web Radio that streams the audio from the Internet, buffers that data, passes the data to the MP3 decoder, updates the screen, gets user input... the list goes on! Coding, ain't it wonderful?
I've done similar things, but being self-taught I didn't realize that the static variable declaration would only be evaluated the first time the function is called and not subsequent calls. I knew about the concept of scope, but not that run once concept. Thanx for clearing that up. 😀
a normal variable inside a function will lose its life. a static variable inside a function doesn't and remember its state. and that's why that line "only runs once". that line is a declaration of a variable with initial value from millis() and a static variable that is already existing won't be "re-declared" in the next function call.
Hi Ralph, another seasoned coder here. I’m glad you are showing these techniques (and I love how you saved yourself a variable by reading the state of the LED rather than drive it from a Boolean). At some point down the road it could be an interesting construct to introduce power management techniques. Eg if the next required state change is not due for x millis then either shut down the main core while pwm runs, or adjust the clock speed to match the requirements of the states that are in the set of states that are under current consideration.
Power management techniques could be included, if there is "nothing to do", so in effect we are just idling. Yes, in a battery-powered device every millisecond asleep is another millisecond we can run on power.
One of the cleraest way of explanation I've ever heard and not just this video, but all of them. Thank you Ralph! Wish we had teachers like you in school - would have gotten much better grades for sure. :)
Hi Mr Ralph, although I’ve been programming for a number of years I’ve actually forgotten about the static variable declaration indeed of a function. Very informative always something new to learn.
Paused: I did this on a project a few years ago; you create a loop, save the current millis() in a seperate variable for each timed activity and then go round the loop subtracting the saved value from the current value of millis() until the difference is the time required. You then reset the saved millis to the current, and go on to check the next variable. Using this technique I was able to get a single arduino to take timed photos with a jpeg camera, and send them off to a phobe via a GSM modem, using the timer to time the pictures, handle the serial communication with the GSM modem, and read various sensors and battery monitors. It would have been quicker to use a raspberry pi, but the friend I was doing the software for wanted to make a dozen, and the cost would have been too high! It worked fine in the end.
I'm glad it worked well (in the end). Without this simple technique I could not have written my ESP32 Web Radio, although admittedly that uses a more developed state machine concept and even a couple of extra tasks.
Great video today Ralph. Thanks for your explanation of "static" in the situation, could you also please contrast that to "const", I would love to see more of the state machine, concepts, and practices. I'm an "old or rather very old" assembly language programmer in multiple flavors, so just now getting my feet wet with C++ and object-oriented ideas, certainly a lot to learn!
const means constant meaning once defined it cannot be changed, static means you can access it from any function, like a global variable but defined in a local scope (memory is "static" and kept after function executes) but you can also change the value.
A "const" value still requires a memory location just like any other variable. Except you can't change it. You can declare it inside a function (to constrain the scope) or make it global (grrr). A #define allows a literal to be substituted by the precompiler and no extra memory is used. It's just becomes part of the code.
@@RalphBacon yes, and static constexpr is better still: no memory overhead, ability to use an explicit type and the static forces the compiler to evaluate at compile time.
Ralph, I've been struggling with sorting out this technique for several projects. You have clarified a programming concept I've been looking for a long time. Thanks! thanks! thanks!
Thanks Ralph, You have presented a well though out video, covering the subject matter very well. It would be great if you would create another video on this subject, I look forward to it. You are a great teacher, your library of videos are a great resource!!!🙂🙃🙂
I'm an experience developer, and I've used C++ for years in the past, and have used to state machines in the past to solve various problems, but it took me more than 10 minutes to solve my state machine problem. Mostly because of complications of C++ compared to Python and general debugging issues, as well as figuring out my state machine!
Ralph very well explained example for multitasking the Arduino. I think you hit a very well deserved explanation for using global variables sparingly. I think that would be a great topic for an Arduino subject.
Thanks for the lesson. I'm new to Arduino and I haven't written any C/C++ code since the 90s! So, I'm really finding this a great help to get up to speed on some recommended tricks and techniques. Right now I'm keen to see how much can be done with a single Arduino. Seems like we could control many devices with this technique. Cheers!
Good explanation Ralph. First video of yours I've watched. I'm a 40 year emerging tech guy, programmer, inventor and software executive currently CTO. So I knew the subject matter already. But I enjoyed your style of making some fairly sophisticated abstract concepts and showing a well-explained working guide through the implementation. What made me inspired enough to Like and comment was your magnificent statement: "Global variables are satan's spawn." I pumped my fist in the air. This indeed is a critical thing for new programmers to understand and probably the most often-seen bad code smells in their code.
Yes, it's unfortunate that the compiler does not warn about global variables if they are only being used in a single (or perhaps a couple) of functions. It encourages untidy programming by allowing globals in a willy-nilly fashion. Passing by reference is better in a lot of cases. That's not to say I never use them. There is a place for them. Ideally in a namespace but even the global namespace works for _some_ variables or objects. I'm glad you liked the video, and thanks for the comment and "like" too, it all helps this channel. 👍🏻 In the meantime I'll keep pushing the message that globals must be reigned in with a cat-o-nine tails!
@@RalphBacon I sometimes use a global Singleton class that encapsulates configuration info. Like you said they are sometimes needed or an accurate representation of things. Global constants are not as big of a deal, but even then I tend to use static constant definitions. Even then I prefer to put the constant to be encapsulated as class members (static or otherwise). It's all about limiting and controlling scope and properly using encapsulation to implement some bit of functionality. For little < 25 line sketches or Python scripts using globals isn't as critical. But I find using the right abstractions and following best practices pays off even in small chunks of code.
I like the idea of using a singleton for pseudo global variables. Nice and tidy. Yes, I agree that using best practices from the word go helps reinforce good programming practices for even small programs.
I advocate strongly for the use of module-global variables (the ones explicitly marked as static at global scope) because they don't come with the same runtime overhead as local static vars
to prevent slowly introducing lag, instead of doing: grnMillis = millis(), you should do: grnMillis += 100. This ensures that regardless of hickups and delays in other functions, the interval will remain (or correct itself to) 100ms.
I'm just trying to work out where the lag is, that you refer to. Just so that others are aware, the timing here is anything but critical but even so... I think I understand your concern: on each invocation we check the millis() and if true grab a new value from the millis(). That new value could be more than 100 + the original millis() (because we started late and/or have done some work). So we slowly drift further and further away from a true 100mS flash - it might be 100, 102, 103, 104... etc since the first flash. Yes, your suggestion would ensure that only 100mS would have passed until we ran the code inside the "if" statement. Good call. 👍
@@RalphBacon I should have called it drift, but that indeed is the issue: scheduling the next action 100ms from now, instead of from the previous action. Keep in mind that the millis-overflow edge-case could be tricky here, so it'd be best to extract it from the two functions and write it as a separate function that handles all the miserable edge cases :)
And for exactly the same reason you could initialize the static variable with 0, Ensuring that the first event happens 'exactly' on the set interval after startup
@@sdr9682 Doesn't that timer counter start at 0 when first turned on? Oh... I just thought of something. I don't think it resets the timer counter when you =reset= the AVR. So initializing it with 0 at startup, you do have a set interval each time, even if you reset rather than power up.
Nice video, this is how i started with arduino 10 years ago. after that i made my own library that can increase or decrease value(s) without blocking. this way i can easly use PWM or servo's with Scene time and fadetime. and 3 years ago i also added sin to it to make it more smooth.
Brilliant video, for getting started on non-blocking coding. I am assuming that reading (and especially de-bouncing) buttons - without the use of delay() - will also be one of the upcoming examples on the state-machine list. Sometimes I need to have buttons de-bounced, both when they are pressed and released. I find that is a good job to define via a state-machine.
9:30 Regarding the explanation for the static unsigned long ..I wanted to give you a hug Ralh❤. Best explanation I have ever seen! Thank you a million times.
Well, it has something to do with the heap and stack, each time the function is or any function is needed its loaded in to the memory (SRAM). so every time when the function is needed, it's recreated along with new variables inside the function and deleted when the function exits. Static prevents this, it's static like the name implies.
Cool as always. You need to take advantage of the pcbway perks and design a custom uno board that people can buy and populate themselves. I would love to have that. I love the proper practices scope, encapsulation, millis. I rarely write my code in the loop. It's usually a series of function calls. I love your videos though as I don't code as much as I used to and you're sharp. No one else introduces this stuff to people. Thank you.
My first HW state machine was an prom and a latch (back in the TTL days). Clicked on this video to see if you had something easier to read than: switch (current State) { Waiting to see your next video😊
That is awesome! So you used the floating MOSFETs in a PROM for a counter, or something? Let's see now. 16-bit PROMs with 16 address lines (64K words of storage, I presume). Bring the PROM data lines out to a 16-bit latch, then connect the sixteen latch outputs back to the PROM address lines. But what's coming out of the PROM data lines? I guess we would need to "burn fuses" there first. Each location would contain the next memory location to be accessed. First location (zero) put 0001, next location (one) put 0002 and so on. A programmable counter. As a State Machine. 🎇✨🌟
@@RalphBacon not so complicated. The latch holds the current state. Feed the outputs of the latch to the prom's address lines along with the inputs to the state machine. Wrap the outputs of the prom back to the inputs to the latch (this represents the next state number). Program the prom so that at the address of the current state, with the inputs to the state machine the prom holds the next state number. Each time you strobe the latch it will move from the current state to the next. Speed is only limited to the speed of the prom, sub microseconds. Pre FPGA's and cheap microcontrollers it worked.
Greetings from Canada, Ralph. PLEASE delve deeper into State Machines, as I have been trying to grasp this concept, and how to implement it, for quite some time, now. Been scouring the internet for good explanations.
Will do! Next video on this subject (soon... ish) will delve into actual "states" so we can skip to the correct bit of code in a longer, single-responsibility function.
I use case statements to code state machines something like: uchar8 abcReceived = FALSE; uchar8 state = 0; while ( abcReceived == FALSE ) switch ( state ) { case 0: if ( inputChar = ='A' ) state = 1; break; case 1: if ( inputChar == 'B' ) state = 2; else state = 0; break case 2: if ( inputChar == 'C' ) { state = 0; abcReceived = TRUE; } else state = 0; break; default: // an error condition or expected state break; }
I can see a major problem with this. Its not preemptive. A subtask has the possibility of locking up the whole thing. I wrote a preemtive task manager in assembler that I use as a pseudo o/s. If i need something done when a condition occurs, I add a task to a Que that is prioritized based upon a priority number. What I put into the Que is a priority, condition, and task to run when condition is true. This condition can be a timer, semaphore, port change, etc. The task in the Que is passed control upon condition being true. The task does get interrupted by a timer interrupt, so a task can not hang the system. Before returning control to the interrupted task, it will place on the Que the interrupted task with the same priority as the original task wjth a condition of true. The task manager will rescan the Que and if no other higher priority task needs to run, the original task will get back control. Easy peasy I use this all the time when coding on micro processors like the arduino, PIC, and TI
Correct, it's not pre-emptive - as I mentioned in the video it's important to prevent one task from monopolising the μController processor. That is, it's important the developer deals with this! However, in the next step towards a State Machine, we can simply exit the function at a particular state and allow other things to run. A pseudo-yield, if you will. It's certainly nothing as sophisticated as what you have described and written, but does work well enough for non-critical tasks. Have you published your task scheduler? I've been made aware of FreeRTOS (optimised for Arduino) which I hope to try out soon, especially if it runs on the Nano Every (AtMega4809 chip which as 6K SRAM). github.com/feilipu/Arduino_FreeRTOS_Library
Very good instruction !!! Three thumbs up ! My question is, what happens on day 50....when the "static unsigned long" variable is as high as it can go...but the time counter starts over .....Does everything stop ? Have I missed something?
Yes, everything stops and the universe implodes. It's a well-known Arduino side effect. 😉 On the other hand, nothing happens because the counter (and millis) is an UNsigned long integer, it tries to take away a larger number (the old value of millis) from a tiny number (the new millis) and gets the correct value! Magic! Arduino have thought of everything. As an example, let's pretend that millis was stored in a single byte, unsigned integer so could only count to 255 before overflowing (going back to zero). When that happened, we might suddenly be faced with a calculation such as 2 - 200 which normally could not be done (or would go negative, ie -198). But the value you get is 58 (ie the number of milliseconds that has passed since the count was 200 and is now 2. If you don't believe me, write a simple sketch to prove it to yourself!
@@RalphBacon I've no reason not to believe you. I'm just trying to understand..... time now = 2 Millis() time then was 4 million +/- redmillis so, if(now (2) - then (4 million) > 500) { do stuff... redMillis = millis(); //
Hello Ralph, I would like to see a follow up to this video, I enjoyed your informative and educational video. I'm somewhat of a neophyte to Arduino sketch coding and would like if the sketch you presented will work with a Larson Scanner sketch using millis()? Looking forward to viewing your other related videos. Thanks again for your instructional video.
Neophytes are more than welcome to my channel; it's why I created it! When you say "will [it] work with a Larson Scanner", do you mean you want to write a sketch that does a Knight Rider/Battlestar Galactica sweep of LEDs from left-to-right and back again? If so, there are easier ways of doing that with NeoPixels and I demoed that (in passing) when I did recent video on NeoPixels (see video #239 ruclips.net/video/SStRG-_1wXc/видео.html ). If you really want to build a Larson Scanner (or have one) with a number of LEDs then a State Machine (my next video in this series, coming "soon") would certainly work (each LED is a State).
@@RalphBacon Hello Ralph. Thank you for welcoming this newbie and helping this old dog learn a new trick. I apologize for not being clear in my wording of my question to you. What I was trying to convey is I have a Larson Scanner (LS) using millis(), which I would like to incorporate with your sketch/code. When I wrote will [it] work with a Larson Scanner, I neglected to write I am planning to add the #define LEDs from the LS code at below your #define grnLED 8 of your sketch. The void from the LS below your void blinkGrnLed() and will place the LS void loop() below the void BlinkGrnLed() of your sketch and was wondering if you thought combining your code/sketch and the LS code work? Or is this combining of codes/actions require a “state machine” sketch type code? Thank you for your thoughts and I will let you know what comes of my attempts to combine these sketches.
If you can describe your Larson Scanner (or give me a link to it) I can help further. For example, is your LS just a string of LEDs that you can control however you want or has it got electronics in there too? Have you got any code that already works with it?
@@RalphBacon Hello, Ralph, thank you again for responding back to me, it's kind of you. Just a quick FYI to let you know I was able to combine your sketch with the LS sketch I found it made for a interested time. Thank you for the link (video #239 ruclips.net/video/SStRG-_1wXc/видео.html ) to your video on NeoPixels it will be a help in learning to control these as well. I am looking forward to viewing your other videos and hope you don't mind me dropping you a question from time to time. :-D
I will probably show a quick and simple transition diagram but will concentrate on getting the states written and read. Noobs, remember. KISS as they say.
Not having blocking functions and in this case, using a timer, is good programming practice and you have explained it well. I have never used static before and used global variables as counters triggered from a timer, it works but not ideal. Look forward to your next video and implementing states machines as never really understood them.
Using timer-driven tasks is another concept (let's face it, using FreeRTOS to run tasks is exactly that) but not just yet, Crawl before we walk, right?
haven't done any coding in months now and have forgotten all of it, came back here for a refresher. wrote a code and designed a pcb and control panel/box to control the doors and hydraulic teasing rotors on my diet feeder on the farm and looking back over it I know i wrote it but can't remember all of why i wrote it 😅 have a 4 rotor silage rake to do next, trying to use multiple switches on a single wire(10 infact) to control it. one question i have, I want to individually control the rotors but from one signal, i.e I press one of 2 buttons and hold it, first rotor lifts one one side, I release and the opposite rotor lifts, now my problem is that these rotors both need to stop at a set height AND the rear rotors need to lift after a certain delay from both the first rotor and second rotor on the respective sides and also stop at a set height. I see 2 options, first would be an awful lot of code, the second would be to use 3 arduino, on to accept and relay the signal and 2 more one for each side of the rake to control the left and right pairs of back and front rotors?
Using more than one Arduino board is probably overkill. I would suggest building (designing) your project stage by stage so you're not trying to do 10 things at once.
@@RalphBacon funny how fate works, I'm only after starting this project seriously 2 days ago and I've figured a way using 2 state machines contained in 2 separate voids to work the machine independently in each side. Basically recapping your videos made me realise that the voids worked independently of each other(or what it seems to be to me) and that cleared a path to do what I wanted. Thanks for the reply
Another Great video. I've seen static mentioned in some sample programs but never understood it. Thanks, it will be very useful. State Machine lecture Please.
It's a _simple_ state machine, I don't want to scare the noobs. I don't use any timer libraries as such; you mean to trigger functions or something else?
Wow ! I self taught myself in C+ and didn’t know about static variable or the ! ‘trick’. Most of my Esp8266s use Blynk which requires keeping the loop clean, thus the extensive use of timers. Looking forward to the next video. Is that push button going to be used as an interrupt ?
Thank you! Seems silly NOW to move the millis() value into the "current time" as most do , where you just utilizing the mills() in the IF statement to compare it to the last time the IF statement was executed. Every one teaching on RUclips has created the extra variable "current time". I will be following you. Thank you Sir! I will be studying the "static" and how the compiler does stuff with it during optimization.
@@RalphBacon I like this simple use of comparing millis but, what about when the clock rolls back to zero? If the capture of the millis is at the end of the clock and rolls over to zero shortly after, the “if” statement will never execute again because the millis compare will never be greater than 500, or whatever value you have.
Great video! I've been using pseudo multitasking on Arduino's, ESP8266's and ESP32's for years but learned a few things from this video (Ie. the use of Static). I'm glad you pointed out that this is not true multi-tasking as that is often mistaken when discussing this with the Arduino/C++ coded devices. It would be interested to have a discussion of true multi-tasking with the dual processor ESP32 but that probably would be a fairly advanced topic?
I'm not sure what the fascination is with "true multitasking" versus pseudo-multitasking using time-slicing. Yes, using both cores on the ESP32 is a possibility for true multi-tasking but then you run the risk of crashing Wi-Fi or BT or something on Core 0. We're not running NASA here, so why the fascination? That all said, I did a video on simple dual core multitasking on an ESP32 here in video #149: ruclips.net/video/jpVcCmh8sig/видео.html and on passing values between tasks in video #151: ruclips.net/video/ywbq1qR-fY0/видео.html But none of my Real World ESP32 projects use both cores as application cores.
I've just found your channel and I'm enjoying your videos very much indeed! My first introduction to Arduino-esque non-blocking code was the example sketch 'BlinkWthoutDelay' that's bundled in with the IDE.
Welcome aboard! Yes, the Blink-Without-Delay is a good intro to only carrying out tasks after a predetermined time. Not quite multi-tasking but getting there, and could be "enough" for many projects.
Thank you for the millis inclusion for pseudo state operation on an Arduino. I have to think about an LDR to start the millis counter to flash LEDs for a period of time then stop until the LDR detects nightfall again. (Two red LEDs hanging in a tree at Halloween to simulate a critter looking down on you.)
@@RalphBacon yes, but here is a fuller explanation ... The question often comes up in conversations about signed Vs unsigned integer calculations: what happens when the max value is reached and goes negative if incremented. As any assembly programmer should know, the compiler generates the same code for both signed and unsigned. Let that sink in and you will realise the it MUST still work.This is the beauty of twos-complement!
@@jestr1845 When millis() goes back to zero, the variable (redMillis) is going to still be really big, which is going to result in a negative number and the difference never pass the 500ms again. This can be a bigger issue when you use the micros(), that reset every 70min
@@FelipeLenschow It should still work. Lets use bytes for numeric simplicity: 255 - 250 = 5 // as expected 256 - 250 // is in reality... 1 - 250 // which simplifies to... -249 // which as a byte would be... 6 // as expected. Or am I wrong?
Very good video! I think the next step into a state machine is to use a state variable instead of digitalRead. After that, show how to make the on and off-times different for a LED.
I want to show some Real World use of a State Machine; using one to record the state of the LED is not necessarily the best use as we can do _that_ the way I've already done it. Better use would be to be able to exit (and re-enter at the same point) the function between states (assuming there are some, not yet in the demo I showed).
Hello. I really like your videos. I'm really new to Arduino but not in programming. I have a question. Is calling the functions many many times "for nothing" in the loop using more power? I'm thinking of battery powered projects. Would it be better if we put a delay of 100 ms in the loop? Thanks for your great channel.
Whether the loop is called thousands of times a second, or you put in a "delay" (which is an even tighter loop) uses the same power. If you _know_ you have nothing to do for a while, the best approach is to put the microcontroller to sleep; essential in battery powered projects. I've done a few videos on Deep Sleep and the Arduino, have a look!
It's good to see you educating folks on good programming practice. Hopefully in a subsequent video the blinkxxxLed() functions get replace by one blinkLed() function, initialized with pin number and interval. (i.e., remove the duplicated code from the sketch.)
Nope. This was a simple demo just to do two things. The fact that it was the same code was done for simplicity. The second function could have turned a beeper on/off but would have got very annoying very quickly.
@@RalphBacon Sorry, but "beeper"??? All I was suggesting was that a class could be instantiated with something like redLed = blinkLed(redPin, redInterval) and greenLed = blinkLed(greenPin, greenInterval), which is scalable to the number of available pins. Because code isn't duplicated in such a case, it is more maintainable, which I hope all would agree is a good programming practice. If that's not something you want to cover on your channel, that's fine. It is after all your channel.
I've covered the DRY concept whenever it seems sensible to do so; what I meant by my comment is that you're concentrating on (and commenting on) the duplicated code whereas in Real Life the two functions would be doing something very different (to each other). That's all 😜
Rather than repeating the code snippet over and over again, I think I would just create a class called "IntervalTimer" that has a constructor which takes the interval time. The class has a Poll() method that you call in the loop and returns true when the timer has expired (and resets internally when that occurs). Then create two instances of that class, one called "redIntervalTimer" and one called "greenIntervalTimer". Better yet, I would create an additional constructor that accepts a lambda that is the action.
As soon as you mentioned the word "class" it told me this video was way below your pay grade. Beginners have no idea what a class is or how to use one.
@@RalphBacon And then I mentioned "lambda" :). Fair point, although judging from the comments, your followers seem to have a fairly wide range of experience. Well then, I suppose we can view my comment as "an exercise for the interested reader" then! :)
A very good answer. A bit like a teacher saying "I'll leave it to you to read more about OOP in your own time, class!" Yeah, like that's ever going to happen.
@@RalphBacon Seriously though, and judging by the comments, I do think many of your followers are more sophisticated than you might think. In addition, I would hope that beginners who mainly use libraries ought to learn at some point that they are making an "instance" of something based on a "class" definition (BTW, I may have "reinvented" the "PollingTimer" library so I might suggest that beginners examine that library after watching your video). After all a "sketch" is C++ so why not take advantage of the "++"? I gather that you will eventually get to discussing the notion of a "class" and "instance" at some point. At least I hope so! :) BTW, I really enjoy your videos and the way you approach things, wish we lived in the same neighborhood! :)
Me, after reading some of the comments: Man, some of these people need to relax - he made it clear that this is just the first video, etc. Also me: BUT WHAT ABOUT WHEN MILLIS() ROLLS OVER TO ZERO AFTER ~49 DAYS??????
@@RalphBacon Well, that was kinda the whole point of my post, innit bruv? 😊I'm sure that the state machine code will handle the rollover case with aplomb and literally zero flashes will be missed when it does roll over.
I know you stated "no bit manipulation", but the first solution I thought was: Leds on PD0 and PD3: void setup(){ DDRD=255; } void loop(){ PORTD ++; delay(300); }
Hi, just found your channel, this video is really useful for my projects since I'm always seeing projects using millis() but didn't know exactly how it worked. Thanks!
Ralph is this part of the code explicitly necessary for this to code to work? static unsigned long redMillis = millis(); Couldn't one just declare the variable in the beginning equal to zero. unsigned long redMillis = 0; this would be done in the setup loop or when declaring the variable. Then the redMillis = millis(); in the if function would update it properly? I liked the introduction of the static modifier but don't really see a benefit. Unless the main benefit is for readability. Being able to set a variable once within another part of code looks great from readability standpoint.
If the variable were declared as a _global_ variable and initialised in the setup() and then modified in the function, that would work. But! But I dislike global variables because their scope is, well, global! By using the _static_ keyword from within the function that it is being used in, it means that the scope is restricted to that function - and it's "just a snapshot", not an important, global variable (like the time), after all.
What would happen when millis() is called after 60 days? Edit - I discovered becuase it's unsigned it'll just overflow to 0 and the subtraction method will still work just fine
Nice video ! one question: Isn't correct on the forward declarations put also their inside code ? (and skip like this the part after the loop) How critical is the place of the functions ( i see that you put them after the loop) ?
If I understand the question correctly, you can write the entire function before you reference it (in the loop, for example). That way you do not need to forward declare separately.
@@RalphBacon So i suppose the placing of the functions inside the code is just a matter of personal taste, and if i decide to place them before setup (like i usually do, so i can have all ''basic'' ingredients of the code gathered), then forward declare and complete function code is just matching (and there is no need to write them twice). Thank you for clearing out to me this part !
Cool video, IMO using object oriented code can make this sort of program much cleaner, as you wouldn't need to repeat the state machine logic code for every instance you want to create
No beginner understands OOP so I'll be giving that a miss for now. This is still very clean _and maintainable_ code. Also, this demo flashed LEDs but in the Real World I hope we do more than that!
Hi Ralph, great subject to cover but I have a question and that is why is it preferable to use 'const' instead of '#define' as the Arduino manual recommends ?
I don't know why they would recommend that. A literal value (eg from a #define) is substituted by the pre-compiler and then used directly (it isn't fetched from memory). It does not use up memory space as it is not held in memory. It's part of the code. Const defines a fixed variable (so it does take up a memory location, unless the compiler is smart enough not to do that?) that can be scope-controlled (eg only valid in the function it was declared in, just like any other variable) whereas a #define can be used anywhere - but that's no advantage, not really. I think the use of #define is much better than having magic numbers as long as the name is unique; using const might make your program more robust with its scope control but it does use a small amount of memory.
@@RalphBacon Explanation from the Bald Engineer site. Sorry YT blocks outside links. Since the avr-gcc compiler is smart enough to keep variables out of RAM when using the const keyword, it is better to just use that. Eventually the “text-replace” nature of #define is going to bite you with a stray semicolon or other unexpected replace
Dear Ralph, I installed VS code and the paltfomIO extension. I was able to run this code and even make some modifications for views in the thermonal. However, after closing the project and VS code and reopening VS code, the PlatformIO icon no longer appears. I've done everything, reinstalled, PlatIO and VSC and nothing. I made a thousand queries in Git and found that there are dozens of cases opening with the same problems. Please do you have any tips so I can reset PlatIO in VSC? I found your explanation very interesting, saying that the compiler only uses the static variable once inside the if() loop. Congratulations!
My PlatformIO (alien head) icon appears several (long) seconds after opening Visual Studio Code. It's an add-in, after all. Click on the extensions icon (4 square boxes) and ensure that "Platformio IDE" is enabled globally and running. After that let me know if it's working or not.
Awesome, Ralph. It's great to have someone address the things that can really trip up us Arduino newbies. And your presentation really makes learning this stuff a lot more fun, when it can be VERY frustrating! Thanks! PS: I have a simple sketch that I have been working on that fails and make no sense why. Want to have a go at it?
@@RalphBacon What I'm trying to do: NEMA 17 Stepper, TB6600 Driver, Arduino Uno, (2) momentary switches, Potentiometer, Rotary Encoder. One switch runs motor in Fwd, one in reverse, Pot controls speed for both. Code to change driver ENA pin to LOW when button is released so motor will spin freely. (works fine) Rotary Encoder to step the motor a few steps per de-tent. (Also works, but will not release the power to spin freely) Adding code to switch ENA pin to LOW causes the Encoder to become very erratic and not work. Thanks Ralph
@@RalphBacon Not using an Interrupt. May I email you using the address in the "About" tab of your RUclips channel? Thanks so much for taking the time, Ralph. Regards
Hello Ralph. If 'delay(1000)' is placed in the main loop this will cause the LED's to be affected by the delay. The functions (redLED and grnLED) will then be delayed by 1second..
Hang on, are you suggesting we _put in a delay_ or something else? Or is this just an example of something that might affect the timing? The thing about multi-tasking (pseudo or otherwise) is that we _never, ever_ use delay.
@@RalphBacon dear Ralph. In your example the main loop will sequentially poll the red and green functions. According to my assesment if a delay(1000) is placed in the main loop the main loop will be 'stationary' for the 1000ms caused by the delay() function. At the end of the delay(1000) the next polling of the red and green functions will take place obviously both functions will return millis() that will be greater than at least 1000ms which is greater than the delta 500ms and delta 100 millis read by those functions, thus causing the delay() to become the dominant timing cycle. In conclusion if the intention is to do something every 500ms and 100ms delay(x) may not have x values greater than 100ms. Then if the processor is not reset within about 57days the millis register will roll over starting at 0.
Hmm, for some reason I cannot see your reply to my response but you mentioned that I'd already done it and I forgot to paste in the link to the video in my first reply... TL;DR here's the missing link: video #209 Arduino Timer Interrupts ruclips.net/video/fFrL5Vh8Dis/видео.html BTW did you notice the bit manipulation we had to do in that video? No easy Arduino-speak and no-one has invented a library for it yet - although *Norman Dunbar* has *AVRAssist* which at least does all the hard work for you: github.com/normandunbar/avrassist
@@RalphBacon Yeah, my reply had a link to the exact same video you pointed out. Maybe RUclips has a thing against linking. However I can see the one you posted just fine (probably because you're the OP, therefore more trustworthy in YT's eyes). I just re-watched that video and frankly that bit-magic is not that scary once you know what's behind it. I went through all that 30 years ago in college when I had my first programming courses (general C / C++ and also some microcontroller stuff). Anyway, I really like the way you handle the educational approach compared to other resources that tell you the WHAT but not the WHY.
12:30 Could you elaborate a bit more what the problem is with defining those global variables? That is the way I always work and to be honest I don't understand why that is such a problem. I'm only an amateur but keen to lean :)
I've ranted before about global variables but here goes. Although I (tongue-in-cheek) call them the Spawn of the Devil, that's not strictly true. I sometimes use global variables IF they are being used globally; that is, by more than one function. But variables, in general, should be restricted in their visibility (and, hence, use) by defining them in the function (or, better still, namespace) to which they belong. That way, they are not arbitrarily changed by functions which have no business using them, let alone updating them. It's to ensure your code is "cleaner". For example, if you were to refactor (refine, rewrite) one of your functions by removing it and adding it to a different namespace, would the rest of your code throw a hissy fit at not finding a variable any more? So, put logically connected functions into one file. Include that file into the main sketch with the use of the #include statement. Ensure that file has a namespace so that you can't just access its functions or variables without also specifying the namespace. See my simple videos on namespaces if this is not clear to you! Real World Example: you are reading a temperature sensor. Chances are, that you want other functions to be able to know what the current temperature is (eg, to display it, or take action when it reaches a certain level). So should this variable (let's call it "currTemperature") be visible to all functions in your sketch? Not if you want to do a good job. The functions that are to do with the temperature sensor might be in a namespace call "sensor". So that variable is now referred to, by the rest of the sketch, as "sensor::currTemperature". But it is not global, and it should be very clear in the code that you are referring to the variable in the "sensor" namespace. TL;DR I covered all this in various videos before! Grab the pdf file of all my videos and search for ones that cover this topic. Worth understanding the concept, at least!
@@RalphBacon Thanks so much, this provides a good starting point for understanding the namespace concept better. I will definitely check out your other video's for this topic!
Yep, learn something that I can use in future but as your note in the video pointed out, this only works until you have something like a slow sensor that locks up the CPU, right?
This is partly true; my DS18b20 temp sensor takes "ages" to get a reading after a request but that's where a true State Machine will deal with it. OTOH if the call to initialise your SPI bus takes a whole second there's not lot that can be done other than to rewrite the call to make it more responsive. Fortunately such cases are rare. Stay tuned as we progress this!
You would check every so often if the sensor has data "ready" sometimes there is a flag to check for this, that way you don't need to wait for a long time, you would also benefit from interrupts.
A variable has lifetime and scope. For that „static“, scope is „inside the function loop()“, but lifetime „global“. In less formal words, it is a global variable, but hidden inside function loop(). BTW, „static auto readMillis = millis();“ would avoid using the wrong type. And the code only works for „unsigned long“. Missing the „unsigned“ (and ignoring compiler warnings, which is the default for Arduinistie) breaks the code.
'Auto' is a great new keyword and I often use it. What I don't understand is why the compiler picks an 'int' type for small counts (less than 255) so I use uint8_t specifically.
@@RalphBacon because it’s a language definition. avr-gcc also has a switch to make int 8-bit, as you would have if the hardware register size typically defines the int size. But don’t use that switch, it will open Pandora’s box. With every inconvenience of the AVR8 GCC, just be thankful it even exists.
Generally, local static variables aren't a good idea in C++ because the have runtime cost.Each time the function runs, the compiler has to check whether or not the variable has been initialized yet. And on PCs, this is even thread-safe, thus adding mare runtime overhead.
@@didgerihorn never saw this in generated AVR8 code. The local statics are just global variables, initialized by the startup code, but w/o visibility outside the function. So „general“ is not always the same as „specific“.
@@fromgermany271 At least the program has to check whether or not the variable has already been initialized. Ok, here in this example you would have to do this manually anyway. However, in general, is is not a good practice to use local statics. People will hopefully move on from AVR :)
Could you make a video where you can make a button function as a switch and a second button to change which device it enables/disabled? I can’t seem to find a good tutorial on something like that yet it seem like a simple task. Thanks
Remember that the button doesn't do the actual switching; it's your microcontroller that decides what "thing" to switch. So if you detect a button push, then decide from the second button (state) which "thing" to switch all will be well. My next video on State Machines is due in a couple of weeks or so, and uses a button, so you may get some ideas from that.
decent beginner explanation, for true robust multitasking, give each LED a dedicated micro controller. This would apply to control of other "multi tasked " devices.
Does giving each LED a dedicated microcontroller actually achieve a more robust solution? Especially if each LED is in some way dependent on the others?
digitalWrite/Read are relatively slow as they carry out several checks (safety checks for beginner coders). You can speed them up by setting the relevant registers (eg PORTC). That will hardly take any time at all.
@@RalphBacon Even doing such checks digitalWrite is faaar faster than that... Assembly for them should not have more than 30 instructions... Most uC do one instruction in 1-3 CPU cycles, so even 1MHz CPU can do about 500 instructions in ONE ms. Notice that slowest uC has at least 8 MHz - so it should do at least houdred digitalWrite() per millisecond. For blinking it's overkill.
I get the heebie-jeebies whenever I read code that has an implicit reliance on time or the specific hardware. We're dealing with C++. Functions can be overwritten by, for example, a function that invokes an actuator ("Open the door, Hal") that operates in realworld time, not in CPU cycles. If we rely on the speed of the processor, we're not doing a good job at abstraction.
Very interesting video. But in reality, if the green LED function is doing something that take e.g. 5 seconds, then red LED function is not that independent anymore. Or am I wrong?
Not at all, Pekka. I did make a note in this video (you missed it? 😲) that it was important not to let a single function monopolise the processor time. That's where the true State Machine helps by allowing the function to exit in a timely manner but come back in at the same point. More in a later video.
I understand that you are starting from the beginning but putting state machine in the title may be a little confusing / misleading, it is essentially a timed function loop.
It's the first step towards ensuring a State Machine can even run. Or do you propose we have a State Machine in a monolithic structure? We've got to get more things running _first_ and that we did!
Another great video and of great interest (to me at least :-)!) I can see where I can break down my process and can step-wise deal with certain operations. I'm unclear however how you would deal with overal; (Global??) operations like checking and acting (possibly) on a temperature reading that should happen over various states. What happens if you define a temp control cycle whilst the program 'jumps' from state to state? Thanks!
Glad it was of interest! Regarding conflicting states, remember that the program is only doing one thing at a time. So if we move from one state to another then that happens before the program can even notice that a temp control reading has to happen. Is that what you meant? Or were you talking about interrupts? Or perhaps a common routine to read the temperature in various states?
@@RalphBacon Hi Ralph, thanks!! Sorry for being unclear, but I meant the latter; a common routine to read parameters in (across?) various states. I could repeat the lines/call the subroutines from every state, but wonder if there is indeed a way to have a common routine?
Well you could have a function that is called from various states, that's the most obvious way of doing it. Or a timer task that runs X times a second (or minute) to update the value regardless of what else the sketch is doing.
It's a good breadboard, I've got five now. It's one row of pins wider than standard too, so good for ESP32 type modules. It's called a "AD-11 Advanced Solderless Breadboard" and can be found here bit.ly/3LUnsNJ but if you don't live in the UK you will have to Google!
Um... I'm not clear of the context in which you are applying this. I do millis() now minus oldMillis > delay between iterations then reset oldMillis and do the work.
@RalphBacon millis is timeNow, oldMillis is timeStarted and check if it's greater than delay - setTimePeriod. Reset oldMillis - is the new timeStarted, which is now..
Thank Ralph. I've used millis() before but your explanation of "Static" was helpful. When I first saw the subroutine code I said wait, that's not going to work the timer is constantly being reset. Nope! For some reason I only thought static was when you didn't want the program to ever change the value. I can think of several projects I've done where timer and other declarations are littered throughout the program, when they can be neatly stored as a local variable in the subroutine.
Yes, keep things tidy and your program will be more maintainable, for sure.
I would prefer redMillis += 500; and grnMillis += 100; in the if () statements. If the processor gets busy enough, millis() can count a few times before the assignment and these missed counts will accumulate. By adding a fixed amount, one loop might go long, but the next will be short and restore the timing.
Yes, what you say will ensure it is self regulating, good call 😁
What
As a retired ex HW engineer I have decided to play with Arduino and after the first exercise I was stuck as I could not “accept” an infinite loop. I started to look for resources till I found your video. It is great, it has answered in a very clear way to all my doubts. Thank you for your clarity. This should become something that the Arduino team should explain to all beginners.
Glad I could help!
A state mahine demo would be wonderful. Great video as always, thank you!
Noted!
A wonderful video, Ralph. The comments demonstrate its efficacy by the fact that viewers have started thinking out of the blink sketch box. The important thing to take away from the loop you showed is that other tasks can be added to the loop by adding another function into the loop. Of course, just as with intercepts, one can't use too much time in that function. However, the added task could be broken up into smaller bits. An example: say you have an LCD and a real time clock (RTC) chip and you want to change the display of time once a second; but you find that each time that function 'activates' the procedure, the pattern of the LEDs stutters. In that case, you would have the function read the RTC and save the value in a static variable and sets a static flag. The nest time the function is called by the loop it checks the flag (in truth, the function checks the flag every time it is called) and ignores the millis count but changes the display to reflect the value that was saved last time and clears the flag. Thus, the two blink functions get their chance to check the millis count in the middle of your task.
Wouldn't an interrupt be far more superior yo implement that scenario? One that either frequently reads from the RTC or even better if wired properly uses the RTC as the interrupt source.
Of course, the more "work" we do in (or call from) the loop the less time there is to go around. But the next stage of this multi-tasking approach is to do "bits" of each function for a short time but return to the same point on the next invocation - hence the 'state' of each function must be known.
But there is nothing to stop us calling functions like this:
A ⬅
B
C
A ⬅
D
E
A ⬅
F
G
where function A is time-critical and must run more often.
Ultimately, a true multitasking system will only ever allow each function a maximum of (say) 1mS of run-time before switching context to the next function (round-robin approach). But that has its drawbacks too which you alluded to. And running a critical function at a higher priority just starves lower priority tasks.
I had to take into account all this with my ESP32 Web Radio that streams the audio from the Internet, buffers that data, passes the data to the MP3 decoder, updates the screen, gets user input... the list goes on!
Coding, ain't it wonderful?
I've done similar things, but being self-taught I didn't realize that the static variable declaration would only be evaluated the first time the function is called and not subsequent calls. I knew about the concept of scope, but not that run once concept. Thanx for clearing that up. 😀
Glad it helped, Christopher! 👍
a normal variable inside a function will lose its life. a static variable inside a function doesn't and remember its state. and that's why that line "only runs once". that line is a declaration of a variable with initial value from millis() and a static variable that is already existing won't be "re-declared" in the next function call.
Hi Ralph, another seasoned coder here. I’m glad you are showing these techniques (and I love how you saved yourself a variable by reading the state of the LED rather than drive it from a Boolean). At some point down the road it could be an interesting construct to introduce power management techniques. Eg if the next required state change is not due for x millis then either shut down the main core while pwm runs, or adjust the clock speed to match the requirements of the states that are in the set of states that are under current consideration.
Power management techniques could be included, if there is "nothing to do", so in effect we are just idling. Yes, in a battery-powered device every millisecond asleep is another millisecond we can run on power.
It was a lifesaver when I found the millis function, I have been using it for years but now I am more into timer interrupts.
Timer interrupts are good for the Arduino. Tasks are (much) better for the ESP32, though.
One of the cleraest way of explanation I've ever heard and not just this video, but all of them. Thank you Ralph!
Wish we had teachers like you in school - would have gotten much better grades for sure. :)
Glad it was helpful!
Hi Mr Ralph, although I’ve been programming for a number of years I’ve actually forgotten about the static variable declaration indeed of a function. Very informative always something new to learn.
Excellent! Always worth getting a refresher!
Really well explained Mr Bacon. Yes, please, I'd like to see more on State Machines.
Yes, I'll expand on this in the future, no problemo.
Paused: I did this on a project a few years ago; you create a loop, save the current millis() in a seperate variable for each timed activity and then go round the loop subtracting the saved value from the current value of millis() until the difference is the time required. You then reset the saved millis to the current, and go on to check the next variable. Using this technique I was able to get a single arduino to take timed photos with a jpeg camera, and send them off to a phobe via a GSM modem, using the timer to time the pictures, handle the serial communication with the GSM modem, and read various sensors and battery monitors. It would have been quicker to use a raspberry pi, but the friend I was doing the software for wanted to make a dozen, and the cost would have been too high! It worked fine in the end.
I'm glad it worked well (in the end). Without this simple technique I could not have written my ESP32 Web Radio, although admittedly that uses a more developed state machine concept and even a couple of extra tasks.
Great video today Ralph. Thanks for your explanation of "static" in the situation, could you also please contrast that to "const", I would love to see more of the state machine, concepts, and practices. I'm an "old or rather very old" assembly language programmer in multiple flavors, so just now getting my feet wet with C++ and object-oriented ideas, certainly a lot to learn!
const means constant meaning once defined it cannot be changed, static means you can access it from any function, like a global variable but defined in a local scope (memory is "static" and kept after function executes) but you can also change the value.
A "const" value still requires a memory location just like any other variable. Except you can't change it. You can declare it inside a function (to constrain the scope) or make it global (grrr).
A #define allows a literal to be substituted by the precompiler and no extra memory is used. It's just becomes part of the code.
@@RalphBacon yes, and static constexpr is better still: no memory overhead, ability to use an explicit type and the static forces the compiler to evaluate at compile time.
Ralph, I've been struggling with sorting out this technique for several projects. You have clarified a programming concept I've been looking for a long time. Thanks! thanks! thanks!
You are welcome! I'll expand on this topic in a future video.
@@RalphBacon I'm looking forward to more on this.
Looking forward to more "short" state machine videos. 😀
I'll try and make the next one shorter - or I'll record it at 1.5x speed.
@@RalphBacon 🤣🚀🚀🚀
Thanks Ralph,
You have presented a well though out video, covering the subject matter very well. It would be great if you would create another video on this subject, I look forward to it. You are a great teacher, your library of videos are a great resource!!!🙂🙃🙂
I found part 2. Thank you for putting in the time and sharing you knowledge with us.🙂😀🙂
I'm glad you found part 2 as that leveraged part 1 but then used a true 'state' too. Your projects now have guaranteed success 🤔
Some techniques I haven't used in many years, now that I am a java programmer. You always explain things so well.
Glad it helped. My most recent project uses FSM methodology.
I'm an experience developer, and I've used C++ for years in the past, and have used to state machines in the past to solve various problems, but it took me more than 10 minutes to solve my state machine problem. Mostly because of complications of C++ compared to Python and general debugging issues, as well as figuring out my state machine!
I think most developers (pro or bedroom) would be happy to solve a coding issue in just 10 minutes! So go for the small wins!
The shortest Blink sketch I know:
void setup(){
pinMode(LED_BUILTIN,OUTPUT);
}
void loop(){
digitalWrite(LED_BUILTIN,millis() % 500 < 250);
}
Yes, I use that too! 👍
This is something i've been trying to do off and on for 2 months ! this is going to help massively thanks for the content!
Great to hear!
Ralph very well explained example for multitasking the Arduino. I think you hit a very well deserved explanation for using global variables sparingly. I think that would be a great topic for an Arduino subject.
Glad it was helpful!
Thanks for the lesson. I'm new to Arduino and I haven't written any C/C++ code since the 90s! So, I'm really finding this a great help to get up to speed on some recommended tricks and techniques. Right now I'm keen to see how much can be done with a single Arduino. Seems like we could control many devices with this technique. Cheers!
You're very welcome!
Good explanation Ralph. First video of yours I've watched. I'm a 40 year emerging tech guy, programmer, inventor and software executive currently CTO. So I knew the subject matter already. But I enjoyed your style of making some fairly sophisticated abstract concepts and showing a well-explained working guide through the implementation. What made me inspired enough to Like and comment was your magnificent statement: "Global variables are satan's spawn." I pumped my fist in the air. This indeed is a critical thing for new programmers to understand and probably the most often-seen bad code smells in their code.
Yes, it's unfortunate that the compiler does not warn about global variables if they are only being used in a single (or perhaps a couple) of functions. It encourages untidy programming by allowing globals in a willy-nilly fashion. Passing by reference is better in a lot of cases.
That's not to say I never use them. There is a place for them. Ideally in a namespace but even the global namespace works for _some_ variables or objects.
I'm glad you liked the video, and thanks for the comment and "like" too, it all helps this channel. 👍🏻
In the meantime I'll keep pushing the message that globals must be reigned in with a cat-o-nine tails!
@@RalphBacon I sometimes use a global Singleton class that encapsulates configuration info. Like you said they are sometimes needed or an accurate representation of things. Global constants are not as big of a deal, but even then I tend to use static constant definitions. Even then I prefer to put the constant to be encapsulated as class members (static or otherwise). It's all about limiting and controlling scope and properly using encapsulation to implement some bit of functionality. For little < 25 line sketches or Python scripts using globals isn't as critical. But I find using the right abstractions and following best practices pays off even in small chunks of code.
I like the idea of using a singleton for pseudo global variables. Nice and tidy. Yes, I agree that using best practices from the word go helps reinforce good programming practices for even small programs.
I advocate strongly for the use of module-global variables (the ones explicitly marked as static at global scope) because they don't come with the same runtime overhead as local static vars
Great, i finally understand what the mean for static statement. Thanks
Glad it helped!
to prevent slowly introducing lag, instead of doing: grnMillis = millis(), you should do: grnMillis += 100. This ensures that regardless of hickups and delays in other functions, the interval will remain (or correct itself to) 100ms.
I'm just trying to work out where the lag is, that you refer to. Just so that others are aware, the timing here is anything but critical but even so...
I think I understand your concern: on each invocation we check the millis() and if true grab a new value from the millis(). That new value could be more than 100 + the original millis() (because we started late and/or have done some work). So we slowly drift further and further away from a true 100mS flash - it might be 100, 102, 103, 104... etc since the first flash.
Yes, your suggestion would ensure that only 100mS would have passed until we ran the code inside the "if" statement. Good call. 👍
@@RalphBacon I should have called it drift, but that indeed is the issue: scheduling the next action 100ms from now, instead of from the previous action. Keep in mind that the millis-overflow edge-case could be tricky here, so it'd be best to extract it from the two functions and write it as a separate function that handles all the miserable edge cases :)
Oh, man, I came here to say this!
And for exactly the same reason you could initialize the static variable with 0, Ensuring that the first event happens 'exactly' on the set interval after startup
@@sdr9682 Doesn't that timer counter start at 0 when first turned on? Oh... I just thought of something. I don't think it resets the timer counter when you =reset= the AVR.
So initializing it with 0 at startup, you do have a set interval each time, even if you reset rather than power up.
Nice video, this is how i started with arduino 10 years ago. after that i made my own library that can increase or decrease value(s) without blocking. this way i can easly use PWM or servo's with Scene time and fadetime. and 3 years ago i also added sin to it to make it more smooth.
Cool. Sounds great Emile 👍
Brilliant video, for getting started on non-blocking coding.
I am assuming that reading (and especially de-bouncing) buttons - without the use of delay() - will also be one of the upcoming examples on the state-machine list.
Sometimes I need to have buttons de-bounced, both when they are pressed and released. I find that is a good job to define via a state-machine.
Well, I'd probably be more inclined to have a "readButton" function that does all the debouncing but we will see.
@@RalphBacon I did think to put the state-machine into the function "readButton", so the main loop, can still consist of a list of function calls.
9:30 Regarding the explanation for the static unsigned long ..I wanted to give you a hug Ralh❤. Best explanation I have ever seen! Thank you a million times.
Thanks for that! Glad you found it helpful.
Well, it has something to do with the heap and stack, each time the function is or any function is needed its loaded in to the memory (SRAM).
so every time when the function is needed, it's recreated along with new variables inside the function and deleted when the function exits.
Static prevents this, it's static like the name implies.
Nice job Ralph. This is probably one of most useful thing newcomers to embedded programming can learn.
Glad it was helpful!
Very good video, calm and patient explanation. Please follow up with a real state machine video - I barely can wait!
Regards
Soon. All things come to those who wait. And wait. And... you get the idea.
Always have trouble getting a grip of this ....thanks...look forward to next installment.
I hope that after this video, at least the first (multitasking) step should be pretty easy, Jorgo?
Cool as always. You need to take advantage of the pcbway perks and design a custom uno board that people can buy and populate themselves. I would love to have that.
I love the proper practices scope, encapsulation, millis.
I rarely write my code in the loop. It's usually a series of function calls. I love your videos though as I don't code as much as I used to and you're sharp. No one else introduces this stuff to people. Thank you.
Thanks for your kind words. I have ideas for a couple more videos that might help people write better code. Stay tuned!
My first HW state machine was an prom and a latch (back in the TTL days). Clicked on this video to see if you had something easier to read than: switch (current State) {
Waiting to see your next video😊
That is awesome!
So you used the floating MOSFETs in a PROM for a counter, or something?
Let's see now. 16-bit PROMs with 16 address lines (64K words of storage, I presume). Bring the PROM data lines out to a 16-bit latch, then connect the sixteen latch outputs back to the PROM address lines.
But what's coming out of the PROM data lines? I guess we would need to "burn fuses" there first. Each location would contain the next memory location to be accessed. First location (zero) put 0001, next location (one) put 0002 and so on. A programmable counter.
As a State Machine. 🎇✨🌟
@@RalphBacon not so complicated. The latch holds the current state. Feed the outputs of the latch to the prom's address lines along with the inputs to the state machine. Wrap the outputs of the prom back to the inputs to the latch (this represents the next state number). Program the prom so that at the address of the current state, with the inputs to the state machine the prom holds the next state number. Each time you strobe the latch it will move from the current state to the next. Speed is only limited to the speed of the prom, sub microseconds. Pre FPGA's and cheap microcontrollers it worked.
Awesome. The good old days. Well, maybe not, in this case. 😁
Greetings from Canada, Ralph. PLEASE delve deeper into State Machines, as I have been trying to grasp this concept, and how to implement it, for quite some time, now. Been scouring the internet for good explanations.
I agree, the archer video dude helped (here on YT) but I'd like to see more.
Will do! Next video on this subject (soon... ish) will delve into actual "states" so we can skip to the correct bit of code in a longer, single-responsibility function.
I use case statements to code state machines something like:
uchar8 abcReceived = FALSE;
uchar8 state = 0;
while ( abcReceived == FALSE )
switch ( state )
{
case 0:
if ( inputChar = ='A' ) state = 1;
break;
case 1:
if ( inputChar == 'B' )
state = 2;
else
state = 0;
break
case 2:
if ( inputChar == 'C' )
{
state = 0;
abcReceived = TRUE;
}
else
state = 0;
break;
default:
// an error condition or expected state
break;
}
@@RalphBacon Thank you, Ralph. Looking forward to your next lesson.
Your name is a great component of second breakfast! Much appreciate all your hard work to help teach those of us whom hobby our hobbies.
Thank you very much!
I can see a major problem with this. Its not preemptive. A subtask has the possibility of locking up the whole thing.
I wrote a preemtive task manager in assembler that I use as a pseudo o/s. If i need something done when a condition occurs, I add a task to a Que that is prioritized based upon a priority number. What I put into the Que is a priority, condition, and task to run when condition is true. This condition can be a timer, semaphore, port change, etc. The task in the Que is passed control upon condition being true. The task does get interrupted by a timer interrupt, so a task can not hang the system. Before returning control to the interrupted task, it will place on the Que the interrupted task with the same priority as the original task wjth a condition of true. The task manager will rescan the Que and if no other higher priority task needs to run, the original task will get back control. Easy peasy
I use this all the time when coding on micro processors like the arduino, PIC, and TI
Correct, it's not pre-emptive - as I mentioned in the video it's important to prevent one task from monopolising the μController processor. That is, it's important the developer deals with this!
However, in the next step towards a State Machine, we can simply exit the function at a particular state and allow other things to run. A pseudo-yield, if you will.
It's certainly nothing as sophisticated as what you have described and written, but does work well enough for non-critical tasks. Have you published your task scheduler?
I've been made aware of FreeRTOS (optimised for Arduino) which I hope to try out soon, especially if it runs on the Nano Every (AtMega4809 chip which as 6K SRAM). github.com/feilipu/Arduino_FreeRTOS_Library
@@RalphBacon hey Ralph. No I did not publish it as it is written in assembler for each processor type i use. Its not ready for prime-time :)
Very good instruction !!! Three thumbs up !
My question is, what happens on day 50....when the "static unsigned long" variable is as high as it can go...but the time counter starts over .....Does everything stop ?
Have I missed something?
Yes, everything stops and the universe implodes. It's a well-known Arduino side effect. 😉
On the other hand, nothing happens because the counter (and millis) is an UNsigned long integer, it tries to take away a larger number (the old value of millis) from a tiny number (the new millis) and gets the correct value! Magic! Arduino have thought of everything.
As an example, let's pretend that millis was stored in a single byte, unsigned integer so could only count to 255 before overflowing (going back to zero). When that happened, we might suddenly be faced with a calculation such as 2 - 200 which normally could not be done (or would go negative, ie -198). But the value you get is 58 (ie the number of milliseconds that has passed since the count was 200 and is now 2.
If you don't believe me, write a simple sketch to prove it to yourself!
@@RalphBacon I've no reason not to believe you.
I'm just trying to understand.....
time now = 2 Millis()
time then was 4 million +/- redmillis
so, if(now (2) - then (4 million) > 500)
{
do stuff...
redMillis = millis(); //
Write that simple demo sketch! It's an eye opener! Nothing to do with millis, really, more to do with maths and unsigned integers.
@@RalphBacon Thank you , Sir.
Hello Ralph, I would like to see a follow up to this video, I enjoyed your informative and educational video. I'm somewhat of a neophyte to Arduino sketch coding and would like if the sketch you presented will work with a Larson Scanner sketch using millis()? Looking forward to viewing your other related videos. Thanks again for your instructional video.
Neophytes are more than welcome to my channel; it's why I created it!
When you say "will [it] work with a Larson Scanner", do you mean you want to write a sketch that does a Knight Rider/Battlestar Galactica sweep of LEDs from left-to-right and back again?
If so, there are easier ways of doing that with NeoPixels and I demoed that (in passing) when I did recent video on NeoPixels (see video #239 ruclips.net/video/SStRG-_1wXc/видео.html ).
If you really want to build a Larson Scanner (or have one) with a number of LEDs then a State Machine (my next video in this series, coming "soon") would certainly work (each LED is a State).
@@RalphBacon Hello Ralph. Thank you for welcoming this newbie and helping this old dog learn a new trick. I apologize for not being clear in my wording of my question to you. What I was trying to convey is I have a Larson Scanner (LS) using millis(), which I would like to incorporate with your sketch/code. When I wrote will [it] work with a Larson Scanner, I neglected to write I am planning to add the #define LEDs from the LS code at below your #define grnLED 8 of your sketch. The void from the LS below your void blinkGrnLed() and will place the LS void loop() below the void BlinkGrnLed() of your sketch and was wondering if you thought combining your code/sketch and the LS code work? Or is this combining of codes/actions require a “state machine” sketch type code? Thank you for your thoughts and I will let you know what comes of my attempts to combine these sketches.
If you can describe your Larson Scanner (or give me a link to it) I can help further.
For example, is your LS just a string of LEDs that you can control however you want or has it got electronics in there too? Have you got any code that already works with it?
@@RalphBacon Hello, Ralph, thank you again for responding back to me, it's kind of you. Just a quick FYI to let you know I was able to combine your sketch with the LS sketch I found it made for a interested time. Thank you for the link (video #239 ruclips.net/video/SStRG-_1wXc/видео.html ) to your video on NeoPixels it will be a help in learning to control these as well. I am looking forward to viewing your other videos and hope you don't mind me dropping you a question from time to time. :-D
State machines are fun! Show transition diagrams. Pass on UML. Maybe show how easy to add states in a table-based machine.
I will probably show a quick and simple transition diagram but will concentrate on getting the states written and read. Noobs, remember. KISS as they say.
Not having blocking functions and in this case, using a timer, is good programming practice and you have explained it well. I have never used static before and used global variables as counters triggered from a timer, it works but not ideal. Look forward to your next video and implementing states machines as never really understood them.
Using timer-driven tasks is another concept (let's face it, using FreeRTOS to run tasks is exactly that) but not just yet, Crawl before we walk, right?
haven't done any coding in months now and have forgotten all of it, came back here for a refresher. wrote a code and designed a pcb and control panel/box to control the doors and hydraulic teasing rotors on my diet feeder on the farm and looking back over it I know i wrote it but can't remember all of why i wrote it 😅 have a 4 rotor silage rake to do next, trying to use multiple switches on a single wire(10 infact) to control it.
one question i have, I want to individually control the rotors but from one signal, i.e I press one of 2 buttons and hold it, first rotor lifts one one side, I release and the opposite rotor lifts, now my problem is that these rotors both need to stop at a set height AND the rear rotors need to lift after a certain delay from both the first rotor and second rotor on the respective sides and also stop at a set height.
I see 2 options, first would be an awful lot of code, the second would be to use 3 arduino, on to accept and relay the signal and 2 more one for each side of the rake to control the left and right pairs of back and front rotors?
Using more than one Arduino board is probably overkill. I would suggest building (designing) your project stage by stage so you're not trying to do 10 things at once.
@@RalphBacon funny how fate works, I'm only after starting this project seriously 2 days ago and I've figured a way using 2 state machines contained in 2 separate voids to work the machine independently in each side. Basically recapping your videos made me realise that the voids worked independently of each other(or what it seems to be to me) and that cleared a path to do what I wanted. Thanks for the reply
Great Ralph, just explained something that has kept me awake a few nights
42. Always the answer.
@@RalphBacon But what is The Ultimate Question??
The Ultimate Question is always the same: Why?
Another Great video. I've seen static mentioned in some sample programs but never understood it. Thanks, it will be very useful. State Machine lecture Please.
Glad it was helpful!
Yes please to a state machine follow on. Do you use any timer libraries ?
It's a _simple_ state machine, I don't want to scare the noobs. I don't use any timer libraries as such; you mean to trigger functions or something else?
@@RalphBacon Thanks Ralph, loving you posts! Yes, timer libraries for doing the 'have x millis passed' type functions
@@georgeboydratcliff1036 please, which would such library be?
@@alvarobyrne I like Eventually - makes timers almost like an event (hence the name!)
@@georgeboydratcliff1036 Thank you will check
Great Video !! Well explained!! I hope there are more parts to come. Up to a real State Machine.
That's the plan!
Quite good training indeed. Thank you for the time and effort you put into this.
Glad you enjoyed it!
I use Finite State Machine Library, FSM. Very easy yo use.
Thanks for the tip!
Wow ! I self taught myself in C+ and didn’t know about static variable or the ! ‘trick’. Most of my Esp8266s use Blynk which requires keeping the loop clean, thus the extensive use of timers. Looking forward to the next video. Is that push button going to be used as an interrupt ?
The push button (and yellow LED) will be used to further develop the State Machine code. Stay tuned! Or subscribe!
Thank you! Seems silly NOW to move the millis() value into the "current time" as most do , where you just utilizing the mills() in the IF statement to compare it to the last time the IF statement was executed. Every one teaching on RUclips has created the extra variable "current time". I will be following you. Thank you Sir! I will be studying the "static" and how the compiler does stuff with it during optimization.
Glad it helped!
@@RalphBacon
I like this simple use of comparing millis but, what about when the clock rolls back to zero? If the capture of the millis is at the end of the clock and rolls over to zero shortly after, the “if” statement will never execute again because the millis compare will never be greater than 500, or whatever value you have.
Great video! I've been using pseudo multitasking on Arduino's, ESP8266's and ESP32's for years but learned a few things from this video (Ie. the use of Static). I'm glad you pointed out that this is not true multi-tasking as that is often mistaken when discussing this with the Arduino/C++ coded devices. It would be interested to have a discussion of true multi-tasking with the dual processor ESP32 but that probably would be a fairly advanced topic?
I'm not sure what the fascination is with "true multitasking" versus pseudo-multitasking using time-slicing.
Yes, using both cores on the ESP32 is a possibility for true multi-tasking but then you run the risk of crashing Wi-Fi or BT or something on Core 0. We're not running NASA here, so why the fascination?
That all said, I did a video on simple dual core multitasking on an ESP32 here in video #149: ruclips.net/video/jpVcCmh8sig/видео.html and on passing values between tasks in video #151: ruclips.net/video/ywbq1qR-fY0/видео.html
But none of my Real World ESP32 projects use both cores as application cores.
Nice. I am intrigued by the button and yellow led. Interrupt example?
No spoilers but it's still to do with moving to a State Machine.
Static local variable tip was really important and I think should help resolve an issue in some code I was looking at a while back, thanks.
Anything that makes your code better means my work here is done!
Just found your video series. Thank you. I need all the help you offer
Happy to help!
I've just found your channel and I'm enjoying your videos very much indeed!
My first introduction to Arduino-esque non-blocking code was the example sketch 'BlinkWthoutDelay' that's bundled in with the IDE.
Welcome aboard! Yes, the Blink-Without-Delay is a good intro to only carrying out tasks after a predetermined time. Not quite multi-tasking but getting there, and could be "enough" for many projects.
Thank you for the millis inclusion for pseudo state operation on an Arduino. I have to think about an LDR to start the millis counter to flash LEDs for a period of time then stop until the LDR detects nightfall again. (Two red LEDs hanging in a tree at Halloween to simulate a critter looking down on you.)
Sounds scary 👀
Great stuff Ralph! For 5 bonus points code how you deal with the millis() rolling over...
It already deals with the rollover just as it stands. That was an easy 5 points.
@@RalphBacon yes, but here is a fuller explanation ... The question often comes up in conversations about signed Vs unsigned integer calculations: what happens when the max value is reached and goes negative if incremented. As any assembly programmer should know, the compiler generates the same code for both signed and unsigned. Let that sink in and you will realise the it MUST still work.This is the beauty of twos-complement!
Pro code right here! Thanks for the lesson!
You're welcome!
A problem of using millis() without any type o case protection is when millis() overflows, the LEDs are not going to blink anymore
I thought with unsigned long it will just going to start over from 0 and count up again ?
Wouldnt using the "millis() - variable" technique be overflow proof though? Whereas "millis() > variable" would be troublesome.
@@jestr1845 When millis() goes back to zero, the variable (redMillis) is going to still be really big, which is going to result in a negative number and the difference never pass the 500ms again. This can be a bigger issue when you use the micros(), that reset every 70min
If millis( ) overflows, it will have NO EFFECT on these sketches. Trust me, I'm a doctor, er, programmer 😆
@@FelipeLenschow It should still work. Lets use bytes for numeric simplicity:
255 - 250 = 5 // as expected
256 - 250 // is in reality...
1 - 250 // which simplifies to...
-249 // which as a byte would be...
6 // as expected. Or am I wrong?
Very good video! I think the next step into a state machine is to use a state variable instead of digitalRead. After that, show how to make the on and off-times different for a LED.
I want to show some Real World use of a State Machine; using one to record the state of the LED is not necessarily the best use as we can do _that_ the way I've already done it.
Better use would be to be able to exit (and re-enter at the same point) the function between states (assuming there are some, not yet in the demo I showed).
Great Video Ralph - Reinforces use of Functions
Well, we don't want huge big monolithic loops, do we?
Hello. I really like your videos. I'm really new to Arduino but not in programming. I have a question. Is calling the functions many many times "for nothing" in the loop using more power? I'm thinking of battery powered projects. Would it be better if we put a delay of 100 ms in the loop? Thanks for your great channel.
Whether the loop is called thousands of times a second, or you put in a "delay" (which is an even tighter loop) uses the same power.
If you _know_ you have nothing to do for a while, the best approach is to put the microcontroller to sleep; essential in battery powered projects.
I've done a few videos on Deep Sleep and the Arduino, have a look!
@@RalphBacon Thank you. I will surely watch them.
It's good to see you educating folks on good programming practice.
Hopefully in a subsequent video the blinkxxxLed() functions get replace by one blinkLed() function, initialized with pin number and interval. (i.e., remove the duplicated code from the sketch.)
Nope. This was a simple demo just to do two things. The fact that it was the same code was done for simplicity. The second function could have turned a beeper on/off but would have got very annoying very quickly.
@@RalphBacon Sorry, but "beeper"??? All I was suggesting was that a class could be instantiated with something like redLed = blinkLed(redPin, redInterval) and greenLed = blinkLed(greenPin, greenInterval), which is scalable to the number of available pins. Because code isn't duplicated in such a case, it is more maintainable, which I hope all would agree is a good programming practice. If that's not something you want to cover on your channel, that's fine. It is after all your channel.
I've covered the DRY concept whenever it seems sensible to do so; what I meant by my comment is that you're concentrating on (and commenting on) the duplicated code whereas in Real Life the two functions would be doing something very different (to each other). That's all 😜
Rather than repeating the code snippet over and over again, I think I would just create a class called "IntervalTimer" that has a constructor which takes the interval time. The class has a Poll() method that you call in the loop and returns true when the timer has expired (and resets internally when that occurs). Then create two instances of that class, one called "redIntervalTimer" and one called "greenIntervalTimer". Better yet, I would create an additional constructor that accepts a lambda that is the action.
As soon as you mentioned the word "class" it told me this video was way below your pay grade. Beginners have no idea what a class is or how to use one.
@@RalphBacon And then I mentioned "lambda" :). Fair point, although judging from the comments, your followers seem to have a fairly wide range of experience. Well then, I suppose we can view my comment as "an exercise for the interested reader" then! :)
A very good answer. A bit like a teacher saying "I'll leave it to you to read more about OOP in your own time, class!" Yeah, like that's ever going to happen.
@@RalphBacon Seriously though, and judging by the comments, I do think many of your followers are more sophisticated than you might think. In addition, I would hope that beginners who mainly use libraries ought to learn at some point that they are making an "instance" of something based on a "class" definition (BTW, I may have "reinvented" the "PollingTimer" library so I might suggest that beginners examine that library after watching your video).
After all a "sketch" is C++ so why not take advantage of the "++"? I gather that you will eventually get to discussing the notion of a "class" and "instance" at some point. At least I hope so! :)
BTW, I really enjoy your videos and the way you approach things, wish we lived in the same neighborhood! :)
Me, after reading some of the comments: Man, some of these people need to relax - he made it clear that this is just the first video, etc.
Also me: BUT WHAT ABOUT WHEN MILLIS() ROLLS OVER TO ZERO AFTER ~49 DAYS??????
Nothing happens. It all "just works". I'll have to explain why in another video.
@@RalphBacon Well, that was kinda the whole point of my post, innit bruv? 😊I'm sure that the state machine code will handle the rollover case with aplomb and literally zero flashes will be missed when it does roll over.
Quite so, you have understood it all very well. 👍 Hang on, "innit bruv"? 🙄
@@RalphBacon I like to pretend I'm a West Brom ultra, even though I'm a Chelsea supporter living on the US.
I know you stated "no bit manipulation", but the first solution I thought was:
Leds on PD0 and PD3:
void setup(){
DDRD=255;
}
void loop(){
PORTD ++;
delay(300);
}
Well, you've broken the rules (admittedly, my own rules) but if it works then it works!
Excellent content, thank you! In times of inflation, 15 minutes can become 24, no problem 😊
Very true! When I say "15" that's _Arduino_ minutes which, as everyone knows, is longer than a Real World minute 🤣
Hi, just found your channel, this video is really useful for my projects since I'm always seeing projects using millis() but didn't know exactly how it worked. Thanks!
Glad it was helpful! Feel free to subscribe so you don't miss the next video (end of shameful plug) 😁
Ralph is this part of the code explicitly necessary for this to code to work? static unsigned long redMillis = millis(); Couldn't one just declare the variable in the beginning equal to zero. unsigned long redMillis = 0; this would be done in the setup loop or when declaring the variable. Then the redMillis = millis(); in the if function would update it properly? I liked the introduction of the static modifier but don't really see a benefit. Unless the main benefit is for readability. Being able to set a variable once within another part of code looks great from readability standpoint.
If the variable were declared as a _global_ variable and initialised in the setup() and then modified in the function, that would work. But!
But I dislike global variables because their scope is, well, global! By using the _static_ keyword from within the function that it is being used in, it means that the scope is restricted to that function - and it's "just a snapshot", not an important, global variable (like the time), after all.
What would happen when millis() is called after 60 days? Edit - I discovered becuase it's unsigned it'll just overflow to 0 and the subtraction method will still work just fine
Exactly so!
The timing would not likely be 500ms though. It should use a signed variable to maintain timing.
Another terrific explanation Ralph. Thank you!
Glad you liked it!
Good explanation. Thank you
You are welcome!
great video and a very good explaination of static and global commands in a program I think 😄
Glad you liked it!
@@RalphBacon I did like it of course understanding it is another thing😁
My choice of words was deliberate! 🤭
I'm not sure "short video" means what you think it means, Ralph! 😂 Very useful info, thank you!
Less than an hour, in my book. In fact this was less than half an hour. Hardly worth making, it was so short. 🤣😁
😁😁😁
Back to basics -sort of, nice content. Thanks.
"Sort of", Flemming? I see you liked the content, so that's pleasing to me 👍
Nice video ! one question: Isn't correct on the forward declarations put also their inside code ? (and skip like this the part after the loop) How critical is the place of the functions ( i see that you put them after the loop) ?
If I understand the question correctly, you can write the entire function before you reference it (in the loop, for example). That way you do not need to forward declare separately.
@@RalphBacon So i suppose the placing of the functions inside the code is just a matter of personal taste, and if i decide to place them before setup (like i usually do, so i can have all ''basic'' ingredients of the code gathered), then forward declare and complete function code is just matching (and there is no need to write them twice). Thank you for clearing out to me this part !
Cool video, IMO using object oriented code can make this sort of program much cleaner, as you wouldn't need to repeat the state machine logic code for every instance you want to create
If you are using C rather than C++ then classes and such doesn't exist so it depends what your compiler is.
No beginner understands OOP so I'll be giving that a miss for now. This is still very clean _and maintainable_ code. Also, this demo flashed LEDs but in the Real World I hope we do more than that!
Hi Ralph, great subject to cover but I have a question and that is why is it preferable to use 'const' instead of '#define' as the Arduino manual recommends ?
I don't know why they would recommend that.
A literal value (eg from a #define) is substituted by the pre-compiler and then used directly (it isn't fetched from memory). It does not use up memory space as it is not held in memory. It's part of the code.
Const defines a fixed variable (so it does take up a memory location, unless the compiler is smart enough not to do that?) that can be scope-controlled (eg only valid in the function it was declared in, just like any other variable) whereas a #define can be used anywhere - but that's no advantage, not really.
I think the use of #define is much better than having magic numbers as long as the name is unique; using const might make your program more robust with its scope control but it does use a small amount of memory.
Cheese!!!!!!!!!!!!!!!!!!!!!
@@RalphBacon Explanation from the Bald Engineer site. Sorry YT blocks outside links.
Since the avr-gcc compiler is smart enough to keep variables out of RAM when using the const keyword, it is better to just use that. Eventually the “text-replace” nature of #define is going to bite you with a stray semicolon or other unexpected replace
@@RalphBacon Why n9t use #define for the size of each of your timing periods, then? Aren't the 100 and 500 "magic numbers" as it stands?
@@RalphBacon Ta !
Dear Ralph, I installed VS code and the paltfomIO extension. I was able to run this code and even make some modifications for views in the thermonal. However, after closing the project and VS code and reopening VS code, the PlatformIO icon no longer appears.
I've done everything, reinstalled, PlatIO and VSC and nothing. I made a thousand queries in Git and found that there are dozens of cases opening with the same problems. Please do you have any tips so I can reset PlatIO in VSC? I found your explanation very interesting, saying that the compiler only uses the static variable once inside the if() loop. Congratulations!
My PlatformIO (alien head) icon appears several (long) seconds after opening Visual Studio Code. It's an add-in, after all. Click on the extensions icon (4 square boxes) and ensure that "Platformio IDE" is enabled globally and running.
After that let me know if it's working or not.
Genius! I'd love to know more.
All in good time more will be forthcoming.
Awesome, Ralph. It's great to have someone address the things that can really trip up us Arduino newbies. And your presentation really makes learning this stuff a lot more fun, when it can be VERY frustrating! Thanks!
PS: I have a simple sketch that I have been working on that fails and make no sense why. Want to have a go at it?
It depends on what you mean by "it fails". Compilation or run time?
@@RalphBacon What I'm trying to do:
NEMA 17 Stepper, TB6600 Driver, Arduino Uno, (2) momentary switches, Potentiometer, Rotary Encoder.
One switch runs motor in Fwd, one in reverse, Pot controls speed for both. Code to change driver ENA pin to LOW when button is released so motor will spin freely.
(works fine) Rotary Encoder to step the motor a few steps per de-tent. (Also works, but will not release the power to spin freely) Adding code to switch ENA pin to LOW causes the Encoder to become very erratic and not work. Thanks Ralph
Is the Encoder serviced via an interrupt routine? Or done in the main loop, looking for a pin to go low/high?
@@RalphBacon Not using an Interrupt.
May I email you using the address in the "About" tab of your RUclips channel?
Thanks so much for taking the time, Ralph.
Regards
Yes, do that.
Hello Ralph. If 'delay(1000)' is placed in the main loop this will cause the LED's to be affected by the delay. The functions (redLED and grnLED) will then be delayed by 1second..
Hang on, are you suggesting we _put in a delay_ or something else? Or is this just an example of something that might affect the timing?
The thing about multi-tasking (pseudo or otherwise) is that we _never, ever_ use delay.
@@RalphBacon dear Ralph. In your example the main loop will sequentially poll the red and green functions. According to my assesment if a delay(1000) is placed in the main loop the main loop will be 'stationary' for the 1000ms caused by the delay() function. At the end of the delay(1000) the next polling of the red and green functions will take place obviously both functions will return millis() that will be greater than at least 1000ms which is greater than the delta 500ms and delta 100 millis read by those functions, thus causing the delay() to become the dominant timing cycle.
In conclusion if the intention is to do something every 500ms and 100ms delay(x) may not have x values greater than 100ms. Then if the processor is not reset within about 57days the millis register will roll over starting at 0.
rollover is no problem if you do it right
learnt about usage of static variables
Yes, always useful to know about them.
Me too.
Of course, once you're done with this State Machine mini-series you will revisit this setup with the Timers->Interrupts->ISR approach, won't you?
Interesting you should say that...
@@RalphBacon ... or maybe not, since you've done it already not so long ago. My bad for not paying attention!
ruclips.net/video/fFrL5Vh8Dis/видео.html
Hmm, for some reason I cannot see your reply to my response but you mentioned that I'd already done it and I forgot to paste in the link to the video in my first reply... TL;DR here's the missing link:
video #209 Arduino Timer Interrupts ruclips.net/video/fFrL5Vh8Dis/видео.html
BTW did you notice the bit manipulation we had to do in that video? No easy Arduino-speak and no-one has invented a library for it yet - although *Norman Dunbar* has *AVRAssist* which at least does all the hard work for you:
github.com/normandunbar/avrassist
@@RalphBacon Yeah, my reply had a link to the exact same video you pointed out. Maybe RUclips has a thing against linking. However I can see the one you posted just fine (probably because you're the OP, therefore more trustworthy in YT's eyes).
I just re-watched that video and frankly that bit-magic is not that scary once you know what's behind it. I went through all that 30 years ago in college when I had my first programming courses (general C / C++ and also some microcontroller stuff).
Anyway, I really like the way you handle the educational approach compared to other resources that tell you the WHAT but not the WHY.
12:30 Could you elaborate a bit more what the problem is with defining those global variables? That is the way I always work and to be honest I don't understand why that is such a problem. I'm only an amateur but keen to lean :)
I've ranted before about global variables but here goes.
Although I (tongue-in-cheek) call them the Spawn of the Devil, that's not strictly true. I sometimes use global variables IF they are being used globally; that is, by more than one function.
But variables, in general, should be restricted in their visibility (and, hence, use) by defining them in the function (or, better still, namespace) to which they belong. That way, they are not arbitrarily changed by functions which have no business using them, let alone updating them.
It's to ensure your code is "cleaner". For example, if you were to refactor (refine, rewrite) one of your functions by removing it and adding it to a different namespace, would the rest of your code throw a hissy fit at not finding a variable any more?
So, put logically connected functions into one file. Include that file into the main sketch with the use of the #include statement. Ensure that file has a namespace so that you can't just access its functions or variables without also specifying the namespace. See my simple videos on namespaces if this is not clear to you!
Real World Example: you are reading a temperature sensor. Chances are, that you want other functions to be able to know what the current temperature is (eg, to display it, or take action when it reaches a certain level). So should this variable (let's call it "currTemperature") be visible to all functions in your sketch?
Not if you want to do a good job. The functions that are to do with the temperature sensor might be in a namespace call "sensor". So that variable is now referred to, by the rest of the sketch, as "sensor::currTemperature". But it is not global, and it should be very clear in the code that you are referring to the variable in the "sensor" namespace.
TL;DR I covered all this in various videos before! Grab the pdf file of all my videos and search for ones that cover this topic. Worth understanding the concept, at least!
@@RalphBacon Thanks so much, this provides a good starting point for understanding the namespace concept better. I will definitely check out your other video's for this topic!
Yep, learn something that I can use in future but as your note in the video pointed out, this only works until you have something like a slow sensor that locks up the CPU, right?
This is partly true; my DS18b20 temp sensor takes "ages" to get a reading after a request but that's where a true State Machine will deal with it.
OTOH if the call to initialise your SPI bus takes a whole second there's not lot that can be done other than to rewrite the call to make it more responsive. Fortunately such cases are rare.
Stay tuned as we progress this!
You would check every so often if the sensor has data "ready" sometimes there is a flag to check for this, that way you don't need to wait for a long time, you would also benefit from interrupts.
Ooo that's interesting, I can use this on my boat automation project.
Quite possibly. If you need to do more than one thing at a time this is the way to do it (on an Arduino).
@@RalphBacon I'll be using esp32
A variable has lifetime and scope. For that „static“, scope is „inside the function loop()“, but lifetime „global“.
In less formal words, it is a global variable, but hidden inside function loop().
BTW,
„static auto readMillis = millis();“
would avoid using the wrong type. And the code only works for „unsigned long“. Missing the „unsigned“ (and ignoring compiler warnings, which is the default for Arduinistie) breaks the code.
'Auto' is a great new keyword and I often use it. What I don't understand is why the compiler picks an 'int' type for small counts (less than 255) so I use uint8_t specifically.
@@RalphBacon because it’s a language definition.
avr-gcc also has a switch to make int 8-bit, as you would have if the hardware register size typically defines the int size.
But don’t use that switch, it will open Pandora’s box.
With every inconvenience of the AVR8 GCC, just be thankful it even exists.
Generally, local static variables aren't a good idea in C++ because the have runtime cost.Each time the function runs, the compiler has to check whether or not the variable has been initialized yet. And on PCs, this is even thread-safe, thus adding mare runtime overhead.
@@didgerihorn never saw this in generated AVR8 code. The local statics are just global variables, initialized by the startup code, but w/o visibility outside the function.
So „general“ is not always the same as „specific“.
@@fromgermany271 At least the program has to check whether or not the variable has already been initialized. Ok, here in this example you would have to do this manually anyway. However, in general, is is not a good practice to use local statics. People will hopefully move on from AVR :)
Could you make a video where you can make a button function as a switch and a second button to change which device it enables/disabled? I can’t seem to find a good tutorial on something like that yet it seem like a simple task. Thanks
Remember that the button doesn't do the actual switching; it's your microcontroller that decides what "thing" to switch. So if you detect a button push, then decide from the second button (state) which "thing" to switch all will be well.
My next video on State Machines is due in a couple of weeks or so, and uses a button, so you may get some ideas from that.
decent beginner explanation, for true robust multitasking, give each LED a dedicated micro controller. This would apply to control of other "multi tasked " devices.
Does giving each LED a dedicated microcontroller actually achieve a more robust solution? Especially if each LED is in some way dependent on the others?
company boss loves your solution and gladly spends more money on hardware
What about digitalWrite and read taking some ms to complete in both functions, they will take it from 500/200 ms check. Isnt it?
digitalWrite/Read are relatively slow as they carry out several checks (safety checks for beginner coders). You can speed them up by setting the relevant registers (eg PORTC). That will hardly take any time at all.
@@RalphBacon Even doing such checks digitalWrite is faaar faster than that... Assembly for them should not have more than 30 instructions... Most uC do one instruction in 1-3 CPU cycles, so even 1MHz CPU can do about 500 instructions in ONE ms. Notice that slowest uC has at least 8 MHz - so it should do at least houdred digitalWrite() per millisecond. For blinking it's overkill.
you need almost 300 digitalWrite to waste one millisecond
I get the heebie-jeebies whenever I read code that has an implicit reliance on time or the specific hardware. We're dealing with C++. Functions can be overwritten by, for example, a function that invokes an actuator ("Open the door, Hal") that operates in realworld time, not in CPU cycles. If we rely on the speed of the processor, we're not doing a good job at abstraction.
Very interesting video. But in reality, if the green LED function is doing something that take e.g. 5 seconds, then red LED function is not that independent anymore. Or am I wrong?
Not at all, Pekka. I did make a note in this video (you missed it? 😲) that it was important not to let a single function monopolise the processor time. That's where the true State Machine helps by allowing the function to exit in a timely manner but come back in at the same point. More in a later video.
@gsdcbill Thanks, interesting part: Task (preemptive scheduling) implementation
@@RalphBacon Yes, you are right. I look forward to more videos about multitasking on Arduino
I understand that you are starting from the beginning but putting state machine in the title may be a little confusing / misleading, it is essentially a timed function loop.
It's the first step towards ensuring a State Machine can even run. Or do you propose we have a State Machine in a monolithic structure? We've got to get more things running _first_ and that we did!
@@RalphBacon I am not proposing anything, but reading the comments some think that this is a state machine when it is not.
I seem to remember this type of thing was called "timeslicing" back in the distant past.
Pretty much!
This is great 👍 keep coming with more videos like this.
Will do!
Thanks
Thanks Ralph - very informative ..you are the Champ!
Ooh, I like that: champ Ralph 😁
Another great video and of great interest (to me at least :-)!) I can see where I can break down my process and can step-wise deal with certain operations. I'm unclear however how you would deal with overal; (Global??) operations like checking and acting (possibly) on a temperature reading that should happen over various states. What happens if you define a temp control cycle whilst the program 'jumps' from state to state? Thanks!
Glad it was of interest!
Regarding conflicting states, remember that the program is only doing one thing at a time. So if we move from one state to another then that happens before the program can even notice that a temp control reading has to happen.
Is that what you meant? Or were you talking about interrupts? Or perhaps a common routine to read the temperature in various states?
@@RalphBacon Hi Ralph, thanks!! Sorry for being unclear, but I meant the latter; a common routine to read parameters in (across?) various states. I could repeat the lines/call the subroutines from every state, but wonder if there is indeed a way to have a common routine?
Well you could have a function that is called from various states, that's the most obvious way of doing it. Or a timer task that runs X times a second (or minute) to update the value regardless of what else the sketch is doing.
@@RalphBacon Clear, many thanks!!
Where can I buy that bread-bord with the power terminals?
Please put it in the description info above!
It's a good breadboard, I've got five now. It's one row of pins wider than standard too, so good for ESP32 type modules. It's called a "AD-11 Advanced Solderless Breadboard" and can be found here bit.ly/3LUnsNJ but if you don't live in the UK you will have to Google!
@@RalphBacon Thank you very much!
If timeNow minus timeStarted is greater than (or equal to) setTimePeriod, do something and then reset timeNow to now.
Um... I'm not clear of the context in which you are applying this.
I do
millis() now minus oldMillis > delay between iterations
then reset oldMillis and do the work.
@RalphBacon millis is timeNow, oldMillis is timeStarted and check if it's greater than delay - setTimePeriod. Reset oldMillis - is the new timeStarted, which is now..
Thanks very informative leaves me wanting more information
So you'll be back then? See you in the next video (not about State Machines).
Thanks Ralph, that's really useful.
You're welcome!