Skip to content

Công nghệ 9 giây

“Website hướng dẫn, thử nghiệm và giải thích công nghệ theo cách dễ hiểu cho mọi người.”

Menu
  • Home
  • Công nghệ
  • STM32
  • ESP32
  • Altium Desinger
  • Kiến thức điện máy
Menu

BÀI 35 – DEBOUNCE NÚT NHẤN + EXTI + TIMER TRÊN STM32F103

Posted on August 23, 2026August 23, 2026 by congnghe9s

BÀI 35 – DEBOUNCE NÚT NHẤN + EXTI + TIMER TRÊN STM32F103

Đây là bài rất quan trọng vì từ bài này chúng ta bắt đầu kết hợp nhiều ngoại vi với nhau:

GPIO Input
   ↓
EXTI
   ↓
NVIC
   ↓
Timer
   ↓
Debounce
   ↓
Ứng dụng thực tế

Sau bài này, bạn sẽ hiểu tại sao một nút nhấn cơ khí nhấn 1 lần nhưng vi điều khiển có thể tưởng rằng bạn nhấn 5–10 lần.


1. Vấn đề: tại sao nút nhấn bị “dội”?

Ở bài 33, chúng ta dùng:

PA0 = Button
PC13 = LED

và:

PA0 → EXTI0

Khi nhấn nút:

HIGH → LOW

EXTI phát hiện cạnh xuống.

Về lý thuyết:

1 lần nhấn
    ↓
1 interrupt

Nhưng thực tế nút cơ khí không chuyển trạng thái sạch như vậy.


2. Tín hiệu thực tế của nút nhấn

Khi bạn nhấn nút, tiếp điểm kim loại có thể rung trong vài mili-giây.

Thay vì:

HIGH ────────────────
                    │
                    └──────── LOW

ta có thể nhận được:

HIGH ─────┐
          └─ LOW
              ┌─ HIGH
              └─ LOW
                  ┌─ HIGH
                  └──────── LOW

Tức là:

HIGH → LOW → HIGH → LOW → HIGH → LOW

trong một khoảng thời gian rất ngắn.

Đây gọi là:

Mechanical Bounce – hiện tượng dội tiếp điểm cơ khí.


3. Tại sao EXTI lại bị ảnh hưởng?

EXTI rất nhanh.

Nếu chúng ta cấu hình:

EXTI->FTSR |= (1 << 0);

thì mỗi cạnh HIGH → LOW đều có thể tạo interrupt.

Ví dụ một lần nhấn thực tế:

        1 lần nhấn
             │
             ▼
HIGH ────────┐
             └─ LOW      ← interrupt 1
                ┌─ HIGH
                └─ LOW   ← interrupt 2
                   ┌─ HIGH
                   └─ LOW ← interrupt 3

CPU có thể nhận:

3 interrupt

trong khi người dùng chỉ nhấn:

1 lần

4. Hậu quả

Nếu ISR của chúng ta là:

void EXTI0_IRQHandler(void)
{
    if (EXTI->PR & (1 << 0))
    {
        GPIOC->ODR ^= (1 << 13);

        EXTI->PR = (1 << 0);
    }
}

thì:

Nhấn 1 lần
      ↓
3 interrupt
      ↓
LED đảo 3 lần
      ↓
LED cuối cùng có thể quay về trạng thái cũ

Bạn sẽ thấy:

“Tôi nhấn nút nhưng LED lúc được lúc không.”

Đây không phải lỗi EXTI.

Đó là bounce.


5. Debounce là gì?

Debounce nghĩa là:

Lọc hiện tượng dội của nút nhấn để biến nhiều chuyển trạng thái rất nhanh thành một sự kiện nhấn hợp lệ duy nhất.

Ví dụ:

Tín hiệu thật:

HIGH LOW HIGH LOW HIGH LOW LOW LOW LOW
     └──── bounce ────┘

Debounce:

HIGH ───────────────── LOW
                       ↑
                  1 lần nhấn

6. Có những phương pháp debounce nào?

Có nhiều cách.

Cách 1 – Delay

delay_ms(20);

Dễ nhưng không tốt trong hệ thống lớn.


Cách 2 – Polling

Đọc nút định kỳ:

10 ms
20 ms
30 ms
40 ms
...

Cách 3 – EXTI + Timer

