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 36 – TIMER INTERRUPT TRÊN STM32F103: TỰ TẠO HỆ THỐNG millis() 1 ms

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

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

Đây là một bài **rất quan trọng** trong quá trình học STM32 của bạn.

Từ Bài 35, chúng ta đã dùng Timer để debounce nút nhấn. Bây giờ chúng ta sẽ tiến thêm một bước:

> **Tự xây dựng một bộ đếm thời gian 1 ms bằng Timer Interrupt, tương tự ý tưởng của `millis()` trong Arduino.**

Sau bài này, bạn sẽ hiểu rất rõ:

“`text
Clock STM32
↓
TIM2
↓
Interrupt mỗi 1 ms
↓
millis++
↓
Chương trình chính sử dụng thời gian
“`

Và quan trọng nhất:

> **Không cần `delay()` để tạo các khoảng thời gian trong chương trình.**

—

# 1. Vấn đề của `delay()`

Ví dụ bạn muốn LED nhấp nháy mỗi 1 giây.

Cách đơn giản:

“`c
while (1)
{
GPIOC->ODR ^= (1 << 13);

delay_ms(1000);
}
“`

Nhưng trong thời gian:

“`text
delay_ms(1000)
“`

CPU gần như chỉ:

“`text
CHỜ
CHỜ
CHỜ
CHỜ
…
“`

Nếu chương trình còn phải làm:

“`text
LCD
UART
Button
Sensor
Motor
ADC
“`

thì cách này rất bất tiện.

—

# 2. Cách tốt hơn: non-blocking

Thay vì:

“`c
delay_ms(1000);
“`

ta hỏi:

> “Đã đủ 1000 ms chưa?”

Ví dụ:

“`c
if (millis – last_time >= 1000)
{
last_time = millis;

LED_Toggle();
}
“`

CPU không phải đứng chờ.

Nó có thể tiếp tục:

“`text
LCD
↓
Button
↓
Sensor
↓
Motor
↓
UART
↓
kiểm tra millis
↓
lặp lại
“`

Đây gọi là:

> **Non-blocking programming**

—

# 3. `millis()` thực chất là gì?

Trong Arduino, bạn thường thấy:

“`c
millis();
“`

Ví dụ:

“`c
if (millis() – lastTime >= 1000)
{
lastTime = millis();

digitalWrite(LED, !digitalRead(LED));
}
“`

`millis()` trả về:

> Số mili-giây đã trôi qua kể từ khi hệ thống bắt đầu chạy.

Ví dụ:

“`text
0 ms
1 ms
2 ms
3 ms
…
1000 ms
1001 ms
…
“`

—

# 4. STM32F103 không tự nhiên có `millis()`

Đây là điều rất quan trọng.

Nếu bạn lập trình STM32F103 trực tiếp bằng thanh ghi:

“`c
STM32F103
“`

thì không có một thanh ghi phần cứng tên:

“`c
millis
“`

Chúng ta phải **tự tạo nó**.

Ý tưởng:

“`c
volatile uint32_t millis = 0;
“`

Sau mỗi 1 ms:

“`c
millis++;
“`

—

# 5. Ai làm nhiệm vụ tăng `millis`?

Chính là:

# TIM2 + Interrupt

Sơ đồ:

“`text
72 MHz
│
▼
TIM2
│
mỗi 1 ms
│
▼
TIM2 interrupt
│
▼
TIM2_IRQHandler()
│
▼
millis++
“`

—

# 6. Tại sao chọn TIM2?

STM32F103C8T6 có:

“`text
TIM1
TIM2
TIM3
TIM4
“`

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

“`text
TIM2
“`

TIM2 rất tiện để học vì nó là **general-purpose timer** và không cần sử dụng chân GPIO để tạo tick nội bộ.

—

# 7. Timer clock là bao nhiêu?

Với cấu hình STM32F103 phổ biến:

