Embedded-Software-Projects

Log | Files | Refs | README | LICENSE

commit 6bbb373cccca2e72cfb8642ccf9f0e7f986b6b19
parent edd2936f6a34f1adbc50e4ccda423dfc99e87b85
Author: William Lindholm <william_lindholm@outlook.com>
Date:   Mon, 14 Oct 2024 22:00:22 +0100

Added split mode, show voltage reading and cylon

Diffstat:
MProject1/.vs/Project1/v14/.atsuo | 0
MProject1/Project1/main.c | 285+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
2 files changed, 208 insertions(+), 77 deletions(-)

diff --git a/Project1/.vs/Project1/v14/.atsuo b/Project1/.vs/Project1/v14/.atsuo Binary files differ. diff --git a/Project1/Project1/main.c b/Project1/Project1/main.c @@ -10,19 +10,33 @@ #include <avr/cpufunc.h> #include <stdbool.h> -#define THREE_VOLT 153 // 3*255 /5 = 153 -#define EIGHT_S 2441 // 20Mhz => 0.05 uS, 0.05 * 1024 = 51.2 uS, 0.125 S = 125 000 uS, 125 000 / 51.2 ~2441 counts -#define HALF_S 9766 // 20Mhz => 0.05 uS, 0.05 * 1024 = 51.2 uS, 0.5 S = 500 000 uS, 500 000 / 51.2 ~9766 counts +/************************************************************************/ +/* Precalculated Time & Voltage values */ +/************************************************************************/ +#define THREE_VOLT 153 // 3*255 /5 = 153 +#define EIGHT_S 2441 // 20Mhz => 0.05 uS, 0.05 * 1024 = 51.2 uS, 0.125 S = 125 000 uS, 125 000 / 51.2 ~2441 counts +#define HALF_S 9766 // 20Mhz => 0.05 uS, 0.05 * 1024 = 51.2 uS, 0.5 S = 500 000 uS, 500 000 / 51.2 ~9766 counts + +#define SPLIT_LED 4 // When in split mode, split on led 4 + + +/************************************************************************/ +/* Global program modes */ +/************************************************************************/ enum MODES { - VOLTAGE, - CYLON + VOLTAGE, // Show voltage reading (full display; 10 LED:s) + CYLON, // Show Cylon animation (full display; 10 LED:s) + SPLIT // Show Cylon animation (half display, 5 LED:s) & voltage reading (half display, 5 LED:s) }; -enum MODES DISPLAY_MODE = CYLON; +enum MODES DISPLAY_MODE = SPLIT; -/* Use a struct to make the association between PORTs and bits connected to the LED array more explicit */ + +/************************************************************************/ +/* LED PORT definitions */ +/************************************************************************/ struct LED_BITS { PORT_t *LED_PORT; @@ -33,83 +47,115 @@ struct LED_BITS LED_Array[10] = { {&PORTC, PIN5_bm}, {&PORTC, PIN4_bm}, {&PORTA, PIN0_bm}, {&PORTF, PIN5_bm}, {&PORTC, PIN6_bm}, {&PORTB, PIN2_bm}, {&PORTF, PIN4_bm}, {&PORTA, PIN1_bm}, {&PORTA, PIN2_bm}, {&PORTA, PIN3_bm} }; +/************************************************************************/ +/* Function declarations */ +/************************************************************************/ void clock_init (void); void set_leds_output(void); void set_clr_leds(bool set); +void set_clr_leds_range(bool set, uint8_t from, uint8_t to); void configure_TCA0(void); void configure_ADC0(void); void configure_button_input(void); -void display_voltage(uint8_t); +void display_voltage(void); void configure_TCB0(void); +void configure_RTC(void); + +/************************************************************************/ +/* Main */ +/************************************************************************/ int main(void) { clock_init(); set_leds_output(); - set_clr_leds(0); // ensure all leds are turned off by default + set_clr_leds(0); // ensure all LED:s are turned off by default configure_TCA0(); configure_ADC0(); configure_button_input(); - configure_TCB0(); + configure_RTC(); - sei(); // Global interrupts enable + sei(); // Enable global interrupts for (;;){} } -/* Set the CPU frequency to 20Mhz, default 6Mhz */ + +/************************************************************************/ +/* Set the CPU frequency to 20MHz, default 6MHz */ +/************************************************************************/ void clock_init (void) { // Disable CLK_PER Prescaler ccp_write_io( (void *) &CLKCTRL.MCLKCTRLB , (0 << CLKCTRL_PEN_bp)); } -/* Set all led pins to output mode */ + +/************************************************************************/ +/* Set all led pins to output mode */ +/************************************************************************/ void set_leds_output() { - PORTC.DIR = PIN6_bm | PIN5_bm | PIN4_bm; - PORTA.DIR = PIN3_bm | PIN2_bm | PIN1_bm | PIN0_bm; - PORTB.DIR = PIN2_bm; - PORTF.DIR = PIN5_bm | PIN4_bm; + PORTC.DIR = PIN6_bm | PIN5_bm | PIN4_bm; // Set PORTC pin 4, 5 and 6 as output + PORTA.DIR = PIN3_bm | PIN2_bm | PIN1_bm | PIN0_bm; // Set PORTA pin 0, 1, 2 and 3 as output + PORTB.DIR = PIN2_bm; // Set PORTB pin 2 as output + PORTF.DIR = PIN5_bm | PIN4_bm; // Set PORTF pin 4 and 5 as output } -/* Enable and configure TCA0 */ + +/************************************************************************/ +/* Enable and configure TCA0 */ +/************************************************************************/ void configure_TCA0(void) { - TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm; //Counter overflow interrupt option + TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm; // Counter overflow interrupt option TCA0.SINGLE.CTRLB = 0x00; // Normal Mode selected - TOP value in PER register TCA0.SINGLE.EVCTRL = 0x00; // TCA0 can count events from the EVENT module - disable this option - TCA0.SINGLE.PER = 9766; // The TCA0 counter will overflow when it reaches the PER value + TCA0.SINGLE.PER = 9766; // The TCA0 counter will overflow when it reaches the PER value (count 9766) TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1024_gc | TCA_SINGLE_ENABLE_bm; // Prescale set to /1024 and enable TCA0 (start count) } -/* Enable and configure TCB0 */ -void configure_TCB0() -{ - TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; - TCB0.CCMP = 0xFFFF; - TCB0.INTCTRL = TCB_CAPT_bm; - TCB0.CTRLA |= TCB_ENABLE_bm; -} -/* Enable and configure ADC0 */ +/************************************************************************/ +/* Enable and configure ADC0 */ +/************************************************************************/ void configure_ADC0() { - ADC0.CTRLA = ADC_RESSEL_8BIT_gc | ADC_FREERUN_bm; // Set 8 bit resolution and free run mode - ADC0.CTRLC = VREF_AC0REFSEL_AVDD_gc | ADC_PRESC_DIV128_gc; // Set reference voltage to 5V and set ADC prescaler to div 128 - ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; // Set input to Analog in 3 - ADC0.CTRLA |= ADC_ENABLE_bm; // Enable ADC - ADC0.COMMAND = ADC_STCONV_bm; // Start first measurement + ADC0.CTRLA = ADC_RESSEL_8BIT_gc | ADC_FREERUN_bm; // Set 8 bit resolution and free run mode + ADC0.CTRLC = VREF_AC0REFSEL_AVDD_gc | ADC_PRESC_DIV128_gc; // Set reference voltage to 5V and set ADC prescaler to div 128 + ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; // Set input to Analog in 3 + ADC0.INTCTRL = ADC_RESRDY_bm; // Enable interrupt on result ready + ADC0.CTRLA |= ADC_ENABLE_bm; // Enable ADC + ADC0.COMMAND = ADC_STCONV_bm; // Start first measurement } + +/************************************************************************/ +/* Configure PORTE bit 1 button */ +/************************************************************************/ void configure_button_input() { - PORTE.DIRCLR = (1<<1); // Set port E pin 1 to input mode - PORTE.PIN1CTRL = PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc; // Enable pull up resistor, set interrupt to both edges + PORTE.DIRCLR = (1<<1); // Set port E pin 1 to input mode + PORTE.PIN1CTRL = PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc; // Enable pull up resistor, set interrupt to both edges } -/* Set or clear all LED port bits, 1 - set, 0 - clear */ +/************************************************************************/ +/* Configure and enable Real Time Counter */ +/************************************************************************/ +void configure_RTC() +{ + // 16 / (1/1024hz) = 16384 cycles for 16 second timer + RTC.CLKSEL = RTC_CLKSEL_INT1K_gc; // Select Internal 1.024 kHz oscillator + RTC.PITCTRLA = RTC_PERIOD_CYC16384_gc | (1<<0); // Interrupt every 16384 cycles, Periodic Interrupt Timer enabled + RTC.PITINTCTRL = (1<<0); // The periodic interrupt is enabled + RTC.CTRLA = RTC_PRESCALER_DIV1_gc | (1<<0); // Select prescaler div1 and enable +} + + +/************************************************************************/ +/* Set or clear all LED:s or a range of LED:s */ +/************************************************************************/ void set_clr_leds(bool set) { uint8_t i; @@ -126,68 +172,138 @@ void set_clr_leds(bool set) { } } -/* Display a number of leds as a bar (thermometer reading) */ -void display_voltage(uint8_t num_of_leds) +void set_clr_leds_range(bool set, uint8_t from, uint8_t to) { + + for (uint8_t i = from; i <= to; i++) + { + if (set) + { + LED_Array[i].LED_PORT->OUTSET = LED_Array[i].bit_mapping; + } + else + { + LED_Array[i].LED_PORT->OUTCLR = LED_Array[i].bit_mapping; + } + } +} + + +/************************************************************************/ +/* Display voltage reading (thermometer) */ +/************************************************************************/ +void display_voltage() { - for (uint8_t i = 0; i <= num_of_leds; i++) + uint8_t bot_led; + uint8_t top_led; + + if (DISPLAY_MODE == SPLIT) + { + bot_led = SPLIT_LED + 1; // Start from SPLIT_LED + top_led = sizeof(LED_Array) / sizeof(LED_Array[0]) - 1; // Use LEDs from SPLIT_LED to the end + } + else + { + bot_led = 0; + top_led = sizeof(LED_Array) / sizeof(LED_Array[0]) - 1; + } + + uint8_t n_leds = top_led - bot_led + 1; // Number of LEDs used for voltage display + + set_clr_leds_range(0, bot_led, top_led); // Clear only the LEDs used for voltage display + + uint8_t scaled_value = (ADC0.RES * n_leds) / 255; // Scale ADC value to the number of LEDs + + for (uint8_t i = bot_led; i < bot_led + scaled_value; i++) { LED_Array[i].LED_PORT->OUTSET = LED_Array[i].bit_mapping; } } -/* button interrupt for voltage display */ + +/************************************************************************/ +/* Trigger next Cylon LED */ +/************************************************************************/ +void display_cylon() +{ + uint8_t top_led; + uint8_t bot_led = 0; + + static uint8_t i = 0; // Index of the current LED + static bool direction = 1; // direction of the cylon. 0 = left, 1 = right + + // Handle split display mode + if (DISPLAY_MODE == SPLIT) + { + top_led = SPLIT_LED; + } + else + { + top_led = sizeof(LED_Array) / sizeof(LED_Array[0]) - 1; // length of a LED array + } + + // Switch off the current LED + LED_Array[i].LED_PORT->OUTCLR = LED_Array[i].bit_mapping; + + // Move to the next LED + i = direction ? i+1 : i-1; + + // Change direction if outside bounds + if (i >= top_led || i <= bot_led) + { + direction = !direction; + } + + // Switch on the new LED + LED_Array[i].LED_PORT->OUTSET = LED_Array[i].bit_mapping; +} + + +/************************************************************************/ +/* Button interrupt for voltage display */ +/************************************************************************/ ISR(PORTE_PORT_vect) { - if ((PORTE.IN & (1<<1)) == 0) // Rising edge + if ((PORTE.IN & PIN1_bm) == 0) // Falling edge { DISPLAY_MODE = VOLTAGE; } - else // Falling edge + else // Rising edge { DISPLAY_MODE = CYLON; - set_clr_leds(0); + set_clr_leds(0); // Clear voltage reading from display + display_cylon(); // restart cylon (prevent delay) } - PORTE.INTFLAGS = (1<<1); + PORTE.INTFLAGS = PIN1_bm; // Clear interrupt flag } -/* Timer interrupt for cylon animation */ + +/************************************************************************/ +/* Timer Counter A ISR (trigger cylon animation) */ +/************************************************************************/ ISR(TCA0_OVF_vect) { - TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // Clear interrupt flag - - // Do not show cylon animation unless in cylon mode - if (DISPLAY_MODE != CYLON) - { - return; - } - - static uint8_t i = 0; // Index of the current LED - static bool direction = 1; // direction of the cylon. 0 = left, 1 = right - - if (direction) + if (DISPLAY_MODE != VOLTAGE) { - LED_Array[i].LED_PORT->OUTCLR = LED_Array[i].bit_mapping; - LED_Array[i+1].LED_PORT->OUTSET = LED_Array[i+1].bit_mapping; - i += 1; - } - else - { - LED_Array[i].LED_PORT->OUTCLR = LED_Array[i].bit_mapping; - LED_Array[i-1].LED_PORT->OUTSET = LED_Array[i-1].bit_mapping; - i -= 1; + display_cylon(); } - if (i >= 9 || i <= 0) { - direction = !direction; // reverse direction - } + TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // Clear interrupt flag } -ISR(TCB0_INT_vect) +/************************************************************************/ +/* ADC0 result ready ISR (handle voltage reading measurements) */ +/************************************************************************/ +ISR(ADC0_RESRDY_vect) { // adjust speed of cylon based on voltage if (ADC0.RESL <= THREE_VOLT) - { + { + // reset counter to prevent PER being set to lower value than CNT during PER change + if (TCA0.SINGLE.PER == HALF_S) + { + TCA0.SINGLE.CNT = 0; + } TCA0.SINGLE.PER = EIGHT_S; } else @@ -196,12 +312,27 @@ ISR(TCB0_INT_vect) } // show voltage reading - if (DISPLAY_MODE == VOLTAGE) + if (DISPLAY_MODE != CYLON) { - set_clr_leds(0); - int scaled_value = (ADC0.RES * 9) / 255; // scale the input value 0 - 255 to a value 0 - 9 - display_voltage(scaled_value); + display_voltage(); + } + + ADC0.INTFLAGS = ADC_RESRDY_bm; // Clear interrupt flag +} + +/************************************************************************/ +/* ISR for RTC, Change to split mode every 16 seconds */ +/************************************************************************/ +ISR(RTC_PIT_vect) +{ + if (DISPLAY_MODE == SPLIT) + { + DISPLAY_MODE = CYLON; + } + else + { + DISPLAY_MODE = SPLIT; } - TCB0.INTFLAGS = TCB_CAPT_bm; // reset interrupt flag + RTC.PITINTFLAGS = (1<<0); // Clear interrupt flag } \ No newline at end of file