5

国产单片机替代-CH32替代STM32

 3 years ago
source link: http://yiiyee.cn/blog/2021/09/12/%E5%9B%BD%E4%BA%A7%E5%8D%95%E7%89%87%E6%9C%BA%E6%9B%BF%E4%BB%A3-ch32%E6%9B%BF%E4%BB%A3stm32/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

国产单片机替代-CH32替代STM32

请保留-> 【原文:  https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】

随着芯片价格疯涨,项目的不可控性越来越大。特别是价格方面,达到了无法想象的地步了。

以之前《UEFI编程实践》所用的YIE002开发板为例,当时选择使用STM32F103C8T6,也是因为它是一款性价比较高的MCU。当然,也有我之前用这款CPU做过几个项目,比较熟悉的原因在。

按我的记忆,之前项目中所用的STM32F103C8T6,价格在9元左右;而现在到立创商城上去查,单片价格到了惊人的109元!十几倍的涨幅,哪个项目还敢用它?

因此,大部分公司,都在准备各种替代方案。

我们也一样,预备使用CH32F103C8T6替代STM32F103C8T6。这两种芯片引脚兼容,内部的资源差不多,理论上代码移植也比较方便。

我就是这么想的,然后就被打脸了。

最大的原因在于,厂家提供的资料太少了!编程相关的CH32F103应用手册,只有短短的31页。我想看的USB设备控制器的寄存器细节,甚至都没有。想想STM32丰富的应用资料、例程和各种视频,感觉从新手级难度到了骨灰级难度了。

不过,再想想CH32这友好的价格,也就释然了。

周末两天,把之前的USB HID通信,在CH32F103C8T6上实现了,估计不久能很快地应用到项目中去。

预计也也有不少网友有类似的需求,我把探索的过程记录下来。

1 固件下载

CH32F103的芯片,支持WCH-Link或者其他SW仿真工具下载,也支持使用WCHISPTool通过USB和串口下载。考虑到后续开发的时候需要调试,我使用的是WCH-Link进行下载。

如图1所示,给出了WCH-Link的实物图(摘自《WCH-Link使用说明-V1.3》)。

图1 WCH-Link实物

由于我的目标是使用它下载程序到CH32F103C8T6中,只需要使用ARM模式就行了,不需要关注RISC-V模式。

拿到的WCH-Link,一般是RISC-V模式,需要将其切换到ARM模式。

模式切换的方法如下:

  1. WCH-Link 断电, 将图一正面图 1 中排针, TX 接 GND;
  2. WCH-Link 上电, 切换模式成功后, 断开 TX 和 GND;
  3. 后续使用时, WCH-Link 保持切换后的模式。

判断的方法如下:

  1. WCH-Link空闲时蓝灯常灭,是为RISC-V模式;
  2. WCH-Link空闲时蓝灯常亮,为ARM模式。

在ARM模式下,Windows 10下是不需要安装驱动的,而Win7有些情况下需要更换驱动,具体可以向厂家索取资料。

图2是WCH-Link在Win7下的设备显示。

图2 WCH-Link的ARM模式

实际使用中,直接使用SWD协议的两线以及GND就可以下载了。软件的使用方法,可以参考官方提供的《CH32F103评估板说明书》,其中介绍了详细的下载和仿真调试方法。

2 代码编写

我的目标在篇首就给出了,使用CH32F103C8T6实现之前的USB HID双向通信。

在经历了若干款MCU编写USB代码后,对这块内容已经比较熟悉了。简单来说,只要在USB HID的示例上,修改各类描述符,添加需要的命令处理就可以了。

可惜的是,厂家提供的示例代码非常少。CH32F103C8T6支持两个USB端口,一个是可做全速主机或设备的USBHD,另一个是全速设备USBD。

提供的示例代码中,USBD给出了VirtualCom的工程;USBHD给出了DEVICE、HOSG、HOST_Udisk三个示例。

USBD的工程,类似于STM32的Legacy Library;而USBHD的工程,则使用了沁恒电子自己的库。

