//----------------------------------------------------------------------------- // F330DC_FeaturesDemoSDDC.c //----------------------------------------------------------------------------- // // Program Description: // // This program demonstrates the features available on the ToolStick // F330 Daughter Card. // // Based on the setting of a level-sensitive interrupt pin, the firmware // will either blink the green LED or PWM the LED. The blinking speed // or the PWM duty cycle is set based on the value of the potentiometer. // Voltage across the potentiometer is measured using the on-chip 10-bit ADC. // // The program also outputs the status of the LED to the UART at a baud rate // defined by . // // How To Test: // 1) Connect the ToolStickF330DC to the PC and download the code // 2) Turn the potentiometer and see that the blink rate increases or decreases // 3) Connect to the ToolStickF330DC using ToolStickTerminal and see that // the UART output changes as the potentiometer is turned // 4) In ToolStickTerminal, toggle GPIO0/RTS to force the firmware to // switch modes. // 5) Turn the potentiometer and see that the brightness of the LED changes // and check that the output of the UART also changes. // // // Target: ToolStickF330DC // Tool chain: SDCC 2.8.0 // SiLabs IDE 3.42 // // Release 101 // // Changelog // 2009-02-03 Initial Revision // (based on silabs toolstick sample F330DC_FeaturesDemo.c) // 2009-02-04 added putchar() for stdout // fix timer3 TMR3RL overflow (minblinkrate=4hz) // fix UART0_Init compiler warnings //----------------------------------------------------------------------------- // Includes //----------------------------------------------------------------------------- #include // SFR declarations #include #include //----------------------------------------------------------------------------- // SFR and Bit Definitions //----------------------------------------------------------------------------- #define LED P1_3 // LED='0' means led is turned on //----------------------------------------------------------------------------- // Global Constants //----------------------------------------------------------------------------- #define SYSCLK 3000000L // SYSCLK frequency in Hz // 24.5Mhz/8 = ~3000000? #define BAUDRATE 115200 // Baud rate of UART in bps #define PRINT_RATE 20 // Times per second to update on UART #define OVERFLOWS_NEEDED (SYSCLK / 65536 / PRINT_RATE) #define ADC_CLOCK 100000 // SAR clock #define ADC_SAMPLE_RATE 100 // Timer 2 overflow rate in Hz #define ADC_COUNTS 1024 // Number of ADC counts #define MAX_BLINK_RATE 40 // Max blink rate in Hz for Timer mode #define MIN_BLINK_RATE 4 // Min blink rate in Hz for Timer mode // lowest possible blinkrate is 4hz, due to the limit of the 16bit timer // sysclk/12/65536 = 250000/65536 =3.8Hz // #define BLINK_DIFF (MAX_BLINK_RATE - MIN_BLINK_RATE) // For easy calculations // Firmware states #define TIMER_MODE 0 // Uses the Timer to blink the LED #define PWM_MODE 1 // Use a PCA channel to PWM the LED //----------------------------------------------------------------------------- // Function Prototypes //----------------------------------------------------------------------------- char _sdcc_external_startup( void ); void Oscillator_Init( void ); void Port_Init( void ); void Timer0_Init( void ); void Timer2_Init( int counts ); void Timer3_Init( void ); void ADC0_Init( void ); void PCA0_Init( void ); void UART0_Init( void ); void INT0_Init( void ); // ISRs defined: INT0_ISR, TIMER0_ISR, TIMER3_ISR, ADC0_ISR. //----------------------------------------------------------------------------- // Global Variables //----------------------------------------------------------------------------- bit Blink_State = TIMER_MODE; // Starts with blinking the LED int PWM_Duty_Cycle = 0; // Percentage from 0 to 100 int Blink_Rate = 0; // From MIN_BLINK_RATE to MAX_BLINK_RATE bit update = 0; // Forces a UART update long Num_LED_Flashes = 0; // Simply counts the number of times // the LED blinks long Num_ADC_conversions = 0; long Num_chars_send = 0; #ifdef DEBUG long currentTMR3RL = 0; int rbTMR3RL = 0; int adcResult = 0; #endif bit sysInit = 0; //----------------------------------------------------------------------------- // MAIN Routine //----------------------------------------------------------------------------- void main(void) { //PCA0MD &= ~0x40; // watchdog disable // is done via _sdcc_external_startup Oscillator_Init(); // Initialize system clock to 24.5 Mhz Port_Init(); // Initialize crossbar and GPIO Timer0_Init(); // Initialize Timer0 for UART update Timer2_Init(SYSCLK / 12 / ADC_SAMPLE_RATE); // Init Timer2 for ADC Timer3_Init(); // Init timer used to blink LED PCA0_Init(); // Init PCA Module 0 to blink LED ADC0_Init(); // Enable ADC0 INT0_Init(); // Enable /INT0 UART0_Init(); // Enable UART using global settings sysInit = 1; // allow putchar to work EA = 1; // Enable global interrupts while (1) {} } //----------------------------------------------------------------------------- // SDCC special - stop the Watchdog timer prior to initialization // // !!! to work properly this requires prototype define (see above) //----------------------------------------------------------------------------- char _sdcc_external_startup(void) { // disable watchdog on F330 PCA0MD &= ~0x40; // WDTE = 0 (clear watchdog timer enable) return 0; // do not skip variable initialization } //----------------------------------------------------------------------------- // Interrupt Service Routines //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // INT0_ISR //----------------------------------------------------------------------------- // // INT0_ISR is triggered upon a level transition on the /INT0 pin. // If /INT0 is logic high, the firmware switches to a state that blinks the LED // If /INT0 is logic low, the fimware applies PWM wave to the LED to control // the brightness. // //----------------------------------------------------------------------------- void INT0_ISR (void) interrupt 0 { if (IT01CF & 0x08) // Currently active-high { IT01CF &= ~0x08; // Switch to active-low Blink_State = TIMER_MODE; // State is now Timer mode update = 1; // Switch to Timer Mode CR = 0; // Stop PCA Counter XBR1 &= ~0x01; // Remove CEX0 from Crossbar TMR3CN |= 0x04; // Start Timer3 } else { IT01CF |= 0x08; // Switch to active-high Blink_State = PWM_MODE; // State is now PWM mode update = 1; // Switch To PWM Mode TMR3CN &= ~0x04; // Stop Timer3 XBR1 |= 0x01; // Put CEX0 on Crossbar (P1.3) CR = 1; // Start PCA Counter } } //----------------------------------------------------------------------------- // TIMER0_ISR //----------------------------------------------------------------------------- // // This ISR is triggered upon a Timer0 overflow. // It is used to update the UART with the status of the LED. // //----------------------------------------------------------------------------- void Timer0_ISR (void) interrupt 1 { static int overflows = 0; // Timer0 is used to schedule the number of times per second the status is // printed to the UART. Since Timer0 and Timer1 share a SYCLK prescaler, it // is easier to use the prescaler only for Timer1 (UART baud rate) and run // Timer0 from SYSCLK. Running Timer0 from SYSCLK requires keeping track // of the number of Timer0 overflows to schedule a reasonable UART update // rate. Updating on every Timer0 overflow would print too fast. // The benefit is that there is more flexibility in scheduling the update // rate as it is no longer any dependent on the fixed UART prescaler. overflows++; if (overflows == OVERFLOWS_NEEDED) // Time to print { if ((Blink_State == TIMER_MODE) & update) // Timer mode { #ifdef DEBUG printf("LED Blink Rate: %d Hz", Blink_Rate); printf(" tim3RL=%ld", currentTMR3RL); printf(" %ld:", (SYSCLK/12/Blink_Rate) ); printf(" %u %d %d\n", rbTMR3RL, adcResult ); #else putchar('\f'); // Clear screen printf("LED Blink Rate: %d Hz\n", Blink_Rate); #endif update = 0; } else if ((Blink_State == PWM_MODE) & update) // PWM mode { putchar('\f'); // Clear screen printf("LED PWM Duty Cycle: %d %%\n",PWM_Duty_Cycle); update = 0; } overflows = 0; // Reset the count } } //----------------------------------------------------------------------------- // ADC0_ISR //----------------------------------------------------------------------------- // // ADC0_ISR is triggerered upon the completion of an ADC conversion. The ISR // calculates the Blink_Rate or PWM_Duty_Cycle based on the voltage // across the potentiometer. // // Blink mode: // A full-scale reading will configure the Timer to blink the LED at // // A zero reading will configure the Timer the LED to blink at // // PWM mode: // A full scale value will configure the PCA to use a duty cycle of 100% // A zero reading will configure the PCA to use a duty cycle of 0% // //----------------------------------------------------------------------------- void ADC0_ISR (void) interrupt 10 { unsigned int ADC_result; unsigned long reload_speed, percentage; static int PWM_Duty_Cycle_Old = 0; static int Blink_Rate_Old = 0; AD0INT = 0; // Clear conversion complete flag ADC_result = ADC0; if (Blink_State == TIMER_MODE) { if (ADC_result == 0x03FF) // Assume max reading indicates VDD { // and not (VDD - 1 LSB) reload_speed = MAX_BLINK_RATE; } else { reload_speed = (long) (ADC_result * BLINK_DIFF); reload_speed = reload_speed / ADC_COUNTS; reload_speed += MIN_BLINK_RATE; } Blink_Rate = reload_speed; // Set global variable if (Blink_Rate != Blink_Rate_Old) { update = 1; } Blink_Rate_Old = Blink_Rate; TMR3RL = - ((unsigned int) (SYSCLK / 12 / reload_speed)); #ifdef DEBUG if ( update ) { currentTMR3RL = SYSCLK/12/reload_speed; rbTMR3RL = TMR3RL; adcResult = ADC_result; } #endif } else { if (ADC_result == 0x03FF) // Assume max reading indicates VDD { // and not (VDD - 1 LSB) percentage = 255; PWM_Duty_Cycle = 100; } else { percentage = (long) ADC_result * (long) 256; percentage = (long) percentage / (long) ADC_COUNTS; PWM_Duty_Cycle = (int) ( ((long)ADC_result * (long)100) / (long)ADC_COUNTS ); } if (PWM_Duty_Cycle != PWM_Duty_Cycle_Old) { update = 1; } PWM_Duty_Cycle_Old = PWM_Duty_Cycle; // Set new PCA0 Module0 PWM duty cycle PCA0CPH0 = (char) percentage; } //LED = !LED; Num_ADC_conversions++; } //----------------------------------------------------------------------------- // TIMER3_ISR //----------------------------------------------------------------------------- // // This ISR is triggered upon a Timer3 overflow. The ISR simply toggles // the state of the LED and keeps track of the number of times the LED blinks. // //----------------------------------------------------------------------------- void Timer3_ISR (void) interrupt 14 { TMR3CN &= ~0x80; // Clear Timer3 Flags LED = !LED; Num_LED_Flashes++; } //----------------------------------------------------------------------------- // Initialization Subroutines //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // OSCILLATOR_Init //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // This function initializes the system clock to use the internal 24.5/8 MHz // oscillator as its clock source. Also enables missing clock detector reset. // //----------------------------------------------------------------------------- void Oscillator_Init (void) { OSCICN = 0x80; // Configure internal oscillator for // its lowest frequency RSTSRC = 0x04; // Enable missing clock detector } //----------------------------------------------------------------------------- // PORT_Init //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // This function configures the crossbar and GPIO ports. // // // P0.4 digital push-pull UART TX // P0.5 digital open-drain UART RX // P0.6 digital open-drain /INT0 // P1.3 digital push-pull LED (and CEX0 depending on Blink_State) // P1.6 analog Potentiometer (ADC input) // //----------------------------------------------------------------------------- void Port_Init (void) { P1MDIN = 0xBF; // P1.6 is analog, rest of P1 digital P0MDOUT = 0x10; // P0.4 is push-pull P1MDOUT = 0x08; // P1.3 is push-pull P0SKIP = 0xCF; // Skip all of P0 except for UART0 pins P1SKIP = 0x47; // Skip P1.6 and push CEX0 to P1.3 P0 |= 0x60; // Set port latch for pins RX and /INT0 // to configure as inputs XBR0 = 0x01; // Enable UART XBR1 = 0x40; // Enable Crossbar; pull-ups enabled } //----------------------------------------------------------------------------- // TIMER0_Init //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // Initializes the Timer in 16-bit reload mode using SYSCLK as the time base. // //----------------------------------------------------------------------------- void Timer0_Init (void) { // Initialize Timer0; set global variable for number overflows to reach // interrupt rate set by TMOD |= 0x01; // Mode 1 - 16-bit counter CKCON |= 0x04; // Use SYSCLK as timebase TL0 = 0; // Clear Timer0 low byte TH0 = 0; // Clear Timer0 high byte IE |= 0x02; // Enable Timer0 Interrupts TR0 = 1; // Enable Timer 0 } //----------------------------------------------------------------------------- // TIMER2_Init //----------------------------------------------------------------------------- // // Return Value : None // Parameters : // 1) int counts - number of milliseconds of delay // range is postive range of integer: 0 to 32767 // // Configure Timer2 to 16-bit auto-reload and generate an interrupt at // interval specified by using SYSCLK/12 as its time base. // //----------------------------------------------------------------------------- void Timer2_Init (int counts) { TMR2CN = 0x00; // Stop Timer2; Clear TF2; // use SYSCLK/12 as timebase CKCON &= ~0x30; // Timer2 clocked based on T2XCLK; TMR2RL = -counts; // Init reload values TMR2 = 0xffff; // Set to reload immediately ET2 = 0; // Disable Timer2 interrupts TR2 = 1; // Start Timer2 } //----------------------------------------------------------------------------- // TIMER3_Init //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // Configure Timer3 to 16-bit auto-reload and generate an interrupt at // interval specified by using SYSCLK/12 as its time base. // // Initially sets Timer3 to overflow at the maximum blink rate. The Timer3 ISR // is used to toggle the LED. // //----------------------------------------------------------------------------- void Timer3_Init (void) { TMR3CN = 0x00; // Stop Timer3; Clear flags; // use SYSCLK/12 as timebase CKCON &= ~0xC0; // Timer3 clocked based on T3XCLK; TMR3RL = -((unsigned int)(SYSCLK/12/MIN_BLINK_RATE)); // Init reload values TMR3 = 0xffff; // Set to reload immediately EIE1 |= 0x80; // Enable Timer3 interrupts TMR3CN |= 0x04; // Start Timer3 } //----------------------------------------------------------------------------- // ADC0_Init //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // This function initializes the ADC to measure potentiometer connected // to P1.6 in single-ended mode. Also enables the internal voltage reference. // //----------------------------------------------------------------------------- void ADC0_Init (void) { ADC0CN = 0x02; // Disable ADC0 // Normal Track Mode // Enable conversion on Timer2 overflow AMX0P = 0x0E; // P1.6 is the positive input AMX0N = 0x11; // GND is the negative input ADC0CF = (SYSCLK/ADC_CLOCK) << 3; // ADC conversion clock <= 3MHz ADC0CF &= ~0x04; // Make ADC0 right-justified EIE1 |= 0x08; // Enable ADC0 conversion-complete // interrupts REF0CN = 0x0A; // VREF = VDD, bias generator is on. AD0EN = 1; // Enable ADC0 } //----------------------------------------------------------------------------- // UART0_Init //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // Configure the UART1 using Timer1, for and 8-N-1. // //----------------------------------------------------------------------------- void UART0_Init (void) { static long baudrate = BAUDRATE; int sysbd256 = SYSCLK/baudrate/2/256; SCON0 = 0x10; // SCON0: 8-bit variable bit rate // level of STOP bit is ignored // RX enabled // ninth bits are zeros // clear RI0 and TI0 bits if (sysbd256 < 1) { TH1 = -(SYSCLK/baudrate/2); CKCON &= ~0x0B; // T1M = 1; SCA1:0 = xx CKCON |= 0x08; } else if (sysbd256 < 4) { TH1 = -(SYSCLK/baudrate/2/4); CKCON &= ~0x0B; // T1M = 0; SCA1:0 = 01 CKCON |= 0x09; } else if (sysbd256 < 12) { TH1 = -(SYSCLK/baudrate/2/12); CKCON &= ~0x0B; // T1M = 0; SCA1:0 = 00 } else { TH1 = -(SYSCLK/baudrate/2/48); CKCON &= ~0x0B; // T1M = 0; SCA1:0 = 10 CKCON |= 0x02; } TL1 = TH1; // Init Timer1 TMOD &= ~0xf0; // TMOD: timer 1 in 8-bit autoreload TMOD |= 0x20; TR1 = 1; // START Timer1 TI0 = 1; // Indicate TX0 ready } //----------------------------------------------------------------------------- // PCA0_Init //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // Configure Module0 to 8-bit PWM. The duty cycle is initially set to 50%. The // duty cycle will actually be determined by the value of the potentiometer. // // The PCA counter isn't started until the user switches to PWM mode. // //----------------------------------------------------------------------------- void PCA0_Init (void) { // Configure PCA time base; overflow interrupt disabled PCA0CN = 0x00; // Stop counter; clear all flags PCA0MD = 0x08; // Use SYSCLK as time base PCA0CPM0 = 0x42; // Module 0 = 8-bit PWM mode // Configure initial PWM duty cycle = 50% PCA0CPH0 = 256 - (256 * 0.5); } //----------------------------------------------------------------------------- // INT0_Init //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // Configure /INT0 (P0.6) to be a level-sensitive interrupt pin. // Starts active-low, but the INT0 ISR will switch this back and forth. // //----------------------------------------------------------------------------- void INT0_Init (void) { IT01CF = 0x06; // Configure /INT0 to P0.6 // Active High IT0 = 0; // /INT0 is level sensitive EX0 = 1; // Enable /INT0 interrupts } //----------------------------------------------------------------------------- // Support Functions //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // putchar //----------------------------------------------------------------------------- // // Return Value : None // Parameters : char (8bit) // // Prints char to the UART. // //----------------------------------------------------------------------------- void putchar ( char c ) { if ( sysInit == 0 ) return; //LED = !LED; Num_chars_send++; while ( !TI0 ); TI0 = 0; SBUF0 = c; } //----------------------------------------------------------------------------- // End Of File //-----------------------------------------------------------------------------