MCU learning, Entry-Level, Part 3, GPIO, General-Purpose IO

Background:

GPIO provides the most basic function of an MCU which make it so important as well. For example, you should find that in most of the teaching courses, the exercise after the “HelloWorld” is using GPIO to light up an LED. Another examaple could be read a potential meter and print it on serial port.

image source: https://docs.particle.io/datasheets/wi-fi/photon-datasheet/

What do you need for this exercise?

  1. Particle Photon(LINK)
  2. A Computer/Laptop with a USB port(Windows is ideal)
  3. A through hole led (Optional)

Let’s have a look at Particle Photon pin

The “GPIO” we mentioned here are pins with the following function:

  1. ADC, Analog to digital converter
    It has to be input. It is for reading a 0–3.3V analogue signal and using digital number to present it. Some MCU ADC channel is able to assign to a certain pin.
  2. Digital IO,
    It can be both input and output depends on the application. But once your circuit design is fixed. It can only be one of them. For this function, it will have high/low definition. Hign in the Particle Photon will be 3.3V and low will be 0 V.
  3. PWM, Pulse-width modulation. It will generate a square wave from 0V to 3.3V in Particle Photon. It is very command function for applications, such as controlling LED brightness, Motor speed etc.
  4. Communication, Such as I2C, SPI, CAN, UART, … etc.

There is an important concept: Pins so-called GPIO will have a certain level of flexibility.

For example, D1 & D2 in Particle Photon are GPIOs for digital IO application. However, it also can be CAN_TX and CAN_RX at the same time. So we can call these 2 pins GPIO. But if we check VIN pin, it only can be called Power input Pin, it can NOT serve as other function.

Let’s get hands dirty.

1. Digital Output

a. Initialize the Pin function: You have to tell MCU function of this pin.

Syntax:

void pinMode(uint16_t pin, PinMode mode);

Example:

const uint16_t led_blue_pin = D7;void setup()
{
pinMode(led_blue_pin, OUTPUT);
}

Note 1: Several years ago, the Particle official document is using “int” only to declare PIN which is confused to me. Pin number usually is a positive number in the normal case. But with “int” we can have a negative number. What will happen if we using a negative number in an accident. We will discuss the data type as an independent topic later.

Note 2: You may also notice that I add “const” before “uint16_t”. By adding “const”, the number of led_blue_pin will be fixed into D7 only. It is not implemented in official reference. Normally, we will assign the pin and know the function before we start coding. So, there is an extremely rare situation that you will change the GPIO pin function during the executing phase. Adding “const” will reduce the chance that the program changes the number of led_blue_pin accidentally.

b. Use the following command to change the output state:

Syntax:

void digitalWrite(uint16_t pin, uint8_t value);

Example:

digitalWrite(led_blue_pin, HIGH);
/* HIGH/LOW are pre-defined values */

Note 2: There is something I am not happy with. The input value is an “uint8_t” value. In my personal opinion, it should be a boolean type which makes more sense. Maybe there is something related to the implementation of this library in the lower driver layer.

2. Digital Input

a. Initialize the Pin function.

Using the same syntax as digital write:

Example:

pinMode(digi_input_pin, INPUT_PULLDOWN);

b. Use the following command to read the input pin state:

Syntax:

int32_t digitalRead(uint16_t pin)

Again, I am also confused the first time for the return datatype. Normally, it should be 1/0 or High/Low only. Somehow, it is an “int32_t” data type.

3. Analog Input

Analog does not need initialization. In fact, if you used pinMode to initialize it, might have some issue in some OS version.

All you need to do is using the following command to read the value.

int32_t digitalRead(uint16_t pin)

Well…again and again, the return value’s data type is “int32” which is not proper for ADC. In 99.9%, ADC reading is a positive integer value. The resolution might vary depending on MCU. In Particle Photon, ADC is 12-bits resolution. In some high precision ADC module, it can be 16-bits resolution.

ADC resolution is not the higher the better. It depends on several parameters, such as Signal-Noise ratio, the clearness of your ADC voltage reference…etc. Sometimes high-resolution ADC is delta-sigma ADC which might take longer conversion time as well.

We will discuss more ADC feature since it is an interesting topic.

4. PWM output

The PWM signal in generally speaking is a square wave signal. It is a very useful and common feature. We can use the PWM signal to adjust LED brightness, fan speed, motor speed etc,

a. Initialize the Pin function: Which is the same as Digital output.

Syntax:

void pinMode(uint16_t pin, PinMode mode);

b. Use the following command for the output PWM signal

analogWrite(pin, value); 
OR
analogWrite(pin, value, frequency);

The frequency parameter is optional. There are some application or device will need a certain PWM frequency. The default value is 500 Hz.