Đây là phương pháp chúng ta học hôm nay.

Nút nhấn
   ↓
EXTI
   ↓
không xử lý ngay
   ↓
Timer đếm khoảng 20 ms
   ↓
đọc lại nút
   ↓
nếu vẫn LOW
   ↓
xác nhận nhấn

Đây là cách rất hay vì:

EXTI phát hiện sự kiện, Timer tạo khoảng thời gian xác nhận.


7. Ý tưởng cốt lõi của bài 35

Chúng ta không làm:

Button
 ↓
EXTI
 ↓
LED

mà làm:

Button
   ↓
EXTI
   ↓
"có khả năng vừa nhấn"
   ↓
Start Timer
   ↓
20 ms
   ↓
Timer Interrupt
   ↓
đọc lại GPIO
   ↓
vẫn LOW?
   ↓
YES
   ↓
Xác nhận nhấn

Đây là kiến trúc rất quan trọng.


8. Tại sao dùng Timer?

Giả sử thời gian debounce là:

20 ms

Khi EXTI xảy ra:

t = 0 ms

chúng ta chưa biết đây có phải nhấn thật hay bounce.

Ta chờ:

t = 20 ms

rồi đọc lại.

Nếu vẫn:

PA0 = LOW

thì xác nhận:

BUTTON PRESSED

9. Sơ đồ hoạt động

                  ┌─────────────┐
                  │   BUTTON    │
                  └──────┬──────┘
                         │
                         ▼
                       PA0
                         │
                         ▼
                       EXTI0
                         │
                         ▼
                        NVIC
                         │
                         ▼
                 EXTI0_IRQHandler
                         │
                         │
                  Start Timer
                         │
                         ▼
                    20 ms delay
                         │
                         ▼
                  TIM_IRQHandler
                         │
                         ▼
                   Đọc lại PA0
                    /         \
                  LOW         HIGH
                   │            │
                   ▼            ▼
               Nhấn thật      Bounce
                   │
                   ▼
               Xử lý nút

10. Chúng ta sẽ dùng Timer nào?

Với STM32F103C8T6, chúng ta có:

TIM1
TIM2
TIM3
TIM4

Trong bài này chúng ta dùng:

TIM2

vì nó rất phù hợp để học Timer interrupt.


11. Timer hoạt động như thế nào?

Timer có một bộ đếm:

CNT

Nó tăng theo clock.

Ví dụ:

CNT = 0
CNT = 1
CNT = 2
CNT = 3
...

Khi:

CNT == ARR

Timer tạo sự kiện update.

Thanh ghi:

TIM2->CNT
TIM2->PSC
TIM2->ARR

12. Ba thanh ghi Timer cần nhớ

PSC

TIM2->PSC

Prescaler.

Dùng để chia clock.


ARR

TIM2->ARR

Auto Reload Register.

Xác định giá trị đếm tối đa.


CNT

TIM2->CNT

Counter.

Giá trị hiện tại của bộ đếm.


13. Công thức Timer

Tần số Timer:

[
f_{timer} = \frac{f_{clock}}{PSC+1}
]

Thời gian một lần đếm:

[
T_{tick} = \frac{PSC+1}{f_{clock}}
]

Thời gian overflow:

[
T = \frac{(PSC+1)(ARR+1)}{f_{clock}}
]


14. Ví dụ tạo tick 1 ms

Giả sử Timer clock:

72 MHz

Ta muốn:

1 tick = 1 ms

Chọn:

PSC = 7199

Khi đó:

[
72,MHz / 7200 = 10,kHz
]

Một tick:

[
1/10000 = 100\mu s
]

Nếu:

ARR = 9

thì:

[
10 × 100\mu s = 1ms
]

Vậy:

TIM2->PSC = 7199;
TIM2->ARR = 9;

sẽ tạo update event khoảng:

1 ms

15. Tạo debounce 20 ms

Chúng ta có thể cho Timer chạy với chu kỳ:

1 ms

Sau đó đếm:

20 lần

Ví dụ:

volatile uint8_t debounce_counter = 0;

Timer:

1 ms
 ↓
counter = 1
 ↓
1 ms
 ↓
counter = 2
...
 ↓
20 ms
 ↓
counter = 20

16. Nhưng có cách đơn giản hơn

Trong bài đầu tiên, chúng ta có thể cấu hình:

