Microcontroller Introduction Lesson 2 - Blinking Light Master

Microcontroller Introduction Lesson 2 - Blinking Light Master

The first lesson officially entering hardware control

Last updated 10/21/2022 6:53 PM
陈显达
13 min read
Category
Sharing
Tags
Hardware Related

This article is contributed by a netizen; the site owner knows nothing about hardware.

Author: Chen Xianda

Original Title: Microcontroller Introduction Lesson 2 – The Master of Blinking Lights

Original Link: https://www.cnblogs.com/1996-Chinese-Chen/p/16814553.html

Introduction

In the previous blog post, we officially started our journey of learning microcontrollers. We discussed the concept of microcontrollers, the IO pins of the ESP32 series we are using, what GPIO is, related bus communication concepts (UART, IIC, SPI), pulse-width modulation (PWM), analog-to-digital and digital-to-analog conversion (ADC and DAC), and some built-in features of the board. In today's blog, I will guide you into the first lesson of controlling hardware.

No matter which microcontroller you use, the first lesson is always to light up an LED, commonly known as the "Master of Blinking Lights." Haha, our first lesson is also about lighting an LED. I didn't ask everyone in the group to buy an LED because we can actually control one of the built-in LEDs on the ESP board using code. When the power is connected, a red power LED lights up, indicating normal power. There is also a blue LED that is off by default. Next, we will make the other LED on the ESP32 development board blink.

Lighting the LED

void setup() {  

  // put your setup code here, to run once:  
 pinMode(2,OUTPUT);  
}  

void loop() {  
  digitalWrite(2, HIGH);   // sets the LED on  
  delay(1000);                  // waits for a second  
  digitalWrite(2, LOW);    // sets the LED off  
  delay(1000);  
}  

Here is the code, as shown above, which will light up the other blue LED on the board. Let me explain the code. The setup() function runs only once when the microcontroller is powered on, i.e., once per power cycle. The loop() function is a cyclic code that runs continuously while the board is powered. It first runs setup(), then loop(). The setup() function is used to configure the microcontroller – here, we set pin 2 as an output, meaning the microcontroller outputs a high or low level to pin 2 to light up the LED. On the ESP32, the blue LED is connected to pin 2, so we set pin 2 as output mode.

The second part, the loop() code: The first line calls the digitalWrite() method, which writes a high or low level to the specified pin, turning the pin's power on or off. The first parameter is the pin number (pin value), and the second parameter is the value to write: HIGH for power on, LOW for power off. The second line is a delay function; the value is in milliseconds, so 1000 means a one-second pause.

The GIF below shows the result of this operation – you can see the blue LED blinking continuously.

Code Compilation and Upload

After writing the code, we need to compile it and then upload it to the microcontroller. Each time you finish writing, click the checkmark button in the upper-left corner of the editor. The IDE will start compiling our code. After compilation, we need to upload the code to the microcontroller. Click the right-arrow button next to the checkmark, and the code begins uploading. When you see "Connecting..." at the bottom, you need to put the microcontroller into download mode. There are two buttons on the board: one above the power LED and another horizontally next to it. When "Connecting..." appears, press and hold the button indicated by the arrow below (without releasing it), and the program will be written.

Arduino

Our development IDE is Arduino. I previously configured VS Code to develop for the ESP32 using pure C language, but later switched to Arduino because it is simpler than pure C and more suitable for beginners. However, the principle is the same for C development – the only difference is the syntax.

In program execution, both methods run code in a loop, but one uses a main() function and the other uses loop(). The rest is just syntax differences. Arduino is built on top of C and C++, and its encapsulation is closer to high-level languages. Here, I show some Arduino methods, constants, and data types.

C Language

For those with a weak foundation in C or who haven't used it deeply, C can be challenging. I will paste here some code I wrote earlier for an infrared-controlled smart car, using native C files for the ESP32. Its complexity is slightly higher compared to Arduino.

/* brushed dc motor control example  
   This example code is in the Public Domain (or CC0 licensed, at your option.)  

   Unless required by applicable law or agreed to in writing, this  
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR  
   CONDITIONS OF ANY KIND, either express or implied.  
*/  

/*  
 * This example will show you how to use MCPWM module to control brushed dc motor.  
 * This code is tested with L298 motor driver.  
 * User may need to make changes according to the motor driver they use.  
 */  

