为了让CSR867x的开发更容易,现与思度科技联合推出CSR867x学习板【淘宝链接:思度科技CSR开发板】。

技术交流QQ群号:743434463
开发板会员QQ群号:725398389(凭订单号入群,赠PPT、项目源码、视频教程)
——————————正文分割线———————————–

#1. 引言
常见的PC端与CSR8675的通信方式有USB HID和UART这两种。UART通信方式简单,但在产品结构上需预留专门的硬件接口,给ID设计带来不便。USB HID通信可以与USB音频播放、USB充电功能共用一个硬件接口,是较理想的通信方式。

#2. 基本概念
##2.1. USB HID
Universal Serial Bus(USB)是一种4线连接的通信接口,用于PC与不同设备间的通信互联。这些设备分为不同的类。每种设备有着共同的行为和协议,以提供相似的功能,例如:
|设备类|示例设备|
|:|:|
|显示|监视器|
|通信|调制器|
|音频|扬声器|
|存储|硬盘|
|人机接口|键盘|

Human Interface Device(HID)类设备用来由人控制电脑系统的运行,典型的HID类设备分两大类:

  • 键盘、鼠标、按键、开关、旋钮、进度条、遥控器等与人交互的设备
  • 一些不需要人参与交互,但有着与HID类设备相似的数据格式的设备

USB HID类设备使用相应的HID类驱动来检索和路由数据。数据的路由和检索是通过检查设备描述符及其提供的数据来完成的。

##2.2. HID类设备描述符
HID类设备描述符定义了HID类描述符的数量和长度,例如报告描述符和物理描述符。

报告描述符描述了设备产生的数据的方方面面,以及什么样的数据是正在监控的。通过检查items,HID类驱动程序可以确定来自HID类设备的数据报告的大小和组成。

物理描述符集是可选的描述符,它提供有关用于激活设备上的控件的人体部位的信息。

上述HID描述符是设备描述符结构整体中的一部分:

##2.3. HID类接口描述符
HID有四种功能特性:

  • Class(类):HID的Class必须是3
  • SubClass(子类):0-不支持Boot设备,1-支持Boot设备,
  • Protocol(协议):仅当SubClass为1时有效,0-None,1-键盘,2-鼠标
  • Interface(接口):控制(Endpoint 0),中断输入,中断输出

CSR8675的接口描述符如下:

#define B_INTERFACE_CLASS_HID 0x03
#define B_INTERFACE_SUB_CLASS_HID_NO_BOOT 0x00
#define B_INTERFACE_PROTOCOL_HID_NO_BOOT 0x00
#define I_INTERFACE_INDEX 0x00static const UsbCodes usb_codes_hid_no_boot = {B_INTERFACE_CLASS_HID, /* bInterfaceClass */B_INTERFACE_SUB_CLASS_HID_NO_BOOT, /* bInterfaceSubClass */B_INTERFACE_PROTOCOL_HID_NO_BOOT, /* bInterfaceProtocol */I_INTERFACE_INDEX /* iInterface */};

其中的I_INTERFACE_INDEX指的是当前接口描述符对应的字符串描述符的索引号,CSR8675支持16个字符串描述符。可在PSKEY中修改:

##2.4. HID类报告描述符
HID类报告描述符定义了通过HID设备传输的数据的格式,官方提供了简易工具用于查看、编辑和保存HID类报告描述符(官方下载链接:HID Descriptor Tool),工具界面如下:

用这个工具可以生成面向C的代码,方便实现自定义的HID类报告描述符。CSR8675的HID类报告描述符的代码如下:

