Skip to content
/ user_io Public

A universal embedded-c library that handles up to 256 buttons and LEDs and an unlimited amount of switches. It also lets you run code at a specific interval, using the "intervals" feature. Use it on any platform!

License

Notifications You must be signed in to change notification settings

AJ747/user_io

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

About User IO - Porting guide below

User IO is a library for reading buttons, switches, controlling LEDs and running code with fixed intervals in a non-blocking way for embedded applications written in C. Add up to 256 buttons, LEDs and "intervals" to your project and control them easily using User IO, add unlimited amount of switches. You can also choose which features you want to use, and which to exclude, see step 4 - Update macro-parameters in the porting guide.

It needs a timer to trigger its handler every x milliseconds to function properly.

Note

It's recommended to call the handler a minimum of every 10 ms. Calling it quicker than this will result in poor system performance.


Features for LED-control

Control Individually:
  • On - led_on()
  • Off
    • Off - led_off()
    • Force off - led_force_off()
  • Pulse (custom on-time) - led_pulse()
  • Blink (custom period)
    • Indefinitely - led_blink_infinite()
    • n times - led_blink_n_times()
    • For x milliseconds - led_blink_ms()
Controll all simultaneously:
  • All on - led_all_on()
  • All off
    • Off - led_all_off()
    • Force off - led_all_force_off()
  • All pulse (custom on-time) - led_all_pulse()
  • All blink (custom period)
    • Indefinitely - led_all_blink_infinite()
    • n times - led_all_blink_n_times()
    • For x milliseconds - led_all_blink_ms()

Important

If a LED is set to a new effect when an ongoing effect hasn't finished, then the ongoing effect will be discarded.

Warning

led_off() and led_all_off() only works for effects lasting indefinitely. Does not work with effects lasting a finite time as these automatically turn off when the effect is done.

Tip

Use led_force_off() and led_all_force_off() instead, this will force the LED to turn off no matter what effect is currently ongoing.


Features for button-sensing:

  • Sense click - btn_click()
  • Sense hold (custom threshold) - btn_hold_ms()
  • Sense release after click/hold - btn_released()
  • Sense if depressed - btn_depressed()
  • Sense no input on all btns for a given duration - btns_no_input_ms()
  • All buttons debounced (custom threshold)

Features for switch-sensing:

  • Sense on - switch_on()
  • Sense off - switch_off()

Features for "intervals":

An "interval" enables you to run code at a specific interval in a non-blocking way, an example is shown below:

int main(void) {
	// Init
	system_init();

	while (1) {
		...
		
		// Prints every 1000 ms, 1 sec, in a non-blocking way
		if (interval_reached_ms(INTERVAL0, 1000)) {
			printf("Hello, World!");
		}

		...
	}
}

Important

An "interval" counter can run up to ~49 days, 2^32 ms, before overflowing.


Porting guide

A quick guide on how to properly port the User IO library to any embedded platform. Here you must open some source-files and edit lines, I've added // <-- EDIT HERE on every line you must edit.

Note

A dedicated timer is needed that triggers an interrupt every x milliseconds.

Important

Button, LED-states and "intervals" are all updated in the TIMx IRQ handler, this can take some time. Advised to use lowest IRQ priority for TIMx if possible. Switches are read from directly, these states are not stored.

See step 2 - Timer setup (Alternative method) for an alternative method where we set a simple flag in the TIMx IRQ handler and run the User IO handler in the main-loop.

This will cause timing to always be off by a certain amount depending on what's in the main loop.


Steps:

1. Init library

Include user_io.h and call user_io_init() in the setup section.

#include "user_io.h"

int main(void) {
    // Setup section
    user_io_init();

    ...
}

2. Timer setup (Normal method)

Init a timer that calls an interrupt handler every x milliseconds. This depends on how responsive you want the buttons, LEDs and "intervals" to be. A minimum of x = 10 ms should be more than fast enough.

Here you must add user_io_irq_handler() in the interrupt handler. In our case, we are using TIMx with a period of 10 ms.

void TIMx_IRQHandler(void) {
	// TIMx overflow IRQ
	if (TIMx_OVR()) {
		user_io_irq_handler();		
		
		// Clear flag
		CLEAR_TIMx_OVR();
	}
}

Important

Advised to configure TIMx IRQ handler with the lowest possible priority.


2. Timer setup (Alternative method)

Init a timer that calls an interrupt handler every x milliseconds. This depends on how responsive you want the buttons, LEDs and "intervals" to be. A minimum of x = 10 ms should be more than fast enough.

