Bài 17: Lập trình STM32 USB HID chuột máy tính

lập trình stm32 usb hid giả lập chuột máy tính

Các thiết bị USB HID rất gần gũi với chúng ta, ví dụ như chuột máy tính, bàn phím, sound card… Vi điều khiển STM32 hỗ trợ giao thức HID Device giúp chúng ta có thể lập trình tạo ra các sản phẩm giống như những thiết bị đó. Bài hôm nay mình sẽ nói đến USB HID Mouse, làm thế nào để nó có thể truyền nhận dữ liệu với máy tính, hãy cùng tìm hiểu nhé!

Bài 17 trong Serie Học lập trình STM32 từ A tới Z

USB HID là gì?

HID (viết tắt của Human Interface Device) là một tiêu chuẩn cho các thiết bị máy tính được vận hành bởi con người. Tiêu chuẩn này cho phép sử dụng các thiết bị mà không cần cài thêm phần mềm hoặc driver nào.

HID là một tiêu chuẩn được tạo ra nhằm đơn giản hóa quá trình cài đặt các thiết bị đầu vào như chuột, bàn phím… Một thiết bị tuân thủ HID gửi dữ liệu theo các “gói dữ liệu” (report) có thể chứa tất cả các hành động của thiết bị.

Ví dụ: bàn phím có thể có một phím để điều chỉnh âm lượng. Khi nhấn phím đó, “bộ mô tả HID” (HID descriptor) đã cho máy tính biết từ trước rằng bit nào trong gói tin ứng với phím âm lượng, nên máy tính đọc gói tin là biết cần làm gì.

Cách giao tiếp với tất cả các thiết bị USB HID

Trước tiên nếu bạn chưa bao giờ làm việc với một thiết bị USB thì hãy quay lại Bài 16 để đọc bài viết và tài liệu USB in a Nutshell để có cái nhìn tổng quát nhất.

Thiết bị HID và Host (máy chủ) dùng Control Transfer qua Endpoint 0 để trao đổi descriptor và các request cấu hình. Dữ liệu thật (report) được gửi bằng Interrupt Transfer: endpoint Interrupt IN là bắt buộc, Interrupt OUT là tùy chọn. Đặc tả của lớp HID cho phép truyền dữ liệu ở cả tốc độ Low Speed, Full Speed và High Speed.

Human Interface Device Class hoạt động như thế nào?

Trước khi máy chủ có thể nói chuyện với thiết bị, nó cần biết thiết bị này dùng để làm gì? Dữ liệu của nó được tổ chức như thế nào? Và dữ liệu đó thực sự đo lường điều gì?

Lấy ví dụ: nếu thiết bị của bạn là một con chuột máy tính, các nút bấm và tọa độ sẽ điều khiển con trỏ (Pointer) trên màn hình. Sự kiện click hoặc right click sẽ làm gì, scroll sẽ làm gì. Để tất cả các sự kiện đó xảy ra, trình điều khiển lớp HID phải biết rõ:

  • Thiết bị HID kết nối sẽ điều khiển Pointer của máy tính
  • Có 3 nút trên thiết bị và khi nhấn tương ứng với các chức năng của Pointer
  • Có 2 byte dữ liệu tọa độ sẽ thay đổi vị trí của Pointer

Tất cả các thông tin này được mô tả trong phần Report Descriptor. Khi trình điều khiển phân tích Report Descriptor, nó sẽ hiểu được khi chuột truyền dữ liệu lên thì byte nào, bit nào mang ý nghĩa gì (tương tự như bạn tự quy ước khung dữ liệu khi truyền UART vậy).

Khi một thiết bị HID được kết nối, Host sẽ gửi các request GET_DESCRIPTOR để đọc descriptor của thiết bị. Sau khi hoàn tất quá trình này, chuột và máy tính sẽ giao tiếp với nhau mà không cần thêm driver gì cả.

Cấu trúc của Report Descriptor

Report Descriptor được mô tả bởi chuỗi các mục (item), các mục này mô tả dữ liệu sẽ truyền đi khi thiết bị USB HID truyền hoặc nhận. Mỗi mục bắt đầu bằng 1 byte tiền tố quy định loại mục và độ dài dữ liệu của nó.

Các mục chia làm 3 loại chính:

  • Main: mô tả thực tế dữ liệu truyền đi và nơi dữ liệu được sử dụng. Các mục Global và Local có chức năng bổ nghĩa cho Main
  • Global: mô tả thuộc tính của tất cả các mục Main phía sau nó, cho đến khi có 1 mục Global khác xuất hiện
  • Local: mô tả thuộc tính chỉ của mục Main ngay phía sau nó.

Mỗi loại bao gồm một số mục chính như:

Với Main item:

  • Input: mô tả dữ liệu truyền từ thiết bị lên Host như sự kiện nhấn nút, dữ liệu cảm biến…
  • Output: mô tả dữ liệu từ Host truyền về thiết bị như điều khiển LED, động cơ…
  • Feature: mô tả dữ liệu dùng để cấu hình thiết bị, như tăng giảm tốc độ nháy của LED, tốc độ động cơ…
  • Collection và End Collection: mỗi thiết bị HID phải có ít nhất 1 Application Collection, để trình điều khiển biết được dữ liệu đang dùng cho ứng dụng nào. Ví dụ: 1 thiết bị có 3 tính năng chuột, bàn phím và nút nguồn thì phải có 3 Application Collection để phân biệt dữ liệu gửi đi và trả về.

Với Global item:

  • Usage Page: mô tả nhóm chức năng lớn nhất của thiết bị như Generic Desktop Controls (điều khiển máy tính để bàn), Game Controls, Telephony…
  • Logical Minimum: giá trị số nguyên nhỏ nhất của Main item
  • Logical Maximum: giá trị số nguyên lớn nhất của Main item
  • Report Size: kích thước của mỗi trường dữ liệu (tính theo bit)
  • Report Count: số lượng trường dữ liệu

Với Local item:

  • Usage: mô tả chi tiết hơn bên trong Usage Page. Ví dụ: Usage Page là Generic Desktop Controls thì Usage có thể là Mouse, Keyboard hoặc System Control.

Mỗi mục sẽ được mã hóa bằng 1 byte tiền tố (giá trị từ 0 – 255).

Cấu trúc của Report Descriptor như sau:

hid class struct

Lấy ví dụ về Report Descriptor cho chuột máy tính như sau:

HIDReportDescriptor

Phần khoanh đỏ cấu hình 3 nút nhấn của chuột, phần khoanh xanh cấu hình tọa độ của chuột.

Phần Usage Page và Usage xác định kiểu thiết bị là Mouse thuộc nhóm Generic Desktop.

Các bạn có thể tham khảo link này để phân tích 1 Report Descriptor: https://www.crifan.com/files/doc/docbook/usb_hid/release/webhelp/hid_report_example_analysis.html

Các tool dùng trong lập trình STM32 USB HID

Phần mềm gửi và nhận dữ liệu HID Terminal:

Link download: HID Terminal

Tool giải mã Report Descriptor:

http://eleccelerator.com/usbdescreqparser/

Cách biến Kit Bluepill thành chuột máy tính với USB HID

Để biến KIT Blue Pill (STM32F103C8T6) thành chuột máy tính, chúng ta cần phân tích dữ liệu mà 1 con chuột truyền lên máy tính.

Cấu trúc dữ liệu truyền lên bao gồm:

  • 3 phím bấm: Left, Right, Middle (nút cuộn), mỗi phím có 2 mức logic 0 và 1
  • 2 biến tọa độ X và Y với giá trị từ -127 đến 127

Chúng ta sẽ sử dụng Joystick, đọc ADC 2 kênh X, Y, và nút nhấn trên Joystick sẽ tương ứng với phím Left Click.

joystick ps2

Mỗi khi có sự kiện nhấn nút hoặc di chuyển, chúng ta sẽ gửi dữ liệu theo gói thông qua USB HID.

Lập trình STM32 USB HID như 1 con chuột (Mouse) điều khiển con trỏ máy tính

Trong bài này mình sẽ sử dụng Joystick đọc giá trị ADC biểu thị trục X, Y. Và nút nhấn trên Joystick biểu thị cho Left Button trên chuột máy tính.

Cấu hình STM32 USB HID trên Cube MX

Mở CubeMX, chọn chip STM32F103C8T6, trong System Core:

USB HID
SYS chọn Debug Serial Wire: cho phép debug qua ST-Link

 