#include <stdio.h>  
#include "sdkconfig.h"  
#include <string.h>  
#include "freertos/FreeRTOS.h"  
#include "freertos/task.h"  
#include "esp_attr.h"  
#include "driver/rmt.h"  
#include "driver/mcpwm.h"  
#include "soc/mcpwm_periph.h"  
#include "IR_Rev.h"  
// const static char *TAG = "IR_Rre Demo";  

#define RECV_PIN 23  // Integrated infrared receiver GPIO  
uint8_t command = 0; // Received ENC infrared command  
int direction = 0;  
float currentspeed = 0;  
int currentcolor = 2;  
#define GPIO_PWM0A_OUT 15 // Set GPIO 15 as PWM0A  
#define GPIO_PWM0B_OUT 16 // Set GPIO 16 as PWM0B  
#define GPIO_PWM1A_OUT 17  
#define GPIO_PWM1B_OUT 18  
#define GPIO_PWM2A_OUT 12 // Set GPIO 15 as PWM0A  
#define GPIO_PWM2B_OUT 14 // Set GPIO 16 as PWM0B  
#define GPIO_PWM3A_OUT 25  
#define GPIO_PWM3B_OUT 26  
#define RED 2  
#define GREEN 4  
#define BLUE 5  
#define INFARE 21  
static void mcpwm_example_gpio_initialize(void)  
{  
    printf("initializing mcpwm gpio...\n");  
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT);  
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT);  
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_PWM1A_OUT);  
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1B, GPIO_PWM1B_OUT);  
    mcpwm_gpio_init(MCPWM_UNIT_1, MCPWM0A, GPIO_PWM2A_OUT);  
    mcpwm_gpio_init(MCPWM_UNIT_1, MCPWM0B, GPIO_PWM2B_OUT);  
    mcpwm_gpio_init(MCPWM_UNIT_1, MCPWM1A, GPIO_PWM3A_OUT);  
    mcpwm_gpio_init(MCPWM_UNIT_1, MCPWM1B, GPIO_PWM3B_OUT);  
}  
// forwards he forward 替换实现顺时针吹风和逆时针吹风  
/**  
 * @brief motor moves in forward direction, with duty cycle = duty %  
 */  
static void brushed_motor_forward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, float duty_cycle)  
{  
    mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A);  
    mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_B, duty_cycle);  
    mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_B, MCPWM_DUTY_MODE_0); // call this each time, if operator was previously in low/high state  
}  
static void brushed_motor_forwards(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, float duty_cycle)  
{  
    ESP_ERROR_CHECK(mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B));  
    ESP_ERROR_CHECK(mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_A, duty_cycle));  
    ESP_ERROR_CHECK(mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_A, MCPWM_DUTY_MODE_0)); // call this each time, if operator was previously in low/high state  
}  
// forwards he forward 替换实现顺时针吹风和逆时针吹风  
/**  
 * @brief motor moves in forward direction, with duty cycle = duty %  
 */  
static void brushed_motor_backward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, float duty_cycle)  
{  
    mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B);  
    mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_A, duty_cycle);  
    mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_A, MCPWM_DUTY_MODE_0); // call this each time, if operator was previously in low/high state  
}  
static void brushed_motor_backwards(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, float duty_cycle)  
{  
    ESP_ERROR_CHECK(mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A));  
    ESP_ERROR_CHECK(mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_B, duty_cycle));  
    ESP_ERROR_CHECK(mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_B, MCPWM_DUTY_MODE_0)); // call this each time, if operator was previously in low/high state  
}  

/**  
 * @brief motor stop  
 */  
static void brushed_motor_stop(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num)  
{  
    mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A);  
    mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B);  
}  

static void setspeed(uint8_t command)  
{  
    if ((uint8_t)command == 22)  
    {  
        printf("%d\n", 22);  
        currentspeed = 10;  
    }  
    else if ((uint8_t)command == 12)  
    {  
        printf("%d\n", 12);  
        currentspeed = 20;  
    }  
    else if ((uint8_t)command == 24)  
    {  
        printf("%d\n", 24);  
        currentspeed = 30;  
    }  
    else if ((uint8_t)command == 94)  
    {  
        printf("%d\n", 94);  
        currentspeed = 40;  
    }  
    else if ((uint8_t)command == 8)  
    {  
        printf("%d\n", 8);  
        currentspeed = 50;  
    }  
    else if ((uint8_t)command == 28)  
    {  
        printf("%d\n", 28);  
        currentspeed = 60;  
    }  
    else if ((uint8_t)command == 90)  
    {  
        printf("%d\n", 90);  
        currentspeed = 70;  
    }  
    else if ((uint8_t)command == 66)  
    {  
        printf("%d\n", 66);  
        currentspeed = 80;  
    }  
    else if ((uint8_t)command == 82)  
    {  
        printf("%d\n", 82);  
        currentspeed = 90;  
    }  
    else if ((uint8_t)command == 74)  
    {  
        printf("%d\n", 74);  
        currentspeed = 100;  
    }  
}  
static void head(float speed)  
{  
    brushed_motor_forward(MCPWM_UNIT_0, MCPWM_TIMER_0, speed);  
    brushed_motor_forwards(MCPWM_UNIT_0, MCPWM_TIMER_1, speed);  
    brushed_motor_forward(MCPWM_UNIT_1, MCPWM_TIMER_0, speed);  
    brushed_motor_forwards(MCPWM_UNIT_1, MCPWM_TIMER_1, speed);  
}  
static void last(float speed)  
{  
    brushed_motor_backward(MCPWM_UNIT_0, MCPWM_TIMER_0, speed);  
    brushed_motor_backwards(MCPWM_UNIT_0, MCPWM_TIMER_1, speed);  
    brushed_motor_backward(MCPWM_UNIT_1, MCPWM_TIMER_0, speed);  
    brushed_motor_backwards(MCPWM_UNIT_1, MCPWM_TIMER_1, speed);  
}  
static void left(float speed)  
{  
    mcpwm_set_signal_low(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_OPR_A);  
    mcpwm_set_signal_low(MCPWM_UNIT_1, MCPWM_TIMER_0, MCPWM_OPR_B);  
}  
static void right(float speed)  
{  
    mcpwm_set_signal_low(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_B);  
    mcpwm_set_signal_low(MCPWM_UNIT_1, MCPWM_TIMER_1, MCPWM_OPR_A);  
}  
/**  
 * @brief Configure MCPWM module for brushed dc motor  
 */  
