/* * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Joerg Wunsch * ---------------------------------------------------------------------------- * * Simple AVR demonstration. Controls a LED that can be directly * connected from OC1 (PB1 on the '2333 chip, PB3 on the '2313 chip) * to GND. The brightness of the LED is controlled with the PWM. * * PB2 will be toggled with each internal clock tick (approx. ~ 10 ms). * * PD0 and PD1 are configured as UART IO, they need a MAX232 or * compatible in order to connect them to the serial port of a PC. * Simply run "cu -lcuaa0" on that PC to connect to the MCU. (If * your port is in -clocal mode, also shortcut DTR with DCD on the * RS-232 connector. If it is in crtscts mode, also shortcut DTR * with CTS.) The demo application talks to the serial port, and it * can be controlled from the serial port. * * PD2 through PD4 are configured as inputs, and control the * application unless control has been taken over by the serial * port. Shorting PD2 to GND will decrease the current PWM value, * shorting PD3 to GND will increase it. * * When using a '2333 chip, the on-chip ADC can be included into the * demonstration. While PD4 is shorted to GND, one ADC conversion * for channel 0 (ADC input is on PC0) will be triggered each internal * clock tick, and the resulting value will be used as the PWM value. * So the brightness of the LED follows the analog input value on PC0. * Don't forget to connect the AVCC and AREF pins, the latter could * simply be tight to VCC as well for this simple demo. * * When running in serial control mode, the functioning of the * watchdog timer can be demonstrated by typing an `r'. This will * make the demo application run in a tight loop without further * watchdog resets, so after some seconds, the watchdog will reset the * MCU. On the '2333, this situation can be figured out on startup by * reading the MCUSR register. * * The current value of the PWM is backed up in an EEPROM cell after * some idle time after the last change. If that EEPROM cell contains * a reasonable (i. e. non-erased) value at startup, it is taken as the * initial value for the PWM. This virtually preserves the last value * across power cycles. By not updating the EEPROM immmediately but * only after a timeout, we preserve a bit of the EEPROM's life. * * Code sizes by now are (for the '2333, -O2, avr-gcc 3.0.1): * * 1500 bytes of flash size, containing: * 28 bytes interrupt vectors * 320 bytes of constant strings [__attribute__((progmem))] * 878 bytes of application code, herein are * 158 bytes of interrupt routines * 50 bytes of run-time system (initialization etc.) * 222 bytes of library code (EEPROM handling, integer division/ * modulo routine) * 2 bytes of initialization data for the .data variables * * 2 bytes of EEPROM data * * (total: 1502 bytes to download to MCU) * * 16 bytes of RAM data, containing: * 2 bytes of initialized data, 1 byte from application program, * 1 byte padding * 16 bytes of uninitialized data (.bss), zeroed out at startup * by the initialization code * * $Id: demo.c,v 1.20 2005/04/08 12:05:13 j Exp $ */ #include #include #include #include #include #include #include #include #if defined(__AVR_AT90S2333__) # define HAVE_ADC 1 # define OC1 PB1 #elif defined(__AVR_AT90S2313__) # define UCSRA USR # define UCSRB UCR # define OC1 PB3 #else # error "Don't know what kind of MCU you are compiling for" #endif #define TRIGGER_DOWN PD2 #define TRIGGER_UP PD3 #define TRIGGER_ADC PD4 //#define F_CPU 9216000UL /* CPU clock in Hertz */ #define F_CPU 4000000UL /* CPU clock in Hertz */ /* * Timeout to wait after last PWM change till updating the EEPROM. * Measured in internal clock ticks (approx. 100 Hz). */ #define EE_UPDATE_TIME (3 * 100) /* ca. 3 seconds */ /* * Bits that are set inside interrupt routines, and watched outside in * the program's main loop. */ volatile struct { uint8_t tmr_int: 1; #if HAVE_ADC uint8_t adc_int: 1; #endif uint8_t rx_int: 1; } intflags; /* * Last character read from the UART. */ volatile char rxbuff; #if HAVE_ADC /* * Last value read from ADC. */ volatile uint16_t adcval; #endif /* * Where to store the PWM value in EEPROM. This is used in order * to remember the value across a RESET or power cycle. */ uint16_t ee_pwm __attribute__((section(".eeprom"))); /* * Current value of the PWM. */ int16_t pwm; /* * EEPROM backup timer. Bumped by the PWM update routine. If it * expires, the current PWM value will be written to EEPROM. */ int16_t pwm_backup_tmr; /* * Timer1 overflow interrupt will be called with F_CPU / 2046 * frequency. This interrupt routine further divides that value, * resulting in an internal update interval of approx. 10 ms. * (The complicated looking scaling by 10 / addition of 9 is * poor man's fixed-point rounding algorithm...) */ #define TMR1_SCALE ((F_CPU * 10) / (2046UL * 100) + 9) / 10 SIGNAL(SIG_OVERFLOW1) { static uint8_t scaler = TMR1_SCALE; if (--scaler == 0) { scaler = TMR1_SCALE; intflags.tmr_int = 1; } } #if HAVE_ADC /* * ADC conversion complete. Fetch the 10-bit value, and feed the * PWM with it. */ SIGNAL(SIG_ADC) { adcval = ADCW; intflags.adc_int = 1; } #endif /* HAVE_ADC */ /* * UART receive interrupt. Fetch the character received and buffer * it, unless there was a framing error. Note that the main loop * checks the received character only once per 10 ms. */ SIGNAL(SIG_UART_RECV) { uint8_t c; c = UDR; if (bit_is_clear(UCSRA, FE)) { rxbuff = c; intflags.rx_int = 1; } } /* * Do all the startup-time peripheral initializations. */ void ioinit(void) { uint16_t pwm_from_eeprom; #if defined(COM11) TCCR1A = _BV(PWM10)|_BV(PWM11)|_BV(COM11); /* tmr1 is 10-bit PWM */ #elif defined(COM1A1) TCCR1A = _BV(PWM10)|_BV(PWM11)|_BV(COM1A1); /* tmr1 is 10-bit PWM */ #else # error "need either COM1A1 or COM11" #endif TCCR1B = _BV(CS10); /* tmr1 running on full MCU clock */ OCR1 = 0; /* set PWM value to 0 */ DDRB = _BV(OC1)|_BV(PB2); /* enable OC1 and PB2 as outputs */ /* enable pull-ups for our trigger inputs */ PORTD = _BV(TRIGGER_DOWN)|_BV(TRIGGER_UP)|_BV(TRIGGER_ADC); DDRD = _BV(PD1); /* enable UART TxD output */ UCSRB = _BV(TXEN)|_BV(RXEN)|_BV(RXCIE); /* tx/rx enable, rx complete intr */ UBRR = (F_CPU / (16 * 9600UL)) - 1; /* 9600 Bd */ #if HAVE_ADC /* * enable ADC, enable ADC conversion complete interrupt, * select ADC clock = F_CPU / 32 if F_CPU less than * 6.4 MHz, otherwise F_CPU / 64. */ # if F_CPU < 6400000UL ADCSR = _BV(ADEN) | _BV(ADIE) | _BV(ADPS2) | _BV(ADPS0); # else ADCSR = _BV(ADEN) | _BV(ADIE) | _BV(ADPS2) | _BV(ADPS1); # endif #endif /* HAVE_ADC */ TIMSK = _BV(TOIE1); sei(); /* enable interrupts */ /* * Enable the watchdog with the largest prescaler. Will cause a * watchdog reset after approximately 2 s @ Vcc = 5 V (appr. 6 s @ * Vcc = 2.6 V) */ wdt_enable(WDTO_2S); /* * Read the value from EEPROM. If it is not 0xffff (erased cells), * use it as the starting value for the PWM. */ if ((pwm_from_eeprom = eeprom_read_word(&ee_pwm)) != 0xffff) OCR1 = (pwm = pwm_from_eeprom); } /* * Some simple UART IO functions. */ /* * Send character c down the UART Tx, wait until tx holding register * is empty. */ void putchar(char c) { loop_until_bit_is_set(UCSRA, UDRE); UDR = c; } /* * Send a C (NUL-terminated) string down the UART Tx. */ void printstr(const char *s) { while (*s) { if (*s == '\n') putchar('\r'); putchar(*s++); } } /* * Same as above, but the string is located in program memory, * so "lpm" instructions are needed to fetch it. */ void printstr_p(const char *s) { char c; for (c = pgm_read_byte(s); c; ++s, c = pgm_read_byte(s)) { if (c == '\n') putchar('\r'); putchar(c); } } /* * Update the PWM value. If it has changed, send the new value down * the serial line. */ void set_pwm(int16_t new) { char s[8]; if (new < 0) new = 0; else if (new > 1023) new = 1023; if (new != pwm) { OCR1 = (pwm = new); itoa(new, s, 10); printstr(s); putchar(' '); pwm_backup_tmr = EE_UPDATE_TIME; } } /* * A bunch of constant strigs to be stored in program memory. Note * that all other strings (and initialized variables) have to be moved * out of the flash ROM into RAM cells at startup. This is done * `behind the scene' by the startup code. * * __attribute__((progmem)) items seem to have to be at global level, * otherwise the compiler will throw them into section .data (as * opposed to .progmem.data). I believe that's a compiler bug. */ const char greeting[] __attribute__((progmem)) = "\nHello, this is the avr-gcc/libc demo running on an " #if defined(__AVR_AT90S2333__) "AT90S2333 (with ADC)" #elif defined(__AVR_AT90S2313__) "AT90S2313" #endif "\n"; const char quit_serial[] __attribute__((progmem)) = "\nThank you for using serial mode. Good-bye!\n"; const char hello_serial[] __attribute__((progmem)) = "\nWelcome at serial control, type +/- to adjust, or 0/1 to turn on/off\n" "the LED, q to quit serial mode, r to demonstrate a watchdog reset\n"; const char watchdog[] __attribute__((progmem)) = "\nOoops, the watchdog bit me!"; const char snooze[] __attribute__((progmem)) = "\nzzzz... zzz..."; const char ee_done[] __attribute__((progmem)) = "[EEPROM updated] "; /* * Contrary to C programs in an operating-system hosted environment * like Unix, the main() function in microcontroller C is not being * passed any arguments, nor does it pass any return value back. * Actually, it is not even expected to return; should it return * at all, the operating environment adds an endless loop behind it. * (Even more, avr-gcc doesn't even treat it as a function, in order * to avoid the otherwise useless stack frame required for main().) */ void main(void) { /* * Our modus of operation. MODE_UPDOWN means we watch out for * either PC2 or PC3 being low, and increase or decrease the * PWM value accordingly. This is the default. * MODE_ADC means the PWM value follows the value of ADC0 (PC0). * This is enabled by applying low level to PC1. * MODE_SERIAL means we get commands via the UART. This is * enabled by sending a valid V.24 character at 9600 Bd to the * UART. */ enum { MODE_UPDOWN, #if HAVE_ADC MODE_ADC, #endif MODE_SERIAL } mode = MODE_UPDOWN; ioinit(); /* the '2313 doesn't have MCUSR despite of having a watchdog */ #ifdef MCUSR /* cannot use bit_is_set() here since MCUSR > 32 */ if ((MCUSR & (_BV(WDRF) | _BV(PORF))) == _BV(WDRF)) printstr_p(watchdog); MCUSR = 0; #endif printstr_p(greeting); for (;;) { wdt_reset(); if (intflags.tmr_int) { /* * Our periodic 10 ms interrupt happened. See what we can * do about it. */ intflags.tmr_int = 0; /* * toggle PB2, just to show the internal clock; should * yield ~ 48 Hz on PB2 */ PORTB ^= _BV(PB2); switch (mode) { case MODE_SERIAL: break; case MODE_UPDOWN: /* * NB: watch out to use PINx for reading, as opposed * to using PORTx which would be the mirror of the * _output_ latch register (resp. pullup configuration * bit for input pins)! * * Using outw() automatically choses the proper * sequence when writing to two-byte registers, make * sure to always specify the lower register number. */ if (bit_is_clear(PIND, TRIGGER_DOWN)) set_pwm(pwm - 10); else if (bit_is_clear(PIND, TRIGGER_UP)) set_pwm(pwm + 10); #if HAVE_ADC else if (bit_is_clear(PIND, TRIGGER_ADC)) mode = MODE_ADC; #endif break; #if HAVE_ADC case MODE_ADC: if (bit_is_set(PIND, TRIGGER_ADC)) mode = MODE_UPDOWN; else ADCSR |= _BV(ADSC); /* start one conversion */ break; #endif } if (pwm_backup_tmr && --pwm_backup_tmr == 0) { /* * The EEPROM backup timer expired. Save the current * PWM value in EEPROM. Note that this function might * block for a few milliseconds (after writing the * first byte). */ eeprom_write_word(&ee_pwm, pwm); printstr_p(ee_done); } } #if HAVE_ADC if (intflags.adc_int) { intflags.adc_int = 0; set_pwm(adcval); } #endif if (intflags.rx_int) { intflags.rx_int = 0; if (rxbuff == 'q') { printstr_p(quit_serial); mode = MODE_UPDOWN; } else { if (mode != MODE_SERIAL) { printstr_p(hello_serial); mode = MODE_SERIAL; } switch (rxbuff) { case '+': set_pwm(pwm + 10); break; case '-': set_pwm(pwm - 10); break; case '0': set_pwm(0); break; case '1': set_pwm(1023); break; case 'r': printstr_p(snooze); for (;;) ; } } } } }