Microcontroller programming tutorial - Page 1
Forum section: electronics / beginners

author: Rdx
author_rank: Contributor
timestamp: January 11, 2020, 03:19:55 pm
content: Being a mechanical engineer with electronics as a hobby I mainly got into it by Dave's EEVBlog Fundamental Fridays or general other explanations of his.I mean Dave just can show stuff in exactly that way to entertain, but also transmitting the required info in a way that makes a lot of sense to me.Just recently I got more into microcontroller programming as well, and I have some experience with Arduinos (and compatibles).For diving deeper into C or C++ programming on a micro I am lacking some knowledge and experience though. Especially when looking into using serial communication or for example, but also more C-related stuff like pointers.Is there any useful resource of tutorials that not only teach you the C related stuff, but also the hardware close stuff like buffers in serial comm, etc?Some I saw were really very focused on C itself (on a PC), but those just dont really cover a lot of relevant stuff when it comes to micros.Maybe Dave/David could post a series on programming micros, as I think it would really help to see some practical programming and best practices.Lookin forward for any input in this regard

author: rstofer
author_rank: Super Contributor
timestamp: January 11, 2020, 04:24:24 pm
content: You have two distinct issues: C programming and low level programming. They are separable...There must be thousands of tutorial on the C language on the Internet. If nothing else, maybe one of the courses at Udemy. Get a copy of "The C Programming Language" ANSI Edition (you should also have the classic edition at some point). Pointers are both the worst and the most powerful concept in C.Want to learn C programming? Start writing C code. Write lots of C code. Thousands and thousands of lines of C code. Then you will know C programming. The path isn't short.Then there is the matter of low level programming. Jumping past the Arduino library stuff and heading toward bare metal. The other day I was looking at this Instructable and while the result is not especially interesting, to me, what is presented seemed to be an excellent tutorial. At least it shows what is involved:https://www.instructables.com/id/Girino-Fast-Arduino-Oscilloscope/One of the things you should see at first glance is the reliance on the datasheet. Users of Arduino libraries are shielded from the necessity to actually read and understand a datasheet. No longer! Look at all the bit-twiddling in the registers. You get this information from the datasheet. Exercise: Match up the code and comments to the related section in the datasheet until you actually understand what is happening and why.Buffers, in this case circular queues, are covered in EVERY book on data structures and keeping the pointers straight is critical. There MUST be examples of receive data queues on the Internet. They all work the same. There is a HEAD pointer, a TAIL pointer and an array that is conceptually wrapped into a circle.A character comes in, the UART generates an interrupt, the interrupt code fetches the character and stuffs it into the queue and maybe sets a flag or count variable. The mainline code eventually wants to process the character so it extracts it from the queue, updates the tail pointer and the count (if any). The counter idea is in addition to what is usually taught - it is simply a way to find out if there are chars in the queue without have to do arithmetic on the pointers.https://www.studytonight.com/data-structures/circular-queueHere's a free HINT: Disable interrupts in the mainline code before trying to extract a char. You don't want the head pointer changing in the middle of the extraction.Here's another free HINT: Make the queue length a power of 2. 2, 4, ..., 64, 128 and like that up to whatever size you want. It makes doing the wrap-around arithmetic on the pointers as easy as an AND operation to kill the overflow when the pointer runs off the end.Good luck!There are better forums here at EEVblog for programming related issues.

author: rstofer
author_rank: Super Contributor
timestamp: January 11, 2020, 04:34:21 pm
content: You have two distinct issues: C programming and low level programming. They are separable...There must be thousands of tutorial on the C language on the Internet. If nothing else, maybe one of the courses at Udemy. Get a copy of "The C Programming Language" ANSI Edition (you should also have the classic edition at some point). Pointers are both the worst and the most powerful concept in C.Want to learn C programming? Start writing C code. Write lots of C code. Thousands and thousands of lines of C code. Then you will know C programming. The path isn't short.Then there is the matter of low level programming. Jumping past the Arduino library stuff and heading toward bare metal. The other day I was looking at this Instructable and while the result is not especially interesting, to me, what is presented seemed to be an excellent tutorial. At least it shows what is involved:https://www.instructables.com/id/Girino-Fast-Arduino-Oscilloscope/One of the things you should see at first glance is the reliance on the datasheet. Users of Arduino libraries are shielded from the necessity to actually read and understand a datasheet. No longer! Look at all the bit-twiddling in the registers. You get this information from the datasheet. Exercise: Match up the code and comments to the related section in the datasheet until you actually understand what is happening and why.Buffers, in this case circular queues, are covered in EVERY book on data structures and keeping the pointers straight is critical. There MUST be examples of receive data queues on the Internet. They all work the same. There is a HEAD pointer, a TAIL pointer and an array that is conceptually wrapped into a circle.A character comes in, the UART generates an interrupt, the interrupt code fetches the character and stuffs it into the queue and maybe sets a flag or count variable. The mainline code eventually wants to process the character so it extracts it from the queue, updates the tail pointer and the count (if any). The counter idea is in addition to what is usually taught - it is simply a way to find out if there are chars in the queue without havimg to do arithmetic on the pointers.https://www.studytonight.com/data-structures/circular-queueHere's a free HINT: Disable interrupts in the mainline code before trying to extract a char. You don't want the head pointer changing in the middle of the extraction.Here's another free HINT: Make the queue length a power of 2. 2, 4, ..., 64, 128 and like that up to whatever size you want. It makes doing the wrap-around arithmetic on the pointers as easy as an AND operation to kill the overflow when the pointer runs off the end.The head and tail pointers are actually indices into an array, not usually actual address pointers. Suppose your queue had 16 entries (0..15). Then when you wanted to update an index, say tail, the math looks liketail = (tail + 1) & 0x0f;Notice how, if tail was already 15 (0x0f) when it is incremented to 0x10, it is out of range but that's ok because the AND operation throws out the high bit and the result is 0x00 and that's exactly what we want.If your queue had 256 entries the math would betail = (tail + 1) & 0xff;As soon as the value increments to 0x100, it is brought back into range.https://www.programiz.com/c-programming/c-operatorsGood luck!There are better forums here at EEVblog for programming related issues.

author: rstofer
author_rank: Super Contributor
timestamp: January 11, 2020, 04:56:27 pm
content: First exercise: Write the 'enqueue' and 'dequeue' for a queue of size 16. Detect when it is full and when it is empty. Keep a running count of chars in the queue.Test it by trying to insert as many chars as it is supposed to hold. Then see if it pops an error when you try to add another. Note that, conceptually, this would be inside an interrupt handler, how do you proposed to deal with the error? Ignoring it might be acceptable because the queue should have never overflowed. Something else is wrong in the program. Set a permanent "oops" flag and call it a day.Try to remove as many chars as it is supposed to hold - they should be there, you just filled the queue. Then try to remove one more. You should see that the 'count' is zero and return something (0xff?) that indicates the queue is empty.You can write these functions and play with them inside the loop() function of a standard Arduino sketch.You might start with this:https://electrofriends.com/source-codes/software-programs/c/data-structures-c/c-program-to-implement-circular-queue-operations/

author: admiralk
author_rank: Regular Contributor
timestamp: January 12, 2020, 02:05:39 am
content: As rstofer said, C/C++ is C/C++, it really is not any different whether it is for an MCU or PC. At a low level, which Arduino hides from you, you are mostly just flipping or reading bits. The datasheet is your best friend, but @~ 300 pages it is hard to figure out where to look.I like this playlist for reference.Unless you are using the same MCU, the registers will likely be different, but he points you to right place to look instead of just saying do it this way.

author: wilfred
author_rank: Super Contributor
timestamp: January 12, 2020, 06:52:11 am
content: ISTR in a recent tweet or post David has left the building.I found this series of videos on programming embedded systems useful and well structured. You can do a bit of it without a dev board but it uses a cheap TI launchpad later on. I think it was called Stellaris but TI later changed the name to TIVA.https://www.youtube.com/user/StateMachineCOM/featuredor search youtube "quantum Leaps"

author: tggzzz
author_rank: Super Contributor
timestamp: January 12, 2020, 11:06:15 am
content: Quote from: rstofer on January 11, 2020, 04:34:21 pmOr rather you will think you know C programmingConsider all the implementation defined and undefined behaviour; not many people avoid all those pitfalls.<other good points snipped>

author: rstofer
author_rank: Super Contributor
timestamp: January 12, 2020, 04:17:54 pm
content: One thing to consider is to write your own drivers and don't try to generalize it to cover all chips ever produced clear back to the beginning of time. Write exactly the code required for the current application and not a bit more. Code reuse seldom applies to embedded.Next, take a subsystem and beat on it until you have it down cold. Maybe start with simple interrupts responding to a button push on an input and an LED output. That is a worthy example because it requires that a pin be configured as input, another pin as output, the interrupt enabled, the handler needs to be written and linked to the correct place in the vectors (if used, depends on chip), then the handler can do something like toggle an LED (again, a pin needs to be configured) and the main code has to set up all the registers and then loop forever. This is a lot bigger project than it seems. It is an essential project.How about timers? Maybe set up a time to blink an LED without code intervention. Timers are a complex subject and worth the time to study.PWM - absolutely need to study PWM and all of the ramifications. Can it run slow enough for servos? Probably not. But it will run at a rate consistent with small DC motors, like a continuous rotation servo.UART - console input and output. What about a queue on input? A queue on output may not be useful but it presents another complication in turning the interrupt on and off when the queue contains data.Each of these little blocks are worthy of study - one block at a time. It isn't sufficient to find code on the Internet and do the copy/paste bit, the code needs to closely mirror the datasheet. Just like the code in that example I linked above. The code tells a story that can be reread later on to reinforce the topic.There are other peripherals (SPI, I2C, CAN, etc) that also need to be understood. SPI is dead simple, I2C is a trainwreck (in most general case) and I have never used CAN.

author: rstofer
author_rank: Super Contributor
timestamp: January 12, 2020, 04:29:09 pm
content: Quote from: tggzzz on January 12, 2020, 11:06:15 amIt takes experience and none of that comes free. OP, why is 'junk' declared 'volatile'? It took me a second or two this morning to remember why I did that! And, yes, it is absolutely required... As pointed out, there's C and then there's C.Code:[font=courier]void spi_slave_init(void) {volatile int junk;LPC_SC->PCONP |= (1 << PCSPI); // pedantic, should already be powered upLPC_SC->PCLKSEL0 &= ~(3 << PCLK_SPI); // reset clock select bits for PCLK_SPILPC_SC->PCLKSEL0 |= (1 << PCLK_SPI); // set SPI clock to CCLKLPC_PINCON->PINSEL0 |= 0xC0000000; // set up SCK in PINSEL0LPC_PINCON->PINSEL1 |= (3 << 0); // set up SSEL in PINSEL1LPC_PINCON->PINSEL1 |= (3 << 2); // set up MISO in PINSEL1LPC_PINCON->PINSEL1 |= (3 << 4); // set up MOSI in PINSEL1LPC_SPI->SPCR = (1 << SPIE); // set up control registerjunk = LPC_SPI->SPSR; // clear status registerjunk = LPC_SPI->SPDR; // clear data registerLPC_SPI->SPDR = 0x00; // initial return valueLPC_SPI->SPINT = (1 << SPIF); // clear interrupt flagNVIC_SetVector(SPI_IRQn, (uint32_t) &spi_slave_handler);NVIC_SetPriority(SPI_IRQn, 1);NVIC_EnableIRQ(SPI_IRQn);}

author: Rdx
author_rank: Contributor
timestamp: January 12, 2020, 05:00:27 pm
content: Thank you guys for your feedback, I will definitely look into the stuff you posted - will just need some free time in order to get it started.I have used a TI launchpad before, as it came with a (jumper-able) programmer/debugger included on the board and I got the simple stuff to work, so interrupts, RTC, sleepmodes, PWM, I/O, etc all worked. I failed implementing a serial communication which was not a main part of the project actually, so i just skipped that part back then.I have to mention that my prior programming experience is basically limited to php and javascript as I did some web development before. Then got started with Arduinos and C, not exactly copying over ready-made libraries but also not quite the level of C that you would require to use a bare micro./EditAs for that volatile junk, I have no clue why that is.

author: rstofer
author_rank: Super Contributor
timestamp: January 12, 2020, 05:41:41 pm
content: Quote from: Rdx on January 12, 2020, 05:00:27 pmFair enough!Volatile comes up from time to time and the code won't run without it. Here's the issue: You will note that 'junk' isn't returned by the function nor does it appear on the right-hand-side of an expression later on. In other words, it is never used. The friendly C compiler realizes this and optimizes the entire statement away (twice). As a result, the registers are never read and the hardware flags never cleared.By declaring the variable 'volatile', we're telling the compiler to quit thinking and do what it is told; all in support of a hardware design factor.FWIW, variables shared with interrupt routines must also be declared volatile so that the compiler knows that the value can change at any time. This comes up in code loops where the value is used over and over but could change between iterations if an interrupt were involved. If you set a flag in an interrupt routine and test it in mainline code, it must be declared volatile. Just something to remember.This is an example of C code which is very likely unique to embedded programming. In a more general purpose program the issue is unlikely to occur so it isn't talked about very much.There's C and then there's C. Note that C, as a language, has the facilities for dealing with these kinds of things. That's why most, if not all, low level code is written in C.

author: Rdx
author_rank: Contributor
timestamp: January 12, 2020, 05:59:17 pm
content: Yes, that makes a lot of sense!I guess C is from a time when programming on a PC also still meant to stick close to the hardware. (Which I am not exactly used to in php or javascript, both being interpreted)Those small details are one reason for my question in here: Does it make sense to dive deep into a "regular" C course that will most likely focus more on PC operating system dependent calls and not really at those microcontroller specific stuff?

author: rstofer
author_rank: Super Contributor
timestamp: January 12, 2020, 06:19:24 pm
content: Quote from: Rdx on January 12, 2020, 05:59:17 pmYes it does! All of those -> operators are pointing to a field in a structure defined in a header somewhere. A lot of the code dealing with hardware registers will be dealing with structs and typedefs. These don't tend to come up as often in general purpose programming but they're all over the place in embedded programming.The thing is, these constructs aren't unique to embedded and can be explored at the PC level as well. You would usually see a struct used as a Pascal 'record'. The various fields within an 'employee' record would be reached using the -> operators. As well, it is likely that you won't deal with a struct directly but rather as a struct reached by a pointer. A linked list of structs would actually be a linked list of pointers to structs allocated with 'malloc()' when the item is added. This is exactly the same in C++ using new().I tend to avoid dynamic allocation in embedded code because I worry about the collision when the heap, growing upward in memory, collides with the stack growing downward. To this end, I avoid string.h at every opportunity. printf() also uses the string library so I avoid that as well. If I see sbrk() as undefined, or worse, defined, in a linker map, I get very nervous. sbrk() is the code that does the actual allocation. This is often left as a function for the writer to create based on how they want to implement dynamic allocation and there will be a linker error if it isn't defined. OTOH, if it is defined in some library somewhere, it is probably incorrect for your environment so even when defined, there is still a problem.Where C gets unique to embedded is at the point where you need to dig out a datasheet.

author: rstofer
author_rank: Super Contributor
timestamp: January 12, 2020, 06:32:09 pm
content: If we consider 1981 as the introduction of the PC then C, introduced in 1972, is a lot older. It was used on minicomputers and mainframes long before it came to the PC universe. In fact, it was probably running on my Altair 8800 in the late '70s. I don't recall the exact date or the variant.FORTRAN and COBOL are even older. FORTRAN was introduced in 1957 and COBOL was introduced in 1959. Both are still in use. It is said that every ATM transaction, at one point or another, is dealt with in COBOL. Banks are actually hurting for COBOL programmers because the universities don't teach it and the oldtimers are dying off. FORTRAN (now Fortran) is still in common use in the scientific community. I still use it for various math demos and I've been using it since 1970.Fun fact, the language PL/M, created for Intel and their new 8080 uCs was written in Fortran.

author: tggzzz
author_rank: Super Contributor
timestamp: January 12, 2020, 06:36:00 pm
content: Quote from: rstofer on January 12, 2020, 05:41:41 pmAs an example of how many peopleincorrectlythink they understand C, consider that as late as 2004 it was beneficial for this paper to be written "Threads cannot be implemented as a library", Boehm.http://www.hpl.hp.com/techreports/2004/HPL-2004-209.pdfEvan a decade later many people didn't understand!I believe the latest standards do finally (i.e. after 40 tears!) provide the necessary guarantees, but you have to be allowed to use them and I'll bet many compilers don't avoid "unpleasant surprises" in the generated code.