Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

GNU C++ BlinkenLED Part 1 on the AVR (ATmega1284P with MikroElektronika EasyAVR6) and Atmel AVR Studio 5.1

Pete Brown - 15 January 2012

In this Part 1 post, I'll show how to interface with ATmega ports and pins to light up LEDs on a board. The follow-up post will show how to use the timer to actually blink the LED.

Most of the Microcontroller code out in the public is C or Assembly. I have nothing personal against assembly or C (and may end up using a little assembly in the future), but I decided I wanted to write C++, as I like its encapsulation and structure when compared to straight C.

I've written some other posts about getting started with these boards and environments. Be sure to check them out:

Disclaimer: I don't have a lot of experience with the AVR. I'm writing as I'm learning. If you find a mistake below, please point it out in the comments.

C++ Disclaimer: If code size and execution speed are your primary concerns, you can certain do a bit better in C or even more so in assembly. C++ does add some overhead, especially if you go nuts on the class hierarchies. I've tried to strike a balance between size, speed, and encapsulation in my own libraries here, and also to show the differences. If you intend to write for some of the ATtiny chips with their miniscule 2k or 4k (sometimes a bit more) program memory, write some macro-oriented C or better yet, break out that assembler. For the larger chips with a little more headroom, C++ is a fun way to go, and is my preference.

Platform Disclaimer: Most of my readers will be far more productive on the .NET Micro Framework and the Netduino, FEZ Devices, or Gadgeteer. If you're not doing heavy speed-critical signal processing, you'll probably have more fun with NETMF and get from zero to application much more quickly. For people not using C# but who wish to continue with C/C++ on the AVR specifically, Arduino has most of these types of things figured out in its rich library. It's not really C++, but it's a good approach to developing on a subset of the AVR MCUs. If you're interested more in just getting something done specifically on the AVR, I recommend looking at that. Finally, if you want to write C on the metal, Atmel also makes their own AVR library which helps abstract away the differences between MCUs. I personally found that confusing to use, with many different ways to accomplish the same tasks, and a large number of poorly documented APIs to learn. They were C as well, and without great naming conventions on their part, it was next to impossible to get a ful picture of everything available in a particular problem domain. In my case, I specifically want to learn MCU programming without all that baggage, so I'm simply coding on the metal but using C++.

Ports and pins

This family of MCUs have 8*4 (32) General Purpose Input/Output (GPIO) pins. They can all be used for GPIO, but some ports can also be assigned different functions. Here's are the pin configurations for the PDIP and the TQFP/QFN/MLF surface mount versions from the data sheet. (There are other pages covering the tiny VFBGA and DRQFN versions, in case you wanted super tiny surface mount versions)

image

I'm using the PDIP version on the left. On that, you can see, for example, that pins 14 through 21 (port D) have the transmit (TXD) and receive (RXD) functions for the USARTs. When you use those pins for the USART, you can't use them for GPIO, so you need to be smart about which pins you use for which functions. (I've been doing a lot with port D and MIDI - more on that in a future post.) You can also see that the entire family of chips ATmega164A, ATmega164PA, ATmega324A, ATmega644A, ATmega644PA, ATmega1284, and ATmega1284P all share the same pin configuration and device layout. (and yeah, I'll admit that listing them all here was for search engines <g>). Where they differ is in memory. Here's the table from the same data sheet.

image

In this post, I'll concentrate on using Port A. As I previously mentioned, the MCU I'm using is the ATmega1284P, so I have a whopping (in MCU terms) available program size of 128K. That's as much total memory as the Commodore 128 computer sitting behind me on my desk, and that doesn't even count the 16K available RAM and 4k available EEPROM. In a nutshell, here's how that memory is used:

  • Flash: This is the memory used to store your program code. When you "program" the MCU, this memory is directly consumed by the instructions for your application.
  • EEPROM: This is where you can store persistent settings, like configuration set by the user of your end product.
  • RAM: Allocated variables, arrays etc. You can blow through 16K very quickly if you try and do things like you would on the desktop (like a lookup table of 16 bit values to make SIN calculations faster), so you need to be smart about that.

Now, back to pins and ports. The ATmega and ATxmega MCUs by Atmel (and possibly the ATtiny and others - I haven't checked) group their IO pins into ports. The ATmega1284 has four ports A, B, C and D, each of which has 8 pins. By grouping this way, a single 8 bit register can contain the digital information for each in in the port.

image

So, if you know the base address of Port A's pins data register is 0x20 (or 0x00, for reasons explained in the data sheet), you can find out the value of pin 2 using code like this:

#define PINA_ADDRESS 0x20

isHigh = (*PINA_ADDRESS) & 0b00000100;

You can set pin 2 to high using this code, assuming that all the pins were low before (this actually *toggles* pin 2):

#define PINA_ADDRESS 0x20
(*PINA_ADDRESS) |= 0b00000100;

After you set it, that bit of PINA will be 1 if the pin is high, and 0 if it is low, regardless of what your code actually sets it to when toggling.

You can see how using preprocessor defines like that makes the code easier to read, but still results in a really tiny program. By default, the AVR libraries include even nicer preprocessor macros to get to individual pins, but we're not using them here. Of course, I did oversimplify a little, at least in the second example. There are other things you must consider before just setting pin values.

Direction and Data Registers

When you want to use pins on a port, you should configure its pins as input or output. For Port A, this involves the DDRA (Port A Data Direction Register). This is another 8 bit register which defines the direction for each pin: 1 is output, 0 is input.

Also, when reading and writing values, you don't always use the same register.

image

You have to read the data sheet about 100 times to make sense of these groups. When compared to the xmega, I found the uses a bit opaque. So, here's my summary:

  • To mark a pin as input, write a 0 to the appropriate bit in the DDRx register.
  • To mark a pin as output, write a 1 to the appropriate bit in the DDRx register
  • To read the value of a pin, check the appropriate bit in the PINx register
  • To TOGGLE the value of a pin, write to the appropriate bit in the PINx register
    • This means you need to read the pin value before you toggle it.

Beyond that, there are some other registers that affect the entire port. This table is useful for figuring out what they do.

image

Basic Implementation

Let's first look at an implementation that looks like C code in a CPP file. We'll use that as the benchmark for the rest of the application.

Creating the project

First, I assume you have Atmel AVR Studio 5.1 installed on your machine. If not, please refer to the two previous posts linked at the top of this post. I'm also going to be using a JTAGICE-compatible programmer and debugger here, also covered in that previous post.

Create a new project in AVR Studio 5.1. If you're running only 5.0, you won't see the C++ options unless you have the C++ add-in. Also notice how these show up under "C/C++" and not under the board-specific folders. We won't be using the board templates here.

image

I named the project AvrBlinkenLed.

The next step is to pick the processor. Make sure you pick the one you're using. I'm using the ATmega1284P. The easiest way to find your processor is to enter its model number in the search box at the upper right.

image

Once you select the processor, Visual Studio will load up the project template and put you right in the main .cpp file. You'll want to adjust a couple things before you do any real coding, however.

Setting clock speed and Debugger

Next, we'll need to set the clock speed. You could leave it at the default 1Mhz, as we're not doing anything special here. However, why not bump it up to 8Mhz? To do that, we'll need to make changes in a few places. First, we'll need to actually set the value on the chip. In this case, we only need to clear the multiplier fuse which forces the ship at 1MHz by default.

Go into Tools | AVR Programming. After a moment, the AVR Programming tool will come up. It'll look for your JTAG programmer and, if found, will display it in the tool list at the left and the found MCU in the Device drop down to its right. If it was not found, your JTAG programmer/ debugger is not set up correctly, or your board isn't connected. Refer to the manufacturer instructions to fix that before going any further.

image

If you only have the one JTAG programmer/debugger installed, and aren't doing any chaining of devices (if you're reading this post, you almost certainly are not) simply click the "Apply" button to pull back the MCU information. Click on the "Fuses" tab.

image

WARNING: Setting some fuses or lock bits can brick your MCU and require special high-power parallel programming to reset it. Be sure you completely understand the purpose of a given fuse or lock before you do anything with it.

I had already cleared the CKDIV8 fuse in my MCU. If you just got yours, it will be set. What this does is divide the 8MHz clock down so you get a CPU clock of 1MHz. Clear that fuse, and only that fuse, and then click the "Program" button on the right. Make sure if works.

While there, make sure the JTAGEN fuse is set so you can use the JTAG debugger. If not set, set it and program that one fuse setting.

Next, go into the project properties (right-click the AvrBlinkenLed project and select "Properties") and click the "Tool" tab. This is where you set up the programmer/debugger to use.

image

Pick the JTAG programmer from the list. Once you do that, change the interface on the right to JTAG so you can debug. By default, it is ISP (In System Programmer) which only allows programming. Then change the JTAG clock so it's some value lower than 8Mhz / 4.

image

Finally, we'll define a compiler constant in the main file. The F_CPU constant is a standard approach you'll see often. In fact, if you use some of the AVR libraries, they require that specific constant to be set.

/*
* AvrBlinkenLed.cpp
*
* Created: 1/15/2012 4:21:24 PM
* Author: Peter.Brown
*/
#define F_CPU 8000000
//#include <avr/io.h>

int main(void)
{
while(1)
{
//TODO:: Please write your application code
}
}
That's the default template with the addition of the F_CPU constant. Also note that I commented out the io.h include. We're going to do things from scratch here. The code doesn't do anything yet, though, although it does compile.

A few more helpful constants and macros

One reason straight C code tends to be so efficient, is that there are almost no constants, enums, or variables in use for chip-specific stuff. Instead, most everything is done in the preprocessor, which ends up resolving down to pointer manipulation.

We're not taking that approach here, but we'll still use some constants to help us out.

Add a new include file to the project, named ATmega1284P.h

image

The file will contain the processor-specific constants and types we want to use. In this case, the addresses for the registers.

/*
* ATmega1284P.h
*
* Created: 1/15/2012 4:41:26 PM
* Author: Peter.Brown
*/


#ifndef ATMEGA1284P_H_
#define ATMEGA1284P_H_

typedef volatile unsigned char register8_t;

#define PINA (*(register8_t*) 0x20)
#define DDRA (*(register8_t*) 0x21)
#define PORTA (*(register8_t*) 0x22)

#endif /* ATMEGA1284P_H_ */

Once you do that, we have enough to be able to turn the LEDs on or off. We're going to light the LEDs in a pattern as shown in this photo. You can see from the little diagram on the board (under the RoHS symbol) that the LEDs are configured in current source configuration, so setting them to 1 will light them up.

image

Here's the code for the main file. Doing this will set the above pattern for the LEDS.

/*
* AvrBlinkenLed.cpp
*
* Created: 1/15/2012 4:21:24 PM
* Author: Peter.Brown
*/
#define F_CPU 8000000

#include "ATmega1284P.h"

int main(void)
{
DDRA = 0b11111111;
PINA = 0b10101010;

while(1)
{
//TODO:: Please write your application code

}
}

In this code, I set all 8 pins to output, and then set alternated pins to 1 to set them to high (it actually toggles the pin value, but because they were all low (0) to begin with, this works). In a current source configuration, where the LEDs get their voltage from the MCU and the MCU is the source of the current, setting the pin to high will cause the LED to light. If they were in a current sink configuration, where the MCU provides ground reference, setting them to low (0) would light the LEDs. The LED class we create will support either configuration.

Deploying and Running

To deploy the code, first compile. You'll see output which states how small the code is (178 bytes total, wow!)

------ Build started: Project: AvrBlinkenLed, Configuration: Debug AVR ------
Build started.
Project "AvrBlinkenLed.cppproj" (default targets):
Target "PreBuildEvent" skipped, due to false condition; ('$(PreBuildEvent)'!='') was evaluated as (''!='').
Target "CoreBuild" in file "C:\Program Files (x86)\Atmel\AVR Studio 5.1\Vs\Compiler.targets" from project "D:\Documents\Docs\Projects\AvrBlinkenLed\AvrBlinkenLed\AvrBlinkenLed.cppproj" (target "Build" depends on it):
Task "RunCompilerTask"
C:\Program Files (x86)\Atmel\AVR Studio 5.1\make\make.exe all
AvrBlinkenLed.cpp
Invoking: AVR8/GNU C++ Compiler
"C:\Program Files (x86)\Atmel\AVR Studio 5.1\extensions\Atmel\AVRGCC\3.3.1\AVRToolchain\bin\avr-g++.exe" -funsigned-char -funsigned-bitfields -O1 -fpack-struct -fshort-enums -g2 -Wall -c -mmcu=atmega1284p -o"AvrBlinkenLed.o" ".././AvrBlinkenLed.cpp"
Finished building: .././AvrBlinkenLed.cpp
Building target: AvrBlinkenLed.elf
Invoking: AVR8/GNU C++ Linker
"C:\Program Files (x86)\Atmel\AVR Studio 5.1\extensions\Atmel\AVRGCC\3.3.1\AVRToolchain\bin\avr-g++.exe" -o AvrBlinkenLed.elf AvrBlinkenLed.o -Wl,-Map="AvrBlinkenLed.map" -Wl,-lm -mmcu=atmega1284p
Finished building target: AvrBlinkenLed.elf
"C:\Program Files (x86)\Atmel\AVR Studio 5.1\extensions\Atmel\AVRGCC\3.3.1\AVRToolchain\bin\avr-objcopy.exe" -O ihex -R .eeprom -R .fuse -R .lock -R .signature "AvrBlinkenLed.elf" "AvrBlinkenLed.hex"
"C:\Program Files (x86)\Atmel\AVR Studio 5.1\extensions\Atmel\AVRGCC\3.3.1\AVRToolchain\bin\avr-objcopy.exe" -j .eeprom --set-section-flags=.eeprom=alloc,load --change-section-lma .eeprom=0 --no-change-warnings -O ihex "AvrBlinkenLed.elf" "AvrBlinkenLed.eep" || exit -j .eeprom --set-section-flags=.eeprom=alloc,load --change-section-lma .eeprom=0 --no-change-warnings -O ihex "AvrBlinkenLed.elf" "AvrBlinkenLed.eep" || exit 0
"C:\Program Files (x86)\Atmel\AVR Studio 5.1\extensions\Atmel\AVRGCC\3.3.1\AVRToolchain\bin\avr-objdump.exe" -h -S "AvrBlinkenLed.elf" > "AvrBlinkenLed.lss"
"C:\Program Files (x86)\Atmel\AVR Studio 5.1\extensions\Atmel\AVRGCC\3.3.1\AVRToolchain\bin\avr-size.exe" -C --mcu=atmega1284p "AvrBlinkenLed.elf"
AVR Memory Usage
----------------
Device: atmega1284p
Program: 178 bytes (0.1% Full)
(.text + .data + .bootloader)
Data: 0 bytes (0.0% Full)
(.data + .bss + .noinit)
Done executing task "RunCompilerTask".
Done building target "CoreBuild" in project "AvrBlinkenLed.cppproj".
Target "PostBuildEvent" skipped, due to false condition; ('$(PostBuildEvent)' != '') was evaluated as ('' != '').
Target "Build" in file "C:\Program Files (x86)\Atmel\AVR Studio 5.1\Vs\Avr.common.targets" from project "D:\Documents\Docs\Projects\AvrBlinkenLed\AvrBlinkenLed\AvrBlinkenLed.cppproj" (entry point):
Done building target "Build" in project "AvrBlinkenLed.cppproj".
Done building project "AvrBlinkenLed.cppproj".

Build succeeded.
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========

The next step is to take the hex file that was created and load that to the board via the JTAG programmer. Go under Tools | AVR Programming again, and select the "Memories" tab after hitting "apply" to pull up the ATmega we're using.

image

