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 44 – HC-SR04 NÂNG CAO TIMER + INPUT CAPTURE + TIMEOUT + STATE MACHINE TRÊN STM32F103

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

BÀI 44 – HC-SR04 NÂNG CAO

TIMER + INPUT CAPTURE + TIMEOUT + STATE MACHINE TRÊN STM32F103

Ở Bài 43, chúng ta đã làm được:

HC-SR04
   ↓
ECHO
   ↓
TIM2 Input Capture
   ↓
CCR1
   ↓
Thời gian ECHO
   ↓
Khoảng cách

Nhưng driver Bài 43 vẫn còn một điểm chưa tốt:

delay_ms(60);

Trong hệ thống thực tế, đặc biệt là robot tránh vật cản, ta không muốn CPU bị chặn bởi delay.

Bài 44 sẽ giải quyết vấn đề đó.


1. Mục tiêu của Bài 44

Sau bài này, driver HC-SR04 sẽ hoạt động như một máy trạng thái – State Machine:

IDLE
 │
 ▼
TRIGGER
 │
 ▼
WAIT_RISING
 │
 ▼
WAIT_FALLING
 │
 ▼
CALCULATE
 │
 ▼
DONE
 │
 ▼
IDLE

Nếu cảm biến không phản hồi:

WAIT_RISING
     │
     │ timeout
     ▼
   ERROR

hoặc:

WAIT_FALLING
     │
     │ timeout
     ▼
   ERROR

2. Tại sao phải dùng State Machine?

Hãy tưởng tượng robot đang chạy:

Robot
 │
 ├── Motor PWM
 │
 ├── HC-SR04
 │
 ├── IR Remote
 │
 ├── Encoder
 │
 └── Logic AUTO/MANUAL

Nếu viết:

HCSR04_Trigger();

delay_ms(60);

distance = HCSR04_Read();

thì trong 60 ms đó chương trình có thể bị chặn.

Trong robot, điều này không tốt.


3. Cách làm chuyên nghiệp hơn

Thay vì:

delay_ms(60);

ta làm:

Trigger
   ↓
CPU tiếp tục chạy
   ↓
Timer chờ
   ↓
Input Capture bắt ECHO
   ↓
Interrupt
   ↓
Driver cập nhật trạng thái

CPU không cần đứng chờ.


4. Tư duy quan trọng

Đây là một trong những tư duy quan trọng nhất khi lập trình embedded:

Không phải lúc nào cũng dùng vòng while để chờ phần cứng.

Không tốt:

while(ECHO == 0);
while(ECHO == 1);

Tốt hơn:

Timer + Interrupt + State Machine

5. Kiến trúc mới

                 HC-SR04
                    │
                    │ ECHO
                    ▼
              TIM2 Input Capture
                    │
                    ▼
                TIM2 IRQ
                    │
                    ▼
              HCSR04 Driver
                    │
              ┌─────┴─────┐
              │           │
           State       Distance
              │
              ▼
         Application

6. Hai Timer sẽ làm hai nhiệm vụ khác nhau

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

TIM2

Đo ECHO:

TIM2
 ↓
Input Capture
 ↓
CCR1
 ↓
Echo width

Một Timer tick 1 ms

Theo kiến thức Bài 36:

SysTick
 ↓
1 ms tick
 ↓
timeout / scheduling

Như vậy:

TIM2
→ đo thời gian chính xác đến µs

SysTick
→ quản lý timeout đến ms

Đây là kiến trúc rất hợp lý.


7. State Machine

Ta định nghĩa:

typedef enum
{
    HCSR04_IDLE,
    HCSR04_WAIT_RISING,
    HCSR04_WAIT_FALLING,
    HCSR04_DONE,
    HCSR04_TIMEOUT
} HCSR04_State;

8. Ý nghĩa từng trạng thái

IDLE

Không đo.

sẵn sàng

WAIT_RISING

Đã gửi TRIG.

Đang chờ:

ECHO LOW → HIGH

WAIT_FALLING

Đã nhận Rising.

Đang chờ:

ECHO HIGH → LOW

DONE

Đã đo xong.

TIMEOUT

Cảm biến không phản hồi đúng thời gian.


9. Biến driver

Ví dụ:

volatile HCSR04_State hcsr04_state;

volatile uint16_t echo_start;
volatile uint16_t echo_end;
volatile uint16_t echo_time;

volatile uint16_t distance_cm;

volatile uint32_t timeout_counter;

10. Vì sao volatile?

Ví dụ:

volatile uint16_t echo_time;

Biến này có thể được thay đổi trong:

