/************************************************* Name: aid_lib.atmega16.c Author: Mel Wilson, Stefan Wiechula, Ben Bogart Created: 2004-06-02 Purpose: AID utility functions. $Id$ Copyright: This program is copyright 2003, 2004 Stefan Wiechula, Ben Bogart, Yehoshua Bendah, Tim Moody, Ian Garmaise, Mel Wilson. License: This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA The full text of the GPL is also online: http://www.gnu.org/copyleft/gpl **************************************************/ /**************************************************************** * The ATMega16 is described in Atmel document doc2466.pdf . * Cited page numbers refer to this document. ****************************************************************/ #include #include const char aid_lib_c_rev[] = "$Revision: 1.5 $ aid_lib.atmega16.c"; /**************************************************************** * Some default values (as set on startup, e.g.) ****************************************************************/ #define DEFAULT_PWM_FREQ 20 /**************************************************************** * Conventional return values. ****************************************************************/ #define SUCCESS 0 #define FAILURE 1 /**************************************************************** * Hardware addressing stuff... pin addresses, etc. ****************************************************************/ /* AID bus address pins: B4..7 */ #define AID_BUS_ADDR PORTB #define AID_BUS_ADDR_DDR DDRB #define AID_BUS_ADDR0 PORTB4 #define AID_BUS_ADDR1 PORTB5 #define AID_BUS_ADDR2 PORTB6 #define AID_BUS_ADDR3 PORTB7 #define AID_BUS_ADDR0_DDR DDB4 #define AID_BUS_ADDR1_DDR DDB5 #define AID_BUS_ADDR2_DDR DDB6 #define AID_BUS_ADDR3_DDR DDB7 /* AID bus control pins: B0, B1 for /R and /W */ #define AID_BUS_RW PORTB #define AID_BUS_RW_DDR DDRB #define AID_BUS_R PORTB0 #define AID_BUS_W PORTB1 #define AID_BUS_R_DDR DDB0 #define AID_BUS_W_DDR DDB1 /* AID bus data pins: C0..7 * * So far, these pins are only addressed en masse as PORTC, not * individually. */ #define AID_BUS_DATA PORTC #define AID_BUS_DATA_DDR DDRC /* Analog to digital converter pins: A0..7 */ #define AID_ADC PORTA #define AID_ADC_DDR DDRA /* PWM pins: OC0(B3), OC1A(D5), OC1B(D4), OC2(D7) */ /**************************************************************** * Delay and timing stuff.... * * Functions available: * DelayUs(x) Delay specified number of microseconds * DelayMs(x) Delay specified number of milliseconds * * Warning: These both accept unsigned integer arguments but may * overflow the timer at large values of the argument. Needs fixing * someday. * DelayUs has a useable range of 13107 microseconds * DelayMs has a useable range of 104 milioseconds ****************************************************************/ #define XTAL 16000000L /* Crystal frequency in Hz */ #define Fosc XTAL /* Instruction clock frequency in Hz */ void DelayUs (unsigned int cnt) { #if (Fosc != 16000000L) Error "This code assumes a 16 MHz clock." #endif if (cnt < 1) return; /* the above compiles to "sbiw R,0 breq L which takes 4 clocks */ asm volatile ( "\n\t" "rjmp L_dl2%=" "\n" /* skip 6 nops for this instruction and the ones above */ "L_dl1%=:" "\n\t" /* almost one microsecond worth of NOPs follows .. */ "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n" "L_dl2%=:" "\n\t" /* enter here to skip 6 of the nops */ "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "sbiw %0,1" "\n\t" /* 2-clock instruction */ "brne L_dl1%=" "\n\t" /* usually 2-clock instruction */ ::"w" (cnt)); } /* DelayUs */ void DelayMs (unsigned int ms) { if (ms < 1) return; { uint16_t cnt; asm volatile ( "\n" "L_dl1%=:" "\n\t" "ldi %A0,%2" "\n\t" /* set up inner loop count */ "ldi %B0,%3" "\n" "L_dl2%=:" "\n\t" /* start a two-instruction, 4-clock inner loop */ "sbiw %A0, 1" "\n\t" "brne L_dl2%=" "\n\t" "sbiw %1,1" "\n\t" /* count one millisecond */ "brne L_dl1%=" "\n\t" : "=&w" (cnt) : "w" (ms), "M" ((unsigned int)(Fosc/4000) & 0xFF), "M" ((unsigned int)(Fosc/4000) >> 8) ); } } /* DelayMs */ /**************************************************************** * getch * * Get one character from the serial link. ****************************************************************/ unsigned char getch (void) { while (! (UCSRA & (1 << RXC))) ; return UDR; } /* getch */ /**************************************************************** * putch * * Put one character onto the serial link. ****************************************************************/ void putch (char c) { while (! (UCSRA & (1 << UDRE))) ; UDR = c; } /* putch */ /**************************************************************** * set_aid_bus_address * * A helper function to aid_bus_read() and aid_bus_write(). * * The relevant pins are configured as outputs by * aid_mainboard_init(). * * Returns SUCCESS or FAILURE based on the plausability of the given * address. ****************************************************************/ int set_aid_bus_address (unsigned char address) { if (address > 15) return FAILURE; AID_BUS_ADDR = (AID_BUS_ADDR & 0x0F) | (address << AID_BUS_ADDR0); return SUCCESS; } /* set_aid_bus_address */ /**************************************************************** * set_aid_bus_data * * A helper function to aid_bus_write(). * ****************************************************************/ void set_aid_bus_data (unsigned char data) { AID_BUS_DATA_DDR = 0xFF; /* Make port C an output. */ AID_BUS_DATA = data; } /* set_aid_bus_data */ /**************************************************************** * get_aid_bus_data * * A helper function to aid_bus_read(). * ****************************************************************/ unsigned char get_aid_bus_data (void) { AID_BUS_DATA_DDR = 0; /* Make port C an input. */ return AID_BUS_DATA; } /* get_aid_bus_data */ /**************************************************************** * char aid_bus_read * * Error checking idea: we could define this function as type * int and use numbers outside the range of an unsigned char to * indicate error conditions (e.g. invalid address). The onus * would be on the caller to check if the return value indicates * an error conditions and if not, then cast to extract the * intended 8 bits. ****************************************************************/ unsigned char aid_bus_read (unsigned char address) { unsigned char data = 0; /* default value in case read fails */ if (set_aid_bus_address(address) == SUCCESS) { AID_BUS_RW &= ~(1 << AID_BUS_R); /* assert the /read signal */ DelayUs(1); /* Wait for the daughter card to respond before we grab the data. */ data = get_aid_bus_data(); AID_BUS_RW |= (1 << AID_BUS_R); /* unassert the /read signal */ } return data; } /* aid_bus_read */ /**************************************************************** * aid_bus_write * * Returns SUCCESS or FAILURE based on the plausibility of the * given address. ****************************************************************/ int aid_bus_write (unsigned char address, unsigned char data) { if (set_aid_bus_address(address) == SUCCESS) { set_aid_bus_data(data); AID_BUS_RW &= ~(1 << AID_BUS_W); /* assert the /write signal */ DelayUs(1); /* Give the daughter card time to grab the data. */ AID_BUS_RW |= (1 << AID_BUS_W); /* unassert the /write signal */ return SUCCESS; } return FAILURE; } /* aid_bus_write */ /**************************************************************** * aid_pwm_init * * Initialisation of the PWM outputs. ****************************************************************/ void aid_pwm_init(void) { /* Timer/Counter 0 .. */ /* Fast PWM mode, Fosc/1024 */ /* frequency is Fosc / (Prescale * 256), Prescale in 1,8,64,256,1024 */ /* Duty cycle set by TCNT0 */ TCCR0 = (1 << WGM00) | (1 << WGM01) /* fast PWM mode */ | (2 << COM00) /* clear OC0 on compare match */ | (1 << CS00); /* frequency prescale = 1 */ /* page 83 */ DDRB |= (1 << DDB3); /* make OC0 pin an output */ OCR0 = 0; /* set duty cycle to 0 */ /* Timer/Counter 1 .. page 96 */ /* ICR1 can define TOP value, therefore PWM frequency */ /* TCCR1A, see page 109 */ TCCR1A = (2 << COM1A0) /* fast PWM mode channel A */ | (2 << COM1B0) /* fast PWM mode channel B */ | (2 << WGM10); /* fast PWM mode freq. from ICR1 */ /* TCCR1B, see page 112 */ TCCR1B = (3 << WGM12) /* fast PWM mode freq. from ICR1 */ | (5 << CS10); /* clock prescaled by 256 */ DDRD |= (1 << DDD5) | (1 << DDD4); /* make A and B pins output */ OCR1AH = 0; OCR1AL = 0; OCR1BH = 0; OCR1BL = 0; /* Timer/Counter 2 .. */ /* Seems just like Timer/Counter 0 */ /* Fast PWM mode, Fosc/256 */ /* frequency is Fosc / (Prescale * 256), Prescale in 1,8,64,256,1024 */ /* Duty cycle set by TCNT2 */ TCCR2 = (1 << WGM20) | (1 << WGM21) /* fast PWM mode */ | (2 << COM20) /* clear OC0 on compare match */ | (1 << CS20); /* frequency divisor = 1 */ /* page 129 */ DDRD |= (1 << DDD7); /* make OC2 pin an output */ OCR2 = 0; /* set duty cycle to 0 */ } /* aid_pwm_init */ /**************************************************************** * set_pwm_duty_cycle * * Set the PWM duty cycle by writing to the CCPR1L register and * CCP1CON<5:4> bits. * * PWM duty cycle = (CCPR1L:CCP1CON<5:4>) * TOSC * (TMR2 prescale value) * * If the PWM duty cycle value is longer than the PWM period, the CCP1 * pin will not be cleared. The two LSB of each duty cycle are in a * different register. These are set to zero in pwm_init and not * touched here. ****************************************************************/ int set_pwm_duty_cycle (unsigned char addr, unsigned int duty_cycle) { if (addr > 3) return FAILURE; /* illegal PWM address */ else if (addr == 0) /* fixed frequency PWM */ OCR0 = (256 * duty_cycle) / 100; else if (addr == 3) /* fixed frequency PWM */ OCR2 = (256 * duty_cycle) / 100; else { unsigned int p; /* compute duty cycle as a percent of the PWM frequency count */ p = ICR1L; p = p | (ICR1H << 8); p = (p * (long)duty_cycle) / 100; if (addr == 1) { OCR1AH = p >> 8; OCR1AL = (unsigned char)p; } else if (addr == 2) { OCR1BH = p >> 8; OCR1BL = (unsigned char)p; } } return SUCCESS; } /* set_pwm_duty_cycle */ /**************************************************************** * set_pwm_frequency * * The argument is used to set the Timer 2 period. * * TODO: What units should freq have? Hz? kHz? * * Both PWM pins use the same timer and therefore have the same frequency. * Set the PWM period by writing to the PR2 register. * * PWM period = [(PR2) + 1] * 4 * TOSC * (TMR2 prescale value) * 1/f = [(PR2) + 1] * 4 * FOSC/4 * (1) * 1/f = [(PR2) + 1] * FOSC * (1) * 1/f = [(PR2) + 1] * FOSC * [PR2 + 1] = 1/(f * FOSC) * PR2 = 1/(f * FOSC) - 1 * * The PR2 register is 8 bits wide. ****************************************************************/ void set_pwm_frequency (unsigned int freq) { /* Temporary, this whole if statment should be replaced with a * calculation of PR2 from freq * * Here's what we'd really like to do: PR2 = 1/(freq*FOSC) - 1; */ /* probably stop PWM First, so as not to skip a compare match */ ICR1H = freq >> 8; ICR1L = (unsigned char)freq; } /* set_pwm_frequency */ /**************************************************************** * set_pwm * * Set the frequency and duty cycles of both PWM outputs at once. ****************************************************************/ void set_pwm(unsigned int freq, unsigned int dc1, unsigned int dc2) { set_pwm_frequency(freq); set_pwm_duty_cycle(1, dc1); set_pwm_duty_cycle(2, dc2); } /* set_pwm */ /**************************************************************** * get_ad * * Handler function for the "aA" command. Initiates an analog to * digital conversion, wait for it to complete, and returns the result * over the serial port. ****************************************************************/ unsigned int get_ad (unsigned char address) { unsigned int result; ADCSRA = (1 << ADEN) /* switch on the ADC converter */ | (1 << ADIF) /* ensure interrupt flag is clear */ | (0 << ADIE) /* ensure interrupt is disabled */ | (7 << ADPS0) /* prescale I/O Clock by 128 for ADC clock */ ; ADMUX = (0 << ADLAR) /* right-adjust ADC result */ | (1 << REFS0) /* use AVcc as reference */ | (address & 7) /* ADC channel address */ ; /* 3. Wait the required acquisition time. */ /*DelayMs (10);*/ /* 4. Start conversion */ ADCSRA |= (1 << ADSC); /* 5. Wait for A/D conversion to complete, */ while (ADCSRA & (1 << ADSC)) ; /* wait */ /* 6. Read A/D result register pair . */ result = ADCL; result |= ADCH << 8; ADCSRA |= (1 << ADIF); /* Turn off the interrupt flag. */ ADCSRA &= ~(1 << ADEN); /* turn off the ADC to conserve batteries */ return result; } /* get_ad */ /**************************************************************** * aid_serial_init * ****************************************************************/ void aid_serial_init(void) { UCSRA &= ~(1 << U2X); /* disable "extra speed" switch */ UBRRH = ((long)Fosc / (16 * 19200L) - 1) >> 8; /* 19200 baud */ UBRRL = ((long)Fosc / (16 * 19200L) - 1) & 0xFF; /* 19200 baud */ UCSRC = (1 << URSEL) | (0 << USBS) | (3 << UCSZ0); /* framing 8N1 */ UCSRB = (1 << RXEN) | (1 << TXEN); /* enable receive and send */ } /* aid_serial_init */ /**************************************************************** * aid_bus_init * ****************************************************************/ void aid_bus_init(void) { /* Initialise the /R and /W pins to logic high... */ AID_BUS_RW |= (1<