Inputs and Outputs

When working with Plaquette, inputs and outputs allow you to interact with the physical world. Whether you are reading a sensor’s value or controlling an actuator, Plaquette makes this process intuitive and efficient. This section introduces the basic concepts of digital and analog inputs and outputs, presents Plaquette’s unique syntax for efficient and expressive code, explains how to clean noisy data, shows how to make decisions based on input values, and describes the different configuration modes of input and output units.

Let’s explore these ideas step by step.

Digital vs Analog

Before diving into code, let’s first clarify the difference between digital and analog signals.

  • Digital signals represent binary states: ON or OFF, HIGH or LOW, 1 or 0. Examples of digital inputs include buttons and presence sensors, while digital outputs might control LEDs or relays.

  • Analog signals represent a continuous range of values. Think of a dimmer switch (potentiometer) or a light sensor that measures brightness levels. Analog outputs control devices such as LEDs with variable brightness and DC motors where the speed can vary.

Warning

On many microcontrollers, analog outputs are generated using Pulse Width Modulation (PWM) rather than a true Digital-to-Analog Converter (DAC). PWM rapidly switches the output pin between HIGH and LOW, creating the illusion of a continuous analog voltage when averaged over time. While this works for controlling brightness in LEDs or speed in motors, it may not be suitable for applications requiring a steady, smooth signal.

For more information, visit the official Arduino documentation on Pulse Width Modulation.

Digital Inputs and Outputs

Let’s look at the basics of digital signals by working with LEDs and buttons.

Digital Outputs

A DigitalOut unit controls devices that can be turned ON or OFF, such as LEDs or relays. Here’s an example of how to control an LED:

#include <Plaquette.h>

DigitalOut led(13); // LED connected to pin 13

void begin() {
  led.on(); // Turn the LED on initially
}

void step() {
  if (seconds() > 10.0)
    led.off(); // Turn the LED off after 10 seconds
}

Digital Inputs

A DigitalIn unit reads binary states from devices like buttons or switches. The easiest way to connect a button or switch is to use the internal pull-up resistor on Arduino boards. One leg of the button should be connected to ground (GND) while the other should be connected to a digital pin.

#include <Plaquette.h>

DigitalIn button(2, INTERNAL_PULLUP); // Button connected to pin 2
DigitalOut led(13);                    // LED connected to pin 13

void begin() {}

void step() {
  if (button.isOn()) {  // Check if the button is pressed
    led.on();
  } else {
    led.off();
  }
}

Analog Outputs and Inputs

Next, we will explore analog signals, which allow for finer control and more detailed readings.

Analog Outputs

An AnalogOut unit controls devices like LEDs or motors with continuous values. Here’s an example of dimming an LED. The cathode (short leg) of the LED should be connected to ground, while the anode (long leg) should be connected to a 300 \(\Omega\) resistor, which in turn should be connected to an analog / PWM pin (eg. pin 9).

#include <Plaquette.h>

AnalogOut led(9); // LED connected to pin 9

void begin() {
  led.put(0); // Set LED brightness to 0%
}

void step() {
  led.put( seconds() / 10 ); // Will reach 100% after 10 seconds
}

Analog Inputs

An AnalogIn unit reads continuous values from sensors, such as potentiometers, light, or temperature sensors.

