As I decided to have an argument with a very sharp craft knife, my typing has slowed down considerably. It may take longer to respond to your comments. Sigh. github.com/RalphBacon/224-Superior-Serial.print-statements/blob/main/Thumb%20Argument%20With%20Craft%20Knife.jpg
Sadly RUclips requires every title to have a little bit of clickbait into it or your video won't be recommended to people. When I saw the title I was intrigued but never did I think I could never ever use Serial.print. It was on topic. So clickbait percentage was low, usefulness high for a lot of people. @RalpBacon: Thank you for the title or I would have never seen this. And thank you for the video. My 13 teen your old daughter loved it as well (we are learning about the Arduino environment together).
@@RalphBacon 100% it's a clickbait video title.... But as a newbie programmer this kind of information is incredibly useful, and I wouldn't have found it without your clickbait. I think this video is a great use case for clickbait. Experienced Devs will know what you're going to do within a minute or two, and everyone else can stay for the explanation and demos.
@@BeardyMikeI totally agree. Took the bait and enjoyed every bite. I've never seen #define used in Serial.print statements before and found this video extremely useful and informative. Just as a side note, you can also use #define debugbegin(x) Serial.begin(x) to remove the serial begin statement as well. Initializing serial takes up memory as well. Thanks for the great video! 👍👍👍
I am a mech engineering student and I am doing a 99% electrical project for my research dissertation. I spent weeks trying to figure out why my sketch has been printing data every 0,033 seconds (30 Hz) instead of 1kHz+ as it should with timer interrupt. Now I know why........ I really appreciate the video Mr. Bacon. Highly informative and needed!
Interesting topic! But, imho you shouldn't blame the Serial.print statements for slowing down your program when you are not doing the house keeping after debugging by removing those print commands.
Well, yes, it's always the responsibility of the developer to "tidy up" after coding is finished; I'm hoping this will make it so simple that they will do it. And learn something about pre-processing whilst they're at it!
Yes, however it can also be beneficial to have certain debug / trace logic kept in place so it's ready to be used when adding future enhancements, and testing at release level performance can be necessary while still developing and needing debug code. So for both scenarios, a way to knock out debug code while it's still present in the source is useful.
Uh but what if your code is timing sensitive? Doesn't necessarily matter about cleaning up after. Also, sometimes serial is used for communication and often s.p is used for that too
Deleting debugging statements after debugging is like throwing away the sponge after the dishes are washed. Complex programs are rarely, if ever, completed and bug-free. And most programs are susceptible to changes in their environment -- e.g., libraries, or 3rd-party connectivity policy changes. It's helpful to build in a way of retracing your steps without having to "re-do from start"
Genius idea! I'm pretty picky about my code but every once in a while I forget to take out one of these statements. This is a far better idea. I can even use multiple debug variables to turn different sections on and off as needed. Thank you, sir and my you have a great and prosperous day.
Thanks @Ralph for this video. It's one of many differences between a beginner person and more advanced /experienced person. Pre processor directives is amazing.
I have to say that this "simple" method of deactivating debug statements has to be a winner for all the reasons you highlighted. I will be using it big time. thank you for such a bright shining light on a very simple way round the problem. Even simpler than having all the debug Serial.print statements inside IF debug on THEN.
Indeed, I dislike wrapping all my Serial.print statements inside #if...#else...#endif as it destroys the layout of my code. I find this a neater solution!
@@RalphBacon the precompiler approach is realy nice to save size, where size is not an issue, comiled if-statements can be nice to enable/disable debuging on the fly, for troubleshooting without recompiling and loading new binary.
Very good message. I have been working with a Nordic chip and initially I was inserting debug messages. Later when I starting the power reduction phase I realized that activating the UART port increased the power consumption more than 10X. Still at some point you really need the serial printing but when moving to running on battery I disable the UART totally. I have not tested on a Arduino if the Serial.begin() is impacting on the power consumption.
Good info, Tom. And we can disable the UART in the Arduino world by using: #include power_usart0_disable(); which could possibly be part of some precompiler construct too (not done it, but it should work).
@@RalphBacon Even if you have never used a metric unit to measure something, it is still a standard. The same can be applied for programming techniques in C, C++ or other languages.
I wonder what "standard programming" you are referring to. IoT is a different beast and an entirely different ecosystem for the hobbyist. Not everybody knows this...like Ralph said...its not standard if they don't know about it or never read it, experienced it, and no tutorial or documentation I've read the past 3years mentions it. 😉 That being said, try and be considerate of hobbyists. Thank you
@@Invatator The information is important because the use of #ifdef in C and C++ do make it a lot easier to remove/include debug code. It was the time to get there i criticized because I think that many that could have need for this will fall of and watch something else before he gets to the point. Close to 30 minutes is long bit long. "and no tutorial or documentation". It is "funny" how often that can be a problem since the programmer that write it can so easily forget that even very basic techniques most be learned and #ifdef is something you learn early. It is not perfect and have problems, but it is a simple and easy way to control your debug code.
@@bknesheim that's what I mean, ...I don't think I have "proper basic" introduction to C or C++, and used it last time before Arduino like 10-15 years ago, and Arduino feels a bit different. Ifdef define and other "optimizations" don't seem properly introduced to Arduino hobbyists...just "how to get fast through the door". Anyhow...are those concepts you talked about in the C/C++ basics? I can dig out old books if any...as I'm really interested in making my microcontrollers performant and improve myself
#Define calls the pre-compiler and does a ‘find & replace’ before compiling the code. As a computer science student, a loooong time ago (1985), I had to write a C pre-compiler as an assignment. It wasn’t very difficult but I’ll never forget the use of pre-compilers.
Thanks for the information; a useful reference. I have got into the habit of using a DEBUG definition and then wrapping each of my debugging Serial.Print commands in an #if....#endif block, but this way you showed is a much more elegant method that I hadn't thought of before. I am by no means a beginner, but never ignore information like this aimed at beginners, as sometimes I find I've been set in my ways and they aren't necessarily the best!
I was ready to say exactly the same. This method avoids my adding all those #ifdef's. I always start my programs saying I'll only need 1 print so I don't bother with the #define debug. Finally I end up with many more prints and many more #ifdef's.
My thoughts exactly. While watching the start all I kept thinking was "well, just conditional compile them by wrapping them it #ifdef DEBUG's", but it never occured to me I could alias/macro it all in/out that way. And I mean, I'm not really a beginner either, and I've used both conditional compiling with #ifdef's and macros, but it just never occured to me I could shorthand all of this this way. This is one more confirmation to the idea that it's never wrong to re-review basics even if you are "an expert" - there's always a possibility you've missed or forgotten something useful.
You mean you can always uncomment out the lines again? But this is better than that, you have different levels of serial prints. But it your way works for you, and you're happy with it, keep using it!
Thank you so much Ralph, not just for this video but your channel in general. You've taught me so much, and you have a very pleasant way of teaching things. Keep up the good work :)
I consider myself a beginner and i have never considered this before. I was wondering how there can be almost 30 minutes of video on this topic. But im so glad i took the time to watch it. It was very eye opening to me and informative!
great tip! just wondering what happens to the semicolon when the debug statements are replaced with nothingness. i assume it stays, but empty lines are not compiled into the final program. or would it be useful to include the semicolon in the define statement?
Extra semicolons get ignored; you can include them as part of the define but that might upset the syntax on the rest of the line that you're using the define. Best to keep it as part of the code, the compiler will remove it anyway.
An elegant simple solution to a sometimes cumbersome headache. An enhancement would be to define debug levels. For example, when debug is set to 1 only "where I am" messages would be printed, whereas debug set to 2 would also print variable values, and so on. Quick question. What IDE are you using in this video? Thanks, Jack
My GitHub for this video has an advanced version which does just that. See video description for link! github.com/RalphBacon/224-Superior-Serial.print-statements
The initial statement was that Serial.print slows down the microprocessor (and it does) and that's why you will propose a better solution. In fact, the solution doesn't offer an alternative. I just shows a way to enable/disable all the Serial.print lines :( Not quite a solution!
Nice, but how to apply such method to Serial.printf(char* format, arg1, arg2....). This function can have more than one argument, so one (x) doesn't work. Serial.printf is convenient and is available in Arduino ESP32 SDK.
Just change the definition to something like this: #define debugLn(x, ...) Serial.printLn(x, ##__VA_ARGS__); which allows any (0 to unlimited) second parameters. I might have included this in the "advanced" folder examples in the GitHub for this project.
I have watched a number of you videos and have found that you are a very good instructor. Would you be able to explain the "(x, ##__VA_ARGS__);" part of this reply in a video sometime? Thanks for sharing your knowledge.
When I first saw this video being suggested to me, I thought this was an advisement against using Serial.print commands for passing information between microcontrollers and computers, so this was a bit of a surprise. On the other hand, I guess this is down to personal code style preferences, as I'd much, /much/ rather NOT use custom-defined preprocessor "functions", as that severely limits what you can pass into them. Given the fact that I've been using C and C++ for years, along with the preprocessor's very powerful replacement system, I'd rather use "#ifdef DEBUG" blocks around the code specifically handling debugging. Plus, this practice highlights your debugging code more strongly than yet another apparent function call that doesn't get coloured as such by the syntax highlighting.
It's a case of horses for courses, Daryl. Use whatever works for you. I personally hate the #if...#else...#endif construct for debugging statements, but if you don't mind the strange formatting this brings it's not a big deal. I just thought I'd share something I use myself. I think it's useful for hobbyists and beginners rather than professional C++ programmers, but that's just my take on things!
Nowadays, good compilers will eliminate dead code. Thus, I usually use simple VERBOSE adjective before the Serial.print/printf or blocks doing debug prints, which I defined before as "#define VERBOSE if(0)" or "if (1)". For the different levels of verbosity (e.g. -v -vv -vvv flags) can be done by "#define VERBOSE2 if(verbose_level >= 2)", etc. #ifdef #if look quite ugly, but I also use them in some cases, it's good alternative to have commented out section of old/different code.
Thanks for this great tip! I have a couple of further suggestions: 1. Use all caps when you define a macro so you can tell the difference between a function call and a macro because macros are expanded before compile, and sometimes this is important to know. 2. Surround your macro definition with '"{" and "}" so that multi-statement macros will be guaranteed to behave even if you don't know they're macros. As example, a multi-statement debug macro will compile fine and yet misbehave in code that looks like this: for(i = 0; i < 100; i++) debug("foo");
I had no idea you could #define a function in this way. That's amazing. I was using a bunch of if statements around the serial stuff, but this is much better!
That's what drove me to doing this, John. I was sick of all the #if...#else...#endif statements everywhere, destroying the layout of my beautiful (ahem!) code.
That's a good indication for reading through "The C Programming Language" or other reference material once a year or so, to remind you of things you may have forgotten or overlooked before.
@@JohnDlugosz There is a book by Henry F. Ledgard called Programming Proverbs (short title). It was one of our textbooks in school. It contains a lot of good advice. One of the best pieces is Re-read the Manual. The point is that a lot of what you encountered on previous readings may not have been well absorbed because it had no good relevance or contextual framework within your needs and experiences. As you progress, you encounter new situations which make the relevant and re-reading the manual will allow you to now see how things previously glossed over are now of use.
@@JohnDlugosz I remember that4 book. Now retired, during my career I used C occasionally and every time it kicked my but. POINTERS!!! What are they pointing to, addresses, variables, names of variables! Pull my hair out. ;-(
Yes, you could, but you don't really save much space (a few bytes at best). Not like the complete Serial.print statements now being sent into the ether! Additionally, you might want to actually still output an actual Serial.print statement (eg fatal errors) so this would still be needed.
Removing Serial.print() statements from your code is as simple as a Ctrl-F to find them to delete them. Doing so is the same as cleaning up after any job; doing the dishes, putting away tools, or using toilet paper. Cleaning up (or "linting") software code is what any good programmer does. It involves deleting commented-out lines, unnecessary blank lines, excessive comments, etc. Though your #define debug(x) and its associated conditional compiler directive will keep those lines from being compiled in the final version, the debug(x) lines will still be littering the code, impairing long-term readability (even for you after time passes), and adding lines (and associated scrolling) that have no value in the future. Let me just add that there's a click-baitiness to the video title: STOP using something everybody uses. It's a tactic similar to videos that say, "You're Doing {something} Wrong." The current video has been cluttering up my feed for a long time, and I've been ignoring it because it looked like click bait. But I finally thought, "Fine, I'll just see what he says." And I was right: click bait and nothing I think is very useful. You don't even actually advocate stopping the use of Serial.print() debugging. You just show a way of globally deactivating it when it's served its purpose. I understand, of course, that you got credit for my view anyway, and the algorithm is giving you credit for my "viewer interaction." You got what you want, and by complaining about it now so have I. You win, I win.
Not at all. Removing the Serial.print statement removes a level of documentation from your code that you might well need when (if?) it goes wrong. You put it there originally for a reason, right? Using the method described here keeps it in place but removes it, conditionally, from the compile process, thus putting away the dishes after washing up. 🤣 But allowing you to instantly (well, after a recompile) to reinstate them. After a few decades as a pro developer I've valued decent comments and print statement in code (not just mine). By the way, this was not click bait of any kind. I thought it described my video perfectly as I believe we should indeed STOP using standard Serial.print statements (in the ESP32 world I never use them, using the built-in conditional debugging levels instead). You didn't find it useful, fine. But don't tell me that I'm into sucking you in to watching a video you don't find value in. That's what the STOP 😁 or BACK button is for.
@@RalphBacon 🤣 100% agree. You even covered it in your video. "Crap, it's broken again - let me put them back in for a sec". This is real world programming. It's not the same as academic programming. Sometimes you have 1 day for a turnaround POC. DRY isn't the best way to do things. Obviously he's right once you move into a final production product that will never change again, but how often does that happen? We're always iterating
You should rationalize your debugging code and only leave in what is likely to help in the future (because now you know what helped), review what remains to make sure it has no side-effects that would break your code if it was removed, and then use C# preprocessor directives to control whether you include the debugging code in your executable. A more sophisticated version of the above is to define a set of debugging levels, again which you control by preprocessor directives. Finally, if it is possible in your environment and seems warranted, add some kind of logging, using the same levels strategy as for debugging.
So what does switching between "Debug" and "Release" do in visual studio C/C++ you think? Exactly what he demonstrated in his video... Your find and replace method doesn't work in many editors when you have multiple files. And you can add a header to your arduino library so you have these debug methods available in all your projects by simple one define and include. I have a book of C++ for beginners and the first chapter after the introduction is about the preprocessor. Only plebs use find & replace.
In my personal library I made a header that replaces serial.print with spr or sprl, and I can enter up to 4 different parameters. It's compact, very easy to type, and much more clear to read. Also, with one line of code or even an input wire, I can disable all serial prints at once to speed up the program
A slightly better, more correct way of defining your debug() macros to do nothing is #define debug(x) ((void)0) instead of really nothing. Please remember that these macros in the actual code are followed by semicolons. So now when they expand to nothing, you have lonely semicolons left in your code. This might sometimes lead to compilation errors and even logic errors. The macros expanding to ((void)0) avoid the empty semicolon problem.
Hmm, you may be right but empty semicolons are not a syntax error: while (1) { ; } is perfectly legal (and what my IDE insists on converting an empty while(1); to!
@@RalphBacon One example where the empty debug() macro will cause a compilation error is: a ? foo() : debug(...); This will become a ? foo() : ; and that won't compile. I understand that this is a contrived example, but you'd be surprised how "creative" people might be in using your stuff. (For example, people might create their own macros based on yours, which might use something like that.) My point is that it does not cost you anything to define debug() as ((void)0), which will prevent all (or at least most) such corner cases. I like your presentation. I just wanted to help...
Yes, your suggestion is a good one, and the, er, ingenuity 🤔 of developers cannot be underestimated 😜. So, to prevent potential problems your suggestion is good and I hope others read this comment thread in case they are having some weird compilation errors when using my alternate use of debug.
@@StateMachineCOM Thanks for pointing out a question I had. I tried your suggestion though and it does not compile. Just leaving the thing empty it does. Mind you, I am using Wokwi's emulator and not the real thing. Nevertheless I would like to thank ALL the people who make helpful videos like Ralph and those who make constructive comments and suggestions like Quantum.
Ralph i know you have being doing this a long time this one was easy To follow But can you make the code show better ie larger in the code box when showing programs on tablet it is to small and on to tv small Love the voice asking questions but for me to follow you i need above Showing for all your code parts the i can pause and read it Brian
OK, I'll bear that in mind, Alison. In the meantime you can download (and follow) the code from my GitHub whilst watching the video: github.com/RalphBacon/224-Superior-Serial.print-statements
#if DEBUG_FLAG == 1 #define debug(x) Serial.print(x) #define debugln(x) Serial.println(x) #else #define debug(x) #define debugln(x) #endif This code snippet adds a great value for this video thank you very much , can make your mic little clear so audio is perfectly audible
I risk being unable to see the obvious but the Simple and Advanced Example at github don't seem to demonstrate 'printf' and I also don't see how the printf library gets included into Platformio. Can you elaborate; I appreciate the issue with the sharp knife - been there, done that. Cheers.
Thumb is slowly healing, Thornton, thanks! The Simple and Advanced sketches do not use printf at all; the Advanced example shows a more granular approach to debugging levels (I'm using it right now in my new sketch). I'll be doing a complete video on printf very soon. In the meantime, you can include it in the *PlatformIO* environment by clicking on the Alien Head, choosing *Libraries* and entering *arduino-printf* in the search bar. The first entry in the resulting list (on my system, at least) was *LibPrintf by Embedded Artistry* which you can then click and *Add To Project* Note that ESP32 projects won't need this, ESP32 has printf already built in.
Click bait. Stop using Serial.print but still uses it as a #define. You are still using Serial.print and makin it more complicated then it needs to be.
Yes, that good to suppress the warnings. Or, _include_ the extra parameters as I do now. Here's an example of the 'Error' level warnings: #if DEBUGLEVEL > 0 #define debugE(x, ...) \ Serial.print(x, ##__VA_ARGS__); #define debuglnE(x, ...) \ Serial.println(x, ##__VA_ARGS__); #else #define debugE(__VA_ARGS__) // Nothing to see here #define debuglnE(__VA_ARGS__) // Or here #endif So now, debuglnE(aNumberGoesHere, HEX); will work.
Making a DebugIf(condition, x) is good too. This allows you to have other debug symbols, such as DEBUG_SERIAL, DEBUG_PWM etc. Having DEBUG=0 to switch all debug printing off is useful for a production build, but having all of the prints running when DEBUG=1 can be very tiresome to read, and obviously makes your code very much slower. This more fine-grained approach allows you to just show the debug prints that are relevant to the part you’re working on / testing.
define is good for debug, where the compiler handles it. But using it in cases like "LedPin" (Which I have seen many times like you said) uses more memory than a const if you use it more than once. Const occupies 1 place in memory, but if you put "13" all over the place it's going to use more.
Hmm. I'm pretty sure using a #define macro name instead of a const does not use more memory. The #define method just substitutes the value _before_ compilation, so: #define ledPin 13 digitalWrite(ledPin, HIGH); becomes digitalWrite(13,HIGH); Using literals here, is not memory hungry (it's a one byte integer). But it can depend on the compiler. Using a const: const uint8_t LEDPIN = 13; digitalWrite(LEDPIN,13); will require the LEDPIN value to be retrieved from memory by code unless the code has been optimises and the value substituted like a literal. The compiler will try to keep the const value out of SRAM (it knows it cannot be modified). Which one uses less memory is hard to determine.
@@RalphBacon When you're using multiple like I said: #define ledPin 13 printf("%d", ledPin); printf("%d", ledPin); printf("%d", ledPin); printf("%d", ledPin); Is: printf("%d", 13); printf("%d", 13); printf("%d", 13); printf("%d", 13); in this scenario you're assigning 4 different constants, all of them are 13 vs const int ledPin = 13; printf("%d", ledPin); printf("%d", ledPin); printf("%d", ledPin); printf("%d", ledPin); living in a single place of the program. I'm talking I've seen programs with hundreds of places that define rewrites at compile time. In situations like above program size will be bigger.. but if you're running those defined preprocessor consts all over the place especially running simultaneously it ends up being more memory, too (as well as having to be loaded into memory, and removed rather than just sitting there as a global constant for the program's entire running duration) That's all I meant though, not for like single use things doesn't really matter
#define DEBUG #ifdef DEBUG Serial.begin(9600); Serial.print(debug message); #endif if you comment out //#define DEBUG and then compile it, all your serial functions should be removed in the compiled bin, well as far as i understand it anyways. just using an if debug == 1 is not enough as your still doing that check. Also #ifdef is already part of the sdk and you wont need a lib for it.
I would also recommend such an approach👍I always define myself a function that wraps around the serial.print function. Inside of this wrapper function i will have the #if ... statement. With this you can keep your code a little cleaner, as you don't need precompiler statements in every print call, you just call the wrapper function☺️
So many comments about using "find & replace" while real developers use the method described in this video, it's even built into most C/C++ compilers (switching between debug & release). Even in C# debug functions don't output when you switch to release mode. People learn about macros and criticize because they think they know better, pretty arrogant. With precompile definitions you can leave out whole sections of code so you can include and remove functionality of your program too. (Eg, compile code for a display with code for certain sensors in or removed). You can switch between versions of components or just remove them from your code. It's very useful
That looks too simple to be true. Oh wait. Hang on... does that work? Have you tried it? I cannot believe I did not do this (or at least try it). A plausible explanation is that by leaving the Serial.begin alone, you can always use the standard Serial.print in the sketch and (perhaps more importantly) any included libraries will also be able to output errors. Otherwise it could all go very dark, so to speak.
@@RalphBacon I don't know if you are being sarcastic. But anyway, placing the serial begin also in your debug precompiler section removes even more bloat
@@RalphBacon Your video is certainly valid and good advice. To also put serial.begin in the DEBUG macros is just a small improvement. Of course, if one wants to use the Serial methods outside of debugging this would disable it. You still need to put Debug println and other methods also in the DEBUG macros, not only the serial.begin.
I have been using FOR AGES bool debug = true; if (debug) { Serial.print("my text here or whatever") } and thats it... Okay thank you. And I am sure a lot of people are actually doing it this way. Why do I think its better? Because you can fast an easy create differen Serial debugs for different tasks. like add another one that shouldnt be run with another. bool debug2 = true; if (debug2) { Serial.print("my text here or whatever") } And so on.. makes it even better and faster
Having granularity with debugging is a good idea; if you look at the Advanced example in my GitHub you will see a variant of this, where I have Verbose, Debugging, Warnings & Errors selectable as debugging level too. But now I'm thinking that having some descriptive debugging names like you suggest - good idea, thanks for sharing!
function debug(text){ if (debug){ serial.print(text); } } 😁 + Indentation //I wonder if there's a way to detect if the potential of the tx pin changes....... Could enable debug when something is plugged in.... 🤠
I usually just comment out the serial.begin statement in Setup when I'm finished with the code. Doesn't really help with code cleanup at compile, but it does get rid of the slow speed issue due to serial outputs.
Well, yes, I did, but this could also be removed if it is a problem. We can even switch off the UART completely if required: #include power_usart0_disable(); For battery powered projects it might be advantageous.
Compilers are tricky these days. Since that's the only statement left, and the library's not otherwise used, I wouldn't be surprised if it was optimized out of the compiled code. Would certainly warrant some experimentation.
Just 15 minutes, Jack? I must remember to pad it out a bit more! But seriously, setting the scene for beginners is essential. Glad you (eventually) liked it though!
The irony of you stopping watching videos that tell you to stop doing something is not lost on me! Anyway, you missed a good video, and have perpetuated the use of Serial.print in your sketches. Deffo worth watching!
I thought you were going to suggest toggling GPIO pins to indicate execution state. It's fast and if you have a multi-channel scope and only a few events to monitor it's good for getting an indication of when things are happening. Anyway, it's something that I do when printing to serial takes too much time or would result in excessive output.
You can also control the Serial.print statements if a particular (spare) GPIO is low. Connect a jumper between the pin and GND to turn Serial.print statements on, remove it when done for faster execution. The macro in this instance would interrogate the state of the (input) pin, ideal for those on the ESP32 that are INPUT only.
Not really. Beginners need a detailed, step-by-step approach and an explanation of _why_ we do things - no sudden jumping to conclusions or they get lost and switch off. Yes, I use CAPS for constants but I'm not always a fan for #define names.
I hear you but a warning is just that: a warning. Not an error. I try not to get hung up over things that are known (not just ignored) and accepted. But I'm sure you could tweak the code (or the #define) to remove the warning if it really annoys you. And there is the "Report by Exception" rule, where all warnings and errors MUST be removed before sending anything live, otherwise we could have a dozen warnings!
@@RalphBacon totally agree that a warning i not to worry about, when you know what it is about. Main reason (for me) to clear it, would be to "clear the sheets", to make it more noticable whenever there is new warning that I have not looked at yet, as a warning can be caused by compilable bugs. I also thought about possibly bitmask logic, to only enable partial debuging: 0: no debug, 1: debug input, 2: debug output, 3: debug input and otput.. etc Then you can enable debuginfo for the parts as you need. But disable parts you don't need at the time. To avoid wasting resources on debug all, and to save space, and to avoid bloading the debug console. But if size is not a problem, debug enable/disable by input is nice too.
I'm glad you said "it's a great concept for beginners". Imagine yourself being taught to drive a military tank. (I hope you don't have military service, especially in a tank 😲). Do you think 3 minutes of instruction is enough to even get you to move the tank forward (without destroying everything in its path)? Put yourself in the shoes of a noob who needs every single thing explained in a clear and concise way. At least once. Ideally more than once. That's why it's as long as it is. 😜
Very nice Ralph. I've watched this a couple of times, and picked up things each time, as I'm new to this stuff. I really like the way you explain things. Effective and entertaining as well. Personal thought: When you change the code during the video, you use a Screen Transition. It sometimes causes me to get lost and I have to "find my place" again on the screen of code. I know you do this to save time and not show backspacing the code and rewriting it, but since you are explaining it anyway, I think I would rather follow along as you do so. Just my personal thoughts. Thanks. Regards
@@RalphBacon Indeed. As a retired software engineer having worked in customised compilers and language interpreters for several companies, I’m currently an occasional contributor of the LLVM Compiler project, so I can confidently assert that a modern compiler is able to make certain optimisations and code transformations that are even beyond what an average programmer would be able to implement in assembly language
As soon as it goes to some more than an AVR, I use "#define DEBUG(...) printf(__VA_ARGS__), but in more complex systems I prefer to work with tokens added it automatically so I can see the part of the software, the warning is coming from. But for that you have to write an va_args warpper that then connects two printf resulting in something like [ENC] Stream picked up [MOD] setupt tx frequenxy [LO] Frequency set [MOD] Gain set [RF] TX On [ENC] Stream started A bit like the ESP_LOG mechnaism but not that bloated in the output. And I also often have 2 Levels of debug. One that helps you to design the thing and one that stays on forever and gives you a clue in cases your thing doesn't work as expected. But other than that I prefer to work on STM32 and just use JTAG online debugging diretly into the thing, and keep printfs as rare as possible.
Yes, that sounds like a good method. I sometimes use a spare GPIO pin that only outputs the Debug message when it is is LOW. It doesn't help with program bloat but does keep things moving at speed when you don't need the serial output.
ESP32's log_x functions are defined inside the ESP32 Arduino core (esp32-hal-log.h and esp32-hal-uart.c). You do not need any additional library and maybe you should have mentioned that ArduinoIDE has a menu item to control that log level. Define is needed only for PIO
What is amazing to me is that it has taken you all this time to figure this out. ALWAYS remove debugging code from release versions. I use a #define and #if statements to turn on/off serial.print and other debugging code.
Not that I know of, but that's because I am rubbish at microPython. If there _is_ a way you can be sure someone has found it. Bear in mind though that Micropyton is _interpreted_ and so I guess it's going to be a lot slower than compiled C++ anyway.
I've watched so many videos with demos that show the serial output on a separate window, but I can never find anything showing how it is done. My output always shows at the bottom of the IDE 2.0 sketch. Will you please explain how to set this up?
That's because you are using the in-built Serial Monitor of the Arduino IDE. I'm using a standalone program called CoolTerm which is nice, and it's free too, although Roger is grateful for any donation if you find that you use it a lot: freeware.the-meiers.org
Great Idea! Until now I used a function for that and commented the serial.print out after debugging so the text was not sent. Your way the text dissapears completely so it is much better.
This is great! my rookie approach was always to wrap all the serial.prints in an if statement with a global true/false var. This is a much better solution as those operations get removed completely from the compiled code.
I must admit a dislike to creating a buffer of unknown length to receive a message that we then output. I think printf works better and the ESP32 has it built in and now the Arduino can have it for very little cost (code size-wise, I mean).
Hi Ralph. I do something similar. I have a function debugPrint() which checks whether isDebug() is true (which is an analog read if it's brought low it's true, on A0, usually in my code) and then I have an #ifdef _DEBUG_ENABLED_ #define debugPin A0 #define debugLED 13 #endif then you just need to define _DEBUG_ENABLED otherwise it doesn't compile in any debug support. It's useful if you're developing on a huge processor (like ATMega2560) and then wanna compile it down to 328 (for example). I think adding a counter to the debug function might be useful, but haven't thought about it, since haven't had much time without brain fog to work on Arduino projects.
I think experienced programmers, like you Jess, will probably all be using some variation of this, tailored to the way you like it. But beginners will never have used any precompiler constructs so I'm hoping this introduces them to the concept as well as allowing them to make their programs look more streamlined without #if...#else...#endif statements everywhere! Good to hear from and hope you're enjoying the new pad.
Awesome simple solution 👍🏻👍🏻 For the remaining unused 'b' variable couldn't you do a define for that as well so it'll get toggled on/off like the rest?
how about this: #define verbose true if (verbose) {Serial.print.....} then, once you are done with debugging and you go production you change to false and nothing is printed except that we have lots of "ifs"
This is not used in production, and the simple macro method is only used. Here you are on the mercy of the compiler optimization to remove the dead unreachable code, whereas in the macro method, you are controlling when it will be part of the code or not.
if you would define the debug functions as regular C functions, and have a boolean that can turn them off (make the functions do nothing), shouldn't the compiler detect that all that and actually optimize the code such that the debug functions are not even encoded to any processor instructions.
That's exactly how Espressif do it on the ESP32. They have various logging levels that are not even compiled if the Core Debug Level is not set to certain messages. This here, is a simpler version of that.
Thank you for your beautiful tutorials What is the difference between debugD and debugV? In both definitions, debugE, debugW, and debugD are executed. Was there a specific reason you defined debugV? And why is the debugNothing function used in debugV and not used in the rest of the functions? Is there a specific reason? Thank you for your reply
The "V" stands for Verbose and is used for those really low level messages that otherwise would be really annoying and flood the output. "D" is for more general Debugging and E/W/I you know about. The level you set (eg "I") ensures that all message at that level AND HIGHER are output (so W and E too). debugNothing should exist for all levels if they are not defined.
Thank you for the very comprehensive tutorial. Excellent tutorial for beginner programmers, but it could be cut down to a 5 minute video for intermediate to experienced programs.
Glad it was helpful! And, yes, for those beyond beginner stage it doubtless could be done more succinctly but! This channel is about getting beginners' projects off the ground so more detail is required. Incidentally, if I watch a video that is going too slowly for me I just increase the playback speed to 1.25x or even 1.5x and it gets things moving along rather nicely!
@@RalphBacon I subscribed to your channel as you lend a very comfortable and comprehensive means of educating about Arduino. Thank you, again. Best regards
another tip, we could have differently debug mode such as 2/3/4/5...etc, so we can preset a bunch of variable in Preprocessor , so we test our code with predetermined inputs as the alpha test. by combined using #define #ifdef to activate and deactivied piece of code.
I've got a more granular version in my GitHub that allows Errors, Warnings, General, Verbose settings. But another viewer has suggested different names for the debug variable so we can debug just one function, for example. It's something I'm thinking about.
Hey there Ralph. Another great video; informative and entertaining. I'm going to add this to every sketch I develop from now on. It is just to simple and easy not to do.
Fantastic! I'm using it right now in my current sketch, Adrian, and it works very well! It can be tweaked as required too, or the Advanced version can be used (in my GitHub).
Thank you Ralph, this is so useful and I was very impressed how it worked and saved space for me. Silly question, is there a way of making the debug trick available to all Arduino code? So, that when I open a new sketch the whole debug solution is already available please? Thank you.
The _easiest_ way of doing this is to keep all the DEBUG stuff in a separate file (I'd suggest of type hpp, which is a C++header with code too). Place it in your libraries folder (usually a sub-folder in your sketches folder). Then, in each sketch just include a simple #include "MySpecialDebugStuff.hpp" (or whatever you called it, keep it easy to identify but not as verbose as this!) at the top of your sketch and it will be included in the sketch. Glad you found it useful!
@@RalphBacon Hi Ralph - I have tried it as you described but running the Arduino verify reports "Connection failure". Would you mind including an example hpp file containing the DEBUG stuff please? I am sure I am probably doing something silly! Thank you.
I believe you know your add 1 never changed the value of counter! I do have an ESP32, I may go get the log lib. Will it have issues if I add the printf for the arduino or do you know? Lee
If you have an ESP32 then you have a separate way of displaying debug information, by using log_x where "x" is e,w,i,d,v for errors, warnings, info, debug and verbose output respectively. It uses the "printf" functionality behind the scenes. Just compile the sketch with the build options (menu Tools...) set to VERBOSE (5) thru NONE (0) or something in between for this to work. Very simple, very useful. Using the printf functionality with an Arduino just uses memory so keep the messages short!
For the unused b variable issue, couldn't you create something like `#define debugCode(x) x` and then encapsulate the variable definition and counting lines with b within debugCode()?
A word of warning: If you define a symbol to be an expression, put parentheses around the expression to make sure that they evaluate the way that you wish. Example: #define ratio( x, y ) x / y if you use ratio in the following, you will not get what you intended float a = 5.0; float b = 10.0; float c = ratio( a, b ) ** 2; The expected result would be 0.25 (= 0.5 **2 ). UNFORTUNATELY, the compiler was presented with x / y ** 2 The operator precedence of C/C++ will perform the exponentiation first and the the division. This means that c will be 0.05 ( 5 / 100 ) If instead, you change the define to be #define ratio( x, y ) ( x / y ) then the expression seen by the compiler is ( a / b ) ** 2 which is what you wanted.
I've never done a video like that because installing VSC plus PlatformIO (plus all the bits that you inevitably need) would be a looong video. That said, I think it might be more streamlined now so I'll take a look and see whether it's worth doing!
👍👍👍👍👍 Thank you, thank you,, I had just come to the realisation of why my code and memory usage was changing and realised I needed to take out all the boating "print" statements. But now you have given me a way to manage debugging in a nice convenient way, so thank you for your video and your help. ...!! Very much appreciate learning how to write better code. ...!
Sorry to hear about your argument with the knife. I had another thought about using this. Would it be possible to turn this on or off with a MQTT message or some other means of remote control?
We would not be able to control the _compilation_ process, so the Serial statements would be present; but the _execution_ of those statements could certainly be controlled by pretty much anything that the microcontroller can receive (it's just a flag, after all).
I used to save the overhead (time and memory) by debugging MCU projects code using different colour LEDs on GPIO pins as a crude form of debug. It does use IO but it can be fast and compact. The LED colour would signify different parts of code or deferent meanings (semaphores).
Indeed. Your method is not that different to the way I proposed (and subsequently used) in videos #251 and #252 - a simple two digit display hard wired into your project to show the [error] status.
Yes, this is also the way to go for things which you would like to remain in final build. But whenever you will decide to go back to code to apply some change or bugfix you can realize it would be nice to have a cheap method which would bring back all logging as fast as possible. And this is why all logging statements should remain in code. They just have to be configurable (switch on and off, specify which should be the minimal lever for the statement to be preserved by compiler, ...).
The highest speed I use (usually with ESP32s), Vasily, is 115200; I will try higher speeds and see how it copes. The Arduino, with its 16MHz processor speed, is surely limited to 115200 at best? I tend to use 9600 on the Arduino to make it super safe and no risk of corruption. I guess I should increase that too! I can remember when 300 baud was the maximum I could get from some chips!
@@RalphBacon 921600 is one of standard values for UART. The FT232 USB-UART chip has 3Mbaud maximum data rate, same is for many clones and alternatives. As for microcontrollers, they usually have hardware implementation of UART, cpu clock is not that important.
@@RalphBacon Also, how faster one can go over UART for different boards can be a good investigation for your future video. BTW, for esp8266 and esp32 I used 921600 with no problems. 115200 is very safe for atmega328p (depending on USB-UART IC).
Hmm. If I clock an Arduino at 8MHz then my maximum sketch upload speed is only 19200, otherwise it might fail. On the other hand, an ESP32 can upload at 921600 no problem at all! There must be a correlation between processor speed and clock speed. Unless this is only true for storing program code into Flash (which is slower, we know).
@@RalphBacon Well, this is true. The correlation is in how good and functional is the clock divider or PLL in MCU. That can be proved only experimentally or computed from datasheet.
Thanks you so much for this! My code was working fine, but it always bugged me that the serial was constantly printing to nowhere and taking up processing time. Now I have a solution! and...Now I have to go back and update ALL my sketches!
Valuable reminder of good practice but worth stressing that any testing needs to be completed on the debug-stripped version of the code ... can’t count the number of times I’ve been chasing bugs generated by accidentally non-included or over-defined values not apparent until the effects of the def changes played out. Keep up the great work 😀👍
Sorry, Chris, what is this word "testing"? I've looked up the definition in the Arduino Coder's Bible and it states: "Testing, v, The iterative act of releasing code into the Real World and seeing what happens, mostly using the happy path". Sounds about right.
Hi Ralph good stuff again well done\\. I #define debug as true or false, and I use "if(debug)Serial.print(x); I takes a little longer to write but then toggle the define to false on the final compile. I tend to leave setup print statements as only run once and give an indication the thing is working.
But once you are "done done" with the code, I'm assuming you are no longer monitoring the serial output? In which case an LED or Buzzer is a better indicator that things are running OK, I find.
Nice explanation, a bit long-winded ;-) , but i'm running in trouble on places where i've used something like Serial.print(foo, BIN); of course an extra define debug_with_parameter(x,y) serial.print(x,y) can be a solution .... But not so elegant.
Well, Jan, you had to stumble across the bit I did NOT want to do in the demo as most would have switched off! However... If you change the definition of each debug(x) and debugln(x) to: debugln(x, ...) // at least one parameter, maybe more then change the underlying Serial.print to: Serial.println(x, ##__VA_ARGS__) you can use debugln("this is a string") debugln(42); debugln(42, HEX); Works for me! But on your own head be it! Let me know how you get on.
Fantastic, thanks Ralph... wish I'd known about this a few years ago....!!! An ask, if you read this... would love to have a similar video on how to setup serial logging to a "Logserver", not had any joy getting this working.... I have scattered ESP devices around the house and I'd like them to write diagnostic data to a central point for me to keep an eye on.....
Interesting that I already have some components on order to enable remote logging for at least two projects - not necessarily "centralised" (you would have to cater for data collisions) but we will see how far it gets and you can then decide whether it can be expanded. Small world!
That would be CoolTerm, an independent serial port monitor. Free to use, although if you find that useful the author, Roger, appreciates a contribution. You can have several instances running at once, which I do, as I'm monitoring several ESP32 projects at once: freeware.the-meiers.org/
Thanks for an excellent video. This is a more elegant solution than I have been using. I did encounter a problem with the following program statement: debug("Error[%u]: ", error); This caused an error: macro "debug" passed 2 arguments, but takes just 1 My #define debug(x) Serial.print(x) looks like yours, is there a way to pass either 1 or 2 arguments?
I just noticed that the original statement was Serial.printf("Error[%u]: ", error); (not Serial.print) So I understand I need a new #define statement to cover this, still don't know how to handle multiple arguments?
Well, #define can have more than one argument eg min(a, b) but whether we can use ... or __VA_ARGS__ to represent multiple (and unknown) parameters in the #definition is something I never tried (yet).
@@RalphBacon Ralph it definitely can be done with variadic macros. #define DEBUG_STATE true #if DEBUG_STATE #define BUFF 50 #define Serialprint(...) Serial.print(__VA_ARGS__) #define Serialprintln(...) Serial.println(__VA_ARGS__) #define Serialprintf(...) Serial.printf(__VA_ARGS__) #define Serialbegin(baud) Serial.begin(baud) #define aSerialprintf(...) {char buff[BUFF];\ snprintf(buff, BUFF, __VA_ARGS__);\ Serial.print(buff);} #else #define Serialprint(...) #define Serialprintln(...) #define Serialprintf(...) #define Serialbegin(baud) #define aSerialprintf(...) #endif Just replace Serial.print with Serialprint in the sketch code to invoke the macro definitions. Of course Serial.print will still work for non-debug messages. That is really only replacing debug with Serialprint from your code but I find that more intuitive. The aSerialprintf is an alternative definition for Serialprintf and is only needed if Serial.printf is not available (ie Arduino boards) in your setup (via a library or built in for ESP's) otherwise leave it out. The printf buffer size is hard coded to 50 characters. The BUFF definition is also not needed if aSerialprintf is not implemented.
Looks good, Phil. I never considered multiple parameters in Serial.print statements but, of course, you _can_ use them so people will. Excellent stuff, thanks for sharing.
I'm working at a project that has an lcd display, I use that for debugging! I guess I would use serial print too but a few lines where it matters don't think they would slow much..plus i leave them as commentals when not needed
When doing PIC work I used a purpose built 16x2 LCD for debugging! But I don't use PIC chips any more. In a small sketch I don't suppose it matters too much, as you say.
@Ralph S Bacon How would one use the optional second parameter which specifies the base (format) to use; i.e. Serial.print(0x61, HEX); ? At the moment I will receive an error requiring 2 arguments or 1 argument. To ask another way, How can I specify #define debug(x,y) Serial.print(x,y) but allow Y to be optional?
I did update my examples to include further arguments (you were not the first to ask this). Basically, use the ellipsis (...) as the second argument in the definition, which means "an unknown number of parameters", from none to infinity. That way we can parse any further parameters and pass them onto the Serial.print statement.
Thank you Ralph for the video, like many others I hadn't consider using a #define statement to enable/disable debugging. I'm keen to understand more about the ESP32 Log_v and how it works with the Arduino IDE (2.x) as I haven't been able to get verbose logging working in the Arduino IDE as there isn't a menu option for verbose under the 'core debug level' menu, the highest level is debug. I could simply just use Log_d, but I'd prefer to use verbose to provide the additional level. I haven't tested this, but does changing the logging level reduce the resource (flash/ram) consumption when using the Log_x command as demonstrated with the #define statement with the serial.print(), I presume it would. If it's not possible I might have to migrate my project to PIO :)
Yes. What, you want more? OK. On the Arduino IDE, the verbose option is level 5. The new, improved Arduino IDE 2.0.x makes this a bit clearer. (0 is no debug😲, 1 is [fatal] errors only, 2 is warnings, 3 is info, 4 is debug and 5 is verbose). On the ESP32, if you select higher than _verbose_ then the log_v statements simply don't get compiled into the program. And the same happens with log_d, log_i and so forth. So, yes, you save space (and execution time) but making sure your _final_ code is compiled with the lowest level of debugging you can live with. This works whether you use the #define I show here, or not (ie using Espressif's _log_v_ etc).
Arghhhhhhh why have you got the brackets on a separate line from the else and if statements 😂 I'm not trying to do vim vs emacs (I use nano), just curious why you choose that
I use Visual Studio Code with predefined clang formats so my code is consistent. I'm guessing that I do it this way after being "encouraged" by my employers over the years to use the "company standard". If you don't like my way, use another way but keep it consistent!
debugln ((unsigned long) addOne (counter)); // eliminates the warning. Note that 'debug' and 'release' profiles define (or undef) 'DEBUG' on the command line.
I like it! I think the Arduino IDE always compiles in release mode, doesn't it? In my PlatformIO setup I usually have more than one profile depending on what I'm doing with the code.
As I decided to have an argument with a very sharp craft knife, my typing has slowed down considerably. It may take longer to respond to your comments. Sigh.
github.com/RalphBacon/224-Superior-Serial.print-statements/blob/main/Thumb%20Argument%20With%20Craft%20Knife.jpg
It would seem that you lost that argument. 🤦♂️
hey at least you can't miss the space bar anymore
Yeah, space bar, b, m, n, comma all in one go!
Ouch! Sorry to hear that. Hopefully it wasn't too bad and didn't hit anything important.
My thumb _is_ important. Well, to me. And my soldering iron.
STOP using "STOP this and that" clickbaits!!!
So WHY is this click bait? EXPLAIN!
Sadly RUclips requires every title to have a little bit of clickbait into it or your video won't be recommended to people. When I saw the title I was intrigued but never did I think I could never ever use Serial.print. It was on topic. So clickbait percentage was low, usefulness high for a lot of people. @RalpBacon: Thank you for the title or I would have never seen this. And thank you for the video. My 13 teen your old daughter loved it as well (we are learning about the Arduino environment together).
It’s entirely truthful, and it makes more people click on the video completely for free. Why on earth would he stop?
@@RalphBacon 100% it's a clickbait video title.... But as a newbie programmer this kind of information is incredibly useful, and I wouldn't have found it without your clickbait.
I think this video is a great use case for clickbait. Experienced Devs will know what you're going to do within a minute or two, and everyone else can stay for the explanation and demos.
@@BeardyMikeI totally agree. Took the bait and enjoyed every bite. I've never seen #define used in Serial.print statements before and found this video extremely useful and informative.
Just as a side note, you can also use #define debugbegin(x) Serial.begin(x) to remove the serial begin statement as well. Initializing serial takes up memory as well. Thanks for the great video! 👍👍👍
I am a mech engineering student and I am doing a 99% electrical project for my research dissertation. I spent weeks trying to figure out why my sketch has been printing data every 0,033 seconds (30 Hz) instead of 1kHz+ as it should with timer interrupt. Now I know why........
I really appreciate the video Mr. Bacon. Highly informative and needed!
Glad it helped you!
Interesting topic! But, imho you shouldn't blame the Serial.print statements for slowing down your program when you are not doing the house keeping after debugging by removing those print commands.
Well, yes, it's always the responsibility of the developer to "tidy up" after coding is finished; I'm hoping this will make it so simple that they will do it. And learn something about pre-processing whilst they're at it!
Yes, however it can also be beneficial to have certain debug / trace logic kept in place so it's ready to be used when adding future enhancements, and testing at release level performance can be necessary while still developing and needing debug code. So for both scenarios, a way to knock out debug code while it's still present in the source is useful.
Uh but what if your code is timing sensitive? Doesn't necessarily matter about cleaning up after. Also, sometimes serial is used for communication and often s.p is used for that too
@@forbiddenera the topic was serl.print for debugging, not project messages. Those time need to be considered in a project.
Deleting debugging statements after debugging is like throwing away the sponge after the dishes are washed. Complex programs are rarely, if ever, completed and bug-free. And most programs are susceptible to changes in their environment -- e.g., libraries, or 3rd-party connectivity policy changes. It's helpful to build in a way of retracing your steps without having to "re-do from start"
Hi Ralph,
Just want to say that I enjoy watching your videos. The content is interesting and your presentation style is entertaining.
Thank you!
Thank you very much! Glad you're enjoying them.
Genius idea! I'm pretty picky about my code but every once in a while I forget to take out one of these statements. This is a far better idea. I can even use multiple debug variables to turn different sections on and off as needed. Thank you, sir and my you have a great and prosperous day.
Glad it was helpful!
You can do a proper inline debugging with a JTAG programmer on the ESP32
Yes you can, and I covered this in video #261. Works well enough.
@@RalphBaconGreat
Thanks @Ralph for this video. It's one of many differences between a beginner person and more advanced /experienced person. Pre processor directives is amazing.
Glad it was helpful!
I have to say that this "simple" method of deactivating debug statements has to be a winner for all the reasons you highlighted. I will be using it big time. thank you for such a bright shining light on a very simple way round the problem. Even simpler than having all the debug Serial.print statements inside IF debug on THEN.
one reason for IF debug on THEN, would be to be possible to enable/disable by input.
you would save time, but not size.
0
Indeed, I dislike wrapping all my Serial.print statements inside #if...#else...#endif as it destroys the layout of my code. I find this a neater solution!
@@RalphBacon the precompiler approach is realy nice to save size, where size is not an issue, comiled if-statements can be nice to enable/disable debuging on the fly, for troubleshooting without recompiling and loading new binary.
Very good message. I have been working with a Nordic chip and initially I was inserting debug messages. Later when I starting the power reduction phase I realized that activating the UART port increased the power consumption more than 10X. Still at some point you really need the serial printing but when moving to running on battery I disable the UART totally. I have not tested on a Arduino if the Serial.begin() is impacting on the power consumption.
Good info, Tom. And we can disable the UART in the Arduino world by using:
#include
power_usart0_disable();
which could possibly be part of some precompiler construct too (not done it, but it should work).
This was very long winded about a standard programming method.
It's not "standard" if you've never done it before, is it?
@@RalphBacon Even if you have never used a metric unit to measure something, it is still a standard. The same can be applied for programming techniques in C, C++ or other languages.
I wonder what "standard programming" you are referring to. IoT is a different beast and an entirely different ecosystem for the hobbyist.
Not everybody knows this...like Ralph said...its not standard if they don't know about it or never read it, experienced it, and no tutorial or documentation I've read the past 3years mentions it. 😉
That being said, try and be considerate of hobbyists.
Thank you
@@Invatator The information is important because the use of #ifdef in C and C++ do make it a lot easier to remove/include debug code. It was the time to get there i criticized because I think that many that could have need for this will fall of and watch something else before he gets to the point. Close to 30 minutes is long bit long.
"and no tutorial or documentation".
It is "funny" how often that can be a problem since the programmer that write it can so easily forget that even very basic techniques most be learned and #ifdef is something you learn early. It is not perfect and have problems, but it is a simple and easy way to control your debug code.
@@bknesheim that's what I mean, ...I don't think I have "proper basic" introduction to C or C++, and used it last time before Arduino like 10-15 years ago, and Arduino feels a bit different.
Ifdef define and other "optimizations" don't seem properly introduced to Arduino hobbyists...just "how to get fast through the door".
Anyhow...are those concepts you talked about in the C/C++ basics? I can dig out old books if any...as I'm really interested in making my microcontrollers performant and improve myself
#Define calls the pre-compiler and does a ‘find & replace’ before compiling the code.
As a computer science student, a loooong time ago (1985), I had to write a C pre-compiler as an assignment. It wasn’t very difficult but I’ll never forget the use of pre-compilers.
Quite so. The modern IDE (not Arduino classic) does have some intelligence regarding precompiler directives which makes it even more useful.
Thanks for the information; a useful reference. I have got into the habit of using a DEBUG definition and then wrapping each of my debugging Serial.Print commands in an #if....#endif block, but this way you showed is a much more elegant method that I hadn't thought of before. I am by no means a beginner, but never ignore information like this aimed at beginners, as sometimes I find I've been set in my ways and they aren't necessarily the best!
I was ready to say exactly the same. This method avoids my adding all those #ifdef's. I always start my programs saying I'll only need 1 print so I don't bother with the #define debug. Finally I end up with many more prints and many more #ifdef's.
I'm sure everyone experiences the same as you, Bill! Me included. I mean, the next sketch will be so simple what could possibly go wrong? Oh.
@@RalphBacone
yes!!!
My thoughts exactly. While watching the start all I kept thinking was "well, just conditional compile them by wrapping them it #ifdef DEBUG's", but it never occured to me I could alias/macro it all in/out that way. And I mean, I'm not really a beginner either, and I've used both conditional compiling with #ifdef's and macros, but it just never occured to me I could shorthand all of this this way.
This is one more confirmation to the idea that it's never wrong to re-review basics even if you are "an expert" - there's always a possibility you've missed or forgotten something useful.
I always comment serial.print after im done, compiling removes commented bytes and you can always go back to it whenever something goes wrong
You mean you can always uncomment out the lines again? But this is better than that, you have different levels of serial prints. But it your way works for you, and you're happy with it, keep using it!
Thank you so much Ralph, not just for this video but your channel in general. You've taught me so much, and you have a very pleasant way of teaching things. Keep up the good work :)
I appreciate that! Sharing is caring, as they say!
I consider myself a beginner and i have never considered this before. I was wondering how there can be almost 30 minutes of video on this topic. But im so glad i took the time to watch it. It was very eye opening to me and informative!
Glad it was helpful!
@@RalphBacon most definitely! Thanks so much for taking the time to put together this video!!! Merry Christmas!
great tip! just wondering what happens to the semicolon when the debug statements are replaced with nothingness. i assume it stays, but empty lines are not compiled into the final program. or would it be useful to include the semicolon in the define statement?
Extra semicolons get ignored; you can include them as part of the define but that might upset the syntax on the rest of the line that you're using the define. Best to keep it as part of the code, the compiler will remove it anyway.
@@RalphBacon great, thank you for the clarification!
An elegant simple solution to a sometimes cumbersome headache. An enhancement would be to define debug levels. For example, when debug is set to 1 only "where I am" messages would be printed, whereas debug set to 2 would also print variable values, and so on.
Quick question. What IDE are you using in this video?
Thanks,
Jack
Visual studio with platformio, he mentioned it in the video. Highly recommend.
@@MadScienceWorkshoppe Thank you. I guess I didn't catch that when he said it.
My GitHub for this video has an advanced version which does just that. See video description for link!
github.com/RalphBacon/224-Superior-Serial.print-statements
The initial statement was that Serial.print slows down the microprocessor (and it does) and that's why you will propose a better solution. In fact, the solution doesn't offer an alternative. I just shows a way to enable/disable all the Serial.print lines :( Not quite a solution!
yeah, no real solution and too much ads at the beginning...
I believe it shows a _better_ (and more efficient way) of tailoring your Serial output.
@@RalphBacon How would one use the optional second parameter which specifies the base (format) to use; i.e. Serial.print(0x61, HEX); ?
Nice, but how to apply such method to Serial.printf(char* format, arg1, arg2....). This function can have more than one argument, so one (x) doesn't work. Serial.printf is convenient and is available in Arduino ESP32 SDK.
Just change the definition to something like this:
#define debugLn(x, ...) Serial.printLn(x, ##__VA_ARGS__);
which allows any (0 to unlimited) second parameters. I might have included this in the "advanced" folder examples in the GitHub for this project.
I have watched a number of you videos and have found that you are a very good instructor.
Would you be able to explain the "(x, ##__VA_ARGS__);" part of this reply in a video sometime?
Thanks for sharing your knowledge.
When I first saw this video being suggested to me, I thought this was an advisement against using Serial.print commands for passing information between microcontrollers and computers, so this was a bit of a surprise.
On the other hand, I guess this is down to personal code style preferences, as I'd much, /much/ rather NOT use custom-defined preprocessor "functions", as that severely limits what you can pass into them.
Given the fact that I've been using C and C++ for years, along with the preprocessor's very powerful replacement system, I'd rather use "#ifdef DEBUG" blocks around the code specifically handling debugging. Plus, this practice highlights your debugging code more strongly than yet another apparent function call that doesn't get coloured as such by the syntax highlighting.
It's a case of horses for courses, Daryl.
Use whatever works for you. I personally hate the #if...#else...#endif construct for debugging statements, but if you don't mind the strange formatting this brings it's not a big deal. I just thought I'd share something I use myself.
I think it's useful for hobbyists and beginners rather than professional C++ programmers, but that's just my take on things!
Nowadays, good compilers will eliminate dead code.
Thus, I usually use simple VERBOSE adjective before the Serial.print/printf or blocks doing debug prints, which I defined before as "#define VERBOSE if(0)" or "if (1)".
For the different levels of verbosity (e.g. -v -vv -vvv flags) can be done by "#define VERBOSE2 if(verbose_level >= 2)", etc.
#ifdef #if look quite ugly, but I also use them in some cases, it's good alternative to have commented out section of old/different code.
@@vasiliynkudryavtsev i love that, im gonna use that from now on for debugging
@@alexstone691 I'm glad, my experience helps.
Thanks for this great tip! I have a couple of further suggestions:
1. Use all caps when you define a macro so you can tell the difference between a function call and a macro because macros are expanded before compile, and sometimes this is important to know.
2. Surround your macro definition with '"{" and "}" so that multi-statement macros will be guaranteed to behave even if you don't know they're macros. As example, a multi-statement debug macro will compile fine and yet misbehave in code that looks like this:
for(i = 0; i < 100; i++)
debug("foo");
Good suggestions, Jeff, thanks for sharing. 👍
I had no idea you could #define a function in this way. That's amazing. I was using a bunch of if statements around the serial stuff, but this is much better!
That's what drove me to doing this, John. I was sick of all the #if...#else...#endif statements everywhere, destroying the layout of my beautiful (ahem!) code.
That's a good indication for reading through "The C Programming Language" or other reference material once a year or so, to remind you of things you may have forgotten or overlooked before.
@@JohnDlugosz There is a book by Henry F. Ledgard called Programming Proverbs (short title). It was one of our textbooks in school. It contains a lot of good advice. One of the best pieces is Re-read the Manual. The point is that a lot of what you encountered on previous readings may not have been well absorbed because it had no good relevance or contextual framework within your needs and experiences. As you progress, you encounter new situations which make the relevant and re-reading the manual will allow you to now see how things previously glossed over are now of use.
@@JohnDlugosz I remember that4 book. Now retired, during my career I used C occasionally and every time it kicked my but. POINTERS!!! What are they pointing to, addresses, variables, names of variables! Pull my hair out. ;-(
Why does the Serial class initiation remain? - Could you not use the same method to remove Serial.Begin(xxx) from compiled code?
Yes, you could, but you don't really save much space (a few bytes at best). Not like the complete Serial.print statements now being sent into the ether!
Additionally, you might want to actually still output an actual Serial.print statement (eg fatal errors) so this would still be needed.
Removing Serial.print() statements from your code is as simple as a Ctrl-F to find them to delete them. Doing so is the same as cleaning up after any job; doing the dishes, putting away tools, or using toilet paper. Cleaning up (or "linting") software code is what any good programmer does. It involves deleting commented-out lines, unnecessary blank lines, excessive comments, etc.
Though your #define debug(x) and its associated conditional compiler directive will keep those lines from being compiled in the final version, the debug(x) lines will still be littering the code, impairing long-term readability (even for you after time passes), and adding lines (and associated scrolling) that have no value in the future.
Let me just add that there's a click-baitiness to the video title: STOP using something everybody uses. It's a tactic similar to videos that say, "You're Doing {something} Wrong." The current video has been cluttering up my feed for a long time, and I've been ignoring it because it looked like click bait. But I finally thought, "Fine, I'll just see what he says." And I was right: click bait and nothing I think is very useful. You don't even actually advocate stopping the use of Serial.print() debugging. You just show a way of globally deactivating it when it's served its purpose. I understand, of course, that you got credit for my view anyway, and the algorithm is giving you credit for my "viewer interaction." You got what you want, and by complaining about it now so have I. You win, I win.
Not at all.
Removing the Serial.print statement removes a level of documentation from your code that you might well need when (if?) it goes wrong. You put it there originally for a reason, right?
Using the method described here keeps it in place but removes it, conditionally, from the compile process, thus putting away the dishes after washing up. 🤣 But allowing you to instantly (well, after a recompile) to reinstate them.
After a few decades as a pro developer I've valued decent comments and print statement in code (not just mine).
By the way, this was not click bait of any kind. I thought it described my video perfectly as I believe we should indeed STOP using standard Serial.print statements (in the ESP32 world I never use them, using the built-in conditional debugging levels instead). You didn't find it useful, fine. But don't tell me that I'm into sucking you in to watching a video you don't find value in. That's what the STOP 😁 or BACK button is for.
@@RalphBacon 🤣 100% agree. You even covered it in your video. "Crap, it's broken again - let me put them back in for a sec". This is real world programming. It's not the same as academic programming. Sometimes you have 1 day for a turnaround POC. DRY isn't the best way to do things. Obviously he's right once you move into a final production product that will never change again, but how often does that happen? We're always iterating
You should rationalize your debugging code and only leave in what is likely to help in the future (because now you know what helped), review what remains to make sure it has no side-effects that would break your code if it was removed, and then use C# preprocessor directives to control whether you include the debugging code in your executable.
A more sophisticated version of the above is to define a set of debugging levels, again which you control by preprocessor directives.
Finally, if it is possible in your environment and seems warranted, add some kind of logging, using the same levels strategy as for debugging.
So what does switching between "Debug" and "Release" do in visual studio C/C++ you think? Exactly what he demonstrated in his video... Your find and replace method doesn't work in many editors when you have multiple files. And you can add a header to your arduino library so you have these debug methods available in all your projects by simple one define and include. I have a book of C++ for beginners and the first chapter after the introduction is about the preprocessor. Only plebs use find & replace.
In my personal library I made a header that replaces serial.print with spr or sprl, and I can enter up to 4 different parameters. It's compact, very easy to type, and much more clear to read. Also, with one line of code or even an input wire, I can disable all serial prints at once to speed up the program
Sounds like you've done a similar thing then, Nathan. I think most coders eventually devise some method of doing this, thanks for sharing.
A slightly better, more correct way of defining your debug() macros to do nothing is #define debug(x) ((void)0) instead of really nothing. Please remember that these macros in the actual code are followed by semicolons. So now when they expand to nothing, you have lonely semicolons left in your code. This might sometimes lead to compilation errors and even logic errors. The macros expanding to ((void)0) avoid the empty semicolon problem.
Hmm, you may be right but empty semicolons are not a syntax error:
while (1) { ; }
is perfectly legal (and what my IDE insists on converting an empty while(1); to!
@@RalphBacon One example where the empty debug() macro will cause a compilation error is: a ? foo() : debug(...); This will become a ? foo() : ; and that won't compile. I understand that this is a contrived example, but you'd be surprised how "creative" people might be in using your stuff. (For example, people might create their own macros based on yours, which might use something like that.) My point is that it does not cost you anything to define debug() as ((void)0), which will prevent all (or at least most) such corner cases. I like your presentation. I just wanted to help...
Yes, your suggestion is a good one, and the, er, ingenuity 🤔 of developers cannot be underestimated 😜. So, to prevent potential problems your suggestion is good and I hope others read this comment thread in case they are having some weird compilation errors when using my alternate use of debug.
@@StateMachineCOM Thanks for pointing out a question I had.
I tried your suggestion though and it does not compile.
Just leaving the thing empty it does.
Mind you, I am using Wokwi's emulator and not the real thing.
Nevertheless I would like to thank ALL the people who make helpful videos like Ralph and those who make constructive comments and suggestions like Quantum.
Ralph i know you have being doing this a long time this one was easy
To follow
But can you make the code show better ie larger in the code box when showing programs on tablet it is to small and on to tv small
Love the voice asking questions but for me to follow you i need above
Showing for all your code parts the i can pause and read it
Brian
OK, I'll bear that in mind, Alison. In the meantime you can download (and follow) the code from my GitHub whilst watching the video:
github.com/RalphBacon/224-Superior-Serial.print-statements
#if DEBUG_FLAG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif
This code snippet adds a great value for this video thank you very much , can make your mic little clear so audio is perfectly audible
Thanks for sharing 👍
I risk being unable to see the obvious but the Simple and Advanced Example at github don't seem to demonstrate 'printf' and I also don't see how the printf library gets included into Platformio. Can you elaborate; I appreciate the issue with the sharp knife - been there, done that. Cheers.
Thumb is slowly healing, Thornton, thanks!
The Simple and Advanced sketches do not use printf at all; the Advanced example shows a more granular approach to debugging levels (I'm using it right now in my new sketch).
I'll be doing a complete video on printf very soon. In the meantime, you can include it in the *PlatformIO* environment by clicking on the Alien Head, choosing *Libraries* and entering *arduino-printf* in the search bar. The first entry in the resulting list (on my system, at least) was *LibPrintf by Embedded Artistry* which you can then click and *Add To Project*
Note that ESP32 projects won't need this, ESP32 has printf already built in.
@@RalphBacon Clear reply, cheers, mate.
Click bait. Stop using Serial.print but still uses it as a #define.
You are still using Serial.print and makin it more complicated then it needs to be.
Thanks for the feedback.
To avoid unused variable or parameter warnings, try this for the no-debugging case:
#define debug(x) (void)(x)
#define debugln(x) (void)(x)
Yes, that good to suppress the warnings. Or, _include_ the extra parameters as I do now. Here's an example of the 'Error' level warnings:
#if DEBUGLEVEL > 0
#define debugE(x, ...) \
Serial.print(x, ##__VA_ARGS__);
#define debuglnE(x, ...) \
Serial.println(x, ##__VA_ARGS__);
#else
#define debugE(__VA_ARGS__) // Nothing to see here
#define debuglnE(__VA_ARGS__) // Or here
#endif
So now, debuglnE(aNumberGoesHere, HEX); will work.
#define DEBUG 0
#if DEBUG == 1
#define DebugBegin(x) Serial.begin(x)
#define Debug(x) Serial.print(x)
#define Debugln(x) Serial.println(x)
#else
#define DebugBegin(x)
#define Debug(x)
#define Debugln(x)
#endif
If it works for you then it works! I haven't tested this but I might give it a try in my next sketch!
@@RalphBacon Yes it works very well
Making a DebugIf(condition, x) is good too. This allows you to have other debug symbols, such as DEBUG_SERIAL, DEBUG_PWM etc. Having DEBUG=0 to switch all debug printing off is useful for a production build, but having all of the prints running when DEBUG=1 can be very tiresome to read, and obviously makes your code very much slower. This more fine-grained approach allows you to just show the debug prints that are relevant to the part you’re working on / testing.
hello Ralph A few weeks ago I just learnt what #if meant and now thank to you I have an application for it, thank you Ralph.
I knew you were learning this, Robert (obviously) and thought you might benefit. Glad it was helpful!
Brilliant!!!
Thanks for the feedback!
define is good for debug, where the compiler handles it. But using it in cases like "LedPin" (Which I have seen many times like you said) uses more memory than a const if you use it more than once. Const occupies 1 place in memory, but if you put "13" all over the place it's going to use more.
Hmm. I'm pretty sure using a #define macro name instead of a const does not use more memory.
The #define method just substitutes the value _before_ compilation, so:
#define ledPin 13
digitalWrite(ledPin, HIGH);
becomes
digitalWrite(13,HIGH);
Using literals here, is not memory hungry (it's a one byte integer). But it can depend on the compiler.
Using a const:
const uint8_t LEDPIN = 13;
digitalWrite(LEDPIN,13);
will require the LEDPIN value to be retrieved from memory by code unless the code has been optimises and the value substituted like a literal.
The compiler will try to keep the const value out of SRAM (it knows it cannot be modified). Which one uses less memory is hard to determine.
@@RalphBacon When you're using multiple like I said:
#define ledPin 13
printf("%d", ledPin);
printf("%d", ledPin);
printf("%d", ledPin);
printf("%d", ledPin);
Is:
printf("%d", 13);
printf("%d", 13);
printf("%d", 13);
printf("%d", 13);
in this scenario you're assigning 4 different constants, all of them are 13
vs
const int ledPin = 13;
printf("%d", ledPin);
printf("%d", ledPin);
printf("%d", ledPin);
printf("%d", ledPin);
living in a single place of the program. I'm talking I've seen programs with hundreds of places that define rewrites at compile time. In situations like above program size will be bigger.. but if you're running those defined preprocessor consts all over the place especially running simultaneously it ends up being more memory, too (as well as having to be loaded into memory, and removed rather than just sitting there as a global constant for the program's entire running duration)
That's all I meant though, not for like single use things doesn't really matter
#define DEBUG
#ifdef DEBUG
Serial.begin(9600);
Serial.print(debug message);
#endif
if you comment out //#define DEBUG and then compile it, all your serial functions should be removed in the compiled bin, well as far as i understand it anyways.
just using an if debug == 1 is not enough as your still doing that check.
Also #ifdef is already part of the sdk and you wont need a lib for it.
I would also recommend such an approach👍I always define myself a function that wraps around the serial.print function. Inside of this wrapper function i will have the #if ... statement. With this you can keep your code a little cleaner, as you don't need precompiler statements in every print call, you just call the wrapper function☺️
Yes, removing the Serial.begin is very doable and ensures the entire library is removed by the linking process.
Why don't you put the Serial.begin inside your debug macros?
#if DEBUG == 1
#define debuginit(x) Serial.begin(x)
#else
#define debuginit(x)
#endif
So many comments about using "find & replace" while real developers use the method described in this video, it's even built into most C/C++ compilers (switching between debug & release). Even in C# debug functions don't output when you switch to release mode. People learn about macros and criticize because they think they know better, pretty arrogant. With precompile definitions you can leave out whole sections of code so you can include and remove functionality of your program too. (Eg, compile code for a display with code for certain sensors in or removed). You can switch between versions of components or just remove them from your code. It's very useful
That looks too simple to be true. Oh wait.
Hang on... does that work? Have you tried it? I cannot believe I did not do this (or at least try it).
A plausible explanation is that by leaving the Serial.begin alone, you can always use the standard Serial.print in the sketch and (perhaps more importantly) any included libraries will also be able to output errors. Otherwise it could all go very dark, so to speak.
@@RalphBacon I don't know if you are being sarcastic. But anyway, placing the serial begin also in your debug precompiler section removes even more bloat
No sarcasm implied. My (mostly) plausible explanation though is a valid one, right?
@@RalphBacon Your video is certainly valid and good advice. To also put serial.begin in the DEBUG macros is just a small improvement. Of course, if one wants to use the Serial methods outside of debugging this would disable it. You still need to put Debug println and other methods also in the DEBUG macros, not only the serial.begin.
I have been using FOR AGES
bool debug = true;
if (debug) {
Serial.print("my text here or whatever")
}
and thats it... Okay thank you.
And I am sure a lot of people are actually doing it this way.
Why do I think its better?
Because you can fast an easy create differen Serial debugs for different tasks.
like add another one that shouldnt be run with another.
bool debug2 = true;
if (debug2) {
Serial.print("my text here or whatever")
}
And so on.. makes it even better and faster
Having granularity with debugging is a good idea; if you look at the Advanced example in my GitHub you will see a variant of this, where I have Verbose, Debugging, Warnings & Errors selectable as debugging level too.
But now I'm thinking that having some descriptive debugging names like you suggest - good idea, thanks for sharing!
function debug(text){
if (debug){
serial.print(text);
}
}
😁 + Indentation
//I wonder if there's a way to detect if the potential of the tx pin changes....... Could enable debug when something is plugged in.... 🤠
I usually just comment out the serial.begin statement in Setup when I'm finished with the code. Doesn't really help with code cleanup at compile, but it does get rid of the slow speed issue due to serial outputs.
So by including the Serial.setup(x) as part of the overall #define we could get the best of both worlds, I guess?
But you left in Serial.begin(); so you've missed the chance to remove a whole library
Well, yes, I did, but this could also be removed if it is a problem.
We can even switch off the UART completely if required:
#include
power_usart0_disable();
For battery powered projects it might be advantageous.
Compilers are tricky these days. Since that's the only statement left, and the library's not otherwise used, I wouldn't be surprised if it was optimized out of the compiled code. Would certainly warrant some experimentation.
Blimey....15 minutes to get to the point...... Still it was actually quite a nice idea, eventually :-)
Just 15 minutes, Jack? I must remember to pad it out a bit more! But seriously, setting the scene for beginners is essential. Glad you (eventually) liked it though!
Thanks for the handy tips. So, there's no debugF to get everything on 1 line?
Yes, there is. You can install the printf library. Demoed in video #227:
ruclips.net/video/lhwk5vJ1iMA/видео.html
@@RalphBacon Great. Thanks for that!
Normally I love your videos but I have stopped watching anything entitled, "STOP doing !" So please stop doing that.
The irony of you stopping watching videos that tell you to stop doing something is not lost on me!
Anyway, you missed a good video, and have perpetuated the use of Serial.print in your sketches. Deffo worth watching!
I thought you were going to suggest toggling GPIO pins to indicate execution state. It's fast and if you have a multi-channel scope and only a few events to monitor it's good for getting an indication of when things are happening. Anyway, it's something that I do when printing to serial takes too much time or would result in excessive output.
You can also control the Serial.print statements if a particular (spare) GPIO is low. Connect a jumper between the pin and GND to turn Serial.print statements on, remove it when done for faster execution.
The macro in this instance would interrogate the state of the (input) pin, ideal for those on the ESP32 that are INPUT only.
@@RalphBacon Yeah, that is a good strategy as well.
Use capital letters for defines and constants. Video could be 5 minutes to explain such simple method.
Not really. Beginners need a detailed, step-by-step approach and an explanation of _why_ we do things - no sudden jumping to conclusions or they get lost and switch off. Yes, I use CAPS for constants but I'm not always a fan for #define names.
21:03 can you do some kind of #define or something to replace line 30 too? then get rid of that "b"-warning when debug is off.
I hear you but a warning is just that: a warning. Not an error. I try not to get hung up over things that are known (not just ignored) and accepted.
But I'm sure you could tweak the code (or the #define) to remove the warning if it really annoys you.
And there is the "Report by Exception" rule, where all warnings and errors MUST be removed before sending anything live, otherwise we could have a dozen warnings!
@@RalphBacon totally agree that a warning i not to worry about, when you know what it is about.
Main reason (for me) to clear it, would be to "clear the sheets", to make it more noticable whenever there is new warning that I have not looked at yet, as a warning can be caused by compilable bugs.
I also thought about possibly bitmask logic, to only enable partial debuging:
0: no debug, 1: debug input, 2: debug output, 3: debug input and otput.. etc
Then you can enable debuginfo for the parts as you need. But disable parts you don't need at the time. To avoid wasting resources on debug all, and to save space, and to avoid bloading the debug console.
But if size is not a problem, debug enable/disable by input is nice too.
Have a look at the Advanced example in my GitHub; it does exactly what you suggest:
github.com/RalphBacon/224-Superior-Serial.print-statements
You dragged a 3 minutes video way too long but it's a great concept for beginners.
I'm glad you said "it's a great concept for beginners".
Imagine yourself being taught to drive a military tank. (I hope you don't have military service, especially in a tank 😲).
Do you think 3 minutes of instruction is enough to even get you to move the tank forward (without destroying everything in its path)?
Put yourself in the shoes of a noob who needs every single thing explained in a clear and concise way. At least once. Ideally more than once.
That's why it's as long as it is. 😜
3 min video is too short for google to plant any adverts. Therefore, no money. Get it?
Very nice Ralph. I've watched this a couple of times, and picked up things each time, as I'm new to this stuff. I really like the way you explain things. Effective and entertaining as well.
Personal thought: When you change the code during the video, you use a Screen Transition. It sometimes causes me to get lost and I have to "find my place" again on the screen of code. I know you do this to save time and not show backspacing the code and rewriting it, but since you are explaining it anyway, I think I would rather follow along as you do so. Just my personal thoughts. Thanks. Regards
I'll bear your feedback in mind the next time I do a code walkthrough. 👍
Lots of blabla
Thank you for the feedback.
Rude
Unused variables in proper compilers would be optimized out. Example, the STM32 cortex M series compilers. Not sure about the arduino though.
Arduino uses the GCC compiler, so yes, this also applies
It's quite amazing what is removed as part of the optimisation process, including whole functions.
@@RalphBacon Indeed. As a retired software engineer having worked in customised compilers and language interpreters for several companies, I’m currently an occasional contributor of the LLVM Compiler project, so I can confidently assert that a modern compiler is able to make certain optimisations and code transformations that are even beyond what an average programmer would be able to implement in assembly language
Good but you talk too much, gets bit boring .
My middle name is boring. Ralph Boring Bacon. That's me. 😲
You can always stop watching, nobody is forcing you.
As soon as it goes to some more than an AVR, I use "#define DEBUG(...) printf(__VA_ARGS__), but in more complex systems I prefer to work with tokens added it automatically so I can see the part of the software, the warning is coming from. But for that you have to write an va_args warpper that then connects two printf resulting in something like
[ENC] Stream picked up
[MOD] setupt tx frequenxy
[LO] Frequency set
[MOD] Gain set
[RF] TX On
[ENC] Stream started
A bit like the ESP_LOG mechnaism but not that bloated in the output. And I also often have 2 Levels of debug. One that helps you to design the thing and one that stays on forever and gives you a clue in cases your thing doesn't work as expected.
But other than that I prefer to work on STM32 and just use JTAG online debugging diretly into the thing, and keep printfs as rare as possible.
Yes, that sounds like a good method. I sometimes use a spare GPIO pin that only outputs the Debug message when it is is LOW. It doesn't help with program bloat but does keep things moving at speed when you don't need the serial output.
ESP32's log_x functions are defined inside the ESP32 Arduino core (esp32-hal-log.h and esp32-hal-uart.c). You do not need any additional library and maybe you should have mentioned that ArduinoIDE has a menu item to control that log level. Define is needed only for PIO
Good information, thanks for sharing!
What is amazing to me is that it has taken you all this time to figure this out. ALWAYS remove debugging code from release versions. I use a #define and #if statements to turn on/off serial.print and other debugging code.
Absolutely, I couldn't agree more.
Is there a way to achieve something similar on the Pi Pico using Micropython? Really useful video 😁
Not that I know of, but that's because I am rubbish at microPython. If there _is_ a way you can be sure someone has found it. Bear in mind though that Micropyton is _interpreted_ and so I guess it's going to be a lot slower than compiled C++ anyway.
I've watched so many videos with demos that show the serial output on a separate window, but I can never find anything showing how it is done. My output always shows at the bottom of the IDE 2.0 sketch. Will you please explain how to set this up?
That's because you are using the in-built Serial Monitor of the Arduino IDE. I'm using a standalone program called CoolTerm which is nice, and it's free too, although Roger is grateful for any donation if you find that you use it a lot: freeware.the-meiers.org
I don't see any chapter marks in the timeline nor a list in the description...is it just me? (Probably, it usually is.)
Not just you!
@@IanSlothieRolfe Thank-you Ian, very much appreciated.
I had a mishap with a craft knife so I couldn't do this... typing this with one hand today! Slowly.
Chapters now added!
@@RalphBacon Thanks for the update Ralph...you're a cut above the rest!
Great Idea! Until now I used a function for that and commented the serial.print out after debugging so the text was not sent. Your way the text dissapears completely so it is much better.
Glad you found it useful, David!
This is great! my rookie approach was always to wrap all the serial.prints in an if statement with a global true/false var. This is a much better solution as those operations get removed completely from the compiled code.
Yes, and you can customise exactly what you want to do with the statements too.
Yes you can't use printf but you can use sprintf() and it works great just create buffer and provide this string for example to Serial.print()
I must admit a dislike to creating a buffer of unknown length to receive a message that we then output. I think printf works better and the ESP32 has it built in and now the Arduino can have it for very little cost (code size-wise, I mean).
Hi Ralph. I do something similar. I have a function debugPrint() which checks whether isDebug() is true (which is an analog read if it's brought low it's true, on A0, usually in my code) and then I have an #ifdef _DEBUG_ENABLED_ #define debugPin A0 #define debugLED 13 #endif then you just need to define _DEBUG_ENABLED otherwise it doesn't compile in any debug support. It's useful if you're developing on a huge processor (like ATMega2560) and then wanna compile it down to 328 (for example). I think adding a counter to the debug function might be useful, but haven't thought about it, since haven't had much time without brain fog to work on Arduino projects.
I think experienced programmers, like you Jess, will probably all be using some variation of this, tailored to the way you like it.
But beginners will never have used any precompiler constructs so I'm hoping this introduces them to the concept as well as allowing them to make their programs look more streamlined without #if...#else...#endif statements everywhere! Good to hear from and hope you're enjoying the new pad.
Awesome simple solution 👍🏻👍🏻
For the remaining unused 'b' variable couldn't you do a define for that as well so it'll get toggled on/off like the rest?
Almost certainly although I have not tested that. Go on, have a go!
how about this:
#define verbose true
if (verbose) {Serial.print.....}
then, once you are done with debugging and you go production you change to false and nothing is printed
except that we have lots of "ifs"
This is not used in production, and the simple macro method is only used. Here you are on the mercy of the compiler optimization to remove the dead unreachable code, whereas in the macro method, you are controlling when it will be part of the code or not.
@@PriyankBolia you are right
See my GitHub for an advanced version!
github.com/RalphBacon/224-Superior-Serial.print-statements
@@RalphBacon grabbed - thank you
if you would define the debug functions as regular C functions, and have a boolean that can turn them off (make the functions do nothing), shouldn't the compiler detect that all that and actually optimize the code such that the debug functions are not even encoded to any processor instructions.
That's exactly how Espressif do it on the ESP32. They have various logging levels that are not even compiled if the Core Debug Level is not set to certain messages. This here, is a simpler version of that.
Took longer to compile with Debug off??? 3:34 against 3:21
Perhaps my PC was busy? Sometimes I do wonder what the compilation process is doing.
Thank you for your beautiful tutorials
What is the difference between debugD and debugV?
In both definitions, debugE, debugW, and debugD are executed.
Was there a specific reason you defined debugV?
And why is the debugNothing function used in debugV and not used in the rest of the functions? Is there a specific reason?
Thank you for your reply
The "V" stands for Verbose and is used for those really low level messages that otherwise would be really annoying and flood the output.
"D" is for more general Debugging and E/W/I you know about. The level you set (eg "I") ensures that all message at that level AND HIGHER are output (so W and E too).
debugNothing should exist for all levels if they are not defined.
Thank you for the very comprehensive tutorial. Excellent tutorial for beginner programmers, but it could be cut down to a 5 minute video for intermediate to experienced programs.
Glad it was helpful! And, yes, for those beyond beginner stage it doubtless could be done more succinctly but! This channel is about getting beginners' projects off the ground so more detail is required.
Incidentally, if I watch a video that is going too slowly for me I just increase the playback speed to 1.25x or even 1.5x and it gets things moving along rather nicely!
@@RalphBacon I subscribed to your channel as you lend a very comfortable and comprehensive means of educating about Arduino. Thank you, again. Best regards
another tip, we could have differently debug mode such as 2/3/4/5...etc, so we can preset a bunch of variable in Preprocessor , so we test our code with predetermined inputs as the alpha test. by combined using #define #ifdef to activate and deactivied piece of code.
I've got a more granular version in my GitHub that allows Errors, Warnings, General, Verbose settings. But another viewer has suggested different names for the debug variable so we can debug just one function, for example. It's something I'm thinking about.
Hey there Ralph. Another great video; informative and entertaining. I'm going to add this to every sketch I develop from now on. It is just to simple and easy not to do.
Fantastic! I'm using it right now in my current sketch, Adrian, and it works very well! It can be tweaked as required too, or the Advanced version can be used (in my GitHub).
Thank you Ralph, this is so useful and I was very impressed how it worked and saved space for me. Silly question, is there a way of making the debug trick available to all Arduino code? So, that when I open a new sketch the whole debug solution is already available please? Thank you.
The _easiest_ way of doing this is to keep all the DEBUG stuff in a separate file (I'd suggest of type hpp, which is a C++header with code too). Place it in your libraries folder (usually a sub-folder in your sketches folder).
Then, in each sketch just include a simple
#include "MySpecialDebugStuff.hpp"
(or whatever you called it, keep it easy to identify but not as verbose as this!) at the top of your sketch and it will be included in the sketch.
Glad you found it useful!
@@RalphBacon Hi Ralph - I have tried it as you described but running the Arduino verify reports "Connection failure". Would you mind including an example hpp file containing the DEBUG stuff please? I am sure I am probably doing something silly! Thank you.
I believe you know your add 1 never changed the value of counter!
I do have an ESP32, I may go get the log lib. Will it have issues if I add the printf for the arduino or do you know?
Lee
If you have an ESP32 then you have a separate way of displaying debug information, by using log_x where "x" is e,w,i,d,v for errors, warnings, info, debug and verbose output respectively.
It uses the "printf" functionality behind the scenes. Just compile the sketch with the build options (menu Tools...) set to VERBOSE (5) thru NONE (0) or something in between for this to work. Very simple, very useful.
Using the printf functionality with an Arduino just uses memory so keep the messages short!
You can also speed up those Serial.print statements, by simply setting the baud rate to 2000000 instead of 9600 for ATMEGAs, or 115200 for ESP8266.
I've never used higher than 115200; I will see if my IDE allows me to select a higher speed and see whether I get corruption on long(er) USB leads.
For the unused b variable issue, couldn't you create something like `#define debugCode(x) x` and then encapsulate the variable definition and counting lines with b within debugCode()?
I dare say you could. I haven't investigated but what you say certainly sounds plausible.
How implement Debug statement in different cpp when it once defined in Main.INO?
Put the definition into a separate header file and #include that file in every other file you need to reference that debug statement.
A word of warning:
If you define a symbol to be an expression, put parentheses around the expression to make sure that they evaluate the way that you wish.
Example:
#define ratio( x, y ) x / y
if you use ratio in the following, you will not get what you intended
float a = 5.0;
float b = 10.0;
float c = ratio( a, b ) ** 2;
The expected result would be 0.25 (= 0.5 **2 ).
UNFORTUNATELY, the compiler was presented with x / y ** 2
The operator precedence of C/C++ will perform the exponentiation first and the the division. This means that c will be 0.05 ( 5 / 100 )
If instead, you change the define to be
#define ratio( x, y ) ( x / y )
then the expression seen by the compiler is ( a / b ) ** 2 which is what you wanted.
A very good point you make, Michael; many libraries will contain all the #defines in "(" and ")" just to be sure (and UPPERCASE).
Hi Ralph! This is off topic but can you or someone point me to the video where you walk over installing Arduino ide on visual studio?
I've never done a video like that because installing VSC plus PlatformIO (plus all the bits that you inevitably need) would be a looong video. That said, I think it might be more streamlined now so I'll take a look and see whether it's worth doing!
printf is working on my esp32 without adding any libraries. Have not tried it on a atmega.
Indeed, it's built in. But for logging, I'd recommend log_x where x is v,d,i,w,e depending on the level you want).
👍👍👍👍👍 Thank you, thank you,, I had just come to the realisation of why my code and memory usage was changing and realised I needed to take out all the boating "print" statements. But now you have given me a way to manage debugging in a nice convenient way, so thank you for your video and your help. ...!! Very much appreciate learning how to write better code. ...!
You're welcome! Glad you are finding it useful, Philip.
Sorry to hear about your argument with the knife.
I had another thought about using this. Would it be possible to turn this on or off with a MQTT message or some other means of remote control?
We would not be able to control the _compilation_ process, so the Serial statements would be present; but the _execution_ of those statements could certainly be controlled by pretty much anything that the microcontroller can receive (it's just a flag, after all).
I used to save the overhead (time and memory) by debugging MCU projects code using different colour LEDs on GPIO pins as a crude form of debug. It does use IO but it can be fast and compact.
The LED colour would signify different parts of code or deferent meanings (semaphores).
Indeed. Your method is not that different to the way I proposed (and subsequently used) in videos #251 and #252 - a simple two digit display hard wired into your project to show the [error] status.
Yes, this is also the way to go for things which you would like to remain in final build. But whenever you will decide to go back to code to apply some change or bugfix you can realize it would be nice to have a cheap method which would bring back all logging as fast as possible. And this is why all logging statements should remain in code. They just have to be configurable (switch on and off, specify which should be the minimal lever for the statement to be preserved by compiler, ...).
@@VictorYarema Yes that is a pro tip for sure - thanks!
Could you also define debug to "//" so it effectively comments out the line?
No, the preprocessor only get executed after all the comments have been stripped out, so "debug" would always be an empty string
Ian says it all.
One of your problems, mr. Bacon, is that you go Serial.begin(9600).
You can be 96 times faster with 921600.
The highest speed I use (usually with ESP32s), Vasily, is 115200; I will try higher speeds and see how it copes.
The Arduino, with its 16MHz processor speed, is surely limited to 115200 at best? I tend to use 9600 on the Arduino to make it super safe and no risk of corruption. I guess I should increase that too! I can remember when 300 baud was the maximum I could get from some chips!
@@RalphBacon 921600 is one of standard values for UART. The FT232 USB-UART chip has 3Mbaud maximum data rate, same is for many clones and alternatives. As for microcontrollers, they usually have hardware implementation of UART, cpu clock is not that important.
@@RalphBacon Also, how faster one can go over UART for different boards can be a good investigation for your future video. BTW, for esp8266 and esp32 I used 921600 with no problems. 115200 is very safe for atmega328p (depending on USB-UART IC).
Hmm. If I clock an Arduino at 8MHz then my maximum sketch upload speed is only 19200, otherwise it might fail.
On the other hand, an ESP32 can upload at 921600 no problem at all! There must be a correlation between processor speed and clock speed. Unless this is only true for storing program code into Flash (which is slower, we know).
@@RalphBacon Well, this is true. The correlation is in how good and functional is the clock divider or PLL in MCU. That can be proved only experimentally or computed from datasheet.
Thanks you so much for this! My code was working fine, but it always bugged me that the serial was constantly printing to nowhere and taking up processing time. Now I have a solution! and...Now I have to go back and update ALL my sketches!
Glad I could help! Well, I'm sorry about the mountain of work ahead of you, though. No, really.
Valuable reminder of good practice but worth stressing that any testing needs to be completed on the debug-stripped version of the code ... can’t count the number of times I’ve been chasing bugs generated by accidentally non-included or over-defined values not apparent until the effects of the def changes played out. Keep up the great work 😀👍
Sorry, Chris, what is this word "testing"? I've looked up the definition in the Arduino Coder's Bible and it states:
"Testing, v, The iterative act of releasing code into the Real World and seeing what happens, mostly using the happy path".
Sounds about right.
Hi Ralph good stuff again well done\\.
I #define debug as true or false, and I use "if(debug)Serial.print(x); I takes a little longer to write but then toggle the define to false on the final compile. I tend to leave setup print statements as only run once and give an indication the thing is working.
But once you are "done done" with the code, I'm assuming you are no longer monitoring the serial output? In which case an LED or Buzzer is a better indicator that things are running OK, I find.
Instead of if(debug) use #ifdef debug as this won't include the code. The #ifdef can be used on any statement including variable declarations.
Nice explanation, a bit long-winded ;-) , but i'm running in trouble on places where i've used something like Serial.print(foo, BIN); of course an extra define debug_with_parameter(x,y) serial.print(x,y) can be a solution .... But not so elegant.
Well, Jan, you had to stumble across the bit I did NOT want to do in the demo as most would have switched off! However...
If you change the definition of each debug(x) and debugln(x) to:
debugln(x, ...) // at least one parameter, maybe more
then change the underlying Serial.print to:
Serial.println(x, ##__VA_ARGS__)
you can use
debugln("this is a string")
debugln(42);
debugln(42, HEX);
Works for me! But on your own head be it! Let me know how you get on.
@@RalphBacon Thanks Ralph, tested it and it works as supposed! 👌Grtz from the Netherlands
Woah this is such a great idea, thank you very much for sharing!
You are so welcome!
Fantastic, thanks Ralph... wish I'd known about this a few years ago....!!! An ask, if you read this... would love to have a similar video on how to setup serial logging to a "Logserver", not had any joy getting this working.... I have scattered ESP devices around the house and I'd like them to write diagnostic data to a central point for me to keep an eye on.....
Interesting that I already have some components on order to enable remote logging for at least two projects - not necessarily "centralised" (you would have to cater for data collisions) but we will see how far it gets and you can then decide whether it can be expanded. Small world!
@@RalphBacon Great, looking forward to it!
hi sir.. what is the name of your serial monitor software..it seems so good.
thankyou very much.
It's the brilliant CoolTerm serial monitor from guru Roger Meiers: freeware.the-meiers.org/
@@RalphBacon thanks so much...🌹🌟
Thanks for the video. Please advise. How did you bring up the "call term" window. (Or what did you call it?)
That would be CoolTerm, an independent serial port monitor. Free to use, although if you find that useful the author, Roger, appreciates a contribution.
You can have several instances running at once, which I do, as I'm monitoring several ESP32 projects at once:
freeware.the-meiers.org/
Thanks! Really nice explanation of how to do functions using the preprocessor.
Glad you enjoyed it!
Thanks for an excellent video. This is a more elegant solution than I have been using. I did encounter a problem with the following program statement:
debug("Error[%u]: ", error);
This caused an error: macro "debug" passed 2 arguments, but takes just 1
My #define debug(x) Serial.print(x) looks like yours, is there a way to pass either 1 or 2 arguments?
I just noticed that the original statement was Serial.printf("Error[%u]: ", error); (not Serial.print)
So I understand I need a new #define statement to cover this, still don't know how to handle multiple arguments?
Well, #define can have more than one argument eg min(a, b) but whether we can use ... or __VA_ARGS__ to represent multiple (and unknown) parameters in the #definition is something I never tried (yet).
@@RalphBacon
Ralph it definitely can be done with variadic macros.
#define DEBUG_STATE true
#if DEBUG_STATE
#define BUFF 50
#define Serialprint(...) Serial.print(__VA_ARGS__)
#define Serialprintln(...) Serial.println(__VA_ARGS__)
#define Serialprintf(...) Serial.printf(__VA_ARGS__)
#define Serialbegin(baud) Serial.begin(baud)
#define aSerialprintf(...) {char buff[BUFF];\
snprintf(buff, BUFF, __VA_ARGS__);\
Serial.print(buff);}
#else
#define Serialprint(...)
#define Serialprintln(...)
#define Serialprintf(...)
#define Serialbegin(baud)
#define aSerialprintf(...)
#endif
Just replace Serial.print with Serialprint in the sketch code to invoke the macro definitions. Of course Serial.print will still work for non-debug messages. That is really only replacing debug with Serialprint from your code but I find that more intuitive.
The aSerialprintf is an alternative definition for Serialprintf and is only needed if Serial.printf is not available (ie Arduino boards) in your setup (via a library or built in for ESP's) otherwise leave it out.
The printf buffer size is hard coded to 50 characters. The BUFF definition is also not needed if aSerialprintf is not implemented.
Looks good, Phil. I never considered multiple parameters in Serial.print statements but, of course, you _can_ use them so people will. Excellent stuff, thanks for sharing.
Great solution and great explanation. Will use this in future. Subscribed
Awesome, thank you!
I'm working at a project that has an lcd display, I use that for debugging! I guess I would use serial print too but a few lines where it matters don't think they would slow much..plus i leave them as commentals when not needed
When doing PIC work I used a purpose built 16x2 LCD for debugging! But I don't use PIC chips any more. In a small sketch I don't suppose it matters too much, as you say.
@Ralph S Bacon How would one use the optional second parameter which specifies the base (format) to use; i.e. Serial.print(0x61, HEX); ? At the moment I will receive an error requiring 2 arguments or 1 argument. To ask another way, How can I specify #define debug(x,y) Serial.print(x,y) but allow Y to be optional?
I did update my examples to include further arguments (you were not the first to ask this).
Basically, use the ellipsis (...) as the second argument in the definition, which means "an unknown number of parameters", from none to infinity. That way we can parse any further parameters and pass them onto the Serial.print statement.
@@RalphBacon thanks for the solution Ralph! I will give this a try on my next upload 👍
Thank you Ralph for the video, like many others I hadn't consider using a #define statement to enable/disable debugging.
I'm keen to understand more about the ESP32 Log_v and how it works with the Arduino IDE (2.x) as I haven't been able to get verbose logging working in the Arduino IDE as there isn't a menu option for verbose under the 'core debug level' menu, the highest level is debug. I could simply just use Log_d, but I'd prefer to use verbose to provide the additional level.
I haven't tested this, but does changing the logging level reduce the resource (flash/ram) consumption when using the Log_x command as demonstrated with the #define statement with the serial.print(), I presume it would.
If it's not possible I might have to migrate my project to PIO :)
Yes.
What, you want more? OK.
On the Arduino IDE, the verbose option is level 5. The new, improved Arduino IDE 2.0.x makes this a bit clearer. (0 is no debug😲, 1 is [fatal] errors only, 2 is warnings, 3 is info, 4 is debug and 5 is verbose).
On the ESP32, if you select higher than _verbose_ then the log_v statements simply don't get compiled into the program. And the same happens with log_d, log_i and so forth. So, yes, you save space (and execution time) but making sure your _final_ code is compiled with the lowest level of debugging you can live with.
This works whether you use the #define I show here, or not (ie using Espressif's _log_v_ etc).
Arghhhhhhh why have you got the brackets on a separate line from the else and if statements 😂 I'm not trying to do vim vs emacs (I use nano), just curious why you choose that
I use Visual Studio Code with predefined clang formats so my code is consistent. I'm guessing that I do it this way after being "encouraged" by my employers over the years to use the "company standard".
If you don't like my way, use another way but keep it consistent!
I prefer them on their own line as well. C# formats it that way...makes more sense to me.@@RalphBacon
Thanks Ralph I'm just starting out with Arduino at the my late stage of life but I find it very cool. I look towards many more of your videos.
Best of luck!
debugln ((unsigned long) addOne (counter)); // eliminates the warning.
Note that 'debug' and 'release' profiles define (or undef) 'DEBUG' on the command line.
I like it! I think the Arduino IDE always compiles in release mode, doesn't it? In my PlatformIO setup I usually have more than one profile depending on what I'm doing with the code.