typedef struct {uint8 report_id;uint8 command;uint8 data[1021];
} hid_command_t;typedef struct {uint8 report_id;uint8 last_command;        uint8 last_command_status;
} hid_status_t;#define REPORT_COMMAND_ID       1
#define REPORT_COMMAND_SIZE     ((sizeof(hid_command_t)/sizeof(uint8))-1)
#define REPORT_STATUS_ID        2
#define REPORT_STATUS_SIZE      ((sizeof(hid_status_t)/sizeof(uint8))-1)/*HID Report Descriptor - HID Control Device */
static const uint8 report_descriptor_hid_control[] =
{   0x06, 0x00, 0xff,              /* USAGE_PAGE (Vendor Defined Page 1) */0x09, 0x01,                    /* USAGE (Vendor Usage 1) */0xa1, 0x01,                    /* COLLECTION (Application) */0x15, 0x80,                    /*   LOGICAL_MINIMUM (-128) */0x25, 0x7f,                    /*   LOGICAL_MAXIMUM (127) */0x85, 0x01,                    /*   REPORT_ID (1) */0x09, 0x02,                    /*   USAGE (Vendor Usage 2) */0x96,                          /*   REPORT_COUNT */(REPORT_COMMAND_SIZE&0xff), (REPORT_COMMAND_SIZE>>8), 0x75, 0x08,                    /*   REPORT_SIZE (8) */0x91, 0x02,                    /*   OUTPUT (Data,Var,Abs) */0x85, 0x02,                    /*   REPORT_ID (2) */0x09, 0x02,                    /*   USAGE (Vendor Usage 2) */0x95,                          /*   REPORT_COUNT */(REPORT_STATUS_SIZE&0xff),0x75, 0x08,                    /*   REPORT_SIZE (8) */0x81, 0x02,                    /*   INPUT (Data,Var,Abs) *//*0xb1, 0x02,*/0xc0                           /* END_COLLECTION */
}

用工具生成的C代码如下:

char ReportDescriptor[33] = {0x06, 0x00, 0xff,              // USAGE_PAGE (Vendor Defined Page 1)0x09, 0x01,                    // USAGE (Vendor Usage 1)0xa1, 0x01,                    // COLLECTION (Application)0x15, 0x80,                    //   LOGICAL_MINIMUM (-128)0x25, 0x7f,                    //   LOGICAL_MAXIMUM (127)0x85, 0x01,                    //   REPORT_ID (1)0x09, 0x02,                    //   USAGE (Vendor Usage 2)0x96, 0xff, 0x03,              //   REPORT_COUNT (1023)0x75, 0x08,                    //   REPORT_SIZE (8)0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)0x85, 0x02,                    //   REPORT_ID (2)0x09, 0x02,                    //   USAGE (Vendor Usage 2)0x95, 0x03,                    //   REPORT_COUNT (3)0x75, 0x08,                    //   REPORT_SIZE (8)0x81, 0x02,                    //   INPUT (Data,Var,Abs)0xc0                           // END_COLLECTION

可见两者格式基本一致。为了便于理解HID报告描述符的数据格式,给出一般的数据结构如下:

  • 每个报告描述符中包含1个Application Collection
  • 每个Application Collection中包含多个Report
  • 每个Report包含1组描述,包括报告数据的位数(Report Size),数据的长度(Report Count)等
  • 每个Report可对应多个用途(Usage)

结合CSR8675的HID报告描述符,可以观察到其中包含2个报告:

  • 报告1:共1024字节(报告ID占1字节+数据占1023字节),HID设备接收此报告
  • 报告2:共4字节(报告ID占1字节+数据占3字节),HID设备发送此报告
    ##2.5. HID类端点描述符
    PC端与HID设备端的数据传输基于“管道——端点“”机制,示意图如下:

    PC端的一个线程是一个管道的起点,通过USB线对接HID设备端的一个端点。

PC端通过读取HID类接口描述符获取HID类端点描述符以建立合适的通信管道。每个接口支持最多16个端点。其中端点0是默认控制端口,即每个USB HID接口都有端点0,其余15个端口可以配置成其他传输模式。

HID类端点描述符的主要属性如下:

  • 端点地址:共16个地址,以1字节表示。bit 7:1-to host,0- to device;bit 0-3:地址。
  • 端点传输模式: 端点有4种传输模式,分别是控制传输模式、批量传输模式、同步传输模式和中断传输模式。不同的传输模式适用于不同的设备类型。控制传输模式适用于PC端作为主控制台,批量传输模式适用于文件传输,同步传输模式适用于音视频数据传输,中断模式适用于实时性强的控制和数据采集。
  • 最大数据包大小:端口处理数据的能力。不同的控制传输模式对应的最大数据包大小不一样。
  • 轮询间隔:访问端口数据缓冲区的时间间隔。

CSR8675的HID端点描述符如下:

#define end_point_int_out   (0x81) /*!< Interrupt ToHost */
#define end_point_bulk_in   (0x02) /*!< Bulk FromHost */
#define end_point_bulk_out  (0x82) /*!< Bulk ToHost */
#define end_point_iso_in    (0x03) /*!< Isochronous FromHost */
#define end_point_iso_out   (0x83) /*!< Isochronous ToHost */
#define end_point_int_out2  (0x85) /*!< Interrupt ToHost */
#define end_point_bulk_in2  (0x06) /*!< Bulk FromHost */
#define end_point_bulk_out2 (0x86) /*!< Bulk ToHost */
#define end_point_iso_in2   (0x07) /*!< Isochronous FromHost */
#define end_point_int_out3  (0x89) /*!< Interrupt ToHost */
#define end_point_bulk_in3  (0x0A) /*!< Bulk FromHost */
#define end_point_bulk_out3 (0x8A) /*!< Bulk ToHost */
#define end_point_int_out4  (0x8D) /*!< Interrupt ToHost */
#define end_point_bulk_in4  (0x0E) /*!< Bulk FromHost */
#define end_point_bulk_out4 (0x8E) /*!< Bulk ToHost */typedef enum
{end_point_attr_ctl = 0,    /*!< Control.*/end_point_attr_iso = 1,    /*!< Isochronous.*/end_point_attr_bulk = 2,   /*!< Bulk.*/end_point_attr_int = 3,    /*!< Interrupt.*/end_point_attr_iso_sync = 13  /*!< Isochronous & Synchronisation Type Synchronous (bits 3:2 = 11) */
} EndPointAttr;/* USB HID endpoint information */
static const EndPointInfo epinfo_hid_control_transport[] =
{{        end_point_int_out, /* address */end_point_attr_int, /* attributes */16, /* max packet size */1, /* poll_interval */0, /* data to be appended */0, /* length of data appended */},{        end_point_bulk_in, /* address */end_point_attr_int, /* attributes */64, /* max packet size */1, /* poll_interval */0, /* data to be appended */0, /* length of data appended */}
};

可以看到描述了两个HID端口,一个用于向PC端发送数据,工作在中断传输模式;另一个用于接收PC端的数据,工作在批量传输模式。

#3. USB HID通信(设备端)
##3.1. 配置PSKEY
将下列PSKEY配置通过PSTool工具写入CSR8675的内部Flash:

&0001 = 0000 1213 005b 0002
// PSKEY_USB_DATA_PLUS_PULL_CONTROL
&01f0 = 0001// sets D+ when configuration is done (when ready)
// PSKEY_HOST_INTERFACE
&01f9 = 0002// USB link
// PSKEY_USB_DEVICE_CLASS_CODES
&02bd = 0000 0000 0000
// PSKEY_USB_PRODUCT_ID
&02bf = 1243
// PSKEY_USB_PIO_VBUS
&02d1 = fffe// Use VDD_CHG (battery charger)
// PSKEY_USB_CONFIG
&02d9 = 0038
// PSKEY_USB_ALLOW_DEEP_SLEEP
&02fc = 0003
// PSKEY_USB_VM_CONTROL
&03c0 = 0001// True
// PSKEY_ONCHIP_HCI_CLIENT
&03cc = 0001
// PSKEY_INITIAL_BOOTMODE
&03cd = 0001

上述配置是为了确保CSR8675的USB的描述符由VM层设定。另一个关键点是要确保boot mode 1的专属PSKEY段没有覆盖上述PSKEY值。

##3.2. 枚举设备
CSR8675作为USB HID device,需要在上电时完成枚举动作:

static const usb_device_class_hid_control_config usb_hid_config_control =
{{interface_descriptor_hid_control_transport,sizeof(interface_descriptor_hid_control_transport),epinfo_hid_control_transport},{report_descriptor_hid_control,sizeof(report_descriptor_hid_control),NULL}
};static bool usbEnumerateHidControl(void)
{if (!usb_hid_control_config){usb_hid_control_config = &usb_hid_config_control;        PRINT(("USB: HID control default descriptors\n"));}device->usb_interface[usb_interface_hid_control] = UsbAddInterface(&usb_codes_hid_no_boot, B_DESCRIPTOR_TYPE_HID, usb_hid_control_config->interface.descriptor, usb_hid_control_config->interface.size_descriptor);if (device->usb_interface[usb_interface_hid_control] == usb_interface_error)return FALSE;/* Register HID Control Device report descriptor with the interface */PRINT(("USB: HID control UsbAddDescriptor\n"));if (UsbAddDescriptor(device->usb_interface[usb_interface_hid_control], B_DESCRIPTOR_TYPE_HID_REPORT, usb_hid_control_config->report.descriptor, usb_hid_control_config->report.size_descriptor) == FALSE)return FALSE;/* Add required endpoints to the interface */PRINT(("USB: HID control UsbAddEndPoints\n"));if (UsbAddEndPoints(device->usb_interface[usb_interface_hid_control], 2, usb_hid_control_config->interface.end_point_info) == FALSE)return FALSE;device->usb_task[usb_task_hid_control].handler = hidControlHandler;(void) VmalMessageSinkTask(StreamUsbClassSink(device->usb_interface[usb_interface_hid_control]), &device->usb_task[usb_task_hid_control]);(void) VmalMessageSinkTask(StreamUsbEndPointSink(end_point_bulk_in), &device->usb_task[usb_task_hid_control]);return TRUE;
}

上述代码中可以看到,初始化USB HID时需要用到HID接口描述符、报告描述符、端口描述符,且将hidControlHandler作为USB sink的消息处理函数。

##3.3. 下行数据接收(host to device)
hidControlHandler用来与PC端通过HID接口交换数据。其源码如下:

static void hidControlHandler(Task task, MessageId id, Message message)
{MessageMoreData *msg = (MessageMoreData*)message;uint16 packet_size;hid_status_t status_report;const uint8 *in;if (id == MESSAGE_MORE_DATA){PRINT(("USB: MESSAGE_MORE_DATA hid consumer\n"));if (msg->source == StreamUsbClassSource(device->usb_interface[usb_interface_hid_control])){handleHidClassRequest(StreamUsbClassSource(device->usb_interface[usb_interface_hid_control]), USB_DEVICE_CLASS_TYPE_HID_CONTROL);}else if (msg->source == USB_SOURCE){while ((packet_size = SourceBoundary(msg->source)) != 0) {in = SourceMap(msg->source);PRINT(("USB MORE INT DATA: %d\n",packet_size));PRINT(("command: %d\n",((hid_command_t*)in)->command));status_report.report_id = REPORT_STATUS_ID;status_report.last_command = ((hid_command_t*)in)->command;status_report.last_command_status= STATUS_CMD_FAILED;SourceDrop(msg->source, packet_size);HidSendStatus(&status_report);}}}
}

##3.4. 上行数据发送(device to host)
USB源收到新的数据后,hidControlHandler会收到MESSAGE_MORE_DATA消息。此时判断USB数据源是默认端口0还是端口end_point_bulk_in。如果是端口end_point_bulk_in,读取端口数据并调用HidSendStatus(&status_report)返回消息状态。HidSendStatus源码如下:

/* send a status report over the interrupt endpoint */
static void HidSendStatus(hid_status_t *status_report)
{Sink sink = StreamUsbEndPointSink(end_point_int_out);uint8 *out;if ((out = claimSink(sink, sizeof(hid_status_t))) != 0) {PRINT(("Last command status=%d\n",status_report->last_command_status)); memmove(out, status_report,  sizeof(hid_status_t));PRINT(("USB sending %d bytes\n", sizeof(hid_status_t))); PanicFalse(SinkFlush(sink, sizeof(hid_status_t)));}else{PRINT(("USB cannot claim sink space\n")); }
}

StreamUsbEndPointSink(end_point_int_out)的意思是将消息状态数据通过USB端口end_point_int_out发送给PC端程序。

#4. USB HID通信(PC端)
Windows平台为USB HID提供了通用的API支持,实现与HID类设备间的USB接口通信。用VC++编写应用程序调用此API,即可方便地实现定制化的USB HID功能开发。

##4.1. 搭建环境

  • 下载安装WinDDK(官方链接)
  • 新建Win32项目,参考如下配置修改工程属性:
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"><ClCompile><Optimization>Disabled</Optimization><PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_DDK_;%(PreprocessorDefinitions)</PreprocessorDefinitions><MinimalRebuild>true</MinimalRebuild><BasicRuntimeChecks>Default</BasicRuntimeChecks><RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary><PrecompiledHeader>Use</PrecompiledHeader><WarningLevel>Level3</WarningLevel><DebugInformationFormat>EditAndContinue</DebugInformationFormat><AdditionalIncludeDirectories>$(WDKPATH)\inc\ddk;$(WDKPATH)\inc\api;$(WDKPATH)\inc\crt;D:\WinDDK\7600.16385.1\inc\ddk;D:\WinDDK\7600.16385.1\inc\api;D:\WinDDK\7600.16385.1\inc\crt;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories><StructMemberAlignment>1Byte</StructMemberAlignment><BufferSecurityCheck>false</BufferSecurityCheck><FunctionLevelLinking>true</FunctionLevelLinking><CallingConvention>Cdecl</CallingConvention><TreatWarningAsError>false</TreatWarningAsError></ClCompile><Link><AdditionalDependencies>Setupapi.lib;Hid.lib;%(AdditionalDependencies)</AdditionalDependencies><GenerateDebugInformation>true</GenerateDebugInformation><SubSystem>Console</SubSystem><TargetMachine>MachineX86</TargetMachine><AdditionalLibraryDirectories>$(WDKPATH)\lib\win7\i386;D:\WinDDK\7600.16385.1\lib\win7\i386;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories><IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries><Driver>NotSet</Driver><EntryPointSymbol></EntryPointSymbol><RandomizedBaseAddress>false</RandomizedBaseAddress><DataExecutionPrevention>false</DataExecutionPrevention><GenerateMapFile>true</GenerateMapFile></Link><ProjectReference><LinkLibraryDependencies>false</LinkLibraryDependencies></ProjectReference></ItemDefinitionGroup>

##4.2. 查询目标HID类设备
尝试打开HID类设备:

/* returns handle when device found or NULL when not found */
HANDLE OpenDevice(void) {wchar_t device_path[MAX_PATH];HANDLE DeviceHandle;if (EnumerateDevices(device_path)) {/* create handle to the device */DeviceHandle=CreateFile(device_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, (LPSECURITY_ATTRIBUTES)NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (DeviceHandle!=INVALID_HANDLE_VALUE) {return(DeviceHandle);}}return(NULL);
}

枚举设备时会查询每个HID设备的接口描述符中的Product ID、Vendor ID、HID类报告描述符的Usage Page和Usage属性值是否与目标HID设备的相符。当检查到匹配设备后,返回设备的句柄DeviceHandle。

int EnumerateDevices(wchar_t *device_path) {SP_DEVICE_INTERFACE_DATA devInfoData;int MemberIndex;ULONG Length;GUID HidGuid;HANDLE hDevInfo;HANDLE LocDevHandle;HIDD_ATTRIBUTES Attributes;PSP_DEVICE_INTERFACE_DETAIL_DATA detailData;PHIDP_PREPARSED_DATA PreparsedData;HIDP_CAPS Capabilities;int result=0;/* get HID GUID */HidD_GetHidGuid(&HidGuid);/* get pointer to the device information */hDevInfo = SetupDiGetClassDevs(&HidGuid,NULL, NULL, DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);/* go through all the device infos and find devices we are interested in */devInfoData.cbSize = sizeof(devInfoData);MemberIndex = 0;while((SetupDiEnumDeviceInterfaces(hDevInfo, 0, &HidGuid, MemberIndex, &devInfoData))&&(result==0)) {/* first get the size of memory needed to hold the device interface info */SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInfoData, NULL, 0, &Length, NULL);/* allocate memory */detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(Length);/* and set the size in the structure */detailData -> cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);/* now get the actual device interface info */SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInfoData, detailData, Length, NULL, NULL);
#ifdef DEBUGwprintf(L"%s\n",detailData->DevicePath);
#endif/* create handle to the device */LocDevHandle=CreateFile(detailData->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, (LPSECURITY_ATTRIBUTES)NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);/* set the size in the structure */Attributes.Size = sizeof(Attributes);/* get and test the VID and PID */HidD_GetAttributes(LocDevHandle,&Attributes);if ((Attributes.ProductID == 0x1243) &&(Attributes.VendorID == 0xa12)) {/* found the right device *//* is it the right HID collection? */HidD_GetPreparsedData(LocDevHandle, &PreparsedData);HidP_GetCaps(PreparsedData, &Capabilities);
#if 1wprintf(L"%04x %04x\n",Capabilities.UsagePage,Capabilities.Usage);
#endifif ((Capabilities.UsagePage == 0xFF00) &&(Capabilities.Usage == 0x0001)) {/* this is the correct HID collection */if (device_path!=NULL) {wcscpy(device_path,detailData->DevicePath);}
#ifdef DEBUG                wprintf(L"Device Found\n");
#endifresult=1;}}/* close the device handle again */CloseHandle(LocDevHandle);/* and free the memory used to hold device info */free(detailData);/* try the next device */MemberIndex++;}/* free memory used for the device information set */SetupDiDestroyDeviceInfoList(hDevInfo);return result;
}

##4.3. 下行数据发送(host to device)
PC端调用API函数向device发送数据:

 /* reboot to bootmode 0 */command_report.report_id=REPORT_COMMAND_ID;command_report.command=COMMAND_NOP;command_report.data[0]=0x00;status_response.last_command_status=-1;if (!WriteFile(DeviceHandle,&command_report,sizeof(hid_command_t),&count,NULL)) {/* cannot write */return(FALSE);}

这里的REPORT_COMMAND_ID与CSR8675程序中定义的值相同。

##4.4. 上行数据接收(device to host)
PC端调用API函数查询接收device的上行数据:

 /* wait for response */if (!ReadFile(DeviceHandle,&status_response,sizeof(hid_status_t),&count,NULL)) {/* cannot read */return(FALSE);}wprintf(L"Response is %d.\n", status_response.last_command_status);

这里需要注意的是,如果上行数据未能发送成功,程序会一直阻塞在ReadFile函数,不能往下执行。容易犯的错误是,设备端未按照HID类报告描述符中规定的数据格式发送数据。

#5. 总结

  • 调试过程遇到问题时,可借助Bus Hound工具捕捉PC端的USB HID通信数据包来分析定位问题。
  • CSR8675的PID和VID存储在PSKEY中,可使用PSTool工具修改。

#6. 参考文章

  • HID读写过程
  • C++ USB HID host 示例代码
  • USB HID协议中几个关键概念的理解
  • Device Class Definition for HID 1.11
  • Tutorial about USB HID Report Descriptors
  • USB端点描述符
  • USB4种传输类型和端点
  • USB描述符详解

CSR8675学习笔记:USB HID通信相关推荐

  1. USB协议学习笔记 - CUSTOM HID控制LED

    简介 前面了解了 STM32 CUSTOM HID 设备,但是有几个细节没有处理好,如接收到主机的报告后,如何接收到指定的数组,并实现通信功能,如控制LED亮灭? 还有就是CUSTOM HID设备的[ ...

  2. Android学习笔记---22_访问通信录中的联系人和添加联系人,使用事物添加联系人...

    Android学习笔记---22_访问通信录中的联系人和添加联系

  3. ROS学习笔记-多机器人通信(1)-实现两台机器通信

    ROS是一个分布式的计算环境.一个正在运行的ROS可以在多个机器人之间分布成几十甚至上百个节点.取决于系统的配置方式,任何节点可能需要随时与任何其他节点进行通信,为实现使用同一个master控制多台机 ...

  4. 【K210】K210学习笔记五——串口通信

    [K210]K210学习笔记五--串口通信 前言 K210如何进行串口通信 K210串口配置 K210串口发送相关定义 K210串口接收相关定义 K210串口发送接收测试 完整源码 前言 本人大四学生 ...

  5. 安卓与单片机进行usb hid通信

    安卓USB_HID通信文档 因为一直在研究stm32,需要用到usb hid通信,就研究了安卓与stm32进行usb hid通信,目前已经能进行正常通信,我这里只跟大家说一下通信时需要注意的问题. u ...

  6. USB协议学习笔记 - CUSTOM HID 设备

    简介 这里使用STM32平台进行USB 协议的学习与USB 设备的调试开发 USB HID设备,协议较固定,无法实现数据的自由接收与发送 USB CUSTOM HID设备(自定义HID协议)可以实现简 ...

  7. 学习笔记:匿名通信与暗网研究综述

    本文仅为作者学习笔记,内容源自论文"匿名通信与暗网研究综述--罗军舟等"本身以及相关网络搜索 1.匿名通信与暗网 匿名通信指采取一定的措施隐蔽通信流中的通信关系,使窃听者难以获取或 ...

  8. CSR8675学习笔记:I2C Master通信

    为了让CSR867x的开发更容易,现与思度科技联合推出CSR867x学习板[淘宝链接:思度科技CSR开发板]. 技术交流QQ群号:743434463 开发板会员QQ群号:725398389(凭订单号入 ...

  9. 51单片机学习笔记-6串口通信

    6 串口通信 [toc] 注:笔记主要参考B站江科大自化协教学视频"51单片机入门教程-2020版 程序全程纯手打 从零开始入门". 注:工程及代码文件放在了本人的Github仓库 ...

最新文章

  1. 初始化Mysql系统报错,begin failesd--conpilation aborted at scripts........
  2. Permission denied: make_sock: could not bind to address [::]:81 Apache 虚拟主机
  3. GIT:本地有更改,但强制作远程仓库里作更新
  4. 指示灯组与3个复位按钮的介绍Arduino Yun快速入门教程
  5. MySQL 清理slowlog方法
  6. 吴恩达深度学习课程deeplearning.ai课程作业:Class 2 Week 2 Optimization methods
  7. 图:DFS(深度优先搜索)图解分析代码实现
  8. UltraVNC反向连接方式的使用
  9. 项目中的模块剥离成项目_使用MCEBuddy 2从电视录制中剥离广告
  10. system health_可重复使用的MicroProfile Health探针
  11. java中未解决的编译问题_java – 我遇到了这个异常:未解决的编译问题
  12. mysql根用户的密码是什么_Mysql忘记根用户密码 怎么办?
  13. RSA算法的Java实现
  14. 史上最全 | 室外大规模3D检测数据集汇总
  15. 国内外各大搜索引擎登录入口
  16. 对于路由地址并未切换,但是地址栏发生地址发生变化原因
  17. 批处理CMD显示彩色文字
  18. 润乾报表echarts统计图省份地图设置
  19. Ti 官方文档阅读笔记
  20. 怎么配置java环境_idea配置java环境

热门文章

  1. python当型循环_Pro108-泡面Python[Py#008]——5min-当型循环 While
  2. 闲的发慌系列01-家庭版NAS
  3. Linux系统Redis安装教程-附带后台启动
  4. linux肉鸡检测,一台linux肉鸡的简单手工入侵检测过程
  5. wps工资表怎么用计算机,wps表格中怎么把工资表变成工资条
  6. 香港服务器的3c直连网络是什么概念,跟CN2的线路有什么不一样
  7. 为什么我们的计算机毕业设计要早做准备?
  8. 高数【积分-不定积分】--猴博士爱讲课
  9. 【Python】Matplotlib绘制折线图
  10. 苍蓝誓约服务器维护什么时候结束,苍蓝誓约手游2021年7月15日停服维护公告_苍蓝誓约手游2021年7月15日更新了什么_玩游戏网...