I have been corresponding a bit with an audio DIY master: Grumble, from the electro-music DIY audio forum. Just when I think I've done something cool, I see what Grumble comes up with and shake my head: Wow, this guy's kung fu is unreal!
A lot of Grumble's projects (but not all) are digital in nature, using Atmel CPU's.
I was curious how some of his stuff works under the hood and Grumble was nice enough to send me some of his Atmel C code to study.
Hmm, not entirely what I expected. It seems his projects are too advanced to be easily crafted from Arduino's super friendly sketch programming language. I started to dig in and found Grumble uses what professional microprocessor programmers call "pure C" instead.
Pure C: not a Citrus Product. I've also seen it called "raw C". Or maybe "C/C++".
So what's all this then? Arduino's sketch language is a library, written in C and C++, allowing us DIYers to do hard things quickly with user friendly code. If you dig deeper into the C library for Arduino, you see a lot of code like Grumble's. That's because the Arduino sketch language itself is written in C and C++--"pure C".
I don't always like keeping things easy, so let's cut Arduino's sugar coated, human friendly feel-good C library and instead use C and C++ to program our MPU in a stripped down, bare knuckle form--like it or not, this is the way the big kids roll when programming MCUs. I need to roll up my sleeves and start learning.
Here's an example of Pure C. What the hell does this mean?
DDRD |= ( 1 < DDD4); // answer: same as pinMode(4,OUTPUT);
But why read when there is YouTube? A really good (albeit lengthy) vid about "why to use pure C, what is pure C, how do I get Pure C, how do I make Pure C go, is here. For anyone with previous Arduino Sketch programming experience, I think the the vid is clear and easy to follow. Worth a look.
I watched the vid a couple of times and began to screw around with Pure C. here's what I've found so far:
- You can code with the usual Arduino Sketch feel-good stuff in the Arduino IDE, and throw in dashes of Pure C as needed. It all works; the two can mix. UPDATE: not quite. I noticed to mix analogRead() statments into your pure C plus arduino library mixed masterpiece, you need to use void setup() for initialization, not int main() as shown in the code example below.
- Does the usual SIM suspect, Tinkercad, work for Pure C development? Sort of. A vid showing Pure C on Tinkercad to get the immortal blinking LED to blink is here. But I could not get Tinkercad to work reliably beyond simple blink tests--for instance, using pure C to set up one of the simulated Atmel CPU's built in timers--all programmed in Pure C--caused the simulation to bog down in terms of performance to a point where I felt it wasn't useful.
- Virtual Breadboard, as described in this post, works. Oddly, using the IDE to talk to an Amtel ATmega328P directly didn't, but using a Nano SIM, it did. The developer told me a patch will be needed to have the Atmel SIM work but it's not available yet. Update: in version 6.08 of VBB I have verified that this is fixed.
- I have found using a NANO V3, pure C programming experiments (which to date, for me, are mostly redos of the "blink the LED" sketch) work as expected. So I can take my existing breadboard setup, Jam-o a Nano, put Pure C code into Arduino IDE, compile, and upload. We're good!
If you want to play along at home: here's a rundown of some essentials I needed to get started with Pure C:
I needed the datasheet for the Atmel chip that constitutes the guts of the Arduino on my bench. For me, it was an ATMEGA328P, data sheet for that is found here. Why? Pure C is all about finding bits in registers and manipulating them. These registers hold essential information like "is this pin an input or output?" "Do you want this pin go to binary high or low?" "How should this timer work?" and so on. You flip bits in these registers--akin perhaps to turning on and off physical switches--to make things work the way you want. That means, to master this pure C business, I needed to understand at a very basic level what many of the AVR's registers and bits do. No way around that. It's a pretty steep uphill climb.
And--there are a whole butt-ton of registers in these Atmel CPUs. Sorry.
I needed to see how my Arduino board is wired--really. Pure C goes straight to the heart of an Arduino: the CPU itself. If a pin on the Atmel CPU isn't wired to the same pin on the Arduino PCB, or if the board has a different pin designation, your Pure C code may not work.
For the Nano I found the Nano Eagle schematic, here, which clearly shows which pin on the ATMEGA chip goes to which pin on the Arduino board. With that in hand I could successfully move LEDs around, change my pure C code, and still have them blink.
I needed a simple breadboard setup: Nano and an LED.
I needed to understand the strange statements you see over and over when reviewing pure C code. One we already saw:
DDRD |= ( 1 << DDD4);
This means, for register DDRD, put a 1 (high) into bit DDD4. This specifically tells the Nano's CPU that pin D4 is going to be an Output.
Getting further into beginner's stuff:
You can OR these "bit shift" statements together:
DDRD = ( 1 << DDD4) | ( 1 << DDD5) | ( 1 << DDD6) ;
Same idea, but we are putting a 1 in for bits 4, 5, and 6. So D4, D5, D6 are outputs.
So how to make DDD4 a zero? So DDD4 is an input? Use a NOT, also known as an XOR:
DDRD ^=(1 << DDD4);
And another way to change DDD4 from 1 to a zero (so, flip things?) This tests DDD4, if DDD4 already contains a 1, it makes DDD4 a zero. If it's a zero, make it a one.
DDRD &=~(1 << DDD4);
You also set the entire register--for instance, make every pin in the Pin Group D inputs. Here 0x00 means use hex (base 16) to set all the bits to zero.
DDRD = 0x00;
And this sets them all to one:
DDRD = 0xFF;
Tip (for me anyway?) Remember that even after you power cycle, the CPU chip "remembers" your register settings. Compiling and uploading code again won't change registry bits unless you specifically twiddle them. Not so with a blank Arduino sketch, which behind the scenes (as far as I can tell) resets everything to null before applying new code.
Enough for now? Just with these few discoveries, I can already read small chunks of Grumbles code and maybe see what he's after. Go A's! To wrap up: Here is a Pure C program I wrote that compiles in Arduino IDE and flashes an LED. Works on my bench anyway. Remove comments, change 1's to 0's, etc., and have fun.
#define myPin DDD2 //change 2 to whatever D group in you are using
my expanded pure C blink demo.
Nano V3,0, with LED tied to pin D4;
1K resistor between LED cathode and ground so to not fry the LED.
uncomment stuff to experiment.
/* all pins in D pin group are now input. For DDRD, 1 means output and 0 means input. */
DDRD = B00000000;
/* we are putting a 1 into bit DD4 in the DDRD //register, making D4 on the nano an output. */
DDRD |= (1<<myPin);
/* make DDD4 (pin D4 on Nano) a zero, now it won't flash because it's an input. So do the opposite of what I say. */
// DDRD &=~(1 << myPin);
/*test DDD4 to see if it's a one. If it's a zero, toggle the //bit to one. If it's one, toggle to zero */
// DDRD ^=(1 << myPin );
// BONUS COMMAND USING HEX vs. BIT TWIDDLING.
/* this will set all D pins but leave TX RX to output */
//DDRD = DDRD | 0x00 ;
/* this will set all D pins but TX RX to input */
//DDRD = DDRD | 0xFF ;
/*Note: if you screw up TX and RX,
* which are part of the DDDx, DDRX, PINx" pin group mishpucha,
* upload into your arduino a normal sketch with serial.begin(). this fixes it.
/* WHILE(1) is the same as the main repeat forever loop you see on arduino */
//all port D pins (0-7) are now high.
/* I put in 200 to make sure I'm not running the default "blink" sketch. */
PORTD=B00000000; //D2 is now low. same exercise.
/* same way to do what we just did with 2 lines! Go Pure C!
PORTD ^=(1 << myPin );
/* don't breathe the fumes */