TIM2_IRQHandler()

nhưng được đọc trong:

main()

Compiler phải biết rằng giá trị có thể thay đổi bất kỳ lúc nào.

Do đó:

volatile

rất quan trọng trong chương trình interrupt.


11. State Machine nhìn bằng sơ đồ

                ┌─────────┐
                │  IDLE   │
                └────┬────┘
                     │
                 Trigger
                     │
                     ▼
          ┌───────────────────┐
          │  WAIT_RISING      │
          └─────────┬─────────┘
                    │
               Rising Edge
                    │
                    ▼
          ┌───────────────────┐
          │  WAIT_FALLING     │
          └─────────┬─────────┘
                    │
               Falling Edge
                    │
                    ▼
          ┌───────────────────┐
          │      DONE         │
          └─────────┬─────────┘
                    │
                    ▼
                  IDLE

Timeout:

WAIT_RISING ─────timeout────► TIMEOUT
WAIT_FALLING ────timeout────► TIMEOUT

12. Timer TIM2 cấu hình 1 µs

Ta giữ nguyên nguyên lý Bài 43.

STM32F103:

TIM2 clock = 72 MHz

Chọn:

TIM2->PSC = 71;

Ta có:

[
72MHz/(71+1)=1MHz
]

Do đó:

1 tick = 1 µs

13. ARR

TIM2->ARR = 0xFFFF;

Timer:

0
1
2
...
65535
0
1
...

14. Cấu hình PA0

Ta sử dụng:

PA0 = TIM2_CH1 = ECHO

và:

PA1 = TRIG

15. Khởi tạo GPIO

void HCSR04_GPIO_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

    /*
        PA0 = Input
    */

    GPIOA->CRL &= ~(0xF << 0);
    GPIOA->CRL |=  (0x4 << 0);

    /*
        PA1 = Output Push-Pull 2 MHz
    */

    GPIOA->CRL &= ~(0xF << 4);
    GPIOA->CRL |=  (0x2 << 4);

    /*
        TRIG LOW
    */

    GPIOA->BRR = GPIO_BRR_BR1;
}

16. Khởi tạo TIM2

void HCSR04_TIM2_Init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

    TIM2->PSC = 71;

    TIM2->ARR = 0xFFFF;

    TIM2->CNT = 0;

    /*
        CH1 = Input Capture
        CC1S = 01
    */

    TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;
    TIM2->CCMR1 |= TIM_CCMR1_CC1S_0;

    /*
        Rising edge
    */

    TIM2->CCER &= ~TIM_CCER_CC1P;

    /*
        Enable Capture
    */

    TIM2->CCER |= TIM_CCER_CC1E;

    /*
        Enable CC1 interrupt
    */

    TIM2->DIER |= TIM_DIER_CC1IE;

    TIM2->CR1 |= TIM_CR1_CEN;

    NVIC_EnableIRQ(TIM2_IRQn);
}

17. Trigger không dùng delay dài

TRIG chỉ cần khoảng:

10 µs

Ở đây ta có thể dùng một hàm delay_us() rất ngắn:

void HCSR04_Trigger(void)
{
    GPIOA->BRR = GPIO_BRR_BR1;

    delay_us(2);

    GPIOA->BSRR = GPIO_BSRR_BS1;

    delay_us(10);

    GPIOA->BRR = GPIO_BRR_BR1;
}

Điểm quan trọng:

Chỉ có 10 µs delay để tạo xung TRIG; chúng ta không dùng delay_ms(60) để chờ ECHO nữa.


18. Bắt đầu một phép đo

Ta viết:

void HCSR04_Start(void)
{
    if (hcsr04_state != HCSR04_IDLE)
        return;

    timeout_counter = 0;

    echo_start = 0;
    echo_end = 0;
    echo_time = 0;

    /*
        Rising edge
    */

    TIM2->CCER &= ~TIM_CCER_CC1P;

    hcsr04_state = HCSR04_WAIT_RISING;

    HCSR04_Trigger();
}

19. Điều gì xảy ra sau HCSR04_Start()?

Trình tự:

HCSR04_Start()
      │
      ▼
state = WAIT_RISING
      │
      ▼
TRIG = HIGH 10 µs
      │
      ▼
TRIG = LOW
      │
      ▼
CPU tiếp tục làm việc

Không có:

delay_ms(60);

20. Khi Rising Edge xuất hiện

TIM2 tự động phát interrupt.

ECHO

LOW
 │
 │ Rising
 ▼
HIGH

Trong ISR:

if (hcsr04_state == HCSR04_WAIT_RISING)
{
    echo_start = TIM2->CCR1;

    TIM2->CCER |= TIM_CCER_CC1P;

    hcsr04_state = HCSR04_WAIT_FALLING;

    timeout_counter = 0;
}

21. Sau Rising

Bây giờ Timer chờ:

Falling Edge
ECHO

HIGH
 │
 │
 │
 │ Falling
 ▼
LOW

22. Khi Falling Edge xảy ra

else if (hcsr04_state == HCSR04_WAIT_FALLING)
{
    echo_end = TIM2->CCR1;

    echo_time =
        echo_end - echo_start;

    distance_cm =
        echo_time / 58;

    hcsr04_state = HCSR04_DONE;

    timeout_counter = 0;
}

23. Công thức lại một lần nữa

Timer:

1 tick = 1 µs

Nếu:

echo_start = 10000
echo_end   = 11740

thì:

[
1740µs
]

Khoảng cách:

[
1740/58=30cm
]


24. ISR hoàn chỉnh

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_CC1IF)
    {
        uint16_t capture =
            TIM2->CCR1;

        if (hcsr04_state ==
            HCSR04_WAIT_RISING)
        {
            echo_start = capture;

            /*
                Chuyển sang Falling
            */

            TIM2->CCER |= TIM_CCER_CC1P;

            hcsr04_state =
                HCSR04_WAIT_FALLING;

            timeout_counter = 0;
        }

        else if (hcsr04_state ==
                 HCSR04_WAIT_FALLING)
        {
            echo_end = capture;

            echo_time =
                echo_end - echo_start;

            distance_cm =
                echo_time / 58;

            hcsr04_state =
                HCSR04_DONE;

            timeout_counter = 0;

            /*
                Chuẩn bị Rising
            */

            TIM2->CCER &=
                ~TIM_CCER_CC1P;
        }

        /*
            Clear flag
        */

        TIM2->SR &=
            ~TIM_SR_CC1IF;
    }
}

25. Nhưng còn Timeout?

Đây mới là phần quan trọng của Bài 44.

Giả sử:

Trigger
 ↓
WAIT_RISING
 ↓
HC-SR04 không phản hồi

Nếu không có timeout:

WAIT_RISING
WAIT_RISING
WAIT_RISING
...

Driver sẽ bị kẹt.


26. Dùng SysTick 1 ms

Bạn đã học Bài 36:

SysTick
 ↓
1 ms

Ta có:

volatile uint32_t system_ms;

ISR:

void SysTick_Handler(void)
{
    system_ms++;
}

27. Không cần tăng timeout trong SysTick

Một cách tốt hơn là lưu:

start_time_ms

Ví dụ:

volatile uint32_t timeout_start;

Khi bắt đầu:

timeout_start = system_ms;

Sau đó kiểm tra:

if ((system_ms - timeout_start) > 30)
{
    ...
}

28. Tại sao dùng phép trừ?

Đây là kỹ thuật rất quan trọng:

system_ms - timeout_start

thay vì:

system_ms > timeout_start + 30

Cách phép trừ an toàn hơn khi uint32_t overflow.

Bạn sẽ gặp kỹ thuật này rất nhiều trong firmware thực tế.


29. Hàm Process

Ta viết:

void HCSR04_Process(void)
{
    if (hcsr04_state ==
        HCSR04_WAIT_RISING)
    {
        if ((system_ms - timeout_start) > 30)
        {
            hcsr04_state =
                HCSR04_TIMEOUT;
        }
    }

    else if (hcsr04_state ==
             HCSR04_WAIT_FALLING)
    {
        if ((system_ms - timeout_start) > 30)
        {
            hcsr04_state =
                HCSR04_TIMEOUT;
        }
    }
}

30. Main loop

Bây giờ main() có thể rất sạch:

while (1)
{
    HCSR04_Process();

    /*
        Các task khác
    */

    Motor_Process();

    Button_Process();

    IR_Process();

    Robot_Process();
}

Không có:

delay_ms(60);

31. Chu trình hoạt động

Ví dụ robot đang chạy.

t = 0 ms
HCSR04_Start()

Sau đó:

t = 0.01 ms
TRIG LOW

CPU quay lại:

Motor
IR
Robot
...

Khi ECHO Rising:

TIM2 IRQ

CPU xử lý vài microsecond rồi quay lại công việc chính.

Khi ECHO Falling:

TIM2 IRQ

Driver tính:

echo_time
distance

32. Đây chính là Non-blocking

Blocking:

Trigger
  ↓