USB HID 2
RCC chọn HSE: thạch anh ngoài
USB HID 3
GPIO chọn PA2 là nút nhấn Pull Up

Trong Tab Analog ta sẽ cấu hình ADC1 để đọc giá trị Joystick

USB HID 4
Chọn 2 kênh IN0 (PA0) và IN1 (PA1). Bật chế độ Scan và Continuous. Nhớ chọn Number of Conversion là 2 và chỉnh Channel của Rank 1, Rank 2 về đúng 0 và 1
USB HID 5
Thêm DMA từ ADC về Memory, chế độ Circular và Data Width là Half Word

Thiết lập USB

USB HID 6
Chọn USB Device (FS), USB chế độ Full Speed
USB HID 7
Chọn HID trong Class của Middleware

Thiết lập Clock cho USB là 48MHz (bắt buộc)

USB HID 8

Đặt tên Project rồi Gen code như tất cả các bài trước

USB HID 9

Lập trình STM32 USB HID trên Keil C

Mở Project bằng Keil C. Đầu tiên chúng ta thêm thư viện HID vào main.c bằng lệnh #include "usbd_hid.h"

USB HID lap trinh 1

Sau đó extern biến chứa cấu hình USB (hUsbDeviceFS) vào main.c

USB HID lap trinh 2

Tìm hiểu HID_MOUSE_ReportDesc

Khi chọn chế độ USB HID, CubeMX đã mặc định thiết bị HID là chuột máy tính. Chúng ta cùng phân tích Report Descriptor mà CubeMX đã cho sẵn nhé. Các bạn vào file usbd_hid.c và tìm đoạn code như sau:

USB HID lap trinh 3

Copy đoạn dữ liệu và paste vào công cụ phân tích mình vừa nêu ở trên: http://eleccelerator.com/usbdescreqparser/

USB HID lap trinh 4

Kết quả phân tích sẽ như sau:

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x02,        // Usage (Mouse)
0xA1, 0x01,        // Collection (Application)
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x05, 0x09,        //     Usage Page (Button)
0x19, 0x01,        //     Usage Minimum (0x01)
0x29, 0x03,        //     Usage Maximum (0x03)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x01,        //     Logical Maximum (1)
0x95, 0x03,        //     Report Count (3)
0x75, 0x01,        //     Report Size (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01,        //     Report Count (1)
0x75, 0x05,        //     Report Size (5)
0x81, 0x01,        //     Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x09, 0x38,        //     Usage (Wheel)
0x15, 0x81,        //     Logical Minimum (-127)
0x25, 0x7F,        //     Logical Maximum (127)
0x75, 0x08,        //     Report Size (8)
0x95, 0x03,        //     Report Count (3)
0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0x09, 0x3C,        //   Usage (Motion Wakeup)
0x05, 0xFF,        //   Usage Page (Reserved 0xFF)
0x09, 0x01,        //   Usage (0x01)
0x15, 0x00,        //   Logical Minimum (0)
0x25, 0x01,        //   Logical Maximum (1)
0x75, 0x01,        //   Report Size (1)
0x95, 0x02,        //   Report Count (2)
0xB1, 0x22,        //   Feature (Data,Var,Abs,No Wrap,Linear,No Preferred State,No Null Position,Non-volatile)
0x75, 0x06,        //   Report Size (6)
0x95, 0x01,        //   Report Count (1)
0xB1, 0x01,        //   Feature (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0,              // End Collection

// 74 bytes

Ta thấy rằng gói dữ liệu Input mà chuột gửi lên gồm 4 byte theo thứ tự:

1 byte nút nhấn (3 bit cho 3 nút + 5 bit đệm) -> 1 byte X -> 1 byte Y -> 1 byte Wheel

Số bit của mỗi phần bạn tính bằng Report Count × Report Size của từng mục Input, theo thứ tự từ trên xuống dưới: 3×1 + 1×5 = 8 bit (1 byte nút), rồi 3×8 = 24 bit (3 byte X, Y, Wheel). Phần Motion Wakeup phía sau là Feature (Host đọc/ghi qua Control Transfer), còn End Collection chỉ là mục đóng nhóm, cả hai đều không nằm trong gói tin gửi lên.

Vậy nên chúng ta sẽ tạo 1 mảng chứa giá trị các byte gửi lên, mình đặt tên là mouse_report, và 1 mảng lưu 2 giá trị ADC1 truyền qua DMA.

USB HID lap trinh 6

Lưu ý: Endpoint IN của HID mà CubeMX sinh ra có kích thước HID_EPIN_SIZE = 4 byte. Dù mảng mouse_report khai báo dài hơn, khi gọi USBD_HID_SendReport() hãy truyền độ dài đúng 4 byte, gửi dài hơn thì Host có thể hiểu sai gói tin.

Trước hàm main() ta sẽ lập trình như sau:

Tạo một hàm tên là map để chuyển đổi giá trị ADC từ 0 – 4095 thành -127 đến 127

USB HID lap trinh 7 1

 

Tạo hàm đọc giá trị nút nhấn, thực hiện chuyển đổi và ghi vào mảng mouse_report

Trước while, cho bắt đầu chuyển đổi ADC DMA. Trong while, ta đọc giá trị của chuột và gửi qua cổng USB HID.

USB HID lap trinh 8

Kết nối và chạy thử USB HID

Nhấn F7 để Build và F8 để nạp code vào KIT.

Kết nối Joystick vào KIT theo hướng dẫn (chân bên trái là chân trên module Joystick, bên phải là chân trên Blue Pill):

5V (VCC của Joystick) —> 3.3V

GND —> GND

VRX —> PA0

VRY —> PA1

SW —> PA2

Lưu ý: Chân VCC của Joystick tuy in chữ 5V nhưng phải cấp 3.3V. Nếu cấp 5V, điện áp VRX/VRY sẽ lên tới 5V, vượt quá dải đo 0 – 3.3V của ADC và có thể làm hỏng chân PA0, PA1 (2 chân này không chịu được 5V).

Cắm dây Micro USB vào mạch và cắm đầu còn lại vào máy tính. Mở Device Manager ra xem, bạn sẽ thấy thêm 1 thiết bị Mouse

USB HID chay thu

Mở Paint, sử dụng Joystick điều khiển thử.

Ta thấy rằng chuột chạy quá nhanh, không thể kiểm soát, và khi không di chuyển Joystick thì chuột vẫn tự chạy. Lý do là điểm cân bằng thực tế của Joystick không đúng bằng giá trị lý thuyết 2048.

Chuột chạy quá nhanh vì giá trị X, Y là độ dịch chuyển tương đối (Rel), mạch gửi các giá trị lên tới -127 đến 127 liên tục khi giữ Joystick, nên mỗi gói tin con trỏ nhảy cả trăm điểm ảnh. Vậy làm thế nào để hiệu chỉnh hai thứ đó?

Tinh chỉnh code (Calibration)

Rút dây USB ra khỏi máy tính (để chuột không chạy lung tung), chạy chế độ debug qua ST-Link.

Trong debug ta add biến ADC_Val vào Watch 1 và nhấn chạy chương trình. Chúng ta thấy rằng giá trị ở vị trí cân bằng của mỗi trục khác 2048, dẫn tới chuột luôn luôn di chuyển.

USB HID calib 1

Sửa lại trong hàm Get Action như sau:

USB HID calib 2

Sửa -127, 127 thành -10 và 10 sẽ giúp chuột di chuyển chậm hơn.

Nạp và chạy thử kết quả

Kết

STM32 USB HID được sử dụng rất rộng rãi khi muốn giao tiếp với máy tính, điện thoại, gamepad một cách đơn giản nhất. Về cơ bản tất cả các thiết bị USB HID đều làm việc giống nhau, sự khác nhau của chúng là cấu trúc gói tin truyền và ý nghĩa của nó, được định nghĩa hết trong Report Descriptor.

Nếu thấy bài viết này có ích hãy like và chia sẻ cho người khác, đừng quên vào nhóm Anh Em Nghiện Lập Trình để giao lưu nhé các bạn

4.4/5 - (7 bình chọn)

2 những suy nghĩ trên “Bài 17: Lập trình STM32 USB HID chuột máy tính”

  1. Hải nói:

    A ơi e có tải phần code của a trên github ý nạp vào stm32 thì lúc chạy nó chỉ chạy 1 đường chéo chứ ko dùng analog được ạ a giải đáp giùm e ạ

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *