Giao tiếp usb là giao tiếp rất tiện lợi giúp cho các thiết bị kết nối với máy tính nhanh chóng tiện lợi. Ngoài USB CDC. các bạn có thể phát triển ứng dụng theo hướng USB HID, điểm lợi so với USB CDC là phần mềm sẽ tự động nhận dạng và kết nối với thiết bị ngay lập tức mà không cần phải kiểm tra thủ công qua cổng COM, hơn nữa USB HID STM32 sẽ giúp bạn cá nhân hóa sản phẩm làm cho project trở nên chuyên nghiệp hơn.
USB Custom HID là dạng giao tiếp usb do người dùng tùy biến do vậy, mình đã cấu hình và chỉnh sửa sẵn thư viện usb_hid_device để có thể hoạt động được ngay mà không cần chỉnh sửa gì hết
Tạo project trên chip stm32
Trong bài này mình sẽ dùng kit stm32f103c8t6 bluepill
Các bạn mở cubemx và tạo project như bình thường
Đừng quên tích chọn Serial Write để có thể nạp chương trình và debug
Tích chọn USB Device (FS)
Chúng ta sẽ sử dụng thư viện Muddlewave USB_DEVICE cho cubemx hỗ trợ, các bạn chọn class là Custom Human Interface Device Class
Sau đó sinh code với KeilC hoặc tool khác nếu các bạn muốn
COPY thư viện Custom HID by IoT47
Do các gói thư viện mà ST cung cấp luôn được update thường xuyên nên những gì mình hướng dẫn có thể sẽ không chạy được ở phiên bản CubeMx mà các bạn dùng trong tương lại, do vậy hãy copy đè thư viện USB mà mình cung cấp vào project của bạn để đảm bảo hoạt động chính xác
Các bạn tải thư viện tại đây
Ngoài ra thư mục project do CubeMX sinh ra cho mình như sau
Nếu của bạn khác thì có lẽ phiên bản cubemx bạn đang dùng là bản cũ. Các bạn nên update lên phiên bản cubemx mới nhất ( phiên bản cubemx của mình là 6.1.0)
Tiến hành copy đè như sau: ( chú ý màu cam là file/thư mục của mình, màu xanh là file/thư mục của CUBEMX sinh ra cho các bạn
- Ghi đè hoàn toàn thư mục STM32_USB_Device_Library vào Middlewares/ST
- Ghi đèn hoàn toàn thư muc USB_DEVICE vào thư muc USB_DEVICE
- Ghi đè 2 file stm32f1xx_hal_pcd.c và stm32f1xx_ll_usb.c trong thư mục Driver vào thư mục Driver/STM32F1xx_HAL_Driver/Src
- Ghi đè 2 file stm32f1xx_hal_pcd.h và stm32f1xx_ll_usb.h ở trong thư mục Driver vào thư mục Driver/STM32F1xx_HAL_Driver/Inc
Vậy là xong, tiến hành biên dịch và nạp code, kết nối cổng usb vào máy tính. Trong phần Setting – Devices của win10, máy tính đã nhận ra thiết bị USB có tên là IOT47 USB STM32 Demo
Ngoài ra, trong Device Manger máy tính đã nhận dạng và thêm nó vào phân lớp Human Interface Devices (HID)
Tùy biến tên thiết bị của bạn và VID PID
Máy tính đã nhận ra thiết bị của mình với tên gọi là IOT47 USB HID Demo, để đổi tên này, các bạn thay đổi trong file usbd_desc.c
Các bạn cũng nên thay đổi cả chỉ số VID và PID
Giao tiếp truyền nhận dữ liệu với máy tính
Phân lớp USB HID được window hỗ trợ nên hoàn toàn không cần cài driver gì cả. Tuy nhiên ngôn ngữ lập trình c# được thiết kế để che đi phần cứng rất nhiều và việc dùng c# để lập trình giao tiếp với 1 thiết bị ở phân lớp HID là khá khó khăn. Do vậy thay vì giao tiếp c# với stm32 trực tiếp ở lớp HID – USB Input Device. Mình lập trình thông qua 1 thư viện driver được gọi là LibUSBDotNet
Tải LibUSBDotNet tại đây
Sau khi tải về, các bạn giải nén và vào thư mục bin, chạy phần mềm inf-wizard. Phần mềm này sẽ giúp chúng ta tạo ra file driver ( file driver này các bạn sẽ đưa cho người dùng cài đặt vào máy tính của họ)
Lúc này, phần mềm sẽ hiện ra danh sách các thiết bị usb đang được kết nối với máy tính. Mình sẽ chọn tới thiết bị USB của mình có tên là IOT47 USB STM32 Demo và số VID là 0x0483 (1155) và PID là 0x5762 (22370)
Chọn ví trí lưu, sau đó ấn Install
Vậy là xong, driver đã được cài vào máy tính của bạn
Lúc này thiết bị USB cũng không nằm trong mục Human Interface devices nữa mà nằm trong lớp libusb-win32 devices
Lập trình window form trên Visual Studio
Sau khi khởi tạo project c#, các bạn tiến hành cài đặt thư viện libusbdotnet cho c#
Click chuột phải vào tên tên project trong ở bảng Solution Explorer -> vào Manage NuGet Packages for Solution.
Sau đo chọn tab Browser và gõ tìm kiếm libusbdotnet
Các bạn nhấn install để cài đặt thư viện libusbdotnet vào cho c#
Tạo giao diện truyền nhận dữ liệu usb
Ở đây mình tạo 1 giao diện rất đơn giản gồm có 1 ô truyền dữ liệu đi và 1 ô show dữ liệu nhận đc
Trong file code, add thư viện libusbdotnet vô
1 2 |
using LibUsbDotNet; using LibUsbDotNet.Main; |
Khai báo 1 số biến toàn cục
1 2 3 4 |
public static UsbDevice MyUsbDevice; public static UsbDeviceFinder MyUsbFinder = new UsbDeviceFinder(1155, 22370); UsbEndpointReader reader; UsbEndpointWriter writer; |
Trong đó 1155 là VID và 22370 là PID ( nó phải trùng với vid và pid của stm32 nhé)
Trong sự kiện ấn vào nút kết nối chúng ta sẽ khởi tạo cổng usb và kết nối với thiết usb phía dưới. Nếu kết nối thành công thì sẽ chuyển thành chữ “Ngắt kết nối”, ngược lại thì báo lỗi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
private void button2_Click(object sender, EventArgs e) { if (button2.Text == "Kết nối") //kết nối { try { MyUsbDevice = UsbDevice.OpenUsbDevice(MyUsbFinder); if (MyUsbDevice == null) throw new Exception("Device Not Found."); IUsbDevice wholeUsbDevice = MyUsbDevice as IUsbDevice; if (!ReferenceEquals(wholeUsbDevice, null)) { wholeUsbDevice.SetConfiguration(1); wholeUsbDevice.ClaimInterface(0); } reader = MyUsbDevice.OpenEndpointReader(ReadEndpointID.Ep01); writer = MyUsbDevice.OpenEndpointWriter(WriteEndpointID.Ep01); reader.DataReceived += (OnRxEndPointData); reader.DataReceivedEnabled = true; button2.Text = "Ngắt kết nối"; } catch { MessageBox.Show("error"); } } else // ngắt kết nối { reader.DataReceivedEnabled = false; reader.DataReceived -= (OnRxEndPointData); reader.Dispose(); writer.Dispose(); if (MyUsbDevice != null) { if (MyUsbDevice.IsOpen) { IUsbDevice wholeUsbDevice = MyUsbDevice as IUsbDevice; if (!ReferenceEquals(wholeUsbDevice, null)) { wholeUsbDevice.ReleaseInterface(0); } MyUsbDevice.Close(); } MyUsbDevice = null; UsbDevice.Exit(); } button2.Text = "Kết nối"; } } |
Tiếp theo mình sẽ viết hàm truyền dữ liệu usb xuống khi ấn vào button Truyền
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private void button1_Click(object sender, EventArgs e) { if(textBox1.Text.Length > 63) { MessageBox.Show("Gửi không quá 63 kí tự"); } try { int bytesWritten; writer.Write(Encoding.Default.GetBytes("\x02" + textBox1.Text), 1000, out bytesWritten); } catch (Exception err) { MessageBox.Show("Can Not Send Data To USB Device\nDetails: " + err); } } |
Và hàm nhận dữ liệu khi có dữ liệu usb từ stm32 truyền lên. Khi đó chúng ta sẽ in nó ra textbox. Do đây là 1 event nên chúng ta sẽ in ra textbox thông qua Invoke để tránh bị lỗi
1 2 3 4 5 6 7 8 9 |
private void OnRxEndPointData(object sender, EndpointDataEventArgs e) { Action<string> Action = addToTextBox; this.BeginInvoke(Action, (Encoding.Default.GetString(e.Buffer, 0, e.Count))); } private void addToTextBox(string input) { textBox2.Text += input.Substring(1); } |
Full code trên c# như sau
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using LibUsbDotNet; using LibUsbDotNet.Main; using System.Text.RegularExpressions; namespace USB_window_form { public partial class Form1 : Form { public Form1() { InitializeComponent(); } public static UsbDevice MyUsbDevice; public static UsbDeviceFinder MyUsbFinder = new UsbDeviceFinder(1155, 22370); UsbEndpointReader reader; UsbEndpointWriter writer; private void OnRxEndPointData(object sender, EndpointDataEventArgs e) { Action<string> Action = addToTextBox; this.BeginInvoke(Action, (Encoding.Default.GetString(e.Buffer, 0, e.Count))); } private void addToTextBox(string input) { textBox2.Text += input.Substring(1); } private void button1_Click(object sender, EventArgs e) { if(textBox1.Text.Length > 64) { MessageBox.Show("Gửi không quá 64 kí tự"); } try { int bytesWritten; writer.Write(Encoding.Default.GetBytes("\x02" + textBox1.Text), 1000, out bytesWritten); } catch (Exception err) { MessageBox.Show("Can Not Send Data To USB Device\nDetails: " + err); } } private void button2_Click(object sender, EventArgs e) { if (button2.Text == "Kết nối") //kết nối { try { MyUsbDevice = UsbDevice.OpenUsbDevice(MyUsbFinder); if (MyUsbDevice == null) throw new Exception("Device Not Found."); IUsbDevice wholeUsbDevice = MyUsbDevice as IUsbDevice; if (!ReferenceEquals(wholeUsbDevice, null)) { wholeUsbDevice.SetConfiguration(1); wholeUsbDevice.ClaimInterface(0); } reader = MyUsbDevice.OpenEndpointReader(ReadEndpointID.Ep01); writer = MyUsbDevice.OpenEndpointWriter(WriteEndpointID.Ep01); reader.DataReceived += (OnRxEndPointData); reader.DataReceivedEnabled = true; button2.Text = "Ngắt kết nối"; } catch { MessageBox.Show("error"); } } else // ngắt kết nối { reader.DataReceivedEnabled = false; reader.DataReceived -= (OnRxEndPointData); reader.Dispose(); writer.Dispose(); if (MyUsbDevice != null) { if (MyUsbDevice.IsOpen) { IUsbDevice wholeUsbDevice = MyUsbDevice as IUsbDevice; if (!ReferenceEquals(wholeUsbDevice, null)) { wholeUsbDevice.ReleaseInterface(0); } MyUsbDevice.Close(); } MyUsbDevice = null; UsbDevice.Exit(); } button2.Text = "Kết nối"; } } } } |
Lập trình truyền nhận trên STM32
Để gửi, các bạn include thư viện include “usbd_customhid.h” vào trong main.c
Bây giờ có thể gọi hàm USBD_CUSTOM_HID_SendReport để gửi dữ liệu
Lưu ý: khi gửi bắt buộc phải gửi 65 byte với byte đầu tiên luôn là 0x01
Do đó mình sẽ khai báo mảng uint8_t USB_TX_Buffer[65]
Mình sẽ liên tục gửi lên 1 tin nhắn là “Iot47 USB hid demo” mỗi 100ms kèm theo 1 biến đếm tăng dần, nên mình khai báo thêm 1 biến int dem=0;
Bây giờ trong hàm main, vòng lặp while(1)
1 2 3 |
sprintf((char *)USB_TX_Buffer,"\x01Iot47 USB hid demo %i\r\n",dem++); USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,USB_TX_Buffer,65); HAL_Delay(100); |
Còn nhận thì sao ?
Khi có dữ liệu gửi tới cho stm32, hàm ngắt CUSTOM_HID_OutEvent_FS trong file usbd_custom_hid_if.c sẽ được gọi.
65 byte dữ liệu ( byte đầu mặc định là 0x02) sẽ được lưu vào mảng USB_RX_Buffer trong file usbd_custom_hid_if.c. Có code gì thì viết vô đó nhé !
Các bạn dán tên mảng USB_RX_Buffer vào Watch1 ( của sổ debug) để coi dữ liệu nhận được cho tiện nhé !
Bây giờ test thử nào !
Tải toàn bộ project stm32 và c# tại đây
Cảm ơn bạn vì (những) bài viết.
Nhờ bạn kiểm tra lại 2 file:
LibUSBDotNet
project stm32
Link không còn tải được.
dạo này google chặn sạch file exe. chắc chuyển sang mediafire quá
hi a,
anh ơi cho em xin file được không ạ, link trên hỏng rồi em ko tải đc.
cảm on a nhiều.
reader.DataReceived += (OnRxEndPointData);
reader.DataReceivedEnabled = true;
báo lỗi nhiều quá bạn ơi
bạn tải toàn bộ project về ở cuối bài tham khảo cho tiện
Do project có chứa file exe nên trình duyệt và window10 hay chặn nên các bạn tắt window defender đi nhé !
Mình upload lại tài liệu lên github cho b nào không tải đc các link trong bài
https://github.com/daonguyen207/usbhidstm32.git
Cảm ơn a nhiều <3 Chúc a có nhiều sức khỏe, công việc thuận lợi, gặp nhiều may mắn trong cuộc sống
Em còn vài bài tập chưa làm được nên có thể nhờ anh một chút được không ạ?Nếu có thể em sẽ trả phí anh nhé! 1. Dùng vi điều khiển STM32F103 giao tiếp với 8 led đơn và 2 nút nhấn ON, OFF. Khicấp điện thì 8 led tắt, khi nhấn ON thì 8 led sáng, khi nhấn OFF thì 8 led tắt. (Dùng ngắt ngoài). 2. Dùng vi điều khiển STM32F103 giao tiếp với 8 led đơn và 3 nút nhấn ON, OFF,INV. Khi cấp điện thì 8 led tắt, khi nhấn ON thì 4 led thấp (D) sáng, khi nhấn INV thì đảo trạng thái của 8 led: 4 sáng thành tắt, 4 tắt thành sáng, khi nhấn OFF thì 8 led tắt.(Dùng ngắt ngoài). 3. Mạch đếm từ 00 đến 99, dùng vi điều khiển STM32F103 kết nối với 2 led 7 đoạn anode chung và 3 nút nhấn Start, Stop. Viết chương trình thực hiện chức năng: khi cấp điện thì led hiển thị 00, khi nhấn Start thì mạch đếm từ 00 đến 99, nếu nhấn Stop thì ngừng tại giá trị đang đếm, nhấn Start thì đếm tiếp (đếm xuống đến 00 thì ngừng ở 00, đếm lên đến 99 thì ngừng ở 99). 4. Hãy thêm vào bài 6 một nút nhấn có tên là INV có chức năng đảo chiều đếm lên, đếm xuống. Đang đếm lên nếu nhấn INV thì sẽ đếm xuống từ giá trị đang đếm, tương tự khi đang đếm xuống mà nhấn INV thì sẽ đếm lên (có 1 led hiển thị chiều đếm, đếm lên led sáng, đếm xuống led tắt, chân tuỳ chọn).
*Yêu cầu: phân tích đề bài, chức năng cần sử dụng, vào CubeMX cấu hình, sinh code, mở Keil C viết code và giải thích ý nghĩa từng dòng code, đoạn code mà mình thêm vào, mở proteus (nối mạch sẵn) nạp code kiểm nghiệm hoạt động của chương trình
Bạn cần hỗ trợ gì thì liên hệ với mình theo thông tin ở dưới blog nhé ! Không cmt chủ để không liên quan tới bài viết