本帖最后由 RISCVLAR 于 2021-3-20 13:24 编辑
CH32V103应用教程——USB Host
本章教程主要使用CH32V103 USB用作主机模式,程序仅供参考。
1、USB简介及相关函数介绍 CH32V103芯片内嵌USB主从控制器及收发器,支持USB Host主机功能和USB Device设备功能。
在 USB 主机模式下,芯片提供了一组双向主机端点,包括一个发送端点 OUT 和一个接收端点 IN,一个数据包的最大长度是 1023 字节(V103),支持控制传输、中断传输、批量传输和实时/同步传输。
主机端点发起的每一个 USB 事务,在处理结束后总是自动设置RB_UIF_TRANSFER 中断标志。应用程序可以直接查询或在 USB 中断服务程序中查询并分析中断标志寄存器 R8_USB_INT_FG,根据各中断标志分别进行相应的处理;并且,如果 RB_UIF_TRANSFER 有效,那么还需要继续分析 USB 中断状态寄存器 R8_USB_INT_ST,根据当前 USB 传输事务的应答 PID 标识 MASK_UIS_H_RES 进行相应的处理。
如果事先设定了主机接收端点的 IN 事务的同步触发位(RB_UH_R_TOG),那么可以通过RB_U_TOG_OK 或者 RB_UIS_TOG_OK 判断当前所接收到的数据包的同步触发位是否与主机接收端点的同步触发位匹配,如果数据同步,则数据有效;如果数据不同步,则数据应该被丢弃。每次处理完USB 发送或者接收中断后,都应该正确修改相应主机端点的同步触发位,用于同步下次所发送的数据包和检测下次所接收的数据包是否同步;另外,通过设置 RB_UH_T_AUTO_TOG 和 RB_UH_R_AUTO_TOG可以实现在发送成功或接收成功后自动翻转相应的同步触发位。
USB 主机令牌设置寄存器 R8_UH_EP_PID 用于设置被操作的目标设备的端点号和本次 USB 传输事务的令牌 PID 包标识。SETUP 令牌和 OUT 令牌所对应的数据由主机发送端点提供,准备发送的数据在R16_UH_TX_DMA 缓冲区中,准备发送的数据长度设置在 R16_UH_TX_LEN 中;IN 令牌所对应数据由目标设备返回给主机接收端点,接收到数据存放 R16_UH_RX_DMA 缓冲区中,接收到的数据长度存放在R8_USB_RX_LEN 中。
关于USB主机模式具体信息以及寄存器相关描述,可参考CH32V103应用手册。
2、硬件设计 本章教程主要进行USB主机模式实验,仅需用到开发板USB口。
3、软件设计 本章程序全在主函数中进行,具体程序如下: main.c文件 /********************************** (C) COPYRIGHT *******************************
* File Name : main.c
* Author : WCH
* Version : V1.0.0
* Date : 2019/10/15
* Description : Main program body.
*******************************************************************************/
/*
*@Note
USB设备的简易枚举过程例程:
USBHDM(PB6)、USBHDP(PB7)。
*/
#include "debug.h"
#include "string.h"
#include "ch32v10x_conf.h"
/* Global Variable */
__attribute__ ((aligned(4))) UINT8 RxBuffer[MAX_PACKET_SIZE] ; // IN, must even address
__attribute__ ((aligned(4))) UINT8 TxBuffer[MAX_PACKET_SIZE] ; // OUT, must even address
__attribute__ ((aligned(4))) UINT8 Com_Buffer[128];
_DevOnHubPort DevOnHubPort[HUB_MAX_PORTS];
_RootHubDev ThisUsbDev;
/*******************************************************************************
* Function Name : Set_USBConfig
* Description : Set USB clock.
* Input : None
* Return : None
*******************************************************************************/
void USBHD_ClockCmd(UINT32 RCC_USBCLKSource,FunctionalState NewState)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, NewState);
EXTEN->EXTEN_CTR |= EXTEN_USBHD_IO_EN;
RCC_USBCLKConfig(RCC_USBCLKSource); //USBclk=PLLclk/1.5=48Mhz
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_USBHD,NewState);
}
/*******************************************************************************
* Function Name : main
* Description : Main program.
* Input : None
* Return : None
*******************************************************************************/
int main()
{
UINT8 s;
UINT8 i, k, len, endp;
UINT16 loc;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n",SystemCoreClock);
printf("USBHD HOST Test\r\n");
USBHD_ClockCmd(RCC_USBCLKSource_PLLCLK_1Div5,ENABLE);
pHOST_RX_RAM_Addr = RxBuffer;
pHOST_TX_RAM_Addr = TxBuffer;
USB_HostInit(); //初始化USB主机模式
printf( "Wait Device In\n" );
while(1)
{
s = ERR_SUCCESS;
//RB_UIF_DETECT:USB主机模式下USB设备连接或断开事件中断标志位
if ( R8_USB_INT_FG & RB_UIF_DETECT )
{
R8_USB_INT_FG = RB_UIF_DETECT ; //检测到USB设备连接或断开触发
s = AnalyzeRootHub( ); //分析跟集线器状态
if ( s == ERR_USB_CONNECT ) //如果检测到设备连接
{
printf( "New Device In\r\n" );
FoundNewDev = 1;
}
if( s == ERR_USB_DISCON ) //如果检测到设备断开连接
{
printf( "Device Out\r\n" );
}
}
if ( FoundNewDev || s == ERR_USB_CONNECT ) //如果检测到有USB设备连接
{
FoundNewDev = 0;
s = EnumAllRootDevice( ); //枚举所有ROOT-HUB端口的USB设备
if ( s == ERR_SUCCESS ) //如果枚举成功
{
printf( "Device Enum Succeed\r\n" );
}
else //如果枚举失败
{
printf( "Device Enum Failed\r\n" );
printf( "EnumAllRootDev err = %02X\n", (UINT16)s );
}
}
//如果CH32V103下端连接的是HUB,则先枚举HUB
s = EnumAllHubPort( ); //枚举所有ROOT-HUB端口下外部HUB后的二级USB设备
if ( s != ERR_SUCCESS ) //可能是HUB断开了
{
printf( "EnumAllHubPort err = %02X\n", (UINT16)s );
}
//如果设备是鼠标
loc = SearchTypeDevice( DEV_TYPE_MOUSE ); // 在ROOT-HUB以及外部HUB各端口上搜索指定类型的设备所在的端口号
//printf("loc=%d\n",loc);
if ( loc != 0xFFFF ) // 找到了,如果有两个MOUSE如何处理?
{
//printf( "Query Mouse @%04X\n", loc );
i = (UINT8)( loc >> 8 );
len = (UINT8)loc;
//printf("len=%d\n",len);
SelectHubPort( len ); // 选择操作指定的ROOT-HUB端口,设置当前USB速度以及被操作设备的USB地址
//printf("len1=%d\n",len);
endp = len ? DevOnHubPort[len-1].GpVar[0] : ThisUsbDev.GpVar[0]; // 中断端点的地址,位7用于同步标志位
//printf("endp=%d\n",endp);
if ( endp & USB_ENDP_ADDR_MASK ) // 端点有效
{
s = USBHostTransact( USB_PID_IN << 4 | (endp & 0x7F), endp & 0x80 ? RB_UH_R_TOG | RB_UH_T_TOG : 0, 0 );// CH32传输事务,获取数据,NAK不重试
if ( s == ERR_SUCCESS )
{
endp ^= 0x80; // 同步标志翻转
if ( len ) DevOnHubPort[len-1].GpVar[0] = endp; // 保存同步标志位
else ThisUsbDev.GpVar[0] = endp;
len = R8_USB_RX_LEN; // 接收到的数据长度
if ( len )
{
printf("Mouse data: ");
for ( i = 0; i < len; i ++ )
{
printf("x%02X ",(UINT16)(RxBuffer[i]) );
}
printf("\n");
}
}
else if ( s != ( USB_PID_NAK | ERR_USB_TRANSFER ) )
{
printf("Mouse error %02x\n",(UINT16)s); // 可能是断开了
}
}
else
{
printf("Mouse no interrupt endpoint\n");
}
SetUsbSpeed( 1 ); // 默认为全速
}
//如果设备是键盘
loc = SearchTypeDevice( DEV_TYPE_KEYBOARD ); // 在ROOT-HUB以及外部HUB各端口上搜索指定类型的设备所在的端口号
if ( loc != 0xFFFF ) // 找到了,如果有两个KeyBoard如何处理?
{
//printf( "Query Keyboard @%04X\n", loc );
i = (UINT8)( loc >> 8 );
len = (UINT8)loc;
SelectHubPort( len ); // 选择操作指定的ROOT-HUB端口,设置当前USB速度以及被操作设备的USB地址
endp = len ? DevOnHubPort[len-1].GpVar[0] : ThisUsbDev.GpVar[0]; // 中断端点的地址,位7用于同步标志位
//printf("%02X ",endp);
if ( endp & USB_ENDP_ADDR_MASK ) // 端点有效
{
s = USBHostTransact( USB_PID_IN << 4 | (endp & 0x7F), endp & 0x80 ? RB_UH_R_TOG | RB_UH_T_TOG : 0, 0 );// CH32传输事务,获取数据,NAK不重试
if ( s == ERR_SUCCESS )
{
endp ^= 0x80; // 同步标志翻转
if ( len ) DevOnHubPort[len-1].GpVar[0] = endp; // 保存同步标志位
else ThisUsbDev.GpVar[0] = endp;
len = R8_USB_RX_LEN; // 接收到的数据长度
if ( len )
{
SETorOFFNumLock(RxBuffer);
printf("keyboard data: ");
for ( i = 0; i < len; i ++ )
{
printf("x%02X ",(UINT16)(RxBuffer[i]) );
}
printf("\n");
}
}
else if ( s != ( USB_PID_NAK | ERR_USB_TRANSFER ) )
{
printf("keyboard error %02x\n",(UINT16)s); // 可能是断开了
}
}
else
{
printf("keyboard no interrupt endpoint\n");
}
SetUsbSpeed( 1 ); // 默认为全速
}
// //操作USB打印机
// if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1) == 0) //PA1为低,开始打印
// {
// memset(TxBuffer,0,sizeof(TxBuffer));
//
// TxBuffer[0]=0x1B;TxBuffer[1]=0x40;TxBuffer[2]=0x1D;TxBuffer[3]=0x55;TxBuffer[4]=0x42;TxBuffer[5]=0x02;TxBuffer[6]=0x18;TxBuffer[7]=0x1D;
// TxBuffer[8]=0x76;TxBuffer[9]=0x30;TxBuffer[10]=0x00;TxBuffer[11]=0x30;TxBuffer[12]=0x00;TxBuffer[13]=0x18;
loc = SearchTypeDevice( USB_DEV_CLASS_PRINTER ); // 在ROOT-HUB以及外部HUB各端口上搜索指定类型的设备所在的端口号
if ( loc != 0xFFFF ) // 找到了,如果有两个打印机如何处理?
{
//printf( "Query Printer @%04X\n", loc );
i = (UINT8)( loc >> 8 );
len = (UINT8)loc;
SelectHubPort( len ); // 选择操作指定的ROOT-HUB端口,设置当前USB速度以及被操作设备的USB地址
endp = len ? DevOnHubPort[len-1].GpVar[0] : ThisUsbDev.GpVar[0]; // 端点的地址,位7用于同步标志位
//printf("%02X ",endp);
if ( endp & USB_ENDP_ADDR_MASK ) // 端点有效
{
R8_UH_TX_LEN = 64; // 默认无数据故状态阶段为IN
s = USBHostTransact( USB_PID_OUT << 4 | (endp & 0x7F), endp & 0x80 ? RB_UH_R_TOG | RB_UH_T_TOG : 0, 0xffff );// CH554传输事务,获取数据,NAK重试
if ( s == ERR_SUCCESS )
{
endp ^= 0x80; // 同步标志翻转
memset(TxBuffer,0,sizeof(TxBuffer));
R8_UH_TX_LEN = 64; // 默认无数据故状态阶段为IN
s = USBHostTransact( USB_PID_OUT << 4 | (endp & 0x7F), endp & 0x80 ? RB_UH_R_TOG | RB_UH_T_TOG : 0, 0xffff );// CH554传输事务,获取数据,NAK重试
}
else if ( s != ( USB_PID_NAK | ERR_USB_TRANSFER ) )
printf("Printer error %02x\n",(UINT16)s); // 可能是断开了
}
}
// }
//操作HID复合设备
loc = SearchTypeDevice( USB_DEV_CLASS_HID ); // 在ROOT-HUB以及外部HUB各端口上搜索指定类型的设备所在的端口号
if ( loc != 0xFFFF ) // 找到了
{
//printf( "Query USB_DEV_CLASS_HID @%04X\n", loc );
loc = (UINT8)loc;
for(k=0;k!=4;k++)
{
//端点是否有效?
endp = loc ? DevOnHubPort[loc-1].GpVar[k] : ThisUsbDev.GpVar[k]; // 中断端点的地址,位7用于同步标志位
if ( (endp & USB_ENDP_ADDR_MASK) == 0 ) break;
//printf("endp: %02X\n",(UINT16)endp);
SelectHubPort( loc ); // 选择操作指定的ROOT-HUB端口,设置当前USB速度以及被操作设备的USB地址
s = USBHostTransact( USB_PID_IN << 4 | (endp & 0x7F), endp & 0x80 ? RB_UH_R_TOG | RB_UH_T_TOG : 0, 0 );// CH554传输事务,获取数据,NAK不重试
if ( s == ERR_SUCCESS )
{
endp ^= 0x80; // 同步标志翻转
if ( loc ) DevOnHubPort[loc-1].GpVar[k] = endp; // 保存同步标志位
else ThisUsbDev.GpVar[k] = endp;
len = R8_USB_RX_LEN; // 接收到的数据长度
if ( len )
{
printf("keyboard data: ");
for ( i = 0; i < len; i ++ )
{
printf("x%02X ",(UINT16)(RxBuffer[i]) );
}
printf("\n");
}
}
else if ( s != ( USB_PID_NAK | ERR_USB_TRANSFER ) )
{
printf("keyboard error %02x\n",(UINT16)s); // 可能是断开了
}
}
SetUsbSpeed( 1 ); // 默认为全速
}
}
}
main.c文件主要进行插入设备的检测、识别以及数据的传输,具体可见程序和注释。
4、下载验证 将编译好的程序下载到开发板并复位,打开串口调试助手,串口打印如下: 当插入鼠标设备并移动鼠标时,串口打印显示如下: 其他设备与之类似,不再一一附图展示。
|
@洛川飞鱼 :找到原因了 20.2.2 功能配置 1)GPIO 端口 一旦使能了 USBD 模块,作为 UDP 和 UPM 的 GPIO 口会自动连接到内部 USB 收发器,而断开其 GPIO 外设的端口设置。所以推荐 GPIO 口配置为推挽方式输出低电平,防止在未开启 USBD 功能前,出现端 口不确定状态或者连接 PC 主机时,提前通知有 USB 设备接入。
请教一下,哪里设置的IO口,没找到,看注释写的是PB的口,我这板子是PA的口,直接烧录竟然跑得通?
参考做扫码枪上位机,注释细致,很受用,感谢