硬汉嵌入式论坛

 找回密码
 立即注册
查看: 7479|回复: 11
收起左侧

[SPI/QSPI] 基于8线DUAL QSPI的BOOT开发总结

  [复制链接]

27

主题

95

回帖

181

积分

初级会员

积分
181
发表于 2020-3-12 22:42:40 | 显示全部楼层 |阅读模式
最近项目需要,搞双QSPI FLASH操作,包括BOOT和应用程序都使用双FLASH。网上的资料比较少,只得自己研究,现做个总结,希望对朋友们有用。

程序:BOOT
功能: DUAL QSPI操作,U盘启动升级,带LCD显示(升级进度)
升级:两部分。1:内部FLASH为主程序,2:QSPI FLASH存的是LCD显示资源


背景
硬件: 底板自制,核心板H743XI(隔壁XX火的进单位已成型,不然真不推荐),屏幕800*480(自购)
软件:根据需求,采用硬汉哥3个例程的代码,分别是:
V7-029_QSPI读写例程(四线DMA方式,读每秒48MB)
V7-026_FatFS文件系统例子(外挂U盘)
V7-049_内部Flash模拟EEPROM
屏幕驱动参考XX火的代码。

开发思路:代码不可能从0开始写,不然要吐血。因此考虑选取硬汉哥的一个例程作为基础,然后添加其他的驱动进去,这样最省事。目测
选取V7-026 这个最合适,因为U盘和FATFS都已具备,代码几乎不用修改直接就可以用。(这里要吐槽XX火的代码,很多代码感觉没怎么测试就放出来了,完全经不起折腾,代码里有些很明显的BUG,代码组织上也是有些山寨的感觉,出了问题基本只能自已摸索,因为技术支持=0,这也是前面说的不推荐的原因---个人看法不代表单位意见)。


开始动手了。。

U盘程序先下进去,啥也不用改,不得不说,硬汉哥的程序就是稳,直接启动,按照DEMO走一遍,功能都OK。

接下来就是重点搞双QSPI了,这个要单独测,基于V7-029_QSPI读写例程。网上关于H7 双QSPI操作的例子不多,最后还是在官方的软件包里的QSPI 例程
里找到两个关于双QSPI的代码,但官方的代码都比较简单,只能参考。
目录:STM32Cube_FW_H7_V1.7.0\Projects\STM32H743I-EVAL\Examples\QSPI
1.png


硬件设计框图:

2.png


如果对QSPI不是太熟,要先对H7手册上的QSPI模块部分要仔细看一看,寄存器不是太多,CCR寄存器里的东西要了解下,因为程序里发命令的参数
都是在配置这个寄存器。


3.png


这里有一段描述,感觉比较有价值,因为将直接影响后面的部分程序设计:

4.png


从上可知两片FLASH获取指令都是一样的,只是数据阶段分成两半写入或读取,其中FLASH BK1是偶地址数,BK2是奇地址数据。
之前有一个疑惑:单个FLASH时,一个扇区4K,写入4K数据,OK没问题,那双片FLASH时,一次多传输一半的数据,那这个4K数据是不是要变成8K或2k?
其实不动变,该怎么操作还怎么操作,这个是自动完成的。如下面这段码,单片或双片时都一样,只是在FLASH中占用的空间减小了一半,因为分成
两个来存了。

        for(i = 0; i< TEST_SIZE; i += QSPI_PAGE_SIZE)
        {
                if (QSPI_WriteBuffer(buf, TEST_ADDR + i, QSPI_PAGE_SIZE) == 0)
                {
                        printf("写串行Flash出错!\r\n");
                        return;
                }               
        }


一定要把手册或相关教程看一下,然后开始改代码了。。在改代码前一定要注意板载的是什么型号的FLASH,切记。像我板上挂的是W25Q256JVEQ,
事实上还有一个型号W25Q256FV,而这两款芯片的手册不完一样,有些命令不相同。我前面看了好一会,感觉怎么总是怪怪的,再一看娘的,型号不对。

