Skip to content

Instantly share code, notes, and snippets.

@uxp
Last active January 25, 2023 10:50
Show Gist options
  • Save uxp/329fdbb10488c69b392b to your computer and use it in GitHub Desktop.
Save uxp/329fdbb10488c69b392b to your computer and use it in GitHub Desktop.

Analog Read Serial

Hardware List

  • Arduino Uno
  • Breadboard
  • 10k Ohm Potentiometer

Schematic

404

Code

/*
 * =====================================================================================
 *
 *       Filename:  analog.c
 *
 *    Description:  Print the value of a potentiometer to the serial output
 *
 *        Version:  1.0
 *        Created:  05/24/2011 17:45:19
 *       Revision:  none
 *       Compiler:  avr-gcc
 *
 * =====================================================================================
 */

#define F_CPU 16000000UL
#define BAUD 9600

#include <avr/io.h>
#include <util/setbaud.h>
#include <util/delay.h>
#include <stdlib.h>
#include "serial.h"


void
ad_init(void)
{
     // PC0
     PORTC |= _BV(PORTC0);           // setting these low reduces ADC noise.
     DDRC |= _BV(PORTC0);           // Set our data direction.

     ADMUX = _BV(REFS0) | 0x0F;
     ADCSRA |= _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0) | _BV(ADSC);

     loop_until_bit_is_clear(ADCSRA, ADSC);
}

int
ad_read(unsigned char pin)
{
     unsigned char low, high, r;

     r = (ADMUX & 0xf0) | (pin & 0x0f);
     ADMUX = r;               // select input
     ADCSRA |= _BV(ADSC);
     loop_until_bit_is_clear(ADCSRA, ADSC);

     low = ADCL;                // Read low byte before
     high = ADCH;                // we read the high byte.

     return (high << 8) | low;
}

int
main (int argc, char const* argv[])
{
     int raw;
     char result[10];

     ad_init();

     s_init();
     s_connect();

     DDRB = 0xFF;

     while     (1) {
          raw = ad_read(0);

          itoa(raw, result, 10);
          s_writes(result);

          _delay_ms(500);
     }

     return 0;
}

Digital Button

Hardware List

  • Arduino Uno
  • Breadboard
  • LED
  • Momentary Switch or Button
  • 10K Ohm Resistor
  • 220 Ohm Resistor

Schematic

button

Code

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

Digital Read Serial

Hardware List

  • Arduino Uno
  • Breadboard
  • LED
  • Momentary Switch or Button
  • 10K Ohm Resistor
  • 220 Ohm Resistor

Schematic

button

Code

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

Analog Read Serial

Hardware List

  • Arduino Uno
  • Breadboard
  • 220 Ohm Resistor
  • LED

Schematic

404

Code

/*
 * =====================================================================================
 *
 *       Filename:  fade.c
 *
 *    Description:  Fade an LED using PWM and AVR Timers
 *
 *        Version:  1.0
 *        Created:  07/12/2011 14:22:47
 *       Revision:  none
 *       Compiler:  avr-gcc
 *
 * =====================================================================================
 */


#include <avr/io.h>
#include <stdlib.h>

int
main (int argc, char const* argv[])
{
     DDRB = 0xFF;

     while     (1) {
          _delay_ms(500);
     }

     return 0;
}

http://www.atmel.com/dyn/resources/prod_documents/doc2542.pdf

Serial Communication

Hardware List

  • Arduino Uno
  • Breadboard
  • LED
  • 220 Ohm Resistor

Schematic

This is set up exactly like the [[ Arduino/Blink | Blink ]] example.

blink

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.

serial.h

/*
 * =====================================================================================
 *
 *       Filename:  serial.h
 *
 *    Description:  Serial Communication Interface declarations.
 *
 *        Version:  1.0
 *        Created:  05/26/2011 10:22:33
 *       Revision:  none
 *       Compiler:  avr-gcc
 *
 * =====================================================================================
 */

unsigned char s_rxcheck(void);

unsigned char s_txcheck(void);

unsigned char s_read(void);

void s_writec(unsigned char data);

void s_writes(char *s);

void s_connect(void);

void s_init(void);

serial.c

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

Setting up an Arduino Environment

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.

blink

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment