RT-Thread Studio configura QSPI y SFUD

1. Introducción

Esta vez, se usa la placa de la serie STM32F767 de Punctual Atom, que es diferente de la SPI en F1 y F4, y se agrega QSPI a F7. El Flash de tipo W25Q256FV a bordo también realiza comunicación de lectura y escritura a través de QSPI. Hay muchos ejemplos en Internet sobre RT-Thread abriendo SPI y usando SFUD para lectura y escritura FLASH, pero la mayoría de ellos se basan en ENV, porque esta vez utilizo el recién lanzado Studio de RT-Thread, y estoy configurando QSPI y el proceso SFUD China también pisó muchos pozos para registrar el proceso de aprendizaje y configuración.

Materiales de referencia:
Sitio web original de SFUD Github
Centro de documentación RT-Thread
IOT-OS RT-Thread (9) -SPI Device Management Object and SFUD Management Framework
Notas de estudio de Rtthread (9) RT-Thread Studio abre el bus SPI1, flash externo (W25Q64 ) Como dispositivo esclavo
SFUD | Una biblioteca de controladores universales Flash en serie

2 descripción general

2.1 SPI y QSPI

SPI (interfaz periférica en serie, interfaz periférica en serie) es un bus de comunicación síncrono, dúplex completo y de alta velocidad, que se utiliza a menudo para comunicaciones de corta distancia, generalmente con 4 cables para la comunicación
Inserte la descripción de la imagen aquí

  • Salida maestra MOSI / línea de datos de entrada esclava (Salida maestra de bus SPI / Entrada esclava).
  • Línea de datos de entrada maestra MISO / salida esclava (entrada maestra de bus SPI / salida esclava).
  • Línea de reloj en serie SCLK (reloj en serie), el dispositivo maestro envía la señal de reloj al dispositivo esclavo.
  • Línea de selección CS-Slave (selección de chip). También llamado SS, CSB, CSN, EN, etc., el dispositivo maestro envía señales de selección de chip al dispositivo esclavo

El modo de trabajo específico es el siguiente:
el reloj del dispositivo esclavo lo proporciona el dispositivo maestro a través de SCLK, y MOSI y MISO completan la transmisión de datos en función de este pulso. El modo de secuencia de trabajo de SPI está determinado por la relación de fase entre CPOL (Polaridad de reloj) y CPHA (Fase de reloj). CPOL determina el estado de inactividad del reloj, CPOL = 0 significa que el estado inactivo es de nivel bajo y CPOL = 1 significa que el estado de inactividad es de nivel alto. CPHA significa muestreo en el primer borde, CPHA = 0 significa muestreo en el primer borde de cambio de reloj, correspondiente a la línea roja en la figura siguiente, CPHA = 1 significa muestreo en el segundo borde de cambio de reloj, correspondiente a la figura siguiente La línea azul en.
Inserte la descripción de la imagen aquí
QSPI es la abreviatura de Queued SPI, una extensión de la interfaz SPI lanzada por Motorola, y se usa más ampliamente que SPI. Sobre la base del protocolo SPI, Motorola ha mejorado sus funciones, ha aumentado el mecanismo de transmisión de la cola e introducido el protocolo de interfaz periférico serie de cola (es decir, el protocolo QSPI). QSPI es una interfaz de comunicación dedicada que conecta medios de almacenamiento SPI Flash simples, duales o cuádruples (líneas de datos).

La interfaz puede funcionar en los siguientes tres modos:

① Modo indirecto: utilice el registro QSPI para realizar todas las operaciones

② Modo de sondeo de estado: lea periódicamente el registro de estado de Flash externo, y se generará una interrupción cuando la bandera se establezca en 1 (como borrar o completar la programación, se generará una interrupción)

③ Modo de mapeo de memoria: el flash externo se asigna al espacio de direcciones del microcontrolador, por lo que el sistema lo considera como memoria interna

Cuando se adopta el modo de flash dual, se accederá a dos flashes Quad-SPI al mismo tiempo, y el rendimiento y la capacidad se pueden duplicar.

Puede entenderse simplemente como una versión avanzada de SPI.

2.2 SFUD

