You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This example just blinks an LED on and off once a second.
Hardware List
Arduino Uno
Breadboard
LED
220 Ohm Resistor
Schematic
Code
/*
* =====================================================================================
*
* Filename: blink.c
*
* Description: Blink an LED on the Arduino without the Arduino
*
* Version: 1.0
* Created: 05/23/2011 16:27:51
* Revision: none
* Compiler: avr-gcc
*
* =====================================================================================
*/
#include <avr/io.h>
#ifndef F_CPU
#define F_CPU 16000000
#include <util/delay.h>
#endif
enum {
BLINK_DELAY_MS = 1000,
};
int
main (int argc, char const* argv[])
{
DDRB |= _BV(DDB5);
while (1) {
PORTB |= _BV(PORTB5);
_delay_ms(BLINK_DELAY_MS);
PORTB &= ~_BV(PORTB5);
_delay_ms(BLINK_DELAY_MS);
}
return 0;
}
If you've tried the Arduino Blink Tutorial, this code mimics that almost exactly. #include <avr/io.h> loads the appropriate Pin definition header for your hardware. In order to pause, we need to also define the CPU speed, and then load the #include <util/delay.h> header.
Next, we decide to set the time we want to wait between switching to 1000 milliseconds (or 1 second) in the Enumeration. [[ Enumerators behave as constants.
The int main() loop comes next. You can see I use the standard C declaration, which lets you pass in some values on the command line. Those value declarations are just habit (and part of my snippets collection). You can safely ignore them, since you can't really pass in any arguments to the program.
We don't have to initialize the board like we do with the Arduino libraries, but we do have to declare pins that we will be using. Registers, I think, are a single value containing a byte, or 8 bits. Each bit represents a single pin. The next line tells the DDRB output register to initialize it's 5th pin. Take a look at the ATmega328p Datasheet[PDF] and the Arduino Uno schematic[PDF] to better understand the registers and how they are wired on the Arduino Board.
Next comes the infinite loop. Normally, you don't want infinite loops, but in our case, we don't want the program to exit immediately. The first line does a Bitwise OR assignment PORTB |= _BV(PORTB5);, setting the value of the LED port to the OR value of the bit at the LED pin. This will set the LED on high output, exactly like digitalWrite() in the Arduino library will, but does it much much faster and in only two operations (by looking up the value of the pin, and then ORing it to the value of the port. Bill Grundmann, a person I don't know, wrote about digitalWrite and why it is slow in this awesome blog post, which explains it better than I fully understand at this point.
Then we delay for 1000 milliseconds using a built in delay function _delay_ms(BLINK_DELAY_MS);. I'm not sure why it is prefixed by an underscore, usually representing that it is an internal function and shouldn't be used. Some older examples I've seen using this don't have the underscore prefix, so I need to take a closer look at how I am using it.
And then we switch off the LED by doing a Bitwise AND assignment PORTB &= ~_BV(PORTB5);. I'm still a little fuzzy on Binary Algebra operations, But I think this takes the default state of the register and compares it to the current state of the register. If any any pins are active at the LED pin bit, it ANDs it to the default state, and turns of any that don't return 1. 1 AND 1 is 1, but 1 AND 0 is 0, since any difference between the two is false.
Then we delay again, and repeat.
Bonus points if you remove two lines of the loop and do a Bitwise XOR assignment.
/*
* =====================================================================================
*
* Filename: button.c
*
* Description: Switch an LED on or off with a button
*
* Version: 1.0
* Created: 05/23/2011 16:27:51
* Revision: none
* Compiler: avr-gcc
*
* =====================================================================================
*/
#include <avr/io.h>
#include <avr/sfr_defs.h>
#ifndef F_CPU
#define F_CPU 16000000UL
#include <util/delay.h>
#endif
// PORTD -- button port
// PIND -- button register pin
// PD3 -- button bit
// PORTB -- LED port
// PB5 -- LED bit
// DDRB -- led data direction
enum {
BLINK_DELAY_MS = 1000,
};
int
button_pressed(int sfr, int bit)
{
if (bit_is_clear(sfr, bit)) {
_delay_ms(25);
if (bit_is_clear(sfr, bit)) {
return 1;
}
}
return 0;
}
int
main (int argc, char const* argv[])
{
DDRB |= _BV(DDB5); // set pin B5(13) as digital out for LED
PORTD |= _BV(PORTD3); // set pin D3 as input for button
while (1) {
if (button_pressed(PIND, PD3) == 0) {
PORTB ^= _BV(PORTB5);
_delay_ms(250);
}
}
return 0;
}
As you can see, this project builds on the first tutorial, [[ Arduino/Blink | Blink ]], at least for the hardware aspect. We also introduce declaring a function that we can call inside our main event loop. I'll skip the stuff we've already covered, like _delay_ms() and enum.
Inside main(), we declare DDB5 as an output, and then PORTD3 as an input by setting the register to the ORed bit value of those pins.
Then we enter our event loop. Much of Arduino programming here is Event Driven Programming, which is very similar to the way one designs games, and then checks the status of various things, firing events if those conditions are true or false, depending on what you want to be done. Asynchronous event servers like Node.JS also function in much the same way. Here, we check the status of the button with the function button_pressed(). button_pressed() takes in the register and then the bit that represents the specific pin we have the button wired to. bit_is_clear() returns a non-zero status if there is any activity on the pin. Our button will either be off or on (not pressed or pressed), and whenever it is pressed, bit_is_clear()# returns 0. Then we delay for 25 milliseconds, and check again. If it is again clear, we return false. This basically says, if the button is pressed for longer than 25 milliseconds, then it's not just an anomaly, someone is pressing the button.
Then we flip the bit with a XOR assignment like I hinted to at the last example, only if the button was indeed pressed, and then wait for a quarter second (250 ms), just so we don't accidentally pickup that the button is still depressed while we are lifting our finger.
/*
* =====================================================================================
*
* Filename: main.c
*
* Description: Read a button and print it to the serial communication line
*
* Version: 1.0
* Created: 05/27/2011 08:35:36
* Revision: none
* Compiler: avr-gcc
*
* =====================================================================================
*/
#include <avr/io.h>
#include <stdlib.h>
#include "serial.h"
int
main (int argc, char const* argv[])
{
s_init(); // Initialize
DDRB |= _BV(DDB5); // set pin B5 as out for LED indicator
PORTD |= _BV(PORTD3); // pin D3 will be input
PORTB |= _BV(PORTB5);
s_connect();
PORTB &= ~_BV(PORTB5);
while (1) { // Loop forever
if (bit_is_clear(PIND, PD3) != 0) { // if we see that the pin has a value
s_writes("Pressed"); // then print that the button is pressed
} else {
s_writes("Clear");
}
s_writec('\n'); // and then write a new line and
s_writec('\r'); // carriage return on every iteration
}
return 0;
}
Copy over "serial.h" and "serial.c", and then compile it as normal.
The LED on PB5 is just an indicator. If we aren't connected to the computer's terminal, the LED goes on, but if we are connected, then it turns off. You can do away with it, but I think it helps with debugging a bit.
Just like the [[ Arduino/Serial | Serial ]] example, we initialize our serial communication pins, turn on the LED to note that we are not connected, and then try to connect by sending a single value until we get a response. Think of it like a heartbeat, or PING/PONG message, but for this, we manually have to type something into the terminal. As soon as we are aware of an established connection, we turn the LED off, and then enter our loop.
We don't need all the overhead of the button_pressed() function in our DigitalButton example, because we don't need to save state. This time, we call bit_is_set() on our own, which will give us an immediate value. The manpage says:
'''bit_is_clear(sfr, bit)'''
#define bit_is_clear(sfr, bit) (!(_SFR_BYTE(sfr) & _BV(bit)))
#include <avr/io.h>
Test whether bit bit in IO register sfr is clear. This will return non-zero if the bit is clear, and a 0 if the bit is set.
Pretty standard, if you remember the TRUE/FALSE equivalents. 0 is false, so when this returns 0, that means that it is not clear (the button is pressed), but anything else means that it is positive that the button is clear. Because this can return strange values, we need to do a simple if statement to print out our value as something we can interpret easily. One option would to use s_writec('0') to print out ones and zeros which is like the original Arduino [[ http://arduino.cc/en/Tutorial/DigitalReadSerial | DigitalReadSerial ]] page, but I decided to use a function that we have not used yet, which is s_writes(). s_writes() can print a string, much like Serial.println() in the Arduino Library. For this example here, we write out the words "Pressed" or "Clear". Overall, it is pretty simple.
Lastly, we write out a newline and carriage return to put our cursor on a clean new line, and then repeat ad nauseam.
Play with other ways of trying to determine the value of the button. The manpage for _BV(), "avr-man 3 _BV" has some good documentation, as well as the documentation for avr-libc, which should be in "/usr/local/avr/share/doc", or the equivalent for wherever you installed your tools and libraries. It is also available online: avr-libc-user-manual
This is set up exactly like the [[ Arduino/Blink | Blink ]] example.
Code
So far we've seen how we can register a bit for output, to light an LED for example, and for input, like to check the status of a button. Unfortunately, we haven't found a way to communicate between the Arduino and our computer. This communication is awesome, because it can enable us to send data from the internet without hooking up our microcontroller to an ethernet board and dealing with all that overhead, especially for simple things. Lets take a look at a simple library I've written that lets us read and write to our Arduino.
/*
* =====================================================================================
*
* Filename: serial.c
*
* Description: Basic Serial Communication with the Arduino implementation
*
* Version: 1.0
* Created: 05/26/2011 07:53:12
* Revision: none
* Compiler: avr-gcc
*
* =====================================================================================
*/
#include <avr/io.h>
#include <ctype.h>
#include "serial.h"
#ifndef F_CPU
#define F_CPU 16000000UL
#endif
#ifdef F_CPU
#define THIS_UBRR (F_CPU / 16 / 9600) - 1
#else
#error "F_CPU is not defined, cannot determine CPU clock speed"
#endif
#include <util/delay.h>
// returns nonzero if serial data is available to read
unsigned char
s_rxcheck(void)
{
return (UCSR0A & _BV(RXC0));
}
// returns nonzero if register is clear
unsigned char
s_txcheck(void)
{
return (UCSR0A & _BV(UDRE0));
}
unsigned char
s_read(void)
{
while (s_rxcheck() == 0) { }
return UDR0;
}
void
s_writec(unsigned char data)
{
while (s_txcheck() == 0) { }
UDR0 = data;
}
void
s_writes(char *s)
{
while (*s) {
s_writec(*s);
s++;
}
}
void
s_connect()
{
while (s_rxcheck() == 0) {
s_writec('\0');
_delay_ms(10);
}
}
void
s_init()
{
UBRR0H = (unsigned char)((THIS_UBRR) >> 8); // 0
UBRR0L = (unsigned char) THIS_UBRR; // 103
UCSR0B = (1 << RXEN0) | (1 << TXEN0); // enables receiver and transmitter
UCSR0C = (3 << UCSZ00); // Frame format: 8data, no parity, 1stop bit
}
Hopefully this makes sense. This code is not compatible with anything besides the ATmega 8/16/32 series, though.
Lets look at a simple program that will listen for input from the attached computer, and then if the user types in any character, it will spit out the same character, but capitalized if the input was lower case, and the lower case version if the input was capitalized.
/*
* =====================================================================================
*
* Filename: main.c
*
* Description: Serial Communication
*
* Version: 1.0
* Created: 05/26/2011 10:21:22
* Revision: none
* Compiler: avr-gcc
*
* =====================================================================================
*/
#include <avr/io.h>
#include <util/delay.h>
#include "serial.h"
int
main (int argc, char const* argv[])
{
DDRB |= _BV(DDB5); // DDB5 = 5
s_init();
PORTB |= _BV(PB5); // pin 13 LED on
s_connect();
PORTB &= ~_BV(PB5); // pin 13 LED off
int input = 0;
while (1) {
if (s_rxcheck()) {
PORTB |= _BV(PB5); // pin 13 LED on
input = s_read();
int output;
if (input == '\r') {
s_writec('\n');
}
if (isupper(input)) {
output = tolower(input);
} else {
output = toupper(input);
}
s_writec(output);
PORTB &= ~_BV(PB5); // pin 13 LED off
}
}
return 0;
}
Ok, so first we setup our outputs. The LED in this example is an internal indicator of when there is data sitting on the line. We first register it, and then set up our serial communication with s_init().
Then we try to communicate with the computer, and so we turn the LED on high, attempt to connect (by sending a NULL value, waiting for a response, and then trying again). If we do get a response, we then turn our indicating LED off, and then begin to enter our event loop. We need a variable to store any data we receive, so we set that up first, though.
We know we have proper communication, but we don't want to constantly be returning nothing if nothing comes in either, so we loop indefinitely until we notice that there is data on the receiving pin (RX). As soon as we get something, we turn our LED on, to show that we are aware of the data and want to process it. We read the data, store it in our variable, and create a new variable to store our response.
First, this is a dumb terminal. It only knows about what we tell it about. The "enter" key is mapped to a carriage return on my computer terminal emulator, which if you remember old typewriters, the carriage return only returns the printing head back to the beginning of the line. It doesn't actually feed the paper up to a clean line. So, if we notice that the user enters a carriage return, we immediately spit out a newline character. This prevents us from overwriting the same line over and over again.
Then we get to the heart of the logic of this simple program, and determine what kind of character we have. "ctype.h" has some useful functions that let us determine if our input is an uppercase letter, isupper() or lowercase letter islower(). We then flip the case of our character, and then write it back out to the user.
Since we are done at this point, we turn off the LED and then loop around again, waiting for new data to come down the line.
Not a very useful program in itself, but I will continue to use this serial library for other things. Next example, we will recreate the button example, and instead of flipping an LED on and off, we'll print out the state on the serial connection.
This isn't quite a Hello, World! tutorial, but more on how to get to a point to write a Hello World program. Specifically, how to cross compile Gnu-Binutils, Gnu GCC, and avr-libc on a Unix-like machine. The Arduino IDE is a nice IDE, but I'm stuck in my ways and want to type a few commands and have it run, not click through a bunch of menus.
The Environment
Setting up a GCC Cross environment is really really simple. So simple, this tutorial is basically worthless.
Read through this page here, from nongnu.org. I recommend installing with a PREFIX of /usr/local/avr or /usr/local/arduino if you have sudo privileges. $HOME/avr will work of you don't. (update: use brew, or your package manager of choice if possible)
The Hardware
This is the same design as the Arduino Tutorial. Pin 13 should already have a surface mounted LED, so if you are just getting started and you don't have any spare hardware pieces, you still should be good to go for this exercise. It should also have a resistor mounted on it's line, so for this particular example, you can forget adding one.
The Code
/*
* =====================================================================================
*
* Filename: blink.c
*
* Description: Blink an LED on the Arduino without the Arduino
*
* Version: 1.0
* Created: 05/23/2011 16:27:51
* Revision: none
* Compiler: avr-gcc
*
* =====================================================================================
*/
#include <avr/io.h>
#ifndef F_CPU
#define F_CPU 16000000
#include <util/delay.h>
#endif
// PORTB -- LED port
// PB5 -- LED bit
// DDRB -- led data direction (ie, output)
// This enumeration could be a define, but we might end
// up with a few values, and the enumeration fits it well.
enum {
DELAY_MS = 1000,
};
int
main (int argc, char const* argv[])
{
DDRB |= _BV(DDB5);
while (1) {
// Turn on and wait
PORTB |= _BV(PORTB5);
_delay_ms(DELAY_MS);
// Turn off and wait
PORTB &= ~_BV(PORTB5);
_delay_ms(DELAY_MS);
}
// return 1, only because we should never actually
// reach this, if we do, something went wrong.
return 1;
}
A few things to point out, especially if the Arduino is your first time programming hardware directly.
Pin 13 that we have the LED wired up to is known to the Atmel chip as PB5, which is wired to Pin 19 of the chip. There are 3 groups, PBx, PCx and PDx, and they all have 5 or 7 registers. Arduino Digital pins 1-7 are wired to the Atmel PDx, where the numbers correlate (ie. PD4 is Pin 4). Arduino Analog pins 0-5 are wired to PCx, where the numbers correlate (ie. PC1 is Pin A1). Finally, the second digital header on the Arduino is wired to PBx, mostly. The last two pins aren't connected to the chip, but they're just a Ground and a Analog Reference pin (you can safely ignore it.) For the last header, subtract the number 8 from the Arduino pin to find the corresponding Atmel pin, prefixed by PBx, where "x" is the number. Take a look at the "avr/io.h", which is the header that tries to load the appropriate device header, which has all these pins defined. The Arduino Uno, with an Atmel 328p, loads "avr/iom328p.h".
The Makefile
We need a simple makefile. I probably copied a lot of this from other people. Credit goes to them.
PRG = blink
OBJ = $(PRG).o
MCU_TARGET = atmega328p
DEFS = -D__AVR_ATmega3280__
PROTO = stk500v1
RATE = 115200
LIBS =
# this PORT define is for OS X, change it to whatever for your OS.
# The find call helps if you happen to move the USB cable from port to port, like a sailor.
PORT = `find /dev -name "tty.usbmodem*" -type c -print 2>/dev/null`
# You should not have to change anything below here.
CC = avr-gcc
OPTIMIZE = -O2
# Override is only needed by avr-lib build system.
override CFLAGS = -g -Wall $(OPTIMIZE) -mmcu=$(MCU_TARGET) $(DEFS) -I/usr/local/avr/include
override LDFLAGS = -Wl,-Map,$(PRG).map -L/usr/local/avr/lib
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
AVRDUDE = avrdude
all: $(PRG).elf lst text eeprom
$(PRG).elf: $(OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)
# dependency:
$(PRG).o: $(PRG).c
clean:
rm -rf *.o $(PRG).elf *.eps *.png *.pdf *.bak
rm -rf *.lst *.map $(EXTRA_CLEAN_FILES)
lst: $(PRG).lst
%.lst: %.elf
$(OBJDUMP) -h -S $< > $@
# Rules for building the .text rom images
text: hex bin srec
hex: $(PRG).hex
bin: $(PRG).bin
srec: $(PRG).srec
%.hex: %.elf
$(OBJCOPY) -j .text -j .data -O ihex $< $@
%.srec: %.elf
$(OBJCOPY) -j .text -j .data -O srec $< $@
%.bin: %.elf
$(OBJCOPY) -j .text -j .data -O binary $< $@
# Rules for building the .eeprom rom images
eeprom: ehex ebin esrec
ehex: $(PRG)_eeprom.hex
ebin: $(PRG)_eeprom.bin
esrec: $(PRG)_eeprom.srec
%_eeprom.hex: %.elf
$(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O ihex $< $@ \
|| { echo empty $@ not generated; exit 0; }
%_eeprom.srec: %.elf
$(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O srec $< $@ \
|| { echo empty $@ not generated; exit 0; }
%_eeprom.bin: %.elf
$(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O binary $< $@ \
|| { echo empty $@ not generated; exit 0; }
# Every thing below here is used by avr-libc's build system and can be ignored
# by the casual user.
FIG2DEV = fig2dev
EXTRA_CLEAN_FILES = *.hex *.bin *.srec
dox: eps png pdf
eps: $(PRG).eps
png: $(PRG).png
pdf: $(PRG).pdf
%.eps: %.fig
$(FIG2DEV) -L eps $< $@
%.pdf: %.fig
$(FIG2DEV) -L pdf $< $@
%.png: %.fig
$(FIG2DEV) -L png $< $@
# avrdude uploading helpers
install: $(PRG).hex
$(AVRDUDE) -p$(MCU_TARGET) -c$(PROTO) -P$(PORT) -b$(RATE) -D -Uflash:w:$(PRG).hex:i
The Execution
I write code best in Vim, and with this Makefile, all I have to do is type :make, which builds my project. I can immediately see if there are any errors. If you are using VIM, you can do the same. I think emacs has a tiny bit of Make integration (I've never used it, as I can never remember how to exit the thing outside of "killall emacs", which doesn't seem to rid it from this planet unfortunately.)
It is a standard GNU Makefile, so you can also go ahead and type 'make install', and it will build a fresh copy if it needs to be rebuilt and upload it to your Arduino device.
Keep in mind, that the Makefile here is tied to the Uno with an Atmel328p. Other Arduino's will need the MCU_TARGET redefined, as well as DEFS, which is what "avr/io.h" uses to figure out which device specific header to include. This is not Arduino Specific, however. I also have a standalone Atmel AT90S8515 which I use this same Makefile with.
From here, I would recommend opening up any of the header files in "/usr/local/avr/avr/include", which has a ton of pre defined macros and functions you can stick in your code. I'm currently re-implementing much of the Arduino Tutorial examples using non-Arduino code, only avr-libc. For one, it is much much faster, and much much smaller. It also gets you in the mode of working with the hardware itself. Arduino was designed for everyone to be able to program these things that is not C, or C++, but some crafty hybrid that doesn't look like anything. It is amazingly simple, but I want to start working in embedded environments, and this will help me get into this mode.