Timer = 20 ms

mỗi khi EXTI xảy ra:

Start TIM2

Timer hết 20 ms:

TIM2_IRQHandler()

sau đó:

đọc PA0

Đây là cách dễ hiểu nhất để học.


17. Trạng thái hệ thống

Chúng ta sẽ dùng biến:

volatile uint8_t debounce_active = 0;

Ý nghĩa:

0 = không debounce
1 = đang chờ debounce

Khi EXTI xảy ra:

if (debounce_active == 0)
{
    debounce_active = 1;
    Start_Timer();
}

Nếu bounce tiếp tục xảy ra:

debounce_active == 1

thì bỏ qua.


18. Đây là điểm rất hay

Ví dụ:

t = 0 ms
EXTI
 ↓
debounce_active = 1
 ↓
Timer chạy

Sau đó:

t = 2 ms
bounce → EXTI

Nhưng:

debounce_active == 1

nên:

Bỏ qua

Tiếp tục:

t = 5 ms
bounce → EXTI

vẫn:

Bỏ qua

Đến:

t = 20 ms

Timer kiểm tra trạng thái thật.


19. Code cấu hình GPIO

Trước tiên:

void GPIO_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;

    /* PA0 Input Pull-up */
    GPIOA->CRL &= ~(0xF << 0);
    GPIOA->CRL |=  (0x8 << 0);

    GPIOA->ODR |= (1 << 0);

    /* PC13 Output Push-Pull */
    GPIOC->CRH &= ~(0xF << 20);
    GPIOC->CRH |=  (0x2 << 20);
}

20. Cấu hình EXTI

void EXTI_Init(void)
{
    EXTI->IMR |= (1 << 0);

    /* Falling Edge */
    EXTI->FTSR |= (1 << 0);

    NVIC_SetPriority(EXTI0_IRQn, 1);

    NVIC_EnableIRQ(EXTI0_IRQn);
}

Vì:

PA0 Pull-up

nên khi nhấn:

HIGH → LOW

do đó dùng:

FTSR

21. Cấu hình TIM2

Bây giờ phần quan trọng.

Chúng ta cần bật clock:

RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

Sau đó:

TIM2->PSC = 7199;
TIM2->ARR = 199;

Với clock 72 MHz:

72 MHz / 7200 = 10 kHz

Mỗi tick:

100 µs

200 tick:

20 ms

Vậy:

PSC = 7199
ARR = 199

→ khoảng:

20 ms

22. Cho phép Timer tạo interrupt

Thanh ghi:

TIM2->DIER

Dùng:

DMA/Interrupt Enable Register

Cho phép update interrupt:

TIM2->DIER |= TIM_DIER_UIE;

23. Bật TIM2 trong NVIC

NVIC_SetPriority(TIM2_IRQn, 2);
NVIC_EnableIRQ(TIM2_IRQn);

Chúng ta đặt:

EXTI0 = priority 1
TIM2  = priority 2

Như vậy EXTI có mức ưu tiên cao hơn Timer.


24. Nhưng chưa Start Timer

Đây là điểm quan trọng.

Nếu chúng ta:

TIM2->CR1 |= TIM_CR1_CEN;

ngay từ đầu:

Timer chạy liên tục

không phù hợp với debounce hiện tại.

Chúng ta muốn:

Không có nút nhấn
→ Timer STOP

Có nút nhấn
→ Timer START

20 ms
→ Timer STOP

25. Hàm Start Timer

void Timer2_Start(void)
{
    TIM2->CNT = 0;

    TIM2->SR &= ~TIM_SR_UIF;

    TIM2->CR1 |= TIM_CR1_CEN;
}

26. Hàm Stop Timer

void Timer2_Stop(void)
{
    TIM2->CR1 &= ~TIM_CR1_CEN;
}

27. EXTI ISR

Bây giờ chúng ta viết:

volatile uint8_t debounce_active = 0;

ISR:

void EXTI0_IRQHandler(void)
{
    if (EXTI->PR & (1 << 0))
    {
        if (debounce_active == 0)
        {
            debounce_active = 1;

            Timer2_Start();
        }

        EXTI->PR = (1 << 0);
    }
}

Chú ý:

Không đảo LED ở đây.

EXTI chỉ nói:

“Có khả năng nút vừa được nhấn.”


28. Timer ISR

Sau khoảng 20 ms:

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF)
    {
        TIM2->SR &= ~TIM_SR_UIF;

        Timer2_Stop();

        debounce_active = 0;

        if ((GPIOA->IDR & (1 << 0)) == 0)
        {
            GPIOC->ODR ^= (1 << 13);
        }
    }
}

Đây mới là nơi xác nhận.


29. Logic cực kỳ quan trọng

Chúng ta có:

EXTI
 ↓
Phát hiện thay đổi
 ↓
Start Timer
 ↓
20 ms
 ↓
Timer interrupt
 ↓
Đọc PA0
 ↓
PA0 vẫn LOW?
 ├── YES → Nhấn thật
 └── NO  → Bounce

Đây là debounce bằng xác nhận trạng thái sau một khoảng thời gian.


30. Code hoàn chỉnh

#include "stm32f10x.h"

volatile uint8_t debounce_active = 0;

void GPIO_Init(void)
{
    /* GPIOA + GPIOC + AFIO */
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;

    /* PA0 = Input Pull-up */
    GPIOA->CRL &= ~(0xF << 0);
    GPIOA->CRL |=  (0x8 << 0);

    GPIOA->ODR |= (1 << 0);

    /* PC13 = Output Push-Pull 2 MHz */
    GPIOC->CRH &= ~(0xF << 20);
    GPIOC->CRH |=  (0x2 << 20);
}

void EXTI_Init(void)
{
    /* PA0 -> EXTI0 */

    EXTI->IMR |= (1 << 0);

    /* Falling Edge */
    EXTI->FTSR |= (1 << 0);

    NVIC_SetPriority(EXTI0_IRQn, 1);
    NVIC_EnableIRQ(EXTI0_IRQn);
}

void TIM2_Init(void)
{
    /* Enable TIM2 clock */
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

    /*
       72 MHz / 7200 = 10 kHz
       1 tick = 100 us

       ARR = 199
       200 ticks = 20 ms
    */

    TIM2->PSC = 7199;
    TIM2->ARR = 199;

    TIM2->CNT = 0;

    /* Update interrupt enable */
    TIM2->DIER |= TIM_DIER_UIE;

    NVIC_SetPriority(TIM2_IRQn, 2);
    NVIC_EnableIRQ(TIM2_IRQn);

    /* Timer initially stopped */
    TIM2->CR1 &= ~TIM_CR1_CEN;
}

void Timer2_Start(void)
{
    TIM2->CNT = 0;

    TIM2->SR &= ~TIM_SR_UIF;

    TIM2->CR1 |= TIM_CR1_CEN;
}

void Timer2_Stop(void)
{
    TIM2->CR1 &= ~TIM_CR1_CEN;
}

void EXTI0_IRQHandler(void)
{
    if (EXTI->PR & (1 << 0))
    {
        if (debounce_active == 0)
        {
            debounce_active = 1;

            Timer2_Start();
        }

        /* Clear EXTI pending */
        EXTI->PR = (1 << 0);
    }
}

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF)
    {
        /* Clear Timer flag */
        TIM2->SR &= ~TIM_SR_UIF;

        /* Stop Timer */
        Timer2_Stop();

        /* End debounce */
        debounce_active = 0;

        /*
           Confirm button state
           PA0 = LOW → valid press
        */
        if ((GPIOA->IDR & (1 << 0)) == 0)
        {
            GPIOC->ODR ^= (1 << 13);
        }
    }
}

int main(void)
{
    GPIO_Init();

    EXTI_Init();

    TIM2_Init();

    while (1)
    {
    }
}

31. Hãy chú ý volatile

Chúng ta khai báo:

volatile uint8_t debounce_active = 0;

Tại sao?

Bởi vì biến này được sử dụng giữa:

main
EXTI ISR
Timer ISR

Interrupt có thể thay đổi giá trị của nó bất kỳ lúc nào.

Vì vậy:

volatile

là phù hợp.


32. Tại sao không dùng delay_ms(20)?

Một người mới học có thể viết:

void EXTI0_IRQHandler(void)
{
    delay_ms(20);

    if (...)
    {
        GPIOC->ODR ^= (1 << 13);
    }
}

Có thể hoạt động trong một project rất đơn giản, nhưng không phải cách chúng ta muốn xây dựng firmware tốt.

Vấn đề:

CPU
 ↓
EXTI ISR
 ↓
delay 20 ms
 ↓
CPU bị giữ trong ISR

Trong 20 ms đó, hệ thống khó đáp ứng tốt các interrupt/tác vụ khác.


33. Cách Timer tốt hơn

Chúng ta làm:

EXTI ISR
    ↓
Start Timer
    ↓
Thoát ISR ngay
    ↓
CPU làm việc khác
    ↓
20 ms
    ↓
Timer ISR
    ↓
Kiểm tra nút

Điểm quan trọng:

Không giữ CPU trong EXTI ISR để chờ debounce.

Đây là tư duy firmware rất quan trọng.


34. Nhưng code trên vẫn còn một điểm cần cải thiện

Bạn có thể hỏi:

“Nếu nút vẫn đang được giữ, sau 20 ms Timer kiểm tra thấy LOW rồi đảo LED. Vậy khi giữ nút lâu thì sao?”

Trong cấu hình chỉ dùng Falling Edge, chúng ta sẽ không liên tục nhận Falling Edge chỉ vì giữ nút LOW.

Vì vậy:

Nhấn
 ↓
LOW
 ↓
20 ms
 ↓
xác nhận
 ↓
LED đổi

Sau đó giữ nút:

LOW LOW LOW LOW LOW

không tạo thêm Falling Edge.

Đây là điều chúng ta mong muốn.


35. Khi nhả nút

Nhả nút:

LOW → HIGH

Vì chúng ta chỉ dùng:

FTSR

nên:

LOW → HIGH

không tạo interrupt.

Lần nhấn tiếp theo:

HIGH → LOW

lại tạo Falling Edge.

Do đó:

Nhấn 1 → LED đổi
Nhả
Nhấn 2 → LED đổi
Nhả
Nhấn 3 → LED đổi

36. Tại sao 20 ms?

Không phải con số bắt buộc.

Có thể:

5 ms
10 ms
20 ms
30 ms
50 ms

Tùy nút.

Thông thường:

10–20 ms

là khoảng thời gian rất hay dùng cho nút nhấn cơ khí.

Trong bài học, chúng ta dùng:

20 ms

để dễ quan sát và hiểu nguyên lý.


37. Một tư duy rất quan trọng

Bạn vừa học được mô hình:

EVENT
 ↓
EXTI
 ↓
TIMER
 ↓
WAIT
 ↓
VERIFY
 ↓
ACTION

Đây là một pattern rất phổ biến trong embedded programming.

Không chỉ dùng cho nút nhấn.

Nó còn có thể dùng cho:

  • cảm biến;
  • tín hiệu công tắc;
  • encoder;
  • tín hiệu ngoài;
  • giao tiếp thiết bị;
  • phát hiện trạng thái ổn định.

38. So sánh 3 cách debounce

Phương pháp Ưu điểm Nhược điểm
Delay Rất dễ Block CPU
Polling Đơn giản Phải đọc liên tục
EXTI + Timer Không block ISR Phức tạp hơn

Với firmware thực tế, EXTI + Timer là một kiến trúc rất đáng biết.


39. Một lỗi rất quan trọng với Timer

Trong:

TIM2_IRQHandler()

chúng ta phải xử lý:

TIM2->SR

Ví dụ:

if (TIM2->SR & TIM_SR_UIF)
{
    TIM2->SR &= ~TIM_SR_UIF;
}

Nếu quên xử lý cờ:

UIF vẫn = 1

có thể dẫn đến interrupt tiếp tục được xử lý.


40. Kiến trúc hoàn chỉnh chúng ta vừa xây dựng

Bạn hãy ghi nhớ sơ đồ này:

                BUTTON
                   │
                   ▼
                  PA0
                   │
                   ▼
                 EXTI0
                   │
                   ▼
                  NVIC
                   │
                   ▼
           EXTI0_IRQHandler
                   │
                   ▼
             Start TIM2
                   │
                   ▼
               CPU tiếp tục
               làm việc khác
                   │
                   │
                20 ms
                   │
                   ▼
           TIM2_IRQHandler
                   │
                   ▼
              Read PA0
             /         \
          LOW          HIGH
           │             │
           ▼             ▼
       Nhấn thật       Bounce
           │
           ▼
       Toggle LED

41. Đây là bước tiến lớn so với Bài 32

