Friday, January 11, 2019

Programming Arduino in "Embedded 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 skills!

A lot of Grumble's projects (but not all) are digital in nature, using Atmel  MCU'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.  

Instead, Grumble uses what professional microprocessor programmers call "pure C" or "AVR-C" or "Embedded C".



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.

However, if you peruse the libraries that comes with the Arduino IDE you see code like Grumble's. That's because the Arduino sketch language itself is written in C and C++.

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

For example, for 328P 10 bit ADC conversion, using Embedded 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 Embedded 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);

Initially I had no idea. I need to do some reading!

Why read when there is YouTube? A really good (albeit lengthy) video about "why to use embedded C, what is embedded C, how do I get embedded C, how do I make embedded C work", is here

For anyone with previous Arduino Sketch programming experience, I think the the video is clear and easy to follow. It's worth a look.



I watched the video a couple of times and began to experiment with embedded C. Here's what I've found so far:
  • You can code using Arduino Sketch in the Arduino IDE, but throw in dashes of Embedded 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 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 video 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 End Of Life. Gone!  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 is the founmd in the  Arduino Uno R3. Get the ATMEGA328P data sheet hereYes, this datasheet has over 300 pages.  

Why all the reading? This will seem rudimentary to a professional embedded-C programmer, but maybe less so 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 pure C, I needed to understand at a very basic level not just a new programming language but also what many of the AVR MCU's registers really do. There is no way around that. It's a pretty steep uphill climb.

However, to make it easier, Atmel supplies many #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 that does....

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

I needed to understand 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 Nano board. You will need that information to interface things like LEDs, switches, etc. to your MCU as you write your code. That's because Pure-C doesn't care what pins are called on a development board, rather, it cares about the pins on the MCU IC itself.

I needed to understand a few 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 now 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 ignore others.  

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
}
 
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 online C and C++ programming classes, 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. C and C++ are statically typed languages, meaning you have to declare what your function will return, as you well as declaring the data type for each variable. For me that was unfamiliar, having previously worked with PowerShell, Python, Perl, PHP, and other dynamically typed languages where the interpreter figured out the variable type for me. 

At first this was hard to get used to, but after programming in C for a bit, I started to appreciate the extra control this gave me over my program and the extra sanity gained from knowing what each variable type was, and not getting confused when the interpreter chose a data type different than what I had in mind.

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 Embedded 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

Rotary Encoder Expermenter's Board: Improving the Hardware

Quick one this time....I have posted a few projects lately that incorporated a Raspberry Pi Pico, rotary encoder, and .96" OLED:  here ...