Pull all together:

1. Circuit connection

I will show you how to calculate the LED circuit later. Don’t worry about it now.

2. Coding

There are 3 files you need as the following

a. “gpio_demo.ino”

b. “gpio.cpp”

c. “gpio.h”

Using the following command in the .ino file to select which function you want to demo.

/* Select which funciton to run */
#define CURRENT_FUNCTION DIGI_WRITE

Note 3: We put all of the GPIO related function in the “gpio.cpp”. It creates some benefits, such as all GPIO pin define will only exist in this file.

Note 4: We also using #if , #elif function to select which part of code will actually be“Compiled” and execute, instead of using the comment. We will talk about it in another topic.

3. Run the code

a. Let’s select “DIGI_WRITE” function first.

Change the CURRENT_FUNCTION to the following code

#define CURRENT_FUNCTION DIGI_WRITE

Compile -> Flash

Once you flashed it, you should find the blue user-led on the Photon will flash at 1Hz. This is because we put all of the GPIO demo function in a 1Hz timer loop. And every time the “void digital_write_demo()” is called, it will invert the previous state of pin by the “ !pin_value ” as the following command.

/*Inverte the pin_value*/
pin_value = !pin_value;

b. Change CURRENT_FUNCTION to “digi_input_pin”

Compile -> Flash -> Serial monitor

Then, if you connect a press or tactile button and then press it, you should see the state changing from 1 to 0 or 0 to 1 in the serial monitor. It can be achieved by direct connect the D1 pin to 3.3V power source in Photon too.

Since we make the D1 INPUT_PULLDOWN, the real circuit will look like this:

The feature of “Internal Pull-down” is when the button is not pressed, the D1 port will connect to the ground via the internal resistor which will be “Low”. If the button is pressed, D1 will have 3.3V voltage reference which will be “High”.

Why do we need pull-down or pull-up resistor? Imagine if the resistor is missing, what is the state of D1 when the button is not pressed? The answer is unknown and it depends on MCU internal circuit design. The worst situation will be “Floating” which means could be anywhere between 0V to 3.3V.

In the datasheet of Photon, it also mentioned that :

INPUT_PULLUP and INPUT_PULLDOWN are approximately 40K on Gen 2 devices”

c. Change CURRENT_FUNCTION to “ANALOG_READ”

Compile -> Flash -> Serial monitor

When you changing the variable resistors, you should see the number changing as well. Since it is a 12-bits resolution ADC, the number range could be 0 to 4095 to represent 0 to 3.3V. You also can image that 3.3V is divided by 4095 which means each number is 0.0008 volts (0.8 mV).

d. Change CURRENT_FUNCTION to “PWM_WRITE”

Compile -> Flash -> Serial monitor

The last exercise will require Analog read function from exercise c. It will read the analog signal and send a PWM signal. The PWM signal’s duty cycle is determined by the ADC’s reading.

If you turn the variable resistor, you should find that your led brightness will change as well. This is caused by the higher duty cycle the more LED turn-on time. That is why your eye feels the brightness is increased. It is the same technique for some cheap LED monitor.

If you are not familiar with PWM signal, please check this website:
https://www.arduino.cc/en/Tutorial/Foundations/PWM

e. Extra content: DAC, Digital to Analog

DAC normally will not be categorized in GPIO, since it requires a special circuit. But it creates a very similar effect with PWM on LED brightness.

By replacing:

analogWrite(pwm_pin, analog_value/16);

To:

analogWrite(DAC1, analog_value);

And connecting LED to the DAC pin on Photon. When you change the variable resistor’s value, you should see the LED changing the brightness as well.

Conclusion

GPIO provides a very basic function but also necessary in the embedded system. This article should give you an overview of how to use GPIO in Particle Photon.

Important Note: How to calculated LED resistor value?

Let’s take a particular part number of LED as an example:

OSRAM Opto Semiconductors: KP DELLS1.22-FGGI-25 (LINK)

From the LED datasheet we know that:

a. If — Forward Current: 2 mA(Typical)

b. Vf — Forward Voltage: 1.8V(Typical)

The calculation will be:

From the calculation, you should be able to find a proper resistor for this LED. Make sure the resistor value is equal or higher than the calculation value and power rating is enough. Higher resistor value will cause LED lower brightness. But lower resistor value might burn the LED.

If you are a lazy guy like me, you can try the resistor value from 330 ohms. Increase or decrease the resistor value depends on the brightness. BUT! It might burn your LED by accident.

We also need to consider the Max current output from the Particle Photon pin which is 25mA. If the LED datasheet shows that it requires a higher forward current and you are using Particle Photon…

TRY OTHER METHOD! Like using BJT or even RELAY.

Software engineer, Movie lover, Karate beginner.