搜索
bottom↓
回复: 0

《MiniPro STM32H750 开发指南》第六十章 USB鼠标键盘(Host)实验

[复制链接]

出0入234汤圆

发表于 2022-10-11 15:24:16 | 显示全部楼层 |阅读模式
1)实验平台:正点原子MiniPro STM32H750开发板
2)平台购买链接:https://detail.tmall.com/item.htm?id=677017430560
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boar
4)MiniPro STM32H750技术交流QQ群:756580169 lQLPJxaFi2zaB4UWWrDAMgIsFEW2pwLb3abnwDMA_90_22.png
lQDPJxaFi2nfFizMjM0CbLCPlxn_FVheIQLb3aGrwFQA_620_140.jpg

lQLPJxaFi2nfFhLMkM0BXrDNvOUyeU_FPgLb3aGvQNIA_350_144.png



第六十章 USB鼠标键盘(Host)实验


本章我们介绍如何使用STM32H750的USB HOST来驱动USB鼠标/键盘。
本章分为如下几个小节:
60.1 USB鼠标键盘简介
60.2 硬件设计
60.3 程序设计
60.4 下载验证


60.1 USB鼠标键盘简介

传统的鼠标和键盘是采用PS/2接口和电脑通信的,但是现在PS/2接口在电脑上逐渐消失,所以现在越来越多的鼠标键盘采用的是USB接口,而不是PS/2接口的了。
USB鼠标键盘属于USB HID设备。USB HID即:Human Interface Device(人机交互设备)的缩写,键盘、鼠标与游戏杆等都属于此类设备。不过HID设备并不一定要有人机接口,只要符合HID类别规范的设备都是HID设备。关于USB HID的知识,我们这里就不详细介绍了,请大家自行百度学习。
本章,我们同上一章一样,我们直接移植官方的USB HID例程,官方例程路径:光盘8,STM32参考资料 1,STM32CubeH7固件包 STM32Cube_FW_H7_V1.6.0Projects
STM32H743I-EVALApplicationsUSB_HostHID_Standalone,该例程支持USB鼠标和键盘等USB HID设备,本章我们将移植这个例程到我们的开发板上。
60.2 硬件设计
1. 例程功能
本实验代码,开机的时候先显示一些提示信息,然后初始化USB HOST,并不断轮询。当检测到USB鼠标/键盘的插入后,显示设备类型,并显示设备输入数据:
如果是USB鼠标:将显示鼠标移动的坐标(X,Y坐标),滚轮滚动数值(Z坐标)以及按键(左中右)。
如果是USB键盘:将显示键盘输入的数字/字母等内容(不是所有按键都支持,部分按键没有做解码支持,比如F1~F12)。
2. 硬件资源
1)RGB灯
   RED :LED0 - PB4
GREEN :LED1 - PE6
2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4)USB_HOST接口(D-/D+连接在PA11/PA12上)
60.3 程序设计
60.3.1 程序流程图


第六十章 USB鼠标键盘1004.png
图60.3.1.1 USB鼠标键盘实验(Host)程序流程图

60.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。
本实验,我们在上一个实验的基础上修改,对照官方HID例子,然后,我们在工程里面添加USB HID相关代码,如图60.3.2.1所示:
第六十章 USB鼠标键盘1151.png
图60.3.2.1 USB鼠标键盘工程分组

可以看到,USB部分代码,同上一章的在结构上是一模一样的,只是.c文件有些变化。我们重点介绍下USB_HOST里面的usbh_hid_mouse.c这个文件,该文件拷贝自ST官方例程,但是ST官方例程对鼠标的支持不好(兼容性差,不支持滚轮等),我们重写了该文件,下面介绍该文件。
1. USB驱动代码
usbh_hid_mouse.h,首先介绍usbh_hid_mouse.h里面定义的一个结构体,具体如下:
/* 鼠标信息结构体*/
typedef struct _HID_MOUSE_Info
{
  uint8_t              x;           /* x轴增量(强制转换成signed char后使用)*/
  uint8_t              y;           /* y轴增量(强制转换成signed char后使用)*/
  uint8_t              z;           /* z轴增量(强制转换成signed char后使用)*/
  uint8_t              button;     /* 将buttons修改为button,存储按键状态 */
}
HID_MOUSE_Info_TypeDef;
HID_MOUSE_Info_TypeDef结构体中,成员x和y表示鼠标的横向/纵向移动的增量值,成员z表示鼠标的滚轮的滚动增量值,在使用的时候,需要强制转换成unsigned char类型后使用(有±)。button的bit0、bit1、bit2分别表示鼠标的:左键、右键和滚轮键的按下状态。我们通过这个结构体,就可以获得当前鼠标的输入状态。
usbh_hid_mouse.c,总共有三个函数,首先介绍的是USBH鼠标初始化函数,其定义如下:
/* 鼠标信息(坐标+按钮状态)*/
HID_MOUSE_Info_TypeDef    mouse_info;           
/* 鼠标上报数据长度,最多HID_QUEUE_SIZE个字节*/
uint8_t mouse_report_data[HID_QUEUE_SIZE];      

/**
* @brief       USBH 鼠标初始化
* @param       phost       : USBH句柄指针
* @retval      USB状态
*   @arg       USBH_OK(0)   , 正常;
*   @arg       USBH_BUSY(1) , 忙;
*   @arg       其他         , 失败;
*/
USBH_StatusTypeDef USBH_HID_MouseInit(USBH_HandleTypeDef *phost)
{
HID_HandleTypeDef *HID_Handle =  
(HID_HandleTypeDef *) phost->pActiveClass->pData;
    mouse_info.x = 0;
    mouse_info.y = 0;
    mouse_info.button = 0;
    if (HID_Handle->length > sizeof(mouse_report_data))
    {
        HID_Handle->length = sizeof(mouse_report_data);
    }
    HID_Handle->pData = (uint8_t *)mouse_report_data;
    USBH_HID_FifoInit(&HID_Handle->fifo, phost->device.Data, HID_QUEUE_SIZE);
    return USBH_OK;
}
USBH_HID_MouseInit函数,用于初始化鼠标,指定USB鼠标输出数据的缓存,初始化FIFO等,该函数在USBH_HID_Process函数里面被调用。
接下来介绍的是USBH获取鼠标信息函数,其定义如下:
/**
* @brief       USBH 获取鼠标信息
* @param       phost       : USBH句柄指针
* @retval      鼠标信息(HID_MOUSE_Info_TypeDef)
*/
HID_MOUSE_Info_TypeDef *USBH_HID_GetMouseInfo(USBH_HandleTypeDef *phost)
{
    if (USBH_HID_MouseDecode(phost) == USBH_OK)
    {
        return &mouse_info;
    }
    else
    {
        return NULL;
    }
}
USBH_HID_GetMouseInfo函数,用于获取鼠标信息,通过USBH_HID_MouseDecode函数获取当前鼠标传回来的数据。该函数在USB_Demo函数里面被调用。
最后介绍的是USBH 鼠标数据解析函数,其定义如下:
/**
* @brief       USBH 鼠标数据解析函数
* @param       phost       : USBH句柄指针
* @retval      USB状态
*   @arg       USBH_OK(0)   , 正常;
*   @arg       USBH_BUSY(1) , 忙;
*   @arg       其他           , 失败;
*/
USBH_StatusTypeDef USBH_HID_MouseDecode(USBH_HandleTypeDef *phost)
{
HID_HandleTypeDef *HID_Handle =
(HID_HandleTypeDef *) phost->pActiveClass->pData;

    if (HID_Handle->length == 0)return USBH_FAIL;

if (USBH_HID_FifoRead(&HID_Handle->fifo, &mouse_report_data,
HID_Handle->length) == HID_Handle->length) /* 读取FIFO*/
    {
        mouse_info.button = mouse_report_data[0];
        mouse_info.x      = mouse_report_data[1];
        mouse_info.y      = mouse_report_data[2];
        mouse_info.z      = mouse_report_data[3];
        return USBH_OK;
    }
    return   USBH_FAIL;
}
USBH_HID_MouseDecode函数,用于解析USB HOST获取到的USB鼠标数据,并存放到mouse_info结构体里面。该函数被USBH_HID_GetMouseInfo函数调用。
另外,为了提高对鼠标键盘的识别率和兼容性,对usbh_hid_core.c里面的两处代码进行了修改:
1,在usbh_hid.c里面,USBH_HID_Process函数,修改case HID_IDLE部分代码(366行)为:
case HID_IDLE:
        status = USBH_HID_GetReport(phost, 0X01U, 0U, HID_Handle->pData, (uint8_t)
HID_Handle->length);
        if(status == USBH_OK)
        {        
          fifo_write(&HID_Handle->fifo, HID_Handle->pData, HID_Handle->length);  
          HID_Handle->state = HID_SYNC;
        }else if (status == USBH_BUSY)
        {
          HID_Handle->state = HID_IDLE;
          status = USBH_OK;
        }else if (status == USBH_NOT_SUPPORTED)
        {
          HID_Handle->state = HID_SYNC;
          status = USBH_OK;
        }else
        {
          HID_Handle->state = HID_ERROR;
      status = USBH_FAIL;
        }
