Reading out values of code variables in microcontrollers - Page 1
Forum section: electronics / general

author: iwtommo
author_rank: Contributor
timestamp: May 12, 2019, 04:38:52 pm
content: Im making the leap from arduino to microcontroller programming, and i think im struggling with a bit of culture shock. Specifically - writing and debugging C code on an attiny.Im trying to code a servo motor driver. 2 analogue inputs - a setpoint and a feedback, and 2 outputs - motor forward and motor reverse. The feedback signal is connected to the motor position and its the servo controllers job to make the feedback input match the setpoint input by driving the motor forward and backward - fairly standard stuff. This is my first proper C coding project and im struggling with one very particular aspect of debugging/developing the code for this little thing.In an arduino, when writing code, i would find myself typically writing code, clicking upload and finding it wouldnt work as expected, then using the serial.print function to spit out the values of various variables to the computer screen to aid in debugging. So i could very quickly find out where id made a mistake in any part of my arduino code by printing out all the codes variables/registers/measured values/output values/whatever and it was usually quickly obvious where id screwed up. "oh, the measured setpoint is stuck on 255 whys that, oh look ive shorted it to v+" sort of thing.Now that ive moved over to coding in C on a attiny, ive realised i have no way of 'knowing' what the value of any variable in my code actually is!I remember dave once saying that he used to do things like toggle a pin high or low when the microcontroller entered a certain part of its code, which is a trick i am using, but really knowing a value of a variable is what im after. Do i really need to hack my own project to drive a character display (probably a more technically demanding project to build than the project i would use it on!)This problem is a massive bottleneck in my development/learning process here and any tips/solutions would be massively appreciated!

author: Freddie Chopin
author_rank: Regular Contributor
timestamp: May 12, 2019, 05:21:01 pm
content: You need just one UART with one TX pin to output whatever you like to the terminal. Or just buy yourself a chip which can be connected to a proper debugger - either a bigger AVR or just jump to ARMs.

author: Siwastaja
author_rank: Super Contributor
timestamp: May 12, 2019, 06:08:20 pm
content: Why don't you do the same you did before - print out debug information to UART?UART initialization on AVR is around 3 lines, and the print function is another 3-4 lines of code, and you'll need that eventually anyway, so start from there. UART functionality is often the very first thing I code for a new project, or for a new MCU family when I start from scratch.You can use sprintf() to generate the string, or, for more lightweight code, utilize itoa, utoa functions.Sometimes you have timing-critical things going on and have no time for blocking printing; then, it may be handy to buffer the data, then print out later, like:int trace;int trace_at = 0;fast_thing():if(trace_at < 100){trace = current_val;trace_at++;}slow_thing():if(trace_at == 100)print_out_everything();The greatest thing here is that all of this scales very well to on-field debug and analysis systems, whenever the MCU-specific debug HW is often inaccessible.

author: RoGeorge
author_rank: Super Contributor
timestamp: May 12, 2019, 06:24:08 pm
content: It's called a debugger. It lets you run the program step by step, pause the execution, insert breakpoints, inspect or change variables, registers, flags, and so on. Arduino doesn't have any debugger yet.ATtiny has one wire debugging capabilities, so all you need is a compatible programmer and an IDE with debugger.What IDE are you using for your project, and what programmer?

author: rstofer
author_rank: Super Contributor
timestamp: May 12, 2019, 08:57:45 pm
content: Using functions like sprintf() will chew up a LOT of memory and memory is pretty short.When I do a uC project from bare iron, the very first thing I do is get the UART running. Then I write a getchar() and putchar(). Finally, I open up my "The C Programming Language" (Kernighan and Ritchie) book and copy the various conversion functions as well as some string functions. I may or may not need decimal output. I start by writing a hex nibble, call it twice from write hex byte and call that twice from write hex short. The code is pretty short and stays around forever. Obviously, I need a printstr() function.This is a short bunch of code that allows me to debug just about any project.If I get to the point of having to single-step the code, like I used to do back in the early days of 8080s, I have really screwed up. I hope to avoid that! It's a colossal waste of time.

author: Siwastaja
author_rank: Super Contributor
timestamp: May 12, 2019, 09:22:40 pm
content: Quote from: rstofer on May 12, 2019, 08:57:45 pmReally depends, a "microcontroller" is a flexible term, sometimes you have 128 bytes, sometimes 512 kB of RAM; and often the libraries provide stripped-down versions of *printf, which aren't too horrible.I use my own implementations I wrote years ago, they are much smaller than printf, and I like the fact I could design the interface as I wanted to (it's a bit different to standard itoa functions, allowing easier string concatenation)... But still, really, I see no big issue in recommending the standard printf functions, to avoid wasting time in premature optimization. Beginners tend have to enough learning curve there anyway. String generation memory or CPU time usage is easy to fix once you see it becoming a limitation.QuoteIndeed. Mostly, I don't have time to work with the "just use a debugger" mindset, I have work to do. Tooling is too important to be replaceable by a generic "one size fits all" strategy offered by debuggers and IDEs. But this really divides the people, we are most productive with different tools and mindsets.