Select the hex file from your project. You can see where it's located on my file system. The programmer will remember this from project to project, so always double-check to make sure you're programming the right code. Once sure, click the "Program" button. You'll see the following status:

image

You should immediately see the output on your test board. If not, close the AVR Programming tool and hit the Reset button on your board.

Now, all of that is wonderfully efficient, but not particularly friendly or reusable. So, let's look at how to do this another way.

First Implementation in real C++

The C code is simple and very tight. However, once you get into a complex application, the code can get confusing and jumbled. The nature of a language like C requires extreme discipline to keep the code compartmentalized and reduce dependencies between the different modules. Often times, the developer will trade away some of that for the sake of performance or code size. The AVR libraries that come with AVR Studio include a fair bit of that.

To be clear, you can write very good code in C as long as you have discipline. You can also write crap code in C++, but the compiler and language give you a few more things to help you do the right thing.

The IOPort class

In my first version of this code, I created an IOPin class rather than IOPort. I did this because I wanted to make my library smell a bit like the NETMF libraries where you code against DigitalInput and DigitalOutput classes. However, that meant creating an instance of the class for each pin you wanted to manipulate - an entire class for 3 bits (literally, bits) worth of data. It also didn't allow me to group operations at the port level. So, in the version I'm presenting here, I went at it at the port level with the IOPort class.

First, create a header file named IOPort.h

/*
* IOPort.h
*
* Created: 1/15/2012 5:43:58 PM
* Author: Peter.Brown
*/


#ifndef IOPORT_H_
#define IOPORT_H_

#include "ATmega1284P.h"

enum PinDirection
{
DirectionInput = 0,
DirectionOutput = 1
};

class IOPort
{
protected:
volatile register8_t* _port;
volatile register8_t* _pins;
volatile register8_t* _ddr;
public:
IOPort(register8_t* portRegister, register8_t* pinsRegister, register8_t* ddrRegister) :
_port(portRegister),
_pins(pinsRegister),
_ddr(ddrRegister) {}

void SetPortDirection(register8_t directionBits);
void SetPortValues(register8_t valueBits);
void GetPortValues(register8_t &valueBits);

void SetPinDirection(int pin, PinDirection direction);
void SetPinValue(int pin, bool value);
void GetPinValue(int pin, bool &value);
};



#endif /* IOPORT_H_ */

You can see the class has separate methods for working with individual pins versus the entire port. In that way, we can do bulk port operations and individual pin operations from the same class. We can get away with that here, unlike in the .NET Micro Framework, because we know that all the AVR pinsNow for the implementation. Create a code file IOPort.cpp

/*
* IOPort.cpp
*
* Created: 1/15/2012 6:03:06 PM
* Author: Peter.Brown
*/

#include "IOPort.h"

void IOPort::SetPortDirection(register8_t directionBits)
{
*_ddr = directionBits;
}
void IOPort::SetPortValues(register8_t valueBits)
{
*_pins = valueBits;
}
void IOPort::GetPortValues(register8_t &valueBits)
{
valueBits = *_pins;
}

void IOPort::SetPinDirection(int pin, PinDirection direction)
{
// assumes pin is correctly in the range of 0..7
if (direction == DirectionOutput)
*_ddr |= (1 << pin);
else
*_ddr ^= (1 << pin);
}


void IOPort::SetPinValue(int pin, bool value)
{
// assumes pin is correctly in the range of 0..7

// if the pin was already high and we want it high, do nothing.
// if the pin was already low and we want it low, do nothing.
// if the pin was high, and we're requesting low, toggle it
// if the pin was low, and we're requesting high, toggle it


if (*_pins & (1 << pin))
{
// pin is currently high

if (!value)
*_pins = (1 << pin); // toggle pin because we want it low
}
else
{
// pin is currently low

if (value)
*_pins = (1 << pin); // toggle pin because we want it high
}

}

void IOPort::GetPinValue(int pin, bool &value)
{
value = (bool)(* _pins & (1 << pin));
}

Note how writing a 1 to the pin doesn't set it high, it toggles it. That means we need to read the current value of the pin, and then decide whether it needs to be toggled or not.

This IOPort class doesn't support everything. For example, I haven't included here the code to set the pull-up resistors. I also didn't add anything optional to the constructor to set initial direction or values from that call. However, it works for our purposes in this example.

Supporting multiple types of processors

In the code I'm using for my own projects, I don't include the processor-specific include file in every class. Instead, I include a common include file which has a preprocessor constant for the processor I'm using and then makes a decision as to which processor-specific include file to pull in. I blatantly stole this idea from the AVR libraries, as it's a good one.

Lighting up some LEDs

Now to light up some LEDs. I very slightly changed the pattern so you can be sure you're seeing the newly flash firmware, and not the original version. Here's the new code for the main function.

/*
* AvrBlinkenLed.cpp
*
* Created: 1/15/2012 4:21:24 PM
* Author: Peter.Brown
*/
#define F_CPU 8000000

#include "ATmega1284P.h"
#include "IOPort.h"

int main(void)
{
//DDRA = 0b11111111;
//PINA = 0b10101010;

IOPort portA(&PORTA, &PINA, &DDRA);

portA.SetPortDirection(0xFF);
portA.SetPortValues(0b10101111);

while(1)
{
//TODO:: Please write your application code

}
}

Program it on the MCU and look at the LEDs. You should now see pins 0-3 lit up, as well as pins 5 and 7. This works because the pins were, by default, all low. Calling SetPortValues toggles all the values for that port. If you called it again with the same pattern, they'd all turn off.

I prefer the ATxmega approach

The ATxmega (the follow-on to the ATmega) has a couple different registers to accomplish the task of setting the pin value. One is OUTSET where any set bits cause the pins to go high. The other is OUTCLR where any set bits cause the pins to go low. In that way, you don't have to do a read-compare-modify cycle. If that chip were available in PDIP so creating DIY kits for other people who don't want to surface mount solder, I'd be in heaven.

As to size: if I comment out the Pin-specific functionality, in order to get a fair comparison with our original program, the program compiles to 320 bytes. Compare that to the 178 bytes of the macro-version. It's a bit under twice the size for this tiny program. Whether that's a concern to you is, well, your concern :) If you leave in the pin-specific functionality, you get around 510 bytes. On a processor with 128K, that's not a big deal, but if you're looking at a processor with only 2K total memory, that can certainly be an important distinction. At the different levels of optimization, I get the following program sizes:

Optimization level Program Size in Bytes
None (-O0) 856
Optimize (-O1) default 512
Optimize more (-O2) 496
Optimize most (-O3) 496
Optimize for size (-Os) 484

The different optimization levels certainly make an impact on size. Beware, though, as sometimes optimization can make the code larger, or slower. It can also make it more difficult to debug or trace. I tend to leave the optimization on the default -O1.

In addition, you can definitely trim this down a bit by removing some of the conversions, the enum, and doing a little more optimization. You can even do a little inline assembly, but I'm not a big fan of the AVR G++ approach to that, using strings. I used to write a fair bit of inline assembly in Borland C++ for DOS, and to do that, you simply had an asm block with statements in it like asm { mov AX, AY }.

Without actually trying it, I suspect that the code to construct the string would take up more space than the inline assembly would give us back, so I'm going to leave it out of this example.

Abstracting Away LED Behavior

LEDs can be connected a two main ways: in current source or current sink configuration. I would like to create an LED class which properly works regardless of how you hook up the LEDs. 

Create a header file named Led.h

/*
* Led.h
*
* Created: 1/15/2012 7:12:19 PM
* Author: Peter.Brown
*/

#ifndef LED_H_
#define LED_H_

#include "ATmega1284P.h"
#include "IOPort.h"

enum LedConfiguration
{
LedCurrentSource,
LedCurrentSink
};

class Led
{
protected:
IOPort* _port;
LedConfiguration _configuration;
uint8_t _pin;

public:
Led(IOPort* port, uint8_t pin, LedConfiguration configuration) :
_port((IOPort *)port),
_configuration(configuration),
_pin(pin) {};


void TurnOn();
void TurnOff();
};

