STM32H723 USB HOST CDC驱动FT232RL
本文介绍了如何使用STM32H723 USB HOST CDC实现与FT232RL通过USB协议通信
STM32H723 USB HOST CDC驱动FT232RL
前言
因为项目上要使用MCU与一台仪器进行通信,而该仪器只留出了USB接口。将该仪器接入PC,发现该仪器使用的是FT232RL芯片将内部的信号转为了USB。我查询了很多资料,发现FT232使用的不是标准USB协议,而是厂商自定义的协议,不能直接使用STM32的USB HOST库来跟FT232通信,而我也试了外加一个USB从设备转TTL模块,也不能成功建立通信,最终还是只能自己修改STM32的USB HOST库从协议层面来兼容FT232。我大概调试了一周的时间,中间也撞了很多次墙,遇到了很多ai也解决不了的问题,最终还是靠翻官方手册(吐槽一下官方手册写的全是它自己的库怎么用的和PC上怎么用的,根本没写底层的通信协议,寄存器地址都没有)和看linux和USB_Host_Shield_2.0的FT232底层驱动,成功在STM32H723ZGT6上实现了FT232的USB通信。
当时翻阅很多国内、国外的网站,并没有找到合适的资料,在STM32的官方社区上面提问也没有得到反馈,中间也看到很多在各种论坛的提问者放弃,但我们项目使用的仪器厂商不给更换接口,只能一条路走到黑,还好最后搞出来了,在此处留下记录以给后来者一点帮助。
开发环境
-
硬件
- STM32H723ZGT6核心板(自己画的,注意usb的d+ d-一定要画成差分等长线,不然完全通信不了)
- FT232RL USB转TTL模块(与STM32H723通过USB CDC通信,TTL端连接CH340)
- CH340 USB转TTL模块(TTL端连接到FT232RL的TTL上,USB端插入PC检测STM32H723USB发送和接收是否成功)

-
软件
- STM32CubeIDE 1.19.0
- VSCode Insiders(纯用来写代码和用copilot)
- web串口上位机
- wireshark(用来抓FT232RL协议包)
FT232 USB协议分析
将FT232RL接入PC,用wireshark来抓FT232协议包,抓到的FT232RL在PC上从获取硬件描述符到可以收发数据的协议包如下:
- 获取硬件描述符阶段

- 不知道在干什么阶段

- 第一次硬件参数配置阶段

- 软重启阶段

- 第二次硬件参数配置阶段

- 在PC上打开上位机,抓取打开上位机之后FT232RL的协议包如下:

FT232RL在2阶段干了什么我查了官方的资料没有提及,而在linux和USB_Host_Shield_2.0库中也没有这一段内容,从linux和USB_Host_Shield_2.0库中分析,要使STM32H723能与FT232RL通信,最重要的是第1阶段和第6阶段。FT232RL的初始化流程如下:
硬件配置
- 配置时钟
USB的时钟一定要使用48MHz的,直接用HSI48或者从PLL里面分频分48MHz的都可以
通过查询STM32官方的手册可以发现HSI48是能直接用于USB的,但是你问ai,ai可能会告诉你HSI48不行,我们以STM32官方的手册为准,手册的名字是STM32H723/733, STM32H725/735 and STM32H730 Value line advanced Arm®-based 32-bit MCUs和Getting started with STM32H723/733, STM32H725/735 and STM32H730 Value Line hardware development

跟据STM32官方的描述,要使用HSI48作为USB的时钟必须加CRS时钟恢复系统,但是经我测试不加也是能正常工作的,对于低负载的情况应该是够了,要是怕出问题还是用外部时钟PLL之后给USB吧。
- USB HOST CDC配置
在Connectivity/USB_OTG_HS中USB配置为内部FS Phy的Host_Only模式,Host的设置可以保持默认,End Point中断可以不用打开。


将Middleware and Software Packs/USB_HOST中设置Class For HS IP为Communication Host Class (Virtual Port Com),然后设置USBH_DEBUG_LEVEL(USBH Debug Level)为3: All messages and internal debug messages are shown(这项设置是为了方便调试)
然后设置USB的供电使能引脚,我的PCB使用了一个MT9700-N芯片给USB Device设备供电,这个芯片需要使用一个使能信号才会输出5V给USB设备,我使用的是PA8引脚,而高使能和低使能在配置硬件的时候可以忽略。
- 其余硬件配置
按照实际使用情况配置串口1作为USB的Debug信息输出,波特率为115200

配置SW调试下载引脚
STM32 USB HOST库修改(C语言版)
为了使FT232RL能够与STM32单片机进行通信,需要对STM32 USB HOST库进行修改以支持FT232的USB协议,以下修改内容均建立在不影响STM32Cube重新生成底层的情况下进行。
为了便于修改和调试,创建一个FT232.c和FT232.h文件,下述的函数若无特殊说明,均在FT232.c中实现,在FT232.h中声明
先在FT232.h中包含必要的USB相关头文件
#include "usbh_cdc.h"
#include "usbh_conf.h"
#include "usbh_def.h"
串口重定向
为了使用STM32的USB Debug Message功能,需要对串口进行重定向。在main.c开头添加这段代码
int fputc(int ch, FILE *f) {
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);// 可以跟据实际情况修改这里的串口句柄
return ch;
}
int _write(int file, char* ptr, int len) {
HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 100);// 可以跟据实际情况修改这里的串口句柄
return len;
}
fputc和_write可以只用其中一个,看你的板子哪个有效就用哪个,对我的板子是_write有效。
修改VBUS使能极性
在USB_HOST/Target文件夹中打开usbh_platform.c文件,找到其中的void MX_DriverVbusHS(uint8_t state)函数,跟据你的EN极性来修改使能信号极性,以我使用的MT9700-N为例,MT9700-N是高使能,低失能。
void MX_DriverVbusHS(uint8_t state)
{
uint8_t data = state;// state为1使能,为2失能
/* USER CODE BEGIN PREPARE_GPIO_DATA_VBUS_HS */
if(state == 0)
{
/* Drive high Charge pump */
data = GPIO_PIN_RESET; // Cube生成的默认为GPIO_PIN_SET
}
else
{
/* Drive low Charge pump */
data = GPIO_PIN_SET; // Cube生成的默认为GPIO_PIN_RESET
}
/* USER CODE END PREPARE_GPIO_DATA_VBUS_HS */
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,(GPIO_PinState)data);
}
完成这一步后把FT232接入STM32H7,用串口打印调试信息,如果前面的步骤都对了,这个时候串口的输出应该是:
如果到这一步输出的不是这样的文字,那说明前面的步骤有问题。先看串口有没有供上电,没供上电就是使能信号极性反了,如果供上电了那大概就是FT232有问题或者d+、d-没有画成差分等长线。
经过上面的步骤,STM32H723已经能成功获取FT232RL的硬件描述符了,但是在STM32的USB CDC协议中找不到FT232RL的协议,所以这时候会提示No registered class for this device.
创建专门用于FT232的USB CDC对象
USB_HOST/App文件夹中打开usb_host.c文件,找到其中的MX_USB_HOST_Init函数,其中if (USBH_RegisterClass(&hUsbHostHS, USBH_CDC_CLASS) != USBH_OK)中输入的USBH_CDC_CLASS形参即为STM32 USB HOST库默认的CDC对象
//Cube默认生成的MX_USB_HOST_Init函数
void MX_USB_HOST_Init(void)
{
/* USER CODE BEGIN USB_HOST_Init_PreTreatment */
/* USER CODE END USB_HOST_Init_PreTreatment */
/* Init host Library, add supported class and start the library. */
if (USBH_Init(&hUsbHostHS, USBH_UserProcess, HOST_HS) != USBH_OK)
{
Error_Handler();
}
if (USBH_RegisterClass(&hUsbHostHS, USBH_CDC_CLASS) != USBH_OK)
{
Error_Handler();
}
if (USBH_Start(&hUsbHostHS) != USBH_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USB_HOST_Init_PostTreatment */
/* USER CODE END USB_HOST_Init_PostTreatment */
}
Middlewares/ST/STM32_USB_HOST_Library/Class/CDC/Inc文件夹中打开usbh_cdc.h文件,找到USBH_CDC_CLASS的define,将其复制一份到FT232.h中,命名为USBH_CDC_FT232_CLASS,值改为&FT232_CDC_Class
#define USB_CDC_FT232_CLASS 0xFFU
#define USBH_CDC_FT232_CLASS &FT232_CDC_Class
Middlewares/ST/STM32_USB_HOST_Library/Class/CDC/Src文件夹中打开usbh_cdc.c文件,找到CDC_Class的定义,将其复制一份到FT232.c中,命名为FT232_CDC_Class,修改其中的USB_CDC_CLASS为USB_CDC_FT232_CLASS,并将usbh_cdc.c文件包含的static函数声明与定义全部复制到FT232.c中
// 将以下函数的定义也复制到usbh_cdc.c文件中,函数定义部分太长,此处省略
static USBH_StatusTypeDef USBH_CDC_InterfaceInit(USBH_HandleTypeDef *phost);
static USBH_StatusTypeDef USBH_CDC_InterfaceDeInit(USBH_HandleTypeDef *phost);
static USBH_StatusTypeDef USBH_CDC_Process(USBH_HandleTypeDef *phost);
static USBH_StatusTypeDef USBH_CDC_SOFProcess(USBH_HandleTypeDef *phost);
static USBH_StatusTypeDef USBH_CDC_ClassRequest(USBH_HandleTypeDef *phost);
//这个函数后面用不上,但是此时也要复制过来
static USBH_StatusTypeDef GetLineCoding(USBH_HandleTypeDef *phost,
CDC_LineCodingTypeDef *linecoding);
//这个函数后面用不上,但是此时也要复制过来
static USBH_StatusTypeDef SetLineCoding(USBH_HandleTypeDef *phost,
CDC_LineCodingTypeDef *linecoding);
static void CDC_ProcessTransmission(USBH_HandleTypeDef *phost);
static void CDC_ProcessReception(USBH_HandleTypeDef *phost);
USBH_ClassTypeDef FT232_CDC_Class =
{
"CDC",
USB_CDC_FT232_CLASS,
USBH_CDC_InterfaceInit,
USBH_CDC_InterfaceDeInit,
USBH_CDC_ClassRequest,
USBH_CDC_Process,
USBH_CDC_SOFProcess,
NULL,
};
现在需要把刚刚创建的FT232_CDC_Class的实例加入MX_USB_HOST_Init中。STM32 USB HOST库使用USBH_RegisterClass(&hUsbHostHS, USBH_CDC_CLASS)来向USB主机协议栈注册一个设备类驱动,我们可以找到这个函数的定义如下:
/**
* @brief USBH_RegisterClass
* Link class driver to Host Core.
* @param phost : Host Handle
* @param pclass: Class handle
* @retval USBH Status
*/
USBH_StatusTypeDef USBH_RegisterClass(USBH_HandleTypeDef *phost, USBH_ClassTypeDef *pclass)
{
USBH_StatusTypeDef status = USBH_OK;
if (pclass != NULL)
{
if (phost->ClassNumber < USBH_MAX_NUM_SUPPORTED_CLASS)
{
/* link the class to the USB Host handle */
phost->pClass[phost->ClassNumber++] = pclass;
status = USBH_OK;
}
else
{
USBH_ErrLog("Max Class Number reached");
status = USBH_FAIL;
}
}
else
{
USBH_ErrLog("Invalid Class handle");
status = USBH_FAIL;
}
return status;
}
实际注册USB设备类驱动的代码就是phost->pClass[phost->ClassNumber++] = pclass;,我们不需要STM32 USB HOST库默认的CDC类,所以在MX_USB_HOST_Init中加入如下代码:
/* USER CODE BEGIN Includes */
#include "FT232.h"//引入刚写的FT232.h头文件
/* USER CODE END Includes */
/*
中间部分无改动,略
*/
void MX_USB_HOST_Init(void)
{
/* USER CODE BEGIN USB_HOST_Init_PreTreatment */
/* USER CODE END USB_HOST_Init_PreTreatment */
/* Init host Library, add supported class and start the library. */
if (USBH_Init(&hUsbHostHS, USBH_UserProcess, HOST_HS) != USBH_OK)
{
Error_Handler();
}
if (USBH_RegisterClass(&hUsbHostHS, USBH_CDC_CLASS) != USBH_OK)
{
Error_Handler();
}
if (USBH_Start(&hUsbHostHS) != USBH_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USB_HOST_Init_PostTreatment */
hUsbHostHS.pClass[hUsbHostHS.ClassNumber - 1] = USBH_CDC_FT232_CLASS;//新增代码
/* USER CODE END USB_HOST_Init_PostTreatment */
}
执行完这步之后,将程序下载至STM32H723,会发现USB Debug Message变化如下:
这代表STM32 USB HOST库已经成功注册FT232_CDC_Class了。
修改FT232 CDC类的初始化函数
打开FT232.c中的USBH_CDC_InterfaceInit函数,跟据USB Debug Message可以发现,现在FT232_CDC_Class在初始化时在下面这里失败了:
/**
* @brief USBH_CDC_InterfaceInit
* The function init the CDC class.
* @param phost: Host handle
* @retval USBH Status
*/
static USBH_StatusTypeDef USBH_CDC_InterfaceInit(USBH_HandleTypeDef *phost)
{
USBH_StatusTypeDef status;
uint8_t interface;
CDC_HandleTypeDef *CDC_Handle;
interface = USBH_FindInterface(phost, COMMUNICATION_INTERFACE_CLASS_CODE,
ABSTRACT_CONTROL_MODEL, COMMON_AT_COMMAND);
// FT232_CDC_Class在这个if语句初始化失败
if ((interface == 0xFFU) || (interface >= USBH_MAX_NUM_INTERFACES)) /* No Valid Interface */
{
USBH_DbgLog("Cannot Find the interface for Communication Interface Class.", phost->pActiveClass->Name);
return USBH_FAIL;
}
// 下略
因为FT232使用的厂商自定义interface,跟据使用wireshark抓取的FT232RL协议包可以发现,FT232RL的bInterfaceClass、bInterfaceSubClass和bInterfaceProtocol都是0xff,在STM32 USB HOST库中使用USBH_FindInterface来寻找设备,而此时USBH_FindInterface输入的形参还是STM32 USB HOST库默认的CDC设备的参数,所以找不到设备导致初始化失败了。
USBH_FindInterface的定义如下:
/**
* @brief USBH_FindInterface
* Find the interface index for a specific class.
* @param phost: Host Handle
* @param Class: Class code
* @param SubClass: SubClass code
* @param Protocol: Protocol code
* @retval interface index in the configuration structure
* @note : (1)interface index 0xFF means interface index not found
*/
uint8_t USBH_FindInterface(USBH_HandleTypeDef *phost, uint8_t Class, uint8_t SubClass, uint8_t Protocol)
{
USBH_InterfaceDescTypeDef *pif;
USBH_CfgDescTypeDef *pcfg;
uint8_t if_ix = 0U;
pif = (USBH_InterfaceDescTypeDef *)NULL;
pcfg = &phost->device.CfgDesc;
while (if_ix < USBH_MAX_NUM_INTERFACES)
{
pif = &pcfg->Itf_Desc[if_ix];
if (((pif->bInterfaceClass == Class) || (Class == 0xFFU)) &&
((pif->bInterfaceSubClass == SubClass) || (SubClass == 0xFFU)) &&
((pif->bInterfaceProtocol == Protocol) || (Protocol == 0xFFU)))
{
return if_ix;
}
if_ix++;
}
return 0xFFU;
}
可以看到USBH_FindInterface的输入参数正好是Class、SubClass和Protocol,所以将USBH_CDC_InterfaceInit函数中USBH_FindInterface输入的默认形参全部改为FT232RL的0xff
// 原来的USBH_FindInterface
// interface = USBH_FindInterface(phost, COMMUNICATION_INTERFACE_CLASS_CODE,
// ABSTRACT_CONTROL_MODEL, COMMON_AT_COMMAND);
// 修改后的USBH_FindInterface
interface = USBH_FindInterface(phost, 0XFF, 0XFF, 0XFF);
将修改后的程序下载至STM32H723,可以看到USB Debug Message变化如下:
继续在USBH_CDC_InterfaceInit中查找这条Debug Message在出现的地方,可以发现是在这里:
interface = USBH_FindInterface(phost, DATA_INTERFACE_CLASS_CODE,
RESERVED, NO_CLASS_SPECIFIC_PROTOCOL_CODE);
if ((interface == 0xFFU) || (interface >= USBH_MAX_NUM_INTERFACES)) /* No Valid Interface */
{
USBH_DbgLog("Cannot Find the interface for Data Interface Class.", phost->pActiveClass->Name);
return USBH_FAIL;
}
标准 CDC 设备有两个独立的接口,分别处理通信协议和收发数据,需要分别查找和初始化,但是FT232RL简化了操作,只需要查找一次interface就能解析出全部的接口,所以直接把上面这段代码从USBH_CDC_InterfaceInit删除,然后下载程序,可以看到USB Debug Message变化如下:
至此FT232_CDC_Class已经初始化完成。
FT232RL参数设置
在完成FT232_CDC_Class的初始化之后,需要设置FT232RL工作时的参数,也就是硬件流控、数据停止位、数据位、校验位、停止位、波特率和工作模式。FT232RL存在标准串口和bit bang两种工作模式,默认是以标准串口模式工作,因项目上只用到标准串口模式,bit bang模式没有研究。
首先需要解决ERROR: Control error: CDC: Device Get Line Coding configuration failed这个错误,在FT232.c中的USBH_CDC_ClassRequest函数,因为FT232RL不支持GetLineCoding功能,注释或删除掉这个函数,直接给status赋值跳过request过程,就不会输出ERROR: Control error: CDC: Device Get Line Coding configuration failed了。
/**
* @brief USBH_CDC_ClassRequest
* The function is responsible for handling Standard requests
* for CDC class.
* @param phost: Host handle
* @retval USBH Status
*/
static USBH_StatusTypeDef USBH_CDC_ClassRequest(USBH_HandleTypeDef *phost)
{
// USBH_StatusTypeDef status;// 原始的status定义
USBH_StatusTypeDef status = USBH_OK;// 直接给status赋值USBH_OK
CDC_HandleTypeDef *CDC_Handle = (CDC_HandleTypeDef *) phost->pActiveClass->pData;
/* Issue the get line coding request */
// status = GetLineCoding(phost, &CDC_Handle->LineCoding);// 注释或删除掉这行代码
if (status == USBH_OK)
{
phost->pUser(phost, HOST_USER_CLASS_ACTIVE);
}
else if (status == USBH_NOT_SUPPORTED)
{
USBH_ErrLog("Control error: CDC: Device Get Line Coding configuration failed");
}
else
{
/* .. */
}
return status;
}
我们需要自行创建一个初始化状态机来设置FT232RL的参数。先创建一个枚举类型FT232_CDC_Init_StateTypeDef用来表示FT232RL的初始化状态,创建一个枚举类型FT232_CDC_Init_CommandTypeDef表示FT232RL的指令,创建一个结构体FT232_CDC_Init_Param用于保存FT232RL的初始化参数,创建一个结构体FT232_CDC_Init_Node用于保存实际调用的指令函数指针和输入指令函数的参数,创建一个结构体FT232_CDC_Init_State_Machine用于管理FT232RL初始化状态机:
// 写在FT232.h中
typedef enum
{
FT232_CDC_RESET = 0x00,
FT232_CDC_SET_MODEM_CTRL = 0x01,
FT232_CDC_SET_FLOW_CTRL = 0x02,
FT232_CDC_SET_BAUD_RATE = 0x03,
FT232_CDC_SET_DATA = 0x04,
FT232_CDC_GET_MODEM_STATUS = 0x05,
FT232_CDC_SET_LATENCY_STATUS = 0x09,
FT232_CDC_SET_BITMODE = 0x0b,
}
FT232_CDC_Init_CommandTypeDef;
typedef enum
{
FT232_CDC_UNDER_INIT = 0x00,
FT232_CDC_INIT_COMPLETE = 0x01,
FT232_CDC_INIT_ERROR = 0x02
}
FT232_CDC_Init_StateTypeDef;
typedef struct{
uint32_t baudrate;
uint32_t modem;
uint32_t flow;
uint32_t latency;
struct{
uint32_t dataBits;
uint32_t stopBits;
uint32_t parity;
}data;
}FT232_CDC_Init_Param;
typedef struct _FT232_CDC_Init_Node{
FT232_CDC_Init_CommandTypeDef command;
USBH_StatusTypeDef state;
USBH_StatusTypeDef (*method)(USBH_HandleTypeDef*, struct _FT232_CDC_Init_Node*);
FT232_CDC_Init_Param param;
}FT232_CDC_Init_Node;
typedef struct{
FT232_CDC_Init_StateTypeDef FT232_CDC_Init_State;
FT232_CDC_Init_Node* currentNode;
}FT232_CDC_Init_State_Machine;
然后声明和定义需要执行的初始化指令函数,并初始化FT232RL的初始化状态机列表:
// 写在FT232.c中
static USBH_StatusTypeDef FTDI_ResetDevice(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* param);
static USBH_StatusTypeDef FTDI_SetLatency(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node);
static USBH_StatusTypeDef FTDI_SetModemCtrl(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node);
static USBH_StatusTypeDef FTDI_SetBaudrate(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node);
static USBH_StatusTypeDef FTDI_SetData(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node);
static USBH_StatusTypeDef FTDI_SetFlowCtrl(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node);
FT232_CDC_Init_Node FT232_CDC_Init_Node_List[] = {
{.command = FT232_CDC_RESET, .state = USBH_OK, .method = &FTDI_ResetDevice,
.param = {.data = {0, 0, 0}, .baudrate = 0, .flow = 0, .latency = 0, .modem = 0x0000}}, // reset
{.command = FT232_CDC_SET_LATENCY_STATUS, .state = USBH_OK, .method = &FTDI_SetLatency,
.param = {.data = {0, 0, 0}, .baudrate = 0, .flow = 0, .latency = 10, .modem = 0x0000}}, // setLatency
{.command = FT232_CDC_SET_MODEM_CTRL, .state = USBH_OK, .method = &FTDI_SetModemCtrl,
.param = {.data = {0, 0, 0}, .baudrate = 0, .flow = 0, .latency = 0, .modem = 0x0101}}, // setModemCtrl
{.command = FT232_CDC_SET_MODEM_CTRL, .state = USBH_OK, .method = &FTDI_SetModemCtrl,
.param = {.data = {0, 0, 0}, .baudrate = 115200, .flow = 0, .latency = 0, .modem = 0x0202}}, // setModemCtrl
{.command = FT232_CDC_SET_BAUD_RATE, .state = USBH_OK, .method = &FTDI_SetBaudrate,
.param = {.data = {0, 0, 0}, .baudrate = 0, .flow = 0, .latency = 0, .modem = 0x0000}}, // setBaudrate
{.command = FT232_CDC_SET_MODEM_CTRL, .state = USBH_OK, .method = &FTDI_SetModemCtrl,
.param = {.data = {0, 0, 0}, .baudrate = 0, .flow = 0, .latency = 0, .modem = 0x0202}}, // setModemCtrl
{.command = FT232_CDC_SET_MODEM_CTRL, .state = USBH_OK, .method = &FTDI_SetModemCtrl,
.param = {.data = {0, 0, 0}, .baudrate = 0, .flow = 0, .latency = 0, .modem = 0x0101}}, // setModemCtrl
{.command = FT232_CDC_SET_DATA, .state = USBH_OK, .method = &FTDI_SetData,
.param = {.data = {8, 1, 0}, .baudrate = 0, .flow = 0, .latency = 0, .modem = 0x0000}}, // setData
{.command = FT232_CDC_SET_FLOW_CTRL, .state = USBH_OK, .method = &FTDI_SetFlowCtrl,
.param = {.data = {0, 0, 0}, .baudrate = 0, .flow = 0, .latency = 0, .modem = 0x0000}}, // setFlow
};
inline static USBH_StatusTypeDef FTDI_EncodeRequest(USBH_HandleTypeDef *phost, uint8_t requestType, uint8_t request, uint16_t value, uint16_t index, uint16_t length)
{
phost->Control.setup.b.bmRequestType = requestType;
phost->Control.setup.b.bRequest = request;
phost->Control.setup.b.wValue.w = value;
phost->Control.setup.b.wIndex.w = index; // 接口号
phost->Control.setup.b.wLength.w = length;
return USBH_CtlReq(phost, NULL, 0);
}
static USBH_StatusTypeDef FTDI_ResetDevice(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node)
{
return FTDI_EncodeRequest(phost, 0x40, node->command, 0, 0, 0);
}
static USBH_StatusTypeDef FTDI_SetLatency(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node)
{
return FTDI_EncodeRequest(phost, 0x40, node->command, node->param.latency, 0, 0);
}
static USBH_StatusTypeDef FTDI_SetModemCtrl(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node)
{
return FTDI_EncodeRequest(phost, 0x40, node->command, node->param.modem, 0, 0);
}
static USBH_StatusTypeDef FTDI_SetBaudrate(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node)
{
static const uint8_t frac_code[] = {0, 3, 2, 4, 1, 5, 6, 7};
uint16_t value, index;
uint32_t divisor;
if (node->param.baudrate == 0){
return USBH_FAIL; // 防止除零
}
// 特殊处理3M波特率
if (node->param.baudrate == 3000000){
divisor = 0; // 实际分频数是1,但寄存器值设为0
}
else
{
// 计算分频数,公式为 Divisor = 3000000 / Baudrate
// 为了进行整数运算并四舍五入,我们将分子乘以8
divisor = (3000000 * 8) / node->param.baudrate;
divisor = (divisor + 1) / 2 * 2; // 四舍五入
}
// 最终要写入寄存器的值
value = (uint16_t)(divisor / 8);
// 如果计算出的整数部分大于最大值 (2^14 - 1),则出错
if (value >= 0x4000)
{
return USBH_FAIL;
}
// 将小数部分的编码放入 value 的高位
// divisor % 8 的结果是 0-7,对应 frac_code 数组的索引
index = divisor & 0x07;
value |= (uint16_t)(frac_code[index] << 14);
return FTDI_EncodeRequest(phost, 0x40, node->command, value, 0, 0);
}
static USBH_StatusTypeDef FTDI_SetData(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node)
{
uint16_t value = 0;
uint8_t data_bits = node->param.data.dataBits;
uint8_t parity = node->param.data.parity;
uint8_t stop_bits = node->param.data.stopBits;
value |= data_bits;
value |= (uint16_t)(parity << 8);
value |= (uint16_t)(stop_bits << 11);
return FTDI_EncodeRequest(phost, 0x40, node->command, value, 0, 0);
}
static USBH_StatusTypeDef FTDI_SetFlowCtrl(USBH_HandleTypeDef *phost, FT232_CDC_Init_Node* node)
{
return FTDI_EncodeRequest(phost, 0x40, node->command, node->param.flow, 0, 0);
}
然后创建状态机变量与状态机执行函数:
// 写在FT232.c中
static void FTDI_Init_State_Machine(USBH_HandleTypeDef* phost);
FT232_CDC_Init_State_Machine sm = {
.FT232_CDC_Init_State = FT232_CDC_UNDER_INIT,
.currentNode = FT232_CDC_Init_Node_List,
.currentNodeIndex = 0,
.nodeLength = sizeof(FT232_CDC_Init_Node_List) / sizeof(FT232_CDC_Init_Node_List[0]),
};
static void FTDI_Init_State_Machine(USBH_HandleTypeDef* phost)
{
if(sm.currentNode != NULL){
USBH_StatusTypeDef status = sm.currentNode->method(phost, sm.currentNode);
if(status == USBH_OK){
sm.currentNode++;
sm.currentNodeIndex++;
if(sm.currentNodeIndex >= sm.nodeLength){
sm.FT232_CDC_Init_State = FT232_CDC_INIT_COMPLETE;
USBH_UsrLog("FTDI Init Complete");
}
}
else if(status == USBH_BUSY){
// 继续等待
}
else{
sm.FT232_CDC_Init_State = FT232_CDC_INIT_ERROR;
USBH_ErrLog("FTDI Init State Machine Error: %d", status);
}
}
}
然后把状态机放到USBH_CDC_Process中运行:
// 写在FT232.c中
static USBH_StatusTypeDef USBH_CDC_Process(USBH_HandleTypeDef *phost)
{
USBH_StatusTypeDef status = USBH_BUSY;
USBH_StatusTypeDef req_status = USBH_OK;
CDC_HandleTypeDef *CDC_Handle = (CDC_HandleTypeDef *) phost->pActiveClass->pData;
// 添加的代码
if(sm.FT232_CDC_Init_State == FT232_CDC_UNDER_INIT){
FTDI_Init_State_Machine(phost);
}
// 添加的代码结束
switch (CDC_Handle->state)
// 后略
下载程序,可以看到USB Debug Message变化如下:
至此FT232RL的初始化部分全部完成了。
用修改的STM32 USB HOST CDC库收发数据
发送
在main.c中写一个简单的USB发送程序如下:
// 写在main.c中
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
extern USBH_HandleTypeDef hUsbHostHS;
extern FT232_CDC_Init_State_Machine sm;
uint8_t isSend = 0, sendover = 0;
/* USER CODE END PV */
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MPU Configuration--------------------------------------------------------*/
MPU_Config();
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
MX_USART3_UART_Init();
MX_USB_HOST_Init();
/* USER CODE BEGIN 2 */
const char* message = "Hello World!\r\n";
uint32_t message_len = strlen(message);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
MX_USB_HOST_Process();
if(hUsbHostHS.gState == HOST_CLASS && isSend == 0 && sm.FT232_CDC_Init_State == FT232_CDC_INIT_COMPLETE){
USBH_StatusTypeDef status = USBH_CDC_Transmit(&hUsbHostHS, message, message_len);
if(status == USBH_OK){
isSend = 1;
}
else{
USBH_UsrLog("USBH_CDC_Transmit call FAILED with status: %d", status);
HAL_Delay(1000); // 失败后等待一下再重试
}
}
else if(hUsbHostHS.gState == HOST_CLASS && isSend == 1 && sm.FT232_CDC_Init_State == FT232_CDC_INIT_COMPLETE){
if(sendover == 1){
CDC_HandleTypeDef *CDC_Handle = (CDC_HandleTypeDef *) hUsbHostHS.pActiveClass->pData;
USBH_URBStateTypeDef status = USBH_LL_GetURBState(&hUsbHostHS, CDC_Handle->DataItf.OutPipe);
if(status == USBH_URB_DONE){
isSend = 0;
sendover = 0;
HAL_Delay(1000);
}
}
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
void USBH_CDC_TransmitCallback(USBH_HandleTypeDef *phost)
{
printf("Data transmission completed!\r\n");
sendover = 1;
}
/* USER CODE END 4 */
如果前面的设置都没问题,PC上位机就能成功接收STM32H723发送的数据了:
接收
在main.c中创建一个接收缓冲区和接收标志位:
uint8_t recievebuf[64];
uint8_t recieveEn = 0;
在main函数的while(1)中添加接收的函数:
if(hUsbHostHS.gState == HOST_CLASS && sm.FT232_CDC_Init_State == FT232_CDC_INIT_COMPLETE && recieveEn == 0){
USBH_CDC_Receive(&hUsbHostHS, recievebuf, 64);
recieveEn = 1;
}
USBH_CDC_Receive为STM32 USB HOST CDC库的接收函数,这个函数不能每次循环都调用,STM32 USB HOST CDC库的接收有两个状态,分别为CDC_RECEIVE_DATA和CDC_RECEIVE_DATA_WAIT,如果每次循环都调用USBH_CDC_Receive,USB将会一直处于CDC_RECEIVE_DATA状态,导致无法完成一次接收。
USBH_StatusTypeDef USBH_CDC_Receive(USBH_HandleTypeDef *phost, uint8_t *pbuff, uint32_t length)
{
USBH_StatusTypeDef Status = USBH_BUSY;
CDC_HandleTypeDef *CDC_Handle = (CDC_HandleTypeDef *) phost->pActiveClass->pData;
if ((CDC_Handle->state == CDC_IDLE_STATE) || (CDC_Handle->state == CDC_TRANSFER_DATA))
{
CDC_Handle->pRxData = pbuff;
CDC_Handle->RxDataLength = length;
CDC_Handle->state = CDC_TRANSFER_DATA;
CDC_Handle->data_rx_state = CDC_RECEIVE_DATA;
Status = USBH_OK;
#if (USBH_USE_OS == 1U)
USBH_OS_PutMessage(phost, USBH_CLASS_EVENT, 0U, 0U);
#endif /* (USBH_USE_OS == 1U) */
}
return Status;
}
//USB接收的状态处理函数
static void CDC_ProcessReception(USBH_HandleTypeDef *phost)
{
CDC_HandleTypeDef *CDC_Handle = (CDC_HandleTypeDef *) phost->pActiveClass->pData;
USBH_URBStateTypeDef URB_Status = USBH_URB_IDLE;
uint32_t length;
switch (CDC_Handle->data_rx_state)
{
case CDC_RECEIVE_DATA:
(void)USBH_BulkReceiveData(phost,
CDC_Handle->pRxData,
CDC_Handle->DataItf.InEpSize,
CDC_Handle->DataItf.InPipe);
#if defined (USBH_IN_NAK_PROCESS) && (USBH_IN_NAK_PROCESS == 1U)
phost->NakTimer = phost->Timer;
#endif /* defined (USBH_IN_NAK_PROCESS) && (USBH_IN_NAK_PROCESS == 1U) */
CDC_Handle->data_rx_state = CDC_RECEIVE_DATA_WAIT;
break;
case CDC_RECEIVE_DATA_WAIT:
URB_Status = USBH_LL_GetURBState(phost, CDC_Handle->DataItf.InPipe);
/*Check the status done for reception*/
if (URB_Status == USBH_URB_DONE)
{
length = USBH_LL_GetLastXferSize(phost, CDC_Handle->DataItf.InPipe);
if (((CDC_Handle->RxDataLength - length) > 0U) && (length == CDC_Handle->DataItf.InEpSize))
{
CDC_Handle->RxDataLength -= length;
CDC_Handle->pRxData += length;
CDC_Handle->data_rx_state = CDC_RECEIVE_DATA;
}
else
{
CDC_Handle->data_rx_state = CDC_IDLE;
USBH_CDC_ReceiveCallback(phost);
}
#if (USBH_USE_OS == 1U)
USBH_OS_PutMessage(phost, USBH_CLASS_EVENT, 0U, 0U);
#endif /* (USBH_USE_OS == 1U) */
}
#if defined (USBH_IN_NAK_PROCESS) && (USBH_IN_NAK_PROCESS == 1U)
else if (URB_Status == USBH_URB_NAK_WAIT)
{
CDC_Handle->data_rx_state = CDC_RECEIVE_DATA_WAIT;
if ((phost->Timer - phost->NakTimer) > phost->NakTimeout)
{
phost->NakTimer = phost->Timer;
USBH_ActivatePipe(phost, CDC_Handle->DataItf.InPipe);
}
#if (USBH_USE_OS == 1U)
USBH_OS_PutMessage(phost, USBH_CLASS_EVENT, 0U, 0U);
#endif /* (USBH_USE_OS == 1U) */
}
#endif /* defined (USBH_IN_NAK_PROCESS) && (USBH_IN_NAK_PROCESS == 1U) */
else
{
/* .. */
}
break;
default:
break;
}
}
然后定义USB的接收中断回调函数,在回调函数中重置标注位并打印接收到的数据:
void USBH_CDC_ReceiveCallback(USBH_HandleTypeDef *phost)
{
uint16_t recieveLen = USBH_CDC_GetLastReceivedDataSize(phost) - 2;
USBH_CDC_Transmit(&hUsbHostHS, recievebuf + 2, recieveLen);
isSend = 1;
recieveEn = 0;
}
通过上位机接收到的数据来看,STM32H723 USB接收到的一帧数据前两位为0x01 0x60,推测为CDC模式的帧头标志,故在接收时跳过前两位,只处理后面的数据。

后续待更新
更多推荐



所有评论(0)