Jump to content
 

Leaderboard


Popular Content

Showing content with the highest reputation since 12/23/2018 in all areas

  1. 3 points
    https://amzn.to/2Vibb9c After posting the negative review on the other book here I realized that it is not much help unless you provide an alternative! A couple of years ago I stumbled upon this book by Elicia White. Ever since I have recommended it as a must read to every new member of my team, even if they had years of experience they always reported back that they learned something valuable from reading it. I stumbled upon this book looking for something on Design Patterns in Embedded Systems, and in terms of that this was not what I was looking for, there is barely a mention of design patterns in the book, but I was pleasantly surprised by what I did find. I like where the book starts of, explaining the value of Design and Architecture and why this is where you should start with your project. She moves on to basic I/O and Timers which I think goes together pretty well, but importantly she covers the important use cases and patterns quite nicely and points out all of the most common pitfalls people fall into. The next chapter, “Making the Flow of Activity” covers the main paradigms for Embedded Systems like superloop and event driven approaches and even covers table driven state machines and even interrupts, I particularly liked the section called “How NOT to use interrupts”. Next chapter “Doing more with less” was a pretty good introduction to the methods you have to learn to tell how much RAM and FLASH you are using, and she covers important concepts like not using malloc. The chapter on Math is sure to teach even experienced engineers a couple of new tricks and the last chapter on power consumption is practical and well done. Overall I felt like this was a great book for beginners and a pretty good recap even for experienced engineers who will no doubt also learn a couple of new tricks after going through this book.
  2. 2 points
    Remember, this timer counts up.and you get the interrupt when it rolls over. To interrupt at a precise interval, you must compute the number of "counts" required for that interval and then subtract from 65535 to determine the timer load value. void setTimer(unsigned int intervalCounts) { TMR1ON = 0; TMR1 = 65535 - intervalCounts; TMR1ON = 1; } By turning the timer off and then setting the counts and restoring the timer, you can be sure that you will not get unexpected behavior if the timer value is written in an unexpected order. I will cover this topic in the next blog post on timers.
  3. 2 points
    Assembly language may no longer be the mainstream way to write code for embedded systems, however it is the best way to learn how a specific CPU works without actually building one. Assembly language is simply the raw instruction set of a specific CPU broken into easy to remember pneumonics with a very basic syntax. This enables you full control of everything the CPU does without any translation provided by a compiler. Sometimes this is the only reasonable way to do something that cannot be represented by a higher level language. Here is an example from a project I was working on today. Today I wanted to create a 128-bit integer (16 bytes). That means I will need to add, subtract, multiply, etc. on my new 128-bit datatype. I was writing for a 32-bit CPU so this would require 4 32-bit values concatenated together to form the 128-bit value. If we consider the trivial problem of adding two of these numbers together, lets consider the following imaginary code. int128_t foo = 432123421234; int128_t bar = 9873827438282; int128_t sum = foo + bar; But my 32-bit CPU does not understand int128_t so I must fake it. How about this idea. int32_t foo[] = {0x00112233, 0x44556677, 0x8899AABB, 0xCCDDEEFF}; int32_t bar[] = {0xFFEEDDCC, 0xBBAA9988, 0x77665544, 0x33221100}; int32_t sum[4]; sum[0] = foo[0] + bar[0]; sum[1] = foo[1] + bar[1]; sum[2] = foo[2] + bar[2]; sum[3] = foo[3] + bar[3]; But back in grade school I learned about the 10's place and how I needed to carry a 1 when the sum of the one's place exceeded 10. It seems that it is possible that FOO[0] + BAR[0] could exceed the maximum value that can be stored in an int32_t so there will be a carry from that add. How do I add carry into the next digit? In C I would need to rely upon some math tricks to determine if there was a carry. But the hardware already has a carry flag and there are instructions to use it. We could easily incorporate some assembly language and do this function in the most efficient way possible. So enough rambling. Let us see some code. First, we need to configure MPLAB to create an ASM project. Create a project in the normal way, but when you get to select a compiler you will select MPASM. Now you are ready to get the basic source file up and running. Here is a template to cut/paste. #include "p16f18446.inc" ; CONFIG1 ; __config 0xFFFF __CONFIG _CONFIG1, _FEXTOSC_ECH & _RSTOSC_EXT1X & _CLKOUTEN_OFF & _CSWEN_ON & _FCMEN_ON ; CONFIG2 ; __config 0xFFFF __CONFIG _CONFIG2, _MCLRE_ON & _PWRTS_OFF & _LPBOREN_OFF & _BOREN_ON & _BORV_LO & _ZCD_OFF & _PPS1WAY_ON & _STVREN_ON ; CONFIG3 ; __config 0xFF9F __CONFIG _CONFIG3, _WDTCPS_WDTCPS_31 & _WDTE_OFF & _WDTCWS_WDTCWS_7 & _WDTCCS_SC ; CONFIG4 ; __config 0xFFFF __CONFIG _CONFIG4, _BBSIZE_BB512 & _BBEN_OFF & _SAFEN_OFF & _WRTAPP_OFF & _WRTB_OFF & _WRTC_OFF & _WRTD_OFF & _WRTSAF_OFF & _LVP_ON ; CONFIG5 ; __config 0xFFFF __CONFIG _CONFIG5, _CP_OFF ; GPR_VAR UDATA Variable RES 1 SHR_VAR UDATA_SHR Variable2 RES 1 ;******************************************************************************* ; Reset Vector ;******************************************************************************* RES_VECT CODE 0x0000 ; processor reset vector pagesel START ; the location of START could go beyond 2k GOTO START ; go to beginning of program ISR CODE 0x0004 ; interrupt vector location ; add Interrupt code here RETFIE ;******************************************************************************* ; MAIN PROGRAM ;******************************************************************************* MAIN_PROG CODE ; let linker place main program START ; initialize the CPU LOOP ; do the work GOTO LOOP END The first thing you will notice is the formatting is very different than C. In assembly language programs the first column in your file is for a label, the second column is for instructions and the third column is for the parameters for the instructions. In this code RES_VECT, ISR, MAIN_PROG, START and LOOP are all labels. In fact, Variable and Variable2 are also simply labels. The keyword CODE tells the compiler to place code at the address following the keyword. So the RES_VECT (reset vector) is at address zero. We informed the compiler to place the instructions pagesel and GOTO at address 0. Now when the CPU comes out of reset it will be at the reset vector (address 0) and start executing these instructions. Pagesel is a macro that creates a MOVLP instruction with the bits <15:11> of the address of START. Goto is a CPU instruction for an unconditional branch that will direct the program to the address provided. The original PIC16 had 35 instructions plus another 50 or so special keywords for the assembler. The PIC16F1xxx family (like the PIC16F18446) raises that number to about 49 instructions. You can find the instructions in the instruction set portion of the data sheet documented like this: The documentation shows the syntax, the valid range of each operand, the status bits that are affected and the work performed by the instruction. In order to make full use of this information, you need one more piece of information. That is the Programmers Model. Even C has a programmers model but it does not always match the underlying CPU. In ASM programming the programmers model is even more critical. You can also find this information in the data sheet. In the case of the PIC16F18446 it can be found in chapter 7 labeled Memory Organization. This chapter is required reading for any aspiring ASM programmers. Before I wrap up we shall modify the program template above to have a real program. START banksel TRISA clrf TRISA banksel LATA loop bsf LATA,2 nop bcf LATA,2 GOTO loop ; loop forever END This program changes to the memory bank that contains TRISA and clears TRISA making all of PORT A an output. Next is changes to the memory bank that contains the LATCH register for PORT A and enters the loop. BSF is the pneumonic for Bit Set File and it allows us to set bit 2 of the LATA register. NOP is for No OPeration and just lets the bit set settle. BCF is for Bit Clear File and allows us to clear bit 2 and finally we have a branch to loop to do this all over again. Because this is in assembly we can easily count up the instruction cycles for each instruction and determine how fast this will run. Here is the neat thing about PIC's. EVERY instruction that does not branch takes 1 instruction cycle (4 clock cycles) to execute. So this loop is 5 cycles long. We can easily add instructions if we need to produce EXACTLY a specific waveform. I hope this has provided some basic getting started information for assembly language programming. It can be rewarding and will definitely provide a deeper understanding on how these machines work. Good Luck
  4. 2 points
    Time for part 2! Last time, I gave you the homework of downloading and installing MPLAB and finding a Curiosity Nano DM164144 . Once you have done your homework, it is time for STEP 3, get that first project running. Normally my advice would be to breakout Mplab Code Configurator and get the initialization code up and running, but I did not assign that for homework! So we will go old school and code straight to the metal. Fortunately, our first task is to blink an LED. Step 1: Find the pin with the LED. A quick check of the schematic finds this section on page 3. This section reveals that the LED is attached to PORT A bit 2. With the knowledge of the LED location, we can get to work at blinking the LED. The first step is to configure the LED pin as an output. This is done by clearing bits in the TRIS register. I will cheat and simply clear ALL the bits in this register. Next we go into a loop and repeatedly set and clear the the PORT A bit 2. #include <xc.h> void main(void) { TRISA = 0; while(1) { PORTA = 0; PORTA = 0x04; } return; } Let us put this together with MPLAB and get it into the device. First we will make a new project: Second, we will create our first source file by selecting New File and then follow the Microchip Embedded -> XC8 Compiler -> main.c give your file a name (I chose main.c) And you are ready to enter the program above. And this is what it looks like typed into MPLAB. But does it work? Plug in your shiny demo board and press this button: And Voila!, the LED is lit... but wait, my code should turn the LED ON and OFF... Why is my LED simply on? To answer that question I will break out my trusty logic analyzer. That is my Saleae Logic Pro 16. This device can quickly measure the voltage on the pins and draw a picture of what is happening. One nice feature of this device is it can show both a simple digital view of the voltage and an analog view. So here are the two views at the same time. Note the LED is on for 3.02µs (microseconds for all of you 7'th graders). That is 0.00000302 seconds. The LED is off for nearly 2µs. That means the LED is blinking at 201.3kHz. (201 thousand times per second). That might explain why I can't see it. We need to add a big delay to our program and slow it down so humans can see it. One way would be to make a big loop and just do nothing for a few thousand instructions. Let us make a function that can do that. Here is the new program. #include <xc.h> void go_slow(void) { for(int x=0;x<10000;x++) { NOP(); } } void main(void) { TRISA = 0; while(1) { PORTA = 0; go_slow(); PORTA = 0x04; go_slow(); } return; } Note the new function go_slow(). This simply executes a NOP (No Operation) 10,000 times. I called this function after turning the LED OFF and again after turning the LED ON. The LED is now blinking at a nice rate. If we attach the saleae to it, we can measure the new blink. Now is is going at 2.797 times per second. By adjusting the loop from 10,000 to some other value, we could make the blink anything we want. To help you make fast progress, please notice the complete project Step_3.zip attached to this post. Next time we will be exploring the button on this circuit board. For your homework, see if you can make your LED blink useful patterns like morse code. Good Luck Step_3.zip
  5. 2 points
    This is a "must read" list for Embedded Software Engineers. If we missed one please let us know in the comments! Please make a contribution to help us improve this list by leaving a comment. We are particularly interested in books we missed when compiling the list. If you leave a comment and we agree it will be added promptly. Here is "The List" in short form conveniently made up as Amazon.com links and remember if you follow any of these links before shopping on Amazon they will make a contribution to help us support this site! Scroll down for a more detailed list with cover pictures. The C Programming Language, 2nd Edition Design Patterns: Elements of Reusable Object-Oriented Software Code Complete: A Practical Handbook of Software Construction, Second Edition Making Embedded Systems: Design Patterns for Great Software Software Estimation: Demystifying the Black Art (Developer Best Practices) The Art of Computer Programming, Volumes 1-4A Boxed Set The Mythical Man-Month: Essays on Software Engineering, Anniversary Edition Refactoring: Improving the Design of Existing Code (2nd Edition) UML Distilled: A Brief Guide to the Standard Object Modeling Language (3rd Edition) Clean Code: A Handbook of Agile Software Craftsmanship Software Architecture in Practice: Software Architect Practice_c3 (SEI Series in Software Engineering) 97 Things Every Programmer Should Know: Collective Wisdom from the Experts Programming 32-bit Microcontrollers in C: Exploring the PIC32 (Embedded Technology) The Pragmatic Programmer: From Journeyman to Master Compilers: Principles, Techniques, and Tools (2nd Edition) Applied Cryptography: Protocols, Algorithms and Source Code in C Structure and Interpretation of Computer Programs - 2nd Edition Introduction to Algorithms, 3rd Edition Honorable Mentions. Books not quite worthy of "The List" but still important recommended reading. The C99 Standard, really, you should have read this already if you are going to program anything embedded! (PDF link to the draft) Zen and the Art of Motorcycle Maintenance: An Inquiry into Values Guide to the Software Engineering Body of Knowledge (SWEBOK(R)): Version 3.0 A Guide to the Project Management Body of Knowledge (PMBOK® Guide)–Sixth Edition Happy Reading! .tg td { font-family: Arial, sans-serif; font-size: 14px; padding: 10px 5px; overflow: hidden; word-break: normal; } .tg .tg-yqpd_img { width: 200px; height: 200px; } .tg-yqpd_img img { border: 1px solid #ddd; border-radius: 4px; padding: 5px; } .tg .tg-yqpd { border-color: #ffffff; text-align: left; vertical-align: top } 1. The C Programming Language, 2nd Edition This is our No1. must read book if you are going to be doing embedded programming. Written by Kerninghan and Ritchie, the inventors of the C language. Learn how the C language was designed to work and why. It is packed with numerous excercises to ensure you understand every concept. You really should keep this on your desk as a reference if you ever get stuck. 2. Design Patterns: Elements of Reusable Object-Oriented Software Design patterns is how we communicate as Software Engineers about architectural details. If a building architect said the building should be "Tuscan Style" this would mean a wealth of things to people on the project about shape, size, colors, building materials etc. Design patterns form a similar language for Software Engineers and is a crucial tool in your arsenal. This is the original book known as the Gang of Four book or GOF for short. A must read before you venture further into other design patterns books. 3. Code Complete: A Practical Handbook of Software Construction, Second Edition This is by far the best all-round book about software development. It covers all aspects of Software Engineering to some degree, it is very thorough and a must-read just to make sure you know what is out there. 4. Making Embedded Systems: Design Patterns for Great Software This is by far the best introductory book we have seen, but it has an equal amount of gems in there for experienced campaigners, especially in the later sections on optimization (Doing less with more) and math which covers floating point issues and precision. We love the section "How NOT to use interrupts", and the one on Bootloaders for example. 5. Software Estimation: Demystifying the Black Art (Developer Best Practices) This is just a brilliant book on software project management. What makes it great is how it covers 100% of the foundational theory on estimation and planning and also covers the personal side. We love the scripts and dialogs coaching you how to present your estimates to management in such a way that they will not force unreasonable deadlines upon your team. McConnell explains that the "Science of Estimation" is mathematically intensive, uses all kinds of really complex formulae and can get estimates in the order of 5%. He then explains that this book is NOT about the science, it is more about what he calls the "Art of Estimation" which will not get you to 5%, but it will be good enough for most projects to be managed. 6. The Art of Computer Programming, Volumes 1-4A Boxed Set Computer programming is based on a lot of science. Without a solid knowledge of data structures and algorithms programming a microcontroller system is like trying to do woodwork with your bare hands scratching away with your nails. You really have to cover these fundamentals and Knuth is the all time master on teaching these fundamentals. 7. The Mythical Man-Month: Essays on Software Engineering, Anniversary Edition This is one of those books which is quoted so often you will quickly give away the fact that you are the only one in the room who has not read this. Don't be that guy! And remember, adding more people to a project when it is late will make it even later, and putting 9 women on the job cannot create a baby in 1 month! But seriously the best part of this book is probably the capter "The Surgical Team" which really explains beautifully the core principles SCRUM and small Agile teams are built on, written decades before the rest of us realized that Fred Brooks was right all along! 8. Refactoring: Improving the Design of Existing Code (2nd Edition) Martin Fowler is probably the greatest mind in Computer Science today and he does not get the credit he deserves for it. Read this book and you will find out first hand just how much we can learn from this guy. I am not kidding if I say that his Event Sourcing Architectural pattern is THE ONLY way to go for even moderately complex embedded systems. This book covers the fundamentals you need to be Agile, get your code out there quickly so you can test your requirements and get customer feedback and then apply this book to refactor your existing code in such a way that your architecture improves and you stay on the blue line (Design Stamina Hypothesis - Google it!). 9. UML Distilled: A Brief Guide to the Standard Object Modeling Language (3rd Edition) Another Martin Fowler book. Especially in Embedded Systems we see time and again that not enough design is happening. The old saying you have to solve the problem first and then write the code is not taught enough! This book will give you all the tools you need to create the sequence diagrams, deployment diagrams and static structure diagrams you need to communicate and "reason about the system" (yes that is indeed me quoting from "Software Architecture in Practice"). 10. Clean Code: A Handbook of Agile Software Craftsmanship Uncle Bob is just a legend when it comes to the tactics of writing sofware. We are a big fan of the SOLID principles and almost everything he covers in this book can make you a better coder. Also check out his website and training videos, most of them will teach you something new and they are all entertaining as hell. 11. Software Architecture in Practice: Software Architect Practice_c3 (SEI Series in Software Engineering) Those who were lucky enough to study computer science will already have this book as every Computer Science course worth it's salt uses this as the textbook for the Architecture course. We really love how this book enumerates and covers the pros and cons of the majority of high-level architectural patterns we use in computer systems today. 12. 97 Things Every Programmer Should Know: Collective Wisdom from the Experts I discoved Kevlin Henney only recently but I love the ideas he is teaching. Things like reminging us the software is written for people to read and understand, and concepts of signal to noise ratio's in code. He explains that spaces are indeed superior to tabs and why. This book is a great collection of almost 100 tactics you can apply on a daily basis to improve your code. If you want to stand on the shoulders of giants it is critical that you heed their advice and this is a great collection of expert advice. 13. Programming 32-bit Microcontrollers in C: Exploring the PIC32 (Embedded Technology) When it comes to the PIC32 there is no better way to discover how it works and how to program it than this book. The fact that he actually works for Microchip gives Lucio amazing depth of insight into how this device was designed to be used and what it's strengths and weakneses are. In fact if you want a book to learn about PIC microcontrollers we recommend you search for Lucio Di Jasio on Amazon and pick the one for your platform! 14. The Pragmatic Programmer: From Journeyman to Master This is another one of those classic books that keeps popping up on every "best programming books" list. This book covers loads of practical advice on how to make your code better in general. Ward Cunningham reviewed it and concluded that "The Pragmatic Programmer illustrates the best practices and major pitfalls of many different aspects of software development. Whether youre a new coder, an experienced programmer". We agree! 15. Compilers: Principles, Techniques, and Tools (2nd Edition) Ok, we know that if you want to learn how to write a compiler today there are better texts than this, but this is still the book every compiler designer recommends at some point. What I love about this book is that it explains pretty early on what the compilation process looks like, which leads to understanding the reasons behind why compilers do things that can seem silly but are actually essential to produce working code consistently. It is always going to help you be a better programmer if you have at least a rundementary understanding of how compilers and linkers work, and this is a great place to start. 16.Applied Cryptography: Protocols, Algorithms and Source Code in C Security is getting more and more inportant in our connected world. Don't even try to do any security yourself unless you have read this book cover to cover. I am serious - don't! This is really the best place to learn the basic fundamentals about information security, Schneier is not only a world-renowned expert on the topic, but he has a talent for explaining an extremely complex topic in a truly accessible way. 17. Structure and Interpretation of Computer Programs - 2nd Edition Brought to you by the "MIT Electrical Engineering and Computer Science" team this is a fantastic book about the science of programming. If you are a "Tinkerer" who is happily surprized when your code runs and leaves comments like "no idea why this works" in your code this is probably not going to be for you. If you want to write robust code in a systematic way using Science and Engineering this will be a must read. If you tought yourself how to program and only knows Imperative programming then this book will go a ling way to filling in your blind spots, introducing you to a wealth of knowledge you never knew was out there. 18. Introduction to Algorithms, 3rd Edition Excellent textbook on algorithms, covering subjects from the basics like big "O" notation to advanced Boas trees and multithreaded algorithms. This book is used as textbook for the algorithms classes at universities like MIT, CMU, Stanford and Yale. Please help us improve this list by posting feedback in the comments below. Let us know if new editions are published, links are dead etc.
  6. 2 points
    Plus you have colleges still making their students use C18... https://www.microchip.com/forums/m1083909.aspx
  7. 2 points
    https://amzn.to/2BR0Xnr (Link to the book on Amazon) I have been a fan of Michael Barr since I read about his work with the Toyota Unintended Acceleration case where his team was able to identify and reproduce some very exotic conditions where automotive software failed. (Every Embedded Engineer should at least read about that case, the link is a good summary) I recently noticed a book co-authored by Barr called “Programming Embedded Systems” and I was excited to get my hands on it as I expected to learn a couple of profound tricks from a master (the book itself is pretty old, 2nd edition published in 2006). Unfortunately the book did not live up to my expectations. Since I am often asked which book I would recommend to start with, I was interested in taking this one for a test drive. Looking over the table of contents I thought that this may be a good beginner’s book, introducing embedded systems. Fair enough. Table of Contents: Introduction Getting to Know the Hardware Your First Embedded Program Compiling Linking and Locating Downloading and Debugging Memory Peripherals Interrupts Putting it All Together Operating Systems eCOS Examples Embedded Linux Examples Extending Functionality Optimization Techniques The first thing I found odd was the choice of development platform. The book is very tightly coupled to the Arcom VIPER-Lite development kit which features a 200MHz PXA255 Xscale processor (based on the ARM v.5TE architecture). This is a PC/104 form factor board and boasts 64MB of SDRAM and 16MB of “ROM” of which 1MB is dubbed as “BOOT ROM”. In fact it has almost every property which the book uses in Chapter 1 to describe what would NOT be an embedded system! I quote "The design of an embedded system to perform a dedicated function is in direct contrast to that of the personal computer. It too is comprised of computer hardware and software and mechanical components (disk drives, for example). However, a personal computer is not designed to perform a specific function. Rather, it is able to do many different things. Many people use the term general-purpose computer to make this distinction clear.". Looking at the VIPER PC104 computer it seems to me very much general purpose and I would by that definition classify the platform chosen here as a "general-purpose computer" instead. Besides, in my world of 8-bit microcontrollers where 16KB of FLASH and 1KB of RAM is large I do not easily consider a machine with 64MB of SDRAM as an embedded system. I do hate to make the distinction between the two myself though (would you consider e.g. your cellular phone an embedded system? It has more power than the original IBM PC or the Apple II after all!). But to be fair the concepts do transfer and the board at least does not have a Keyboard, mouse and VGA port, so I will go along with that if you could get your hands on one! I wanted to get at least the specs for the board only to find that the board is no longer available from Eurotech (it took me some time to realize that in 2007 (11 years ago - 1 year after publication of the 2nd edition of this book) Arcom was re-named to Eurotech). In fact it looks like this board has become entirely obsolete and unobtainable which is a pity as the book’s examples are so tightly coupled to this board that you would not be able to “follow along” if you did not have it at hand. If only they had just used a simple 8051, AVR or PIC or even an Arduino board instead of this very specialized and unique board nobody has ever heard of! Over all the book seems to try and do way too much in too little depth, so we are covering the very basics like introducing what is an embedded system and what is a peripheral through to Embedded Linux and Real Time Systems in one book of 260 odd pages. The result is that the book barely scratches the surface on each topic and has almost no meat behind any of these topics. O’Reilly has entire books dedicated to topics which are dismissed in one paragraph in this one. I was e.g. amused in Chapter 2 when there was a section on “Schematic Fundamentals” showing the symbols used for a resistor, capacitor and a diode and an introduction to what a timing diagram looks like. Chapter 7 explains how bitwise AND, OR and XOR can be used to manipulate bits, etc. but in Chapter 9 the examples start using function pointers without skipping a beat or explaining what a pointer is or how they can point at functions, just assuming the same reader who needed to be explained how to mask bits will be adept at using pointers ... There were also a number of subjective “facts” in the book which I do not quite agree with, e.g. this table: I seriously question the numbers in this table, e.g. I would say from experience that the number of units sold goes up as systems become smaller and simpler. I think I would go out of business rather quickly if I did $100K developments for products selling at $10 a piece and never sold more than 100 pieces of these. I would also say that my car’s ABS controller (likely on the lower end of that resource scale) must be fail-proof, while my Cellular Phone (on the High scale) requires me restarting it regularly, and I would say my ABS computer would have a life of 10’s of years while my phone is hardly going to live for more than 2. So let’s just agree that plenty of embedded systems below 64KB are safety critical and sells millions of units and use nanowatts of electricity so I disagree with the last 4 rows there. It seems like this book is trying to cater for fairly experienced programmers as well as complete novices. My opinion is that any book should always pick a specific audience and speak to that audience well. If you try to speak to everyone you will please nobody. If the reader is someone so green that they cannot mask in bits and do not know how to compile and link their program the same book is not appropriate for showing people real-time OS concepts, schedulers and function pointers. I think the book leaps forward too far too often in a way that newcomers will feel that half of the book was so basic that they already know this, and the other half is so advanced that it would be out of their reach to grasp. And for experts the advanced parts of the book are so shallow that they would get very little value out of it, and of course the other half would be a waste of time. So in summary I think the book covers a lot of ground by scratching the surface on just too many concepts without getting deep enough into any of them to really teach anybody anything new about Embedded Programming. I always make sure to read a couple of the good and bad reviews on Amazon and the reviews there were very much in line with my experience. Like one of the reviewers this was the first time I bought an O’Reilly book where I really felt that it was not worth the money. One reviewer I think summed it up the best “... To experienced programmer, this is never the book for you. To beginner, maybe this is easier to understand, but i really dont think this will help you in EMBEDDED C programming...” you can read his full review as well as the others here https://www.amazon.com/review/R10VHMT76YWZVV/ref=cm_cr_srp_d_rdp_perm?ie=UTF8&ASIN=1565923545
  8. 2 points
    I think you will be pretty happy with the PIC16F1xxxx for that kind of project. The main advantage of the K42 would be the additional RAM and FLASH. I totally agree with @N9WXU there - when you are adding wifi and communication protocols that counts for a lot and you may end up constrained if you are trying to do it all on the smaller PIC16. If that is the next step you may find yourself porting the code right after writing it, which is never a good timeline. The K42 has up to 128KB of FLASH and 8KB of RAM while the PIC16 is capped at 56KB of FLASH and 4KB of RAM. Although it should be fairly straightforward to port the PIC16F18877 code to any K42 if it is written in C and if you use something like MCC to generate the hardware code for you. If you need help with that part please do give us a shout, we can definitely lend a hand with that part!
  9. 2 points
    Everything in your list of requirements looked pretty reasonable for the PIC16 until you said wireless. For wireless, a lot depends upon the details. Bluetooth, WiFi, Subgig, LoRa, etc will all require some kind of RF module. Each RF module generally comes with a co-processor that handles the heavy lifting for the wireless protocol, however... Each wireless module will still have demands upon the host processor. For example, the WINC1500 wifi driver on an ATMEGA4808 took around 10k. After reviewing the driver, it would be possible to shrink the code quite a lot but it would be at the expense of supporting the code for the duration. That is not to suggest that wirelesss is not appropriate for a PIC16, it is just that wireless often comes with additional expectations that are not immediately apparent. Here are a few: 1) Security, most wireless applications require a secure and authenticated connection to the host. 2) phone interface, many wireless applications seem to be tied to iOS and Android. This generally implies BLE4.2 but could mean wifi. 3) IoT. Obviously wireless existed before the internet, most of the wireless customer discussions ended up being Internet of Things discussions. This drove the security requirements. 4) Over the air update. because security and wireless standards are moving fast, most customers end up requiring the ability to update the software to adapt with the times. When you start going through these requirements you can quickly find yourself in a 128k MCU and that will be a PIC18 or AVR in 8-bit land. A reasonable rule of thumb with these kinds of applications is the PIC16/18 will require 2x the FLASH as an AVR for the same application. The details on why this is true would be a future technical post.
  10. 2 points
    Hi Keith, Welcome to our forum! We aim to please 🙂 Short answer is that I think the pic16F18877 is an excellent choice to replace the 16F887. It can pretty much do everything the 887 can do better since it can go 50% faster, and has a lot more to offer like CLC’s, PPS on the pins and a host of other features. Perhaps you can give us a little more detail on what you are aiming to do and how. Will you be using XC8 and using C for this and what are you looking for in terms of analog? There are also a lot of interesting goto parts in the PIC18 family, in particular look at the 18FxxK42, it has interesting additional features like DMA and vectored interrupts which are not on the PIC16F1 parts. They also have more flash and RAM if that is what you need.
  11. 2 points
    Even better. The USB programming interface code is all on GitHub HERE-> https://github.com/MicrochipTech/XPRESS-Loader
  12. 1 point
    It has been a busy few weeks but finally I can sit down and write another installment to this series on embedded programming. Today's project will be another peripheral and a visualization tool for our development machines. We are going to learn about the UART. Then we are going to write enough code to attach the UART to a basic C printing library (printf) and finally we are going to use a program called processing to visualize data sent over the UART. This should be a fun adventure so let us get started. UART (USART?) The word UART is an acronym that stands for Universal Asynchronous Receiver Transmitter. Some UART's have an additional feature so they are Universal Synchronous Asynchronous Receiver Transmitter or USART. We are not going to bother with the synchronous part so let us focus on the asynchronous part. The purpose of the UART is to send and receive data between our micro controller and some other device. Most frequently the UART is used to provide some form of user interface or simply debugging information live on a console. The UART signaling has special features that allow it to send data at an agreed upon rate (the baud rate) and an agreed upon data format (most typically 8 data bits, no parity and 1 stop bit). I am not going to go into detail on the UART signaling but you can easily learn about it by looking in chapter 36 of the PIC16F18446 datasheet. Specifically look at figure 36-3 if you are interested. UARTs are a device that transfer bytes of data (8-bits in a byte) one bit at a time across a single wire (one wire for transmit and one for receive) and a matching UART puts the bits back into a byte. Each bit is present on the wire for a brief but adjustable amount of time. Sending the data slowly is suitable for long noisy wires while sending the data fast is good for impatient engineers. A common baud rate for user interfaces is 9600 baud which is sends 1 letter every millisecond (0.001 seconds). Many years ago, before the internet, I had a 300 baud modem for communicating with other computers. When I read my e-mail the letters would arrive slightly slower than I could read. Later, after the internet was invented and we used modems to dial into the internet, I had a 56,600 baud modem so I could view web-pages and pictures more quickly. Today I use UARTS to send text data to my laptop and with the help of the processing program we can make a nice data viewer. Enough history... let us figure out the UART. Configuring the UART The UART is a peripheral with many options so we need to configure the UART for transmitting and receiving our data correctly. To setup the UART we shall turn to page 571 of our PIC16F18446 data sheet. The section labeled 36.1.1.7 describes the settings required to transmit asynchronous data. And now for some code. Here is the outline of our program. void init_uart(void) { } int getch(void) { } void putch(char c) { } void main(void) { init_uart(); while(1) { putch(getch()+1); } } This is a common pattern for designing a program. I start by writing my "main" function simply using simple functions to do the tricky bits. That way I can think about the problem and decide how each function needs to work. What sort of parameters each function will require and what sort of values each function will return. This program will be very simple. After initializing the UART to do its work, the program will read values from the UART, add 1 to each value and send it back out the UART. Since every letter in a computer is a simple value it will return the next letter in each series that I type. If I type 'A' it will return 'B' and so on. I like this test program for UART testing because it will demonstrate the UART is working and not simply shorting the receive pin to the transmit pin. Now that we have an idea for each function. Let us write them one a time. init_uart() This function will simply write all the needed values into the special function registers to activate the UART. To do that, we shall follow the checklist in the data sheet. We need to configure both the transmit and the receive function. Step 1 is to configure the baud rate. We shall use the BRG16 because that mode has the simplest baud rate formula. Of course, it is even easier when you use the supplied tables. I always figure that if you are not cheating, you are not trying so lets just find the 32MHz column in the SYNC=0, BRGH=0, BRG16 = 1 table. I like 115.2k baud because that makes the characters send much faster. So the value we need for the SPBRG is 16. Step 2 is to configure the pins. We shall configure TX and RX. To do that, we need the schematics. The key portion of the schematics is this bit. The confusing part is, the details of the TX and RX pin. If I had a $ for every time I got these backwards, I would have retired already. Fortunately the team at Microchip who drew these schematics were very careful to label the CDC TX connecting to the UART RX and the CDC RX connecting to the UART TX. They also labeled the UART TX as RB4 and UART RX as RB6. This looks pretty simple. Now we need to steer these signals to the correct pins via the PPS peripheral. NOTE: The PPS stands for Peripheral Pin Select. This feature allows the peripherals to be steered to almost any I/O pin on the device. This makes laying out your printed circuit boards MUCH easier as you can move the signals around to make all the connections straight through. You can also steer signals to more than one output pin enabling debugging by steering signals to your LED's or a test point. After steering the functions to the correct pins, it is time to clear the ANSEL for RB6 (making the pin digital) and clear TRIS for RB4 (making the pin an output). The rest of the initialization steps involve putting the UART in the correct mode. Let us see the code. void init_uart(void) { // STEP 1 - Set the Baud Rate BAUD1CONbits.BRG16 = 1; TX1STAbits.BRGH = 0; SPBRG = 16; // STEP 2 - Configure the PPS // PPS unlock sequence // this should be in assembly because the hardware counts the instruction cycles // and will not unlock unless it is exactly right. The C language cannot promise to // make the instruction cycles exactly what is below. asm("banksel PPSLOCK"); asm("movlw 0x55"); asm("movwf PPSLOCK"); asm("movlw 0xAA"); asm("movwf PPSLOCK"); asm("bcf PPSLOCK,0"); RX1PPSbits.PORT = 1; RX1PPSbits.PIN = 6; RB4PPS = 0x0F; // Step 2.5 - Configure the pin direction and digital mode ANSELBbits.ANSB6 = 0; TRISBbits.TRISB4 = 0; // Step 3 TX1STAbits.SYNC = 0; RC1STAbits.SPEN = 1; // Step 4 TX1STAbits.TX9 = 0; // Step 5 - No needed // Step 1 from RX section (36.1.2.1) RC1STAbits.CREN = 1; // Step 6 TX1STAbits.TXEN = 1; // Step 7 - 9 are not needed because we are not using interrupts // Step 10 is in the putchar step. } To send a character we simply need to wait for room in the transmitter and then add another character. void putch(char c) { while(!TX1IF); // sit here until there is room to send a byte TX1REG = c; } And to receive a character we can just wait until a character is present and retrieve it. int getch(void) { while(!RC1IF); // sit here until there is a byte in the receiver return RC1REG; } And that completes our program. Here is the test run. Every time I typed a letter or number, the following character is echoed. Typing a few characters is interesting and all but I did promise graphing. We shall make a simple program that will stream a series of numbers one number per row. Then we will introduce processing to convert this stream into a graph. The easiest way to produce a stream of numbers is to use printf. This function will produce a formatted line of text and insert the value of variables where you want them. The syntax is as follows: value = 5; printf("This is a message with a value %d\n",value); To get access to printf you must do two things. 1) You must include the correct header file. So add the following line near the top of your program. (by the xc.h line) #include <stdio.h> 2) You must provide a function named putch that will output a character. We just did that. So here is our printing program. #include <stdio.h> #include <math.h> /// Lots of stuff we already did void main(void) { double theta = 0; init_uart(); while(1) { printf("%f\r\n",sin(theta)); theta += 0.01; if(theta >= 2 * 3.1416) theta = 0; } } Pictures or it didn't happen. And now for processing. Go to http://www.processing.org and download a suitable version for your PC. Attached is a processing sketch that will graph whatever appears in the serial port from the PIC16. And here is a picture of it working. I hope you found this exercise interesting. As always post your questions & comments below. Quite a lot was covered in this session and your questions will guide our future work. Good Luck exercise_5a.zip exercise_5.zip graph_data.pde
  13. 1 point
    Today I am going to introduce a peripheral. So far we have interacted with the I/O pins on the micro controller. This can get you pretty far and if you can manipulate the pins quickly enough you can produce nearly any signal. However, there are times when the signal must be generated continuously and your MCU must perform other tasks. Today we will be generating a PWM signal. PWM stands for Pulse Width Modulation. The purpose of PWM is to produce a fixed frequency square wave but the width of the high and low pulses can be adjusted. By adjusting the percentage of the time the signal is high versus low, you can change the amount of energy in a system. In a motor control application, changing the energy will change the speed. In an LED application, changing the energy will change the brightness. We will use the PWM to change the brightness of the LED on our demo board. Here is a sample of a PWM signal. The PWM frequency is 467.3hz and the duty cycle is 35%. One way to produce such a signal is to execute a bit of code such as this one. do_pwm macro banksel pwm_counter movlw pwm_reload decfsz pwm_counter,f movf pwm_counter,w movwf pwm_counter banksel pwm subwf pwm,w banksel LATA movf LATA,w andlw 0xFB btfss STATUS,C iorlw 0x04 movwf LATA endm This code does not help you if you are writing in C but it will serve to show the challenges of this technique. This is a simple macro that must be executed numerous times to produce the entire PWM cycle. The PWM frequency is determined by how often this macro can be called. So one option is quite simply this: loop do_pwm goto loop But if you do this the MCU will not be able to perform any other functions. loop do_pwm banksel reload incfsz reload goto loop banksel pwm incf pwm,f goto loop Here is one that updates the PWM duty cycle every time the reload variable overflows. This is not a lot of additional work but the MCU is still 100% consumed toggling a pin and deciding when to update the duty cycle. Surely there is a better way. Enter a Peripheral MCU Peripherals are special hardware functions that can be controlled by the software but do some tasks automatically. One common peripheral is the PWM peripheral. Different MCU have different peripherals that can produce a PWM signal. For instance, older PICmicrocontrollers have the CCP (Capture, Compare, PWM) peripheral and newer PICmicrocontrollers have dedicated PWM's. To demonstrate the use of this simple peripheral I will switch to C and give some code. The good side to using a peripheral is the work it takes on. The down side is the additional effort required to setup the additional hardware. Microchip provides a tool call MCC to do this work but in this example, we will do the work ourselves. Fortunately, Microchip provided a section in the data sheet that provides an 8 step checklist for configuring the PWM. Time for doing the steps // Steps from Chapter 30.9 of the datasheet // Step 1 TRISAbits.TRISA2 = 1; // Step 2 PWM6CON = 0; // Step 3 T2PR = 0xFF; // Step 4 PWM6DC = 358 << 6; // Step 5 T2CLKCONbits.CS = 1; T2CONbits.CKPS = 0; T2CONbits.ON = 1; // Step 6 // Step 7 TRISAbits.TRISA2 = 0; RA2PPS = 0x0D; // Step 8 PWM6CONbits.EN = 1; And here is 35% just like before, except last time the period of the entire wave was 2.14ms and now it is 0.254ms. That is about 10x faster than before. This time the main loop is doing absolutely nothing making it possible to do all kinds of neat things like implement a flicker to make the LED look like a candle. while(1) { __delay_ms(5); PWM6DC = rand() % 1024 << 6; } So here is a candle. Through honestly it is not the best looking candle. Perhaps you can do a better job. Peripherals can be a huge time saver and can certainly provide more CPU in your application for the real "secret sauce". Most of the data sheet covers peripherals so we will go through a few of them in the next few weeks. Good Luck Step_4.zip step_4_asm.zip
  14. 1 point
    I totally cheat. https://cdecl.org Of course the right to left helps when you don't have the internet.
  15. 1 point
    For me, the trick to strictly read it from right to left helped a lot. volatile List_t * ListHead; A variable ListHead which is a pointer to the type List_t which is volatile. List_t * volatile ListHead; A variable ListHead which is a volatile pointer to a type List_t volatile List_t * volatile ListHead; A variable ListHead which is a volatile pointer to a type List_t which is volatile const int *ptr_to_constant; A variable ptr_to_constant which is a pointer to a type int which is a constant int *const constant_ptr; A variable constant_prt which is a constant pointer to a type int
  16. 1 point
    I realize now that I have a new pet peeve. The widespread and blind inclusion, by Lemmings calling themselves embedded C programmers, of extern "C" in C header files everywhere. To add insult to injury, programmers (if I can call them that) who commit this atrocity tend to gratuitously leave a comment in the code claiming that this somehow makes the code "compatible" with C++ (whatever that is supposed to mean - IT DOES NOT !!!). It usually looks something like this (taken from MCC produced code - uart.h) #ifdef __cplusplus // Provide C++ Compatibility extern "C" { #endif So why does this annoy me so much? (I mean besides the point that I am unaware of any C++ compiler for the PIC16, which I generated this code for - which in itself is a hint that this has NEVER been tested on any C++ compiler ! ) First and foremost - adding something to your code which claims that it will "Provide C++ Compatibility" is like entering into a contract with the user that they can now safely expect the code to work with C++. Well I have bad news for you buddy - it takes a LOT more than wrapping everything in extern "C" to make your code "Compatible with C++" !!! If you are going to include that in your code you better know what you are doing and your code better work (as in actually being tested) with a real C++ compiler. If it does not I am going to call out the madness you perpetrated by adding something which only has value when you mix C and C++, but your code cannot be used with C++ at all in the first place - because you are probably doing a host of things which are incompatible with C++ ! In short, as they say - your header file comment is writing cheques your programming skills cannot cash. Usually a conversation about this starts with me asking the perpetrator if they can explain to me what "That thing" actually does, and when do you think you need this to use the code with a C++ compiler, so let's start there. I can count on one hand the people who have been able to explain to me how this works correctly. Extern "C" - What it does and what it is meant for This construct is used to tell a C++ compiler that it should set a property called langage linkage which affects mangling of names as well as possible calling conventions. It does not guarantee that you can link to C code compiled with a different compiler. This is a good point to quote the C++14 specification, section 7.5 on Language linkage That said, if you are e.g. using GCC for your C as well as your C++ compiler you will have a very good chance of being lucky enough that the implementation-defined linkage will be compatible! A C++ compiler will use "name mangling" to ensure that symbols with identical names can be distinguished from each other by passing additional semantic information to the linker. This becomes important when you use e.g. function overloading, or namespaces. This mangling of names is a feature of C++ (which allows duplicate names across the program, but does NOT exist in C (which does not allow duplicate symbols to be linked at all). When you place an extern "C" wrapper around the declaration of a symbol you are telling the C++ compiler to disable this name mangling feature and also to alter it's calling convention when calling a function to be more likely to link with C code, although calling conventions are a topic all in it's own. As you may have deduced by now there is a reason that this is not called "extern C++"! This is required to make your C++ calls match the names and conventions in the C object files, which does not contain mangled names. If you ARE going to place a comment next to your faux pas, at least get it right and say "For compatibility with C naming and calling conventions" instead of claiming incorrectly that this is required for C++ compatibility somehow! There is one very specific use case, one which we used to encounter all the time in what would now be called "the olden days" of Windows 3.1 when everything was shared through dynamic link libraries (.dll files). It turns out that in order to use a dll which was written using C++ from your C program you had to wrap the publicly exported function declarations in extern "C". Yes that is correct, this is used to make C++ code compatible with C, NOT THE OTHER WAY AROUND! So when do I really need this then? Let's take a small step back. If you want to use your C code with a C++ compiler, you can simply compile it all with the C++ compiler! The compiler will resolve the names just fine after mangling them all, and since you are properly using name spaces with your C++ code this will reduce the chances of name collisions and you will be all the happier for doing the right thing. No need for disabling name mangling there. If you do not need this to get your code to compile with a C++ compiler, then when DO you need it? Ahhh, now you are beginnng to see why this has become a pet peeve of mine ... but the plot still has some thickening to do before we are done... Pretty much the only case where it makes sense to do this is when you are going to compile some of your code with a C compiler, then compile some other code with a C++ compiler, and in the end take the object files from the two compilers and link them together, probably using the C++ linker. Of course when you feed your .c files and your .cpp files to a typical C++ compiler it will actually run a C compiler on the .c files and the C++ compiler on the .cpp files by default, and mangling will be an issue and you will exclaim "you see! He was wrong!", but not that fast ... it is simple enough to tell the compiler to compile all files with the C++ compiler, and this remains the best way to use C libraries in source form with your C++ code. If you are going to compile the C code into a binary library and link it in (which sometimes happens with commercial 3rd party libraries - like .dll's - DUH ! ) then there is a case for you to do this, but most likely this does not apply to you at all as you will have the source available all the time and you are working on an embedded system where you are making a single binary. To help you ease the world of hurt you are probably in for - you should read up on how to tell your compiler to use a particlar calling convention which has a chance of being compatible with the linker. If you are using GCC you can start here. To be clear, if you add extern "C" to your C code and then compiles it into an object file to be linked with your C++ program the extern "C" qualifier is entirely ignored by the C compiler. Yes that is right, all the way to producing the object file this has no effect whatsoever. It is only when you are producing calls to the C code from the C++ code that the C++ code is altered to match the C naming and calling conventions, so this means that the C++ code is altered to be compatible with C. In the end There is a hell of a lot of things you will need to consider if you want to mix C and C++. I promise that I will write another blog on that next time around if I get anybody asking for it in the comments. Adding extern "C" to your code is NOT required to make it magically "compatible with C++". In order to do that you need to heed with extreme caution the long list of incompatibilities between C and C++ and you will probably have to be much more specific than just stating C and C++. Probably something like C89 and C++11 if I had to wager a guess to what will be most relevant. And remember the reasons why it is almost always just plain stupid to use extern "C" in your C headers. It does not do what you think it does. It especially does not do what your comment claims it does - it does not provide C++ compatability! Don't let your comments write cheques your code cannot cash! - Before you even think of adding that, make sure you KNOW AND FOLLOW ALL the C++ compatability rules first. For heaven's sake TEST your code with a C++ compiler ! If you want to use a "C" library with your C++ code simply compile all the code with your C++ compiler. QED - no mess no fuss! If it is not possible to do 5 above, then compile the C code normally (without that nonsense) and place extern "C" around the #include of the C library only! (example below). After all, this is for providing C linkage compatability to C++ compiled code! If you are producing a binary library using C to be used/linked with a C++ compiler, then please save us all and just compile the library with both C and C++ and provide 2 binaries! If all of the above fail, beacuase you really just hit that one in a million case where you think you need this, then for Pete's sake educate yourself before you attempt something that hard, hopefully in the process you will realize that it is just a bad idea after all! Now please just STOP IT! I feel very much like pulling a Jeff Atwood and saying after all that only a moron would use extern "C" in their C headers (of course he was talking about using tabs). Orunmila Oh - I almost forgot - the reasonable way of using extern "C" looks like this: #include <string> // Yes <string>, not <string.h> - this is C++ ! #include <stdio> // Include C libraries extern "C" { #include "clib1.h" #include "clib2.h" } using namespace std; // Because the cuticles on my typing finger hurt if I have to type out "std::" all the time! // This is a proper C++ class because I am too patrician to use "C" like that peasant library writer! class myClass { private: int myPrivateInt; ... ... Appeals to Authority Dan Saks CppCon talk entitled “extern c: Talking to C Programmers about C++” A very good answer on Stackoverflow Some decent answers on this Stackoverflow question Fairly OK post on geeksforgeeks The dynamic loading use case with examples Note: That first one is a bit of a red herring as it does not explain extern C at all - nevertheless it is a great talk which all embedded programmers can benefit from 🙂
  17. 1 point
    Yes you Peripheral clock is definitely 48MHz which means with a BRGH=1 you will only be able to go up to 6MHz, if you change the clock to 96MHz you can generate up to 12MHz BUT you should beware that when you have BRGH=1 the formula is dividing by 4 becuase the oversampling clock is only running at 4x and this can be notoriously unreliable. For more details see this discussion, in summary if you oversample 16x it leaves you with an error budget of 2.03%. When you over sample only 4x the error budged narrows to 0.52% and achieving that accuracy means you will have to drive the lines pretty hard to reduce the slew rate and also match the impedance to prevent ringing if you want to communicate reliably! BTW. note that I used a 10% rise time and fall time for that calculation in the discussion. At a baud rate of 12MHz that means a rise time of 8.3ns, and at 24MHz you will be down to 4.15ns of allowed rise and settling time on the line to sample it accurately. Better use a scope to check if your full system can achieve this, if not you will be able to send but you will not be able to receive successfully.
  18. 1 point
    According to your zip package, PBCLK is set to 48 MHz, not 96MHz? And it's a PIC32MX470F512L, just for clarity 🙂 It would be a good idea to debug your project and have a look at the actual register settings, to see if Harmony did everything right. I would also suggest to check the analog output signal with a scope to check the frequency and the signal quality. Is there a signal at higher baud rates and how does it look like?
  19. 1 point
    It seems that in 2.15 they changed how the command line options are interpreted. in 2.10, there was a -fgnu89-inline option that ensured the inlines operated in a particular way (the GNU89 way). In 2.15 it seems this option is being quietly ignored. You can test by removing the option from the command line when you build in 2.10 and then you should see the same error.
  20. 1 point
    printf will bind to stdio by simply calling putchar repeatedly. If your user will supply the data there is no risk if you run the code in Trustzone as they will not be able to access any data maliciously without causing a hardware fault. So this means your only remaining problem is ensuring that the user does not print backspace characters or \r\n characters, so you can simply remove/ignore backspace and replace every \r\n with "\r\nSECURE:" and you should be good? This can all be done quite safely inside of the implementation of putchar, and you can run that inside of Trustzone (which I have not used myself so I do not know the details of the limitations)
  21. 1 point
    What every embedded programmer should know about ADC measurement, accuracy and sources of error ADC's you encounter will typically be specified as 8, 10 or 12-bit. This is however rarely the accuracy that you should expect from your ADC. It seems counter-intuitive at first, but once you understand what goes on under the hood this will be much clearer. What I am going to do today is take a simple evaluation board for the PIC18F47K40 (MPLAB® Xpress PIC18F47K40 Evaluation Board ) and determine empirically (through experiments and actual measurements) just how accurate you should expect ADC measurements to be. Feel free to skip ahead to a specific section if you already know the basics. Here is a summary of what we will cover with links for the cheaters. Units of Measurement of Errors Measurement Setup Sources and Magnitude of Errors Voltage Reference Noise Offset Gain Error Missing Codes and DNL Integral Nonlinearity (INL) Sampling Error Adding up Errors Vendor Comparison Final Notes Units of Measurement of Errors When we talk about ADC's you will often see the term LSB used. This term refers to the voltage represented by the least significant bit of the ADC, so in reality it is the voltage for which you should read 1 on the ADC. This is a convenient measure for ADC's since the reference voltage is often not fixed and the size of 1 LSB in volts will depend on what you have the reference at, while most errors caused by the transfer function will scale with the reference. For a 10-bit ADC with 3v3 of range one LSB will be 3.3/(2^10) = 3.3/1024 = 3.22mV. An error of 1% on a 10-bit converter would represent 1%*1024 = 10.24x the size of one LSB, so we will refer to this as 10LSB of error, which means our measurement could be off by 32.2mV or ten times the size of 1 LSB. When I have 10 LSB in error I really should be rounding my results to the nearest 10 LSB, since the least significant bits of my measurement will be corrupted by this error. 10LSB will take 3.32 bits to represent. This means that my lowest 3 bits are possibly incorrect and I can only be confident in the values represented by the 7 most significant bits of my result. This means that the effective number of bits (ENOB) for my system is only 7, even though my ADC is taking a 10-bit measurement. The lower 3 bits are affected by the measurement error and cannot be relied upon so they should be discarded if I am trying to make an absolute voltage measurement accurately. We can always work out exactly how many bits of accuracy we are losing, or to how many bits we need to round to, using the calculation: log(#LSB error)/log(2) Note that this calculation will give us fractional numbers of bits. If we have 10LSB error the error does not quite affect a full 4 bits (that happens only at 16LSB), but we can not say it removes only 3 bits, because that already happened at 8LSB, so this is somewhere in between. In order to compare errors meaningfully we will work with fractions of bits in these cases, so 10LSB of error reduces our accuracy by 3.32 bits. This is especially useful when errors are additive because we can add up all the fractional LSB of errors to get the total error to the nearest bit. At this point I would like to encourage you to take your oscilloscope and try to measure how much noise you can detect on your lines. You will probably be surprized that most desk oscilloscopes can only measure signals down to 20mV, which means that 1LSB on a 10-bit ADC with a 3V3 reference will be close to 10x smaller than the smallest signal your digital scope can measure! If you can see noise on the scope (which you probably can) then that means it is probably at least 20mV or 10LSB of error. It turns out that our intuition about how accurate an ADC should be, as well as how accurate our scope can measure is seldom correct ... Measurement Setup I am using my trusty Saleae Logic Pro 8 today. It has a 12-bit ADC and measures +-10V on the analog channel and is calibrated to be accurate to between 9 and 10 ENOB of absolute accuracy. This means that 1LSB of error will be roughly 4.8mV, which for my 2V system with a 10-bit ADC is already the size of 2LSB of measurement error. When I ground the Saleae input and take a measurement we can see how much noise to expect on the input during our measurements. As you will see later we actually want to see 2-3LSB of noise so that we can improve accuracy by digital filtering, if you do not have enough noise this is not possible, so this looks really good. Using the software to find the maximum variation for me you can see that I have about 15.64mV of noise on my line. Since the range is +-10V this is only 15.6/20000 = 0.08% of error, but this is going to be, for my target 2V range, 15.6/2048*1024 = 8LSB of error to start with on my measurement equipment! For an experiment we are going to need an analog voltage source to measure using the ADC. It so happens that this device has a DAC, so why not just use that! You would think that this was a no-brainer, but it turns out, as always, that it is not quite as simple as that would seem! What I will do first is set the DAC and ADC to use the same reference (this has the added benefit that Vref inaccuracy will be cancelled out, nice!).We expect that if we set the DAC to give us 1.024V (50% of full range) and we then measure this using the 10-bit ADC, that we would measure half of the ADC range, or 512 right? For the test I made a simple program that will just measure the ADC every 1 second and print the result to the UART. Well here is the result of the measurement (to the right). Not what you expected ?! Not only are the 1st two readings appallingly bad, but the average seems to be 717, which is a full 40% more than we expect! How is this possible? Well this is how. Not only is the ADC inaccurate here, but the DAC even more so! The DAC is only 5 bits and it is specified to be accurate to 5LSB. That is already a full 320mV of error, but that is still not nearly enough to explain why we are measuring 717/1024*2.048 = 1.434V instead of 1.024V... So what is really going on here? To see I connected my trusty Saleae and changed the application to cycle the DAC though all 32 values, 1s per value, and make a plot for us to look at. On the Saleae we see this. It turns out that the DAC is such a weak source that anything you connect to it's output (like even an ADC input leakage or simply an I/O pin with nothing connected to it!), will load down the DAC and skew the output! This has been the cause of consternation for many a soul actually (see e.g. this post on the Microchip forum) Wow, so that makes sense, but is there anything we can do about this? On this device unfortunately there is not much we can do. There are devices with on-board op-amps you can use to buffer the DAC output like the 16F170x family, but this device does not have op-amps so we are out of luck! I will blog all about DAC's and about what the reasons for this shape is on another occasion, this blog is about ADC after all! So all I am going to do is adjust the DAC setting to give us about the voltage we need by measuring the result using the Saleae and call it a day. Turns out I needed to subtract 6 from the output setting to get close. We now see a measurement of 520 and this is what we see while taking measurements with the Saleae. 10.37mV of noise on just about 1V and we are in business! Sources and Magnitude of Error When measurement errors are uncorrellated it means that they will all add up to form the total worst case error. For example if I have 2LSB of noise, and I also have 2LSB of reference error this means the reading can be 2LSB removed from the correct value as a result of the reference, and an additional 2LSB as a result of the noise, to give 4LSB of total error. This means that these two types of errors are not correllated and the bits contributed by each to the total error are additive. At this point I want to mention that customers often come to me demanding a 16bit-ADC because they feel that the 10-bit one they have is not adequate for their application. They can seldom explain to me why they need 31uV of accuracy or what advanced layout techniques they are applying to keep the noise levels to even remotely this range, and most of the time the real problem turns out to be that their 10bit-ADC is really being used so badly that they are hardly getting 5 bits of accuracy from the converter. I also often see calculations which effectively discard the lower 4 bits of the ADC measurement, leaving you with only 8-bits of effective measurement, so if you do that getting more bits in the ADC is obviously only going to buy you only disappointment! That said, lets look at all of the most significant sources of error one by one in more detail. There are quite a few so we will give them headings and numbers. 1. Voltage Reference To get us started, lets look at the voltage reference and see how many LSB this contributes to our measurement error. If you are using a 1% reference, then please do not insist that you need a 16-bit or even a 12-bit ADC, because your reference alone is contributing errors into the 7th most significant bit and 8 bits is all you are going to get anyway! The datasheet for our evaluation board chip (PIC18F47K40) shows that the voltage reference will be accurate to +-4% when we set it to 2.048V like we did. People are always surprized when they realize how many LSB they are losing due to the voltage reference! 4% of 1024 = 51.2, which means that the reference alone can contribute up to 51.2 LSB of error to our system! Using an expensive off-chip reference also complicates things for us. Using that we would now have to be very careful with the layout to not introduce noise at the reference pin, and also take care of any signals coupling into this pin. Even then the reference will likely be something like a TL431 which will only be accurate to 1% which will be 10 LSB reducing our 10-bit ADC to less than 8 ENOB. We must note that reference errors are not equally distributed. At the maximum scale 1% represents 10LSB of error, but at the lower end of the scale 1% will represent only 1% of 1LSB. Since we are looking for the worst-case error we have to work with 10LSB due to the 1% error over the full ADC range. In your application you may be able to adjust the contribution of this error down to better represent the range you are expecting to measure. For example - at mid range, where our test signal is, the reference error will only contribute 5LSB of error with a 1% reference, or 25LSB for our 4% internal reference. The reference error is something which we could calibrate out if we knew what the error was and many manufacturers discard it stating simply that you should calibrate it out. Sadly these references tend to drift over time, temperature and supply voltages, so you usually cannot just calibrate it in the factory and compensate for the error in software and forget it. To revisit our 16-bit ADC scenario, if I want to measure accurately to 31uV (16 bits on a 2V reference) that reference would have to be accurate to 31uV/2V = 0.0015%. Let's look on Digikey for a price on a Voltage reference with the best specs we can find. Best candidate I can find is this one at $128.31 a piece, and even that gives me only 0.1% with up to 0.6ppm/C drift. This means from 0 to 100C I will have 0.006% of temp drift (2LSB) on top of the 0.1% tolerance (which is another 33LSB). Now to be fair if I am building a control system I am more interested in perturbations from a setpoint and a 16-bit ADC may be valuable even if my reference is off, because I am not trying to take an absolute measurement, but still maintaining noise levels below 30uV is more of a challenge than it sounds, especially if I am driving some power which adds noise to the equation. This is of course the difference between accuracy and resolution. Accuracy gives me the minimum absolute error while resolution gives me the smallest relative unit of measure. 2. Noise Noise is of course the problem we all expect. It can often be a pretty large contributor to your measurement errors, and digital circuits are known for producing lots of noise that will couple into your inputs, but as we will see noise is not all bad and can be essential if you want to improve the results through digital post processing. We have seen that every 2mV of noise will add 1LSB to the error on our system as we have a 2V reference, and 1024 steps of measurement. As you have now seen this 2mV is probably much smaller than we can measure with a typical oscilloscope, so we cannot be sure how much noise we really have if we simply look at it on our scope. For most systems the recommendation would be to place the microcontroller in lowest power sleep mode and avoid toggling any output pins during the sampling of the ADC measurement to get the measurement with the lowest noise level. A simple experiment will show how much noise we could be coupling into the measurement when an adjacent pin is being toggled. I updated our program from before to simply toggle the pin next to the ADC input constantly and measured with the Saleae to see what the effect is. On the left is the signal zoomed out and on the right is one of the transitions zoomed in so you can get a better look. That glitch on the measurement line is 150mV or 75 LSB of noise due to an adjacent pin toggling, and the dev board I have does not even have long traces which would have made this much worse! It seems like a good idea to filter all this noise using analog low-pass filters like filter capacitors, but this is not always wise. We can make small amounts of noise work to our advantage, as long as it is white noise which is uncorrellated with our signal and other errors. When we do post-processing like taking multiple samples and averaging the result we can potentially increase the overall accuracy of our measurement. Using this technique it is possible to increase the ENOB (effective number of bits) of your measurements by simply taking more samples and averaging them. Without getting too deep into the math there, if you oversample a signal by a factor of N you will improve the SNR by a factor of sqrt(N), which means oversampling 256 times and taking the average will result in an increase of 16x the SNR, which represents an additional 4 bits of resolution of the ADC. Of course this is where having uncorrellated white noise of at least +-1LSB is important. If you have no noise on your signal you would likely just sample the same value 256 times and the average would not add any improvement to the resolution. If you had white noise added to the signal however you would sample a variety of values with the average lying somewhere in between the LSB you can measure, and the ratio of difference would represent the value of the signal more accurately. For a detailed discussion on this topic you can take a look at this application note by Silicon Labs https://www.silabs.com/documents/public/application-notes/an118.pdf 3. Offset The internal circuitry in the ADC will cause some offset error added into the conversion. This error will move all measurements either up or down by an equal amount. The Offset is a critical parameter for an ADC and should be specified in the datasheet for your device. For the PIC18F47K40 device the error due to offset is specified as 2.5LSB. Of course if we know what the offset is we could easily subtract this from the results, so many specifications will exclude the offset error and claim that you could easily "calibrate out" the offset. This may be possible, even easy to do, but if you do not write the code for it and do the actual math you will have to include the offset error in your accuracy calculations, and measuring what the current offset is can be a real challenge in a real-world system which is not located on your laboratory bench. If you do decide to measure the offset on the factory floor and calibrate it out using software you need to be careful to use an accurate reference, avoid noise and other sources of error and make sure that the offset will remain constant over the operating range of voltage and temperature and also that it will not drift over time. If any of these are true your calibration will be met with limited success. Offset is often hard to calibrate out since many ADC's are not accurate close to the extremes (at Vref or 0V). If they were you could take a measurement with the input on Vref+ and on Vref- and determine the offset, but we knew it was never going to be this easy! The offset will also be different from device to device, so it is not possible to calibrate this out with fixed values in your code, you will have to actively measure this on every device in the factory and adjust as the offset changes. Some manufacturers will actually calibrate out the offset on an ADC for you during their manufacturing process. If this is the case you will probably see a small offset error of +-1LSB which means that it is calibrated to be within this range. On our device the datasheet specifies a typical offset error of 0.5LSB with a max error of 2LSB, so this device is factory calibrated to remove the offset error, but even after this we should still expect up to 2LSB of drift in the offset around the calibrated value. 4. Gain Error Similar to the offset the internal transfer function of the ADC is designed to be as close as possible to ideal but there is always some error. Gain error will cause the slope of the transfer function to be changed. Depending on the offset this can cause an error which is at maximum either at the top or bottom end of the measurement scale as shown in the figure below. Like the offset it is also possible to calibrate out the gain error, as long as we have enough reference points to use for the calibration. If the transfer function is perfectly linear this would mean we would only require 2 measurement points. For our device the datasheet spec is typically 0.2LSB of gain error with a max error of 1.5LSB. This means that we cannot gain much from attempting to calibrate out the gain on this one. For other manufacturers you can easily find gain and offset errors in the tens of LSB, which makes calibration and compensation for the gain and offset worth the effort. The PIC18F47K40 is not only compensated for drift with temperature but also individually calibrated in the factory, so it seems that any additional calibration measurements will be at most accurate to 1LSB and the device is already specified to typically have less than this error, so calibration will probably gain us nothing. 5. Missing Codes and DNL We expect that every time the code increments by 1LSB that the input voltage has increased by exactly 1LSB in size. For an ADC the DNL error is a measure of how close to this ideal we are in reality. It represents the largest single step error that exists for the entire range of the ADC. If the DNL is stated at 0.5LSB this means that it can take anything from 0.5LSB to 1.5LSB of input voltage change to get the output code to increment by 1. When the DNL is more than 1LSB it means that we can move the input voltage by 2LSB and only get a single count of the converter. When this happens it is possible that it causes the next bit to be squeezed down to 0LSB, which can cause the converter to skip that code entirely as shown below. Most converters will specify that the result will monotonically increase as the voltage increases and that it will have no missing codes as you scan through the range, but you still have to be careful, because this is under ideal conditions and when you add in the other errors it is possible that some codes get skipped, so when you are comparing the output of the converter never check for a specific conversion value. Always look for a value in a range around the limit you are checking. 6. Integral Nonlinearity - INL INL is another of the critical parameters for all ADC's and will be stated in your datasheet if the ADC is any good. For our example the INL is specified as 3.5LSB. The tearm INL refers to the integral of the differential nonlinearity. In effect it represents what the maximum deviation from the ideal transfer function of the ADC will be as shown in the picture below. The yellow line represents the ideal transfer function while the blue line represents the actual transfer function. As you can see the INL is defined as the size of the maximum error through the range of the ADC. Since the INL can happen at any location along the curve it is not possible to calibrate this out. It is also uncorrellated with the other errors we have examined. We just have to live with this one! 7. Sampling error A SAR ADC will consist of a sampling capacitor which holds the voltage we are converting during the conversion cycle. We must take care when we take a sample that we allow enough time for this sampling capacitor to charge to the level of accuracy we want to see in our conversion. Effectively we end up with a circuit that has some serial impedance through which the sampling capacitor is charged. The simplified circuit for the PIC18F47K40 looks as follows (from the datasheet). As you can see the series impedance (Rs) together with the sampling writch and passgate impedance (RIC + Rss) will form a low-pass RC filter to charge Chold. A detailed calculation of the sampling time required to be within 1LSB of the desired sampling value is shown in the ADC section of the device datasheet. If we leave too little time for the sample to be acquired this will directly result in a measurement error. In our case this means that if we have 10K Rs and we wait for 462us after the sampling mux turns to the input we are measuring, the capacitor will be charged to within 0.5LSB of our target voltage. The ADC on the PIC18F47L40 has a built-in circuit that can keep the sampling switch closed for us for a number of Tadc periods. This can be set by adjusting the register ADACQ or using the provided API generated by MCC to achieve this. That first inaccurate result we saw in the conversion was a direct result of the channel not being given enough time to charge the sampling cap since the acquisition time was set to the default value of 0. Of course since we are not switching channels the capacitor is closer to the correct value when to take subsequent samples so the error seems to be going away over time! I have seen customers just throw away the first ADC sample as inaccurate, but if you do not understand why you can easily get yourselfs into a lot of trouble when you need to switch channels! We can re-do the measurement and this time use an acquisition time of 4xTadc = 6.8us. This is the result. NOTE : There is another Errata on this device that you have to wait at least 1 instruction cycle before reading the ADGO bit to see if the conversion is complete after setting the ADGO bit. At first I was just doing what the datasheet suggested, set ADGO and then wait while(ADGO); for the conversion to complete. Due to this errata however the ADGO bit will still read 0 the first time you read it and you will think the conversion is done while it has not started, resulting in an ADC reading of 0 ! After adding the required NOP() to the generated MCC code as follows the incorrect first reading is gone: adc_result_t ADCC_GetSingleConversion(adcc_channel_t channel, uint8_t acquisitionDelay) { // Turn on the ADC module ADCON0bits.ADON = 1; // select the A/D channel ADPCH = channel; //Set the Acquisition Delay ADACQ = acquisitionDelay; //Disable the continuous mode. ADCON0bits.ADCONT = 0; // Start the conversion ADCON0bits.ADGO = 1; NOP(); // NOP workaround for ADGO silicon issue // Wait for the conversion to finish while (ADCON0bits.ADGO) { } // Conversion finished, return the result return (adc_result_t)(((adc_result_t)ADRESH << 8) + ADRESL); } Uncorrelated errors I will leave the full analysis up to the reader, but all of these errors are uncorrellated and thus additive, so for our case the worst case error will be when all of these errors align, the offset is in the same direction as the gain error, as the noise, as the INL error, etc. Of course when we test on the bench it is unlikely that we will encounter a situation where all of these are 100% aligned, but if we have manufactured thousands of units in the field running for years it is definitely going to happen and much more often that you would like, so we have no choice but to design for the worst-case error we are likely to see in the wild. For our exampe the different sources of error add up as follows: Voltage Reference = 4% [41 LSB] Noise [8 LSB] Offset [2.5 LSB] Gain [1.5 LSB] INL [3.5 LSB] For a total of 56.5 LSB of potential absolute error in measurement. This reduces our effective number of bits by log(56.5)/log(2) = 5.8 bits, which means that our 10-bit LSB can have absolute errors running into the 6th bit, giving us only 4 ENB (effective number of bits) when we are looking for absolute accuracy. We can improve this to 26.5 LSB by suing a 1% off-chip reference, which will make the ENB = 5 bits. If we look at the measurement we get using the Saleae we measure 0.99V on the line which should result in 0.99V/2.045V *1024 = 495 but our measurement is in fact 520, which is off by 25LSB. So as we can see our 1-board sample does not hit the worst case error at the center of the sampling range here, but our error extended at least into the 5th bit of the result as our 25LSB error requires more than 4 bits to represent. Nevertheless 25LSB is quite a bit better than the worst-case value of 56.5 LSB of error which we calculated, so this particular sample is not doing too badly! I am going to get my hands on a hair dryer in the week and take some measurements at an elevated temperature and then I will come back and update this for your reading pleasure 🙂 Comparison I recently compared some ADC's from different vendors. I was actually looking more at the other features but since I was busy with this I also noted down the specs. Not all of the datasheets were perfectly clear so do reach out to me if I made a mistake somewhere, but this was how they matched up in terms of ADC performance. As fas as I could find them I used the worst case specifications and not the typical ones. Some manufacturers only specify typical results, so this comparison is probably not fair to those who make better specifications with better information. Let me know in the comments how you feel about this. I will go over the numbers again and maybe come update all of these to typical values for a more fair comparison if someone asks me for this ... Manufacturer -> Device -> Xilinx XC7Z010 Microchip PIC32MZ EF Texas Instruments CC3220SF Espressif ESP32 ST Micro STM32L475 Renesas R65N V2 INL 2 3 2.5 12 2.5 3 DNL 1 1 4 7 1.5 2 Offset 8 2 6(1) 25(1) 2.5 3.5 Gain 0.5 8 82(1) ?(2) 4.5 3.5 Total Error (INL+Offset+Gain) 10.5 13 90.5 37+ 9.5 10 I noted that many of these manufacturers specify their ADC only at one temperature point (25C) so you probably have to dig a little deeper to ensure that the specs will not vary greatly over temperature. (1) These settings were specified in the datasheet as an absolute voltage and I converted them to LSB for max range and best resolution of the ADC. Specifically for the TI device the offset was specified as 2mV and gain error as 20mV on a 1.4V range, and for ESP32 the offset is specified as 60mV but for a wider voltage range of 2.45v (2) For ESP32 I was not able to determine the gain error clearly form the datasheet. Final Notes We can conlcude a couple of very important points from this. If the datasheet claims a 12-bit ADC we should not expect 12-bits of accuracy. First we need to calculate what to expect from our entire system, and we should expect the reference to add the most to our error. All 12-bit converters are not equal, so when comparing devices do not just look at how many bits the converters provide, also compare their performance! The same sytem can yield between 5 and 10 bits of accuracy depending on the specs of the converter, so do not be fooled! Many of the vendors specified their ADC only at a very specific temperature and reference voltage at maximum, take care not to be fooled by this - shall we call it "creative" specmanship and be sure to compare apples with apples when looking for absolute accuracy. Source Code For those who have this board or device I attach the test code I used for download here : ADC_47K40.zip
  22. 1 point
    Hi KM1/Orunmila, adding "NVMREG" even I am able to see the proper result on hardware.
  23. 1 point
    I have to add here that the single thing which trips most people up on RTCOUNTER is the timer period. The timer MUST be free-running, which mean that you can NOT use MCC to set a timer period by using a reload or PR value, the timer HAS to be set to maximum period and the only thing you can modify is the prescaler for the timer to adjust the period. I know this is limiting, but it allows us to have a pure monotonic upwards counting number which we can concatenate with our sw variable to construct the timer. Remember that we want as few interrupts as possible, so running the timer for MAX period is exactly what we want to do here! This library does not have an event when the timer gets an interrupt or overflow (that is what the TIMEOUT driver does if you want that, but there is quite a price for this). When you call the check function it will simply compare the current timer register with the absolute time variable for the next timer to expire, it is a quick compare, so pretty cheap to do this.
  24. 1 point
    Structures in the C Programming Language Structures in C is one of the most misunderstood concepts. We see a lot of questions about the use of structs, often simply about the syntax and portability. I want to explore both of these and look at some best practice use of structures in this post as well as some lesser known facts. Covering it all will be pretty long so I will start off with the basics, the syntax and some examples, then I will move on to some more advanced stuff. If you are an expert who came here for some more advanced material please jump ahead using the links supplied. Throughout I will refer to the C99 ANSI C standard often, which can be downloaded from the link in the references. If you are not using a C99 compiler some things like designated initializers may not be available. I will try to point out where something is not available in older complilers that only support C89 (also known as C90). C99 is supported in XC8 from v2.0 onwards. Advanced topics handled lower down Scope Designated Initializers Declaring Volatile and Const Bit-Fields Padding and Packing of structs and Alignment Deep and Shallow copy of structures Comparing Structs Basics A structure is a compound type in C which is known as an "aggregate type". Structures allows us to use sets of variables together like a single aggregate object. This allows us to pass groups of variables into functions, assign groups of variables to a destination location as a single statement and so forth. Structures are also very useful when serializing or de-serializing data over communication ports. If you are receiving a complex packet of data it is often possible to define a structure specifying the layout of the variables e.g. the IP protocol header structure, which allows more natural access to the members of the structure. Lastly structures can be used to create register maps, where a structure is aligned with CPU registers in such a way that you can access the registers through the corresponding structure members. The C language has only 2 aggregate types namely structures and arrays. A union is notably not considered an aggregate type as it can only have one member object (overlapping objects are not counted separately). [Section "6.5.2 Types" of C99] Syntax The basic syntax for defining a structure follows this pattern. struct [structure tag] { member definition; member definition; ... } [one or more structure variables]; As indicated by the square brackets both the structure tag (or name) and the structure variables are optional. This means that I can define a structure without giving it a name. You can also just define the layout of a structure without allocating any space for it at the same time. What is important to note here is that if you are going to use a structure type throughout your code the structure should be defined in a header file and the structure definition should then NOT include any variable definitions. If you do include the structure variable definition part in your header file this will result in a different variable with an identical name being created every time the header file is included! This kind of mistake is often masked by the fact that the compiler will co-locate these variables, but this kind of behavior can cause really hard to find bugs in your code, so never do that! Declare the layout of your structures in a header file and then create the instances of your variables in the C file they belong to. Use extern definitions if you want a variable to be accessible from multiple C files as usual. Let's look at some examples. Example1 - Declare an anonymous structure (no tag name) containing 2 integers, and create one instance of it. This means allocate storage space in RAM for one instance of this structure on the stack. struct { int i; int j; } myVariableName; This structure type does not have a name, so it is an anonymous struct, but we can access the variables via the variable name which is supplied. The structure type may not have a name but the variable does. When you declare a struct like this it is not possible to declare a function which will accept this type of structure by name. Example 2 - Declare a type of structure which we will use later in our code. Do not allocate any space for it. struct myStruct { int i; int j; }; If we declare a structure like this we can create instances or define variables of the struct type at a later stage as follows. (According to the standard "A declaration specifies the interpretation and attributes of a set of identifiers. A definition of an identifier is a declaration for that identifier that causes storage to be reserved for that object" - 6.7) struct myStruct myVariable1; struct myStruct myVariable2; Example 3 - Declare a type of structure and define a type for this struct. typedef struct myStruct { int i; int j; } myStruct_t; // Not to be confused by a variable declaration // typedef changes the syntax here - myStruct_t is part of the typedef, NOT the struct definition! // This is of course equivalent to struct myStruct { int i; int j; }; // Now if you placed a name here it would allocate a variable typedef struct myStruct myStruct_t; The distinction here is a constant source of confusion for developers, and this is one of many reasons why using typedef with structs is NOT ADVISED. I have added in the references a link to some archived conversations which appeared on usenet back in 2002. In these messages Linus Torvalds explains much better than I can why it is generally a very bad idea to use typedef with every struct you declare as has become a norm for so many programmers today. Don't be like them! In short typedef is used to achieve type abstraction in C, this means that the owner of a library can at a later time change the underlying type without telling users about it and everything will still work the same way. But if you are not using the typedef exactly for this purpose you end up abstracting, or hiding, something very important about the type. If you create a structure it is almost always better for the consumer to know that they are dealing with a structure and as such it is not safe to to comparisons like == to the struct and it is also not safe to copy the struct using = due to deep copy problems (later on I describe these). By letting the user of your structs know explicitly they are using structs when they are you will avoid a lot of really hard to track down bugs in the future. Listen to the experts! This all means that the BEST PRACTICE way to use structs is as follows, Example 4- How to declare a structure, instantiate a variable of this type and pass it into a function. This is the BEST PRACTICE way. struct point { // Declare a cartesian point data type int x; int y; }; void pointProcessor(struct point p) // Declare a function which takes struct point as parameter by value { int temp = p.x; ... // and the rest } void main(void) { // local variables struct point myPoint = {3,2}; // Allocate a point variable and initialize it at declaration. pointProcessor(myPoint); } As you can see we declare the struct and it is clear that we are defining a new structure which represents a point. Because we are using the structure correctly it is not necessary to call this point_struct or point_t because when we use the structure later it will be accompanied by the struct keyword which will make its nature perfectly clear every time it is used. When we use the struct as a parameter to a function we explicitly state that this is a struct being passed, this acts as a caution to the developers who see this that deep/shallow copies may be a problem here and need to be considered when modifying the struct or copying it. We also explicitly state this when a variable is declared, because when we allocate storage is the best time to consider structure members that are arrays or pointers to characters or something similar which we will discuss later under deep/shallow copies and also comparisons and assignments. Note that this example passes the structure to the function "By Value" which means that a copy of the entire structure is made on the parameter stack and this is passed into the function, so changing the parameter inside of the function will not affect the variable you are passing in, you will be changing only the temporary copy. Example 5 - HOW NOT TO DO IT! You will see lots of examples on the web to do it this way, it is not best practice, please do not do it this way! // This is an example of how NOT to do it // This does the same as example 4 above, but doing it this way abstracts the type in a bad way // This is what Linus Torvalds warns us against! typedef struct point_tag { // Declare a cartesian point data type int x; int y; } point_t; void pointProcessor(point_t p) { int temp = p.x; ... // and the rest } void main(void) { // local variables point_t myPoint = {3,2}; // Allocate a point variable and initialize it at declaration. pointProcessor(myPoint); } Of course now the tag name of the struct has no purpose as the only thing we ever use it for is to declare yet another type with another name, this is a source of endless confusion to new C programmers as you can imagine! The mistake here is that the typedef is used to hide the nature of the variable. Initializers As you saw above it is possible to assign initial values to the members of a struct at the time of definition of your variable. There are some interesting rules related to initializer lists which are worth pointing out. The standard requires that initializers be applied in the order that they are supplied, and that all members for which no initializer is supplied shall be initialized to 0. This applies to all aggregate types. This is all covered in the standard section 6.7.8. I will show a couple of examples to clear up common misconceptions here. Desctiptions are all in the comments. struct point { int x; int y; }; void function(void) { int myArray1[5]; // This array has random values because there is no initializer int myArray2[5] = { 0 }; // Has all its members initialized to 0 int myArray3[5] = { 5 }; // Has first element initialized to 5, all other elements to 0 int myArray3[5] = { }; // Has all its members initialized to 0 struct point p1; // x and y both indeterminate (random) values struct point p2 = {1, 2}; // x = 1 and y = 2 struct point p3 = { 1 }; // x = 1 and y = 0; // Code follows here } These rules about initializers are important when you decide in which order to declare your members of your structures. We saw a great example of how user interfaces can be simplified by placing members to be initialized to 0 at the end of the list of structure members when we looked at the examples of how to use RTCOUNTER in another blog post. More details on Initializers such as designated initializers and variable length arrays, which were introduced in C99, are discussed in the advanced section below. Assignment Structures can be assigned to a target variable just the same as any other variable. The result is the same as if you used the assignment operator on each member of the structure individually. In fact one of the enhancements of the "Enhanced Midrange" code in all PIC16F1xxx devices is the capability to do shallow copies of structures faster thought specialized instructions! struct point { // Declare a cartesian point data type int x; int y; }; void main(void) { struct point p1 = {4,2}; // p1 initialized though an initializer-list struct point p2 = p1; // p2 is initialized through assignment // At this point p2.x is equal to p1.x and so is p2.y equal to p1.y struct point p3; p3 = p2; // And now all three points have the same value } Be careful though, if your structure contains external references such as pointers you can get into trouble as explained later under Deep and Shallow copy of structures. Basic Limitations Before we move on to advanced topics. As you may have suspected there are some limitations to how much of each thing you can have in C. The C standard calls these limits Translation Limits. They are a requirement of the C standard specifying what the minimum capabilities of a compiler has to be to call itself compliant with the standard. This ensures that your code will compile on all compliant compilers as long as you do not exceed these limits. The Translation Limits applicable to structures are: External identifiers must use at most 31 significant characters. This means structure names or members of structures should not exceed 31 unique characters. At most 1023 members in a struct or union At most 63 levels of nested structure or union definitions in a single struct-declaration-list Advanced Topics Scope Structure, union, and enumeration tags have scope that begins just after the appearance of the tag in a type specifier that declares the tag. When you use typedef's however the type name only has scope after the type declaration is complete. This makes it tricky to define a structure which refers to itself when you use typedef's to define the type, something which is important to do if you want to construct something like a linked list. I regularly see people tripping themselves up with this because they are using the BAD way of using typedef's. Just one more reason not to do that! Here is an example. // Perfectly fine declaration which compiles as myList has scope inside the curly braces struct myList { struct myList* next; }; // This DOES NOT COMPILE ! // The reason is that myList_t only has scope after the curly brace when the type name is supplied. typedef struct myList { myList_t* next; } myList_t; As you can see above we can easily refer a member of the structure to a pointer of the structure itself when you stay away from typedef's, but how do you handle the more complex case of two separate structures referring to each other? In order to solve that one we have to make use of incomplete struct types. Below is an example of how this looks in practice. struct a; // Incomplete declaration of a struct b; // Incomplete declaration of b struct a { // Completing the declaration of a with member pointing to still incomplete b struct b * myB; }; struct b { // Completing the declaration of b with member pointing to now complete a struct a * myA; }; This is an interesting example from the standard on how scope is resolved. Designated Initializers (introduced in C99) Example 4 above used initializer-lists to initialize the members of our structure, but we were only able to omit members at the end, which limited us quite severely. If we could omit any member from the list, or rather include members by designation, we could supply the initializers we need and let the rest be set safely to 0. This was introduced in C99. This addition had a bigger impact on Unions however. There is a rule in a union which states that initializer-lists shall be applied solely to the first member of the union. It is easy to see why this was necessary, since the members of a each struct which comprizes a union do not have to be the same number of members, it would be impossible to apply a list of constants to an arbitraty member of the union. In many cases this means that designated initializers are the only way that unions can be initialized consistently. Examples with structs. struct multi { int x; int y; int a; int b; }; struct multi myVar = {.a = 5, .b = 3}; // Initialize the struct to { 0, 0, 5, 3 } Examples with a Union. struct point { int x; int y; }; struct circle { struct point center; int radius; }; struct line { struct point start; struct point end; }; union shape { struct circle mCircle; struct line mLine; }; void main(void) { volatile union shape shape1 = {.mLine = {{1,2}, {3,4}}}; // Initialize the union using the line member volatile union shape shape2 = {.mCircle = {{1,2}, 10}}; // Initialize the union using the circle member ... } The type of initialization of a union using the second member of the union was not possible before C99, which also means if you are trying to port C99 code to a C89 compiler this will require you to write initializer functions which are functionally different and your port may end up not working as expected. Initializers with designations can be combined with compound literals. Structure objects created using compound literals can be passed to functions without depending on member order. Here is an example. struct point { int x; int y; }; // Passing 2 anonymous structs into a function without declaring local variables drawline( (struct point){.x=1, .y=1}, (struct point){.y=3, .x=4}); Volatile and Const Structure declarations When declaring structures it is often necessary for us to make the structure volatile, this is especially important if you are going to overlay the structure onto registers (a register map) of the microprocessor. It is important to understand what happens to the members of the structure in terms of volatility depending on how we declare it. This is best explained using the examples from the C99 standard. struct s { // Struct declaration int i; const int ci; }; // Definitions struct s s; const struct s cs; volatile struct s vs; // The various members have the types: s.i // int s.ci // const int cs.i // const int cs.ci // const int vs.i // volatile int vs.ci // volatile const int Bit Fields It is possible to include in the declaration of a structure how many bits each member should occupy. This is known as "Bit Fields". It can be tricky to write portable code using bit-fields if you are not aware of their limitations. Firstly the standard states that "A bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type." Further to this it also statest that "As specified in 6.7.2 above, if the actual type specifier used is int or a typedef-name defined as int, then it is implementation-defined whether the bit-field is signed or unsigned." This means effectively that unless you use _Bool or unsigned int your structure is not guaranteed to be portable to other compilers or platforms. The recommended way to declare portable and robust bitfields is as follows. struct bitFields { unsigned enable : 1; unsigned count : 3; unsigned mode : 4; }; When you use any of the members in an expression they will be promoted to a full sized unsigned int during the expression evaluation. When assigning back to the members values will be truncated to the allocated size. It is possible to use anonymous bitfields to pad out your structure so you do not need to use dummy names in a struct if you build a register map with some unimplemented bits. That would look like this: struct bitFields { unsigned int enable : 1; unsigned : 3; unsigned int mode : 4; }; This declares a variable which is at least 8 bits in size and has 3 padding bits between the members "enable" and "mode". The caveat here is that the standard does not specify how the bits have to be packed into the structure, and different systems do in fact pack bits in different orders (e.g. some may pack from LSB while others will pack from MSB first). This means that you should not rely on the postion of specific position of bits in your struct being in specific locations. All you can rely on is that in 2 structs of the same type the bits will be packed in corresponding locations. When you are dealing with communication systems and sending structures containing bitfields over the wire you may get a nasty surprize if bits are in a different order on the receiver side. And this also brings us to the next possible inconsitency - packing. This means that for all the syntactic sugar offered by bitfields, it is still more portable to use shifting and masking. By doing so you can select exactly where each bit will be packed, and on most compilers this will result in the same amount of code as using bitfields. Padding, Packing and Alignment This is going to be less applicable on a PIC16, but if you write portable code or work with larger processors this becomes very important. Typically padding will happen when you declare a structure that has members which are smaller than the fastest addressible unit of the processor. The standard allows the compiler to place padding, or unused space, in between your structure members to give you the fastest access in exchange for using more RAM. This is called "Alignment". On embedded applications RAM is usually in short supply so this is an important consideration. You will see e.g. on a 32-bit processor that the size of structures will increment in multiples of 4. The following example shows the definition of some structures and their sizes on a 32-bit processor (my i7 in this case running macOS). And yes it is a 64 bit machine but I am compiling for 32-bit here. // This struct will likely result in sizeof(iAmPadded) == 12 struct iAmPadded { char c; int i; char c2; } // This struct results in sizeof(iAmPadded) == 8 (on GCC on my i7 Mac) or it could be 12 depending on the compiler used. struct iAmPadded { char c; char c2; int i; } Many compilers/linkers will have settings with regards to "Packing" which can either be set globally. Packing will instruct the compiler to avoid padding in between the members of a structure if possible and can save a lot of memory. It is also critical to understand packing and padding if you are making register overlays or constructing packets to be sent over communication ports. If you are using GCC packing is going to look like this: // This struct on gcc on a 32-bit machine has sizeof(struct iAmPadded) == 6 struct __attribute__((__packed__)) iAmPadded { char c; int i; char c2; } // OR this has the same effect for GCC #pragma pack(1) struct __attribute__((__packed__)) iAmPadded { char c; int i; char c2; } If you are writing code on e.g. an AVR which uses GCC and you want to use the same library on your PIC32 or your Cortex-M0 32-bitter then you can instruct the compiler to pack your structures like this and save loads of RAM. Note that taking the address of structure members may result in problems on architectures which are not byte-addressible such as a SPARC. Also it is not allowed to take the address of a bitfield inside of a structure. One last note on the use of the sizeof operator. When applied to an operand that has structure or union type, the result is the total number of bytes in such an object, including internal and trailing padding. Deep and Shallow copy Another one of those areas where we see countless bugs. Making structures with standard integer and float types does not suffer from this problem, but when you start using pointers in your structures this can turn into a problem real fast. Generally it is perfectly fine to create copies of structures by passing them into functions or using the assignement operator "=". Example struct point { int a; int b; }; void function(void) { struct point point1 = {1,2}; struct point point2; point2 = point1; // This will copy all the members of point1 into point2 } Similarly when we call a function and pass in a struct a copy of the structure will be made into the parameter stack in the same way. When the structure however contains a pointer we must be careful because the process will copy the address stored in the pointer but not the data which the pointer is pointing to. When this happens you end up with 2 structures containing pointers pointing to the same data, which can cause some very strange behavior and hard to track down bugs. Such a copy, where only the pointers are copied is called a "shallow copy" of the structure. The alternative is to allocate memory for members being pointed to by the structure and create what is called a "deep copy" of the structure which is the safe way to do it. We probably see this with strings more often than with any type of pointer e.g. struct person { char* firstName; char* lastName; } // Function to read person name from serial port void getPerson(struct person* p); void f(void) { struct person myClient = {"John", "Doe"}; // The structure now points to the constant strings // Read the person data getPerson(&myClient) } // The intention of this function is to read 2 strings and assign the names of the struct person void getPerson(struct person* p) { char first[32]; char last[32]; Uart1_Read(first, 32); Uart1_Read(last, 32); p.firstName = first; p.lastName = last; } // The problem with this code is that it is easy for to look like it works. The probelm with this code is that it will very likely pass most tests you throw at it, but it is tragically broken. The 2 buffers, first and last, are allocated on the stack and when the function returns the memory is freed, but still contains the data you received. Until another function is called AND this function allocates auto variables on the stack the memory will reamain intact. This means at some later stage the structure will become invalid and you will not be able to understand how, if you call the function twice you will later find that both variables you passed in contain the same names. Always double check and be mindful where the pointers are pointing and what the lifetime of the memory allocated is. Be particularly careful with memory on the stack which is always short-lived. For a deep copy you would have to allocate new memory for the members of the structure that are pointers and copy the data from the source structure to the destination structure manually. Be particularly careful when structures are passed into a function by value as this makes a copy of the structure which points to the same data, so in this case if you re-allocate the pointers you are updating the copy and not the source structure! For this reason it is best to always pass structures by reference (function should take a pointer to a structure) and not by value. Besides if data is worth placing in a structure it is probably going to be bigger than a single pointer and passing the structure by reference would probably be much more efficient! Comparing Structs Although it is possible to asign structs using "=" it is NOT possible to compare structs using "==". The most common solution people go for is to use memcmp with sizeof(struct) to try and do the comparison. This is however not a safe way to compare structures and can lead to really hard to track down bugs! The problem is that structures can have padding as described above, and when structures are copied or initialized there is no obligation on the compiler to set the values of the locations that are just padding, so it is not safe to use memcmp to compare structures. Even if you use packing the structure may still have trailing padding after the data to meet alignment requirements. The only time using memcmp is going to be safe is if you used memset or calloc to clear out all of the memory yourself, but always be mindful of this caveat. Conclusion Structs are an important part of the C language and a powerful feature, but it is important that you ensure you fully understand all the intricacies involved in structs. There is as always a lot of bad advice and bad code out there in the wild wild west known as the internet so be careful when you find code in the wild, and just don't use typdef on your structs! References As always the WikiPedia page is a good resource Link to a PDF of the comittee draft which was later approved as the C99 standard Linus Torvalds explains why you should not use typedef everywhere for structs Good write-up on packing of structures Bit Fields discussed on Hackaday
  25. 1 point
    Back in the good old days™, real programmers used a modem to dial into BBS's and share programs. A little while later, we used our modems to dial into the University VAX or Unix machines to do our programming assignments. In those days, we interacted with the servers via text terminals and sometimes with graphics terminals over the serial connections (before we had ethernet in the dorm rooms). The interactivity included cursor movements, windowed screen scrolling and even COLOR!!!. I am here to tell you that these wonders are still available today through the magic of the terminal emulation modes built into most every serial terminal program. (that is why they are called terminal emulators). So here is a crash course in using this information. First: the raw data can be had here: https://en.wikipedia.org/wiki/ANSI_escape_code There are a number of terminal emulations but the most common ones were VT100 and ANSI. These were related so most codes do work. Here is how to use them in your program: First let us setup some constants 1 2 3 4 5 6 7 8 9 10 // A selection of ANSI Control Codes #define RESET "\33[0m" #define CLS "\33[2J" #define HOME "\33[1;1H" #define RED "\33[31m" #define GREEN "\33[32m" #define BLUE "\33[34m" #define WHITE "\33[37m" #define INVERSE "\33[7m" #define NORMAL "\33[27m" Next let us use them with some print commands. 1 printf(RESET GREEN "Hello" BLUE "_" RED "World" CSI_RESET "%d\n",5); Notice, that C will automatically concatenate strings when you place them all next to each other. This will issue a RESET to the terminal to ensure all other formatting like double high, flashing or inverse text are all removed. Then It sets the text color to green, prints Hello, sets it to blue and prints the _ finally red for the World, clears the formatting and prints the integer 5. That should be enough of an example for you to go wild. Each control code is about 4-5 bytes long so it does not add much to your program but it sure does make error messages stand out. Good Luck
  26. 1 point
    8-Bit controllers are very "honest" controllers, aren't they? No bus matrix, fixed interrupt latencies, no clock domain crossings.. one clock for everything. Home sweet home! But everyone has to learn new tricks, so I decided to try a jump into the deep end and booted a 32-bit SAM device from scratch. The challenge of these (former Atmel) devices is, that only framework solutions exist. Even the app notes are written in ASF framework, no one will tell you how to set things up in bare metal. So here it is, my first 32-bit and first Github project: https://github.com/dxstp/same54_init Have fun! 😎
  27. 1 point
    Interesting, I remember doing a bunch of products using 16C74, I think those are also still in use :). Which makes me think, we could easily half the price of the microcontroller on those designs by changing to a newer model, but it is just not worth the cost and especially not worth the risk ... When selecting a microcontroller, how important is price to you guys? For me I look for the best match to my requirements first and then if I have more than one solution price is for sure the tie breaker, but I have not once selected a worse match on features to reduce the price (maybe I have just been lucky?). I have to add that the BOM cost of most of these designs were significantly higher than just the microcontroller, at least one of them included a GSM module at $30, which makes the $0.50 for the micro fairly insignificant ... What do you guys think?
  28. 1 point
    After a very mild winter to date, our first cold snap has started here in British Columbia, Canada. It is -17.5C with gusty winds and a wind chill of -31 this morning.
  29. 1 point
    Undocumented instructions.. Interesting! Please share details. About the original post, this is simple laziness. In universities, professors have other interests than updating their slides. Another reason may be the simple architecture, which restricts students. So they have to deal with RMW problems, take care of cycles per instructions etc etc. I remember a simple task given to students, they had to use a very old PIC12. They had to design a piece of assembler, which displays the numbers 1 to 6 on a seven segment display. After pushing a button, the program had to stop, displaying the last active number. They had to prove that all numbers have equal probability of being displayed. Bonus points for least complexity or fastest execution..
  30. 1 point
    Ok I think I get it now. We can separate the quality of the link from the basic budget. We know on a perfect link the slew rate is instant. I will do a 10% bit time slew rate for the table calculation as that is typically what I like to have. Sorry I was too lazy to do the 8x, but it is somewhere inbetween. Over Sampling Samples from Start to centre of stop bit Budget without Sampling Uncertainty As % Sampling Uncertainty Error Budget with Sampling Uncertainty Clock Error Budget Ideal slew Clock Error Budget 10% bit slew 4x 38 +-2 sample periods 5.26% 1 period +-1 sample period +-1.32% +-0.52% 16x 152 +-8 sample periods 5.26% 1 period +-7 sample periods +-2.3% +-2.03% From the PIC16F18875 datasheet the clock accuracy is +-2%, which means that from 0 to 60C between 2.3V to 5.5V you can use the internal oscillator for 2 devices to communicate realiably only if both devices are oversampling at 16x, if you want to use 4x over sampling you will not get reliable communication as the clocks cannot remain within +-0.52%.
  31. 1 point
    How to send an email via dial up modem, circa 1984...
  32. 1 point
    Sadly, free mode is embarrassing and pro mode is expensive. It has been for a long time.
  33. 1 point
    That was what I did first, and was horrified by the code generated by XC8 2.0 in free mode. That was my attempt to force it to do it efficiently. XC8 v.1.34 in Pro mode only has one pointless instruction: 309 ;main.c: 41: return WREG; 310 07FE 0809 movf 9,w ;volatile 311 07FF 0008 return Std and Free mode have an extra pointless instruction: 320 07FD 0020 movlb 0 ; select bank0 321 07FE 0809 movf 9,w ;volatile 322 07FF 0008 return and XC8 2.0 in C90 Free mode (Opt 0) is a bit ordinary: 1218 07D4 ;main.c: 41: return WREG; 1219 movlb 0 ; select bank0 1220 07D4 0020 movf (9),w ;volatile 1221 07D5 0809 goto l9 1222 07D6 2FD7 1223 l594: 1224 07D7 line 42 1225 1226 l9: 1227 07D7 return C90 Free / Opt 1 is same as v1.x in free mode 541 ;main.c: 41: return WREG; 542 07D9 0020 movlb 0 ; select bank0 543 07DA 0809 movf 9,w ;volatile 544 07DB 0008 return C99 mode generated the same code. (And that macro generates horrible code if you are not in Pro mode...)
  34. 1 point
    Coding standards is a interesting and complex topic. We have it listed fairly high up on the list for a blog entry coming in the next couple of weeks. In the meantime this may be of some help to you 🙂 http://www.rosvall.ie/cgi-bin/genCodeStd.pl Just fill out the forms and it will generate a beautiful coding standard for you !
  35. 1 point
    That court transcript was a fantastic read (I also had a good chuckle at "Parody Bits"). That link also led me to the Barr Embedded C Coding Standard, which was also worth a read for me, as I'm currently working under mostly a set of my own rules gathered through just personal experience. Might be finally time to have a formalized standards doc. Shame the textbook is kind of a dud.
  36. 1 point
    What every embedded programmer should know about … Floating Point Numbers "Obvious is the most dangerous word in mathematics." E. T. Bell “Floating Point Numbers” is one of those areas that seem obvious at first, but do not be fooled! Perhaps when we learn how to do algebra and arithmetic we are never bound in our precision in quite the same way a computer is, and when we encounter computers we are so in awe by how “smart” they are that it becomes hard to fathom how limited floating point numbers on computers truly are. The first time I encountered something like this it had me completely baffled: void example1(void) { volatile double dval1; volatile bool result; dval1 = 0.6; dval1 += 0.1; if (dval1 == (0.6+0.1)) result = true; // Even though these are “obviously” the same else result = false; // This line is in fact what happens! } In this example we can see first hand one of the effects of floating point rounding errors. We are adding 0.6 to 0.1 in two slightly different ways and the computer is telling us that the result is not the same! But how is this possible? Surely, something must be broken here? Rest assured, there is nothing broken. This is simply how floating point numbers work, and as a programmer it is very important to know and understand why! In this particular case the compiler is adding up 0.6 and 0.1 at compile time in a different way than the microcontroller does at runtime. 0.7 represented as a binary fraction does not round to the same value as 0.6 + 0.1. This of course is merely scratching the surface, there is an impressive number of pitfalls when using floating point numbers in computer programs which result in incorrect logic, inaccurate calculations, broken control systems and unexpected division by 0 errors. Let’s take a more detailed look at these problems, starting at the beginning. Binary Fractions For decades now computers represent floating point numbers predominantly in a consistent way (IEEE 754-1985). [See this fascinating paper on it’s history by one of it’s architects, Prof W. Kahan] In order to understand the IEEE 754 representation better, we first have to understand how fractional numbers are represented when using binary. We are all familiar with the way binary numbers work. It should come as no surprise that when you go right of the decimal point it works just the same as decimal numbers, you keep on dividing by 2, so in binary 0.1 = 1/2 and 0.01 = 1/4 and so on. Let’s look at an example. The representation of 10.5625 in binary is 1010.1001. ( online calculator if you want to play ) Position 8's 4's 2's 1's 1/2's 1/4's 1/8's 1/16's Value 1 0 1 0 . 1 0 0 1 To get the answer we have to add up all of the digits, so we have 1 x8 + 1 x2 + 1 x 1/2 + 1 x 1/16 = 8 + 2 + 0.5 + 0.0625 = 10.5625 That seems easy enough but there are some peculiarities which are not obvious, one of these is that just like 1/3 is represented by a repeating decimal for decimal numbers, in binary entirely different numbers end up being repeating fractions, like 1/10, which is not a repeating decimal is in fact a repeating binaries fraction. The representation of the decimal number 0.1 in binary turns out to be 0.00011 0011 0011 0011 0011 0011 … When you do math with a repeating fraction like 1/3 or 1/6 or even 1/11 you know to take care as the effects of rounding will impact your accuracy, but it feels counter intuitive to apply the same caution to numbers like 0.1 or 0.6 or 0.7 which we have been trusting for accuracy all of our lives. IEEE 754 Standard Representation Next we need to take a closer look at the IEEE 754 standard so that we can understand what level of accuracy is reasonable to expect. 32 Bit IEEE 754 floating point number Sign (1bit) Exponent (8-bits) Significand (1.xxxxxxxx) (23 bits) The basic representation is fairly easy to understand. (for more detail follow the links from the WikiPedia page ) Standard (or Single Precision) floating point numbers are represented using 32 bits. The number is represented as a 24 bit binary number raised to an 8-bit exponent The 24 bit number is called the significand. The significand is represented as a binary fraction with an implied leading 1,meaning we really only use 23 bits to represent it as the MSB is always 1. 1 bit, the MSB, is always used as a sign bit Some bit patterns have special meanings, e.g. since there is always a leading 1 for the significand representing exactly 0 requires a special rule. There are a lot of special bit patterns with special meanings in the standard e.g. Infinity, NaN and subnormal numbers. NOTE: As you can imagine all these special bit patterns adds quite a lot of overhead to the code dealing with floating point numbers. The Solaris manual has a very good description of the special cases The decimal number 0.25 is for example represented using IEEE 754 floating point standard presentation as follows: Firstly the sign bit is 0 as the number is positive. Next up is the significand. We want to represent ¼ - in binary that would be 0.01, but the rule for is that we have to have a leading 1 in front of the decimal point, so we have to shift the decimal point by 2 positions to give us 1.0 * 2^-2 = 0.25. (This rule is meant to ensure that we will not have 2 different representations of the same number). The exponent is constructed by taking the value and adding a bias of 127 to it, this has some nice side effects, we can represent anything from -127 to +128 without sacrificing another sign bit for the exponent, and we can also sort numbers by simply treating the entire number as a signed integer. In the example we need 2 to the power of (-2) so we take 127 + (-2) which gives 125. In IEEE 754 that would look as follows: Sign (0) Exponent (125) Significand (1.0) 0 0111 1101 000 0000 0000 0000 0000 0000 If you would like to see this all done by hand you can look at this nice youtube video which explains how this works in more detail with an example - Video Link NOTE: You can check these numbers yourself using the attached project and the MPLAB-X debugger to inspect them. If you right-click in the MPLAB-X watch window you can change the representation of the numbers to binary to see the individual bits) Next we will look at what happens with a number like 0.1, which we now know has a repeating binary fraction representation. Remember that 0.1 in binary is 0.00011001100110011..., which is encoded as 1.100110011001100… x 2^(-4) (because the rule demands a leading 1 in the fraction). Doing the same as above this leads to: Sign (0) Exponent (123) Significand (1.100110011...) 0 0111 1011 100 1100 1100 1100 1100 1101 The only tricky bit there is the last digit which is a 1, as we are required to round off to the nearest 23 binary digits and since the next digit would be a 1 that requires us to round up so the last digit becomes a 1. For more about binary fractional rounding see this post on the angular blog This rounding process is one of the primary causes of inaccuracy when we work with floating point numbers! In this case 0.1 becomes exactly 0.100000001490116119384765625, which is very close to 0.1 but not quite exact. To make things worse the IDE will try to be helpful by rounding the displayed number to 8 decimal digits for you, so looking at the number in the watch window will show you 0.1 exactly! Precision Before we look at how things can break, let’s get the concept of precision under the knee first. If we have 24 bits to represent a binary significand that equates to 7.22472 decimal digits of precision (since log(2^24) = 7.22472). This means that the number is by design only accurate to the 7th digit and sometimes it can be accurate to the 8th digit, but further digits should be expected to be only approximations. The smallest absolute value normal 32-bit floating point number would be 1.0 x 2 ^ (-126) = 1.17549453E-38 represented as follows: Sign (0) Exponent (1) Significand (1.0) 0 0000 0001 000 0000 0000 0000 0000 0000 NOTE: Numbers with exponent 0 represent “Subnormal” numbers which can be even smaller but are treated slightly differently, this is out of scope for this discussion. (see https://en.wikipedia.org/wiki/Denormal_number for more info) The absolute precision of floating point numbers vary wildly over their range as a consequence of the point floating with the 7 most significant digits. This means that the largest numbers, in the range of 10^38 have only 7 accurate digits, which means the smallest distance between 2 subsequent numbers will be 10^31, or 10 Quintillion for those who know big numbers. This means that a rounding error can mean we are off by 1 Quintillion in error! To put that in context, if we used 32-bit floating point numbers to store bank balances they would have only 7 digits of accuracy, which means any balance of $100,000.00 or more will not be 100% correct and the bigger the balance gets the larger the error would be. As the numbers get smaller the error floats down with the decimal point so for the smallest numbers the error shrinks to a miniscule 10^-45. Also these increments do not match the increments we would have for decimal digits, which means converting between the two is imprecise and involves choosing the closest representation instead of the exact number (known as rounding). The following figure shows a decimal and binary representation of numbers on the same scale. Note that the binary intervals are divisible only by powers of 2 while the decimals are divided by 10’s, which means the lines to not align (follow the blue line down e.g.) These number lines also show that there are lots of values in-between which cannot be represented and we have to round them to the nearest representable number. Implications and Errors We know now that floating point numbers are imprecise and we have already seen one example of how rounding of these numbers can cause exact comparisons to fail unexpectedly. This will make up our first problem. Example PROBLEM 1 - Comparison of floating point numbers The first typical problem we will look at is the example that we started with. PROBLEM 1 : Floating point numbers are imprecise so we cannot reliably check them for equality. SOLUTION 1 : Never do exact comparisons for equality on floating point numbers, use inequalities like greater than or smaller than instead, and when doing this be sensitive to the fact that the imprecision and rounding can cause unexpected behavior at the boundaries. If you are really forced to look for a specific value consider looking for a small range e.g. check if Temp > 99.0 AND Temp < 101.0 if you are trying to find 100. void example1(void) { // If you REALLY HAVE to compare these then do something like this. // The range you pick needs to be appropriate to cover the maximum // possible rounding error for the calculation. // If we added up only 1 pair the most this error can be is 7 decimal // digits of accuracy, so these values are appropriate if ((dval1 > 0.6999999) && (dval1 < 0.7000001)) { result = true; // This time we get the truth ! } else { result = false; // And this does NOT happen ... } } The attached source code project contains more examples of this. Example PROBLEM 2 - Addition of numbers of different scale The next problem happens often when we implement something like a PID controller where we have to make thousands of additions over a time period intended to move the control variable by a small increment every cycle. Because of the limitation of 7.22 digits of precision we can run into trouble when our sampling rate is high and the value of Pi (integral term) requires more than 7.22 digits of precision. Example 2 shows this case nicely. void example2(void) { volatile double PI = 1E-5; volatile double controlValue = 300; controlValue += 1 * PI; controlValue += 1 * PI; controlValue += 1 * PI; } This failure case happens quite easily when we have e.g. a PWM duty cycle we are controlling, and the current value is 300, but the sampling rate is very high, in this case 100kHz. We want to add a small increment to the controlValue at every sample, but we want these to add up to 1, so that the controlValue becomes 301, after 1 second of control, but since 1E-5 is 8 decimal places down from the value 300 the entire error we are trying to add gets rounded away. If you run this code in the simulator you will see that controlValue remains the same no matter how many times you add this small value to it. When you change the control value to 30 it works though, which means that you can test this as well as you like, and it will look like it is working but it can fail in production! When you change the controlValue to 30 you will see that the first 2 additions work, and then it starts losing precision and it gets worse as you attempt to approach 300 An example of where this happened for real and people actually died is the case of the Patriot Missile software bug - yes this really happened! (The full story here and more about it here ) PROBLEM 2 : When doing addition or subtraction, rounding can cause systems to fail. SOLUTION 2 : Always take care that 7 digits of precision will be enough to do your math, if not make sure you store the integral or similar part in a separate variable to compensate for the precision mismatch. Imprecision and rounding of interim values can also cause all kinds of problems like giving different answers depending on the order the numbers are added together or underflows to 0 for interim values causing division by zero incorrectly when doing multiplication or division. There is a nice example of this when calculating quadratic roots in the attached source code project Example PROBLEM 3 - Getting consistent absolute precision across the range Our third problem is very typical in financial systems, where no matter how large the balance gets, for the books to balance we must always be accurate to 1 cent. The floating point number system ensures that we always get consistent precision as a ratio or percentage of the number we deal with, for single precision numbers that is 7.22 digits or roughly 0.000001 % of accuracy, but as the numbers get really big the absolute precision gets worse as numbers get bigger. If we want to be accurate to 1mm or 1cm or 1 cent this system will not work, but there are ways to get consistent absolute precision, and even avoid the speed penalty of dealing with floating point numbers entirely! PROBLEM 3 : Precision of the smallest increment has to remain constant for my application but floating point numbers break things due to lack of precision when my numbers get large. SOLUTION 3: In cases like this floating point numbers are just not appropriate to store the number. Use a scaled integer to represent your number. For money this is a simple as storing cents as integer instead of dollars. For distances this may mean storing millimeters as an integer instead of storing meters as a floating point, or for time storing milliseconds as an integer instead of storing seconds as a floating point. Conclusion It is quite natural to think that how Floating Point numbers work is obvious, but as we have seen it is everything but. This blog covered only the basics of how floating point numbers are handled on computers, why they are imprecise and the 3 most common types of problems programmers have to deal with, including typical solutions to these problems. There are a lot more things that can go wrong when you start getting numbers close to zero (subnormal), floating point underflow to 0, dealing with infinity etc. during calculations. A lot of these are nicely described by David Goldberg in this paper , but they are probably much less likely to affect you unless you are doing serious number crunching. Please do read that paper if you ever have to build something using any floating point calculations where accuracy is critical like a Patriot Missile System! References Why we needed the IEEE 754 standard by Prof W. Kahan An online Converter between Binary and Decimal The WikiPedia page on the standard Great description in the Solaris Manual Reproduction of the David Goldberg paper Youtube video of how to convert a decimal floating point number to IEEE754 format How rounding of binary fractions work About subnormal or denormal numbers Details on the Patriot Missile disaster - Link 1 Details on the Patriot Missile disaster - Link 2 Online IEEE754 calculator so you can check your compilers and hardware Source Code
  37. 1 point
    See http://www.elm-chan.org/fsw/strf/xprintf.html for one possible embedded implementation. I've seen others out there...
  38. 1 point
    So while I am in a book reviewing mood. This book by Steve McConnell (of Code Complete Fame) I now keep on my desk. It is a reference on all the important topics in software project management. The book is expertly laid out so you can read it in the order which you need for your situation. If you are on a project and you need help there are instructions in the introduction on which order to read, if you are a top-down kind of person there is an order and if you are a bottom up kind of person yet another order. I was giving a class on the topic and made a list of all the important topics to cover, when I got this book I found that it covered them all and then some. This is hands down the best project management book I have read. I really liked the discussion on the difference between a Target Date and a Deadline and also the Cone of Uncertainty in general. Whether you are using Agile methods or more traditional sequential methods to run your projects, this book will help you either way. Perhaps my favorite part of the book is an excercise on estimation where he asks readers to estimate things like the temperature of the surface of the sun, you can make a range as wide as you need to - but you have to make the range wide enough to have 90% confidence that the correct answer is within your range - most people fail miserably at doing this! Check it out here : https://amzn.to/2SszcbR
  39. 1 point
    Looks good so far! The website looks quite modern too. Is the intention to support only Microchip devices (PIC, AVR) or any microcontroller?
  40. 1 point
    Hello, This forum is very refreshing - good content and respectful users! I have a good amount of experience with PIC products, but not full-time day in and day out. My question is regarding the pros and cons of using the advanced versions of the PIC16f family vs the 18f family. I really like the analog peripherals amongst other features and I wonder what others think for new project development? For example, I am looking at a redesign of a product that currently has been using the PIC16f887 for the last 5 years. I am looking at the PIC16f18877 as a replacement unit and have spent some time wading through the 600+(!) page data sheet looking for opportunities to simplify the original circuit design and take advantage of some of these features. I am not looking for a way to avoid reading the data sheet, just some other points of view. Keith
  41. 1 point
    In general any PIC16Fxxx device such as the PIC16F887 can effectively be migrated into a PIC16F1xxxx design (such as the PIC16F18877) as that was a key objective of the F1 redesign. Immediate advantages that you will see are more FLASH, more RAM and more peripherals. Other advantages are very important but can be a little subtle unless you are an assembly programmer. In C, these additional advantages tend to reveal themselves in code that is as fast and a little smaller than the same code on a PIC18. These other advantages are a flat memory map across FLASH & RAM (at least for pointers), 2 FSR's (pointers) and more efficient banking. The PIC16F1's have 31 banks to solve the problem of a 512byte RAM/SFR limit. The new limit is 4K and there are a number of PIC16F1's that make that available. The PIC18 also has a 4K RAM limit but its FLASH limit is 2MB. There are no PIC18's with 2MB but there are many with more than 56KB (The PIC16F1 limit). The new PIC18FxxK42 family has lots of new features and more RAM but there are not very many of the new PIC18 CPU available yet. Welcome aboard and we are very interested in what you are doing with your project.
  42. 1 point
    Saw this on Hackaday today - nice to be able to connect to the cloud using an 8-bitter like this! https://hackaday.com/2018/10/27/new-avr-iot-board-connects-to-google/
  43. 1 point
    When I was looking at these, it didn't appear that they had an onboard debugger. Is that the case?
  44. 1 point
    That looks awesome! Must have been quite a job rewiring that! I have heard of a lot of people rewiring the Fisher and Paykel motors to use them as either E-Bike's or for wind turbines, people even shave the poles for decogging the motor so it will not be stalled in low wind conditions. Here is a picture of one guy filing down the poles: Now that you have that spreadsheet I think the easiest way is to select your switching frequency, scale the values to match your PWM duty cycle range and then simply set up a timer interrupt on which you read out the values and transfer them into the PWM duty cycle register. That will give you a sine wave generator. The next challenge is to control the frequency, this is a bit harder. If you had a micro with a HLT it would adjust the frequency for you automatically and you are done, but without it you need to adjust the time base, which means the range of the PWM changes as the speed changes, and generally this means you have to do math at every interrupt, which will limit how fast you can go. At that point since the 8-bit does not have a hardware multiplier it becomes necessary to move to something like the dsPIC which can handle the heavy lifting of the scaling. I like the HLT solution though, and you could also build what the HLT is doing in software, it really is just a period divider using counters... Now all of that will give you beautiful signals and maximum torque and all that Jazz, but I would just drive it with square waves myself, much simpler and you get pretty close to the performance you will need. Like I said on the mill we did that and got way more torque than we needed, so we never went all the way to SVC. EDIT: Sorry guys, I meant to say Angular Timer there when I said HLT 😞
  45. 1 point
    Pretty cool stuff! Around 2004 I rewound a single phase 3/4 HP 120 VAC 4 pole induction motor, to make a 7 VAC, 12 pole,three phase motor. I used a 220V 3 HP VFD and two step-down transformers to run it at 240 Hz which was about 1800 RPM. My idea was to run a three phase motor directly on a 12 VDC battery, or perhaps a 48V battery pack clocked at 4x frequency to get 4x power. I also made a very rough VFD using a PIC16F684, with modified sine (rectangular) waves, and it did run, but after a bit the driver transistors blew out. I know a lot more now about how to properly choose and drive MOSFETs, and that's what I plan to do with this project. I added functionality to my spreadsheet that adds a percentage of 3rd harmonic to the synthesized waveform, and I can reproduce the increased output voltage in that way. This is 15% harmonic. I updated my file: http://enginuitysystems.com/pix/electronics/Three_Phase_Sine_Waves.ods
  46. 1 point
    Or triggered by holding the PIC in reset, and clocking a defined sequence into the chip via the PGD and PGC pins, so no extra pin is required.
  47. 1 point
    I posed this question because I have seen it about as often as I have seen questions about boot loaders. LVP is a fantastic choice for programming a PICmicro used in this situation. Doing so will require a few things: 1) A hardware connection to the PICmcu from the host. 2) A host driver that can do the work. Let us take these requirements in order. First, the connection: While almost all the functions on a current generation PICmicro have selectable pins through the PPS feature, the LVP pins are NOT re-mappable. That means you must hook them up first and then you can use PPS to steer some other interface onto the pins for the "normal" behavior. Second, the software interface. While Microchip Technology has never published a programming library for a host CPU, there are some interesting sources of information. The best solution would be to adapt the software found here -> https://github.com/MicrochipTech/XPRESS-Loader Specifically, https://github.com/MicrochipTech/XPRESS-Loader/blob/master/MPLAB.X/lvp.c The interface to the I/O pins is not well abstracted (assumes a PICmcu) but it would be trivial to the average user to create some init/set/clear functions and use those. As a bonus, LVP and HV programming is identical except for the programming mode entry. If your PICmcu does not have LVP and you have an easy way to apply 9-12v to the MCLR pin, then you can simply adjust the ICSP_init and ICSP_Release functions. For the nitty-gritty of programming a PICmcu, you can look for programming specifications on Microchip's website. Here is a link to a typical specification: http://ww1.microchip.com/downloads/en/DeviceDoc/41397B.pdf
  48. 1 point
    A lot of people use printf and sprintf quite a lot without thinking how it actually works. The problem here is that the format string is interpreted at runtime, but the parameters are passed in with specific data types. If you look deeper into the specification for these functions you will find that the relationships between the data types in your format string (e.g. %d) to the parameters you are passing through after the format string is purely by agreement and the compiler cannot convert the variables to the correct type automatically at compile time. The conversion rules for printf (and sprintf) say that every parameter passed into the function in the variable argument list (that is after the comma) shall be subjected to integer promotion, which means that it will be increased in size at least to an "int". The size of an int would be different depending on the architecture or device, but it will always be at least 16-bits (as the C standard requires the int type to be at least 16-bits - even if the processor is only 8-bits) After this the parameters are packed next to each other in memory as a raw data block, and the printf code will unpack it again assuming that you followed the agreement of passing the correct data types. What happened in the code in the question above is that the constant used to multiply with was a 32-bit value, which means that we passed 3x 32 bit values into printf. So the raw data block looked like this: 32-bit 32-bit 32-bit 0x00000032 0x00000046 0x00000050 But the sprintf function is expecting 3 x integers which are on this platform 16-bits in size, which means that it is interpreting the data as we specified which is 3x 16bit integers: 16-bit 16-bit 16-bit 0x0032 0x0000 0x0046 Of course the next integers would be 0x0000 followed by 0x0050 and then the last 0x0000. The order of these are such because of the order the bytes are stored within the number (little endian, which means the lowest byte has the lowest address). If we used %ld instead the printf function would have interpreted the data correctly of course, so always make sure you use the correct format specifiers which match your parameters exactly, the compiler is not going to check this for you - it is your responsibility! When you are on a 32-bit processor where an int is actually 32-bits it is very easy to get this right by accident as everything ends up being converted to a 32-bit parameter, but you should still take care. Sometimes we use larger data types and often our code needs to be ported to other devices and this can easily become a hard bug to track down if you are not aware of these mechanics.
  49. 1 point
    I think the main driver here is the sheer volume of old projects available online or floating around in schools. New users don't understand the devices well enough to adapt the code from the old version to run in the new devices, even though the new devices are so much easier to work with. It's going to take time for all this example code to evolve from "original mid-range" to "enhanced mid-range". Some of my favourite features of the new PIC16F devices are: Internal oscillator. No need to debug a crystal circuit just to get some instant gratification. LATx registers. No need to work around RMW problems. Three hardware breakpoints. Don't lose "step over" etc. functionality after setting one breakpoint. Linear memory. Arrays larger than 80 bytes are now easy. Dual FSR pointers, able to access RAM and ROM, with automatic 16 bit pre/post increment/decrement. Hardware context save/restore for interrupt code. Simple single instruction RAM banking. Ability to access WREG the same as any other SFR.
  50. 1 point
    Ok, so every time I set up a pin as an output MCC insists on making it "Analog". It looks like this setting has something to do with the ANSEL register, but surely an output is not Analog so why do they do this?
 


  • Newsletter

    Want to keep up to date with all our latest news and information?

    Sign Up
×
×
  • Create New...