“`text
SYSCLK = 72 MHz
“`

Nhưng cần cẩn thận:

> Clock cấp cho Timer không phải lúc nào cũng đơn giản là SYSCLK.

Với cấu hình APB1 thông dụng:

“`text
SYSCLK = 72 MHz
APB1 = 36 MHz
“`

Do APB1 prescaler khác 1, clock Timer trên APB1 được nhân 2:

“`text
TIM2 clock = 72 MHz
“`

Vì vậy trong cấu hình chuẩn 72 MHz:

“`text
TIM2CLK = 72 MHz
“`

—

# 8. Công thức Timer

Nhắc lại công thức:

[
f_{timer}=
\frac{f_{TIM}}
{(PSC+1)(ARR+1)}
]

Muốn interrupt mỗi:

“`text
1 ms
“`

thì:

[
f = 1000Hz
]

Ta cần:

[
\frac{72,000,000}
{(PSC+1)(ARR+1)}
=1000
]

Hay:

[
(PSC+1)(ARR+1)=72000
]

—

# 9. Chọn Prescaler

Ta chọn:

“`c
PSC = 7199;
“`

Khi đó:

[
72,MHz / 7200 = 10,kHz
]

Tức Timer đếm:

“`text
10.000 tick / giây
“`

Một tick:

[
1/10000 = 100\mu s
]

—

# 10. Chọn ARR

Muốn tạo:

“`text
1 ms
“`

mà mỗi tick:

“`text
100 µs
“`

thì cần:

“`text
10 tick
“`

Do ARR tính từ 0:

“`c
ARR = 9;
“`

Vậy:

“`c
TIM2->PSC = 7199;
TIM2->ARR = 9;
“`

Tạo:

“`text
10 × 100 µs = 1 ms
“`

—

# 11. Đây là hai con số bạn cần nhớ

“`c
TIM2->PSC = 7199;
TIM2->ARR = 9;
“`

với:

“`text
TIM2 clock = 72 MHz
“`

ta có:

“`text
1 interrupt ≈ 1 ms
“`

—

# 12. Bật clock cho TIM2

TIM2 nằm trên APB1.

Vì vậy:

“`c
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
“`

Đây là bước đầu tiên.

Nếu quên:

“`text
TIM2 không có clock
↓
Timer không chạy
↓
Không có interrupt
↓
millis không tăng
“`

—

# 13. Cấu hình Prescaler và Auto Reload

“`c
TIM2->PSC = 7199;
TIM2->ARR = 9;
“`

Sau đó:

“`c
TIM2->CNT = 0;
“`

đưa bộ đếm về 0.

—

# 14. Cho phép Update Interrupt

Thanh ghi:

“`c
TIM2->DIER
“`

là:

> DMA/Interrupt Enable Register

Ta bật Update Interrupt:

“`c
TIM2->DIER |= TIM_DIER_UIE;
“`

Ý nghĩa:

“`text
Timer overflow/update
↓
Cho phép tạo interrupt
↓
CPU có thể nhận interrupt
“`

—

# 15. Bật NVIC

Đây là phần chúng ta học ở Bài 34.

“`c
NVIC_EnableIRQ(TIM2_IRQn);
“`

Có thể đặt priority:

“`c
NVIC_SetPriority(TIM2_IRQn, 2);
“`

Sau đó:

“`c
NVIC_EnableIRQ(TIM2_IRQn);
“`

—

# 16. Start Timer

Cuối cùng:

“`c
TIM2->CR1 |= TIM_CR1_CEN;
“`

`CEN`:

> Counter Enable

Khi:

“`text
CEN = 1
“`

Timer bắt đầu chạy.

—

# 17. Timer sẽ hoạt động như thế này

“`text
CNT = 0
↓
1
↓
2
↓
3
↓
…
↓
9
↓
Update Event
↓
CNT quay về 0
↓
Interrupt
“`

Chu kỳ:

“`text
1 ms
“`

—

# 18. ISR của TIM2

Chúng ta viết:

“`c
volatile uint32_t millis = 0;

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

millis++;
}
}
“`

Mỗi lần Timer tạo interrupt:

“`text
millis++
“`

—

# 19. Kết quả

Sau khi chạy:

“`text
t = 0 ms
millis = 0

t = 1 ms
millis = 1

t = 2 ms
millis = 2

t = 3 ms
millis = 3

…

t = 1000 ms
millis = 1000
“`

Vậy chúng ta vừa **tự xây dựng `millis()`**.

—

# 20. Tại sao phải dùng `volatile`?

Ta khai báo:

“`c
volatile uint32_t millis = 0;
“`

vì:

“`text
main()
│
├── đọc millis
│
└── ISR thay đổi millis
“`

Giá trị có thể thay đổi bất kỳ lúc nào do interrupt.

Nếu không dùng `volatile`, compiler có thể tối ưu hóa việc đọc biến theo cách không phù hợp với tình huống này.

Vì vậy:

“`c
volatile
“`

rất quan trọng.

—

# 21. Chương trình đầu tiên

Chúng ta có thể viết:

“`c
#include “stm32f10x.h”

volatile uint32_t millis = 0;

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

/* 72 MHz / 7200 = 10 kHz */
TIM2->PSC = 7199;

/* 10 ticks = 1 ms */
TIM2->ARR = 9;

TIM2->CNT = 0;

/* Enable update interrupt */
TIM2->DIER |= TIM_DIER_UIE;

/* NVIC */
NVIC_SetPriority(TIM2_IRQn, 2);
NVIC_EnableIRQ(TIM2_IRQn);

/* Start timer */
TIM2->CR1 |= TIM_CR1_CEN;
}

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

millis++;
}
}

int main(void)
{
TIM2_Init();

while (1)
{
}
}
“`

Đây là **bộ đếm thời gian 1 ms tối thiểu**.

—

# 22. Nhưng `millis` chưa làm được gì?

Hiện tại:

“`c
millis++;
“`

nhưng:

“`c
while (1)
{
}
“`

chưa sử dụng nó.

Bây giờ chúng ta sẽ dùng `millis`.

—

# 23. Điều khiển LED mỗi 1 giây

Giả sử:

“`text
PC13 = LED onboard
“`

Ta có:

“`c
uint32_t last_time = 0;
“`

Trong `while`:

“`c
if (millis – last_time >= 1000)
{
last_time = millis;

GPIOC->ODR ^= (1 << 13);
}
“`

Kết quả:

“`text
1 giây
↓
LED toggle

1 giây
↓
LED toggle

1 giây
↓
LED toggle
“`

—

# 24. Đây chính là non-blocking

Chúng ta không viết:

“`c
delay_ms(1000);
“`

mà viết:

“`c
if (millis – last_time >= 1000)
“`

CPU có thể làm nhiều việc khác.

Ví dụ:

“`c
while (1)
{
Read_Button();

Read_ADC();

Update_LCD();

Process_UART();

if (millis – last_time >= 1000)
{
last_time = millis;

LED_Toggle();
}
}
“`

Không có:

“`c
delay()
“`

—

# 25. Đây là một bước cực kỳ lớn

Bạn vừa chuyển từ:

“`text
Blocking
“`

sang:

“`text
Non-blocking
“`

Ví dụ blocking:

“`c
LED_ON();
delay_ms(1000);
LED_OFF();
delay_ms(1000);
“`

CPU bị giữ trong delay.

Còn non-blocking:

“`c
if (millis – last >= 1000)
{
last = millis;

LED_Toggle();
}
“`

CPU luôn có thể tiếp tục xử lý.

—

# 26. Chạy nhiều tác vụ cùng lúc

Đây là lúc `millis()` thực sự trở nên mạnh.

Ví dụ:

“`c
uint32_t t_led = 0;
uint32_t t_sensor = 0;
uint32_t t_lcd = 0;
“`

Trong `while`:

“`c
if (millis – t_led >= 500)
{
t_led = millis;

LED_Toggle();
}

if (millis – t_sensor >= 100)
{
t_sensor = millis;

Read_Sensor();
}

if (millis – t_lcd >= 1000)
{
t_lcd = millis;

LCD_Update();
}
“`

Ta có:

“`text
LED → 500 ms
Sensor → 100 ms
LCD → 1000 ms
“`

Tất cả chạy trong cùng một `while`.

—

# 27. Không cần 3 Timer

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

Bạn có:

“`text
1 Timer
↓
1 ms system tick
↓
nhiều software timer
“`

Ví dụ:

“`text
TIM2
│
└── millis
│
├── LED timer
├── Sensor timer
├── LCD timer
├── Button timer
├── Motor timer
└── UART timeout
“`

Đây là kiến trúc rất phổ biến.

—

# 28. `millis()` giống như một chiếc đồng hồ hệ thống

Hãy tưởng tượng:

“`text
millis = 123456
“`

nghĩa là hệ thống đã chạy khoảng:

“`text
123.456 giây
“`

Từ đó mọi module có thể sử dụng cùng một mốc thời gian.

—

# 29. Cách kiểm tra thời gian đúng

Bạn nên viết:

“`c
if (millis – last_time >= interval)
“`

chứ không nên viết:

“`c
if (millis >= last_time + interval)
“`

Tại sao?

Vì `uint32_t` sẽ **overflow** sau một khoảng thời gian rất dài.

—

# 30. Overflow của `uint32_t`

`uint32_t` có giá trị tối đa:

“`text
4,294,967,295
“`

Nếu `millis` tiếp tục tăng:

“`text
4,294,967,295
↓
0
“`

Đây gọi là:

> **Integer overflow / wrap-around**

Nếu bạn viết:

“`c
millis – last_time
“`

với kiểu `uint32_t`, phép tính modulo giúp cách kiểm tra khoảng thời gian vẫn hoạt động đúng trong điều kiện khoảng thời gian cần đo nhỏ hơn chu kỳ wrap-around.

Đây là lý do pattern:

“`c
if ((uint32_t)(millis – last_time) >= interval)
“`

rất tốt.

—

# 31. Đây là pattern nên học thuộc

“`c
uint32_t last_time = 0;

if ((uint32_t)(millis – last_time) >= 1000)
{
last_time = millis;

// Task
}
“`

Đây là một trong những pattern quan trọng nhất trong embedded programming.

—

# 32. Ví dụ Timer 500 ms

“`c
uint32_t last_led = 0;

while (1)
{
if ((uint32_t)(millis – last_led) >= 500)
{
last_led = millis;

GPIOC->ODR ^= (1 << 13);
}
}
“`

—

# 33. Ví dụ đọc cảm biến mỗi 100 ms

“`c
uint32_t last_sensor = 0;

while (1)
{
if ((uint32_t)(millis – last_sensor) >= 100)
{
last_sensor = millis;

Sensor_Read();
}
}
“`

—

# 34. Ví dụ LCD mỗi 1 giây

“`c
uint32_t last_lcd = 0;

while (1)
{
if ((uint32_t)(millis – last_lcd) >= 1000)
{
last_lcd = millis;

LCD_Update();
}
}
“`

—

# 35. Một `millis` có thể điều khiển cả hệ thống

Ví dụ firmware:

“`c
while (1)
{
if ((uint32_t)(millis – t_led) >= 500)
{
t_led = millis;
LED_Toggle();
}

if ((uint32_t)(millis – t_sensor) >= 50)
{
t_sensor = millis;
Sensor_Read();
}

if ((uint32_t)(millis – t_motor) >= 10)
{
t_motor = millis;
Motor_Task();
}

if ((uint32_t)(millis – t_lcd) >= 1000)
{
t_lcd = millis;
LCD_Update();
}
}
“`

