Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Max7219, quad encoder, better modularity w.r.t. clock ISRs #4

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

30 changes: 17 additions & 13 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ AR = avr-ar
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump

ifdef TIMER0_PRESCALER
# Needed for Timer::micros
TIMER0_MICRO_SCALE = $(shell python timerscale.py -f $(CPU_FREQUENCY) -p $(TIMER0_PRESCALER))
DEFS+=-DTIMER0_PRESCALER=$(TIMER0_PRESCALER) -DTIMER0_MICRO_SCALE=$(TIMER0_MICRO_SCALE)
endif

VPATH=test

CFLAGS = -g -Wall $(OPTIMIZE) -mmcu=$(MCU_TARGET) $(DEFS)
Expand All @@ -28,18 +34,18 @@ BIN = test/blink.bin test/test_clock.bin test/test_enc28j60.bin \
test/test_ip_layered.bin test/test_clock_serial.bin \
test/test_clock_nanode.bin test/test_ws2811.bin test/test_ws2811_2.bin \
test/test_ws2811_bridge.bin test/test_ws2811_bridge_2.bin \
live/star_slave_onewire.bin
test/test_max7219.bin live/star_slave_onewire.bin test/test_wait.bin

all: avr-ports.h $(BIN) $(BIN:.bin=.lst) sizes/sizes.html
all: $(BIN) $(BIN:.bin=.lst) sizes/sizes.html

.depend: avr-ports.h *.cc *.h test/*.cc live/*.cc
.depend: *.cc *.h test/*.cc live/*.cc
$(CC) $(DEFS) -mmcu=$(MCU_TARGET) -MM *.cc > .depend
$(CC) $(DEFS) -mmcu=$(MCU_TARGET) -MM test/*.cc | sed 's;^\(.*\):;test/\1:;' >> .depend
$(CC) $(DEFS) -mmcu=$(MCU_TARGET) -MM live/*.cc | sed 's;^\(.*\):;live/\1:;' >> .depend

.SUFFIXES: .elf .lst .bin _upload

.cc.o:
.cc.o:
$(CXX) $(CXXFLAGS) -c -o $(<:.cc=.o) $<

.c.o:
Expand All @@ -54,6 +60,11 @@ all: avr-ports.h $(BIN) $(BIN:.bin=.lst) sizes/sizes.html
.o.elf:
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<

# This rule allows flashing the firmware through make
# If you want to flash blink.bin, you can type `make blink_upload`
.bin_upload:
avrdude -F -V -p $(MCU_TARGET) -P $(AVR_TTY) -c $(AVR_PROGRAMMER) -b $(AVR_RATE) -U flash:w:$<

test/test.grb: test/make_grb.py
python test/make_grb.py > test/test.grb

Expand All @@ -63,11 +74,7 @@ test/test.rgb: test/make_rgb.py
sizes/recent_sizes.json sizes/sizes.html: $(BIN)
python sizes/sizes.py recent generate

get-ports.cc: Makefile.local

avr-ports.h: get-ports.lst extract-ports.pl
./extract-ports.pl -f $(CPU_FREQUENCY) < get-ports.lst > avr-ports.h

.PHONY: history
history:
python sizes.py history generate

Expand All @@ -76,9 +83,6 @@ sizeclean:

clean: sizeclean
rm -f *.o *.map *.lst *.elf *.bin test/*.o test/*.map test/*.lst \
test/*.elf test/*.bin avr-ports.h .depend

.bin_upload:
avrdude -F -V -p $(MCU_TARGET) -P $(AVR_TTY) -c $(AVR_PROGRAMMER) -b $(AVR_RATE) -U flash:w:$<
test/*.elf test/*.bin .depend

-include .depend
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# arduino--

arduino-- is a C++ library for AVR microcontrollers.

The project got kicked off by this blog post by Ben Laurie, [Grown-up Arduino programming](http://www.links.org/?p=1057).

In a nutshell, the idea is to move as much work as possible into the
compilation phase via C++ templates, and have a system where toggling an
output pin in C++ compiles down to a single assembler instruction, while
still having an abstraction layer that is at least very similar between devices.

This is what hello world - blinking an LED - looks like:

```c++
/*
The hello world of arduino--, a C++ take on the Arduino libraries.

Blinks an LED connected to digital pin 13 (which is connected to an LED
on all Arduino variants that we know of).
*/

#include "arduino--.h"
#include <avr/sleep.h>

