HAL_Delay là một hàm có sẵn trong thư viện HAL của STM32. Cách hoạt động của HAL_Delay như thế nào và làm như thế nào để tạo một hàm Delay, cách sử dụng chúng như thế nào trong STM32. Tất cả sẽ được đề cập trong bài hôm nay.
Bài này nằm trong Học STM32 từ A tới Z
Delay là gì?
Delay trong tiếng anh là sự chậm trễ, sự trì hoãn. Trong lập trình cũng tương tự như vậy, khi vi điều khiển làm xong 1 việc chúng sẽ nhảy ngay tới việc tiếp theo chỉ trong một vài micro giây ( tùy vào tần số hoạt động của MCU).
Để duy trì trạng thái đó 1 khoảng thời gian, chúng ta dùng delay. Khi đó MCU sẽ không làm gì cả (thực ra là vẫn làm nhưng là chạy lòng vòng trong hàm delay) khi thực hiện xong hàm delay mới thực hiện tiếp lệnh tiếp theo.
Cách sử dụng Systick trong STM32
Systick là gì?
STM32 có một bộ định thời hệ thống (System Timer) 24bit đếm xuống, tự nạp lại. Nghĩa là mỗi khi đếm đến 0 sẽ tự động quay về đếm từ số lớn nhất. Giá trị nạp lại sẽ được cấu hình trong thanh ghi SysTick Reload Value Register (SYST_RVR) của STM32.
Systick là hàm xử lý mỗi khi sảy ra sự kiện ngắt (thanh ghi counter của System Timer đếm về 0). Trong hàm này giá trị của biến đếm uwTick sẽ được tăng lên 1 (hoặc 1 số bất kì) bởi hàm incTick.
Mặc định khi khởi tạo code với CubeMx. Ngắt Systick sảy ra mỗi 1 mili giây.
Sử dụng Systick trong HAL_Delay(ms)
Trong hàm HAL_Delay, khi bắt đầu vào hàm giá trị ban đầu của Systick counter sẽ được đọc bằng hàm HAL_GetTick(); Trong while sẽ so sánh giá trị của System Timer tại thời điểm đó với thời điểm ban đầu, nếu lớn hơn hoặc bằng giá trị delay sẽ thoát vòng lặp và kết thúc hàm delay
Khi sử dụng hàm HAL_Delay, code của chúng ta sẽ chạy ở chế độ blocking mode, nghĩa là chúng không làm gì cả trong suốt thời gian delay( Thực tế là nó đi kiểm tra cái điều kiện while đó). Khi làm xong HAL_Delay thì mới tiếp tục làm những việc khác.
Ưu điểm của phương pháp này: Thời gian delay chính xác
Nhược điểm: Vì code ở chế độ Blocking thế nên có thể làm chậm các tác vụ khác. Ví dụ quét màn hình, hiển thị led 7 …..
Sử dụng HAL_GetTick() code delay chế độ non-Blocking
Phương pháp này cũng gần giống như HAL_Delay chỉ khác là chúng ta sẽ sử dụng if thay cho while. Cấu trúc như thế này:
while(1) { static int last_time = HAL_GetTick(); int current_time = HAL_GetTick(); if(current_time - last_time >= delay) { HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_0); last_time = current_time; } //do something }
Giải thích:
Chúng ta tạo ra 2 biến lưu giá trị counter cuối cùng (hay giá trị bắt đầu delay) và giá trị hiện tại của counter.
Mỗi khi code đi qua sẽ cập nhật giá trị của current time. Nếu giá trị hiện tại – giá trị bắt đầu = thời gian delay, chương trình sẽ thực hiện đảo trạng thái pin PA0. Sau đó lưu giá trị bắt đầu = giá trị hiện tại.
Phương pháp này thường sử dụng trong các việc cần lặp lại liên tục và tuần tự.
Ưu điểm: Chương trình sẽ làm được những việc khác mà không cần phải chờ đợi.
Nhược điểm: Thời gian delay có thể bị sai vì code thực hiện các câu lệnh dài, không kịp quay về kiểm tra biến current time.
Cả 2 phương pháp sử dụng Systick đều chỉ code được các hàm delay ở Mili giây, vậy để code hàm delay ở micro giây chúng ta cần làm như thế nào. Hãy tiếp tục đọc nhé
Giải thích vì sao không nên dùng HAL_Delay trong ngắt
Giả sử ta có một ngắt A có độ ưu tiên ngang bằng hoặc hơn so với ngắt Systick, khi ngắt A được gọi và có hàm HAL_Delay() trong phần xử lý ngắt của A.
Khi đó chương trình sẽ chạy vào HAL_Delay, sau khi biến tickstart lấy được giá trị trả về của hàm GetTick, chương trình chạy vào while, tuy nhiên ngắt Systick lúc này đang ở chế độ chờ (do ngắt A chưa chạy xong) như vậy, hàm GetTick không thể tăng được giá trị trả về, dẫn tới treo tại while.
Để khắc phục hiện tượng này chúng ta có thể giảm ưu tiên của ngắt A hoặc tăng ưu tiên của ngắt Systick, lúc này khi vào HAL_Delay, ngắt Systick sẽ đè lên ngắt A, và chương trình vẫn hoạt động bình thường
Delay us với Timer trong STM32
Nếu các bạn chưa biết Timer là gì và cách khởi tạo Timer thì vui lòng đọc lại bài: Lập trình STM32 Timer nhé!
Ý tưởng: Chúng ta sẽ tạo ra một Timer với mỗi lần đếm (Count Step) là 1 us. Đếm từ 0 tới 0xFFFF và lặp lại liên tục. Sau đó viết một hàm giống HAL_Delay để tạo delay chế độ blocking. Với non-Blocking thì không nên sử dụng vì chắc chắn sẽ sai thời gian (do mỗi lệnh của vdk xử lý đã mất vài us rồi).
Khởi tạo Timer trong CubeMX
fmaser Clock chúng ta sử dụng trong bài này là 72Mhz, nguồn thạch anh ngoài. Nên sẽ setup RCC là Crystal
Tiếp đó chính Clock lên 72Mhz
Timer sử dụng Timer 2. Cấu hình như sau:
- Bộ chia 72-1: do prescale luôn được cộng thêm 1 với bất kì số nào được đưa vào. Với fmaster là 72Mhz, thì Count step sẽ là 1Mhz, tương ứng với 1us.
- Bộ nạp lại ARR sẽ để max là 0xffff -1 : vì đếm từ 0 lên mà
- Chọn chế độ đếm là đếm lên
Gen code và mở bằng Keil C
Lập trình Delay_us với Keil C
Sử dụng HAL_TIM_Base_Start(&htim2);
để khởi động Timer 2
Tạo hàm delay_us như sau:
void delay_us (uint16_t us) { __HAL_TIM_SET_COUNTER(&htim2,0); // set the counter value a 0 while (__HAL_TIM_GET_COUNTER(&htim2) < us); // wait for the counter to reach the us input in the parameter }
Giải thích: Mỗi lần gọi delay, chúng ta sẽ xóa counter về 0. Sau đó chờ trong while tới khi counter bằng giá trị delay thì mới thoát ra.
Thực hành tạo xung với delay_us.
Khởi tạo PC13 là Output. Sau đó trong while như sau:
while (1) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); delay_us(10); }
Sử dụng Oscillo đo xung nhận được chúng ta sẽ được kết quả.
Sử dụng thư viện Delay_Timer
Các bạn download thư viện delay theo hướng dẫn: Download tài liệu lập trình STM32
Add file .c vào project và chỉnh đường dẫn thư viện. Cái này làm nhiều quá rồi nên mình không nhắc lại nữa nhé!
Trong main() chúng ta khởi tạo Timer cho delay bằng hàm DELAY_TIM_Init(&htim2)
Sau đó dùng hàm DELAY_TIM_Us(&htim2, time_delay);
để tạo trễ nhé
Kết
Hal_Delay và Delay_us được sử dụng rất phổ biến trong lập trình nhúng trên STM32. Hãy hiểu rõ và sử dụng chúng một cách linh hoạt để tránh việc bị treo hoặc chậm chương trình nhé.
Nếu cảm thấy bài viết này có ích hãy chia sẻ với bạn bè nhé. Đừng quên ra nhập hội những anh em Nghiện lập trình nhé!!