没什么特别的捷径,从初始化函数看起:

void bsp_InitQSPI_W25Q256(void)
{
        /* 复位QSPI */
        QSPIHandle.Instance = QUADSPI;
        if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)
        {
                Error_Handler(__FILE__, __LINE__);
        }
       
        /* 设置时钟速度,QSPI clock = 200MHz / (ClockPrescaler+1) = 100MHz */
        QSPIHandle.Init.ClockPrescaler  = 1;  
       
        /* 设置FIFO阀值,范围1 - 32 */
        QSPIHandle.Init.FifoThreshold   = 32;
       
        /*
                QUADSPI在FLASH驱动信号后过半个CLK周期才对FLASH驱动的数据采样。
                在外部信号延迟时,这有利于推迟数据采样。DDR模式必须设置为0
        */
        QSPIHandle.Init.SampleShifting  = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
       
        /*Flash大小是2^(FlashSize + 1) = 2^25 = 32MB */
        QSPIHandle.Init.FlashSize       = QSPI_FLASH_SIZE - 1;  // QSPI_FLASH_SIZE =26
       
        /* 命令之间的CS片选至少保持1个时钟周期的高电平 */
        QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_3_CYCLE;
       
        /*
           MODE0: 表示片选信号空闲期间,CLK时钟信号是低电平
           MODE3: 表示片选信号空闲期间,CLK时钟信号是高电平
        */
        QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;
       
        /* QSPI有两个BANK,这里使用的BANK1 . 双QSPI无所谓*/
        QSPIHandle.Init.FlashID   = QSPI_FLASH_ID_2;
       
        /* V7开发板仅使用了BANK1,这里是禁止双BANK */
        //QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
        QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_ENABLE;

        /* 初始化配置QSPI 控制器*/
        if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)
        {
                Error_Handler(__FILE__, __LINE__);
        }       

        if(QSPI_ResetMemory() != HAL_OK)
        {
                Error_Handler(__FILE__, __LINE__);
        }
        HAL_Delay(50);

        QSPI_EnterFourBytesAddress(&QSPIHandle);//add by bwu
        HAL_Delay(10);
}


要知道这些配置是什么意思,一定要进到HAL_QSPI_Init函数里去看,这就要求我们对QSPI的寄存器比较了解,所以这也是为什么前面我说要看手册的原因
不然根本不明白这些配置是怎么来的,会产生什么影响。

接下来,再了解一下怎么发命令的,以复位函数为例:
/**
  * @brief  复位QSPI存储器。
  * @param  QSPIHandle: QSPI句柄
  * @retval 无
  */
static uint8_t QSPI_ResetMemory(void)
{
        QSPI_CommandTypeDef s_command={0};
        /* 初始化复位使能命令 */
        s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
        s_command.Instruction       = RESET_ENABLE_CMD;
        s_command.AddressMode       = QSPI_ADDRESS_NONE;
        s_command.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
        s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
        s_command.DataMode          = QSPI_DATA_NONE;
        s_command.DummyCycles       = 0;
        s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
        s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
        s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

        /* 发送命令 */
        if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
        {
                Error_Handler(__FILE__, __LINE__);
        }

        /* 发送复位存储器命令 ,需紧接66H指令,不然将disable reset*/
        s_command.Instruction = RESET_MEMORY_CMD;
        if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
        {
                Error_Handler(__FILE__, __LINE__);
        }

        /* 配置自动轮询模式等待存储器就绪 */  
        QSPI_AutoPollingMemReady(&QSPIHandle);
       
        return QSPI_OK;
}

这里就要求我们对这句话有一定了解:
QUADSPI 通过命令与 FLASH 通信 每条命令包括指令、地址、交替字节、空指令和数据这 五个阶段 任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。nCS 在每条指令开始前下降,在每条指令完成后再次上升。 ----- 手册原话

