STM32F103C8T6 USB CDC 在 72MHz 下停止模式唤醒后断开(HSE 8MHz)—— 求助

Xin chào mọi người,

Tôi đang gỡ lỗi một vấn đề gián đoạn liên quan đến USB CDC trên bo mạch STM32F103C8T6. Sau khi thức dậy từ chế độ dừng, cổng COM ảo trên máy tính đôi khi bị mất (chỉ có thể khôi phục bằng cách nhấn nút reset cứng). Mong mọi người giúp kiểm tra trình tự khởi tạo clock/USB của tôi có hợp lý không và nếu có giải pháp đã được xác nhận.

Phần cứng và linh kiện

  • MCU: STM32F103C8T6, chạy ở 72MHz thông qua PLL nhân 9 từ HSE 8MHz.
  • Bo mạch: Phiên bản “Blue Pill (bản ‘tiểu lam bản’)”; IC ổn áp LDO chuyển đổi 5V sang 3.3V, tụ điện 0.1µF + 1µF gắn gần chân VDD, thạch anh 8MHz kiểu HC-49 với tụ tải 22pF.
  • USB: Thiết bị tốc độ đầy đủ (Full-speed), chân D+ được kéo lên 3.3V qua điện trở 1.5kΩ; điện trở 22Ω nối tiếp trên đường D+/D-.

Hiện tượng lỗi

  • Khởi động lần đầu: USB CDC hoạt động bình thường trên Windows 10 và Ubuntu 22.
  • Sau khi thức dậy từ chế độ dừng khoảng 5-30 giây, máy chủ đôi khi không nhận diện được thiết bị CDC (không có sự kiện PID/VID mới, cổng COM biến mất). Tháo/gắn lại cáp USB không luôn hiệu quả, nhưng nhấn nút reset NRST luôn khôi phục được. Tỷ lệ tái hiện khoảng 10%-30%.

Các biện pháp đã thử

  1. Sau khi thức dậy: Kích hoạt lại HSE/PLL rồi khởi động lại clock USB.
  2. Chuyển clock ngoại vi USB và thực hiện chuỗi USBD_DeInit() → USBD_Init().
  3. Gọi SystemCoreClockUpdate() sau khi chuyển đổi clock.
  4. Thêm độ trễ 1-10ms giữa các bước khởi tạo RCC và USB.
  5. Đo dao động điện áp 3.3V bằng dao động ký: dao động <20mVpp trong quá trình thức dậy; D+ ở mức cao khoảng 3.3V khi rảnh.

Trình tự cấu hình clock/RCC (dùng thư viện HAL)

// Sau khi thức dậy từ chế độ dừng: kích hoạt lại HSE/PLL và chuyển SYSCLK về PLL
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);

HAL_RCC_OscConfig(&(RCC_OscInitTypeDef){
  .OscillatorType = RCC_OSCILLATORTYPE_HSE,
  .HSEState = RCC_HSE_ON,
  .PLL.PLLState = RCC_PLL_ON,
  .PLL.PLLSource = RCC_PLLSOURCE_HSE,
  .PLL.PLLMUL = RCC_PLL_MUL9
});

HAL_RCC_ClockConfig(&(RCC_ClkInitTypeDef){
  .ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2,
  .SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK,
  .AHBCLKDivider = RCC_SYSCLK_DIV1,
  .APB1CLKDivider = RCC_HCLK_DIV2,
  .APB2CLKDivider = RCC_HCLK_DIV1
}, FLASH_LATENCY_2);

SystemCoreClockUpdate();

Đoạn mã khởi tạo lại USB

// Khởi động lại hoàn toàn ngăn xếp thiết bị sau khi clock ổn định
USBD_DeInit(&hUsbDeviceFS);
__HAL_RCC_USB_FORCE_RESET();
HAL_Delay(2);
__HAL_RCC_USB_RELEASE_RESET();
__HAL_RCC_USB_CLK_ENABLE();

USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);
USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);
USBD_Start(&hUsbDeviceFS);

Kết quả quan sát

  • Nếu bỏ qua chế độ dừng và chỉ chuyển giữa chế độ chạy/ngủ, hệ thống ổn định.
  • Nếu giữ SYSCLK ở HSI (không kích hoạt PLL) sau khi thức dậy, hệ thống có vẻ ổn định hơn nhưng tôi cần 72MHz để đáp ứng yêu cầu thời gian.
  • Kéo thấp D+ khoảng 10ms qua GPIO (giả lập ngắt kết nối) giúp tăng xác suất khôi phục nhưng không đạt 100%.

Câu hỏi cần hỗ trợ

  1. Đối với chip F103, sau khi thức dậy từ chế độ dừng, có trình tự/khoảng thời gian chuẩn nào để kích hoạt HSE→PLL→USBFS nhằm tránh lỗi thiết bị CDC không?
  2. Việc kéo thấp D+ (giả lập ngắt mềm) sau khi thức dậy có phải là thực hành tốt nhất cho dòng F1, hay có phương pháp đặt lại cấp giao thức tốt hơn?
  3. Có tồn tại lỗi đã biết nào liên quan đến ảnh hưởng của HSE+PLL sau chế độ dừng lên tín hiệu SOF (USB frame sync) hoặc miền clock 48MHz không?
  4. Nên dùng HSI trước rồi chuyển sang PLL khi khôi phục clock USB, hay luôn ưu tiên ổn định PLL trước tiên?
  5. Những vấn đề cấp bo mạch nào có thể gây hiện tượng này: dung sai điện trở D+, diode ESD/rò rỉ, tải thạch anh quá mức, đáp ứng quá độ LDO kém?
1 Lượt thích

Xin chào,

Đây là một vấn đề kinh điển và cực kỳ khó chịu với thiết bị series F1 và việc USB xác định lại thiết bị sau khi thoát khỏi chế độ STOP! Bạn chắc chắn không hề điên rồ, và đã thử mọi bước khắc phục sự cố tiêu chuẩn. Tính chất ngắt quãng (tỷ lệ tái hiện 10–30%) cho thấy đây là vấn đề về thời gian/mất ổn định tạm thời (metastability) tinh tế trong quá trình cấu hình lại xung nhịp phức tạp.

Dưới đây là phân tích của tôi và những điểm cần tập trung, đặc biệt liên quan đến miền xung nhịp của ngoại vi USB và độ ổn định xung nhịp 48 MHz.


:alarm_clock: Kiểm tra tính hợp lý chuỗi xung nhịp & Miền 48 MHz

Chuỗi cấu hình RCC của bạn trông tiêu chuẩn và chính xác khi kích hoạt lại HSE/PLL. Vấn đề không nằm ở PLL, mà ở cách ngoại vi USB (cần nguồn xung nhịp 48 MHz lấy từ PLL / 1.5) xử lý quá trình chuyển tiếp từ trạng thái tắt sang hoạt động tốc độ đầy đủ.

  1. Ràng buộc nguồn xung nhịp USB: Xung nhịp USB FS phải lấy từ PLL, cụ thể là PLLCLK / 1.5 để đạt 48\text{ MHz}. Bất kỳ sự mất ổn định nào của PLL, dù ngắn, cũng có thể làm hỏng các gói Start-of-Frame (SOF) đầu tiên mà thiết bị gửi đi, khiến máy chủ bỏ qua thiết bị (zombie CDC).
  2. Đồng bộ hóa xung nhịp lại: Chuỗi __HAL_RCC_USB_FORCE_RESET()__HAL_RCC_USB_RELEASE_RESET() của bạn hợp lý, nhưng có thể quá ngắn hoặc không đồng bộ với thời gian khóa PLL sau khi thiết bị thức dậy. Datasheet F103 chỉ ra thời gian khởi động HSE và thời gian khóa PLL riêng biệt. Về cơ bản bạn đang thực hiện:
    • Thức dậy → Khởi động HSE/PLL
    • … (PLL khóa) …
    • Kích hoạt xung nhịp USB + Xung reset
    • … (Khởi tạo USBD ngay lập tức) …

Nếu PLL vừa mới ổn định khi bạn giải phóng reset USB, xung nhịp ban đầu vẫn có thể không ổn định. Khoảng trễ 2ms có thể chưa đủ để đảm bảo toàn bộ chuỗi xung nhịp PLL/USB ổn định hoàn toàn.

:hammer: Giải pháp đề xuất: Thao tác ngắt/kết nối

Quan sát của bạn về việc kéo thấp D+ giúp thiết bị phục hồi là chìa khóa. Việc ép thiết bị ngắt/kết nối vật lý thường là cách đáng tin cậy duy nhất để báo cho máy chủ rằng thiết bị đã biến mất rồi xuất hiện lại.

Tại sao hiệu quả: Máy chủ xử lý việc xác định lại thiết bị USB. Nếu xung nhịp thiết bị không ổn định trong chu kỳ thức dậy, máy chủ có thể thấy thiết bị không phản hồi (hoặc dữ liệu nhiễu) và đơn giản bỏ qua thiết bị, giữ cổng ở trạng thái ‘tạm dừng’ hoặc ‘lỗi’, chờ reset vật lý (mà nút NRST của bạn cung cấp). Việc ngắt/kết nối mềm buộc máy chủ khởi động lại máy trạng thái xác định cổng USB.

Hãy thử chuỗi sau khi xung nhịp đã ổn định:

  1. Ổn định xung nhịp: Đảm bảo HSE/PLL đã hoàn toàn ổn định và \\text{SYSCLK} = 72\\text{ MHz} (khối RCC hiện tại của bạn).
  2. Vô hiệu hóa USB (ngắt mềm):
    • Tắt điện trở kéo lên D+ (nếu dùng điện trở kéo lên tích hợp). Với Blue Pill/dùng điện trở 1.5 k$\Omega$ ngoài trên D+, bạn cần chủ động kéo D+ xuống thấp qua GPIO trong thời gian ngắn.
    • Cấu hình chân D+ thành GPIO đầu ra thấp.
    • HAL_Delay(10); (10ms là khoảng thời gian ngắt tối thiểu đáng tin cậy phổ biến).
  3. Khởi tạo lại USB (kết nối lại):
    • Cấu hình chân D+ trở lại chức năng USB (hoặc bật lại điện trở kéo lên tích hợp).
    • Thực hiện chuỗi khởi tạo lại USB đầy đủ:
      USBD_DeInit(&hUsbDeviceFS);
      __HAL_RCC_USB_FORCE_RESET();
      HAL_Delay(2); // Vẫn là một ý kiến hay
      __HAL_RCC_USB_RELEASE_RESET();
      __HAL_RCC_USB_CLK_ENABLE();
      
      USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
      // ... (phần khởi tạo còn lại)
      USBD_Start(&hUsbDeviceFS);
      
    • Máy chủ giờ sẽ thấy điện trở kéo lên D+ và bắt đầu xác định lại thiết bị.

Có phải là phương pháp chuẩn? Với series F1, đúng vậy - ép ngắt mềm khi thức dậy từ chế độ STOP để đảm bảo xác định lại USB đáng tin cậy là giải pháp thực tế đã được kiểm chứng.


:magnifying_glass_tilted_right: Vấn đề cấp bo mạch

Dù nguồn điện của bạn ổn định (\u003c20\\text{ mVpp} nhiễu là rất tốt), hãy xem xét các điểm sau:

  • Tụ tải tinh thể (22 pF): Với tinh thể HC-49 8 MHz, 22\\text{ pF} nằm ở mức cao và có thể tăng nhẹ thời gian khởi động HSE hoặc ảnh hưởng đến độ ổn định tần số ngay sau khi thức dậy. Hầu hết tinh thể 8 MHz khuyên dùng tụ tải 10\\text{ pF} đến 15\\text{ pF}. Đây là khả năng ít xảy ra, nhưng đáng kiểm tra datasheet tinh thể để xác định giá trị \\text{C}_{\\text{L}} (điện dung tải) khuyến nghị.
  • Độ chính xác điện trở D+: Điện trở kéo lên 1.5\\text{ k}\\Omega của bạn chính xác. Đảm bảo nó không sai lệch đáng kể (ví dụ: \\text{+/-} 5\\%) vì điện trở này báo cho máy chủ đây là thiết bị Full-Speed.
  • Diode ESD/rò rỉ: Nếu dùng diode bảo vệ ESD ngoài trên D+/D-, hãy kiểm tra chúng không gây điện dung hoặc dòng rò lớn làm nhiễu tín hiệu 3.3\\text{ V} tinh tế.

Hãy thử nghiệm thao tác ngắt/kết nối D+ qua GPIO kỹ càng. Nó giải quyết tận gốc vấn đề máy chủ mất đồng bộ với trạng thái thức dậy của thiết bị.

Xin chào. Đây là một lỗi phổ biến trên F103 + Blue Pill, nguyên nhân chính do Host không nhận diện được thiết bị đã ngắt kết nối và thiết kế phần cứng có khuyết điểm của Blue Pill.

Dưới đây là giải pháp trực tiếp cho vấn đề của bạn:

1. Nguyên nhân cốt lõi: Điện trở kéo lên D+ và kết nối mềm
Điện trở kéo lên D+ (R10) trên bo mạch Blue Pill thường được hàn trực tiếp vào 3.3V, thay vì được điều khiển bởi GPIO của MCU.

  • Hiện tượng: Khi MCU vào chế độ dừng hoặc reset, D+ vẫn bị kéo cao qua điện trở. Máy chủ (PC) cho rằng thiết bị vẫn kết nối và không làm gì, nên không thực hiện lại quá trình liệt kê (enumeration).
  • Hành động của bạn: Bạn đề cập đến việc “kéo thấp 10ms”, thời gian này quá ngắn. Ngăn xếp giao thức USB của PC (đặc biệt là Windows) thường yêu cầu phát hiện D+ ở mức thấp liên tục trên 100ms (khuyến nghị 500ms-1s) để xác định thiết bị ngắt kết nối (trạng thái SE0).

2. Giải pháp: Chuỗi bắt buộc tái liệt kê
Sau SystemCoreClockUpdate() và trước khi khởi tạo USB, thêm đoạn mã “reset thô” này:

// 1. Bắt buộc cấu hình PA12 (D+) thành GPIO đầu ra mức thấp, mô phỏng rút dây
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();

GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET); // Kéo thấp D+

// 2. Delay đủ lâu để Host nhận diện ngắt kết nối
HAL_Delay(600); // Khuyến nghị 500ms - 1000ms, 10ms tuyệt đối không đủ

// 3. Giải phóng chân, trả lại điều khiển cho ngoại vi USB (khi này điện trở kéo lên sẽ kéo D+ cao lại, kích hoạt Host liệt kê)
// Lưu ý: USBD_Init sau này sẽ cấu hình lại chân thành chức năng thay thế, chỉ cần không còn bắt buộc mức thấp là được

3. Bổ sung về cấu hình xung nhịp
Xung USB của F103 phải là 48MHz.

  • Điểm kiểm tra: Đảm bảo RCC_USBCLKSOURCE_PLL_DIV1_5 được thiết lập đúng trong cấu hình RCC.
  • 72MHz (SysClk) / 1.5 = 48MHz. Nếu sai bước này, USB sẽ không giao tiếp được do sai thời gian, ngay cả khi liệt kê thành công cũng sẽ báo “Thiết bị không được nhận diện”.

4. Trả lời nhanh cho vấn đề của bạn

  • Thứ tự thời gian: HSE → PLL → Flash Latency → SysClk → Bắt buộc kéo thấp D+ 600ms → USB Init.
  • Thực hành tốt nhất: Với các bo mạch không có transistor điều khiển điện trở kéo lên, điều khiển phần mềm PA12 kéo thấp là phương pháp chuẩn duy nhất để “ngắt mềm”.
  • Sửa lỗi: Việc đánh thức từ chế độ dừng của F103 thường không gây lỗi silicon khiến USB chết hoàn toàn, vấn đề chủ yếu nằm ở máy chủ không reset trạng thái.

Khuyến nghị tiếp theo:
Thay đoạn delay 10ms kéo thấp D+ thành 600ms, vấn đề sẽ được giải quyết.

Đối với vấn đề của bạn, tôi đã từng gặp trường hợp tương tự. Sau khi thức tỉnh từ chế độ dừng, ngoại vi USB CDC thường bị lỗi do thứ tự khôi phục đồng hồ và trạng thái ngoại vi USB chưa được đặt lại hoàn toàn. Dưới đây là các điểm chính và đề xuất:

1. Thứ tự khôi phục đồng hồ và bản sửa lỗi (errata)
Sau khi thoát khỏi chế độ dừng, F103 mặc định sử dụng HSI (8MHz) làm đồng hồ hệ thống. Các bước bạn thực hiện để kích hoạt lại HSE và PLL là đúng, nhưng cần lưu ý một yếu tố quan trọng về thời gian: phải đợi PLL khóa (lock) xong mới bật đồng hồ USB. USB FS yêu cầu đồng hồ chính xác 48MHz (do PLL cung cấp). Nếu đồng hồ USB được bật khi PLL chưa ổn định, PHY USB sẽ hoạt động sai và máy chủ không thể nhận diện.

Đề xuất: Sau HAL_RCC_OscConfig, thêm đoạn kiểm tra cờ PLLRDY:

while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET);

Sau đó mới thực hiện HAL_RCC_ClockConfig.

2. Đặt lại hoàn toàn ngoại vi USB
Các lệnh USBD_DeInit và chuỗi đặt lại bạn thực hiện là cần thiết, nhưng thứ tự có thể tối ưu hơn. Bản sửa lỗi của ST (cho một số dòng F1) chỉ ra rằng sau khi thức tỉnh từ chế độ dừng, ngoại vi USB có thể cần xung đặt lại dài hơn. Đề xuất tăng thời gian trễ khi vô hiệu hóa, đặt lại và bật lại đồng hồ USB lên ít nhất 10ms.

Giải pháp đáng tin cậy hơn: Sau khi thức tỉnh, giữ đồng hồ USB ở trạng thái vô hiệu hóa, đợi cho đến khi đồng hồ hệ thống ổn định hoàn toàn (PLL khóa và chuyển xong SYSCLK), rồi thực hiện chuỗi đặt lại cứng sau:

__HAL_RCC_USB_CLK_DISABLE();
__HAL_RCC_USB_FORCE_RESET();
HAL_Delay(10);
__HAL_RCC_USB_RELEASE_RESET();
__HAL_RCC_USB_CLK_ENABLE();
// Sau đó thực hiện USBD_Init và các bước khởi tạo khác

3. Về “ngắt mềm” bằng cách kéo thấp DP
Việc kéo thấp DP trong 10ms để mô phỏng ngắt kết nối là một phương pháp hiệu quả, đồng thời cũng là thực hành phổ biến trên dòng F1. Điều này đảm bảo máy chủ phát hiện ngắt vật lý và kích hoạt lại quá trình enumeration. Để tăng độ tin cậy, đề xuất kiểm tra chắc chắn rằng đồng hồ ngoại vi USB đã dừng trước khi kéo thấp DP, và giữ trạng thái này ít nhất 10-20ms. Đây không phải phương pháp ở cấp độ giao thức, nhưng lại trực tiếp và hiệu quả về mặt phần cứng.

4. Gợi ý chuyển đổi nguồn đồng hồ
Không nên chuyển sang HSI khi USB đang hoạt động. Cách đúng là: Sau khi thức tỉnh, mục tiêu là nhanh chóng khôi phục lại đồng hồ PLL 72MHz ban đầu. Ưu tiên ổn định PLL, đảm bảo khóa thành công, rồi mới xử lý USB. Trong khi PLL chưa ổn định, không cấp đồng hồ cho bất kỳ thành phần nào của USB.

5. Nguyên nhân tiềm ẩn từ phần cứng
Theo mô tả của bạn, khả năng lỗi phần cứng thấp, nhưng có thể kiểm tra:

  • Tụ điện tải dao động (load capacitor): 22pF là giá trị tiêu chuẩn cho thạch anh 8MHz HC-49, nhưng nếu HSE khởi động chậm sau khi thức tỉnh sẽ làm tăng thời gian khóa PLL. Có thể thử giảm tụ xuống 18-20pF và kiểm tra dạng sóng ở hai đầu thạch anh.
  • Đường tín hiệu USB: Điện trở nối tiếp 22Ω là tiêu chuẩn, đảm bảo điện trở kéo lên (1.5kΩ) trên D+ được cấp trực tiếp từ nguồn 3.3V của MCU, và nguồn điện ổn định trong quá trình thức tỉnh.
  • Nguồn điện: Dòng điện tăng đột ngột khi thức tỉnh có thể gây sụt áp nhẹ trên LDO. Dù bạn đo được độ gợn nhỏ, vẫn nên thêm tụ tantal 10µF giữa VDD và GND để cải thiện đáp ứng quá độ.

Tóm tắt và các bước đề xuất

  1. Khôi phục đồng hồ sau khi thức tỉnh: Bật HSE → Đợi HSE sẵn sàng → Bật PLL → Đợi PLL khóa → Chuyển SYSCLK sang PLL → Cập nhật SystemCoreClock.
  2. Đặt lại hoàn toàn USB: Vô hiệu hóa đồng hồ USB trước, đặt lại cứng và giữ ít nhất 10ms, sau đó giải phóng đặt lại và bật lại đồng hồ.
  3. Khởi tạo lại ngăn xếp USB: Gọi USBD_DeInit và chuỗi USBD_Init.
  4. Tùy chọn “ngắt mềm”: Nếu vẫn gặp lỗi đột xuất, trước khi khởi tạo lại USB, cấu hình DP (PA12) thành đầu ra đẩy kéo và kéo thấp ít nhất 15ms, sau đó khởi tạo lại và kéo lên.

Thực hiện theo thứ tự này, khả năng khôi phục USB CDC sau khi thức tỉnh sẽ được cải thiện đáng kể.

2 Lượt thích