author: lucazader
author_rank: Regular Contributor
timestamp: May 12, 2019, 09:28:23 pm
content: I agree with Siwastaja and rstofer in regards to almost never using hardware debuggers.I find it much more useful to build in good run-time logging so that even when the program hasn't crashed i can still see when something has gone wrong.And even when it has usually the processors that i work on can still transmit out the uart even when inside the hardfault handlers.While logging all the time can cause certain parts of a program to have slower performance, i find it better to bake in the performance loss at all times that way the "debug" code that i run is actually the same code that is run in production. This way i can make sure i can inspect and fix bugs that might only occur on optimised "production" builds.

author: cv007
author_rank: Frequent Contributor
timestamp: May 12, 2019, 10:00:07 pm
content: You don't say which tiny you are using. If you are using the newer 0/1 series, you can get a nice ATTiny416 nano board for about $9. It has a built in programmer/debugger, and also has a built in usb-cdc in the debugger so you can use the uart to debug if wanted, or just to output data for any number of good reasons (although seems to be limited to 9600 baud for some reason). If you want to see if you are hitting the right registers with the right bits, the debugger works better as you have a gui representation of the registers and bits. For many other things just using the uart to send out data seems nicer as you keep everything running.The 0/1 series of tiny's seem kind of nice to me. I just received an attiny416 xplained nano and created my own 'drivers' for the tiny416 (C++, mostly just headers for inlined code), and use MPLABX and it all seems to work ok.this is what it takes to init usart in my code-Usart u;u.frame_mode( u.MODE8N1 );u.baud( 9600 );u.tx_on( true, u.ALT_PIN );and whenever I want data out, I use u.putchar() or u.putstr(). I have not touched sprintf, and probably won't as I have a 4k part, so I just roll my own output functions as needed.The c equivalent can do the same, but if using their headers or atmel start it is MORE_DIFFICULT_to_read( FOR_ME ); SO_I_DO_MY_own( THING );If you already have the hardware and just want to make what you have work, just use any usb-cdc adapter you have an hook up the uart. Add a little code and you are good to go.

author: brucehoult
author_rank: Super Contributor
timestamp: May 12, 2019, 10:20:12 pm
content: Quote from: Siwastaja on May 12, 2019, 09:22:40 pmHe said "attiny". I think the 85 is the most popular, with 8 KB flash and 512 bytes RAM, but the ATtiny10 has 1 KB flash and 32 bytes of RAM (aka just the registers memory-mapped, I think?)Those aren't really big enough to run sprintf(), either for the code or for the buffer!In such situations I use an ad-hoc kind of logging that is basically a printf, but with the printf() run on the host, not the microcontroller. I send a byte/character telling the host which printf() I want, followed by the values to substitute, in binary (or at most hex).It's pretty easy to write code to run on the host like:Code:char fmt = getbyte();switch (fmt) {case 'a': printf("Entering function foo\n"); break;case 'b': printf("after SPI read, v=%d\n", getHexInt()); break;case 'c': printf("leaving function foo\n"); break;}etcAnd then you can just send a string like "abA9CDb20EDbFD60c" from the microcontroller.With a bit of effort you can make this table-driven instead of hard coded, and generate the table (or the code...) automatically.

author: Siwastaja
author_rank: Super Contributor
timestamp: May 13, 2019, 06:32:51 am
content: brucehoult, a great example of custom tooling. This mindset is very powerful and flexible; once you are at it, nothing prevents you doing the same on SPI, I2C, or even bitbanging on an GPIO if that is all you have, relevant on small microcontrollers. And works a treat during runtime, without special HW, through the interfaces you would use anyway.As no one requires you to follow any existing protocol, the design can be so simple it's not a big deal to implement; just change it on the fly.My current project involves a Cortex M7 microcontroller streaming a lot of pre-processed sensor data to a host computer, using SPI at 25 MHz. For combining flexibility and ability to debug, the very first thing I wrote was a simplistic subscription model, where the host computer can subscribe to certain data source feeds dynamically. When this SPI packetizer is abstracted in its own module, it looks extremely simple for all other modules: you have a struct for each data type, and a pointer, which, when non-NULL, gets you to the correct place in the output FIFO buffer. So if I want to debug a, say, inertial measurement unit (IMU), the IMU code simply does:if(p_message_imu) // subscription enabled{p_message_imu->latest_gyro = cur_gyro;p_message_imu->latest_acceleration = cur_acceleration;}And the parser on the host side automagically calls a function pointer, which does whatever processing is needed during production, or calls printf, or graphical UI draw functions, or whatever needed for fancy output; the same headers are used everywhere.Things like these are surprisingly easy when you are in the mindset of creating your tooling.

author: cv007
author_rank: Frequent Contributor
timestamp: May 13, 2019, 09:40:32 am
content: QuoteI tried compiling for a tiny85, adding a few snprintf's the size increased by 1.6k. So if you have an 8k part, I wouldn't automatically rule out snprintf's until I at least tested to see what it requires with the particular part and compiler, or later started running out of flash.Sprintf is a handy thing to have though. I have a pic32mm where I have a sprintf class (including a 'markup language' for colors/bold/etc), that other classes inherit for output (via their own 'printf' functions). I can debug usb with lots of pretty formatted/colored output (with Cp0 count value for 'time'), can turn it off/on at runtime, and with the baud rate at 1Mbaud it does not cause any problems. Of course, I'm using 37k flash/3k ram for that one. I may have to try to port that sprintf class to a tiny1616/3217 for fun.The OP will have to provide more info for better results.

author: AndyC_772
author_rank: Super Contributor
timestamp: May 13, 2019, 09:56:58 am
content: This is a consideration which I often take into account when choosing a microcontroller for a project.My usual go-to families are 8 bit PICs, and STM32.I use PIC when I need 5V operation, lowest cost, quickest development time (for very simple projects), or the use of certain interesting peripherals.I go for STM32 for higher speed, obviously, but also because debugging (using CrossWorks) is *so* much faster and more capable, it's barely believable that MPLABX is from the same decade in history. An STM32 project may take a bit longer to bring up from blank screen to blinking LED, but for anything but the simplest projects, the saving in debug time later on more than makes up the difference.

author: cv007
author_rank: Frequent Contributor
timestamp: May 13, 2019, 10:39:11 am
content: it's barely believable that MPLABX is from the same decade in historyMPLABX is not the problem. Get yourself a pic programmer/debugger designed in this decadeThe cheap SNAP is way better than the pickit3 types. On a pic32 it is real fast, on a pic16 it is fast. The pickit3 is almost unusable as a debugger, but the SNAP is just fine. The medbg on the tiny416 nano board works pretty well in mplabx, but there are some quirks which is expected for these newly supported devices (not sure if officially supported yet, but I'm using it without much problems).

author: AndyC_772
author_rank: Super Contributor
timestamp: May 13, 2019, 10:53:20 am
content: I use ICD3, which is a bit faster than PICKIT3 (though in some cases manages to belessreliable, which is quite the achievement).What's different about "SNAP"?

author: cv007
author_rank: Frequent Contributor
timestamp: May 13, 2019, 11:35:56 am
content: QuoteIts $15, its fast., its usb HS (and I would guess the usb code inside probably has another 5 years of experience on how it should be done). I really can't compare with an ICD3 but I can compare a pickit3, and the SNAP runs at the speed of light in comparison.I made myself a mating board to get the usb power (and 3v3) out to the pins.https://photos.app.goo.gl/HA7EgmibKAuhWsRN7

author: David Hess
author_rank: Super Contributor
timestamp: May 13, 2019, 03:41:27 pm
content: Various ways I have handled this include:1. One or more output pins to drive an oscilloscope or counter - this is especially useful to make direct quantitative measurements of things like latency and run time. Two signals into a universal counter in ratio mode are an instant multidigit readout with no DAC required.2. UART to a terminal - asynchronous serial has the advantage of requiring only one or two I/O pins and working with any laptop or personal computer with a serial port.3. SPI into custom made I/O board with LED displays and keyboard - if you have the time to design and build this, it can take advantage of the higher speed of SPI to operate in real time. 74165s and 74595s are your friend. Or stick a SIPO shift register on an SPI port as part of the design if you just want a couple of extra outputs.4. In circuit debugger.

author: ehughes
author_rank: Frequent Contributor
timestamp: May 13, 2019, 05:23:47 pm
content: Segger Ozone let's you look at variables real time via SWD.For the ATTiny, just twiddle IO to flag state or using a UART with your own output routine.

author: ejeffrey
author_rank: Super Contributor
timestamp: May 13, 2019, 05:30:31 pm
content: Quote from: lucazader on May 12, 2019, 09:28:23 pmCounterpoint: Hardware debuggers are awesome and if you have one available it is easily worth your time to figure out how to use it. They aren't a replacement for a debug log, but a debug log isn't a replacement for a debugger either. They are good at different things and complimentary.If nothing else, a debug log statement is a good place to set a breakpoint. But rather than logging every state variable that might conceivably be relevant, you just hit the breakpoint and then can inspect whatever you want in memory. When you get errors in drivers, you can look back at the call stack to see who made the IO call with bad input.Single stepping is something I rarely have to resort to, most often that is only if I am trying to debug unexpected behavior in library code. Single stepping is a faster way to understand what a block of code is supposed to do than just reading in non-interactively.However, I completely agree that if at all possible I want to include debug log overhead in the release builds so that if needed I can diagnose problems that only reproduce in production.

author: lucazader
author_rank: Regular Contributor
timestamp: May 13, 2019, 07:52:13 pm
content: @ejeffreyWhile i agree a hardware debugger is ideal if you can pause operation of the micro without breaking the rest of the program or its functionality then yes you should use them.However i have found in many cases the act of pausing a program can change how a bug interacts with he test of the system, especially if you are communicating with other processors etc.One good example is the current development i am doing at work, brushless motor control. If I pause the debugger when running this code i could potentially set fire to the motor, which is not ideal.That's not to say I don't use the hardware debuggers when necessary, I have just found that I need them less and less as time goes on.Especially as I have gotten better are constructing better, more descriptive debug logs.In regards to limiting the impact of the logging overhead, i ahve found the best compromise for my programs is to log to a fifo buffer, and then in the main loop print out a small section of the buffer relatively frequently so that other tasks aren't delayed for long periods.This way also works very well in rtos systems, where you can process and transmit the log data during an idle task. However as you might guess it can require larger amounts of ram for the fifo (which the original poster desnt seem to have) .

author: ejeffrey
author_rank: Super Contributor
timestamp: May 13, 2019, 09:02:26 pm
content: Quote from: lucazader on May 13, 2019, 07:52:13 pmWell, even if pausing the program causes failure (but not a fire as in your examplejust halting the program when a fault is detected and then examining memory with the debugger is pretty useful. And often even if the program is going to fail, single stepping through the rest of the current operation is possible and illuminating.QuoteI have gone the reverse way. I still try to log relevant information, but I usually find it is faster to just look at the debugger than to figure out what information to log and recompile.QuoteThis is the approach taken by the segger RTT which I really like (the idea at least: the implementation is a bit annoying) if you have the memory to support it and don't mind having the debug probe attached. The only difference is that the debug probe periodically sweeps the data out of memory rather than relying on your idle task. Also, it doesn't use a serial port which can be nice.

author: lucazader
author_rank: Regular Contributor
timestamp: May 13, 2019, 09:40:48 pm
content: Yea each to their own with what works best for them!Good to have tools for both either way.This is a good talk on logging in production environments that also does things like @brucehoult mentionsYea logging to a FiFo buffer approach seems to be quite good overall, especially if you have the resources, and have a low overhead way of transmitting the data (eg SWO line, or a DMA uart etc).

author: MarkR42
author_rank: Regular Contributor
timestamp: May 16, 2019, 02:52:49 pm
content: I use a tinyavr-1 series chip and it does have on-device debugging, but you need specific hardware and need to use Atmel Studio.So usually I don't do that, I effectively have printf() in my code. It uses the USART peripheral to write bytes out to the serial tx pin, which is great, except there is only one USART and its baud rate sometimes needs to be set to a strange value in my application (for rx). So the debug sometimes comes out at an "unusual" baud rate. So far I just tolerate that.

author: cv007
author_rank: Frequent Contributor
timestamp: May 20, 2019, 07:58:04 am
content: QuoteYou can get an 8 dollar tiny416 nano or add another 2 dollars for a mega4809 nano. Either of those can do debugging to an external target (I'm assuming any updi target would work, but I have not tried it). The $15 SNAP programmer/debugger is supposed to be able to do updi, but I think you need to add a pullup to the data pin (not sure yet). There is also MPLABX as an alternative to Studio.I have both the tiny416 nano and 4809 nano boards, and using them in MPLABX. For the compiler, I am just pointing to the avr bin folder in xc8 for the toolchain which bypasses their xc8-avr restrictions (same compiler, just avoiding the gatekeeper). The debugging is not real great, but works for what is needed (see registers).I was messing with the fuses on the tiny416 and somehow lost my ability to get good usart output (you really can't screw up the clock, so doesn't make any sense). With the debugger I could see everything was ok, and eventually the problem went away (the vcom on the attiny debugger seems little fragile). So there are certainly times when you need the debugger to peek inside.QuoteI moved all my attiny416 c++ code to the mega4809, added my Sprintf and Markup classes from my pic32mm with basically no changes, setup my Usart class to work with them, and can now easily print in nice colors (and don't have to go through whatever needs to be done to get printf to work- maybe its easy on these avr's, I don't know). Compiled with -Os, my simple demo app uses ~5.5kb (2.5kb is app, so adds about ~3k)- that's definitely livable for a 48k part (or a 16kb part), for me anyway.