以及这些参数主要在配置的寄存器: 通信配置寄存器 (QUADSPI_CCR),

同时还要求对所用的FLASH要了解,明白发出的是什么命令,有什么作用,为什么要这么发。看下面红框里的说明 :


5.png


下面这个函数是查询FLASH 忙状态,


/*
*********************************************************************************************************
*        函 数 名: QSPI_AutoPollingMemReady
*        功能说明: 等待QSPI Flash就绪,主要用于Flash擦除和页编程时使用
*        形    参: hqspi  QSPI_HandleTypeDef句柄
*        返 回 值: 无
*********************************************************************************************************
*/
static void QSPI_AutoPollingMemReady(QSPI_HandleTypeDef *hqspi)
{
        QSPI_CommandTypeDef     sCommand = {0};
        QSPI_AutoPollingTypeDef sConfig = {0};

       
        /* 基本配置 */
        sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */
        sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
        sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
        sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
        sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */
        sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;         /* 每次传输都发指令 */
       
        /* 读取状态*/
        sCommand.Instruction       = READ_STATUS_REG_CMD; /* 读取状态命令 */
        sCommand.AddressMode       = QSPI_ADDRESS_NONE;   /* 无需地址 */
        sCommand.DataMode          = QSPI_DATA_1_LINE;    /* 1线数据 */
        sCommand.DummyCycles       = 0;                   /* 无需空周期 */

        /* 屏蔽位设置的bit0,匹配位等待bit0为0,即不断查询状态寄存器bit0,等待其为0 */
        sConfig.Mask            = 0x0101;
        sConfig.Match           = 0x00;
        sConfig.MatchMode       = QSPI_MATCH_MODE_AND;
        sConfig.StatusBytesSize = 2;//1;
        sConfig.Interval        = 0x10;
        sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;

        if (HAL_QSPI_AutoPolling_IT(&QSPIHandle, &sCommand, &sConfig) != HAL_OK)
        {
                Error_Handler(__FILE__, __LINE__);
        }
}

注意这两句代码:
sConfig.Mask            = 0x0101;
sConfig.Match           = 0x00;

Mask 表示要匹配返回的状态字的bit位(为“1”的bit位有效),Match表示要匹配的值(对应mask的为‘1’bit位)。
这两句就是表示匹配返回的状态字的bit0和bit3位要等于0,即状态字的bit0位=0,即BUSY状态。

MASK在单片FLASH时是一个字节,现在是双FLASH,每个FLASH返回一个字节,共两字节,这个要注意。

再来看看FLASH的状态寄存器。
W25Q256JV  FLASH有3个状态寄存器,每个8位,含义:

6.png 7.png 8.png


下面是写使能函数:

/*
*********************************************************************************************************
*        函 数 名: QSPI_WriteEnable
*        功能说明: 写使能
*        形    参: hqspi  QSPI_HandleTypeDef句柄。
*        返 回 值: 无
*********************************************************************************************************
*/
static void QSPI_WriteEnable(QSPI_HandleTypeDef *hqspi)
{
        QSPI_CommandTypeDef     sCommand = {0};
        QSPI_AutoPollingTypeDef s_config = {0};
       
        /* 基本配置 */
        sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */
        sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
        sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
        sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
        sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */
        sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;         /* 每次传输都发指令 */
       
        /* 写使能 */
        sCommand.Instruction       = WRITE_ENABLE_CMD;  /* 写使能指令 */
        sCommand.AddressMode       = QSPI_ADDRESS_NONE; /* 无需地址 */
        sCommand.DataMode          = QSPI_DATA_NONE;    /* 无需数据 */
        sCommand.DummyCycles       = 0;                 /* 空周期  */

        if (HAL_QSPI_Command(&QSPIHandle, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
        {
                Error_Handler(__FILE__, __LINE__);
        }

        /* Configure automatic polling mode to wait for write enabling */
        s_config.Match           = 0x0202;
        s_config.Mask            = 0x0202;
        s_config.MatchMode       = QSPI_MATCH_MODE_AND;
        s_config.StatusBytesSize = 2;
        s_config.Interval        = 0x10;
        s_config.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;

        sCommand.Instruction    = READ_STATUS_REG_CMD;
        sCommand.DataMode       = QSPI_DATA_1_LINE;

        if (HAL_QSPI_AutoPolling(hqspi, &sCommand, &s_config, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
        {
                Error_Handler(__FILE__, __LINE__);
        }

       
        /* 等待写使能完成 */
        /*StatusMatch = 0;
        QSPI_AutoPollingMemReady(&QSPIHandle);       
        while(StatusMatch == 0);
        StatusMatch = 0;*/       
}

s_config.Match           = 0x0202;
s_config.Mask            = 0x0202;

Write Enable Latch (WEL) is a read only bit in the status register (S1) that is set to 1 after executing a
Write Enable Instruction. The WEL status bit is cleared to 0 when the device is write disabled.

这两句代码检测状态寄存器1的WEL位,通过上在描述,在执行写使能指令时设置为1,当设备写禁止时此状态位清0.

双片FLASH写入数据时,偶地址的数据写入到FLASH1中,奇地址的数据写入到FLASH2中,用下面代码进行测试:
/* 填充测试缓冲区 */
        for (i = 0; i < 1024; i++)
        {
                if(i%2==0)
                        buf = 0x22;//写到FLASH1
                else
                        buf = 0x44;//写到FLASH2
        }
写入后,再读出来数据应该是22 44 22 44....这样的,切换到单片时,FLASH1 中读出来的全部是22 22 22 ...,

在使用过程中遇到一个问题,当时想验证一下写入的值是否正常,双片写入后,切换到单片模式,FLASH BK1 驱动正常中,读写正常。但FLASH BK2 单片怎么也
不好使,不知道是哪里没搞对还是咋的。感觉FLASH BK2的启用必须是两片同时启用时才好用。简单说就是要么单片BK1, 要么双片。


另外要说明的一点是,硬汉哥代码中给出的全片擦除函数是通过一个扇区一个扇区的擦除,64MB擦下来真得太久,而W25Q256JV是有全片擦除指令的(0xC7),
一条指令就可以全片擦除,时间大幅缩短,亲测好用。

了解上面的知识点,基本可以正常驱动DUAL QSPI FLASH了。


将修改测试好的QSPI FLASH 驱动文件移植到USB 的工程中去,再测一把,测试OK。

接下来,测试内部FLASH读写擦除。直接上硬汉的例程,测试没问题,移植到USB工程中去不行,写入一部分数据后就报错,折腾了半天没折,后来还是在硬汉的提醒
下修改正确:FLASH的HAL库中的驱动老版本有BUG,需要用新版本的(HAL 1.7 或者 1.8版本),修改方法:
1. 将stm32h7xx_hal_flash.c,stm32h7xx_hal_flash_ex.c stm32h7xx_hal_flash.h, stm32h7xx_hal_flash_ex.h 四个文件分别替换掉原来的文件。

9.png

2. 修改stm32h743xx.h 关于FALSH的那部分定义,将HAL 1.7.0版本的stm32h743xx.h 中的FLASH部分复制过来(直接替换整个文件会带来别的问题):

老版本的:
/******************************************************************************/
/*                                                                            */
/*                                    FLASH                                   */
/*                                                                            */
/******************************************************************************/
/*
* @brief FLASH Total Sectors Number
*/
#if 0
#define FLASH_SECTOR_TOTAL  16


/*******************  Bits definition for FLASH_ACR register  **********************/
#define FLASH_ACR_LATENCY_Pos                (0U)                              
#define FLASH_ACR_LATENCY_Msk                (0x7U << FLASH_ACR_LATENCY_Pos)   /*!< 0x00000007 */

省略。。。。

新版本的:

/*
* @brief FLASH Total Sectors Number
*/
#define FLASH_SECTOR_TOTAL  8U
#define FLASH_NB_32BITWORD_IN_FLASHWORD  8U

/*******************  Bits definition for FLASH_ACR register  **********************/
#define FLASH_ACR_LATENCY_Pos                (0U)
#define FLASH_ACR_LATENCY_Msk                (0xFUL << FLASH_ACR_LATENCY_Pos)  /*!< 0x0000000F */
#define FLASH_ACR_LATENCY                    FLASH_ACR_LATENCY_Msk             /*!< Read Latency */
#define FLASH_ACR_LATENCY_0WS                (0x00000000UL)
#define FLASH_ACR_LATENCY_1WS                (0x00000001UL)
#define FLASH_ACR_LATENCY_2WS                (0x00000002UL)
#define FLASH_ACR_LATENCY_3WS                (0x00000003UL)

。。。。

/*******************  Bits definition for FLASH_ECC_FA register  *******************/
#define FLASH_ECC_FA_FAIL_ECC_ADDR_Pos       (0U)
#define FLASH_ECC_FA_FAIL_ECC_ADDR_Msk       (0x7FFFUL << FLASH_ECC_FA_FAIL_ECC_ADDR_Pos) /*!< 0x00007FFF */
#define FLASH_ECC_FA_FAIL_ECC_ADDR           FLASH_ECC_FA_FAIL_ECC_ADDR_Msk               /*!< ECC error address */

===============================================END=================================================================

做完上面修改就可以测试一下全片擦除了(除BOOT区外)

在原DEMO上加了一个例子:

case '3':                                       
                                        /* 擦除扇区 */
                                        {
                                                uint32_t i=1, err=0;
                                                uint8_t buf[4096]={0};
                                                uint32_t sector[16]={
                                                ADDR_FLASH_SECTOR_0_BANK1,
                                                ADDR_FLASH_SECTOR_1_BANK1,
                                                ADDR_FLASH_SECTOR_2_BANK1,
                                                ADDR_FLASH_SECTOR_3_BANK1,
                                                ADDR_FLASH_SECTOR_4_BANK1,
                                                ADDR_FLASH_SECTOR_5_BANK1,
                                                ADDR_FLASH_SECTOR_6_BANK1,
                                                ADDR_FLASH_SECTOR_7_BANK1,
                                               
                                                ADDR_FLASH_SECTOR_0_BANK2,
                                                ADDR_FLASH_SECTOR_1_BANK2,
                                                ADDR_FLASH_SECTOR_2_BANK2,
                                                ADDR_FLASH_SECTOR_3_BANK2,
                                                ADDR_FLASH_SECTOR_4_BANK2,
                                                ADDR_FLASH_SECTOR_5_BANK2,
                                                ADDR_FLASH_SECTOR_6_BANK2,
                                                ADDR_FLASH_SECTOR_7_BANK2,
                                        };
                                       
                                        printf("擦除FLASH....\r\n");
                                        for(i=3;i<16;i++) // boot占用前两个扇区
                                                bsp_EraseCpuFlash(sector);
                                        printf("擦除FLASH结束. 开始写入...\r\n");

                                        memset(buf, 0xaa, 4096);
                                        /* 扇区写入数据 */
                                        for(i=0; i< ADDR_FLASH_SECTOR_7_BANK2+128*1024; i+=4096)//820 0000&#8236;
                                        {
                                                err = bsp_WriteCpuFlash((uint32_t)ADDR_FLASH_SECTOR_3_BANK1+i,  (uint8_t *)buf, sizeof(buf));                       
                                                if (err){
                                                        printf("写入失败,err=%d, 位置: %ld\r\n", err, i);
                                                        break;
                                                }
                                        }
                                        printf("写入结束,大小: %ld\r\n", i);
                }
                    break;


到这里,基本BOOT就可以了,其他的都常规操作了,从U盘中检测文件是否存在,存在读取,写入FLASH。先升级内部FLASH,再升级QSPI FLASH。最后跳转。

不得不说,8线QSPI的速度比4线还是快多了,同样的文件,下载明显示快了一倍。比起MDK下载那根本不可同日而语。

走了一遍,确定没问题,跟领导说事情搞定了。领导看完后,淡淡的说句,再加了LCD显示一下升级进度。我说:给了个灯指示,下载的时候灯闪得很快。领导说,不够
人性。我:...

屏幕驱动搞了半天显示不正常,以过往走弯路的直觉,感觉方向不对,果断干掉屏幕程序,把XX火的驱动代码移植过来,一断操作,好了。

这里要注意一点:LCD与USB 的时钟冲突问题,LTDC外设时钟是挂在PLL3上的,所以USB的时钟源不能选PLL3了,因为两个外设的时钟不一样,切记


10.png

11.png


H7的时钟这块感觉还是比较复杂的,要好好看看时钟树的图。

泣血总结。

如果需要代码的可以Email:   624394687@qq.com








回复

使用道具 举报

3

主题

1231

回帖

1240

积分

至尊会员

积分
1240
发表于 2020-3-12 23:40:32 | 显示全部楼层
回复

使用道具 举报

4

主题

33

回帖

45

积分

新手上路

积分
45
发表于 2020-3-13 08:27:39 | 显示全部楼层
牛就一个字
回复

使用道具 举报

27

主题

95

回帖

181

积分

初级会员

积分
181
 楼主| 发表于 2020-3-13 08:47:46 | 显示全部楼层
这份代码是去掉屏幕驱动的,屏幕感觉没什么用
链接:https://pan.baidu.com/s/116zswiTe8KJOeNqvULNRdQ
提取码:9e3h
效果

1.jpg        3.jpg       2.jpg

回复

使用道具 举报

2

主题

85

回帖

91

积分

初级会员

积分
91
发表于 2020-3-13 10:18:08 | 显示全部楼层
厉害
回复

使用道具 举报

18

主题

321

回帖

375

积分

高级会员

积分
375
发表于 2020-3-13 14:33:48 | 显示全部楼层
多谢楼主分享
回复

使用道具 举报

18

主题

321

回帖

375

积分

高级会员

积分
375
发表于 2020-3-13 14:34:08 | 显示全部楼层
多谢楼主分享
回复

使用道具 举报

0

主题

6

回帖

6

积分

新手上路

积分
6
发表于 2020-11-4 18:53:07 | 显示全部楼层
非常感谢楼主的分享,不知道楼主有没有试过将速度配置成133MHZ,我用的QSPI为两片W25Q64JVSIQ,在速度设置成133MHZ后发现跑一两天会有死机的现象,PCB也已重新layout优化。还请大神在你的demo板上尝试一下是否能达到133MHZ
回复

使用道具 举报

0

主题

84

回帖

84

积分

初级会员

积分
84
发表于 2020-11-5 08:08:56 | 显示全部楼层
感谢,,学习了。
回复

使用道具 举报

3

主题

12

回帖

21

积分

新手上路

积分
21
发表于 2022-5-3 15:50:52 | 显示全部楼层
有用,不过不太明白
回复

使用道具 举报

3

主题

12

回帖

21

积分

新手上路

积分
21
发表于 2022-5-3 16:33:28 | 显示全部楼层
sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */
为什么都是1线方式?不是应该4线吗
回复

使用道具 举报

32

主题

262

回帖

363

积分

高级会员

积分
363
发表于 2022-5-6 09:25:54 | 显示全部楼层
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-5-15 23:07 , Processed in 0.223178 second(s), 27 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

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