#endif /* LED_H_ */

Next, create the implementation file Led.cpp. The code in this file will set the pin values based on whether or not we're in Source (1 to light) or Sink (0 to light) mode.

/*
* Led.cpp
*
* Created: 1/15/2012 7:18:36 PM
* Author: Peter.Brown
*/

#include "Led.h"


void Led::TurnOn()
{
_port->SetPinValue(_pin, (_configuration == LedCurrentSource));
}

void Led::TurnOff()
{
_port->SetPinValue(_pin, !(_configuration == LedCurrentSource));
}

To make that work, we're going to need another type, so let's add the following to the processor-specific header file ATmega1284P.h. At the same time, we'll change the definition of register8_t to use this type.

typedef unsigned char uint8_t;
typedef volatile uint8_t register8_t;

I need uint8_t because I'm keeping the pin number in the class, and don't want to allocate a whole 16 bit int to hold a number between 0 and 7. Memory on these devices is even more scarce than program memory so you want to use small types to keep the stack small and the data (if you allocate any) small.

Why no New Operator?

You'll notice that I'm not using the new operator anywhere, instead I'm allocating everything as local variables. At some point, I may change that, but at least for now, the price isn't worth it. Adding in the new operator, and all the malloc stuff it requires, adds a huge amount of code to your application. By default, AVR G++ doesn't include it in so you can save that 4-10k.

The new main function looks like this:

/*
* AvrBlinkenLed.cpp
*
* Created: 1/15/2012 4:21:24 PM
* Author: Peter.Brown
*/
#define F_CPU 8000000

#include "ATmega1284P.h"
#include "IOPort.h"
#include "Led.h"

int main(void)
{
IOPort portA(&PORTA, &PINA, &DDRA);

portA.SetPortDirection(0xFF);
//portA.SetPortValues(0b10101111);

Led ledA0(&portA, (uint8_t)0, LedCurrentSource);
Led ledA1(&portA, (uint8_t)1, LedCurrentSource);
Led ledA2(&portA, (uint8_t)2, LedCurrentSource);
Led ledA3(&portA, (uint8_t)3, LedCurrentSource);
Led ledA4(&portA, (uint8_t)4, LedCurrentSource);
Led ledA5(&portA, (uint8_t)5, LedCurrentSource);
Led ledA6(&portA, (uint8_t)6, LedCurrentSource);
Led ledA7(&portA, (uint8_t)7, LedCurrentSource);

ledA0.TurnOn();
ledA1.TurnOn();
ledA2.TurnOn();
ledA3.TurnOn();
ledA4.TurnOn();
ledA5.TurnOn();
ledA6.TurnOn();
ledA7.TurnOn();

while(1)
{
//TODO:: Please write your application code

}
}

Rather than inherit LED from the port, as I did in the pin-based version in some other code I've written, I decided to have the LED simply take in an instance of the port. The LED class assumes you've already configured the LED pin as output, as that is an operation that is often done an entire port at a time. Again, another small concession to efficiency without sacrificing the main benefits of C++.

If you want to test that the led is properly toggling the values, uncomment the SetPortValues call there to give the port some initial values. Or, you can just make the LEDs toggle like we will in the next post.

At this point, if you run the application, you should see all eight LEDs lit

image

In the next post, we'll learn what it's going to take to blink these LEDs with a specified time interval.

           

Source Code and Related Media

Download /media/82655/avrblinkenled.zip
posted by Pete Brown on Sunday, January 15, 2012
filed under:            

9 comments for “GNU C++ BlinkenLED Part 1 on the AVR (ATmega1284P with MikroElektronika EasyAVR6) and Atmel AVR Studio 5.1”

  1. DTsays:
    I'm afraid you've totally missed the point of the PORTx register. It allows direct control of the port state. You don't need any of the toggle function stuff. Bizarre, but novel, I'll give you that.
  2. Konstantinsays:
    Hello everyone.
    If you interested in good C++ abstraction of GPIO ports you can look at my git repository:
    https://github.com/KonstantinChizhov/Mcucpp
    It allows you to work with ports like this:
    Porta::Write(0x55); // -> PORTA = 0x55;
    // set pins with mask
    Porta::Set(0xAA); // -> PORTA |= 0xAA;
    // clear pins with mask
    Porta::Clear(0xF0); // -> PORTA &= ~0xf0;
    With individual pins:
    // set pin to logical 1
    Pa1::Set();
    // set pin to logical 0
    Pa1::Clear();
    // toggle pin state
    Pa1::Toggle();
    And you can join a arbitrary group of pins in one list and work with it as a single entity:
    // Definition of group of IO pins which acts like a virtual IO port.
    // One group can contain up to 32 pins from different ports.
    // Pins in the group can have an arbitrary order.

    typedef PinList<Pa1, Pb0, Pa2, Pb1, Pc3, Pc4, Pc5> Group1;
    Group1::SetConfiguration(Group1::Out);
    // write a value to group
    Group1::Write(0x55);
    // set pins with mask
    Group1::Set(0xAA);
    // clear pins with mask
    Group1::Clear(0xF0);

    The compiler will generate all required port operations for you. It can be used, for example, for connecting LCD pins in any order.
    You can find complete example here:
    https://github.com/KonstantinChizhov/Mcucpp/blob/master/examples/GPIO_GCC_AVR/GPIO_sample.cpp
    This approach does not lead to any code size, speed and memory usage overhead. Generated code is as efficient as it was carefully handwritten (if compiler optimization is on).
    The library is currently ported to AVR, MSP430 and STM32 microcontrollers. There is also untested ports for STM8 and LPC17xx.
  3. Petesays:
    @DT

    It's quite possible, I'm still learning this stuff :)

    When I was reading through the data sheet, the use of the PORTx register on the chip was very unclear. It also did not seem possible to set the pin high/low state from that register. Instead, its use was described more for setting pullup resistors. I'm looking at the sheet right now (doc8272) and don't see how the PORTx register could possibly be used for getting/setting the value.

    Any reference? I didn't see anything useful on avrfreaks at the time either.

    I'll try it in any case, because it's not like data sheets are always very clear :)

    @Konstantin

    I considered doing something with static methods like you did, but I thought it would cause unnecessary code space usage when the port wasn't being used. I'll definitely look at your approach. Thanks!

    Pete
  4. DTsays:
    DOC8272 Section 14.2.1 (Page 73):
    "If PORTxn is written logic one when the pin is configured as an output pin, the port pin is driven
    high (one). If PORTxn is written logic zero when the pin is configured as an output pin, the port
    pin is driven low (zero)."

    The toggle function is a kind of 'bonus' feature and is not supported on all AVRs. However, most people don't use it. The conventional way to change port pins on the AVR is like this:

    PORTA &= ~0x01; // clear port A0
    PORTA |= 0x01; // set port A0

    C statements like this will normally be compiled to use the single AVR instructions CBI and SBI. The PORTx register also controls the internal pull-ups - if the pin is set as an input then a PORTx bit high turns on the pull-up.
  5. Petesays:
    @DT

    Awesome. Thank you so much for clearing that up :)

    I completely missed that part, it's at the very top of the next page. I read the bit about it controlling the pull-ups and thought that was the purpose of the register. I need to read more closely/carefully.

    Pete
  6. DTsays:
    Spotted something else.
    IOPort::SetPinDirection(int pin, PinDirection direction) won't work when PinDirection is 'DirectionInput ' (zero).

    Instead of
    *_ddr ^= (1 << pin);
    you need
    *_ddr &= ~(1 << pin);


  7. Fahimesays:
    Hi Mr.brown
    I want to change a codvision program to avr studio for use a temperature&humidity sensor but i have more problems!!!
    can you help me?
    pleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeease help me...
  8. Mikkelsays:
    Hi

    I'm sitting here with a 1284P and trying to get PORT A to be general IO, but I can't find any clear information on how to set PORT A to general IO.

    How can I disable the alternate port functions?


    Mikkel

Comment on this Post

Remember me