SFUD es una biblioteca de controladores universales SPI Flash en serie de código abierto. Dado que existen la mayoría de tipos de Flash en serie en el mercado, y las especificaciones y comandos de cada Flash son diferentes, SFUD está diseñado para resolver estas diferencias en Flash, de modo que nuestros productos puedan admitir Flash de diferentes marcas y especificaciones, y mejorar el Flash. La reutilización y la extensibilidad del software funcional también pueden evitar el riesgo de que Flash se agote o la discontinuación del producto.

  • Características principales: admite interfaz SPI / QSPI, orientada a objetos (admite múltiples objetos Flash al mismo tiempo), adaptación flexible, escalabilidad sólida, compatibilidad con direcciones de 4 bytes
  • Ocupación de recursos
    • Ocupación estándar: RAM: 0.2KB ROM: 5.5KB
    • Ocupación mínima: RAM: 0.1KB ROM: 3.6KB

Dirección de la biblioteca de código abierto: https://github.com/armink/SFUD

2.3 W25Q256FV

Los siguientes extractos se seleccionan del manual de la biblioteca de Punctual Atom Hal
Inserte la descripción de la imagen aquí

3 proceso de trasplante

Lea atentamente los documentos de ayuda proporcionados por el código fuente de SFUD y el manual oficial de RT-Thread antes de trasplantar.

3.1 Abrir componentes relacionados

Primero, ingrese al centro de paquetes de software en RT-Thread Settings en la carpeta del proyecto seleccionado , como se muestra en la figura siguiente, seleccione y habilite las dos funciones de SPI y SFUD,
Inserte la descripción de la imagen aquí
y luego seleccione más configuraciones en la esquina inferior derecha , o el símbolo de la flecha izquierda en el extremo derecho de la figura , Ingrese a la interfaz, el proceso de configuración es el siguiente, una vez completada la configuración, como se muestra en la figura,
componente → controlador de dispositivo → use el bus SPI / controlador de dispositivo (verifique)use el modo QSPI (verifique)use el controlador universal Flash en serie ( SFUD) (marcar)marque las primeras tres opciones
Inserte la descripción de la imagen aquí
, guarde después de abrir para actualizar y salga de esta interfaz después de que se complete la actualización.

Si está configurado a través de ENV, debe descargar el programa de origen desde la URL de origen y copiar los archivos dentro (los archivos de origen del núcleo incluyen: sfud.c, sfud_sfdp.c, sfud_port.c), pero RT-Thread Studio completará automáticamente la configuración por usted , Este es también el poder de este editor, pero si no está familiarizado con este software y la configuración del código fuente relacionado, se sentirá nublado cuando comience por primera vez. Una vez completada la configuración, SFUD se encuentra en la ruta "rt-thread → componentes → controladores → spi". Principalmente carpeta sfud y archivos spi_flash_sfud.c, spi_flash_sfud.h.
Inserte la descripción de la imagen aquí
Ignoremos SFUD y configuremos QSPI primero.

3.2 Configuración QSPI

Abra el archivo board.h (debajo de la ruta de los controladores) y busque la información de configuración de QSPI de la siguiente manera:
Inserte la descripción de la imagen aquí
Primero, agregue el archivo board.h: #define BSP_USING_QSPI (configure justo debajo del texto en la figura anterior).

Luego abra CubeMx para la configuración relevante para generar código. Determine los pines que deben encenderse de la siguiente manera:
Inserte la descripción de la imagen aquí
Tenga en cuenta que la serie W25Q256 es un chip Flash en serie de 32M bytes, 32M = 32 1024 1024 = 2 ^ 25, por lo que el tamaño de Flash de configuración debe ser 25-1 = 24. La configuración es la siguiente:
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
De acuerdo con el comunicado oficial, el programa generado debe colocarse en board.c, y luego la función de inicialización correspondiente debe colocarse en el archivo principal. Pero esto hará que el programa sea un poco hinchado y complicado e incómodo de verificar, por lo que agregamos dos nuevas carpetas inc y src debajo del archivo de la aplicación, creamos un nuevo archivo de encabezado qspi.h en inc, y creamos un nuevo archivo fuente qspi.c en src para almacenar El código correspondiente. En el proceso de programación posterior, inc y src también se pueden usar para almacenar otros archivos de encabezado y archivos de origen (recuerde incluir estas dos rutas en el espacio de trabajo para encontrar los archivos correspondientes y compilar correctamente). Los procedimientos internos son los siguientes:

/* qspi.h 文件内容*/
#ifndef APPLICATIONS_INC_QSPI_H_
#define APPLICATIONS_INC_QSPI_H_

#include <board.h>

void HAL_QSPI_MspInit(QSPI_HandleTypeDef* hqspi);
void MX_QUADSPI_Init(void);


#endif /* APPLICATIONS_INC_QSPI_H_ */
/* qspi.c 文件内容*/
#include "qspi.h"

QSPI_HandleTypeDef hqspi;

/**
* @brief QSPI MSP Initialization
* This function configures the hardware resources used in this example
* @param hqspi: QSPI handle pointer
* @retval None
*/
void HAL_QSPI_MspInit(QSPI_HandleTypeDef* hqspi)
{
    
    
  GPIO_InitTypeDef GPIO_InitStruct = {
    
    0};
  if(hqspi->Instance==QUADSPI)
  {
    
    
  /* USER CODE BEGIN QUADSPI_MspInit 0 */

  /* USER CODE END QUADSPI_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_QSPI_CLK_ENABLE();

    __HAL_RCC_GPIOF_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**QUADSPI GPIO Configuration
    PF6     ------> QUADSPI_BK1_IO3
    PF7     ------> QUADSPI_BK1_IO2
    PF8     ------> QUADSPI_BK1_IO0
    PF9     ------> QUADSPI_BK1_IO1
    PB2     ------> QUADSPI_CLK
    PB6     ------> QUADSPI_BK1_NCS
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /* USER CODE BEGIN QUADSPI_MspInit 1 */

  /* USER CODE END QUADSPI_MspInit 1 */
  }

}

/**
  * @brief QUADSPI Initialization Function
  * @param None
  * @retval None
  */
void MX_QUADSPI_Init(void)
{
    
    

  /* USER CODE BEGIN QUADSPI_Init 0 */

  /* USER CODE END QUADSPI_Init 0 */

  /* USER CODE BEGIN QUADSPI_Init 1 */

  /* USER CODE END QUADSPI_Init 1 */
  /* QUADSPI parameter configuration*/
  hqspi.Instance = QUADSPI;
  hqspi.Init.ClockPrescaler = 2;
  hqspi.Init.FifoThreshold = 4;
  hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
  hqspi.Init.FlashSize = 24;
  hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_4_CYCLE;
  hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
  hqspi.Init.FlashID = QSPI_FLASH_ID_1;
  hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
  if (HAL_QSPI_Init(&hqspi) != HAL_OK)
  {
    
    
    Error_Handler();
  }
  /* USER CODE BEGIN QUADSPI_Init 2 */

  /* USER CODE END QUADSPI_Init 2 */

}

En consecuencia, dado que no hay una definición de función en el archivo board.c, necesitamos declarar la función relevante en board.h para realizar la llamada al programa completo normalmente. La configuración es la siguiente:
Inserte la descripción de la imagen aquí
En este momento, el terminal se habilita después de que el programa se compila y descarga en la MCU Y llame al comando list_device para ver que el bus qspi interno se ha encendido
Inserte la descripción de la imagen aquí

3.3 configuración SFUD

Por supuesto, no basta con encender el bus, tenemos que montar el dispositivo en el bus para utilizarlo con normalidad.

En circunstancias normales, después de las operaciones anteriores, nuestra configuración interna en el archivo rtconfig.h debería ser la siguiente (porque he habilitado otras funciones aquí, por lo que puede ser diferente, pero aproximadamente demasiada configuración de QSPI y SFUD debería ser similar de)

/* Device Drivers */

#define RT_USING_DEVICE_IPC
#define RT_PIPE_BUFSZ 512
#define RT_USING_SERIAL
#define RT_SERIAL_RB_BUFSZ 64
#define RT_USING_HWTIMER
#define RT_USING_PIN
#define RT_USING_PWM
#define RT_USING_SPI
#define RT_USING_QSPI
#define RT_USING_SFUD
#define RT_SFUD_USING_SFDP
#define RT_SFUD_USING_FLASH_INFO_TABLE
#define RT_SFUD_USING_QSPI
#define RT_SFUD_SPI_MAX_HZ 50000000

Permítanme hablar brevemente sobre esto aquí. Si se trasplanta dentro del Studio, encontrará que no hay un archivo sfud_port.c. La función relacionada RT-Thread se ha integrado para usted e implementado en spi_flash_sfud.c, por lo que no se requiere configuración. Incluso las funciones relacionadas con la inicialización de QSPI se han implementado para usted en el archivo drv_qspi.c.

Con respecto al proceso de implementación interna más detallado y la portabilidad de SFUD, lea detenidamente los documentos oficiales en Github, donde se describen muchos detalles.

Tenga en cuenta que la información del chip relacionada se ha definido en sfud_flash_def.h, principalmente de la siguiente manera:

#ifdef SFUD_USING_FLASH_INFO_TABLE
/* SFUD supported flash chip information table. If the flash not support JEDEC JESD216 standard,
 * then the SFUD will find the flash chip information by this table. You can add other flash to here then
 *  notice me for update it. The configuration information name and index reference the sfud_flash_chip structure.
 * | name | mf_id | type_id | capacity_id | capacity | write_mode | erase_gran | erase_gran_cmd |
 */
#define SFUD_FLASH_CHIP_TABLE                                                                                       \
{                                                                                                                   \
    {"AT45DB161E", SFUD_MF_ID_ATMEL, 0x26, 0x00, 2L*1024L*1024L, SFUD_WM_BYTE|SFUD_WM_DUAL_BUFFER, 512, 0x81},      \
    {"W25Q40BV", SFUD_MF_ID_WINBOND, 0x40, 0x13, 512L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                        \
    {"W25Q16BV", SFUD_MF_ID_WINBOND, 0x40, 0x15, 2L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                    \
    {"W25Q64DW", SFUD_MF_ID_WINBOND, 0x60, 0x17, 8L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                    \
    {"W25Q128BV", SFUD_MF_ID_WINBOND, 0x40, 0x18, 16L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                  \
    {"W25Q256FV", SFUD_MF_ID_WINBOND, 0x40, 0x19, 32L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                  \
    {"SST25VF016B", SFUD_MF_ID_SST, 0x25, 0x41, 2L*1024L*1024L, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20},              \
    {"M25P32", SFUD_MF_ID_MICRON, 0x20, 0x16, 4L*1024L*1024L, SFUD_WM_PAGE_256B, 64L*1024L, 0xD8},                  \
    {"M25P80", SFUD_MF_ID_MICRON, 0x20, 0x14, 1L*1024L*1024L, SFUD_WM_PAGE_256B, 64L*1024L, 0xD8},                  \
    {"M25P40", SFUD_MF_ID_MICRON, 0x20, 0x13, 512L*1024L, SFUD_WM_PAGE_256B, 64L*1024L, 0xD8},                      \
    {"EN25Q32B", SFUD_MF_ID_EON, 0x30, 0x16, 4L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                        \
    {"GD25Q64B", SFUD_MF_ID_GIGADEVICE, 0x40, 0x17, 8L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                 \
    {"GD25Q16B", SFUD_MF_ID_GIGADEVICE, 0x40, 0x15, 2L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                 \
    {"S25FL216K", SFUD_MF_ID_CYPRESS, 0x40, 0x15, 2L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                   \
    {"S25FL032P", SFUD_MF_ID_CYPRESS, 0x02, 0x15, 4L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                   \
    {"A25L080", SFUD_MF_ID_AMIC, 0x30, 0x14, 1L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                        \
    {"F25L004", SFUD_MF_ID_ESMT, 0x20, 0x13, 512L*1024L, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20},                     \
    {"PCT25VF016B", SFUD_MF_ID_SST, 0x25, 0x41, 2L*1024L*1024L, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20},              \
}
#endif /* SFUD_USING_FLASH_INFO_TABLE */

#ifdef SFUD_USING_QSPI
/* This table saves flash read-fast instructions in QSPI mode, 
 * SFUD can use this table to select the most appropriate read instruction for flash.
 * | mf_id | type_id | capacity_id | qspi_read_mode |
 */
#define SFUD_FLASH_EXT_INFO_TABLE                                                                  \
{                                                                                                  \
    /* W25Q40BV */                                                                                 \
    {
    
    SFUD_MF_ID_WINBOND, 0x40, 0x13, NORMAL_SPI_READ|DUAL_OUTPUT},                                 \
    /* W25Q80JV */                                                                                 \
    {
    
    SFUD_MF_ID_WINBOND, 0x40, 0x14, NORMAL_SPI_READ|DUAL_OUTPUT},                                 \
    /* W25Q16BV */                                                                                 \
    {
    
    SFUD_MF_ID_WINBOND, 0x40, 0x15, NORMAL_SPI_READ|DUAL_OUTPUT},                                 \
    /* W25Q32BV */                                                                                 \
    {
    
    SFUD_MF_ID_WINBOND, 0x40, 0x16, NORMAL_SPI_READ|DUAL_OUTPUT},                                 \
    /* W25Q64JV */                                                                                 \
    {
    
    SFUD_MF_ID_WINBOND, 0x40, 0x17, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO|QUAD_OUTPUT|QUAD_IO},     \
    /* W25Q128JV */                                                                                \
    {
    
    SFUD_MF_ID_WINBOND, 0x40, 0x18, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO|QUAD_OUTPUT|QUAD_IO},     \
    /* W25Q256FV */                                                                                \
    {
    
    SFUD_MF_ID_WINBOND, 0x40, 0x19, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO|QUAD_OUTPUT|QUAD_IO},     \
    /* EN25Q32B */                                                                                 \
    {
    
    SFUD_MF_ID_EON, 0x30, 0x16, NORMAL_SPI_READ|DUAL_OUTPUT|QUAD_IO},                             \
    /* S25FL216K */                                                                                \
    {
    
    SFUD_MF_ID_CYPRESS, 0x40, 0x15, NORMAL_SPI_READ|DUAL_OUTPUT},                                 \
    /* A25L080 */                                                                                  \
    {
    
    SFUD_MF_ID_AMIC, 0x30, 0x14, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO},                            \
    /* A25LQ64 */                                                                                  \
    {
    
    SFUD_MF_ID_AMIC, 0x40, 0x17, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO|QUAD_IO},                    \
    /* MX25L3206E and KH25L3206E */                                                                \
    {
    
    SFUD_MF_ID_MICRONIX, 0x20, 0x16, NORMAL_SPI_READ|DUAL_OUTPUT},                                \
    /* GD25Q64B */                                                                                 \
    {
    
    SFUD_MF_ID_GIGADEVICE, 0x40, 0x17, NORMAL_SPI_READ|DUAL_OUTPUT},                              \
}
#endif /* SFUD_USING_QSPI */

Mire cuidadosamente para ver si hay algún chip que esté usando adentro. Básicamente, la mayoría de los chips deben estar adentro.

También puede configurar la tabla de dispositivos usted mismo. Modifique la definición de macro de SFUD_FLASH_DEVICE_TABLE en sfud_cfg.h

enum {
    
    
    SFUD_W25Q256FV_DEVICE_INDEX = 0,
};

#define SFUD_FLASH_DEVICE_TABLE                                                 \
{                                                                               \
    [SFUD_W25Q256FV_DEVICE_INDEX] = {.name = "W25Q256FV", .spi.name = "qspi1"}  \
}

Luego agregamos el controlador del dispositivo esclavo. En inc y src debajo de la ruta de la aplicación, agregamos los archivos flash.hy flash.c respectivamente. Los procedimientos internos son los siguientes:

#include "board.h"
#include "drv_qspi.h"
#include "rtdevice.h"

#define QSPI_BUS_NAME       "qspi1"
#define QSPI_DEVICE_NAME    "qspi10"
#define W25Q_FLASH_NAME     "W25Q256FV"

#define QSPI_CS_PIN         GET_PIN(B, 6)	//此处注意引脚区分,用你自己的

static int rt_hw_qspi_flash_with_sfud_init(void)
{
    
    
    if (stm32_qspi_bus_attach_device(QSPI_BUS_NAME, QSPI_DEVICE_NAME, (rt_uint32_t)QSPI_CS_PIN, 4, RT_NULL, RT_NULL) != RT_EOK)
        return -RT_ERROR;

#ifdef  RT_USING_SFUD
    if (rt_sfud_flash_probe(W25Q_FLASH_NAME, QSPI_DEVICE_NAME) == RT_NULL)
        return -RT_ERROR;
#endif

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_qspi_flash_with_sfud_init);

Después de compilar y descargar, descubrimos que el chip 32M se leyó correctamente. Escribimos list_device en msh y obtuvimos el resultado que se muestra en la siguiente figura (ignore pwm1 y pwm8, que son mis otros controladores).
Inserte la descripción de la imagen aquí
Use el comando sf para ver el dispositivo y
Inserte la descripción de la imagen aquí
luego trasplantarlo ¡Éxito! Para obtener comandos más detallados, consulte el documento oficial de RT

Señale cualquier error a tiempo

Supongo que te gusta

Origin blog.csdn.net/moumde/article/details/108002454
Recomendado
Clasificación