Let’s use a potentiometer to control an LED’s brightness. For this circuit, the center pin of the potentiometer should be connected to analog input pin (A0`), the left pin to ground (GND) and the right pin to +5V (Vcc).

#include <Plaquette.h>

AnalogIn dimmer(A0); // Potentiometer on analog pin A0
AnalogOut led(9);    // LED on pin 9

void begin() {}

void step() {
  led.put(dimmer.get()); // Map the potentiometer value directly to LED brightness
}

Using Units as Their Own Values

Plaquette offers an elegant shortcut: you don’t need to explicitly call isOn() or get() for digital or analog inputs. Instead, you can use the input or output unit itself as its value. This makes your code cleaner and easier to read.

Here’s the same LED and button example, rewritten with this feature:

#include <Plaquette.h>

DigitalIn button(2, INTERNAL_PULLUP);
DigitalOut led(13);

void begin() {}

void step() {
  if (button) {  // No need for button.isOn(), just use button as its own value
    led.on();
  } else {
    led.off();
  }
}

For analog inputs, this works similarly. Instead of calling dimmer.get(), you can use the dimmer object directly:

#include <Plaquette.h>

AnalogIn dimmer(A0);
AnalogOut led(9);

void begin() {}

void step() {
  led.put(dimmer); // No need for dimmer.get(), just use dimmer
}

These simplifications make your code more expressive and emphasize the logic over the syntax.

The Piping Operator (>>)

In Plaquette, the >> operator allows you to directly send or “pipe” the value of one unit to another. This makes it incredibly simple to map inputs to outputs without extra variables or function calls.

Let’s revisit the potentiometer and LED example using the piping operator:

#include <Plaquette.h>

AnalogIn dimmer(A0);
AnalogOut led(9);

void begin() {}

void step() {
  dimmer >> led; // Directly pipe the potentiometer value to the LED
}

This operator improves code readability and emphasizes the relationship between inputs and outputs.

Dealing with Noisy Signals: Debouncing and Smoothing

In real-world applications, signals can be messy. Buttons can produce electrical noise when pressed, and analog sensors might give fluctuating readings. Plaquette provides tools to handle these issues: debouncing for digital signals and smoothing for analog ones.

Debouncing

Debouncing ensures that a button press is recorded cleanly, ignoring any noise. Here’s how to debounce a button:

#include <Plaquette.h>

DigitalIn button(2, INTERNAL_PULLUP); // Button with pull-up resistor
DigitalOut led(13);                   // LED on pin 13

void begin() {
  button.debounce(); // Debounce the button
}

void step() {
  if (button.rose()) {  // Detect a clean press
    led.toggle();       // Toggle the LED state
  }
}

Smoothing

For analog signals, smoothing helps stabilize noisy data.

Here’s how you can smooth a light sensor (photoresistor). For this circuit, you will need to create a simple voltage divider circuit. Connect the photoresistor between the ground (GND) and the analog input pin (A0`). Then connect a fixed resistor with value matching your photoresistor between analog input pin and +5V (Vcc). For example, for a 1k \(\Omega\) - 10k \(\Omega\) photoresistor you could use a fixed resistor of about 5.5k \(\Omega\)).

#include <Plaquette.h>

AnalogIn lightSensor(A0);
AnalogOut led(9);

void begin() {
  lightSensor.smooth(); // Apply default smoothing
}

void step() {
  lightSensor >> led;
}

You can adjust the level of smoothing and deboucing by indicating a parameter representing the time window (in seconds) over which the value is averaged. Experiment with different smoothing values to see the result:

  • lightSensor.smooth() : Default smoothing window (100ms)

  • lightSensor.smooth(1.0) : Smooth over one second

  • lightSensor.smooth(10.0) : Smooth over 10 seconds

  • lightSensor.smooth(0.01) : Smooth over 10ms

Mapping Values to Different Ranges

Sometimes, the output of a sensor doesn’t match the range needed for an actuator. Plaquette provides a simple mapping function mapTo(low, high) which maps the analog input value to a specified range which is very useful for scaling sensor readings.

Example: Controlling the blinking frequency of an LED based on the value of a light sensor.

#include <Plaquette.h>

AnalogIn lightSensor(A0);
DigitalOut led(13);
SquareWave wave(1.0);

void begin() {}

void step() {
  // Map sensor value to frequency in range 1-10 Hz
  wave.frequency( lightSensor.mapTo(1, 10) );
  // Control LED with wave.
  wave >> led;
}

Making Decisions with Conditions

Interactive systems often need to respond to changes in input. Plaquette provides convenient methods like rose(), fell(), and changed() for detecting transitions in digital signals.

Digital Conditions

Here’s an example of toggling an LED when a button is pressed:

#include <Plaquette.h>

DigitalIn button(2, INTERNAL_PULLUP);
DigitalOut led(13);

void begin() {}

void step() {
  if (button.rose()) { // Detect the moment the button is pressed
    led.toggle();      // Toggle the LED state
  }
}

Analog Conditions

Analog conditions are useful when you want to trigger actions based on a threshold. For instance, turning on an LED when the light level drops below 30% (0.3):

#include <Plaquette.h>

AnalogIn lightSensor(A0);
DigitalOut led(13);