Ta có:

“`text
Motor → 10 ms
Sensor → 50 ms
LED → 500 ms
LCD → 1 s
“`

mà chỉ cần:

“`text
TIM2
“`

làm system tick.

—

# 36. Một vấn đề: ISR chạy mỗi 1 ms

Có nghĩa là:

“`text
1000 interrupt / giây
“`

Điều này có đáng lo không?

Với STM32F103 chạy 72 MHz:

“`text
1000 interrupt/s
“`

thường là hoàn toàn khả thi nếu ISR rất ngắn.

ISR của chúng ta chỉ:

“`c
millis++;
“`

và xử lý cờ.

Do đó thời gian CPU sử dụng rất nhỏ.

—

# 37. ISR tuyệt đối không nên làm việc nặng

Không nên:

“`c
void TIM2_IRQHandler(void)
{
LCD_Print(“Hello”);

ADC_ReadManyTimes();

Motor_LongCalculation();

Delay();

millis++;
}
“`

Nên:

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

millis++;
}
}
“`

**ISR càng ngắn càng tốt.**

—

# 38. Một kiến trúc tốt hơn

Ta có thể dùng Timer ISR để tạo:

“`text
millis
“`

nhưng mọi công việc thực tế nằm ở `main()`:

“`text
TIM2 ISR
↓
millis++
↓
main()
├── LED task
├── Button task
├── Sensor task
├── Motor task
└── LCD task
“`

Đây là kiến trúc rất dễ mở rộng.

—

# 39. Tích hợp với Bài 35

Bạn có thể thấy Bài 35 và Bài 36 liên kết với nhau rất đẹp.

Bài 35:

“`text
EXTI
↓
Timer
↓
Debounce
“`

Bài 36:

“`text
Timer
↓
1 ms tick
↓
millis
↓
software timers
“`

Sau này chúng ta có thể viết debounce mà **không cần start/stop Timer cho từng nút**.

Ví dụ:

“`c
if (millis – button_time >= 20)
{
// kiểm tra nút
}
“`

Đây là cách tiếp cận rất mạnh.

—

# 40. Thậm chí nhiều nút

Ví dụ:

“`text
PA0 → Button 1
PA1 → Button 2
PA2 → Button 3
“`

Ta có thể quản lý:

“`text
button1_time
button2_time
button3_time
“`

với cùng một:

“`text
millis
“`

Không cần:

“`text
TIM2 → Button1
TIM3 → Button2
TIM4 → Button3
“`

—

# 41. Toàn bộ kiến trúc hiện tại

Sau Bài 36:

“`text
STM32F103
│
┌──────────┴──────────┐
│ │
EXTI TIM2
│ │
▼ ▼
NVIC 1 ms interrupt
│ │
▼ ▼
Button event millis++
│ │
└──────────┬──────────┘
▼
main()
│
┌─────────────┼─────────────┐
▼ ▼ ▼
LED task Sensor task Motor task
│ │ │
500 ms 100 ms 10 ms
“`

Đây chính là nền móng cho một **firmware multitasking kiểu đơn giản** mà chưa cần RTOS.

—

# 42. Code hoàn chỉnh: TIM2 + millis + LED

Đây là phiên bản bạn nên tự gõ và chạy trên Blue Pill:

“`c
#include “stm32f10x.h”

volatile uint32_t millis = 0;

void GPIO_Init(void)
{
/* Enable GPIOC clock */
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;

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

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

/*
TIM2 clock = 72 MHz

72 MHz / (7199 + 1)
= 10 kHz

10 kHz / (9 + 1)
= 1 kHz

1 kHz = 1 ms
*/

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

TIM2->CNT = 0;

/* Enable update interrupt */
TIM2->DIER |= TIM_DIER_UIE;

/* NVIC */
NVIC_SetPriority(TIM2_IRQn, 2);
NVIC_EnableIRQ(TIM2_IRQn);

/* Start Timer */
TIM2->CR1 |= TIM_CR1_CEN;
}

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

millis++;
}
}

int main(void)
{
GPIO_Init();
TIM2_Init();

uint32_t last_led = 0;

while (1)
{
if ((uint32_t)(millis – last_led) >= 1000)
{
last_led = millis;

GPIOC->ODR ^= (1 << 13);
}
}
}
“`

—

# 43. Một lưu ý về `TIM2->SR`

Bạn sẽ thấy:

“`c
TIM2->SR &= ~TIM_SR_UIF;
“`

Trong một số dòng STM32, cách xử lý cờ Timer cần chú ý đến cơ chế của từng flag và thao tác đọc/ghi thanh ghi.

Trong ví dụ học tập này, mục tiêu là giúp bạn hiểu:

“`text
UIF = Update Interrupt Flag
“`

và phải **xử lý cờ interrupt** trước khi thoát ISR.

—

# 44. Bài tập thực hành

### Bài tập 1 – LED 1 giây

Cho PC13:

“`text
ON 1 giây
OFF 1 giây
“`

—

### Bài tập 2 – LED 100 ms

Đổi:

“`c
>= 1000
“`

thành:

“`c
>= 100
“`

Quan sát tốc độ.

—

### Bài tập 3 – hai tác vụ

“`text
LED → 500 ms
LED khác → 1000 ms
“`

Bạn có thể dùng hai GPIO hoặc một GPIO với hai task khác nhau.

—

### Bài tập 4 – ba task

Tạo:

“`c
uint32_t t1 = 0;
uint32_t t2 = 0;
uint32_t t3 = 0;
“`

và:

“`text
Task 1 → 10 ms
Task 2 → 100 ms
Task 3 → 1000 ms
“`

—

# 45. Bài tập quan trọng nhất

Hãy thử tự viết:

“`c
uint32_t last_time = 0;

while (1)
{
if ((uint32_t)(millis – last_time) >= 1000)
{
last_time = millis;

// công việc
}
}
“`

Sau đó **không được sử dụng `delay()`**.

Nếu bạn làm được bài này thì bạn đã nắm được tư duy:

> **”Đừng bắt CPU chờ. Hãy để CPU làm việc khác và kiểm tra thời gian khi cần.”**

—

# 46. Bài 36 liên kết với những bài trước như thế nào?

“`text
Bài 30
Tự viết GPIO Driver
↓
Bài 32
GPIO Input + Pull-up
↓
Bài 33
EXTI
↓
Bài 34
NVIC
↓
Bài 35
Debounce + Timer
↓
Bài 36
Timer Interrupt + millis
↓
Bài 37
Timer nâng cao + nhiều tác vụ
“`

Và từ đây chúng ta bắt đầu tiến tới một firmware STM32 thực sự có cấu trúc.

—

## 47. 8 điều bạn cần nhớ sau Bài 36

“`text
1. TIM2 có thể tạo interrupt mỗi 1 ms.

2. ISR của TIM2 tăng biến millis.

3. millis phải khai báo volatile.

4. Không cần delay() để tạo thời gian.

5. Dùng:
millis – last_time >= interval

6. Có thể chạy nhiều task bằng một system tick.

7. ISR nên cực kỳ ngắn.

8. Đây là nền tảng của non-blocking firmware.
“`

### Công thức quan trọng nhất của bài:

“`c
if ((uint32_t)(millis – last_time) >= interval)
{
last_time = millis;

// Task
}
“`

Hãy đặc biệt ghi nhớ dòng này. **Sau này khi chúng ta làm robot, stepper 28BYJ-48, đọc cảm biến, LCD, UART hoặc điều khiển nhiều thiết bị cùng lúc trên Blue Pill, kỹ thuật này sẽ được sử dụng liên tục.**

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