iram void gpios_periodic(void)
{
	gpio_t *gpio;
	const gpio_config_entry_t *cfg;
	unsigned int current;
	unsigned int duty;
	bool_t pwm_changed = false;

	for(current = 0; current < gpio_size; current++)
	{
		gpio = &gpios[current];
		cfg = get_config(gpio);

		if((cfg->mode == gpio_counter) && (gpio->counter.debounce != 0))
		{
			if(gpio->counter.debounce >= 10)
				gpio->counter.debounce -= 10; // 10 ms per tick
			else
				gpio->counter.debounce = 0;

			if(gpio->counter.debounce == 0)
				arm_counter(gpio);
		}

		if((cfg->mode == gpio_timer) && (gpio->timer.delay > 0))
		{
			if(gpio->timer.delay >= 10)
				gpio->timer.delay -= 10; // 10 ms per tick
			else
				gpio->timer.delay = 0;

			if(gpio->timer.delay == 0)
			{
				set_output(gpio, !get_input(gpio));

				if(cfg->timer.repeat)
					gpio->timer.delay = cfg->timer.delay;
			}
		}

		if((cfg->mode == gpio_pwm) && (gpio->pwm.delay_top > 0))
		{
			if(++gpio->pwm.delay_current > gpio->pwm.delay_top)
			{
				gpio->pwm.delay_current = 0;

				duty = pwm_get_duty(gpio->pwm.channel);

				if(gpio->pwm.direction == gpio_up)
				{
					if(duty < gpio->pwm.min_duty)
						duty = gpio->pwm.min_duty;

					if(duty < 16)
						duty = 16;

					duty *= 115;
					duty /= 100;

					if(duty >= gpio->pwm.max_duty)
					{
						duty = gpio->pwm.max_duty;
						gpio->pwm.direction = gpio_down;
					}
				}
				else
				{
					if(duty > gpio->pwm.max_duty)
						duty = gpio->pwm.max_duty;

					duty *= 100;
					duty /= 115;

					if(duty <= gpio->pwm.min_duty)
					{
						duty = gpio->pwm.min_duty;
						gpio->pwm.direction = gpio_up;
					}

					if(duty < 16)
					{
						duty = 16;
						gpio->pwm.direction = gpio_up;
					}
				}

				pwm_changed = true;
				pwm_set_duty(duty, gpio->pwm.channel);
			}
		}
	}

	if(pwm_changed)
		pwm_start();
}
irom static void dump(const gpio_config_t *cfgs, const gpio_t *gpio_in, uint16_t size, char *str)
{
	uint8_t current;
	uint16_t length;
	const gpio_t *gpio;
	const gpio_config_entry_t *cfg;

	for(current = 0; current < gpio_size; current++)
	{
		gpio = &gpios[current];
		cfg = &cfgs->entry[current];

		if(!gpio_in || (gpio_in->id == gpio->id))
		{
			length = snprintf(str, size, "> gpio: %u, name: %s, mode: ", gpio->index, gpio->name);
			size -= length;
			str += length;
			length = 0;

			switch(cfg->mode)
			{
				case(gpio_disabled):
				{
					length = snprintf(str, size, "disabled");
					break;
				}

				case(gpio_input):
				{
					length = snprintf(str, size, "input, state: %s", onoff(get_input(gpio)));
					break;
				}

				case(gpio_counter):
				{
					length = snprintf(str, size, "counter, state: %s, counter: %u, debounce: %u/%u, reset on get: %s",
							onoff(get_input(gpio)), gpio->counter.count,
							cfg->counter.debounce, gpio->counter.debounce,
							onoff(cfg->counter.reset_on_get));
					break;
				}

				case(gpio_output):
				{
					length = snprintf(str, size, "output, state: %s, startup: %s",
							onoff(get_input(gpio)), onoff(cfg->output.startup_state));
					break;
				}

				case(gpio_timer):
				{
					length = snprintf(str, size, "timer, direction: %s, delay: %u ms, repeat: %s, autotrigger: %s, active: %s, current state: %s",
							cfg->timer.direction == gpio_up ? "up" : "down",
							cfg->timer.delay, onoff(cfg->timer.repeat),
							onoff(cfg->timer.autotrigger), onoff(gpio->timer.delay > 0),
							onoff(get_input(gpio)));
					break;
				}

				case(gpio_pwm):
				{
					length = snprintf(str, size, "pwm, ");
					str += length;
					size -= length;

					if(gpio_flags.pwm_subsystem_active)
						length = snprintf(str, size, "active, current frequency: %u Hz, current duty: %u",
								1000000 / pwm_get_period(), pwm_get_duty(gpio->pwm.channel));
					else
						length = snprintf(str, size, "inactive");

					str += length;
					size -= length;

					length = snprintf(str, size, "\ndefault min duty: %u, max duty: %u, speed: %u",
							cfg->pwm.min_duty, cfg->pwm.max_duty, cfg->pwm.speed);

					str += length;
					size -= length;

					length = snprintf(str, size, "\nactive min duty: %u, max duty %u, speed: %u",
							gpio->pwm.min_duty, gpio->pwm.max_duty, gpio->pwm.speed);

					break;
				}

				case(gpio_i2c):
				{
					length = snprintf(str, size, "i2c, pin: %s", cfg->i2c.pin == gpio_i2c_sda ? "sda" : "scl");

					break;
				}


				default:
				{
					length = snprintf(str, size, "unknown mode");
					break;
				}
			}

			str += length;
			size =- length;

			length = snprintf(str, size, "\n");
			str += length;
			size -= length;
		}
	}
}