GD32E225CBT6 · FD6287T · 6-step BLDC

Sổ tay firmware điều khiển cửa trượt BLDC

Tài liệu kiến trúc để tự đọc lại, tự sửa và tự mở rộng code — không phải mô tả tính năng, mà là bản đồ: state nào gọi hàm nào, tín hiệu nào quyết định điều gì, và vì sao vài chỗ trong code trông "khác thường" lại là cố ý.

Core/Src/main.c MyLibrary/Motor MyLibrary/Protection MyLibrary/BSP MyLibrary/Common/app_define.h
01 — Tổng quan

Kiến trúc 4 lớp

main.c không đụng thanh ghi phần cứng, và BSP không biết gì về "cửa đang mở hay đóng". Ranh giới này là thứ giữ cho code sửa được — cứ theo đúng lớp khi thêm tính năng.

main.c — máy trạng thái (ChayTacVu) BatDauMo · BatDauDong · InTrangThaiDebug gọi DongCo_*() / BaoVe_*() ↓ vị trí · RPM · cờ an toàn ↑ Motor commutation · position speed · profile (ramp/VR) Protection PhatHienKet · HallDungYen BaoHallDaDoi · XoaBoDemKet đọc/ghi BSP_*() ↓ Hall / dòng ADC / xung PWM ↑ BSP bsp_hall · bsp_pwm · bsp_adc · bsp_gpio · bsp_uart thanh ghi TIMER / ADC / EXTI ↓↑ Phần cứng FD6287T (6 FET) · Hall 3 kênh · VR1–VR6 · UART debug
main.c không bao giờ gọi BSP trực tiếp — luôn qua Motor hoặc Protection. Muốn đổi cách đọc dòng điện hay chân Hall, chỉ sửa BSP; máy trạng thái ở main.c không cần biết.
02 — ChayTacVu()

Vòng đời vận hành: 6 trạng thái

KieuTrangThai_t chỉ có 6 giá trị. Mỗi lần cấp điện luôn bắt đầu lại từ đầu (TT_HIEUCHINH_TIM_DINH) — không có gì được nhớ qua lần tắt nguồn.

HIỆU CHỈNH (1 lần / lần cấp điện) TÌM ĐỈNH 0 · chạy hết cỡ chiều mở chạm đỉnh TÌM ĐÁY 1 · chạy hết cỡ chiều đóng so_buoc_hanh_trinh_cua = |đỉnh − đáy| VÒNG VẬN HÀNH BÌNH THƯỜNG ĐỨNG YÊN Ở NHÀ 2 · khóa phanh, POS=0 ĐANG MỞ 3 · ramp duty ĐỨNG YÊN Ở ĐÍCH 4 · deadband ±20 bước ĐANG ĐÓNG 5 · ramp duty cảm biến / MỞ đến đích ĐÓNG / hết giờ về đáy thật (1.5% cuối, Hall đứng yên) → dừng, không đảo chiều vật cản giữa đường → mở lại toàn bộ (an toàn)
Đường cam là nhánh an toàn: đóng cửa mà Hall đứng yên (nghi kẹt tay/vật cản) trong 98.5% đầu hành trình sẽ hủy đóng và mở lại hết, không phải là đảo chiều tình cờ. Chỉ 1.5% cuối cùng mới được phép "dừng yên = đã chạm khóa cơ khí thật".
03 — TÌM ĐỈNH / TÌM ĐÁY

Vì sao hiệu chỉnh phải "kick lại" trước khi kết luận

Hall đứng yên có 2 nguyên nhân hoàn toàn khác nhau: giới hạn cơ khí thật (đỉnh/đáy ray trượt), hoặc điểm ma sát/kẹt tạm thời giữa đường. Chỉ đo 1 lần thì không phân biệt được hai cái này.

Hall đứng yên ≥ 1000ms, lần thứ 1 Kick lại 1 lần duty 60% (MOTOR_CAL_KICK_DUTY_PERCENT) Đi tiếp được → chỉ là kẹt tạm coi như chưa xảy ra, quay lại dò bình thường Vẫn đứng yên (lần 2) → giới hạn thật ghi vị trí, chuyển pha kế tiếp
Áp dụng giống hệt ở cả tìm đỉnh và tìm đáy (biến da_kick_lai_hieu_chinh). Trước khi chuyển pha, bộ đếm debounce dòng điện trong Protection cũng được xóa (BaoVe_XoaBoDemKet()) — nếu không, dòng điện còn cao dư từ cú kick sẽ làm pha kế tiếp báo "chạm giới hạn" giả ngay tức khắc.
04 — motor_commutation.c

Bảng chuyển mạch Hall 6 bước

3 pha vật lý trên driver FD6287T, theo đúng tên nội bộ trong code:

PHA_U = pha A · PA10/PB15 PHA_V = pha B · PA9/PB14 PHA_W = pha C · PA8/PB13
BANG_THUAN / BANG_NGUOC trong motor_commutation.c — hall = 0 không dùng
HALLChiều THUẬN (mở) — cao / thấpChiều NGƯỢC (đóng) — cao / thấp
1U cao · W thấpW cao · U thấp
2V cao · U thấpU cao · V thấp
3V cao · W thấpW cao · V thấp
4W cao · V thấpV cao · W thấp
5U cao · V thấpV cao · U thấp
6W cao · U thấpU cao · W thấp

Bảng NGƯỢC không phải đơn giản là đảo cao/thấp của bảng THUẬN — đây là bảng đã đo và xác nhận riêng trên động cơ thật. Nếu đổi động cơ hoặc đấu lại dây pha, phải đo lại toàn bộ 12 dòng này, không suy ra bằng công thức.

Cơ chế bên trong DongCo_ChuyenMach_DatChieu()

Hàm này được gọi mỗi vòng lặp, không điều kiện — nhưng chỉ thực sự động vào phần cứng khi bước hoặc chiều THỰC SỰ đổi. Bí quyết: hàm tự nhớ lại lần gọi trước để so sánh, nên phần lớn các lần gọi là gần như miễn phí.

gọi với (hall, chiều) — mỗi vòng lặp main() Hall hợp lệ? (1..6) KHÔNG Dừng hẳn tắt PWM + xóa nhớ trạng thái return CÓ Giống hệt lần gọi trước? hall & dấu chiều CÓ Không làm gì return ngay (chặn chớp pha thấp) KHÔNG — thực sự đổi Tắt hết 6 FET — BSP_PWM_TatHet() Trễ phần mềm (dead-time) — TreThoiGian_PhanMem() Chọn bảng: chiều≥0 → THUẬN · chiều<0 → NGƯỢC Tra bước ứng với hall → {pha cao, pha thấp} Bật pha THẤP (ON liên tục) Bật pha CAO (enable PWM) + đặt xung hiện tại Lưu lại: hall, dấu chiều, pha cao đang dùng để so sánh ở lần gọi kế tiếp return nhớ lại để so sánh lần sau
Trạng thái được nhớ giữa các lần gọi (đường nét đứt vòng lại lên diamond thứ 2) là toàn bộ bí quyết: hàm chỉ thực sự tắt/bật FET khi bước hoặc chiều thay đổi thật, nên gọi vô điều kiện mỗi vòng lặp vẫn an toàn — không có gì chớp tắt liên tục qua động cơ.
05 — motor_profile.c

Ý nghĩa 6 biến trở

Đọc 1 lần khi bắt đầu mỗi chặng chạy (LayMauThongSo_ChieuMo / LayMauThongSo_ChieuDong) — xoay VR giữa lúc đang chạy sẽ không có tác dụng ngay, để tránh đổi tốc đột ngột.

VR1–VR4 dùng chung cả 2 chiều mở/đóng · VR5, VR6 chỉ dùng riêng
VRVai tròCông thức / phạm vi thực tế
1Tốc độ chạy (duty cruise)0–100% theo ADC/4095 — luôn là trần, VR3/VR4 bị kẹp không vượt quá
2Khoảng tăng + giảm tốc (số bước)≤ 17.5% quãng đường mỗi pha dù vặn hết cỡ (hệ số cố định 35 trong code)
3Duty lúc bắt đầu tăng tốc0–100%, kẹp ≤ VR1 − 5%
4Duty lúc kết thúc giảm tốc0–100%, kẹp ≤ VR1 − 5%
5Quãng mở khi có cảm biến chuyển động% của so_buoc_hanh_trinh_cua — mốc 100% thực tế ~3000, KHÔNG phải 4095
6Thời gian trễ trước khi tự đóngVR_OPEN_TIME_MS_MIN … MAX
06 — InTrangThaiDebug()

Đọc 1 dòng log UART

In không chặn (không block vòng lặp chính) — nếu lần gửi trước chưa xong, dòng đó bị bỏ qua thay vì chờ, nên khoảng cách giữa các dòng không phải lúc nào cũng đều nhau.

ST=3 HALL=6 POS=1862 I1=2035 I2=2051 VR1=3151 VR2=2140 VR3=1000 VR4=394 VR5=3322 VR6=3158 RPM=1895 DUTY=74 BRK=0 PB12=0
TrườngÝ nghĩa
STKieuTrangThai_t hiện tại (0–5, xem sơ đồ vòng đời)
HALLMã Hall vừa xác nhận (1–6, sau debounce 3 lần đọc giống nhau)
POSVị trí đếm theo cạnh Hall, có dấu — 0 = home
I1 / I2ADC dòng điện pha B / pha C (thô, chưa qua debounce)
VR1–VR6Giá trị ADC thô của 6 biến trở (0–4095, thực tế kịch kim ~3100–3300)
RPMƯớc lượng từ chu kỳ điện gần nhất — không tự về 0 khi Hall ngừng đổi, chỉ giữ giá trị cũ
DUTY% duty đang áp cho pha cao đang active (0 khi DongCo_ChuyenMach_Dung())
BRKPhản hồi "đã khóa phanh" từ phần cứng — chỉ quan sát, polarity chưa xác nhận
PB12Đọc thô tức thời chân báo lỗi driver — khác với cờ chốt (đã tắt tạm, xem mục 07)

Bẫy hay gặp: RPM còn hiển thị số lớn không có nghĩa là động cơ đang thực sự quay ngay lúc đó — nó là giá trị đo lần cuối, có thể đã "cũ" nếu Hall vừa đứng yên. Muốn biết motor có đang quay thật, nhìn HALL có đổi giữa các dòng liên tiếp không.

07 — Ghi nhớ khi sửa code

Nguyên tắc thiết kế không hiển nhiên

Mỗi nguyên tắc dưới đây từng là một lỗi thật đã xảy ra trên phần cứng. Phá vỡ nguyên tắc nào, khả năng cao lỗi đó quay lại.

Không kết luận "chạm giới hạn" từ 1 lần Hall đứng yên

Luôn kick lại ở duty cao hơn 1 lần trước khi kết luận (xem mục 03).

Vì sao: điểm ma sát/kẹt giữa đường và giới hạn cơ khí thật đều làm Hall đứng yên giống hệt nhau ở lần đo đầu tiên.

Không giữ duty cố định liên tục lúc "đứng yên"

Đứng yên ở đích chỉ nhích lại khi trôi lùi quá deadband (20 bước), còn lại cắt hẳn điện.

Vì sao: giữ duty liên tục (dù chỉ 8%) có thể thắng lực cản tự nhiên và làm động cơ trôi không giới hạn — từng đo được POS trôi từ ~1900 lên hơn 50.000 trong vài phút "đứng yên".

Target lúc đóng luôn tính động, không dùng số hiệu chỉnh cố định

BatDauDong() lấy DongCo_ViTri_Lay() tại thời điểm gọi làm target, không dùng thẳng so_buoc_hanh_trinh_cua.

Vì sao: cửa có thể đang ở vị trí khác điểm "mở hết" đã hiệu chỉnh (mở một phần qua cảm biến, hoặc trớn qua do giảm tốc chưa đủ dài) — nếu vẫn dùng số cố định, phép tính "còn bao xa" bị lệch và giữ duty thấp ngay đoạn gần điểm dừng thật, dễ báo vật cản giả.

Reset bộ đếm dòng điện khi chuyển pha đo

BaoVe_XoaBoDemKet() gọi ngay sau mỗi lần kick và mỗi lần chuyển trạng thái hiệu chỉnh.

Vì sao: dòng điện cao dư từ pha trước (vd sau cú kick 60%) có thể làm pha sau báo "chạm giới hạn" ngay ở vòng lặp đầu tiên, trước khi động cơ kịp di chuyển bước nào.

1.5% cuối hành trình đóng là "vùng an toàn" — ngoài ra Hall đứng yên luôn là vật cản

MOTOR_OBSTACLE_ZONE_PERCENT_X10 = 985 trong app_define.h quyết định ranh giới này.

Vì sao: lúc đóng, an toàn (chống kẹt tay) được ưu tiên tuyệt đối hơn mọi thứ khác — thà mở lại nhầm còn hơn tiếp tục ép khi nghi có vật cản.

Debounce nhẹ cho tín hiệu điện tần số cao (PB12), không chỉ nút bấm

DRIVER_FAULT_DEBOUNCE_COUNT = 3 vòng lặp — thấp hơn nhiều so với nút bấm (5) hay cảm biến (2).

Vì sao: chân báo lỗi driver không debounce từng bị chốt lỗi giả nhiều lần liên tiếp đúng 1 dải bước cố định trong hành trình đóng, dù đọc thô luôn về 0 giữa các lần — dấu hiệu của xung nhiễu rất ngắn, không phải lỗi dòng điện thật kéo dài. Tính năng khẩn cấp (hãm + khóa phanh khi có lỗi) hiện đang tạm tắt bằng comment trong ChayTacVu() cho tới khi xác định rõ nguồn nhiễu.
08 — Tra cứu nhanh

Sửa gì thì mở file nào

FileVai tròSửa gì ở đây
Core/Src/main.cMáy trạng thái, luồng chínhThêm/đổi trạng thái, điều kiện chuyển pha, logic nút bấm/cảm biến
MyLibrary/Motor/Src/motor_commutation.cBảng Hall 6 bước, PWM pha cao/thấpĐổi động cơ, đấu lại dây pha
MyLibrary/Motor/Src/motor_profile.cCông thức ramp duty, quy đổi VRĐổi cách tăng/giảm tốc, đổi ý nghĩa biến trở
MyLibrary/Motor/Src/motor_position.cĐếm vị trí theo cạnh HallĐổi số bước Hall/vòng, đơn vị vị trí
MyLibrary/Protection/Src/protection.cPhát hiện kẹt/vật cảnĐổi ngưỡng dòng điện, thời gian đứng yên
MyLibrary/BSP/Src/*.cThanh ghi phần cứng (TIMER/ADC/GPIO/UART)Đổi chân, tần số PWM, tốc độ baud
MyLibrary/Common/Inc/app_define.hToàn bộ hằng số tinh chỉnhChỉnh thông số mà không sửa logic — luôn thử ở đây trước