我的目标很明确,实在没太多时间去研究沁恒电子的USB库,因此采用了USBD的示例作为模板,进行开发。

由于USBD的工程与STM32的USB库类似,我选择深入研究下STM32的USB库(毕竟资料更多,而且之前学习过)。

2.1 STM32的USB-FS Device Library

UEFI开发探索85中,曾经介绍过如何使用STM32F103C8T6制作HID设备。不过,对于所使用的的USB Library,并没有讨论。

STMF103的USB库,可以在STSW-STM32121中找到,其应用文档为UM0424。文档中给出了非常详尽的库说明,如图3为USB库的代码结构。

图3 USB库代码结构

USB-FS-Device 库主要分为两层:

  • STM32_USB-FS_Device_Driver: 驱动层,访问USB全速设备外围和USB标准协议,兼容USB2.0标准,与STM32标准库分离;这层不能由用户修改;
  • Applicaton Interface: 在库和最终用户层之间,提供完成的接口,可以由用户修改;

驱动层的代码,大部分情况下是不用修改的,它所包含的源文件说明如下:

  1. USB-FS外围部件接口
usb_reg (.h, .c):硬件抽象层
usb_int.c:传输中断服务函数
usb_mem(.h,.c):数据传输管理
  1. USB-FS设备驱动中间层
usb_init (.h,.c) :USB设备初始化全局变量
usb_core (.h , .c) :USB协议管理(兼容USB2.0规范第9章) 
usb_sil (.h,.c) :读写端点的简化函数(USB-FS_Device外围的抽象层)
usb_def.h / usb_type.h:用于库中的USB定义和类型
platform_config.h:评估板上用到的硬件定义

应用层代码是提供给用户修改用的,所需要实现的功能都在此层实现。它所包含的源文件说明如下:

usb_conf.h: 配置文件
usb_desc (.h, .c):描述符
usb_prop (.h, .c):应用规范属性
usb_endp.c:非控制端口的传输中断处理函数
usb_istr (.h,.c):中断处理函数
usb_pwr (.h, .c) :电源和连接管理函数

对照CH32F103C8T6提供的USBD例程,可以发现其结构与STM32的是一样的。可以断定,它是模仿了STM32的USB Library编写了自己的库函数接口。

这种设计方法,对习惯了STM32编程的工程师是非常好的。大部分情况下,可以直接把STM32的示例工程,直接移植到WCH的芯片上来(毕竟STM32的例程还是比较丰富的)。

本篇所实现的USB HID双向通信,就是参考了STM32的CustomHID例程,在CH32F103的USBD例子上实现的。

2.2 代码移植和修改

如图4所示,给出了CH32F103的USBD工程的代码结构。

图4 CH32F103的USBD工程代码

驱动层的代码完全不用修改。为了确定此事,我对照着STM32的驱动层代码,一个个函数研究了下,除去与芯片相关的部分,其实现代码几乎一致。

所要修改的代码在应用层,也不是所有源文件需要修改,需要修改的文件包括三个:
usb_desc.c、usb_endp.c和usb_prop.c。

看过我UEFI开发探索和YIE002开发探索两个系列博客的网友,应该了解之前我使用STM32开发USB HID设备的过程。而且相关的工程代码,在博客中也提供了(UEFI开发探索85和YIE002开发探索09,前者使用Legacy Library,后者使用Cube Library开发。)。

实际的开发过程,与之前的开发过程类似,只不过由于芯片的不同,有些代码需要进行移植。

2.2.1 usb_desc.c代码修改

所要修改的是各种描述符,包括设备描述符、配置描述符、端点描述符等。

需要注意的地方,是CH32F103的最大包长度为8。如下给出了设备描述符和配置描述符等的代码,其余的代码与之前开发的STM32F103工程相同,就不再给出了。

#define LOBYTE(x)  ((u8)(x & 0x00FF))
#define HIBYTE(x)  ((u8)((x & 0xFF00) >>8))

#define USBD_VID                     0x8765
#define USBD_PID                     0x4321

/* USB Device Descriptors */
const uint8_t  USBD_DeviceDescriptor[CUSTOMHID_SIZ_DEVICE_DESC] = { 
    0x12,                       /*bLength */
    USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType*/
    0x10,                       /*bcdUSB */
    0x01,
    0x00,                       /*bDeviceClass*/
    0x00,                       /*bDeviceSubClass*/
    0x00,                       /*bDeviceProtocol*/
    0x08,                       /*bMaxPacketSize*/
    LOBYTE(USBD_VID),           /*idVendor (0x1234)*/
    HIBYTE(USBD_VID),
    LOBYTE(USBD_PID),           /*idProduct = 0x4321*/
    HIBYTE(USBD_PID),
    0x00,                       /*bcdDevice rel. 1.00*/
    0x01,
    1,                          /*Index of string descriptor describing
                                              manufacturer */
    2,                          /*Index of string descriptor describing
                                             product*/
    3,                          /*Index of string descriptor describing the
                                             device serial number */
    0x01                        /*bNumConfigurations*/
};

/* USB Configration Descriptors */
const uint8_t  USBD_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] = { 
    0x09, /* bLength: Configuration Descriptor size */
    USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */
    CUSTOMHID_SIZ_CONFIG_DESC,    /* wTotalLength: Bytes returned */
    0x00,
    0x01,         /* bNumInterfaces: 1 interface */
    0x01,         /* bConfigurationValue: Configuration value */
    0x00,         /* iConfiguration: Index of string descriptor describing
                                 the configuration*/
    0x80,       /* bmAttributes: Self powered */
    0x32,      /* MaxPower 100 mA: this current is used for detecting Vbus */

    /************** Descriptor of Custom HID interface ****************/
    /* 09 */
    0x09,         /* bLength: Interface Descriptor size */
    USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType: Interface descriptor type */
    0x00,         /* bInterfaceNumber: Number of Interface */
    0x00,         /* bAlternateSetting: Alternate setting */
    0x02,         /* bNumEndpoints */
    0x03,         /* bInterfaceClass: HID */
    0x00,         /* bInterfaceSubClass : 1=BOOT, 0=no boot */
    0x00,         /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
    0,            /* iInterface: Index of string descriptor */
    /******************** Descriptor of Custom HID HID ********************/
    /* 18 */
    0x09,         /* bLength: HID Descriptor size */
    HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */
    0x10,         /* bcdHID: HID Class Spec release number */
    0x01,
    0x00,         /* bCountryCode: Hardware target country */
    0x01,         /* bNumDescriptors: Number of HID class descriptors to follow */
    0x22,         /* bDescriptorType */
    CUSTOMHID_SIZ_REPORT_DESC,/* wItemLength: Total length of Report descriptor */
    0x00,
    /******************** Descriptor of Custom HID endpoints ******************/
    /* 27 */
    0x07,          /* bLength: Endpoint Descriptor size */
    USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */

    0x82,          /* bEndpointAddress: Endpoint Address (IN) */
    0x03,          /* bmAttributes: 00: Control endpoint 01: Isochronous endpoint 02: Bulk endpoint  03: Interrupt endpoint */
    0x40,          /* wMaxPacketSize: 64 Bytes max */
    0x00,
    0x00,          /* bInterval: Polling Interval  */
    /* 34 */
    	
    0x07,	/* bLength: Endpoint Descriptor size */
    USB_ENDPOINT_DESCRIPTOR_TYPE,	/* bDescriptorType: */
			/*	Endpoint descriptor type */
    0x02,	/* bEndpointAddress: */
			/*	Endpoint Address (OUT) */
    0x03,	/* bmAttributes: Interrupt endpoint */
    0x40,	/* wMaxPacketSize: 64 Bytes max  */
    0x00,
    0x00,	/* bInterval: Polling Interval   */
};

2.2.2 usb_prop.c代码修改

这是开发中的重点,原有的CHF103的USBD工程中,包括端口0的控制传输以及若干USB命令都没有实现。

简单来说,就是需要把端口0的控制传输代码实现,以支持各种USB标准命令和USB类命令(主要是HID类)。

以下是我添加的代码,具体就不一一解释了,对照着以前提供的STM32工程和CHF103的USBD工程,还是很容易看明白的。

//robin add 20210910 begin------------------------
ONE_DESCRIPTOR CustomHID_Report_Descriptor =
{
  (uint8_t *)CustomHID_ReportDescriptor,
  CUSTOMHID_SIZ_REPORT_DESC
};
ONE_DESCRIPTOR CustomHID_Hid_Descriptor =
  {
    (uint8_t*)USBD_ConfigDescriptor + CUSTOMHID_OFF_HID_DESC,
    CUSTOMHID_SIZ_HID_DESC
  };
//robin add 20210910 end------------------------  
/*******************************************************************************
* Function Name  : USBD_Reset
* Description    : Virtual_Com_Port Mouse reset routine
* Input          : None.
* Return         : None.
*******************************************************************************/
void USBD_Reset(void)
{
  pInformation->Current_Configuration = 0;
  pInformation->Current_Feature = USBD_ConfigDescriptor[7];
  pInformation->Current_Interface = 0;

  SetBTABLE(BTABLE_ADDRESS);

  SetEPType(ENDP0, EP_CONTROL);
  SetEPTxStatus(ENDP0, EP_TX_STALL);
  SetEPRxAddr(ENDP0, ENDP0_RXADDR);
  SetEPTxAddr(ENDP0, ENDP0_TXADDR);
  Clear_Status_Out(ENDP0);
  SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
  SetEPRxValid(ENDP0);
  _ClearDTOG_RX(ENDP0);
  _ClearDTOG_TX(ENDP0);

    /* Initialize Endpoint 2 */
  SetEPType(ENDP2, EP_INTERRUPT);
  SetEPTxAddr(ENDP2, ENDP2_TXADDR);
  SetEPRxAddr(ENDP2, ENDP2_RXADDR);
  SetEPTxCount(ENDP2, USBD_DATA_SIZE);   //robin: 修改为64字节大小
  //SetEPRxCount(ENDP2, USBD_DATA_SIZE);   //robin: 修改为64字节大小
  SetEPRxStatus(ENDP2, EP_RX_VALID);
  SetEPTxStatus(ENDP2, EP_TX_NAK);
  _ClearDTOG_RX(ENDP2);
  _ClearDTOG_TX(ENDP2);
  
  SetDeviceAddress(0);
  
  bDeviceState = ATTACHED;
}
/*******************************************************************************
* Function Name  : USBD_NoData_Setup.
* Description    : handle the no data class specific requests.
* Input          : Request Nb.
* Return         : USB_UNSUPPORT or USB_SUCCESS.
*******************************************************************************/
RESULT USBD_NoData_Setup(uint8_t RequestNo)
{                                          
  if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
      && (RequestNo == SET_PROTOCOL))
  {
    return CustomHID_SetProtocol();
  }

  else
  {
    return USB_UNSUPPORT;
  }
//  return USB_SUCCESS;
}
//robin add 20210910 begin-----------------------------------------------------------------
//=====================================================================================
//====    ===   ===   ===   === ==== =================================================
//==== == == === == == === ====  === ==================================================
//==== = === === ==   ==== ==== == = =================================================
//==== == ===   === == ==   === ===  ==================================================
//=====================================================================================
/*记录:1 沁恒电子提供的资料中,没有关于HID的示例,因此,在其USBD(VirtualComPort)上进行修改;
        2 除去修改设备描述符等相对简单的部分,主要修改集中在本源文件中;
*/
/*******************************************************************************
* Function Name  : USBD_Data_Setup
* Description    : handle the data class specific requests
* Input          : Request Nb.
* Return         : USB_UNSUPPORT or USB_SUCCESS.
*******************************************************************************/
RESULT USBD_Data_Setup(uint8_t RequestNo)
{
  uint8_t *(*CopyRoutine)(uint16_t);
  
  if (pInformation->USBwIndex != 0) 
    return USB_UNSUPPORT;    
  
  CopyRoutine = NULL;
  
  if ((RequestNo == GET_DESCRIPTOR)
      && (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))
        )
  {
    
    if (pInformation->USBwValue1 == REPORT_DESCRIPTOR)
    {
      CopyRoutine = CustomHID_GetReportDescriptor;
    }
    else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)
    {
      CopyRoutine = CustomHID_GetHIDDescriptor;
    }
    
  } /* End of GET_DESCRIPTOR */
  
  /*** GET_PROTOCOL, GET_REPORT, SET_REPORT ***/
  else if ( (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) )
  {         
    switch( RequestNo )
    {
    case GET_PROTOCOL:
      CopyRoutine = CustomHID_GetProtocolValue;
      break;
    case SET_REPORT:
      CopyRoutine = CustomHID_SetReport_Feature;
      Request = SET_REPORT;
      break;
		//robin add for get_report
		case GET_REPORT:
			if((Report_InOut_Flag==0)&&(Report_Feature_Flag==0))
				return USB_NOT_READY;   //Robin: Inform the host that the data is not ready
			CopyRoutine = CustomHID_GetReport_Feature;
      Request = GET_REPORT;
      break;
    default:
      break;
    }
  }
  
  if (CopyRoutine == NULL)
  {
    return USB_UNSUPPORT;
  }
  
  pInformation->Ctrl_Info.CopyData = CopyRoutine;
  pInformation->Ctrl_Info.Usb_wOffset = 0;
  (*CopyRoutine)(0);
  
  return USB_SUCCESS;
}
/*******************************************************************************
* Function Name  : CustomHID_GetReportDescriptor.
* Description    : Gets the HID report descriptor.
* Input          : Length
* Output         : None.
* Return         : The address of the configuration descriptor.
*******************************************************************************/
uint8_t *CustomHID_GetReportDescriptor(uint16_t Length)
{
  return Standard_GetDescriptorData(Length, &CustomHID_Report_Descriptor);
}
/*******************************************************************************
* Function Name  : CustomHID_GetHIDDescriptor.
* Description    : Gets the HID descriptor.
* Input          : Length
* Output         : None.
* Return         : The address of the configuration descriptor.
*******************************************************************************/
uint8_t *CustomHID_GetHIDDescriptor(uint16_t Length)
{
  return Standard_GetDescriptorData(Length, &CustomHID_Hid_Descriptor);
}
/*******************************************************************************
* Function Name  : CustomHID_GetProtocolValue
* Description    : get the protocol value
* Input          : Length.
* Output         : None.
* Return         : address of the protocol value.
*******************************************************************************/
uint8_t *CustomHID_GetProtocolValue(uint16_t Length)
{
  if (Length == 0)
  {
    pInformation->Ctrl_Info.Usb_wLength = 1;
    return NULL;
  }
  else
  {
    return (uint8_t *)(&ProtocolValue);
  }
}
/*******************************************************************************
* Function Name  : CustomHID_SetProtocol
* Description    : Joystick Set Protocol request routine.
* Input          : None.
* Output         : None.
* Return         : USB SUCCESS.
*******************************************************************************/
RESULT CustomHID_SetProtocol(void)
{
  uint8_t wValue0 = pInformation->USBwValue0;
  ProtocolValue = wValue0;
  return USB_SUCCESS;
}
/*******************************************************************************
* Function Name  : CustomHID_SetReport_Feature
* Description    : Set Feature request handling
* Input          : Length.
* Output         : None.
* Return         : Buffer
*******************************************************************************/
uint8_t *CustomHID_SetReport_Feature(uint16_t Length)
{
	if(pInformation->USBwValues.bw.bb1 == OUT_REPORT)
		Report_InOut_Flag=1;
	else if(pInformation->USBwValues.bw.bb1 == FEATURE_REPORT)
		Report_Feature_Flag=1;
	
  if (Length == 0)
  {
    pInformation->Ctrl_Info.Usb_wLength = 16;  //robin
    return NULL;
  }
  else
  {
//    return Report_Buf;
    return &Report_Buf[pInformation->Ctrl_Info.Usb_wOffset];
  }
}
/*******************************************************************************
* Function Name  : CustomHID_GetReport_Feature
* Description    : Set Feature request handling
* Input          : Length.
* Output         : None.
* Return         : Buffer
*******************************************************************************/
uint8_t *CustomHID_GetReport_Feature(uint16_t Length)
{
   if(pInformation->USBwValues.bw.bb1 == IN_REPORT)
     Report_InOut_Flag=0;
   else if(pInformation->USBwValues.bw.bb1 == FEATURE_REPORT)
     Report_Feature_Flag=0;
	
  if (Length == 0) //此处报告需要发送的长度
  {
    pInformation->Ctrl_Info.Usb_wLength = 16;//2; //robin
    return NULL;
  }
  else  //此处返回需要处理的数据
  {
    if(pInformation->USBwValues.bw.bb1 == FEATURE_REPORT)
    {
      if(Report_Buf[0] == 0xA0)  //将第二个字节改为3返回,表示是Feature 报告;
        Report_Buf[1]=0x3;  
     }
     else
     {
       if(Report_Buf[0] == 0xA0)  //将第二个字节改为2返回,表示是IN/OUT报告;
         Report_Buf[1]=0x2;
      }
    return Report_Buf;
  }
}

//robin add 20210910 end-----------------------------------------------------------------

如果熟悉STM32的USB编程,看过我之前编写的USB HID工程,是很容易看清楚这些代码的结构的。使用Input Report&Output Report,以及Feature Report的通信方式,均在此源文件中实现。

无外乎是将STM32的代码,移植到CH32上来(大部分代码基本不用修改,复制过来就行)。

另外,usb_prop.h头文件中,也会缺少一些类型定义和宏定义,在STM32的工程中找下就可以了,就不贴出内容了。

2.2.3 usb_endp.c代码修改

usb_endp.c中主要实现的端点读写通信,也即对应上位机的ReadFile()和WriteFile()。

在usb_desc.c中的配置描述符中,包含了端点描述符的内容。我们声明了端点2的IN和OUT作为读写接口。所实现的代码如下:

uint8_t Receive_Buffer[0xff];
/*******************************************************************************
* Function Name  : EP2_IN_Callback
* Description    : Endpoint 2 IN.
* Input          : None.
* Return         : None.
*******************************************************************************/
void EP2_IN_Callback (void)
{ 
}

/*******************************************************************************
* Function Name  : EP2_OUT_Callback
* Description    : Endpoint 2 IN.
* Input          : None.
* Return         : None.
*******************************************************************************/
void EP2_OUT_Callback(void)
{
uint32_t DataLength = 0;
  DataLength=USB_SIL_Read(EP2_OUT, Receive_Buffer); //读取端点得到的数据
  SetEPRxStatus(ENDP2, EP_RX_VALID);
	
	if (Receive_Buffer[0] == 0xA0)   //将第二个字节改为1返回,表示是采用端点发送的方式
	{
		Receive_Buffer[1]=0x1;
	}
	USB_SIL_Write(EP2_IN,Receive_Buffer,DataLength);
	SetEPTxStatus(ENDP2,EP_TX_VALID);
}

至此,就完成了所有编程工作了。将其编译下载到CH32F103C8T6的开发板上,就可以进行测试了。

仍旧使用我之前开发的UsbHID上位机工具进行测试(UEFI开发探索74附带的测试工具),结果如下:

图4 CH32F103的USBD工程代码

从图5的测试可以看出,三种读写方式都实现了。

不得不承认,国产的单片机相比于国外的大厂来说,支持资料做得很不足。不过,从功能上来说,还是会有一些亮点的。比如CH32F103C8T6相比于STM32F103C8T6,3个串口保留了,而且还增加了一个USB HOST。

另外,即便是在正常的情况下(现在芯片短缺属于不正常状态),其价格也只有STM32F103C8T6的一半。这对于批量出货的产品来说,是个不能忽视的优势。

我相信随着这波芯片短缺的影响,很多的厂商都会逐渐使用国产单片机了。这种变化,对软硬件工程师来说,可是个不小的考验。

822 total views, 3 views today


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK