Skip to content
Xaeian edited this page Oct 23, 2026 · 2 revisions

General-Purpose Input/Output

GPIO (general-purpose input/output) is a pin the program uses directly: it sets the level of an output and reads the level of an input. Every pin belongs to a port, GPIOA, GPIOB and on, and has a number from 0 to 15, so PA5 is pin 5 of port A. One GPIO_t holds one pin: where it is and how it works. Programs on OpenCPLC controllers use DIN and DOUT, built on GPIO, while drivers and bare-metal projects use GPIO directly.

Modes

Pin modes

  • alternate picks the peripheral by its number in the alternate function table of the datasheet. Drivers of UART, SPI and I2C set their pins from a pin map, GPIO_Map_t, so a program rarely needs to.
  • In analog mode the digital input and output are off, so nothing disturbs the ADC or the DAC on the pin.
  • A pin can change its mode while running: the 1-Wire driver pulls its line low as an output, then reads it as an input.

Output

Push-pull and open-drain

  • Push-pull drives both levels: the upper transistor connects the pin to VDD, the lower one to GND.
  • Open-drain only pulls the line low, and a resistor pulls it high. Many outputs can share one line, as on I2C, and any of them can pull it low.
  • speed sets how steep the edges are: low for a LED or a relay, since steep edges radiate interference, high for SPI or PWM.

Input

Pull-up and pull-down

An input nothing drives is floating: it picks up interference and reads at random. pull ties it weakly to VDD or to GND, so a free input reads 1 with GPIO_Pull_Up and 0 with GPIO_Pull_Down. The internal resistor is weak, so a long cable or strong interference in the machine needs a stronger one on the board.

Logic level

Active high and active low

reverse turns the logic over: GPIO_Set drives the pin low, and GPIO_In reads a low pin as true. So the code says what a level means, on, pressed, selected, and the wiring stays in the declaration of the pin. A button to GND, a LED from VDD or an active-low chip select takes .reverse = true.

Edge interrupts

EXTI_t handles a rising or a falling edge at once, without waiting for the next pass of the loop.

  • rise_detect and fall_detect pick the edges, RiseHandler and FallHandler what runs on each.
  • A handler runs in the interrupt, so it should stay short: set a flag or count.
  • EXTI_Rise, EXTI_Fall and EXTI_Events return the edges since the last read and clear the count, so pulses are counted with no handler at all.
  • One interrupt line serves pin n of every port, so PA3 and PB3 cannot both have one.

Configuration

Fields left out are zero: input, no pull, push-pull, the lowest speed, set off.

// A LED from PA5 to GND, a button from PC13 to GND
static GPIO_t led = { .port = GPIOA, .pin = 5, .mode = GPIO_Mode_Output };
static GPIO_t button = { .port = GPIOC, .pin = 13, .pull = GPIO_Pull_Up,
  .reverse = true };

// Pulses of a meter on PB3, an open collector to GND, counted on the falling edge
static EXTI_t meter = { .port = GPIOB, .pin = 3, .pull = GPIO_Pull_Up,
  .fall_detect = true, .irq_enable = true };

Usage

GPIO_Init writes the configuration to the port and drives the level set holds, so a pin starts as declared.

static uint32_t pulses; // of the meter since the start

void loop(void)
{
  GPIO_InitList(&led, &button, NULL);
  EXTI_Init(&meter);
  while(1) {
    // A pressed button pulls the pin low, and `reverse` reads it as true
    if(GPIO_In(&button)) GPIO_Set(&led);
    else GPIO_Rst(&led);
    pulses += EXTI_Fall(&meter); // the edges since the last pass
    let();
  }
}

Clone this wiki locally