#if (USBH_USE_OS == 1)
    osMessagePut ( phost->os_event, USBH_URB_EVENT, 0);
#endif      
    break;
2,在usbh_ctlreq.c里面,USBH_CtlReq函数,修改case CMD_WAIT代码(552行)为:
case CMD_WAIT:
    status = USBH_HandleControl(phost);
    if (status == USBH_OK)
    {
      /* Commands successfully sent and Response Received  */      
      phost->RequestState = CMD_SEND;
      phost->Control.state =CTRL_IDLE;  
      status = USBH_OK;      
    }else if  (status == USBH_FAIL)
    {
      /* Failure Mode */
      phost->RequestState = CMD_SEND;
      status = USBH_FAIL;
    }else if(status == USBH_NOT_SUPPORTED)
        {
      phost->RequestState = CMD_SEND;
        }         
    break;
经过以上两处修改,可以显著提高USB鼠标的兼容性,基本上各种鼠标都可以正常识别和使用了。
2. main.c代码
下面是main.c的程序,具体如下:
USBH_HandleTypeDef hUSBHost;    /* USB Host处理结构体 */

/* 应用状态结构体类型定义 */
typedef enum
{
    APPLICATION_IDLE = 0,                /* 挂起状态 */
    APPLICATION_DISCONNECT,             /* 断开连接 */
    APPLICATION_START,                   /* 应用开始(连接上了) */
    APPLICATION_READY,                   /* 准备完成(可以执行相关应用代码了) */
    APPLICATION_RUNNING,                /* 运行中 */
} HID_ApplicationTypeDef;

extern HID_MOUSE_Info_TypeDef mouse_info;
HID_ApplicationTypeDef App_State = APPLICATION_IDLE;

uint8_t g_usb_first_plugin_flag = 0; /* USB第一次插入标志,如果为1,说明是第一次插入 */

/**
* @brief       USB信息显示
* @param       msg   : 信息类型
*   @arg               0, USB无连接
*   @arg               1, USB键盘
*   @arg               2, USB鼠标
*   @arg               3, 不支持的USB设备
* @retval      无
*/
void usbh_msg_show(uint8_t msgx)
{
    switch (msgx)
    {
        case 0: /* USB无连接 */
            lcd_show_string(30, 130, 200, 16, 16, "USB Connecting...", RED);
            lcd_fill(0, 150, lcddev.width, lcddev.height, WHITE);
            break;

        case 1: /* USB键盘 */
            lcd_show_string(30, 130, 200, 16, 16, "USB Connected    ", RED);
            lcd_show_string(30, 150, 200, 16, 16, "USB KeyBoard", RED);
            lcd_show_string(30, 180, 210, 16, 16, "KEYVAL:", RED);
            lcd_show_string(30, 200, 210, 16, 16, "INPUT STRING:", RED);
            break;

        case 2: /* USB鼠标 */
            lcd_show_string(30, 130, 200, 16, 16, "USB Connected    ", RED);
            lcd_show_string(30, 150, 200, 16, 16, "USB Mouse", RED);
            lcd_show_string(30, 180, 210, 16, 16, "BUTTON:", RED);
            lcd_show_string(30, 200, 210, 16, 16, "X POS:", RED);
            lcd_show_string(30, 220, 210, 16, 16, "Y POS:", RED);
            lcd_show_string(30, 240, 210, 16, 16, "Z POS:", RED);
            break;

        case 3: /* 不支持的USB设备 */
            lcd_show_string(30, 130, 200, 16, 16, "USB Connected    ", RED);
            lcd_show_string(30, 150, 200, 16, 16, "Unknow Device", RED);
            break;
    }
}

