我正在尝试在 Raspberry Pi Pico 上使用 TinyUSB 开发自定义 UPS,并让 UPower 在我的 Ubuntu 主机上发现它。
目前我的设备已被检测到,但主机未读取任何信息:
root@user-HP:~# upower -i /org/freedesktop/UPower/devices/ups_hiddev0
native-path: /sys/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.0/usbmisc/hiddev0
vendor: APC
model: UPS
serial: 123456
power supply: yes
updated: Sun, 02 Apr 2023 09:35:04 +0300 (16 seconds ago)
has history: yes
has statistics: yes
ups
present: yes
state: empty
warning-level: none
percentage: 0%
icon-name: 'battery-empty-symbolic'
我的期望是
state
将是charging
(请参阅下面的测试报告)。
2023 年 4 月 5 日编辑:根据 aja 的建议更新了报告描述符
设备的报告描述符如下所示:
05 84 09 04 a1 01 09 10 a1 00 85 01 75 08 95 01 09 fd 79 01 b1 03 09 fe 79 02 b1 03 09 ff 79 03 b1 03 09 12 a1 00 85 02 05 85 75 01 95 04 17 00 00 00 00 27 01 00 00 00 09 44 09 45 09 46 09 47 81 82 95 01 75 04 81 01 c0 c0 c0
INPUT(2)[INPUT]
Field(0)
Physical(Power Device.Battery)
Application(Power Device.UPS)
Usage(4)
Battery System.Charging
Battery System.Discharging
Battery System.0046
Battery System.0047
Logical Minimum(0)
Logical Maximum(1)
Report Size(1)
Report Count(4)
Report Offset(0)
Flags( Variable Absolute Volatile )
FEATURE(1)[FEATURE]
Field(0)
Physical(Power Device.BatterySystem)
Application(Power Device.UPS)
Usage(1)
Power Device.iManufacturer
Report Size(8)
Report Count(1)
Report Offset(0)
Flags( Constant Variable Absolute )
Field(1)
Physical(Power Device.BatterySystem)
Application(Power Device.UPS)
Usage(1)
Power Device.iProduct
Report Size(8)
Report Count(1)
Report Offset(8)
Flags( Constant Variable Absolute )
Field(2)
Physical(Power Device.BatterySystem)
Application(Power Device.UPS)
Usage(1)
Power Device.iSerialNumber
Report Size(8)
Report Count(1)
Report Offset(16)
Flags( Constant Variable Absolute )
Battery System.Charging ---> Sync.Report
Battery System.Discharging ---> Sync.Report
Battery System.0046 ---> Sync.Report
Battery System.0047 ---> Sync.Report
解析:
USAGE_PAGE (Power Device)
USAGE (UPS)
COLLECTION (Application)
USAGE (Battery System)
COLLECTION (Physical)
REPORT_ID (1)
REPORT_SIZE (8)
REPORT_COUNT (1)
USAGE (iManufacturer)
STRING_INDEX (1)
FEATURE (Constant Variable Absolute)
USAGE (iProduct)
STRING_INDEX (2)
FEATURE (Constant Variable Absolute)
USAGE (iSerialNumber)
STRING_INDEX (3)
FEATURE (Constant Variable Absolute)
USAGE (Battery)
COLLECTION (Physical)
REPORT_ID (2)
USAGE_PAGE (Battery System)
REPORT_SIZE (1)
REPORT_COUNT (4)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
USAGE (Charging)
USAGE (Discharging)
USAGE (Fully Charged)
USAGE (Fully Discharged)
INPUT (Variable Absolute Volatile)
REPORT_COUNT (1)
REPORT_SIZE (4)
INPUT (Constant Array)
END_COLLECTION
END_COLLECTION
END_COLLECTION
Linux 似乎无法识别“完全充电”和“完全放电”标志。我还尝试删除它们并仅保留 charging 标志,以及添加其他数据点,例如 绝对充电状态。
在我的 main.cpp
文件中,我发送如下报告:
#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "bsp/board.h"
#include "tusb.h"
int main() {
board_init();
tusb_init();
uint32_t last_run = 0;
uint8_t report1[] = {1, 2, 3};
uint8_t report2[] = {0b00000001};
while (1) {
tud_task();
// Send data every 50 ms
if (board_millis() - last_run > 50 && tud_hid_ready() && !tud_suspended()) {
last_run = board_millis();
// tud_hid_report(1, report1, sizeof(report1)); // for some reason if I send 2 reports at once, only #1 is received on the host
tud_hid_report(2, report2, sizeof(report2));
}
}
}
// Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) {
switch (report_id) {
case 1:
buffer[0] = 1;
buffer[1] = 2;
buffer[2] = 3;
return 3;
case 2:
buffer[0] = 0b00000001; // Report charging state
return 1;
default:
return 0;
}
}
// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {
// echo back anything we received from host
tud_hid_report(0, buffer, bufsize);
}
tud_hid_get_report_cb
或使用循环和
tud_hid_report
。任何有关此的提示将不胜感激。
接收端的报告对我来说看起来是正确的:root@user-HP:~# usbhid-dump -a 1:119 -e all
001:119:000:DESCRIPTOR 1680420804.631776
05 84 09 10 A1 01 85 01 75 08 95 01 09 FD 79 01
B1 03 09 FE 79 02 B1 03 09 FF 79 03 B1 03 09 12
A1 00 85 02 05 85 75 01 95 04 17 00 00 00 00 27
01 00 00 00 09 44 09 45 09 46 09 47 81 82 95 01
75 04 81 01 C0 C0
Starting dumping interrupt transfer stream
with 1 minute timeout.
001:119:000:STREAM 1680420804.643196
02 01
001:119:000:STREAM 1680420804.694348
02 01
001:119:000:STREAM 1680420804.745391
02 01
001:119:000:STREAM 1680420804.796426
02 01
001:119:000:STREAM 1680420804.847433
02 01
001:119:000:STREAM 1680420804.898256
02 01
编辑 2023 年 4 月 5 日:我使用 Wireshark 进行了一些调试,并了解了一些有关 USB 端点的知识,我对以下文件进行了一些更改:将 TUD_HID_DESCRIPTOR
更改为 TUD_HID_INOUT_DESCRIPTOR
,并将端点编号更改为 1。现在,从设备到主机的传输完成并显示成功状态,而不是
No such file or directory
。但是,存在一个问题:主机使用 0x81
IN 端点而不是正确的 0x01
OUT 端点向设备发送报告。
最后,这是我的
usb_descriptors.cpp
:
#include "tusb.h"
#include "report-descriptor/descriptor.hpp"
//--------------------------------------------------------------------+
// Device Descriptors
//--------------------------------------------------------------------+
tusb_desc_device_t const desc_device = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
// Using APC vendor ID, because a UPS from an unknown vendor won't be detected
.idVendor = 0x051D,
.idProduct = 0x0003,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
// Invoked when received GET DEVICE DESCRIPTOR
// Application return pointer to descriptor
uint8_t const *tud_descriptor_device_cb(void) {
return (uint8_t const *)&desc_device;
}
//--------------------------------------------------------------------+
// HID Report Descriptor
//--------------------------------------------------------------------+
// Invoked when received GET HID REPORT DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) {
return hid_report_descriptor_test.data();
}
//--------------------------------------------------------------------+
// Configuration Descriptor
//--------------------------------------------------------------------+
enum {ITF_NUM_HID, ITF_NUM_TOTAL};
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN)
#define EPNUM_HID 0x01
uint8_t const desc_configuration[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
TUD_HID_INOUT_DESCRIPTOR(
ITF_NUM_HID,
0,
HID_ITF_PROTOCOL_NONE,
hid_report_descriptor_test.size(),
EPNUM_HID,
0x80 | EPNUM_HID,
CFG_TUD_HID_BUFSIZE,
1
)
};
// Invoked when received GET CONFIGURATION DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_descriptor_configuration_cb(uint8_t index)
{
(void)index; // for multiple configurations
return desc_configuration;
}
//--------------------------------------------------------------------+
// String Descriptors
//--------------------------------------------------------------------+
// array of pointer to string descriptors
char const *string_desc_arr[] = {
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"American Power Conversion", // 1: Manufacturer
"UPS", // 2: Product
"123456", // 3: Serials, should use chip ID
"LiFePO4", // 4: Battery chemistry - unused for now
};
static uint16_t _desc_str[32];
// Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
{
(void)langid;
uint8_t chr_count;
if (index == 0)
{
memcpy(&_desc_str[1], string_desc_arr[0], 2);
chr_count = 1;
}
else
{
// Convert ASCII string into UTF-16
if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])))
return NULL;
const char *str = string_desc_arr[index];
// Cap at max char
chr_count = strlen(str);
if (chr_count > 31)
chr_count = 31;
for (uint8_t i = 0; i < chr_count; i++)
{
_desc_str[1 + i] = str[i];
}
}
// first byte is length (including header), second byte is string type
_desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2);
return _desc_str;
}
我了解到您有一个设备并希望向主机发送 HID 报告。您是否尝试过主机是否可以向设备发送报告?我有一个类似的设置,但不是在 Raspberry Pi 上,而是在 STM32G0B1 上,我尝试为 TinyUSB 为低级驱动程序创建一个端口。就我而言,主机可以发送 HID 报告,然后该报告会到达设备。对我来说,这是一种心跳消息。