Friday, January 11, 2019

Programming Arduino in "Pure C": Now We're Playing with Power! First steps!!

Let's jump right into the why. 

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: this guy has extraordinary DIY skill!

A lot of Grumble's projects (but not all) are digital in nature, using Atmel CPU's.

I was curious how some of his code works and Grumble was nice enough to send me some of his Atmel C code for 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; Grumble uses what professional microprocessor programmers call "pure C" or "AVR-C" or "Embedded C" instead.



Pure C: not a Citrus Product. I've also seen it called "raw C". Or maybe "Embedded Microprocessor C/C++".   For AVR/Atmel Processors, I also hear this called "AVR-C"

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 libraries that comes with the Arduino IDE however, you begin to see a lot of code like Grumble's. That's because the Arduino sketch language itself is written in C and C++, C++ being arguably more modular due to its object oriented nature but rooted in C.

I read that this is what pros do when programming these chips.  Why? Pure C unlocks every capability of the MPU (Atmel 328P for instance), not just the features and peripherals exposed by friendly open source coders at Arduino and elsewhere. 

For example, for 328P 10 bit ADC conversion, using Pure C you can increase the gain of an incoming analog signals before being presented to the MPU for processing. See how to set that up here. As far as I know, Arduino sketch language can't do this.
 
You also potentially gain better control of a program's size since you only use code space for features you need. 

So--enough introduction!.....let's look at some of the common code fragments you see in Pure C MPU programming.

Here's an example of Pure C. What the hell does this mean?

DDRD |= ( 1 < DDD4);  // answer: same in Sketch as pinMode(4,OUTPUT);

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 experiment 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. I figure there are other exceptions as well.
  • 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 the point where I felt it wasn't useful.
  • Virtual Breadboard, as described in this post, works. Oddly, using VBBs 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.  Update 2-1-21: VBB appears to be EOL.  Oh well

Blink LED written entirely in C.  Doesn't work.  My fault.


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 describes the Atmel 328P MPU, which powers the  Arduino Uno R3 on my bench.  Get the ATMEGA328P data sheet hereYes, this datasheet has over 300 pages.  

Why all the reading? This will seem rudimentary to a real Embedded programmer, but maybe not for Arduino programmers? Embedded C is all about finding bits in an MPU's 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 MPU's registers really do. No way around that. It's a pretty steep uphill climb.

However, to make it easier, Atmel supplies tons of #define designations behind the scenes to allow you to use names of registers and bits directly in your Pure C code; these macros match the data sheet, making things a bit easier to keep straight.

1 << TWDS ; // set this bit to zero, see the datasheet for what in tarnation that does....

And--there are a whole lot of registers and #defined bits in these Atmel MPUs. There are also a weaoth of  Internet videos and web pages going over MPU specifics, like timers, peripherals, serial protocols, and so on. You can learn more about this craft from those--Get reading.

I needed to see how my Arduino board is wired--really.  If an Arduino board has a different pin designation your Pure C code may not work unless you keep which MPU pin goes to which external GPIO pin straight.

For instance, for the Nano I found its schematic here, which clearly shows which pin on the ATMEGA chip goes to which pin on the Arduino board. You will need that to interface things like LEDs, switches, etc. to your MPU as you write your code.

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.

You can use binary notation:

DDRD = 0b11111111;  // all 8 bits of port D all outputs.

So how to make DDD4 a zero?  Setting 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;

Another common thing you see all the time with Pure C code: you can mask bits using "&".  This means to the compiler: pay attention to certain bits and set others to 0.  

Best shown with an example:

#include <iostream>

using namespace std;

int main()
{
char x = 0;
char result = 0;
char b = 0b11001111;

result = b & 0b00000011;

printf("result is: %x",result);
// result is 3
}


The other thing I see all the time is bit shift: you use << or  >> .  This shifts bits left or right.  

Again best shown with an example:

#include <iostream>

using namespace std;

int main()
{
char x = 0;
char result = 0;
char b = 0x01;

result = b << 2;

printf("result is: %x",result);
// result is 4
}

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.

I needed to understand some basics about the C language and how it's different than anything else I've ever seen before. For me, taking an online C++ programming class, combined with studying open source C code that's everywhere, was necessary. 

For example: in python, if you want to use a function, you can think it up, type it into your IDE, and start using it. Not so with C.  In the C language, you have to declare your function in the same sort of manner as you declare a variable. Then you define your function, telling the compiler how you want it to work.  Details here. For me that was unexpected, but I quickly found if I skipped these steps or made a mistake in this two step process my code wouldn't compile and my function was useless. 

Enough for now? Just with these few discoveries, I can already read small chunks of Grumbles code and maybe see what he's after. 

To wrap up: Here is a Pure C program I wrote that compiles in Arduino IDE and flashes an LED.  Didn't work at first, now, works on my bench anyway. 

Remove comments, change 1's to 0's, etc., and have fun.

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////



#include<avr/io.h>
#define F_CPU16000000UL
#define myPin DDD2 //change 2 to whatever D group in you are using
#include<util/delay.h>

/*
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.
*/

int main()
  {   

/* 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  */
  
     while(1)
     {

 /*     PORTD=B11111111; 
//all port D pins (0-7) are now high.  
      _delay_ms(1000);  
      /* I put in 200 to make sure I'm not running the default "blink" sketch. */
/*
       PORTD=B00000000; //D2 is now low.  same exercise.
      _delay_ms(200);
*/

/* same way to do what we just did with 2 lines!  Go Pure C!
 */

PORTD ^=(1 << myPin ); 
     _delay_ms(1000); 
     }
  }

/* don't breathe the fumes */

UPDATE 4-6-21 I am using C a lot (vs. sketch) these days. Once you start you can't stop! Here is a cheat sheet I use for bit twiddling the I refer to a lot. Structs, not mentioned above, are explained here.

////C PROGRAMMING LOGIC CHEATSHEET

///x and y are uint8_t's:

x |= (1 << 2);  // set bit 2 in x to 1

x &= ~(1 << 2);  // Set bit 2 in x to 0

x ^= (1 << 2);   //flip existing value of bit 2, 
                 //so 1 becomes 0, 0 becomes 1. 

y = (x >> 2) & 1;  //check value of bit 2 in x 
                   //and put what you find into y.


//SAME IDEA USING STRUCT 

struct bits {
    uint8_t a0:1;
    uint8_t a1:1;
    uint8_t a2:1;
    uint8_t a3:1;
    uint8_t a4:1;
    uint8_t a5:1;
    uint8_t a4:1;
    uint8_t a5:1;
};

struct bits mybits;  //instantiate new struct

//set values
mybits.a0 = 1;
mybits.b0 = 0; (etc)

//To toggle bits, these 3 examples do the same thing.

mybits.a0 = !mybits.a0;  
mybits.a2 = ~mybits.a2;
mybits.a4 ^= 1;   

if (mybits.a0)
{
//do something if .a0==1

/*
POWERS OF 2:
bits: 7      6  5   4   3  2  1  0
vals: 128   64  32  16  8  4  2  1
*/


No comments:

Post a Comment

Minimalist Atmel 328 Development Board--How to Build One

Hello again:  if you've been following the last few posts you see that I am trying to use Arduino's IDE a lot less, and learning to ...