/**
* @brief       完成USBH不同用户状态下的数据处理
* @param       phost : USBH句柄指针
* @param       id    : USBH当前的用户状态
* @retval      无
*/
void USBH_UserProcess(USBH_HandleTypeDef *phost, uint8_t id)
{
    uint8_t dev_type = 0XFF;    /* 设备类型,1,键盘;2,鼠标;其他,不支持的设备 */
    switch (id)
    {
        case HOST_USER_SELECT_CONFIGURATION:
            break;

        case HOST_USER_DISCONNECTION:
            usbh_msg_show(0);   /* 显示已经断开连接,准备重新连接 */
            App_State = APPLICATION_DISCONNECT;
            break;

        case HOST_USER_CLASS_ACTIVE:
            App_State = APPLICATION_READY;
            dev_type = phost->device.CfgDesc.Itf_Desc[phost->
device.current_interface].bInterfaceProtocol;
            if (dev_type == HID_KEYBRD_BOOT_CODE)                  /* 键盘设备 */
            {
                g_usb_first_plugin_flag = 1;                     /* 标记第一次插入 */
                usbh_msg_show(1);                                  /* 显示键盘界面 */
            }
            else if (dev_type == HID_MOUSE_BOOT_CODE)         /* 鼠标设备 */
            {
                g_usb_first_plugin_flag = 1;                     /* 标记第一次插入 */
                usbh_msg_show(2);                                  /* 显示鼠标界面 */
            }
            else
            {
                usbh_msg_show(3);   /* 显示不支持 */
            }
            break;

        case HOST_USER_CONNECTION:
            App_State = APPLICATION_START;
            break;

        default:
            break;
    }
}

/* 临时数组,用于存放鼠标坐标/键盘输入内容(4.3屏,最大可以输入2016字节) */
__align(4) uint8_t g_temp_buffer[2017];

/**
* @brief       USB鼠标数据处理
* @param       data  : USB鼠标数据结构体指针
* @retval      无
*/
void mouse_data_process(HID_MOUSE_Info_TypeDef *data)
{
    static signed short x, y, z;
    if (g_usb_first_plugin_flag)    /* 第一次插入,将数据清零 */
    {
        g_usb_first_plugin_flag = 0;
        x = y = z = 0;
    }
    x += (signed char)data->x;
    if (x > 9999)x = 9999;
    if (x < -9999)x = -9999;
    y += (signed char)data->y;
    if (y > 9999)y = 9999;
    if (y < -9999)y = -9999;
    z += (signed char)data->z;
    if (z > 9999)z = 9999;
    if (z < -9999)z = -9999;
    sprintf((char *)g_temp_buffer, "BUTTON:");
    if (data->button & 0X01)strcat((char *)g_temp_buffer, "LEFT ");
    if ((data->button & 0X03) == 0X02)
    {
        strcat((char *)g_temp_buffer, "RIGHT");
    }
    else
    {
        if ((data->button & 0X03) == 0X03)strcat((char *)g_temp_buffer,"+RIGHT");
    }
    if ((data->button & 0X07) == 0X04)
    {
        strcat((char *)g_temp_buffer, "MID  ");
    }
    else
    {
        if ((data->button & 0X07) > 0X04)strcat((char *)g_temp_buffer, "+MID");
    }
   
    lcd_fill(30 + 56, 180, lcddev.width - 1, 180 + 16, WHITE);
    lcd_show_string(30, 180, 210, 16, 16, (char *)g_temp_buffer, BLUE);
    sprintf((char *)g_temp_buffer, "X POS:%05d", x);
    lcd_show_string(30, 200, 200, 16, 16, (char *)g_temp_buffer, BLUE);
    sprintf((char *)g_temp_buffer, "Y POS:%05d", y);
    lcd_show_string(30, 220, 200, 16, 16, (char *)g_temp_buffer, BLUE);
    sprintf((char *)g_temp_buffer, "Z POS:%05d", z);
    lcd_show_string(30, 240, 200, 16, 16, (char *)g_temp_buffer, BLUE);
//printf("btn,X,Y,Z:0x%x,%d,%d,%d\r\n",data->button,(signed char)
data->x,(signed char)data->y,(signed char)data->z);
}

/**
* @brief       USB键盘数据处理
* @param       data  : USB键盘键值
* @retval      无
*/
void  keybrd_data_process(uint8_t data)
{
    static uint16_t pos;
    static uint16_t endx, endy;
    static uint16_t maxinputchar;
    uint8_t buf[4];

    if (g_usb_first_plugin_flag)    /* 第一次插入,将数据清零 */
    {
        g_usb_first_plugin_flag = 0;
        endx = ((lcddev.width - 30) / 8) * 8 + 30;      /* 得到endx值 */
        endy = ((lcddev.height - 220) / 16) * 16 + 220;/* 得到endy值 */
        maxinputchar = ((lcddev.width - 30) / 8);
        maxinputchar *= (lcddev.height - 220) / 16;/* 当前LCD最大可以显示的字符数 */
        pos = 0;
    }

    sprintf((char *)buf, "%02X", data);
    lcd_show_string(30 + 56, 180, 200, 16, 16, (char *)buf, BLUE);  /* 显示键值 */

    if (data >= ' ' && data <= '~')
    {
        g_temp_buffer[pos++] = data;
        g_temp_buffer[pos] = 0; /* 添加结束符 */
        if (pos > maxinputchar)pos = maxinputchar;  /* 最大输入这么多 */
    }
    else if (data == 0X0D)      /* 退格键 */
    {
        if (pos)pos--;
        g_temp_buffer[pos] = 0; /* 添加结束符 */
    }

    if (pos <= maxinputchar)    /* 没有超过显示区 */
    {
        lcd_fill(30, 220, endx, endy, WHITE);
        lcd_show_string(30, 220, endx - 30, endy - 220, 16,
(char *)g_temp_buffer, BLUE);
    }
    //printf("KEY Board Value:%02X\r\n",data);
    //printf("KEY Board Char:%c\r\n",data);
}

/**
* @brief       USB键盘/鼠标演示demo数据处理
* @param       phost : USBH句柄指针
* @retval      无
*/
void usb_demo(USBH_HandleTypeDef *phost)
{
    char c;
    HID_KEYBD_Info_TypeDef *k_pinfo;
    HID_MOUSE_Info_TypeDef *m_pinfo;
    if (App_State == APPLICATION_READY)
    {
        if (USBH_HID_GetDeviceType(phost) == HID_KEYBOARD)  /* 键盘设备 */
        {
            k_pinfo = USBH_HID_GetKeybdInfo(phost);           /* 获取键盘信息 */
            if (k_pinfo != NULL)
            {
                c = USBH_HID_GetASCIICode(k_pinfo);               /* 转换成ASCII码 */
                keybrd_data_process(c);                           /* 在LCD上显示出键盘字符 */
            }
        }
        else if (USBH_HID_GetDeviceType(phost) == HID_MOUSE)/* 鼠标设备 */
        {
            m_pinfo = USBH_HID_GetMouseInfo(phost);           /* 获取鼠标信息 */
            if (m_pinfo != NULL)
            {
                mouse_data_process(&mouse_info);                 /* LCD上显示鼠标信息 */
            }
        }
    }
}

int main(void)
{
    sys_cache_enable();                           /* 打开L1-Cache */
    HAL_Init();                                     /* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4);        /* 设置时钟, 480Mhz */
    delay_init(480);                              /* 延时初始化 */
    usart_init(115200);                           /* 串口初始化为115200 */
    mpu_memory_protection();                     /* 保护相关存储区域 */
    led_init();                                     /* 初始化LED */
    lcd_init();                                     /* 初始化LCD */
    key_init();                                     /* 初始化按键 */
    my_mem_init(SRAMIN);                          /* 初始化内部内存池(AXI) */
    my_mem_init(SRAM12);                          /* 初始化SRAM12内存池(SRAM1+SRAM2) */
    my_mem_init(SRAM4);                           /* 初始化SRAM4内存池(SRAM4) */
    my_mem_init(SRAMDTCM);                       /* 初始化DTCM内存池(DTCM) */
    my_mem_init(SRAMITCM);                       /* 初始化ITCM内存池(ITCM) */

    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "USB MOUSE/KEYBOARD TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "USB Connecting...", RED);

    USBH_Init(&hUSBHost, USBH_UserProcess, 0);
    USBH_RegisterClass(&hUSBHost, USBH_HID_CLASS);
    USBH_Start(&hUSBHost);
    while (1)
    {
        USBH_Process(&hUSBHost);
        usb_demo(&hUSBHost);
    }
}
这里总共六个函数:
usbh_msg_show函数,用于显示一些提示信息,在USBH_UserProcess函数里面被调用。
USBH_UserProcess函数,用于执行USB主机不同用户状态下的数据处理,在usbh_core.c等相关文件里面被调用,该函数仅针对两个状态进行处理:
1、HOST_USER_DISCONNECTION,表示连接断开了,我们在屏幕显示连接断开,并设置App_State状态为APPLICATION_DISCONNECT;
2、HOST_USER_CLASS_ACTIVE,表示USB主机类激活,即接入USB设备已经准备就绪,可以正常工作了,我们设置App_State状态为:APPLICATION_READY,并根据接入设备的不同(鼠标/键盘)显示不同的信息。
mouse_data_process函数,用于处理USB鼠标数据,在液晶屏上面显示鼠标的移动坐标值和按键状态等信息,该函数在USB_Demo函数里面被调用。
keybrd_data_process函数,用于处理键盘数据,在液晶屏上面显示键盘的输入内容,最多支持2017个字符的输入显示。该函数在USB_Demo函数里面被调用。
usb_demo函数,根据当前用户状态和USB接入设备的类型,执行不同的数据处理。该函数在main函数里面被调用。
main函数,初始化各个外设后,通过USBH_Init等3个函数初始化并启动USB主机通信,然后进入死循环,不停的调用USBH_Process和usb_demo,执行各种USB处理。
60.4 下载验证
将程序下载到开发板后,然后在USB_HOST端子插入USB鼠标/键盘,注意:此时USB SLAVE口不要插USB线到电脑,否则会干扰!
等USB鼠标/键盘成功识别后,便可以看到LCD显示USB Connected,并显示设备类型:USB Mouse或者USB KeyBoard,同时也会显示输入的数据,如图60.4.1和图60.4.2所示:
第六十章 USB鼠标键盘15317.png
图60.4.1 USB鼠标测试

第六十章 USB鼠标键盘15336.png
图60.4.2 USB键盘测试

        其中,图60.4.1是USB鼠标测试界面,图60.4.2是USB键盘测试界面。
        最后,特别提醒大家,由于例程的HID内核,只处理了第一个接口描述符,所以对于USB符合设备,只能识别第一个描述符所代表的设备。体现到实际使用中,就是:USB无线鼠标,一般是无法使用(被识别为键盘),而USB无线键盘,可以使用,因为键盘在第一个描述符,鼠标在第二个描述符  。

阿莫论坛20周年了!感谢大家的支持与爱护!!

阿莫论坛才是最爱国的,关心国家的经济、社会的发展、担心国家被别国牵连卷入战争、知道珍惜来之不易的和平发展,知道师夷之长,关注世界的先进文化与技术,也探讨中国文化的博大精深,也懂得警惕民粹主义的祸国殃民等等等等,无不是爱国忧民的表现。(坛友:tianxian)
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-17 05:03

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表