diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.gitmodules @@ -0,0 +1 @@ + diff --git a/Makefile b/Makefile index bccf61e..4f52af0 100644 --- a/Makefile +++ b/Makefile @@ -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) @@ -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: @@ -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 @@ -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 @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d9347c --- /dev/null +++ b/README.md @@ -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 + +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. diff --git a/arduino--.h b/arduino--.h index 9200ec2..1382bbb 100644 --- a/arduino--.h +++ b/arduino--.h @@ -8,7 +8,17 @@ #include #include #include -#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; @@ -533,29 +543,32 @@ class _TimerTiny_3C2 : public _Timer // 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 +template 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 @@ -608,27 +621,32 @@ class _ChangeInterruptPin : public Pin_ #if defined (ADMUX) && defined (ADCSRA) && defined (ADSC) && defined (ADCH) \ && defined (ADCL) -template -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, @@ -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 _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 @@ -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 class _Clock @@ -677,13 +758,12 @@ template 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) diff --git a/clock16.h b/clock16.h index 8448044..c31dee0 100644 --- a/clock16.h +++ b/clock16.h @@ -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 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 diff --git a/clock32.h b/clock32.h index f5ba503..b45236c 100644 --- a/clock32.h +++ b/clock32.h @@ -22,22 +22,27 @@ The value from Clock32::millis() will wrap around after about 49 days. */ + +// 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 Clock32; Clock32 clock; -/* - * Implementation of the clock ISR for 32 bits resolution - */ -ISR(TIMER0_OVF_vect) +static void clock32_isr() { // copy these to local variables so they can be stored in registers // (volatile variables must be read from memory on every access) typename Clock32::time_res_t m = Clock32::timer0_millis; uint16_t f = Clock32::timer0_fract; - m += (64 * (256 / (F_CPU / 1000000))) / 1000; - f += (64 * (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; @@ -49,4 +54,12 @@ ISR(TIMER0_OVF_vect) Clock32::timer0_overflow_count++; } +/* + * Implementation of the clock ISR for 32 bits resolution + */ +ISR(TIMER0_OVF_vect) + { + clock32_isr(); + } + #endif diff --git a/get-ports.cc b/defs/get-ports.c similarity index 89% rename from get-ports.cc rename to defs/get-ports.c index e45fff4..08478a2 100644 --- a/get-ports.cc +++ b/defs/get-ports.c @@ -10,10 +10,10 @@ int main(int argc, char **argv) #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__) \ || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168A__) \ || defined (__AVR_ATmega168P__) -#include "defs/ports_mx8.i" +#include "ports_mx8.i" #elif defined (__AVR_ATtiny85__) || defined (__AVR_ATtiny45__) \ || defined (__AVR_ATTiny25__) -#include "defs/ports_tnx5.i" +#include "ports_tnx5.i" #else #error "No port definition found" #endif diff --git a/defs/mx8.h b/defs/mx8.h index 89e9b55..9a68c67 100644 --- a/defs/mx8.h +++ b/defs/mx8.h @@ -4,13 +4,6 @@ // Analog input voltage references -// AREF, Internal Vref turned off -const uint8_t REF_AREF = 0; -// AVCC with external capacitor at AREF pin -const uint8_t REF_AVCC_EXT_CAP = 1; -// Internal 1.1V Voltage Reference with external capacitor at AREF pin -const uint8_t REF_INT_1_1_EXT_CAP = 3; - // FIXME: should Pins also be static members instead of typedefs? // These can't be typedefs because then we can't define operator= on them. class Register @@ -46,19 +39,19 @@ template class _Interrupt typedef class _Interrupt Interrupt0; typedef class _Interrupt Interrupt1; -typedef _Timer_2C<_Register, _Register, _Register, - _Register, _Register, +typedef _Timer_2C<_Register, _Register, _Register, + _Register, _Register, _Register, _Register > Timer0; -typedef _Timer_2C3<_Register16, _Register16, - _Register16, _Register16, +typedef _Timer_2C3<_Register16, _Register16, + _Register16, _Register16, _Register, _Register, _Register, _Register, _Register > Timer1; typedef _Timer_2C<_Register, _Register, _Register, - _Register, _Register, + _Register, _Register, _Register, _Register > Timer2; @@ -67,7 +60,7 @@ class Pin public: // Port B typedef _Pin D_B0; - typedef _ChangeInterruptPin B0; + typedef _ChangeInterruptPin B0; typedef _Pin D_B1; typedef _PWMPin OC1A; @@ -81,7 +74,7 @@ class Pin typedef _PWMPin OC2A; typedef _ChangeInterruptPin B3; - typedef _Pin D_B4; + typedef _Pin D_B4; typedef _ChangeInterruptPin B4; typedef _Pin D_B5; @@ -159,7 +152,7 @@ class Arduino : public AVRBase typedef Pin::C2 A2; typedef Pin::C3 A3; typedef Pin::C4 A4; - + // The digital pins in Arduino numbering typedef Pin::D0 D0; typedef Pin::D1 D1; @@ -179,11 +172,27 @@ class Arduino : public AVRBase static void init() { interrupts(); - + Timer0::modeFastPWM(); - // set timer 0 prescale factor to 64 + +#ifndef TIMER0_PRESCALER Timer0::prescaler64(); +#else +# if TIMER0_PRESCALER == 1 + Timer0::prescaler1(); +# elif TIMER0_PRESCALER == 8 + Timer0::prescaler8(); +# elif TIMER0_PRESCALER == 64 + Timer0::prescaler64(); +# elif TIMER0_PRESCALER == 256 + Timer0::prescaler256(); +# elif TIMER0_PRESCALER == 1024 + Timer0::prescaler1024(); +# else +# error "Invalid TIMER0_PRESCALER" +# endif +#endif // Note: timer 0 interrupt is _NOT_ enabled. To enable it, // include one of the clock headers. @@ -240,4 +249,23 @@ class Nanode : public AVRBase } }; +class JeeNode : public AVRBase + { +public: + static void init() + { + Arduino::init(); + } + + typedef Pin::D4 DIO1; + typedef Pin::C0 AIO1; + typedef Pin::D5 DIO2; + typedef Pin::C1 AIO2; + typedef Pin::D6 DIO3; + typedef Pin::C2 AIO3; + typedef Pin::D7 DIO4; + typedef Pin::C3 AIO4; + + }; + #endif // ARDUINO_MINUS_MINUS_MX8 diff --git a/defs/ports_mx8.h b/defs/ports_mx8.h new file mode 100644 index 0000000..262289e --- /dev/null +++ b/defs/ports_mx8.h @@ -0,0 +1,52 @@ +// Generated by extract_ports.py for ports_mx8.i + +#ifndef PORTS_MX8_H +#define PORTS_MX8_H + +#define NPORTB 0x05 +#define NDDRB 0x04 +#define NPINB 0x03 +#define NPORTC 0x08 +#define NDDRC 0x07 +#define NPINC 0x06 +#define NPORTD 0x0B +#define NDDRD 0x0A +#define NPIND 0x09 +#define NPCICR 0x48 +#define NPCIFR 0x1B +#define NPCMSK0 0x4B +#define NPCMSK1 0x4C +#define NPCMSK2 0x4D +#define NSPCR 0x2C +#define NEICRA 0x49 +#define NEIMSK 0x1D +#define NICR1 0x66 +#define NTCNT0 0x26 +#define NTCNT1 0x64 +#define NTCNT2 0x92 +#define NOCR0A 0x27 +#define NOCR0B 0x28 +#define NOCR1A 0x68 +#define NOCR1B 0x6A +#define NOCR2A 0x93 +#define NOCR2B 0x94 +#define NTIMSK0 0x4E +#define NTIMSK1 0x4F +#define NTIMSK2 0x50 +#define NTIFR0 0x15 +#define NTIFR1 0x16 +#define NTIFR2 0x17 +#define NTCCR0A 0x24 +#define NTCCR0B 0x25 +#define NTCCR1A 0x60 +#define NTCCR1B 0x61 +#define NTCCR1C 0x62 +#define NTCCR2A 0x90 +#define NTCCR2B 0x91 +#define NUBRR0H 0xA5 +#define NUBRR0L 0xA4 +#define NUCSR0A 0xA0 +#define NUCSR0B 0xA1 +#define NUDR0 0xA6 + +#endif // PORTS_MX8_H diff --git a/defs/ports_mx8.i b/defs/ports_mx8.i index e189e56..8302b53 100644 --- a/defs/ports_mx8.i +++ b/defs/ports_mx8.i @@ -1,3 +1,5 @@ +// mcu=atmega328p + D(PORTB); D(DDRB); D(PINB); diff --git a/defs/ports_tnx5.h b/defs/ports_tnx5.h new file mode 100644 index 0000000..cfeb94a --- /dev/null +++ b/defs/ports_tnx5.h @@ -0,0 +1,23 @@ +// Generated by extract_ports.py for ports_tnx5.i + +#ifndef PORTS_TNX5_H +#define PORTS_TNX5_H + +#define NPORTB 0x18 +#define NDDRB 0x17 +#define NPINB 0x16 +#define NTCNT0 0x32 +#define NTCNT1 0x2F +#define NOCR0A 0x29 +#define NOCR0B 0x28 +#define NOCR1A 0x2E +#define NOCR1B 0x2B +#define NTIMSK 0x39 +#define NTIFR 0x38 +#define NTCCR0A 0x2A +#define NTCCR0B 0x33 +#define NGTCCR 0x2C +#define NGIFR 0x3A +#define NPCMSK 0x15 + +#endif // PORTS_TNX5_H diff --git a/defs/ports_tnx5.i b/defs/ports_tnx5.i index 9674707..b6696c9 100644 --- a/defs/ports_tnx5.i +++ b/defs/ports_tnx5.i @@ -1,3 +1,5 @@ +// mcu=attiny45 + D(PORTB); D(DDRB); D(PINB); diff --git a/defs/tnx5.h b/defs/tnx5.h index 09b51d5..b3d7a03 100644 --- a/defs/tnx5.h +++ b/defs/tnx5.h @@ -63,8 +63,23 @@ class Arduino : public AVRBase Timer0::fastPWM(); - // set timer 0 prescale factor to 64 +#ifndef TIMER0_PRESCALER Timer0::prescaler64(); +#else +# if TIMER0_PRESCALER == 1 + Timer0::prescaler1(); +# elif TIMER0_PRESCALER == 8 + Timer0::prescaler8(); +# elif TIMER0_PRESCALER == 64 + Timer0::prescaler64(); +# elif TIMER0_PRESCALER == 256 + Timer0::prescaler256(); +# elif TIMER0_PRESCALER == 1024 + Timer0::prescaler1024(); +# else +# error "Invalid TIMER0_PRESCALER" +# endif +#endif // Note: timer 0 interrupt is _NOT_ enabled. To enable it, // include one of the clock headers. diff --git a/eventwait.h b/eventwait.h new file mode 100644 index 0000000..6849a73 --- /dev/null +++ b/eventwait.h @@ -0,0 +1,51 @@ +// -*- mode: c++; indent-tabs-mode: nil; -*- + +#ifndef ARDUINO_MINUS_MINUS_EVWAIT_H +#define ARDUINO_MINUS_MINUS_EVWAIT_H + +// Wait for events. Clock_ must be a Clock class, Cont_ is a container +// and must implement empty +template +class _EventWait : public Cont_ + { +public: + + void wait_event() + { + set_sleep_mode(SLEEP_MODE_IDLE); + sleep_enable(); +#ifdef sleep_bod_disable + sleep_bod_disable(); +#endif + while (Cont_::empty()) + { + sei(); + sleep_cpu(); + } + + sei(); + sleep_disable(); + } + + void wait_event(typename Clock_::time_res_t ms) + { + const typename Clock_::timeres_t start = Clock_::millis(); + + set_sleep_mode(SLEEP_MODE_IDLE); + sleep_enable(); +#ifdef sleep_bod_disable + sleep_bod_disable(); +#endif + while (Cont_::empty() && Clock_::millis() - start <= ms) + { + sei(); + sleep_cpu(); + } + + sei(); + sleep_disable(); + } + }; + +#endif + diff --git a/extract-ports.pl b/extract-ports.pl deleted file mode 100755 index c32233c..0000000 --- a/extract-ports.pl +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/perl -w - -use strict; - -use Carp; -use Getopt::Long; -use POSIX; - -my $f_cpu; -GetOptions ("frequency=f" => \$f_cpu); - -print "// Generated by $0\n\n"; - -# 64 is the timer0 prescale value. We could make that also configurable -# 16 is the bit width of uint16_t -my $scale16 = 16 - ceil(log(64.0 / ($f_cpu / 1000000) * 2 ** 8)/log(2)); -print "#define TIMER16_MICRO_SCALE $scale16\n\n"; - -while (my $line = <>) { - chomp $line; - - next if $line !~ /^\s*D\((.*?)\);$/; - my $port = $1; - - my $val; - do { - $line = <>; - chomp $line; - - if ($line =~ /ldi\tr\d+, (0x[0-9a-fA-F]+)\t;/) { - $val = $1; - } elsif ($line =~ /st\tZ,/) { - print "#define N$port $val\n"; - } else { - croak $line; - } - } while ($line !~ /st\tZ,/); -} diff --git a/max7219.h b/max7219.h new file mode 100644 index 0000000..084405e --- /dev/null +++ b/max7219.h @@ -0,0 +1,139 @@ +/* + Driver for the MAX7219 matrix multiplexer. + Multiple cascaded devices are supported. + + + Code History: + -------------- + +The orginal code was written for the Wiring board by: + * Nicholas Zambetti and Dave Mellis /Interaction Design Institute Ivrea /Dec 2004 + * http://www.potemkin.org/uploads/Wiring/MAX7219.txt + +First modification by: + * Marcus Hannerstig/ K3, malmø høgskola /2006 + * http://www.xlab.se | http://arduino.berlios.de + +Next modifications by: + * tomek ness /FH-Potsdam / Feb 2007 + * http://design.fh-potsdam.de/ + * @acknowledgements: eric f. + +Version for arduino-- by: + * Lars Immisch + + -- + + This example sets the same pattern on all cascaded devices: + + typedef class _Max7219 Max7219; + + Max7219::init(); + + Max7219::set(Max7219::ROW0, 1); // + - - - - - - - + Max7219::set(Max7219::ROW1, 3); // + + - - - - - - + Max7219::set(Max7219::ROW2, 7); // + + + - - - - - + Max7219::set(Max7219::ROW3, 15); // + + + + - - - - + Max7219::set(Max7219::ROW4, 31); // + + + + + - - - + Max7219::set(Max7219::ROW5, 63); // + + + + + + - - + Max7219::set(Max7219::ROW6, 127); // + + + + + + + - + Max7219::set(Max7219::ROW7, 255); // + + + + + + + + + +*/ + +template +class _Max7219 + { +public: + + // max7219 registers + static const byte NOOP = 0x00; + static const byte ROW0 = 0x01; + static const byte ROW1 = 0x02; + static const byte ROW2 = 0x03; + static const byte ROW3 = 0x04; + static const byte ROW4 = 0x05; + static const byte ROW5 = 0x06; + static const byte ROW6 = 0x07; + static const byte ROW7 = 0x08; + static const byte DECODE_MODE = 0x09; + static const byte INTENSITY = 0x0a; + static const byte SCANLIMIT = 0x0b; + static const byte SHUTDOWN = 0x0c; + static const byte DISPLAYTEST = 0x0f; + + static void init(byte intensity = 0x7f) + { + CLOCK_::modeOutput(); + DATA_::modeOutput(); + CS_::modeOutput(); + + CLOCK_::set(); + + // initialize all devices + set(SCANLIMIT, 0x07); + set(DECODE_MODE, 0x00); // using an led matrix (not digits) + set(SHUTDOWN, 0x01); // not in shutdown mode + set(DISPLAYTEST, 0x00); // no display test + + for (byte e = ROW0; e <= ROW7; ++e) + { + set(e, 0); // turn all LEDs off + } + set(INTENSITY, intensity & 0x0f); + } + + // This is the max7219 variant of SPI. + // We can't do it in hardware, so we bit-bang it + static void transmit(byte data) + { + for (byte i = 8; i > 0; --i) + { + byte mask = 0x01 << (i - 1); + CLOCK_::clear(); // tick + if (data & mask) + DATA_::set(); // send 1 + else + DATA_::clear(); // send 0 + CLOCK_::set(); // tock + } + } + + /// set a register for all cascaded devices + static void set(byte reg, byte val) + { + CS_::clear(); // begin + for (byte c = 0; c < max_; c++) + { + transmit(reg); // specify register + transmit(val); // ((data & 0x01) * 256) + data >> 1); // put data + } + CS_::clear(); // and load da shit + CS_::set(); + } + + /// set a register on device + static void set(byte nr, byte reg, byte val) + { + int c = 0; + CS_::clear(); + + for (c = max_; c > nr; c--) + { + transmit(NOOP); + transmit(0); + } + + transmit(reg); // specify register + transmit(val); // send value + + for (c = nr-1; c >= 1; c--) + { + transmit(NOOP); + transmit(0); + } + + CS_::clear(); // and load da shit + CS_::set(); + } + }; diff --git a/nanode/mac.h b/nanode/mac.h index a94443e..f5491ac 100644 --- a/nanode/mac.h +++ b/nanode/mac.h @@ -13,7 +13,7 @@ * NanodeMAC * Rufus Cable, June 2011 (threebytesfull) * - * Library version created by Andrew Lindsay for use with Nanode and + * Library version created by Andrew Lindsay for use with Nanode and * EtherShield Library at https://github.com/thiseldo/EtherShield * * Based on sample code to read the MAC address from the 11AA02E48 on the @@ -45,10 +45,10 @@ class NanodeMAC static const uint16_t TSTBY_US = 600; static const byte THDR_US = 6; #ifndef NANODEMAC_SLOW - static const double QUARTER_BIT = 2.5; + static constexpr double QUARTER_BIT = 2.5; static const byte HALF_BIT = 5; #else - static const double QUARTER_BIT = 10; + static constexpr double QUARTER_BIT = 10; static const byte HALF_BIT = 20; #endif @@ -171,13 +171,13 @@ class NanodeMAC sendByte(0x00); // word address LSB 0xFA sendByte(0xFA); - + // read 6 bytes into array readBytes(macaddr_, 6); // No need to wait here, since we standby() before doing anything. fastStandby(); - + // Re-enable interrupts AVRBase::interrupts(); } diff --git a/pushbutton.h b/pushbutton.h index 4f3facb..cab1645 100644 --- a/pushbutton.h +++ b/pushbutton.h @@ -5,7 +5,7 @@ #include "arduino--.h" -template +template class PushButton { public: @@ -17,21 +17,21 @@ class PushButton keydown }; - void init() + static void init() { - Pin::modeInput(); + Pin_::modeInput(); // aktivate pullup - Pin::set(); - previous_ = !Pin::read(); + Pin_::set(); + previous_ = !Pin_::read(); changed_ = Timer_::millis(); duration_ = 0; } - event_type read() + static event_type read() { const typename Timer_::time_res_t now = Timer_::millis(); // The button is active low - const bool pressed = !Pin::read(); + const bool pressed = !Pin_::read(); // If the switch changed, due to noise or pressing... if (pressed != previous_) @@ -41,7 +41,7 @@ class PushButton const typename Timer_::time_res_t delta = now - changed_; changed_ = now; - if (delta > debounce) + if (delta > debounce_) { if (pressed) { @@ -57,12 +57,22 @@ class PushButton } // duration is only valid after a keyup event - typename Timer_::time_res_t duration() { return duration_; } + static typename Timer_::time_res_t duration() { return duration_; } private: - bool previous_; - typename Timer_::time_res_t changed_; - typename Timer_::time_res_t duration_; + static bool previous_; + static typename Timer_::time_res_t changed_; + static typename Timer_::time_res_t duration_; }; +template +bool PushButton::previous_ = false; + +template +typename Timer_::time_res_t PushButton::changed_ = 0; + +template +typename Timer_::time_res_t PushButton::duration_ = 0; + + #endif diff --git a/quadrature.h b/quadrature.h new file mode 100644 index 0000000..48ff98c --- /dev/null +++ b/quadrature.h @@ -0,0 +1,114 @@ +/****************************************************************************** + * quadrature.h - Arduino library for reading quadrature encoders. + * Version 0.90 + * Created by Keith Neufeld, June 29, 2008. + * + * Templatified for use with arduino-- by Lars Immisch + * + * This work is licensed under the Creative Commons Attribution-Share Alike + * 3.0 Unported License. To view a copy of this license, visit + * http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to + * Creative Commons, 171 Second Street, Suite 300, San Francisco, California, + * 94105, USA. + ******************************************************************************/ + + +#ifndef ARDUINO_MINUS_MINUS_QUADRATURE_H +#define ARDUINO_MINUS_MINUS_QUADRATURE_H + +// These constants should probably be in the _Quadrature class, but I don't +// trust the compiler to fold multiple template instatiations into one yet + +namespace Encoding { + + static const char Half[4][4] = { + { 0, -1, 1, 0 }, // 00 -> 10 is CW + { 1, 0, 0, -1 }, // 01 -> 00 is CW + { -1, 0, 0, 1 }, // 10 -> 11 is CW + { 0, 1, -1, 0 } // 11 -> 01 is CW + }; + + static const char Full[4][4] = { + { 0, 0, 0, 0 }, // 00 -> 10 is silent CW + { 1, 0, 0, -1 }, // 01 -> 00 is CW + { -1, 0, 0, 1 }, // 10 -> 11 is CW + { 0, 0, 0, 0 } // 11 -> 01 is silent CW + }; + + static const char Alps[4][4] = { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { -1, 0, 0, 0 }, + { 0, 0, 0, 0 } + }; + + static const int NoLimit = -1; +}; + +template +class _Quadrature +{ +public: + + static void init() + { + Pin1_::modeInputPullup(); + Pin2_::modeInputPullup(); + + _previous = _readpins(); // read initial position + } + + static int position(void) + { + return _position; + } + + static void position(int pos) + { + _position = pos; + } + + /** This should be called from the clock ISR + */ + static int update() + { + const int quadbits = _readpins(); + + if (quadbits != _previous) + { + int position = _position + + encoding_[_previous][quadbits]; + + // limit to minimum if assigned + position = (min_ != Encoding::NoLimit) + ? (min_ > position ? min_ : position) : position; + + // limit to maximum if assigned + _position = (max_ != Encoding::NoLimit) + ? (max_ < position ? max_ : position) : position; + + _previous = quadbits; + } + + return quadbits; + } + +private: + + static int _readpins(void) + { + return Pin2_::read() << 1 | Pin1_::read(); + } + + volatile static int _position; + volatile static int _previous; +}; + +template +volatile int _Quadrature::_position = 0; + +template +volatile int _Quadrature::_previous = 0; + +#endif // ARDUINO_MINUS_MINUS_QUADRATURE_H diff --git a/queue.h b/queue.h new file mode 100644 index 0000000..58dda15 --- /dev/null +++ b/queue.h @@ -0,0 +1,38 @@ +#ifndef ARDUINO_MINUS_MINUS_QUEUE_H +#define ARDUINO_MINUS_MINUS_QUEUE_H + +// A simple queue +template class +Queue + { +public: + + Queue() : _rpos(0), _wpos(0) {} + + void push(const VAL_ v) + { + if (_wpos >= len_) + return; + + _items[_wpos++] = v; + } + + bool empty() const { return _wpos == 0; } + + bool get(VAL_ &v) + { + if (_wpos <= _rpos) + { + return false; + } + + v = _items[_rpos] + } + +protected: + byte _rpos; + byte _wpos; + VAL_ _items[len_]; + } + +#endif diff --git a/ringbuffer.h b/ringbuffer.h new file mode 100644 index 0000000..628e556 --- /dev/null +++ b/ringbuffer.h @@ -0,0 +1,100 @@ +// ringbuffer.h +// Author: Markus Redeker + +// small changes for arduino-- by Lars Immisch + +#ifndef ARDUINO_MINUS_MINUS_RINGBUFFER_H +#define ARDUINO_MINUS_MINUS_RINGBUFFER_H + +// Usage: + +// _Ringbuffer<...> r; +// r.push(new_element); +// oldest_element = r.front(); r.pop(); + +template +class _Ringbuffer + { +public: + typedef ET_ value_type; + typedef ST_ size_type; + + _Ringbuffer() + { + clear(); + } + + ~_Ringbuffer() {} + + size_type size() const { return _size; } + size_type max_size() const { return S_; } + + bool empty() const { return _size == 0; } + bool full() const { return _size == S_; } + + value_type& front() { return _buffer[_front]; } + value_type& back() { return _buffer[_back]; } + + void clear() + { + _size = 0; + _front = 0; + _back = S_ - 1; + } + + void push() + { + _back = (_back + 1) % S_; + if( size() == S_ ) + _front = (_front + 1) % S_; + else + _size++; + } + + void push(const value_type& x) + { + push(); + back() = x; + } + + void pop() + { + if( _size > 0 ) + { + _size--; + _front = (_front + 1) % S_; + } + } + + void back_erase(const size_type n) + { + if( n >= _size ) + clear(); + else + { + _size -= n; + _back = (_front + _size - 1) % S_; + } + } + + void front_erase(const size_type n) + { + if( n >= _size ) + clear(); + else + { + _size -= n; + _front = (_front + n) % S_; + } + } + +protected: + + value_type _buffer[S_]; + + volatile size_type _size; + volatile size_type _front; + volatile size_type _back; +}; + +#endif // ARDUINO_MINUS_MINUS_RINGBUFFER_H diff --git a/sizes/README.md b/sizes/README.md new file mode 100644 index 0000000..675ea3a --- /dev/null +++ b/sizes/README.md @@ -0,0 +1,63 @@ +# Overview + +In this directory, we generate graphs that track the development of the +binary sizes of the generated code so that we can quickly see outliers +(or better, drastical improvements) + +# sizes.py + +`sizes.py` contains various functions to maintain the sizes files +and generate the HTML. + +There are two sizes we keep track of: + +_recent sizes_ and _git sizes_. + +The recent sizes keep track of the sizes of binary files between commits. +This file (by default recent_sizes.json) is updated with each make. + +The format is: + +{ + "4.6.2": { + "blink.bin": [ + { + "index": 1, + "mtime": 1329313423, + "size": 226 + }, + { + "index": 2, + "mtime": 1329313442, + "size": 220 + }, + ] + } +} + +The git sizes hold the historic data of the current branch and can be updated +by calling this script with the "history" command, e.g. +python sizes/sizes.py history + +(This must be done from a clean working directory i.e. one without modifications +relevant to git) + +The format is: + +[ + { + "git": { + "comment": "First cut.", + "short": "1a32447", + "hash": "1a324470852211e09f383615617d9fd0f159e385", + "author": "Ben Laurie", + "date": "2011-12-18 22:20:44 +0000", + "email": "ben@links.org" + }, + "4.6.2": { + "test_enc28j60.bin": 1358 + } + }, + ... +] + diff --git a/sizes/sizes.py b/sizes/sizes.py index a28273f..ee4c0a1 100644 --- a/sizes/sizes.py +++ b/sizes/sizes.py @@ -8,7 +8,7 @@ import subprocess import tempfile import datetime -from optparse import OptionParser +from argparse import ArgumentParser from urlparse import urlparse template = 'sizes/sizes.template' @@ -17,60 +17,6 @@ recent_sizes_json = 'sizes/recent_sizes.json' quiet = False -'''Various functions to maintain the sizes files and generate the HTML. - -There are two sizes we keep track of: - -recent sizes and git sizes. - -The recent sizes keep track of the sizes of binary files between commits. -This file (by default recenet_sizes.json) is updated with each make. - -The format is: - -{ - "4.6.2": { - "blink.bin": [ - { - "index": 1, - "mtime": 1329313423, - "size": 226 - }, - { - "index": 2, - "mtime": 1329313442, - "size": 220 - }, - ] - } -} - -The git sizes hold the historic data of the current branch and should be updated -by calling this script with the "history" command, e.g. -python sizes/sizes.py history - -It is recommended to update the history in the git post-commit hook or -when a branch is created from an older commit. - -The format is: - -[ - { - "git": { - "comment": "First cut.", - "short": "1a32447", - "hash": "1a324470852211e09f383615617d9fd0f159e385", - "author": "Ben Laurie", - "date": "2011-12-18 22:20:44 +0000", - "email": "ben@links.org" - }, - "4.6.2": { - "test_enc28j60.bin": 1358 - } - }, - ... -] -''' def run(*args): '''Run a command and return stdout and stderr as list of lines''' @@ -504,20 +450,25 @@ def update_history(version, branch = None): return sizes if __name__ == '__main__': - parser = OptionParser('usage: %prog OPTIONS recent|generate|history|prune-boring+') - parser.add_option("-r", "--remote", default='origin', + parser = ArgumentParser(description = 'Update binary size history/graphs') + parser.add_argument("-r", "--remote", default='origin', help="the git REMOTE name (default is origin)") - parser.add_option("-q", "--quiet", action='store_true', + parser.add_argument("-q", "--quiet", action='store_true', help="be somewhat quiet") - options, args = parser.parse_args() + parser.add_argument('command', nargs='+', + help=" one or more of 'recent', 'history', " + "'generate', 'prune-boring'." + "'recent' and 'history' update the JSON data," + "'generate' creates/updates the graphs") + args = parser.parse_args() - quiet = options.quiet + quiet = args.quiet version = avr_gcc_version() recent_sizes = None git_sizes = None - for a in args: + for a in args.command: if a == 'recent': recent_sizes = update_recent(version) write_sizes(recent_sizes, 'sizes/recent_sizes.json') @@ -525,11 +476,11 @@ def update_history(version, branch = None): git_sizes = update_history(version) write_sizes(git_sizes, 'sizes/git_sizes.json') elif a == 'generate': - generate(version, git_sizes, recent_sizes, options.remote) + generate(version, git_sizes, recent_sizes, args.remote) elif a == 'prune-boring': prune_boring_git_sizes() else: parser.print_help() else: - generate(version, git_sizes, recent_sizes, options.remote) + generate(version, git_sizes, recent_sizes, args.remote) diff --git a/spi.h b/spi.h index 4ef3080..f466220 100644 --- a/spi.h +++ b/spi.h @@ -3,7 +3,18 @@ #include "arduino--.h" -template +class NullPin + { +public: + static void modeOutput() { } + static void modeInput() { } + static void set() { } + static void clear() { } + // Using read() or toggle() will trigger a compilation error. + }; + + +template class _SPI { public: @@ -53,18 +64,26 @@ template ::delayMicroseconds(delay); return SPDR; } - }; -class NullPin - { -public: - static void modeOutput() { } - static void modeInput() { } - static void set() { } - static void clear() { } - // Using read() or toggle() will trigger a compilation error. - }; + static uint16_t transfer(uint16_t value, byte delay = 0) + { + uint16_t result = 0; + Ss::clear(); + SPDR = (value & 0xff00) >> 8; + wait(); + result = SPDR << 8; + SPDR = value & 0xff; + wait(); + result |= SPDR; + + Ss::set(); + if (delay > 0) + ::delayMicroseconds(delay); + return result; + } + + }; typedef _SPI SPI; typedef _SPI SPISS; diff --git a/test/test_max7219.cc b/test/test_max7219.cc new file mode 100644 index 0000000..37f785c --- /dev/null +++ b/test/test_max7219.cc @@ -0,0 +1,34 @@ +#include "arduino--.h" +#include "max7219.h" +#include "clock16.h" + +typedef class _Max7219 Max7219; + +int main(void) + { + Arduino::init(); + Max7219::init(); + + Max7219::set(Max7219::ROW7, 0); // - - - - - - - - + Max7219::set(Max7219::ROW6, 208); // - - - - + - + + + Max7219::set(Max7219::ROW5, 80); // - - - - + - + - + Max7219::set(Max7219::ROW4, 213); // + - + - + - + + + Max7219::set(Max7219::ROW3, 85); // + - + - + - + - + Max7219::set(Max7219::ROW2, 86); // - + - - + - + - + Max7219::set(Max7219::ROW1, 0); // - - - - - - - - + Max7219::set(Max7219::ROW0, 0); // - - - - - - - - + + while(true) + { + for (byte i = Max7219::ROW0; i <= Max7219::ROW7; ++i) + { + for (byte j = 0; j < 8; ++j) + { + Max7219::set(i, 1 << j); + Clock16::sleep(50); + Max7219::set(i, 0); + } + } + } + return 0; + } diff --git a/test/test_wait.cc b/test/test_wait.cc new file mode 100644 index 0000000..b66944b --- /dev/null +++ b/test/test_wait.cc @@ -0,0 +1,54 @@ +#include "arduino--.h" +#define CLOCK_NO_ISR +#include "clock16.h" +#include "ringbuffer.h" +#include "eventwait.h" + +/** This test program blinks the LED on D13/B5 by sending a message from + the timer0 overflow ISR to the main program. + */ + +typedef _Ringbuffer BoolBuffer; + +_EventWait BoolQueue; + +ISR(TIMER0_OVF_vect) +{ + static int count = 0; + static bool v = true; + + ++count; + + if (count == 1000) { + BoolQueue.push(v); + v = !v; + count = 0; + } + + clock16_isr(); +} + +int main(void) +{ + Arduino::init(); + Pin::B5::modeOutput(); + + for(;;) { + + BoolQueue.wait_event(); + + if (!BoolQueue.empty()) { + + if (BoolQueue.front()) { + Pin::B5::set(); + } + else { + Pin::B5::clear(); + } + + BoolQueue.pop(); + } + } + + return 0; +} diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..1113084 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,64 @@ +# +# This makefile takes each "at*" file, extracts it's part name +# And compiles it into an ELF binary. +# It also disassembles it for debugging purposes. +# +# Copyright 2008-2012 Michel Pollet +# +# This file is part of simavr. +# +# simavr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# simavr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with simavr. If not, see . + +sources := $(wildcard at*.cc) +simavr = .. + +IPATH += ${simavr}/include +IPATH += ${simavr}/simavr/sim + +tests_src := ${wildcard test_*.c} + +all: obj axf tests + +include ../Makefile.common + +tests := ${patsubst %.c, ${OBJ}/%.tst, ${tests_src}} + +tests: ${tests} + +axf: ${sources:.c=.axf} + + +${OBJ}/%.tst: tests.c %.c +ifeq ($(V),1) + $(CC) -MMD ${CPPFLAGS} ${CFLAGS} ${LFLAGS} -o $@ ${patsubst %.h,, ${^}} $(LDFLAGS) +else + @echo TST $@ + @$(CC) -MMD ${CPPFLAGS} ${CFLAGS} ${LFLAGS} -o $@ ${patsubst %.h,, ${^}} $(LDFLAGS) +endif + +run_tests: all + @export LD_LIBRARY_PATH=${simavr}/simavr/${OBJ} ;\ + num_failed=0 ;\ + num_run=0 ;\ + for test in ${OBJ}/test_*.tst; do \ + num_run=$$(($$num_run+1)) ;\ + if ! $$test; then \ + echo "$$test returned with exit value $$?." ;\ + num_failed=$$(($$num_failed+1)) ;\ + fi ;\ + done ;\ + echo "Tests run: $$num_run Successes: $$(($$num_run-$$num_failed)) Failures: $$num_failed" + +clean: clean-${OBJ} + rm -f *.axf *.vcd diff --git a/tests/atmega2560_uart_echo.cc b/tests/atmega2560_uart_echo.cc new file mode 100644 index 0000000..fe67acc --- /dev/null +++ b/tests/atmega2560_uart_echo.cc @@ -0,0 +1,131 @@ +/* + atmega88_uart_echo.c + + This test case enables uart RX interupts, does a "printf" and then receive characters + via the interupt handler until it reaches a \r. + + This tests the uart reception fifo system. It relies on the uart "irq" input and output + to be wired together (see simavr.c) + */ + +#include +#include +#include +#include +#include +#include + +/* + * This demonstrate how to use the avr_mcu_section.h file + * The macro adds a section to the ELF file with useful + * information for the simulator + */ +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega2560"); +// tell simavr to listen to commands written in this (unused) register +AVR_MCU_SIMAVR_COMMAND(&GPIOR0); +AVR_MCU_SIMAVR_CONSOLE(&GPIOR1); + +/* + * This small section tells simavr to generate a VCD trace dump with changes to these + * registers. + * Opening it with gtkwave will show you the data being pumped out into the data register + * UDR0, and the UDRE0 bit being set, then cleared + */ +const struct avr_mmcu_vcd_trace_t _mytrace[] _MMCU_ = { + { AVR_MCU_VCD_SYMBOL("UDR3"), .what = (void*)&UDR3, }, + { AVR_MCU_VCD_SYMBOL("UDRE3"), .mask = (1 << UDRE3), .what = (void*)&UCSR3A, }, + { AVR_MCU_VCD_SYMBOL("GPIOR1"), .what = (void*)&GPIOR1, }, +}; +#ifdef USART3_RX_vect_num // stupid ubuntu has antique avr-libc +AVR_MCU_VCD_IRQ(USART3_RX); // single bit trace +#endif +AVR_MCU_VCD_ALL_IRQ(); // also show ALL irqs running + +volatile uint8_t cnt = 0; +volatile uint8_t done = 0; + +static int uart_putchar(char c, FILE *stream) +{ + uint8_t startcnt; + if (c == '\n') + uart_putchar('\r', stream); + loop_until_bit_is_set(UCSR3A, UDRE3); + + startcnt = cnt; + UDR3 = c; +// _delay_us(100); + + // Wait until we have received the character back + while(!done && cnt == startcnt) + { + UDR1 = 'a'; + UDR1 = '\n'; + sleep_cpu(); + } + + UDR1 = 'b'; + UDR1 = '\n'; + + _delay_us(1000); + + return 0; +} + +static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, + _FDEV_SETUP_WRITE); + +volatile uint8_t bindex = 0; +uint8_t buffer[80]; + +ISR(USART3_RX_vect) +{ + UDR1 = 'c'; + UDR1 = '\n'; + + uint8_t b = UDR3; + GPIOR1 = b; // for the trace file + buffer[bindex++] = b; + buffer[bindex] = 0; + cnt++; + if (b == '\n') + done++; +// sleep_cpu(); +} + +int main() +{ + stdout = &mystdout; + + UCSR3C = (3 << UCSZ30); // 8 bits + // see http://www.nongnu.org/avr-libc/user-manual/group__util__setbaud.html +#define BAUD 38400 +#include + UBRR3H = UBRRH_VALUE; + UBRR3L = UBRRL_VALUE; +#if USE_2X + UCSR3A |= (1 << U2X3); +#else + UCSR3A &= ~(1 << U2X3); +#endif + + // enable receiver & transmitter + UCSR3B |= (1 << RXCIE3) | (1 << RXEN3) | (1 << TXEN3); + + // this tells simavr to start the trace + GPIOR0 = SIMAVR_CMD_VCD_START_TRACE; + sei(); + printf("Hey there, this should be received back\n"); + loop_until_bit_is_set(UCSR3A, UDRE3); + + while (!done) + sleep_cpu(); + + cli(); + + printf("Received: %s", buffer); + + // this quits the simulator, since interupts are off + // this is a "feature" that allows running tests cases and exit + sleep_cpu(); +} diff --git a/tests/atmega48_disabled_timer.cc b/tests/atmega48_disabled_timer.cc new file mode 100644 index 0000000..0003f1c --- /dev/null +++ b/tests/atmega48_disabled_timer.cc @@ -0,0 +1,35 @@ +/* + * avrtest.c + * + * Created on: 1 Dec 2009 + * Author: jone + */ + +#include "arduino--.h" + +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega48"); + +ISR(TIMER0_COMPA_vect) +{ +} + +int main(void) +{ + // Set up timer0 - do not start yet + TCCR0A |= (1 << WGM01); // Configure timer 0 for CTC mode + TIMSK0 |= (1 << OCIE0A); // Enable CTC interrupt + OCR0A = 0xAA; // CTC compare value + + //TCCR0B |= (1 << CS00) | (1 << CS01); // Start timer: clk/64 + + sei(); // Enable global interrupts + + // here the interupts are enabled, but the interupt + // vector should not be called + sleep_mode(); + + // this should not be reached + cli(); + sleep_mode(); +} diff --git a/tests/atmega48_enabled_timer.cc b/tests/atmega48_enabled_timer.cc new file mode 100644 index 0000000..04b8db4 --- /dev/null +++ b/tests/atmega48_enabled_timer.cc @@ -0,0 +1,39 @@ +/* + * avrtest.c + * + * Created on: 4 Feb 2011 + * Author: sliedes + * This is a very slightly modified version of atmega48_disabled_timer.c + * by jone. + */ + +#include +#include +#include + +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega48"); + +ISR(TIMER0_COMPA_vect) +{ +} + +int main(void) +{ + // Set up timer0 - do not start yet + TCCR0A |= (1 << WGM01); // Configure timer 0 for CTC mode + TIMSK0 |= (1 << OCIE0A); // Enable CTC interrupt + OCR0A = 0xAA; // CTC compare value + + TCCR0B |= (1 << CS00) | (1 << CS01); // Start timer: clk/64 + + sei(); // Enable global interrupts + + // here the interupts are enabled, but the interupt + // vector should not be called + sleep_mode(); + + // this should not be reached + cli(); + sleep_mode(); +} diff --git a/tests/atmega48_watchdog_test.cc b/tests/atmega48_watchdog_test.cc new file mode 100644 index 0000000..cbcf4be --- /dev/null +++ b/tests/atmega48_watchdog_test.cc @@ -0,0 +1,84 @@ +/* + attiny13_watchdog_test.c + + Copyright 2008, 2009 Michel Pollet + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + + +#include +#include +#include +#include +#include +#include + +/* + * This demonstrate how to use the avr_mcu_section.h file + * The macro adds a section to the ELF file with useful + * information for the simulator + */ +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega48"); + +static int uart_putchar(char c, FILE *stream) { + if (c == '\n') + uart_putchar('\r', stream); + loop_until_bit_is_set(UCSR0A, UDRE0); + UDR0 = c; + return 0; +} + +static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, + _FDEV_SETUP_WRITE); + + +ISR(WDT_vect) +{ + // nothing to do here, we're just here to wake the CPU +} + +int main() +{ + stdout = &mystdout; + DDRD = (1< + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#include +#include +#include +#include +#include + +/* + * This demonstrate how to use the avr_mcu_section.h file + * The macro adds a section to the ELF file with useful + * information for the simulator + */ +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega644"); +AVR_MCU_VOLTAGES(3300, 3300, 3300); // 3.3V VCC, AVCC, VREF + +static int uart_putchar(char c, FILE *stream) { + if (c == '\n') + uart_putchar('\r', stream); + loop_until_bit_is_set(UCSR0A, UDRE0); + UDR0 = c; + return 0; +} + +static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, + _FDEV_SETUP_WRITE); + +volatile uint16_t adc_val[8]; + +ISR(ADC_vect) +{ + uint8_t mux = ADMUX, l = ADCL, h = ADCH; + uint8_t i = mux & 7; + adc_val[i] = l | (h << 8); + i = (i + 1) & 7; + ADMUX = (mux & 0xF0) | i; + if (i) + ADCSRA |= (1 << ADSC); // restart one now on the new channel +} + +void adc_init() +{ + // set ADC + ADMUX = (1 << REFS0); // use internal AVCC (3.3) + // enable ADC, start a conversion, with the interrupt + // and with a /128 integration clock + ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADIE) | 0x5; + // doesn't do anything in simavr for now + DIDR0 = 3; // disable digital on these 2 pins ADC0 and ADC1 +} + +int main(void) +{ + stdout = &mystdout; + + sei(); + printf("Read 8 ADC channels to test interrupts\n"); + + adc_init(); + + /* + * The interupt reads all 8 ADCs then stop... + * so this loop will eventually exits + */ + while (ADCSRA & (1 << ADSC)) + sleep_cpu(); + + printf("All done. Now reading the 1.1V value in pooling mode\n"); + ADCSRA &= ~(1 << ADIE); // remove interrupt + + // 1.1 reference voltage, left aligned + ADMUX = (ADMUX & ~0x1f)| (0 << ADLAR) | 0x1e; + ADCSRA |= (1 << ADSC) ; // start conversion + while (ADCSRA & (1 << ADSC)) + ; + uint16_t v = ADCL | (ADCH << 8); + uint16_t volts = (v * 3300L) >> 10; // div 1024 + printf("Read ADC value %04x = %d mvolts -- ought to be 1098\n", v, volts); + + ADCSRA &= ~(1 << ADEN); // disable ADC... + + cli(); + sleep_cpu(); + +} + diff --git a/tests/atmega88_ac_test.cc b/tests/atmega88_ac_test.cc new file mode 100755 index 0000000..8b04f96 --- /dev/null +++ b/tests/atmega88_ac_test.cc @@ -0,0 +1,179 @@ +/* + atmega88_ac_test.c + + Copyright 2017 Konstantin Begun + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +/* + * This demonstrate how to use the avr_mcu_section.h file + * The macro adds a section to the ELF file with useful + * information for the simulator + */ +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega88"); + +static int uart_putchar(char c, FILE *stream) { + if (c == '\n') + uart_putchar('\r', stream); + loop_until_bit_is_set(UCSR0A, UDRE0); + UDR0 = c; + return 0; +} + +static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, + _FDEV_SETUP_WRITE); + +volatile uint8_t int_done; + +ISR(ANALOG_COMP_vect) +{ + int_done = 1; +} + +ISR(TIMER1_CAPT_vect) +{ + int_done = 2; +} + +static inline void output_value() { + putchar(ACSR & _BV(ACO) ? '1':'0'); +} + +static inline void output_test(uint8_t expected) { + putchar(int_done == expected ? 'Y':'N'); +} + +int main(void) +{ + stdout = &mystdout; + + sei(); + + ACSR = 0; + + printf("Check analog comparator with polling values\n"); + + // check all the inputs + for(uint8_t t = 0; t < 2; ++t) { // run twice, first with AIN0, second with bandgap + ADCSRB = 0; // multiplexer off + _NOP(); // for sync delay + output_value(); + // now with multiplexer + ADCSRB = _BV(ACME); + _NOP(); + for(uint8_t i = 0; i < 8;++i) + { + // this is relying that all 3 mux bits are next to each other, which is the case for atmega88 + output_value(); + ADMUX += _BV(MUX0); + _NOP(); + } + // switch to bandgap + ACSR |= _BV(ACBG); + _NOP(); + } + + putchar('\n'); + + // check interrupts + printf("Check analog comparator interrupts\n"); + // in this test bandgap is expected to be above ADC0 and below ADC1 + + ADCSRB = _BV(ACME); + ADMUX = 0; // ADC0 + ACSR = _BV(ACBG) | _BV(ACIE) | _BV(ACI); // enable interrupts on output triggering + + // switch to ADC1 + int_done = 0; + ADMUX = _BV(MUX0); + _delay_us(500); + output_test(1); + + // back to ADC0 + int_done = 0; + ADMUX = 0; + _delay_us(500); + output_test(1); + + // change to trigger on falling edge + ACSR = _BV(ACBG) | _BV(ACIE) | _BV(ACI) | _BV(ACIS1); + + // switch to ADC1 (falling edge) + int_done = 0; + ADMUX = _BV(MUX0); + _delay_us(500); + output_test(1); + + // switch to ADC0 (rising edge) + int_done = 0; + ADMUX = 0; + _delay_us(500); + output_test(0); // expect no interrupt + + // change to trigger on rising edge + ACSR = _BV(ACBG) | _BV(ACIE) | _BV(ACI) | _BV(ACIS1) | _BV(ACIS0); + + // switch to ADC1 (falling edge) + int_done = 0; + ADMUX = _BV(MUX0); + _delay_us(500); + output_test(0); // expect no interrupt + + // switch to ADC0 (rising edge) + int_done = 0; + ADMUX = 0; + _delay_us(500); + output_test(1); + + putchar('\n'); + + // now check timer1 input capture + printf("Check analog comparator triggering timer capture\n"); + // setup AC for rising edge, disable comparator own interrupt and enable timer1 capture + ACSR = _BV(ACBG) | _BV(ACIC) | _BV(ACIS1) | _BV(ACIS0); + + // now set up timer to capture on rising edge + TCCR1A = 0; + TCNT1 = 5555; + TIMSK1 = _BV(ICIE1); // enable capture interrupt + TCCR1B = _BV(ICES1) | _BV(CS10); + + // switch to ADC1 (falling edge) + int_done = 0; + ADMUX = _BV(MUX0); + _delay_us(500); + output_test(0); // expect no interrupt + + // switch to ADC0 (rising edge) + int_done = 0; + ADMUX = 0; + _delay_us(500); + output_test(2); + + cli(); + sleep_cpu(); + +} + diff --git a/tests/atmega88_coroutine.cc b/tests/atmega88_coroutine.cc new file mode 100644 index 0000000..4f9f760 --- /dev/null +++ b/tests/atmega88_coroutine.cc @@ -0,0 +1,155 @@ +/* + atmega88_coroutine.c + + Copyright 2008-2013 Michel Pollet + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#ifndef __AVR_CR_H__ +#define __AVR_CR_H__ +/* + * Smallest coroutine implementation for AVR. Takes + * 23 + (24 * tasks) bytes of SRAM to run. + * + * Use it like: + * + * AVR_TASK(mytask1, 32); + * AVR_TASK(mytask2, 48); + * ... + * void my_task_function() { + * do { + * AVR_YIELD(mytask1, 1); + * } while (1); + * } + * ... + * main() { + * AVR_TASK_START(mytask1, my_task_function); + * AVR_TASK_START(mytask2, my_other_task_function); + * do { + * AVR_RESUME(mytask1); + * AVR_RESUME(mytask2); + * } while (1); + * } + * NOTE: Do *not* use "static" on the function prototype, otherwise it + * will fail to link (compiler doesn't realize the "jmp" is referencing) + */ +#include +#include +static inline void _set_stack(register void * stack) +{ + asm volatile ( + "in r0, __SREG__" "\n\t" + "cli" "\n\t" + "out __SP_H__, %B0" "\n\t" + "out __SREG__, r0" "\n\t" + "out __SP_L__, %A0" "\n\t" + : : "e" (stack) /* : */ + ); +} + +jmp_buf g_caller; +#define AVR_TASK(_name, _stack_size) \ + struct { \ + jmp_buf jmp; \ + uint8_t running : 1; \ + uint8_t stack[_stack_size]; \ + } _name +#define AVR_TASK_START(_name, _entry) \ + if (!setjmp(g_caller)) { \ + _set_stack(_name.stack+sizeof(_name.stack));\ + asm volatile ("rjmp "#_entry); \ + } +#define AVR_YIELD(_name, _sleep) \ + _name.running = !_sleep; \ + if (!setjmp(_name.jmp)) \ + longjmp(g_caller, 1) +#define AVR_RESUME(_name) \ + if (!setjmp(g_caller)) \ + longjmp(_name.jmp, 1) +#endif /* __AVR_CR_H__ */ + + + +#include +#include +#include +#include + +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega88"); + +static int uart_putchar(char c, FILE *stream) +{ + if (c == '\n') + uart_putchar('\r', stream); + loop_until_bit_is_set(UCSR0A, UDRE0); + UDR0 = c; + return 0; +} + +static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, + _FDEV_SETUP_WRITE); + + +AVR_TASK(mytask1, 32); +AVR_TASK(mytask2, 48); + +uint16_t t = 0; + +void task1_function() { + uint16_t c = 0; + do { + c++; + if (c == 1000) { + c = 0; + printf("task1\n"); + t++; + } + AVR_YIELD(mytask1, 1); + } while (1); +} +void task2_function() { + uint16_t c = 0; + do { + c++; + if (c == 2000) { + c = 0; + printf("task2\n"); + } + AVR_YIELD(mytask2, 1); + } while (1); +} + + +int main(void) +{ + stdout = &mystdout; + + sei(); // Enable global interrupts + printf("Starting\n"); + AVR_TASK_START(mytask1, task1_function); + AVR_TASK_START(mytask2, task2_function); + do { + AVR_RESUME(mytask1); + AVR_RESUME(mytask2); + } while (t < 500); + + // exit simavr + cli(); + sleep_mode(); +} + diff --git a/tests/atmega88_example.cc b/tests/atmega88_example.cc new file mode 100644 index 0000000..75194b7 --- /dev/null +++ b/tests/atmega88_example.cc @@ -0,0 +1,66 @@ +/* + atmega88_example.c + + */ + +#ifndef F_CPU +#define F_CPU 8000000 +#endif +#include +#include +#include +#include +#include + +/* + * This demonstrate how to use the avr_mcu_section.h file + * The macro adds a section to the ELF file with useful + * information for the simulator + */ +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega88"); + +/* + * This small section tells simavr to generate a VCD trace dump with changes to these + * registers. + * Opening it with gtkwave will show you the data being pumped out into the data register + * UDR0, and the UDRE0 bit being set, then cleared + */ +const struct avr_mmcu_vcd_trace_t _mytrace[] _MMCU_ = { + { AVR_MCU_VCD_SYMBOL("UDR0"), .what = (void*)&UDR0, }, + { AVR_MCU_VCD_SYMBOL("UDRE0"), .mask = (1 << UDRE0), .what = (void*)&UCSR0A, }, +}; + + +/* declare this in a .eeprom ELF section */ +uint32_t value EEMEM = 0xdeadbeef; + +static int uart_putchar(char c, FILE *stream) { + if (c == '\n') + uart_putchar('\r', stream); + loop_until_bit_is_set(UCSR0A, UDRE0); + UDR0 = c; + return 0; +} + +static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, + _FDEV_SETUP_WRITE); + + +int main() +{ + stdout = &mystdout; + + // read the eeprom value + uint32_t c = eeprom_read_dword((void*)&value); + printf("Read from eeprom 0x%08lx -- should be 0xdeadbeef\n", c); + // change the eeprom + eeprom_write_dword((void*)&value, 0xcafef00d); + // re-read it + c = eeprom_read_dword((void*)&value); + printf("Read from eeprom 0x%08lx -- should be 0xcafef00d\n", c); + + // this quits the simulator, since interupts are off + // this is a "feature" that allows running tests cases and exit + sleep_cpu(); +} diff --git a/tests/atmega88_timer16.cc b/tests/atmega88_timer16.cc new file mode 100644 index 0000000..64233e7 --- /dev/null +++ b/tests/atmega88_timer16.cc @@ -0,0 +1,101 @@ +/* + atmega88_timer16.c + + Copyright 2008, 2009 Michel Pollet + + This file is part of simavr. + + simavr is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simavr is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simavr. If not, see . + */ + +#include +#include +#include +#include + +/* + * This demonstrates how to use the avr_mcu_section.h file. + * The macro adds a section to the ELF file with useful + * information for the simulator. + */ +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega88"); + +/* + * This small section tells simavr to generate a VCD trace dump with changes to these + * registers. + * Opening it with gtkwave will show you the data being read & written to these + * It also demonstrate how you can use unused pins to generate your own traces, with + * your own events to be displayed. + * + * Here the port B first 2 bits are used to display when a tick occurs, and when a + * TCNT reset occurs. + */ +const struct avr_mmcu_vcd_trace_t _mytrace[] _MMCU_ = { + { AVR_MCU_VCD_SYMBOL("TCNT1L"), .what = (void*)&TCNT1L, }, + { AVR_MCU_VCD_SYMBOL("TCNT1H"), .what = (void*)&TCNT1H, }, +}; +AVR_MCU_VCD_PORT_PIN('B', 3, "OC2A"); +AVR_MCU_VCD_PORT_PIN('B', 1, "reset_timer"); +AVR_MCU_VCD_PORT_PIN('B', 0, "tick"); + +volatile uint16_t tcnt; + +ISR(TIMER2_COMPA_vect) // handler for Output Compare 2 overflow interrupt +{ + // this really doesn't no anything but proves a way to wake the main() + // from sleep at regular intervals + PORTB ^= 1; +} + +int main() +{ + // + // start the 16 bits timer, with default "normal" waveform + // and no interupt enabled. This just increments TCNT1 + // at a regular rate + // + // timer prescaler to 64 + TCCR1B |= (0< 10000) { + TCNT1 = 500; // reset it arbitrarily + PORTB ^= 2; // mark it in the waveform file + } + sleep_cpu(); // this will sleep until a new timer2 tick interrupt occurs + } + // sleeping with interrupt off is interpreted by simavr as "exit please" + cli(); + sleep_cpu(); +} diff --git a/tests/atmega88_uart_echo.cc b/tests/atmega88_uart_echo.cc new file mode 100644 index 0000000..31f0ab4 --- /dev/null +++ b/tests/atmega88_uart_echo.cc @@ -0,0 +1,101 @@ +/* + atmega88_uart_echo.c + + This test case enables uart RX interupts, does a "printf" and then receive characters + via the interupt handler until it reaches a \r. + + This tests the uart reception fifo system. It relies on the uart "irq" input and output + to be wired together (see simavr.c) + */ + +#include +#include +#include +#include +#include + +/* + * This demonstrate how to use the avr_mcu_section.h file + * The macro adds a section to the ELF file with useful + * information for the simulator + */ +#include "avr_mcu_section.h" +AVR_MCU(F_CPU, "atmega88"); +// tell simavr to listen to commands written in this (unused) register +AVR_MCU_SIMAVR_COMMAND(&GPIOR0); + +/* + * This small section tells simavr to generate a VCD trace dump with changes to these + * registers. + * Opening it with gtkwave will show you the data being pumped out into the data register + * UDR0, and the UDRE0 bit being set, then cleared + */ +const struct avr_mmcu_vcd_trace_t _mytrace[] _MMCU_ = { + { AVR_MCU_VCD_SYMBOL("UDR0"), .what = (void*)&UDR0, }, + { AVR_MCU_VCD_SYMBOL("UDRE0"), .mask = (1 << UDRE0), .what = (void*)&UCSR0A, }, + { AVR_MCU_VCD_SYMBOL("GPIOR1"), .what = (void*)&GPIOR1, }, +}; + +static int uart_putchar(char c, FILE *stream) +{ + if (c == '\n') + uart_putchar('\r', stream); + loop_until_bit_is_set(UCSR0A, UDRE0); + UDR0 = c; + return 0; +} + +static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, + _FDEV_SETUP_WRITE); + +volatile uint8_t bindex = 0; +uint8_t buffer[80]; +volatile uint8_t done = 0; + +ISR(USART_RX_vect) +{ + uint8_t b = UDR0; + GPIOR1 = b; // for the trace file + buffer[bindex++] = b; + buffer[bindex] = 0; + if (b == '\n') + done++; +} + +int main() +{ + // this tell simavr to put the UART in loopback mode + GPIOR0 = SIMAVR_CMD_UART_LOOPBACK; + + stdout = &mystdout; + + UCSR0C |= (3 << UCSZ00); // 8 bits + // see http://www.nongnu.org/avr-libc/user-manual/group__util__setbaud.html +#define BAUD 38400 +#include + UBRR0H = UBRRH_VALUE; + UBRR0L = UBRRL_VALUE; +#if USE_2X + UCSR0A |= (1 << U2X0); +#else + UCSR0A &= ~(1 << U2X0); +#endif + + // enable receiver & transmitter + UCSR0B |= (1 << RXCIE0) | (1 << RXEN0) | (1 << TXEN0); + + // this tells simavr to start the trace + GPIOR0 = SIMAVR_CMD_VCD_START_TRACE; + sei(); + printf("Hey there, this should be received back\n"); + + while (!done) + sleep_cpu(); + + cli(); + printf("Received: %s", buffer); + + // this quits the simulator, since interupts are off + // this is a "feature" that allows running tests cases and exit + sleep_cpu(); +} diff --git a/tests/attiny85_crash_gdb.cc b/tests/attiny85_crash_gdb.cc new file mode 100644 index 0000000..3bcaf59 --- /dev/null +++ b/tests/attiny85_crash_gdb.cc @@ -0,0 +1,28 @@ +/* + * attiny85_crash_gdb.c + * + * Created on: 1 Dec 2009 + * Author: jone + */ + +#include +#include +#include "avr_mcu_section.h" + +AVR_MCU(F_CPU, "attiny85"); + + +int main() +{ + + /* + * this is not much, but that crashed the core, and should activate + * the gdb server properly, so you can see it stopped, here + */ + + *((uint8_t*)0xdead) = 0x55; + + // should never reach here ! + + sleep_mode(); +} diff --git a/tests/test_atmega2560_uart_echo.c b/tests/test_atmega2560_uart_echo.c new file mode 100644 index 0000000..7f26cdf --- /dev/null +++ b/tests/test_atmega2560_uart_echo.c @@ -0,0 +1,22 @@ +#include "tests.h" +#include "avr_uart.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + + avr_t* avr = tests_init_avr("atmega2560_uart_echo.axf"); + avr->log = LOG_TRACE; + + avr_irq_t * src = avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ('3'), UART_IRQ_OUTPUT); + avr_irq_t * dst = avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ('3'), UART_IRQ_INPUT); + avr_connect_irq(src, dst); + + static const char *expected = + "Hey there, this should be received back\r\n" + "Received: Hey there, this should be received back\r\r\n"; + + tests_assert_uart_receive_avr(avr, 10000000, expected, '3'); + + tests_success(); + return 0; +} diff --git a/tests/test_atmega48_disabled_timer.c b/tests/test_atmega48_disabled_timer.c new file mode 100644 index 0000000..f4bd7ae --- /dev/null +++ b/tests/test_atmega48_disabled_timer.c @@ -0,0 +1,18 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + switch(tests_init_and_run_test("atmega48_disabled_timer.axf", 100000000)) { + case LJR_CYCLE_TIMER: + // the cycle timer fired + break; + case LJR_SPECIAL_DEINIT: + // sleep with interrupts off or some other such reason + fail("AVR woke up from sleep while it shouldn't have (after %" + PRI_avr_cycle_count " cycles)", tests_cycle_count); + default: + fail("Error in test case: Should never reach this."); + } + tests_success(); + return 0; +} diff --git a/tests/test_atmega48_enabled_timer.c b/tests/test_atmega48_enabled_timer.c new file mode 100644 index 0000000..22bade7 --- /dev/null +++ b/tests/test_atmega48_enabled_timer.c @@ -0,0 +1,15 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + switch(tests_init_and_run_test("atmega48_enabled_timer.axf", 100000000)) { + case LJR_CYCLE_TIMER: + fail("AVR did not wake up to the enabled timer."); + case LJR_SPECIAL_DEINIT: + break; + default: + fail("Error in test case: Should never reach this."); + } + tests_success(); + return 0; +} diff --git a/tests/test_atmega48_watchdog_test.c b/tests/test_atmega48_watchdog_test.c new file mode 100644 index 0000000..b2bc562 --- /dev/null +++ b/tests/test_atmega48_watchdog_test.c @@ -0,0 +1,15 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + + static const char *expected = + "Watchdog is active\r\n" + "Waiting for Watchdog to kick\r\n" + "Watchdog kicked us!\r\n"; + + tests_assert_uart_receive("atmega48_watchdog_test.axf", 1000000, + expected, '0'); + tests_success(); + return 0; +} diff --git a/tests/test_atmega644_adc_test.c b/tests/test_atmega644_adc_test.c new file mode 100644 index 0000000..a058f66 --- /dev/null +++ b/tests/test_atmega644_adc_test.c @@ -0,0 +1,15 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + + static const char *expected = + "Read 8 ADC channels to test interrupts\r\n" + "All done. Now reading the 1.1V value in pooling mode\r\n" + "Read ADC value 0155 = 1098 mvolts -- ought to be 1098\r\n"; + tests_assert_uart_receive("atmega644_adc_test.axf", 100000, + expected, '0'); + + tests_success(); + return 0; +} diff --git a/tests/test_atmega88_ac_test.c b/tests/test_atmega88_ac_test.c new file mode 100755 index 0000000..666470b --- /dev/null +++ b/tests/test_atmega88_ac_test.c @@ -0,0 +1,34 @@ +#include "tests.h" +#include "avr_acomp.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + + static const char *expected = + "Check analog comparator with polling values\r\n" + "110110101010000100\r\n" + "Check analog comparator interrupts\r\n" + "YYYYYY\r\n" + "Check analog comparator triggering timer capture\r\n" + "YY"; + + avr_t *avr = tests_init_avr("atmega88_ac_test.axf"); + + // set voltages + avr_raise_irq(avr_io_getirq(avr, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_AIN0), 2000); + avr_raise_irq(avr_io_getirq(avr, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_AIN1), 1800); + avr_raise_irq(avr_io_getirq(avr, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_ADC0), 200); + avr_raise_irq(avr_io_getirq(avr, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_ADC1), 3000); + avr_raise_irq(avr_io_getirq(avr, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_ADC2), 1500); + avr_raise_irq(avr_io_getirq(avr, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_ADC3), 1500); + avr_raise_irq(avr_io_getirq(avr, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_ADC4), 3000); + avr_raise_irq(avr_io_getirq(avr, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_ADC5), 200); + avr_raise_irq(avr_io_getirq(avr, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_ADC6), 3000); + avr_raise_irq(avr_io_getirq(avr, AVR_IOCTL_ACOMP_GETIRQ, ACOMP_IRQ_ADC7), 1500); + + tests_assert_uart_receive_avr(avr, 100000, + expected, '0'); + + tests_success(); + return 0; +} diff --git a/tests/test_atmega88_example.c b/tests/test_atmega88_example.c new file mode 100644 index 0000000..cafcf3e --- /dev/null +++ b/tests/test_atmega88_example.c @@ -0,0 +1,14 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + + static const char *expected = + "Read from eeprom 0xdeadbeef -- should be 0xdeadbeef\r\n" + "Read from eeprom 0xcafef00d -- should be 0xcafef00d\r\n"; + tests_assert_uart_receive("atmega88_example.axf", 100000, + expected, '0'); + + tests_success(); + return 0; +} diff --git a/tests/test_atmega88_timer16.c b/tests/test_atmega88_timer16.c new file mode 100644 index 0000000..0f2c895 --- /dev/null +++ b/tests/test_atmega88_timer16.c @@ -0,0 +1,20 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + enum tests_finish_reason reason = + tests_init_and_run_test("atmega88_timer16.axf", 10000000); + switch(reason) { + case LJR_CYCLE_TIMER: + fail("Test failed to finish properly; reason=%d, cycles=%" + PRI_avr_cycle_count, reason, tests_cycle_count); + break; + case LJR_SPECIAL_DEINIT: + break; + default: + fail("This should not be reached; reason=%d", reason); + } + tests_assert_cycles_between(12500000, 12500300); + tests_success(); + return 0; +} diff --git a/tests/test_atmega88_uart_echo.c b/tests/test_atmega88_uart_echo.c new file mode 100644 index 0000000..6ec944c --- /dev/null +++ b/tests/test_atmega88_uart_echo.c @@ -0,0 +1,14 @@ +#include "tests.h" + +int main(int argc, char **argv) { + tests_init(argc, argv); + + static const char *expected = + "Hey there, this should be received back\r\n" + "Received: Hey there, this should be received back\r\r\n"; + tests_assert_uart_receive("atmega88_uart_echo.axf", 100000, + expected, '0'); + + tests_success(); + return 0; +} diff --git a/tests/tests.c b/tests/tests.c new file mode 100644 index 0000000..579b562 --- /dev/null +++ b/tests/tests.c @@ -0,0 +1,254 @@ +#include "tests.h" +#include "sim_avr.h" +#include "sim_elf.h" +#include "sim_core.h" +#include "avr_uart.h" +#include +#include +#include +#include +#include +#include + +avr_cycle_count_t tests_cycle_count = 0; +int tests_disable_stdout = 1; + +static char *test_name = "(uninitialized test)"; +static int finished = 0; + +#ifdef __MINGW32__ +#define restore_stderr() {} +#define map_stderr() {} +#else +static FILE *orig_stderr = NULL; +#define restore_stderr() { if (orig_stderr) stderr = orig_stderr; } +#define map_stderr() { if (tests_disable_stdout) { \ + orig_stderr = stderr; \ + fclose(stdout); \ + stderr = stdout; \ + } } +#endif + +static void atexit_handler(void) { + if (!finished) + _fail(NULL, 0, "Test exit without indicating success."); +} + +void tests_success(void) { + restore_stderr(); + fprintf(stderr, "OK: %s\n", test_name); + finished = 1; +} + +void tests_init(int argc, char **argv) { + test_name = strdup(argv[0]); + atexit(atexit_handler); +} + +static avr_cycle_count_t +cycle_timer_longjmp_cb(struct avr_t *avr, avr_cycle_count_t when, void *param) { + jmp_buf *jmp = param; + longjmp(*jmp, LJR_CYCLE_TIMER); + return 0; // clear warning +} + +static jmp_buf *special_deinit_jmpbuf = NULL; + +static void special_deinit_longjmp_cb(struct avr_t *avr, void *data) { + if (special_deinit_jmpbuf) + longjmp(*special_deinit_jmpbuf, LJR_SPECIAL_DEINIT); +} + +static int my_avr_run(avr_t * avr) +{ + if (avr->state == cpu_Stopped) + return avr->state; + + uint16_t new_pc = avr->pc; + + if (avr->state == cpu_Running) + new_pc = avr_run_one(avr); + + // run the cycle timers, get the suggested sleep time + // until the next timer is due + avr_cycle_count_t sleep = avr_cycle_timer_process(avr); + + avr->pc = new_pc; + + if (avr->state == cpu_Sleeping) { + if (!avr->sreg[S_I]) { + printf("simavr: sleeping with interrupts off, quitting gracefully\n"); + avr_terminate(avr); + fail("Test case error: special_deinit() returned?"); + exit(0); + } + /* + * try to sleep for as long as we can (?) + */ + // uint32_t usec = avr_cycles_to_usec(avr, sleep); + // printf("sleep usec %d cycles %d\n", usec, sleep); + // usleep(usec); + avr->cycle += 1 + sleep; + } + // Interrupt servicing might change the PC too, during 'sleep' + if (avr->state == cpu_Running || avr->state == cpu_Sleeping) + avr_service_interrupts(avr); + + // if we were stepping, use this state to inform remote gdb + + return avr->state; +} + +avr_t *tests_init_avr(const char *elfname) { + tests_cycle_count = 0; + map_stderr(); + + elf_firmware_t fw; + if (elf_read_firmware(elfname, &fw)) + fail("Failed to read ELF firmware \"%s\"", elfname); + avr_t *avr = avr_make_mcu_by_name(fw.mmcu); + if (!avr) + fail("Creating AVR failed."); + avr_init(avr); + avr_load_firmware(avr, &fw); + return avr; +} + +int tests_run_test(avr_t *avr, unsigned long run_usec) { + if (!avr) + fail("Internal test error: avr == NULL in run_test()"); + // register a cycle timer to fire after 100 seconds (simulation time); + // assert that the simulation has not finished before that. + jmp_buf jmp; + special_deinit_jmpbuf = &jmp; + avr->custom.deinit = special_deinit_longjmp_cb; + avr_cycle_timer_register_usec(avr, run_usec, + cycle_timer_longjmp_cb, &jmp); + int reason = setjmp(jmp); + tests_cycle_count = avr->cycle; + if (reason == 0) { + // setjmp() returned directly, run avr + while (1) + my_avr_run(avr); + } else if (reason == 1) { + // returned from longjmp(); cycle timer fired + return reason; + } else if (reason == 2) { + // returned from special deinit, avr stopped + return reason; + } + fail("Error in test case: Should never reach this."); + return 0; +} + +int tests_init_and_run_test(const char *elfname, unsigned long run_usec) { + avr_t *avr = tests_init_avr(elfname); + return tests_run_test(avr, run_usec); +} + +struct output_buffer { + char *str; + int currlen; + int alloclen; + int maxlen; +}; + +/* static void buf_output_cb(avr_t *avr, avr_io_addr_t addr, uint8_t v, */ +/* void *param) { */ +static void buf_output_cb(struct avr_irq_t *irq, uint32_t value, void *param) { + struct output_buffer *buf = param; + if (!buf) + fail("Internal error: buf == NULL in buf_output_cb()"); + if (buf->currlen > buf->alloclen-1) + fail("Internal error"); + if (buf->alloclen == 0) + fail("Internal error"); + if (buf->currlen == buf->alloclen-1) { + buf->alloclen *= 2; + buf->str = realloc(buf->str, buf->alloclen); + } + buf->str[buf->currlen++] = value; + buf->str[buf->currlen] = 0; +} + +static void init_output_buffer(struct output_buffer *buf) { + buf->str = malloc(128); + buf->str[0] = 0; + buf->currlen = 0; + buf->alloclen = 128; + buf->maxlen = 4096; +} + +void tests_assert_uart_receive_avr(avr_t *avr, + unsigned long run_usec, + const char *expected, + char uart) { + struct output_buffer buf; + init_output_buffer(&buf); + + avr_irq_register_notify(avr_io_getirq(avr, AVR_IOCTL_UART_GETIRQ(uart), UART_IRQ_OUTPUT), + buf_output_cb, &buf); + enum tests_finish_reason reason = tests_run_test(avr, run_usec); + if (reason == LJR_CYCLE_TIMER) { + if (strcmp(buf.str, expected) == 0) { + _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. " + "UART output is correct and complete.", run_usec); + } + _fail(NULL, 0, "Simulation did not finish within %lu simulated usec. " + "UART output so far: \"%s\"", run_usec, buf.str); + } + if (strcmp(buf.str, expected) != 0) + _fail(NULL, 0, "UART outputs differ: expected \"%s\", got \"%s\"", expected, buf.str); +} + +void tests_assert_uart_receive(const char *elfname, + unsigned long run_usec, + const char *expected, + char uart) { + avr_t *avr = tests_init_avr(elfname); + + tests_assert_uart_receive_avr(avr, + run_usec, + expected, + uart); +} + +void tests_assert_cycles_at_least(unsigned long n) { + if (tests_cycle_count < n) + _fail(NULL, 0, "Program ran for too few cycles (%" + PRI_avr_cycle_count " < %lu)", tests_cycle_count, n); +} + +void tests_assert_cycles_at_most(unsigned long n) { + if (tests_cycle_count > n) + _fail(NULL, 0, "Program ran for too many cycles (%" + PRI_avr_cycle_count " > %lu)", tests_cycle_count, n); +} + +void tests_assert_cycles_between(unsigned long min, unsigned long max) { + tests_assert_cycles_at_least(min); + tests_assert_cycles_at_most(max); +} + +void _fail(const char *filename, int linenum, const char *fmt, ...) { + restore_stderr(); + + if (filename) + fprintf(stderr, "%s:%d: ", filename, linenum); + + fprintf(stderr, "Test "); + if (test_name) + fprintf(stderr, "%s ", test_name); + fprintf(stderr, "FAILED.\n"); + + if (filename) + fprintf(stderr, "%s:%d: ", filename, linenum); + + va_list va; + va_start(va, fmt); + vfprintf(stderr, fmt, va); + putc('\n', stderr); + + finished = 1; + _exit(1); +}