void begin() {}

void step() {
  if (lightSensor < 0.3) {
    led.on();  // Turn on LED in low light
  } else {
    led.off(); // Turn off LED in bright light
  }
}

Modes for Inputs and Outputs

All input and output units in Plaquette support different modes, which allow you to adapt to various circuit configurations. You may already be familiar with the INTERNAL_PULLUP mode from DigitalIn, which provides a simple way to connect a button input. Let’s explore how modes affect DigitalIn, AnalogIn, DigitalOut, and AnalogOut units.

Understanding these modes helps you design stable and efficient circuits, whether you’re reading inputs or driving outputs. Choose the mode that best fits your hardware setup and application requirements.

DigitalIn Modes: DIRECT, INVERTED, and INTERNAL_PULLUP

The DigitalIn unit supports three primary modes:

  • DIRECT (default): The unit is ON when the input pin is HIGH (e.g., 5V). This mode is used for buttons with pull-down resistors, which keep the pin LOW (OFF) when the button is not pressed and allow it to go HIGH (ON) when the button is pressed. Pull-down resistors typically have values around 10k \(\Omega\).

    Example: Button connected between pin 2 and 5V with a pull-down resistor to ground:

    DigitalIn button(2, DIRECT);
    DigitalOut led(13);
    
    void step() {
      if (button) {
        led.on();
      } else {
        led.off();
      }
    }
    
  • INVERTED: The unit is ON when the input pin is LOW (e.g., GND). This is useful for buttons with pull-up resistors, which keep the pin HIGH when the button is not pressed and allow it to go LOW when the button is pressed. The INTERNAL_PULLUP mode activates an internal pull-up resistor, simplifying the circuit.

    Example: Button connected between pin 2 and ground with a pull-down resistor to +5V (Vcc):

    DigitalIn button(2, INVERTED);
    
  • INTERNAL_PULLUP: As in mode INVERTED the unit is ON when the input pin is LOW (e.g., GND). Makes use of the internal pull-up resistor on the board, therefore removing the need to add a pull-up resistor.

    Example: Button connected between pin 2 and ground (no need for an extra pull-up resistor):

    DigitalIn button(2, INTERNAL_PULLUP);
    

AnalogIn Modes: DIRECT and INVERTED

The AnalogIn unit also supports DIRECT and INVERTED modes, which determine how the sensor’s voltage is interpreted:

  • DIRECT (default): Reads the raw analog value, normalized to a range of [0.0, 1.0]. This mode is suitable for sensors like photoresistors, where increasing light decreases resistance, resulting in higher voltage and a higher normalized value.

    Example: Using a photoresistor in direct mode:

    AnalogIn lightSensor(A0, DIRECT);
    AnalogOut led(9);
    
    void begin() {}
    
    void step() {
      lightSensor >> led;
    }
    
  • INVERTED: Flips the normalized value, so high input voltage results in a low output value and vice versa. This is useful when you want the sensor to behave oppositely without changing your logic.

    Example: Inverted photoresistor reading:

    AnalogIn lightSensor(A0, INVERTED);
    

DigitalOut and AnalogOut Modes: SOURCE and SINK

The DigitalOut and AnalogOut units control the flow of current and can operate in two modes:

  • SOURCE (default): The pin provides current when ON, suitable for devices like LEDs connected between the pin and ground.

    Example: LED in source mode. Connect the LED anode (long leg) to pin 9 and the cathode (short leg) to ground, with a 330 \(\Omega\) in series.

    AnalogOut led(9, SOURCE);
    SineWave wave(1.0);
    
    void begin() {}
    
    void step() {
      wave >> led;
    }
    
  • SINK: The pin sinks current when ON, suitable for LEDs connected between a positive voltage and the pin.

    Example: LED in sink mode. Connect the LED anode to +5V (Vcc) and the cathode to pin 9, with a 330 \(\Omega\) resistor in series.

    AnalogOut led(9, SINK);
    

Conclusion

Understanding inputs and outputs is crucial for building interactive projects. With Plaquette’s simplified syntax, tools for handling noisy signals, and powerful mapping and conditional features, you can quickly create dynamic and engaging systems. Next, we’ll explore how to use Plaquette’s timing and signal generation features to add even more complexity and creativity to your projects.