Bạn đã đi qua:

Bài 22
GPIO cơ bản
   ↓
Bài 23
GPIO Input
   ↓
Bài 24–30
GPIO Register
   ↓
Bài 31
GPIO Driver
   ↓
Bài 32
Pull-up / Pull-down
   ↓
Bài 33
EXTI
   ↓
Bài 34
NVIC
   ↓
Bài 35
EXTI + Timer + Debounce

Bạn đang bắt đầu chuyển từ:

lập trình từng thanh ghi riêng lẻ

sang:

thiết kế một hệ thống firmware gồm nhiều peripheral phối hợp với nhau.


42. Bài tập thực hành cho Blue Pill

Với board STM32F103C8T6 của bạn, hãy làm:

PA0 ───── Button ───── GND

PC13 ───── LED onboard

Bài tập 1

Nhấn nút một lần:

LED đổi trạng thái đúng 1 lần

Bài tập 2

Nhấn giữ nút 3 giây:

LED chỉ đổi 1 lần

Bài tập 3

Nhấn nhanh 10 lần:

LED phải đổi 10 lần

Bài tập 4

Thay:

TIM2->ARR = 199;

bằng:

TIM2->ARR = 99;

Bạn đang giảm debounce từ khoảng:

20 ms → 10 ms

Quan sát xem nút còn hoạt động ổn định không.


43. Kiến thức cần nhớ sau Bài 35

Nếu chỉ cần nhớ 7 dòng, hãy nhớ:

1. Nút cơ khí bị bounce.
2. EXTI có thể nhìn bounce thành nhiều interrupt.
3. Không nên delay lâu trong ISR.
4. EXTI dùng để phát hiện sự kiện.
5. Timer dùng để tạo khoảng thời gian debounce.
6. Sau khoảng thời gian đó phải đọc lại GPIO.
7. ISR nên ngắn và nhanh.

Và công thức tư duy:

EXTI = "Có chuyện xảy ra!"

Timer = "Hãy chờ một khoảng thời gian!"

GPIO = "Kiểm tra xem chuyện đó có thật không!"

ISR = "Xử lý thật nhanh!"

Bài 36 – tiếp theo

Sau Bài 35, bài rất hợp lý tiếp theo là:

BÀI 36 – TIMER INTERRUPT TRÊN STM32F103: TỰ TẠO HỆ THỐNG millis() 1 ms

Ở bài đó, chúng ta sẽ không dùng delay(), mà tự xây dựng một bộ đếm thời gian kiểu:

volatile uint32_t millis = 0;

để có thể viết:

if (millis - last_time >= 1000)
{
    // 1 giây đã trôi qua
}

Đây là bước rất quan trọng để bạn tiến tới viết firmware STM32 theo kiểu non-blocking, sau này cực kỳ hữu ích khi bạn làm robot, stepper 28BYJ-48, cảm biến, UART, LCD và nhiều tác vụ chạy đồng thời.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent Posts

  • Bạn có thể hướng dẫn tôi quy trình sửa chữa một mainboard laptop cũng như mainboard của máy tính để bàn không?
  • Có những mức nguồn DC nào trên một bo mạch mainboard laptop?Kỹ thuật đốt dòng để kiểm tra nguồn laptop được thực hiện như thế nào?
  • bài tiếp theo hợp lý là: BÀI 63 – SENSOR FUSION THỰC CHIẾN: ENCODER + IMU MPU6050 + EKF + ODOMETRY X,Y,θ TRÊN STM32F103
  • BÀI 62 – EKF (EXTENDED KALMAN FILTER) CHO ROBOT 2 BÁNH: FUSION ENCODER + IMU + ƯỚC LƯỢNG X, Y, θ, V, ω
  • BÀI 61 – KALMAN FILTER CHO ENCODER + IMU: ƯỚC LƯỢNG TỐC ĐỘ, GÓC θ VÀ SENSOR FUSION

Recent Comments

No comments to show.

Archives

  • September 2026
  • August 2026
  • July 2026
  • June 2026
  • May 2026
  • February 2026

Categories

  • Altium Desinger
  • Công nghệ
  • ESP32
  • Khác
  • Kiến thức điện máy
  • STM32
Live 3D Globes Visitor
©2026 Công nghệ 9 giây | Design: Newspaperly WordPress Theme