WAIT
  ↓
WAIT
  ↓
WAIT
  ↓
distance

Non-blocking:

Trigger
  ↓
return
  ↓
Motor
  ↓
IR
  ↓
Robot
  ↓
TIM IRQ
  ↓
Echo
  ↓
distance

Đây là một bước tiến rất lớn trong cách thiết kế firmware.


33. Trạng thái DONE

Sau khi đo xong:

hcsr04_state = HCSR04_DONE;

Application có thể đọc:

if (hcsr04_state == HCSR04_DONE)
{
    uint16_t d = distance_cm;

    hcsr04_state =
        HCSR04_IDLE;
}

34. Tốt hơn: viết hàm kiểm tra kết quả

uint8_t HCSR04_IsReady(void)
{
    return
        hcsr04_state == HCSR04_DONE;
}

Sau đó:

if (HCSR04_IsReady())
{
    distance = HCSR04_GetDistance();

    HCSR04_Clear();
}

35. API của driver

Một driver tốt có thể có:

void HCSR04_Init(void);

void HCSR04_Start(void);

void HCSR04_Process(void);

uint8_t HCSR04_IsReady(void);

uint8_t HCSR04_IsTimeout(void);

uint16_t HCSR04_GetDistance(void);

Application không cần biết:

TIM2
CCR1
CCER
CCMR1

Đó là việc của driver.


36. Ví dụ API

if (HCSR04_IsReady())
{
    uint16_t distance =
        HCSR04_GetDistance();

    if (distance < 30)
    {
        Robot_Stop();
        Robot_Turn();
    }
}

Đây chính là kiểu kiến trúc chúng ta muốn hướng tới.


37. Timeout bao nhiêu?

Ví dụ:

#define HCSR04_TIMEOUT_MS 30

Vì khoảng cách tối đa giả sử:

400 cm

thời gian ECHO khoảng:

23.2 ms

nên:

30 ms

là hợp lý.


38. Nhưng có một vấn đề

Nếu:

WAIT_RISING

timeout 30 ms thì được.

Nhưng nếu:

WAIT_FALLING

cũng timeout 30 ms thì tổng thời gian có thể hơi dài.

Có thể thiết kế:

#define RISING_TIMEOUT_MS 5
#define FALLING_TIMEOUT_MS 30

Ví dụ:

Rising phải xuất hiện nhanh
Falling có thể mất đến ~30 ms

39. State Machine nâng cao

Ta có thể định nghĩa:

typedef enum
{
    HCSR04_IDLE,
    HCSR04_TRIGGER,
    HCSR04_WAIT_RISING,
    HCSR04_WAIT_FALLING,
    HCSR04_DONE,
    HCSR04_TIMEOUT
} HCSR04_State;

40. Sơ đồ hoàn chỉnh

                         ┌─────────────┐
                         │    IDLE     │
                         └──────┬──────┘
                                │
                             Start
                                │
                                ▼
                         ┌─────────────┐
                         │  TRIGGER    │
                         └──────┬──────┘
                                │
                                ▼
                       ┌─────────────────┐
                       │ WAIT_RISING     │
                       └───────┬─────────┘
                          ┌─────┴─────┐
                       Rising       Timeout
                          │             │
                          ▼             ▼
                 ┌────────────────┐  ERROR
                 │ WAIT_FALLING   │
                 └───────┬────────┘
                    ┌────┴─────┐
                 Falling      Timeout
                    │            │
                    ▼            ▼
                 DONE           ERROR
                    │
                    ▼
                  IDLE

41. Một điều rất quan trọng: ISR phải ngắn

Không nên viết trong:

TIM2_IRQHandler()

những thứ như:

LCD_Print(...)
delay_ms(...)
Serial_Print(...)
Motor_Turn(...)

ISR nên làm rất ít:

Capture
 ↓
lưu giá trị
 ↓
đổi state
 ↓
clear flag
 ↓
return

42. Tại sao?

Nếu ISR mất quá nhiều thời gian:

TIM IRQ
   ↓
ISR dài
   ↓
Timer khác phát IRQ
   ↓
CPU chưa xử lý

có thể gây:

latency
missed event
jitter

Trong hệ thống real-time, đây là vấn đề nghiêm trọng.


43. Tách ISR và Application

ISR:

echo_time = ...
state = DONE;

Application:

if (state == DONE)
{
    distance = ...
}

Đây là nguyên tắc rất tốt:

Interrupt thu thập dữ liệu; main xử lý logic.


44. Bài tập thực hành đầu tiên

Hãy nối:

STM32F103
PA1 → TRIG
PA0 → ECHO

qua cầu phân áp:

10k + 20k

Sau đó chạy:

HCSR04_Start()

và kiểm tra:

distance_cm

45. Bài tập thứ hai

Đưa tay:

10 cm
20 cm
30 cm
40 cm

và kiểm tra:

echo_time

Bạn nên thấy xấp xỉ:

10 cm → 580 µs
20 cm → 1160 µs
30 cm → 1740 µs
40 cm → 2320 µs

Không cần chính xác tuyệt đối vì tốc độ âm thanh thay đổi theo nhiệt độ và cảm biến có sai số.


46. Bài tập thứ ba – cố tình tạo Timeout

Không nối ECHO.

Chạy:

HCSR04_Start();

Sau khoảng:

30 ms

phải chuyển:

WAIT_RISING
      ↓
TIMEOUT

Nếu chương trình cứ đứng:

WAIT_RISING
WAIT_RISING
WAIT_RISING

thì driver của bạn đang thiếu hoặc sai timeout.


47. Bài tập thứ tư – robot

Giả sử:

#define OBSTACLE_DISTANCE 30

Robot:

if (distance < 30)
{
    Robot_Stop();
    Robot_TurnRight();
}
else
{
    Robot_Forward();
}

Điểm quan trọng là không được dùng:

delay_ms(1000);

để robot rẽ trong driver HC-SR04.

Mỗi module nên non-blocking.


48. Bài học lớn nhất của Bài 44

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

"Đọc cảm biến"

sang:

"Thiết kế driver cảm biến"

Hai thứ hoàn toàn khác nhau.


49. Từ đây firmware sẽ có cấu trúc

                 APPLICATION
                     │
              ┌──────┴──────┐
              │             │
          Robot Logic    User Input
              │             │
              └──────┬──────┘
                     │
                  DRIVERS
                     │
       ┌─────────────┼─────────────┐
       │             │             │
    HCSR04         Motor          IR
       │             │             │
      TIM          PWM          EXTI/Timer
       │             │             │
       └─────────────┴─────────────┘
                     │
                  HARDWARE

Đây là nền tảng để sau này bạn có thể viết firmware robot rất lớn mà code vẫn kiểm soát được.


50. Kiến thức cần thuộc sau Bài 44

Bạn không cần học thuộc từng dòng code.

Nhưng phải hiểu thật chắc 8 thứ này:

① Timer Prescaler

PSC = 71

→ 72 MHz thành 1 MHz.

② Timer Counter

CNT

→ đồng hồ thời gian.

③ Input Capture

CCR1

→ chụp giá trị CNT khi có cạnh.

④ Rising Edge

LOW → HIGH

→ bắt đầu ECHO.

⑤ Falling Edge

HIGH → LOW

→ kết thúc ECHO.

⑥ Pulse width

falling - rising

→ thời gian ECHO.

⑦ Timeout

Không có phản hồi
→ không được chờ vô hạn

⑧ State Machine

IDLE
→ WAIT
→ DONE
→ IDLE

→ hệ thống không bị blocking.


51. Bài 44 và robot của bạn

Đây chính là phần rất đáng chú ý đối với dự án robot mà bạn đang phát triển.

Thay vì:

HC-SR04
 ↓
delay
 ↓
đọc
 ↓
motor

ta có:

             ┌──────────────┐
             │ HC-SR04      │
             │ State Machine │
             └──────┬───────┘
                    │
              distance
                    │
                    ▼
             ┌──────────────┐
             │ Robot Logic  │
             └──────┬───────┘
                    │
             ┌──────┴───────┐
             ▼              ▼
          Motor PWM       Servo

Trong khi HC-SR04 đang chờ Echo:

CPU vẫn có thể:
✓ điều khiển PWM
✓ đọc IR
✓ xử lý nút
✓ cập nhật trạng thái robot
✓ kiểm tra Manual/Auto

Đó chính là lý do State Machine + Interrupt + Timer quan trọng đến vậy.


Bài tiếp theo

Sau Bài 44, bước rất hợp lý là:

BÀI 45 – TIMER ENCODER MODE TRÊN STM32F103

Đọc encoder bằng phần cứng Timer → đếm xung → xác định chiều → tính RPM → tính quãng đường

Khi đó chúng ta sẽ ghép:

HC-SR04
   ↓
khoảng cách

Encoder
   ↓
tốc độ + quãng đường

PWM
   ↓
điều khiển motor

và bắt đầu tiến rất gần tới bộ điều khiển chuyển động robot thực sự.

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