跳转至

usb开发使用指南

迁移公告

本文档中心已迁移至 新域名(https://document.nationalchip.com/) ,当前文档中心维护有效期至 2025年6月30日。 请更新您的书签或外部链接,感谢您的支持!

usb开发使用指南*

第一章 功能特性*

主要特性

  • 支持全速模式
  • 支持 DMA
  • 最多可以支持 16 个端点,包括端点 0

第二章 使用示例*

2.1 自定义 USB HID 收发*

下面给出一个例子,在电脑上可以被识别为一个HID 鼠标设备和一个HID 私有设备。我们可以通过HID 私有设备与电脑进行自定义数据的通讯。在这里,发现windows电脑上,要实现HID 私有设备与电脑通讯,必现要先支持 HID鼠标或者HID键盘等标准设备。因此,这里也同时创建了一个 HID 鼠标设备。

使用USB功能,cpu的主频必现是144M。因此需要修改对应app目录下的app_system/app_system_clk.c

在 PC 上可以使用 Bus Hound 等工具进行数据的收发测试

#define SYS_CLK_CFG    3 //0:32M; 1:96M; 2:128M; 3:144M; 4:160M
#include <stdio.h>
#include <gx_hal_clk_apus.h>
#include <osal.h>
#include <gx_usbd.h>

static void _app_usb_hid_priv_send(const unsigned char *data, unsigned char len)
{
    usbd_hid_vendor_write((void *)data, len);
}

static void _app_usb_hid_vendor_cb(uint8_t *data, uint32_t size)
{
    printf("size = %d, data = 0x%02x
", size, *data); // 接收到的数据
}

static void usb_send_data_test(void)
{
    printf("send usb data
");

    // 自定义数据
    char data[16] = {0};
    data[0] = 0xFB;
    data[1] = 0x22;
    data[2] = 0x33;
    data[3] = 0x44;
    _app_usb_hid_priv_send(data, 16); //发送的数据不限制大小。usb最大只能发送64字节,如果发送的数据长度大于64字节,内部会自动分多次发送
}

int main(void)
{
    printf("cpu freq =%d div =%d
", gx_hal_clk_mod_get_freq(GX_HAL_CLK_MOD_CPU), gx_hal_clk_mod_get_div(GX_HAL_CLK_MOD_CPU));
    printf("xip freq =%d div =%d
", gx_hal_clk_mod_get_freq(GX_HAL_CLK_MOD_XIP), gx_hal_clk_mod_get_div(GX_HAL_CLK_MOD_XIP));

    usbd_class_list_init();
    usbd_hid_mouse_class_register();
    usbd_hid_vendor_class_register(); // HID私有设备
    usbd_hid_vendor_register_callback(_app_usb_hid_vendor_cb); // HID私有设备接收数据回调
    usbd_register();
    return 0;
}

COMMAND_EXPORT_ALIAS(usb_send_data_test, usb_send_data_test, "usb_send_data_test");

2.2 USB HID OTA*

通过USB HID OTA的方式升级,可以不需要按boot键和进行复位芯片

默认的升级脚本,使用的usb VID和PID都是0x1919 如果自己需要修改,修改config/sdk_config.h

// USB
#define CONFIG_USB_VENDOR_ID    (0x1919)
#define CONFIG_USB_PRODUCT_ID    (0x1919)

OTA升级工具在 apusppsi_mouse ools目录下

windows上,通过cmd命令行的方式,进入tools\windows,在该目录下先手动创建个tmp的文件夹

然后通过命令 >python hid_windows_x64.exe apus.bin 触发OTA升级

#include <stdio.h>
#include <gx_hal_clk_apus.h>
#include <osal.h>
#include <gx_usbd.h>
#include <pm.h>

static void _app_usb_hid_priv_send(const unsigned char *data, unsigned char len)
{
    usbd_hid_vendor_write((void *)data, len);
}

static void _app_usb_hid_vendor_cb(uint8_t *data, uint32_t size)
{
    printf("size = %d, data = 0x%02x
", size, *data);

    // USB HID OTA
    if (*data == 0xf0) //firmware upgrade
    {
        uint8_t v_cmd = 0x80;
        _app_usb_hid_priv_send(&v_cmd, 1);
        gx_mdelay(10);

        pm_enter_boot_mode(BOOT_MEDIUM_USB);
    }
}

static void usb_send_data_test(void)
{
    printf("send usb data
");

    // 自定义数据
    char data[16] = {0};
    data[0] = 0xFB;
    data[1] = 0x22;
    data[2] = 0x33;
    data[3] = 0x44;
    _app_usb_hid_priv_send(data, 16); //发送的数据不限制大小。usb最大只能发送64字节,如果发送的数据长度大于64字节,内部会自动分多次发送
}

int main(void)
{
    printf("cpu freq =%d div =%d
", gx_hal_clk_mod_get_freq(GX_HAL_CLK_MOD_CPU), gx_hal_clk_mod_get_div(GX_HAL_CLK_MOD_CPU));
    printf("xip freq =%d div =%d
", gx_hal_clk_mod_get_freq(GX_HAL_CLK_MOD_XIP), gx_hal_clk_mod_get_div(GX_HAL_CLK_MOD_XIP));

    usbd_class_list_init();
    usbd_hid_mouse_class_register();
    usbd_hid_vendor_class_register(); // HID私有设备
    usbd_hid_vendor_register_callback(_app_usb_hid_vendor_cb); // HID私有设备接收数据回调
    usbd_register();
    return 0;
}

COMMAND_EXPORT_ALIAS(usb_send_data_test, usb_send_data_test, "usb_send_data_test");
升级命令

升级成功显示

2.3 USB HID OTA windows工具*

这里使用python实现个了 windows的界面工具,方便用户使用非命令行的方式进行OTA升级

客户也可以基于这个代码进行二次修改,来满足自己的需要

编译命令:pyinstaller --onefile --hidden-import=usb --hidden-import=usb.backend.libusb1 --add-data "bootx.exe;bin" --add-data "apus.bin;bin" --add-data "hid_windows_x64.exe;bin" usb_upgrade.py

#!/usr/bin/env python3
# coding:utf-8

import usb.core
import usb.util
import usb.backend.libusb1
import subprocess
import asyncio
import argparse
import sys
import os
import time
import shutil
from pathlib import Path
from  tkinter import *

BOOT_VID = 0xa700
BOOT_PID = 0x0002

APP_VID = 0x1919
APP_PID = 0x1919

APP_VENDOR_INTERFACE_PROTOCOL = 0

def find_boot_usb_dev():
    #dev = usb.core.find(idVendor=BOOT_VID, idProduct=BOOT_PID)
    return None

def find_app_usb_dev():
    hid_executable_path = get_binary_path(os.path.join("bin", "hid_windows_x64.exe"))
    command = [hid_executable_path, "0x1919", "0x1919", "r"]

    try:
        # 执行命令并获取输出
        process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

        # 实时读取输出
        while True:
            output = process.stdout.readline()
            if output == '' and process.poll() is not None:
                break
            if output:
                print(output.strip())  # 打印实时输出
                # 检查输出中是否包含 "failed"
                if "failed" in output:
                    print("FF")
                    return False

        # 如果没有找到 "failed"
        print("AA")
        return True

    except Exception as e:
        print(f"Error executing command: {e}")

def get_app_usb_interface(dev, interface_protocol):
    cfg = dev.get_active_configuration()
    interface = None

    for intf in cfg:
        if intf.bInterfaceProtocol == interface_protocol:
            interface = intf

    return interface

def detach_app_usb_interface(dev, interface_number):
    if dev.is_kernel_driver_active(interface_number):
        try:
            dev.detach_kernel_driver(interface_number)
        except usb.core.USBError as e:
            raise RuntimeError("Could not detach kernel driver: %s" % str(e))


def write_out_ep(dev, endpoint, data):
    size = 0
    while(True):
        try:
            size = dev.write(endpoint.bEndpointAddress, data, 1000)  #write(endpoint, data, timeout = None)
        except Exception as e:
            print(e)
        break

    return size

def read_in_ep(dev, endpoint):
    data = None
    while(True):
        try:
            data = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, 3000)  #read(endpoint, size_or_buffer, timeout = None)
        except Exception as e:
            print(e)
        break

    return data

def split_bin(bin_path, target_dir):
    boot_bin_path = os.path.join(target_dir, "tmp_boot.bin")
    app_bin_path = os.path.join(target_dir, "tmp_app.bin")

    bin_size = os.path.getsize(bin_path)

    with open(bin_path, 'rb') as bin_file, open(boot_bin_path, 'wb') as boot_file, open(app_bin_path, 'wb') as app_file:
        bin_data = bin_file.read(0x1000)
        boot_file.write(bin_data)

        bin_file.seek(0x1000)
        bin_data = bin_file.read(bin_size - 0x1000)
        app_file.write(bin_data)

    return boot_bin_path, app_bin_path

def bootx_exec(boox_path, bin_path):
    boot_bin, app_bin = split_bin(bin_path, './')

    print("Startup bootx ...")
    bootx_cmd = 'flash erase 0 0x1000' + ';' + 'download 0x1000 ' + app_bin + ';' + 'download 0 ' + boot_bin + ';' +'reboot'
    ret = subprocess.check_call([boox_path, "-m", "auto", "-t", "u", "-c", bootx_cmd])

    os.remove(boot_bin)
    os.remove(app_bin)

    return ret

def fun_refresh_text(text, str_text):
    text['state'] = "normal"
    text.insert('end', str_text)
    text.insert('end', "
")
    text.see(END)
    text['state'] = "disabled"

def fun_clear_text(text):
    text['state'] = "normal"
    text.delete(0.0, END)
    text['state'] = "disabled"

def bootx_exec_new(boox_path, bin_path):
    boot_bin, app_bin = split_bin(bin_path, './')

    print("Startup bootx ...")
    bootx_cmd = 'flash erase 0 0x1000' + ';' + 'download 0x1000 ' + app_bin + ';' + 'download 0 ' + boot_bin + ';' +'reboot'


    # 启动进程
    process = subprocess.Popen(
    [boox_path, "-m", "auto", "-t", "u", "-c", bootx_cmd],
    stdout=subprocess.PIPE,  # 重定向标准输出
    text=True               # 以文本形式处理输出
    )

    # 实时获取输出(逐行)
    while True:
        output = process.stdout.readline()
        if output == '' and process.poll() is not None:
            break
        if output:
            print(output.strip())
            fun_refresh_text(text_log, output.strip())

    # 获取剩余输出和错误
    stdout, stderr = process.communicate()

    #print("返回码:", process.returncode)
    #fun_refresh_text(text_log, process.returncode)

    #ret = subprocess.check_call([boox_path, "-m", "auto", "-t", "u", "-c", bootx_cmd])

    os.remove(boot_bin)
    os.remove(app_bin)

    return process.returncode

def upgrade_main(bootx_path, bin_path):
    if os.path.isfile(bootx_path) is False:
        raise ValueError("bootx is not found")
        return

    if os.path.isfile(bin_path) is False:
        raise ValueError("bin is not found")
        return

    print("Find target device ...")

    ret = find_app_usb_dev()
    if ret is False:
        dev = find_boot_usb_dev()
        if dev is None:
            print("Recovery...")
            ret = bootx_exec_new(bootx_path, bin_path)
            if ret == 0:
                print("Upgrade success")
        else:
            raise ValueError("Device not found")
        return

    print("Send upgrade cmd")

    #hid_executable_path = os.path.join(py_path, "./hid_windows_x64.exe")
    hid_executable_path = get_binary_path(os.path.join("bin", "hid_windows_x64.exe"))

    try:
        subprocess.check_call([hid_executable_path, "0x1919", "0x1919", "write", "0xf0"])
        print("hid_windows_x64.exe executed successfully.")
    except subprocess.CalledProcessError as e:
        print("Error executing hid_windows_x64.exe: %s" % str(e))
        exit(1)

    time.sleep(1)

    ret = bootx_exec_new(bootx_path, bin_path)
    if ret == 0:
        print("Upgrade success")

    return

def fun_button_exit_command():
    external_exe_path = get_binary_path(os.path.join("bin", "bootx.exe"))
    external_bin_path = get_binary_path(os.path.join("bin", "apus.bin"))

    try:
        upgrade_main(external_exe_path, external_bin_path)
    except Exception as e:
        print(f"Error executing command: {e}")

def get_binary_path(relative_path):
    """获取打包的二进制文件路径"""
    try:
        # PyInstaller创建的临时文件夹路径
        base_path = sys._MEIPASS
    except AttributeError:
        # 不在打包环境中,使用当前目录
        base_path = os.path.dirname(os.path.abspath(__file__))

    return os.path.join(base_path, relative_path)

if __name__ == '__main__':
    py_path = os.path.dirname(os.path.realpath(__file__))

    #external_exe_path = get_binary_path(os.path.join("bin", "bootx.exe"))
    #external_bin_path = get_binary_path(os.path.join("bin", "apus.bin"))

    #upgrade_main(external_exe_path, external_bin_path)

    if getattr(sys, 'frozen', False):
        ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)

    root = Tk()
    root.title("download")

    root.geometry("600x600")

    # 禁止调整窗口大小(宽度和高度都不可调)
    root.resizable(False, False)

    frame_5 = Frame(root)
    button_exit = Button(frame_5, text="升级", font=("Arial", 20), bd = 4, fg = "black", command=fun_button_exit_command)
    button_exit.pack(side=BOTTOM, fill=BOTH, expand=1)
    frame_5.pack()

    frame_3 = Frame(root)
    scrollbar_log = Scrollbar(root)
    text_log = Text(frame_3, font=("Arial", 10), bd=4, height=10, width=30, state=DISABLED)
    scrollbar_log.config(command=text_log.yview)
    text_log.config(yscrollcommand=scrollbar_log.set)
    text_log.pack(side=LEFT, fill=BOTH, expand=1)
    scrollbar_log.pack(side=LEFT, fill=Y)
    frame_3.pack(fill=BOTH, expand=1)

    root.mainloop()

参考代码下载链接(http://yun.nationalchip.com:10000/l/pFDejk)

界面示图: