GPIO trong STM32: Cấu hình Input/Output với SPL

GPIO (General Purpose Input/Output) là ngoại vi cơ bản và quan trọng nhất khi bắt đầu với GPIO trong STM32 – dùng để đọc nút nhấn, điều khiển LED, tạo tín hiệu hay làm chân cho các chuẩn giao tiếp. Bài viết này hướng dẫn cấu hình GPIO bằng thư viện SPL (Standard Peripherals Firmware Library): các chế độ hoạt động, cách cấp clock RCC, struct GPIO_InitTypeDef, các hàm cơ bản và ví dụ thực hành nháy LED trên STM32F1.

Giới thiệu GPIO trong STM32 (thư viện SPL)

Trong bài này, ta cấu hình GPIO trên bộ thư viện SPL. Các hàm phục vụ việc cấu hình GPIO và cấp xung ngoại vi được định nghĩa trong stm32f10x_rcc.hstm32f10x_gpio.h. Các cấu hình được tổ chức thành các trường và định nghĩa bằng structenum.

Các thanh ghi phục vụ chức năng GPIO được tổ chức trong struct GPIO_TypeDef:

typedef struct
{
  __IO uint32_t CRL;     /* Config Register Low  (chân 0-7)  */
  __IO uint32_t CRH;     /* Config Register High (chân 8-15) */
  __IO uint32_t IDR;     /* Input Data Register  */
  __IO uint32_t ODR;     /* Output Data Register */
  __IO uint32_t BSRR;    /* Bit Set/Reset Register */
  __IO uint32_t BRR;     /* Bit Reset Register */
  __IO uint32_t LCKR;    /* Lock Register */
} GPIO_TypeDef;

Các thuộc tính của một chân GPIO được tổ chức trong struct GPIO_InitTypeDef, gồm ba trường: GPIO_Pin, GPIO_ModeGPIO_Speed:

typedef struct
{
  uint16_t GPIO_Pin;              /* Chọn chân cần cấu hình */
  GPIOSpeed_TypeDef GPIO_Speed;   /* Tốc độ đáp ứng của chân */
  GPIOMode_TypeDef  GPIO_Mode;    /* Chế độ hoạt động của chân */
} GPIO_InitTypeDef;

GPIO_Mode – các chế độ hoạt động

GPIO_Mode xác định chế độ hoạt động của chân. Dưới đây là 8 giá trị và ý nghĩa từng chế độ:

Giá trị GPIO_Mode Mô tả Giải thích
GPIO_Mode_AIN Analog Input Đầu vào analog, thường dùng cho ADC.
GPIO_Mode_IN_FLOATING Floating Input Đầu vào thả nổi, không pull-up/pull-down.
GPIO_Mode_IPD Input Pull-down Đầu vào có điện trở kéo xuống; khi không có tín hiệu, chân về mức thấp (GND).
GPIO_Mode_IPU Input Pull-up Đầu vào có điện trở kéo lên; khi không có tín hiệu, chân về mức cao (VDD).
GPIO_Mode_Out_OD Open-drain Output Đầu ra open-drain; chỉ kéo xuống mức thấp, cần điện trở kéo lên ngoài để đạt mức cao.
GPIO_Mode_Out_PP Push-pull Output Đầu ra push-pull; đạt cả mức cao và thấp, không cần phần cứng bổ sung.
GPIO_Mode_AF_OD Alternate Function Open-drain Chức năng thay thế (USART, I2C…) dùng chế độ open-drain.
GPIO_Mode_AF_PP Alternate Function Push-pull Chức năng thay thế dùng chế độ push-pull.

GPIO_Pin – chọn chân

GPIO_Pin xác định chân trong GPIOx, có dạng GPIO_Pin_x với x từ 0–15. Mỗi chân ứng với một bit trong thanh ghi 16-bit:

Macro Giá trị Macro Giá trị
GPIO_Pin_0 0x0001 GPIO_Pin_8 0x0100
GPIO_Pin_1 0x0002 GPIO_Pin_9 0x0200
GPIO_Pin_2 0x0004 GPIO_Pin_10 0x0400
GPIO_Pin_3 0x0008 GPIO_Pin_11 0x0800
GPIO_Pin_4 0x0010 GPIO_Pin_12 0x1000
GPIO_Pin_5 0x0020 GPIO_Pin_13 0x2000
GPIO_Pin_6 0x0040 GPIO_Pin_14 0x4000
GPIO_Pin_7 0x0080 GPIO_Pin_15 0x8000
GPIO_Pin_All 0xFFFF

GPIO_Speed – tốc độ đáp ứng

GPIO_Speed xác định tốc độ đáp ứng của chân, thường cấu hình kèm chế độ Output:

typedef enum
{
  GPIO_Speed_10MHz = 1,
  GPIO_Speed_2MHz,
  GPIO_Speed_50MHz
} GPIOSpeed_TypeDef;

Cấu hình RCC cấp clock cho ngoại vi

Xung clock cho ngoại vi được cấu hình bằng các hàm trong module RCC (định nghĩa trong stm32f10x_rcc.h). Hai hàm thường dùng là RCC_APB1PeriphClockCmd()RCC_APB2PeriphClockCmd() – nhận tham số thứ nhất là macro ngoại vi, tham số thứ hai là ENABLE hoặc DISABLE:

Hàm Bus Một số ngoại vi tiêu biểu
RCC_APB1PeriphClockCmd APB1 (Low Speed) TIM2–TIM7, SPI2/SPI3, USART2–5, I2C1/I2C2, USB, CAN1, PWR, DAC…
RCC_APB2PeriphClockCmd APB2 (High Speed) AFIO, GPIOA–GPIOG, ADC1/ADC2, TIM1, SPI1, USART1…

Các cổng GPIO nằm trên bus APB2. Ví dụ để cấp clock cho GPIOC:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

Các bước khởi tạo GPIO

Hàm GPIO_Init() khởi tạo GPIOx với các tham số đã thiết lập trong GPIO_InitStruct. Quy trình khởi tạo một chân GPIO gồm ba bước: (1) cấp clock, (2) tạo struct GPIO_InitTypeDef và gán tham số, (3) gọi GPIO_Init():

GPIO_InitTypeDef GPIO_InitStruct;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);   /* 1. Cấp clock */

GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_x;                /* 2. Gán tham số */
GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_xx;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_xx;

GPIO_Init(GPIOx, &GPIO_InitStruct);                     /* 3. Khởi tạo */

Các hàm cơ bản trên GPIO

Thư viện SPL hỗ trợ sẵn các hàm thao tác trên GPIO:

Hàm Chức năng
GPIO_SetBits(GPIOx, GPIO_Pin) Đặt một/nhiều chân lên mức cao (logic 1).
GPIO_ResetBits(GPIOx, GPIO_Pin) Đặt một/nhiều chân về mức thấp (logic 0).
GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) Đọc trạng thái một chân cấu hình Input. Trả về Bit_SET/Bit_RESET.
GPIO_ReadOutputDataBit(GPIOx, GPIO_Pin) Đọc trạng thái một chân cấu hình Output.
GPIO_WriteBit(GPIOx, GPIO_Pin, BitVal) Đặt trạng thái một chân theo BitVal (Bit_SET/Bit_RESET).
GPIO_ReadInputData(GPIOx) Đọc 16-bit trạng thái toàn bộ chân Input của cổng.
GPIO_ReadOutputData(GPIOx) Đọc 16-bit trạng thái toàn bộ chân Output của cổng.
GPIO_Write(GPIOx, PortVal) Ghi giá trị 16-bit cho toàn bộ cổng GPIO.
GPIO_PinLockConfig(GPIOx, GPIO_Pin) Khóa cấu hình chân đến khi reset hệ thống.
GPIO_AFIODeInit() Đặt lại toàn bộ thanh ghi AFIO về mặc định.

Ví dụ thực hành

Ví dụ 1: Nháy LED (Blink LED PC13)

Bật/tắt LED trên chân PC13 sau một khoảng delay, dùng thư viện SPL thay vì thao tác trực tiếp thanh ghi. Đầu tiên cấp clock cho GPIOC và cấu hình chân 13 ở chế độ Output Push-Pull:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Mode  = GPIO_Mode_Out_PP;
GPIOInitStruct.GPIO_Pin   = GPIO_Pin_13;
GPIOInitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIOInitStruct);

while(1)
{
    GPIO_WriteBit(GPIOC, GPIO_Pin_13, 0);
    delay(2000);
    GPIO_WriteBit(GPIOC, GPIO_Pin_13, 1);
    delay(2000);
}

Ví dụ 2: Nháy đuổi LED

Hàm GPIO_Init() cho phép cấu hình nhiều chân cùng lúc chỉ với một lần gọi – đưa các chân vào trường GPIO_Pin bằng phép OR. Ở đây cấu hình 4 chân PB5–PB8 làm Output Push-Pull:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Mode  = GPIO_Mode_Out_PP;
GPIOInitStruct.GPIO_Pin   = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8;
GPIOInitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIOInitStruct);

Hàm GPIO_Write() ghi giá trị ra cả cổng cùng lúc; bằng cách dịch bit giá trị ghi ra, ta tạo hiệu ứng nháy đuổi:

void Chasing(void)
{
    uint16_t Ledval;
    for(int k = 0; k < 7; k++)
    {
        if(!fl){ GPIO_Write(GPIOB, 0x0000); return; }
        Ledval = 0x0010;
        for(int i = 0; i < 5; i++)
        {
            Ledval = Ledval << 1;
            GPIO_Write(GPIOB, Ledval);
            Delay_Ms(50);
        }
    }
}

Các hàm GPIO còn được dùng để tạo xung, ghi dữ liệu ra chân trong các chuẩn giao tiếp, hay đọc tín hiệu điện áp trong ứng dụng ADC.

Ví dụ 3: 3 nút nhấn điều khiển 3 trạng thái LED

Dùng 3 nút nhấn (PA0, PA1, PA2) để điều khiển ba trạng thái: nháy, nháy đuổitắt toàn bộ. Các hàm đọc phím dùng GPIO_ReadInputDataBit():

uint8_t bt1(void){ return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0); }
uint8_t bt2(void){ return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1); }
uint8_t bt3(void){ return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2); }

Vấn đề: trong lúc delay hoặc đang chạy hàm nháy/nháy đuổi thì không thể đọc nút. Để khắc phục, dùng thêm thư viện setjmp.h với setjmp()/longjmp() để thay đổi luồng thực thi, kèm hàm delay_And_Check() vừa delay vừa kiểm tra phím:

#include "setjmp.h"
#define BLINKLED_MODE  1
#define CHASELED_MODE  2
#define OFF_LED_MODE   3
jmp_buf buff;

void delay_And_Check(void){
    for(int i = 0; i < 50000; i++){
        if(bt1() == 0){ while(bt1() == 0); longjmp(buff, BLINKLED_MODE); }
        if(bt2() == 0){ while(bt2() == 0); longjmp(buff, CHASELED_MODE); }
        if(bt3() == 0){ while(bt3() == 0); longjmp(buff, OFF_LED_MODE); }
        delay(100);
    }
}

Khi nhấn PA0 sẽ nháy LED, PA1 nháy đuổi, PA2 tắt các LED. setjmp() trả về 0 khi gọi lần đầu; longjmp(buff, para) nhảy về điểm setjmp với giá trị para để chuyển trạng thái ngay cả khi đang trong vòng delay.

Kết luận

Qua bài viết, bạn đã nắm được cách làm việc với GPIO trong STM32 bằng thư viện SPL: struct GPIO_TypeDefGPIO_InitTypeDef, tám chế độ GPIO_Mode (Input/Output, Push-Pull/Open-Drain, Analog, Alternate Function), cách cấp clock RCC (GPIO nằm trên APB2) và quy trình ba bước để khởi tạo một chân.

Cùng với các hàm cơ bản (GPIO_SetBits, GPIO_ResetBits, GPIO_WriteBit, GPIO_Write, GPIO_ReadInputDataBit…) và ba ví dụ nháy LED, bạn đã có nền tảng để điều khiển ngõ ra và đọc ngõ vào – bước khởi đầu cho mọi ứng dụng nhúng, từ đọc cảm biến đến các chuẩn giao tiếp trong automotive embedded.

Leave a Reply