First, go to user_io_config.h and uncomment #define ALTERNATIVE_IRQ_METHOD.

Second, you must set user_io_handle_rdy in the interrupt handler when an overflow occurs.

Third, in the main program, you must check if user_io_handle_rdy is set and call user_io_irq_handler(), then reset user_io_handle_rdy.

Full example:

In `user_io_config.h`

// Comment out if alternative method is used
#define ALTERNATIVE_IRQ_METHOD

In `irq_handler.c`

extern user_io_handle_rdy;

void TIMx_IRQHandler(void) {
	// TIMx overflow IRQ
	if (TIMx_OVR()) {
        // Set flag
        user_io_handle_rdy = true;
        
        // Clear flag
        CLEAR_TIMx_OVR();
	}
}

In `main.c`

extern user_io_handle_rdy; 

int main(void) {
    ...

    while(1) {
        ...

        if (user_io_handle_rdy) {
            // Update states
            user_io_irq_handle();

            // Clear flag
            user_io_handle_rdy = false;
        }

        ...
    }
}

Caution

Since the alternative method is used, there will not be a fixed interval between updating states. This means that timing will always be off by a certain amount depending on what's in the main loop.


3.1 Choose amount of buttons, switches, LEDs and "intervals"

Choose the amount of buttons and LEDs used by opening user_io_config.h and adding these to enum led_id, enum btn_id, enum switch_id and enum interval_id as follows:

enum switch_id {
	SW0 = 0, 	// <-- EDIT HERE
	SW1,		// <-- EDIT HERE
};

enum btn_id {
	BTN0 = 0,  	// <-- EDIT HERE
	BTN1,  		// <-- EDIT HERE
	BTN2,  		// <-- EDIT HERE
};

enum led_id {
	LED0 = 0,  	// <-- EDIT HERE
	LED1,  		// <-- EDIT HERE
	LED2  		// <-- EDIT HERE
};

enum interval_id {
	INTERVAL0 = 0,	// <-- EDIT HERE
	INTERVA1,	// <-- EDIT HERE
	INTERVA2  	// <-- EDIT HERE
};

Here we have 3 buttons, 3 LEDs and 3 "intervals".


3.2 Init pins

Open user_io_driver.c and find btn_pins_init(), switch_pins_init() and led_pins_init(). Here you must add code to init GPIO pins used for buttons and LEDs, then turn all LEDs off.

Note

Recommended to use internal pull-ups on buttons if available, results in easier PCB routing if ground plane is used.

void switch_pins_init(void) {
	// Button pins as output with internal pull-up
	PIN_CONFIG(SW0_PIN, INPUT_PULL_UP); 	// <-- EDIT HERE
	PIN_CONFIG(SW1_PIN, INPUT_PULL_UP); 	// <-- EDIT HERE
}

void btn_pins_init(void) {
    // Button pins as output with internal pull-up
    PIN_CONFIG(BTN0_PIN, INPUT_PULL_UP); // <-- EDIT HERE
    PIN_CONFIG(BTN1_PIN, INPUT_PULL_UP); // <-- EDIT HERE
    PIN_CONFIG(BTN2_PIN, INPUT_PULL_UP); // <-- EDIT HERE
}

void led_pins_init(void) {
    // LED pins as output
	PIN_CONFIG(LED0_PIN, OUTPUT); // <-- EDIT HERE
	PIN_CONFIG(LED1_PIN, OUTPUT); // <-- EDIT HERE
	PIN_CONFIG(LED2_PIN, OUTPUT); // <-- EDIT HERE

	// LEDs off
	PIN_LOW(LED0_PIN); // <-- EDIT HERE
	PIN_LOW(LED1_PIN); // <-- EDIT HERE
	PIN_LOW(LED2_PIN); // <-- EDIT HERE
}

3.3 Modify drivers

Open user_io_driver.c and find btn_get_state(), switch_get_state(), led_driver_on(), led_driver_off() and led_driver_toggle(). Add code to set, reset and toggle the pins as shows below:

Important

If internal pull-ups are used, remember to invert when polling the button-pin and switch-pin as shown below. Meaning, use !PIN_READ(BTNx_PIN); and !PIN_READ(SWx_PIN); instead of PIN_READ(BTNx_PIN); and PIN_READ(SWx_PIN);.

enum switch_state switch_get_state(enum switch_id id) {
	switch (id) {
		case SW0:
			return !PIN_READ(SW0_PIN); // <-- EDIT HERE
			
		case SW1:
			return !PIN_READ(SW1_PIN); // <-- EDIT HERE
		
		default:
			return SWITCH_OFF;
	}
}

enum btn_state btn_get_state(enum btn_id id) {
	switch (id) {
		case BTN0: 
			return !PIN_READ(BTN0_PIN); // <-- EDIT HERE

		case BTN1: 
			return !PIN_READ(BTN1_PIN); // <-- EDIT HERE
			
		case BTN2: 
			return !PIN_READ(BTN2_PIN); // <-- EDIT HERE
			
		default:
			return BTN_DEPRESSED;
	}
}

void led_driver_on(enum led_id id) {
	switch (id) {
		case LED0:
			PIN_HIGH(LED0_PIN); // <-- EDIT HERE
			break;

		case LED1:
			PIN_HIGH(LED1_PIN); // <-- EDIT HERE
			break;

		case LED2:
			PIN_HIGH(LED2_PIN); // <-- EDIT HERE
			break;
	}
}

void led_driver_off(enum led_id id) {
	switch (id) {
		case LED0:
			PIN_LOW(LED0_PIN);	// <-- EDIT HERE
			break;
			
		case LED1:
			PIN_LOW(LED1_PIN);	// <-- EDIT HERE
			break;

		case LED2:
			PIN_LOW(LED2_PIN);	// <-- EDIT HERE
			break;
	}
}

void led_driver_toggle(enum led_id id) {
	switch (id) {
		case LED0:
			PIN_TOGGLE(LED0_PIN); // <-- EDIT HERE
			break;
			
		case LED1:
			PIN_TOGGLE(LED1_PIN); // <-- EDIT HERE
			break;

		case LED2:
			PIN_TOGGLE(LED2_PIN); // <-- EDIT HERE
			break;
	}
}

If you are using more or less buttons, switches and LEDs than shown here, then you must add case BTNx:, case SWx: or case LEDx: to each respective function.


4. Update macro-parameters

Open user_io_config.h and find the #define section, then alter the following:

/// Uncomment if alternative method is used
//#define ALTERNATIVE_IRQ_METHOD // <-- EDIT HERE

// Comment if feature is not needed
#define SWITCHES_USE  // <-- EDIT HERE
#define BTNS_USE // <-- EDIT HERE
#define LEDS_USE // <-- EDIT HERE
#define INTERVALS_USE // <-- EDIT HERE

// IRQ handler is called every 10 ms // <-- EDIT HERE
#define USER_IO_HANDLER_PERIOD_MS TIMx_PERIOD_MS // <-- EDIT HERE

// Button sampling time, alter if faster clicking is required
#define BTN_DEBOUNCE_TRESHOLD_MS 20 // <-- EDIT HERE

#define BTNS_AMOUNT	3 // <-- EDIT HERE
#define LEDS_AMOUNT	3 // <-- EDIT HERE
#define INTERVALS_AMOUNT 3 // <-- EDIT HERE

5. A simple program

A simple program to try out some features:

#include "user_io.h"

int main(void) {
    // Setup
    system_setup();
    ...
    user_io_init();

    while (1) {
        // Do different effects if held down more than 1000 ms
        if (btn_hold_ms(BTN0, 1000)) {
            // Blink, on and off every 100 ms
            led_blink_infinite(LED0, 100);

            // Blink 10 times, on and off every 100 ms
            led_blink_n_times(LED1, 100, 10);

            // Blink for 1 sec, on and off every 100 ms
            led_blink_ms(LED2, 100, 1000);
            
        // Pulse LED if click
        } else if (btn_click(BTN0)) {
            // LED on for 1 sec, then turn off
            led_pulse(LED0, 1000);

        // Turn off all LEDs if not pressed
        } else {
            led_all_off();
        }

	// Prints every 1000 ms 
	if (interval_reached_ms(INTERVAL0, 1000)) {
		printf("Hello, World!");
	}

	// Force all leds off if switch on
	if (switch_on(SW0)) {
		led_all_force_off();
	}

	// Enters sleep if no input for 60 sec
	if (btns_no_input_ms(60000)) {
		...
		screen_off();
		enter_sleep();
	}
    }
}

void TIMx_IRQHandler(void) {
	// TIMx overflow IRQ
	if (TIMx_OVR()) {
		user_io_irq_handler();		
		
		// Clear flag
		CLEAR_TIMx_OVR();
	}
}

Note

For more documentation, see the Doxygen folder. Path is "doxygen/html/index.html".

About

A universal embedded-c library that handles up to 256 buttons and LEDs and an unlimited amount of switches. It also lets you run code at a specific interval, using the "intervals" feature. Use it on any platform!

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published