static void mcpwm_example_brushed_motor_control(void *arg)  
{  
    // 1. mcpwm gpio initialization  
    mcpwm_example_gpio_initialize();  

    // 2. initial mcpwm configuration  
    printf("Configuring Initial Parameters of mcpwm...\n");  
    mcpwm_config_t pwm_config;  
    pwm_config.frequency = 1000; // frequency = 500Hz,  
    pwm_config.cmpr_a = 0;       // duty cycle of PWMxA = 0  
    pwm_config.cmpr_b = 0;       // duty cycle of PWMxb = 0  
    pwm_config.counter_mode = MCPWM_UP_COUNTER;  
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;  
    ESP_ERROR_CHECK(mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config)); // Configure PWM0A & PWM0B with above settings  
    mcpwm_config_t pwm_configs;  
    pwm_configs.frequency = 1000; // frequency = 500Hz,  
    pwm_configs.cmpr_a = 0;       // duty cycle of PWMxA = 0  
    pwm_configs.cmpr_b = 0;       // duty cycle of PWMxb = 0  
    pwm_configs.counter_mode = MCPWM_UP_COUNTER;  
    pwm_configs.duty_mode = MCPWM_DUTY_MODE_0;  
    ESP_ERROR_CHECK(mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_configs)); // Configure PWM0A & PWM0B with above settings  

    mcpwm_config_t pwm_configA;  
    pwm_configA.frequency = 1000; // frequency = 500Hz,  
    pwm_configA.cmpr_a = 0;       // duty cycle of PWMxA = 0  
    pwm_configA.cmpr_b = 0;       // duty cycle of PWMxb = 0  
    pwm_configA.counter_mode = MCPWM_UP_COUNTER;  
    pwm_configA.duty_mode = MCPWM_DUTY_MODE_0;  
    ESP_ERROR_CHECK(mcpwm_init(MCPWM_UNIT_1, MCPWM_TIMER_0, &pwm_configA)); // Configure PWM0A & PWM0B with above settings  
    mcpwm_config_t pwm_configAs;  
    pwm_configAs.frequency = 1000; // frequency = 500Hz,  
    pwm_configAs.cmpr_a = 0;       // duty cycle of PWMxA = 0  
    pwm_configAs.cmpr_b = 0;       // duty cycle of PWMxb = 0  
    pwm_configAs.counter_mode = MCPWM_UP_COUNTER;  
    pwm_configAs.duty_mode = MCPWM_DUTY_MODE_0;  
    ESP_ERROR_CHECK(mcpwm_init(MCPWM_UNIT_1, MCPWM_TIMER_0, &pwm_configAs)); // Configure PWM0A & PWM0B with above settings */ */  
    while (1)  
    {  
        command = IRrecvReadIR();  

        printf("IR Command is %02X\n", command);  
        printf("IR 111 is %d\n", (uint8_t)command);  
        if ((uint8_t)command == 69)  
        {  
            brushed_motor_stop(MCPWM_UNIT_0, MCPWM_TIMER_0);  
            brushed_motor_stop(MCPWM_UNIT_0, MCPWM_TIMER_1);  
            brushed_motor_stop(MCPWM_UNIT_1, MCPWM_TIMER_0);  
            brushed_motor_stop(MCPWM_UNIT_1, MCPWM_TIMER_1);  
        }  
        else if ((uint8_t)command == 64)  
        {  
            direction = 64;  
            head(currentspeed);  
        }  
        else if ((uint8_t)command == 25)  
        {  
            direction = 25;  
            last(currentspeed);  
        }  
        else if ((uint8_t)command == 7)  
        {  
            printf("IR 32 is %d\n", (uint8_t)command);  
            left(currentspeed);  
        }  
        else if ((uint8_t)command == 9)  
        {  
            printf("IR 23 is %d\n", (uint8_t)command);  
            right(currentspeed);  
        }  
        else  
        {  
            printf(" %d\n", direction);  
            setspeed(command);  
            if (direction == 64)  
            {  
                head(currentspeed);  
            }  
            else if (direction == 25)  
            {  
                last(currentspeed);  
            }  
        }  
    }  
}  
static void openlight(void *arg)  
{  
    gpio_set_level(INFARE, 1);  
    while (1)  
    {  

        gpio_set_level(currentcolor, 1);  
        vTaskDelay(200 / portTICK_PERIOD_MS);  
        gpio_set_level(currentcolor, 0);  
        if(currentcolor==2)  
        {  
            currentcolor=4;  
        }  
        else if (currentcolor==4)  
        {  
            currentcolor=5;  
        }  
        else if (currentcolor==5)  
        {  
            currentcolor=2;  
        }  
    }  

}  
void app_main(void)  
{  
    IRrecvInit(RECV_PIN, 3);  
    gpio_set_direction(INFARE, GPIO_MODE_OUTPUT);  
    gpio_set_direction(RED, GPIO_MODE_OUTPUT);  
    gpio_set_direction(GREEN, GPIO_MODE_OUTPUT);  
    gpio_set_direction(BLUE, GPIO_MODE_OUTPUT);  
    xTaskCreate(mcpwm_example_brushed_motor_control, "mcpwm_examlpe_brushed_motor_control", 4096, NULL, 5, NULL);  
    xTaskCreate(openlight, "openlight", 4096, NULL, 5, NULL);  
}  

What I like most about Arduino is that when we need to use third-party libraries, the extensions are much easier to find compared to VS Code. In C, you might not find them in the extension marketplace at all; you need to search online, download the C files, and include them. Finding them can be challenging. For infrared, I spent quite a long time searching last time. In Arduino, I can simply search for the library or keywords I want in the library manager and find them. It feels as simple as NuGet, haha, so I prefer using it.

Here, we can search for third-party libraries by keyword or type, which is very convenient. Some third-party libraries also come with examples that can be used directly. Overall, Arduino is a very good choice for simple entry-level projects.

Conclusion

Alright, that's it for today's first lesson on lighting an LED. If you have any questions, feel free to ask me. You can join this group to learn microcontrollers together. Later, we will also start courses on the STM32 series. For the IDE, you can find the download link in the previous blog post, which includes a Baidu Netdisk address. The group files also have it. If you need it, you can download it. Our tutorial series will continue until we can make a smart car ourselves.

Keep Exploring

Related Reading

More Articles
Recent update 5/18/2026

枝见 Zhijian: A Markdown Mind Map Editor Built with Avalonia

This article introduces Zhijian, a local mind map editor based on Avalonia, supporting blank creation, folder loading, precise onboarding guidance, macOS shortcut adaptation, outline/Markdown/mind map synchronization, node notes, thumbnails, zoom, canvas dragging, and Markdown/OPML/XMind file exchange.

Continue Reading