I typically program PICs in C, usually for switched-mode converters. I've heard of various static analysis tools and standards like MISRA C that can be used to help improve the reliability of code. I'd like to know more. What standards or tools might be appropriate for my context?
Embedded code validation is tricky, especially when dealing with limited-resource parts like PICs. You often don't have the luxury of coding in test cases due to the memnry constraints of the part and the often "real-time" programming done on these sorts of devices.
Here are some of my guidelines:
Write a spec if there isn't one: If you're not coding against a spec, document what your code is supposed to, what are valid inputs, what are expected outputs, how long should each routine take, what can and cannot get clobbered, etc. - a theory of operation, flowcharts, anything is better than nothing.
Comment your code: Just because something is obvious to you doesn't mean that it's obvious (or correct) to someone else. Plain-language comments are necessary for both review and code maintainability.
Code defensively: Don't just include code for normal inputs. Handle missing inputs, inputs that are out of range, mathematical overflows, etc. - the more corners you cover by your code design, the fewer degrees of freedom the code will have when deployed.
Use static analysis tools: It can be humbling just how many bugs tools like PC-lint can find in your code. Consider a clean static analysis run as a good starting point for serious testing.
Peer reviews are essential: Your code should be clean and well-documented enough that it can be efficiently reviewed by an independent party. Check your ego at the door and seriously consider any criticism or suggestions made.
Testing is essential: You should do your own validation, as well as have an independent validation of the code. Others can break your code in ways you can't possibly imagine. Test every valid condition and every invalid condition you can think of. Use PRNGs and feed garbage data in. Do whatever you can to break things, then repair and try again. If you're lucky, you'll be able to run your code in debug mode and peek at registers and variables - if not, you'll need to be crafty and toggle LEDs / digital signals to get an idea of the state of your device. Do whatever is necessary to get the feedback you need.
Look under the hood: Don't be afraid to look at the machine code generated by your C compiler. You may (will?) find places where your beautiful C code has blown up into tens if not hundreds of operations, where something that should be safe (since it's only one line of code, right?) takes so long to execute that multiple interrupts have fired and invalidated the conditions. If something becomes horribly inefficient, refactor it and try again.
Most of the same techniques for creating reliable software on a PC are also applicable to embedded development. Its helpful to separate your algorithms from the hardware-specific code, and test those separately with unit tests, simulations, static analysis, and tools like Valgrind. That way there is much less code that only gets tested on the hardware.
I wouldn't abandon C. While languages like Ada can offer some minor guarantees, it's easy to fall into the trap of thinking the language promises more than it really does.
MISRA-C is indeed very useful to improve the general code quality and minimize bugs. Just make sure you read and understand every rule, most of them are good, but a few of them doesn't make any sense.
A warning here. The MISRA document assumes that the reader is someone with extensive knowledge of the C language. If you have no such hardened C veteran on your team, but decide to get a static analyser and then blindly follow every warning given, it will most likely result in lower quality code, since you might be reducing readability and introduce bugs by accident. I have seen this happen plenty of times, converting code to MISRA compliance is no trivial task.
There are two versions of the MISRA-C document that may apply. Either MISRA-C:2004, which is still the current embedded industry de facto standard. Or the new MISRA-C:2012 which supports the C99 standard. If you have never used MISRA-C before, I would recommend you to implement the latter.
Be aware however that tool vendors usually refer to MISRA-C:2004 when they say that they have MISRA checking (sometimes they even refer to the obsolete MISRA-C:1998 version). As far as I know, the tool support for MISRA-C:2012 is still limited. I think only some static analysers have implemented it so far: Klocwork, LDRA, PRQA and Polyspace. Might be more, but you definitely need to check what version of MISRA it supports.
Before deciding, you can of course start by reading the MISRA document and see what it entails. It can be bought for £10 from misra.org, quite affordable compared to the prices for ISO standards.
Mathworks (the MATLAB folks) have a static code analysis tool called Polyspace.
As well as static code analysis, lint and such like, I would suggest careful definition and design of interfaces (with a formal review process) and code coverage analysis.
You might also want to look at guidelines for safety-critical code design, including MISRA, but also the UL1998, and IEC 61508 standards.
For a complete answer to this question, I'd suppress the thought about "code reliability" and instead think about "design reliability", because the code is just the final expression of the design.
So, start with the requirements and write and inspect those. If you don't have a requirements document, point at a random line of code and ask yourself "why is that line needed?" The need for any line of code should eventually be traceable to a requirement, even if it's as simple/obvious as "the power supply shall output 5VDC if the input is between 12-36VDC." One way of thinking about this is that if that line of code can't be traced to a requirement, then how do you know it's the right code, or that it's needed at all?
Next, verify your design. It's OK if it's completely in the code (e.g., in comments), but that makes it harder to know if the code is doing what is really meant. For example, the code may have a line that reads
output = 3 * setpoint / (4 - (current * 5));
current == 4/5 a valid input that could cause a crash? What should be done in this case to prevent the divide by zero? Do you avoid the operation altogether or degrade the output instead? Having a general note in your design document on how to handle such edge cases makes it much easier to verify the design at a higher level. So, now code inspection is easier because it's a matter of checking if the code correctly implements that design.
Along with that, code inspection should check for common errors that your IDE doesn't catch (you are using an IDE, right?) such as '=' when you meant '==', missing braces that change the meaning of 'if' statements, semicolons where they shouldn't be, etc.
As I write this, it occurs to me that it's really difficult to summarize years of software quality training/experience in a single post. I write code for medical devices and the above is an extremely simplified summary of how we approach it.