int main(void)
{
// Arduino Pin D13 is an output
Arduino::D13::modeOutput();

while(true)
{
// toggle the pin
Arduino::D13::toggle();
// wait
_delay_ms(2000);
}

return 0;
}
```
# Getting started

## Prerequisites

* `avrdude`
* `avr-gcc`
* `avr-binutils`
* `avr-libc`
* `perl`
* `python`

On a Mac, installation via [Homebrew](http://brew.sh/) is recommended:

brew tap osx-cross/avr
brew install avr-gcc

This will automatically install avr-binutils and avr-libs, too.
130 changes: 105 additions & 25 deletions arduino--.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@
#include <avr/sleep.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include "avr-ports.h"

#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__) \
|| defined (__AVR_ATmega168__) || defined (__AVR_ATmega168A__) \
|| defined (__AVR_ATmega168P__)
#include "defs/ports_mx8.h"
#elif defined (__AVR_ATtiny85__) || defined (__AVR_ATtiny45__) \
|| defined (__AVR_ATTiny25__)
#include "defs/ports_tnx5.h"
#else
#error "No port definition for architecture found"
#endif

typedef uint8_t byte;

Expand Down Expand Up @@ -533,29 +543,32 @@ class _TimerTiny_3C2 : public _Timer<TCNT_, TIMSK_, 2> // 2 is TOIEx
necessary, but when the _Pin methods are used via a subclass like
_ChangeInterruptPin, gcc doesn't automatically inline these methods any more.
*/
template <byte ddr, byte port, byte in, byte bit>
template <byte ddr_, byte port_, byte in_, byte bit_>
class _Pin
{
public:

const static byte port = port_;
const static byte pin = bit_;

static void modeOutput() __attribute__((always_inline))
{ _SFR_IO8(ddr) |= _BV(bit); }
{ _SFR_IO8(ddr_) |= _BV(bit_); }
static void modeInput() __attribute__((always_inline))
{ _SFR_IO8(ddr) &= ~_BV(bit); }
{ _SFR_IO8(ddr_) &= ~_BV(bit_); }
static void modeInputPullup() __attribute__((always_inline))
{ modeInput(); set(); }
static void modeInputTristate() __attribute__((always_inline))
{ modeInput(); clear(); }
static void set() __attribute__((always_inline))
{ _SFR_IO8(port) |= _BV(bit); }
{ _SFR_IO8(port_) |= _BV(bit_); }
static void clear() __attribute__((always_inline))
{ _SFR_IO8(port) &= ~_BV(bit); }
{ _SFR_IO8(port_) &= ~_BV(bit_); }

/** Return 1 if the Pin reads HIGH */
static byte read() __attribute__((always_inline))
{ return !!(_SFR_IO8(in) & _BV(bit)); }
{ return !!(_SFR_IO8(in_) & _BV(bit_)); }
static byte toggle() __attribute__((always_inline))
{ return (_SFR_IO8(port) ^= _BV(bit)); }
{ return (_SFR_IO8(port_) ^= _BV(bit_)); }
};

template <class Pin_, class OCR_>
Expand Down Expand Up @@ -608,27 +621,32 @@ class _ChangeInterruptPin : public Pin_
#if defined (ADMUX) && defined (ADCSRA) && defined (ADSC) && defined (ADCH) \
&& defined (ADCL)

template <class Pin_, byte AIN_>
class _AnalogPin : public Pin_
class ADCMux
{
static void analogStart(uint8_t reference) __attribute__((always_inline))
public:

static void enableInterrupt()
{
// set the analog reference (high two bits of ADMUX) and select the
// channel (low 4 bits). this also sets ADLAR (left-adjust result)
// to 0 (the default).
ADMUX = (reference << 6) | (AIN_ & 0x07);
// start the conversion
ADCSRA |= (1 << (ADSC));
ADCSRA |= (1 << ADIE);
}

static int analogRead(uint8_t reference)
static void freeRunning()
{
analogStart(reference);
ADCSRB &= ~0x07;
ADCSRA |= (1 << ADATE);
}

// ADSC is cleared when the conversion finishes
while (ADCSRA & (1 << ADSC))
;
/** Read the left-adjusted 8bit value from the ADC.

The ADC must have been started with LEFT_ADJUST.
*/
static int8_t analogLeftAdjusted() __attribute__((always_inline))
{
return ADCH;
}

static int analogValue() __attribute__((always_inline))
{
// we have to read ADCL first; doing so locks both ADCL
// and ADCH until ADCH is read. reading ADCL second would
// cause the results of each conversion to be discarded,
Expand All @@ -639,6 +657,61 @@ class _AnalogPin : public Pin_
// combine the two bytes
return (high << 8) | low;
}

static void prescaler(byte scale)
{
ADCSRA = (ADCSRA & ~0x07) | (scale & 0x07);
}

static void prescaler2() { prescaler(1); }
static void prescaler4() { prescaler(2); }
static void prescaler8() { prescaler(3); }
static void prescaler16() { prescaler(4); }
static void prescaler32() { prescaler(5); }
static void prescaler64() { prescaler(6); }
static void prescaler128() { prescaler(7); }
};

template <class Pin_, byte AIN_>
class _AnalogPin : public Pin_
{
public:

static const byte AREF = 0; // AREF, internal voltage reference turned off
static const byte AVCC = 1; // AVcc with external capacitor at AREF pin
static const byte V11 = 3; // Internal voltage reference with external
// capacitor at AREF pin

static const byte RIGHT_ADJUST = 0;
static const byte LEFT_ADJUST = (1 << ADLAR);

static void analogActivate() __attribute__((always_inline))
{
ADMUX = (ADMUX & ~0x0f) | ( AIN_ & 0x0f);
}

static void analogStart(byte adjust = RIGHT_ADJUST, byte reference = AVCC)
__attribute__((always_inline))
{
// set the analog reference (high two bits of ADMUX) and select the
// channel (low 4 bits). This also sets ADLAR

ADMUX = (reference << 6) | (AIN_ & 0x0f) | adjust;

// enable ADC and start the conversion
ADCSRA |= (1 << (ADSC)) | (1 << (ADEN));
}

static int analogRead(byte reference = AVCC)
{
analogStart(reference);

// ADSC is cleared when the conversion finishes
while (ADCSRA & (1 << ADSC))
;

return ADCMux::analogValue();
}
};

#endif
Expand All @@ -650,6 +723,14 @@ class AVRBase
static void noInterrupts() { cli(); }
};

#ifndef TIMER0_MICRO_SCALE
# define TIMER0_MICRO_SCALE 6
#endif

#ifndef TIMER0_PRESCALE
# define TIMER0_PRESCALE 64
#endif

/** Don't use this directly, use Clock16 or Clock32 instead
*/
template<typename timeres_t, class Timer> class _Clock
Expand Down Expand Up @@ -677,13 +758,12 @@ template<typename timeres_t, class Timer> class _Clock
ScopedInterruptDisable sid;

t = Timer::TCNT::read();
m = timer_overflow_count % (1 << TIMER16_MICRO_SCALE);
m = timer_overflow_count % (1 << TIMER0_MICRO_SCALE);

if ((Timer::TIFR::read() & _BV(TOV0)) && (t == 0))
m++;

// FIXME: Timer::PRESCALE not actually defined yet, see CLOCK16_PRESCALE
return ((m << 8) + t) * (Timer::PRESCALE / (F_CPU / 1000000L));
return ((m << 8) + t) * (TIMER0_PRESCALE / (F_CPU / 1000000L));
}

static void delay(timeres_t ms)
Expand Down
36 changes: 22 additions & 14 deletions clock16.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,45 @@
reduction of 238 bytes with avr-gcc 4.6.1.
*/

// Define this for a slower clock (for low power modes). Note that you must
// also set the prescaler, for now, unless the default of 64 is used.
#ifndef CLOCK16_PRESCALE
# define CLOCK16_PRESCALE 64
// Define this for a slower clock (for low power modes). Note that you should
// also set TIMER0_MICRO_SCALE if you don't use the default here
// See the Makefile and timerscale.py for details
#ifndef TIMER0_PRESCALE
# define TIMER0_PRESCALE 64
#endif

typedef _Clock<uint16_t,Timer0> Clock16;

Clock16 clock;

/*
* Implementation of the clock ISR for 16 bits resolution
*/
ISR(TIMER0_OVF_vect)
static void clock16_isr()
{
// copy these to local variables so they can be stored in registers
// (volatile variables must be read from memory on every access)
typename Clock16::time_res_t m = Clock16::timer_millis;
uint16_t f = Clock16::timer_fract;

m += (CLOCK16_PRESCALE * (256 / (F_CPU / 1000000))) / 1000;
f += (CLOCK16_PRESCALE * (256 / (F_CPU / 1000000))) % 1000;
m += (TIMER0_PRESCALE * (256 / (F_CPU / 1000000))) / 1000;
f += (TIMER0_PRESCALE * (256 / (F_CPU / 1000000))) % 1000;
if (f >= 1000)
{
f -= 1000;
m += 1;
}
{
f -= 1000;
m += 1;
}

Clock16::timer_fract = f;
Clock16::timer_millis = m;
Clock16::timer_overflow_count++;
}

/*
* Implementation of the clock ISR for 16 bits resolution
*/
#ifndef CLOCK_NO_ISR
ISR(TIMER0_OVF_vect)
{
clock16_isr();
}
#endif

#endif
Loading