0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

STM32通过硬件SPI模块软件模拟驱动来进行拓展

冬至子 来源:ITRelief 作者:Sachefgh Xu 2023-07-25 14:53 次阅读

FSMC一般只有STM32大容量产品才具备。因此在使用中小容量产品外接存储器时,一般会通过硬件SPI模块软件模拟驱动来进行拓展。

本文将以常见的 NOR Flash(多个厂家有对标的同类产品)为例。

我使用的是普亚的P25Q32SH,这个flash除了贵和多一些功能外,在基本控制方面和华邦的W25Q32差不多,基本指令通用。但不同flash之间还是存在一些差异,要注意适配。

一、封装

8引脚的spi Flash除了封装方式有些差异,引脚排列基本是一模一样的。

图片

代码:

总的来说还是很简单的。因为时间比较赶,只求能用,存在代码冗余和效率较低的问题,欢迎改进指正!

//******************************************************************************
//* 文件名   ExtFlashSPI.h
//* 介绍:    利用STM32硬件spi实现对spi的控制
//*  基于W25Q32,在基础指令方面兼容
//*  使用其他芯片请参照手册进行指令集和参数的适配
//*  
//*  ※适用最大容量为16M(128Mbit)Flash 
//* 
//* @Author  Sachefgh Xu 
//*********************************模块介绍************************************
// 适用8引脚的spi flash
//
//
//
//引脚配置: /VCC  一般选择 2.7-3.6v 的元件,flash对电压有要求,推荐供电接稳压管
//    /GND  接地
//    /CS   片选,低电平使能;上电时应当置高电平,推荐NSS引脚使能上拉或外接上拉
//    /DI(IO0) Data-in 
//    /DO         Data-out
//    /CLK  时钟线
//    /WP   写保护 默认不启用;启用后高电平+写使能指令解锁----------本驱动中WP接vcc拉高
//    /HOLD  Hold-input; 时钟线和hold均为低电平时触发暂停;默认高电平------本驱动中HOLD接vcc拉高
//说明:
//对Flash时序的规定:MOSI-》DI,  flash在时钟上升沿采样
// MISO- >DO flash在下降沿设置。当片选使能时时钟处于低电平,视为已接收一个下降沿。主机在上升沿读取采样
//配置spi模块时,时钟线空闲为低电平,上升沿采样(CPHA=0,CPOL=0); MSB模式
//
//上电时,模块写使能被禁用。
//
/***********************************ED***********************************/
#ifndef _SWSPI_FLASH_H_
#define _SWSPI_FLASH_H_  
#include "stm32f1xx_ll_gpio.h"
#include "stm32f1xx_ll_spi.h"
#include "stm32f1xx_ll_dma.h"
#include "stm32f1xx_ll_utils.h"

/***********************************配置参数***********************************/

#define Flash_SPI SPI1   //连接的硬件spi模块,spi应配置全双工主机模式
#define Flash_CSPORT     GPIOA //片选线;应当配置为高速输出,初始高电平
#define Flash_CSPIN      LL_GPIO_PIN_4

#define BlockNumber  64  //块数量

#define Page /*Each Page has*/ 256 /*Bytes*/
#define Sector /*Each Sector has*/ 16 /*Pages*/
#define Block /*Each Block has*/ 16 /*Sectors*/
#define AddressMax (BlockNumber * Page * Sector* Block-1) //最大内存地址,每一地址对应一字节


#if (Page==256)
#define PageMsk  0xFFFF00  

 #if (Sector==16)
#define SectorMsk  0xFFF000     
 #endif // (Sector==16)


#endif 


//不同容量Flash只有块数量有区别,一般扇区数量和页数量一致。
//24Bits地址 最高8位标定block,高16位标定page

//页地址     addr & 0xFFFF00
//扇区地址   addr & 0xFFF000
//块地址  addr & 0xFF0000

//额外指令配置:

//#define _81H  //page erase页擦除功能.-----w25qxx系列无此功能


/******************************************************************************/

 uint8_t ManufacturerID; //制造商信息
 uint8_t MemoryTypeID;
 uint8_t CapacityID; //容量信息
//上述信息在初始化时读取

//临时数据 


/***********************/

__STATIC_INLINE void Flash_GetInformation();
__STATIC_INLINE void Flash_WaitWriteToFinish();



/**
 * @brief  初始化函数,首先调用
 * @note  一并读取和存储制造商信息、容量和存储类型数据
 */
__STATIC_INLINE void Flash_Init()
{ 
 LL_mDelay(7); //等待上电初始化,可删
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);//关闭片选
 //
 LL_SPI_Enable(Flash_SPI); //重新开启SPI模块
 LL_SPI_ReceiveData8(Flash_SPI); //置零RXNE
 Flash_GetInformation();
}

/**
 * @brief  读取制造商ID、存储类型ID、容量ID
 * @cmd:  90h
 * @note  读取后存入 ManufacturerID、MemoryTypeID、CapacityID变量中
 */
__STATIC_INLINE void Flash_GetInformation()
{ //读取Manufacturer ID& Device ID (90h)
 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);
 LL_SPI_TransmitData8(Flash_SPI, 0x9FU);
 while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ; //等待接收完
 LL_SPI_ReceiveData8(Flash_SPI);   //置零RXNE
 LL_SPI_TransmitData8(Flash_SPI, 0x00U);  //生成时钟
 while(!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)); //等待接收完
 ManufacturerID = LL_SPI_ReceiveData8(Flash_SPI);
 LL_SPI_TransmitData8(Flash_SPI, 0x00U);
 while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ;//等待接收完
 MemoryTypeID = LL_SPI_ReceiveData8(Flash_SPI);
 LL_SPI_TransmitData8(Flash_SPI, 0x00U);
 while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ;//等待接收完
 CapacityID = LL_SPI_ReceiveData8(Flash_SPI);
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
 
}

/**
 * @brief  使能擦写
 * @cmd: 06h
 * @note  再通过指令进行页写入、扇区擦除、块擦除、整片擦除、写状态寄存器时均需调用
 */
__STATIC_INLINE void Flash_WriteEnable()
{
 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);
 LL_SPI_TransmitData8(Flash_SPI, 0x06U);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
}

/**
 * @brief  禁用擦写(写入锁)
 * @cmd: 04h
 * @note  写入、擦除、写状态寄存器完成后调用
 */
__STATIC_INLINE void Flash_WriteDisable()
{
 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);
 LL_SPI_TransmitData8(Flash_SPI, 0x04U);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
}
/**
 * @brief  连续字节读取(常速)
 * @cmd: 03h
 * @param 
 *  uint32_t addr  //24位地址(数据最高8位忽略),每一位代表一字节数据;addr可取任意有效地址
 *  uint8_t    *data //传入的uint_8数组地址或者 变量地址(当读取数为1时)
 *  uint8_t  number  //读取字节数
 *
 * @note  发送指令03h后分3字节从高到低传输地址位; Flash将在之后的时钟周期从传入地址开始
 * 以地址递增顺序传出片上数据(数据位数共number位),直到CS被拉高
 * 当number=1,读取指定位数据
 */
__STATIC_INLINE void Flash_ReadData(uint32_t addr, uint8_t *data, uint16_t length)
{
 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);
 LL_SPI_TransmitData8(Flash_SPI, 0x03);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕
 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(addr >>16)&0xFF);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕
 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)((addr > > 8)&0xFF));
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕
 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(addr & 0xFF));
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 LL_SPI_ReceiveData8(Flash_SPI);//置零标志
 //开始读取
 for(uint16_t i = 0 ; i < length ; i++)
 {
  LL_SPI_TransmitData8(Flash_SPI, 0x00U);//generate clock
  while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ;//wait till tranfer complete
  data[i] = LL_SPI_ReceiveData8(Flash_SPI);
 }
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
 //延时
 uint16_t dlay=0;
 while (dlay < 960){dlay++;}
}
/**
 * @brief  整片擦除(变为FF)  ※此操作无法复原,使用请谨慎
 * @cmd: 60h(或C7h)
 * @note 将整片flash数据擦除
 */
__STATIC_INLINE void Flash_EraseChip()
{
 Flash_WriteEnable();//使能写
 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);
 LL_SPI_TransmitData8(Flash_SPI, 0x60U);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕
 LL_mDelay(1);
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
 Flash_WaitWriteToFinish();
}

#ifdef _81H    
/**
 * @brief  擦除整个page(将整页256bytes数据写为FF)  ※此操作无法复原,使用请谨慎
 * @cmd: 81h
 * @param:  uint32_t addr  //24位页地址(数据最高8位忽略)。前16位规定页地址,最后8位无意义(dummy)。
 *  addr可填位于 目标页 的任一地址 
 * 
 * @note 擦除指定Page上的内容;写入前必须先进行擦除
 */
__STATIC_INLINE void Flash_ErasePage(uint32_t addr)
{
 Flash_WriteEnable();//使能读写
 
 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);
 LL_SPI_TransmitData8(Flash_SPI, 0x81U);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕
 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(addr > > 16)&0xFF);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕
 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)((addr > > 8) & 0xFF));
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕
 LL_SPI_TransmitData8(Flash_SPI, 0X00U);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传输完毕
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
 Flash_WaitWriteToFinish();
}
#endif
/**
 * @brief  擦除整个sector(4096bytes = 16 pages)  ※此操作无法复原,使用请谨慎
 * @cmd: 20h
 * @param:  uint32_t addr  //24位扇区地址(数据最高8位忽略)。扇区由addr A23-A12确定
 *  addr可填位于 目标扇区 的任一地址
 * @note 擦除指定Page上的内容;写入前必须先进行擦除
 */
__STATIC_INLINE void Flash_EraseSector(uint32_t addr)
{
 Flash_WriteEnable();
 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);
 LL_SPI_TransmitData8(Flash_SPI, 0x20U);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(addr > > 16)&0xFF);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)((addr > > 8) & 0xF0));
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 LL_SPI_TransmitData8(Flash_SPI, 0X00U);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
 Flash_WaitWriteToFinish();
}
/**
 * @brief  等待退出写BUSY状态
 * @retval   
 */
__STATIC_INLINE void Flash_WaitWriteToFinish()
{
 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);
 LL_SPI_TransmitData8(Flash_SPI, 0x05U);
 while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ;
 LL_SPI_ReceiveData8(Flash_SPI);//clear DR
 do
 {
  LL_SPI_TransmitData8(Flash_SPI, 0x00);//dummy   
  while (!LL_SPI_IsActiveFlag_RXNE(Flash_SPI)) ;
 } while ((LL_SPI_ReceiveData8(Flash_SPI) & 0x01));//忙时循环,不忙退出
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
}

/**************************************************************************************************/

//有问题
/**
 * @brief  写入数据,伴有覆盖擦除功能
 * @cmd: 02h
 * @param:  uint32_t addr  //24位地址(数据最高8位忽略),可填片上任意地址;写入
 * 将从该地址开始递增, ※但写入数据长度不能溢出地址所在页(1页256Bytes).
 * 
 * @param  uint8_t length //数据长度,必须大于0
 * @param  uint8_t *data  //写入数据所在地址指针
 * 
 * @note 本函数工作原理如下:
 * 1.通过宏定义判断是否有页擦除功能
 * 2.将需要写入地址所在的扇区/页数据暂存至temp中
 * 3.进行页/扇区擦除操作
 * 4.将temp对应位置数据用data中待写入数据替换
 * 5.将更改后的temp原位写入
 */
__STATIC_INLINE void Flash_WriteData(uint32_t addr, uint8_t *data, uint16_t length)
{
#ifdef _81H //有页擦除功能
 Flash_WriteEnable();
 uint8_t temp[Page];
 Flash_ReadData((addr & 0xFFFF00), temp, Page);//Read Page
 //根据逻辑分析仪调整ReadData函数最后延迟时间,当执行下一个06h指令时MISO应当不输出(0x00)
 Flash_ErasePage(addr); 
 for (uint16_t i = 0; i < length; i++)
 {
  temp[(addr & 0x0000FF) + i] = data[i];
 }//操作成功 
 Flash_WaitWriteToFinish(); 
 Flash_WriteEnable();  
 LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN); 
 LL_SPI_TransmitData8(Flash_SPI, 0x02U); 
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传送页地址
 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(addr > > 16)&0xFF);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 LL_SPI_TransmitData8(Flash_SPI, (uint8_t)((addr > > 8) & 0xFF));
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ; 
 LL_SPI_TransmitData8(Flash_SPI, 0x00U);
 while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 
 for (uint16_t j = 0; j < Page; j++)
 { //发送没问题
  LL_SPI_TransmitData8(Flash_SPI,temp[j]);
  while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
 }
 LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
 Flash_WaitWriteToFinish();
#else //无页擦除功能,扇区(Sector)擦除
 uint8_t temp[Sector * Page]; // 缓存 
 Flash_WriteEnable();
 Flash_ReadData((addr & SectorMsk), temp, Sector * Page); 
 Flash_EraseSector(addr);
 for (uint16_t i = 0; i < length; i++)
 {
  temp[(uint16_t)(addr & 0x000FFF) + i] = data[i];
 }//替换完成
 //将数据原位写入
 Flash_WriteEnable();
 uint32_t iaddpage= addr & SectorMsk; //扇区的起始地址
 //每次写一页,共Sector次
 int m = 0;
 for(uint8_t j = 0 ; j < Sector ; j++)
 {
  Flash_WriteEnable();
  LL_GPIO_ResetOutputPin(Flash_CSPORT, Flash_CSPIN);
  LL_SPI_TransmitData8(Flash_SPI, 0x02U);
  while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;//传送页地址
  LL_SPI_TransmitData8(Flash_SPI, (uint8_t)(iaddpage > > 16)&0xFF);
  while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
  LL_SPI_TransmitData8(Flash_SPI, (uint8_t)((iaddpage > > 8) & 0xFF));
  while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
  LL_SPI_TransmitData8(Flash_SPI, 0x00U);
  while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
  for (uint16_t i = 0; i < Page; i++)
  {
   LL_SPI_TransmitData8(Flash_SPI, temp[m +i]);
   while (!LL_SPI_IsActiveFlag_TXE(Flash_SPI)) ;
  }
  LL_GPIO_SetOutputPin(Flash_CSPORT, Flash_CSPIN);
  m += 256;
  iaddpage += 0x000100U;
  Flash_WaitWriteToFinish(); 
 }
#endif 

}


#endif // !_SWSPI_FLASH_H_
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储器
    +关注

    关注

    38

    文章

    7484

    浏览量

    163765
  • SPI
    SPI
    +关注

    关注

    17

    文章

    1706

    浏览量

    91508
  • 逻辑分析仪
    +关注

    关注

    3

    文章

    214

    浏览量

    23165
  • FSMC模块
    +关注

    关注

    0

    文章

    9

    浏览量

    1923
  • 模拟驱动电路

    关注

    0

    文章

    2

    浏览量

    746
收藏 人收藏

    评论

    相关推荐

    基于GPIO模拟SPI接口驱动设计与实现

    SPI总线是我们常用的串行设备接口,一般情况下我们都会适应硬件SPI接口,但有些时候当硬件端口不足时,我们也希望可以使用软件
    发表于 12-07 16:21 6336次阅读
    基于GPIO<b class='flag-5'>模拟</b>的<b class='flag-5'>SPI</b>接口<b class='flag-5'>驱动</b>设计与实现

    stm32怎么通过硬件仿真的方法来找错?

    stm32怎么通过硬件仿真的方法来找错?
    发表于 09-11 19:17

    硬件SPI软件模拟SPI速度区别

    ,LORA芯片SX1278等。最近为了驱动彩色OLED显示屏,为了提高显示刷新率,需要对程序代码进行优化。于是,将相关SPI驱动软件
    发表于 07-01 06:40

    怎样通过硬件spi读取offset与gain寄存器的值呢

    之前写过了mcu通过硬件spi接口向dac芯片ad5764的数据寄存器写值输出电压,ad5764的offset与gain寄存器的值也是可以通过硬件spi读出来的。第一步:将待读取的芯片
    发表于 02-14 07:39

    如何软件模拟SPI

    模拟SPI NRF24L01模块的简单使用软件模拟SPI(注:默认已经充分了解
    发表于 02-16 06:59

    STM32F10x_SPI硬件接口 + 软件模拟)读写Flash(25Q16)

    STM32F10x_SPI硬件接口 + 软件模拟)读写Flash(25Q16)
    的头像 发表于 03-25 13:59 1w次阅读
    <b class='flag-5'>STM32F10x_SPI</b> (<b class='flag-5'>硬件</b>接口 + <b class='flag-5'>软件</b><b class='flag-5'>模拟</b>)读写Flash(25Q16)

    单片机通过模拟SPI驱动LD3320模块

    单片机通过模拟SPI驱动LD3320模块仅完成识别部分!仅完成识别部分!仅完成识别部分!根据手册推荐使用3.3V,而不是5V
    发表于 12-16 16:52 7次下载
    单片机<b class='flag-5'>通过</b><b class='flag-5'>模拟</b><b class='flag-5'>SPI</b><b class='flag-5'>驱动</b>LD3320<b class='flag-5'>模块</b>

    SPI软件模拟 NRF24L01

    模拟SPI NRF24L01模块的简单使用软件模拟SPI(注:默认已经充分了解
    发表于 12-17 18:04 8次下载
    <b class='flag-5'>SPI</b><b class='flag-5'>软件</b><b class='flag-5'>模拟</b> NRF24L01

    STM32 SPI 软件NSS和硬件NSS解读

    [导读]SSM可以控制内部NSS引脚与SSI(一个寄存器,软件模式)相连,还是与NSS外部引脚(真正的STM32引脚,硬件模式)相连。真正作用的是内部NSS引脚(内部NSS引脚才真正连接到SP
    发表于 12-22 19:12 14次下载
    <b class='flag-5'>STM32</b> <b class='flag-5'>SPI</b> <b class='flag-5'>软件</b>NSS和<b class='flag-5'>硬件</b>NSS解读

    硬件SPI软件模拟SPI速度区别实测

    ,LORA芯片SX1278等。最近为了驱动彩色OLED显示屏,为了提高显示刷新率,需要对程序代码进行优化。于是,将相关SPI驱动软件
    发表于 12-22 19:13 9次下载
    <b class='flag-5'>硬件</b><b class='flag-5'>SPI</b>与<b class='flag-5'>软件</b><b class='flag-5'>模拟</b><b class='flag-5'>SPI</b>速度区别实测

    软件模拟SPI

    软件模拟硬件操作SPI更为简单,缺点是明显更加消耗CPU软件SPI实例:CPOL=1,CPHA
    发表于 12-22 19:15 10次下载
    <b class='flag-5'>软件</b><b class='flag-5'>模拟</b><b class='flag-5'>SPI</b>

    stm32f103使用dma和fpga进行spi通信

    stm32作为从机,fpga作为主机。进行spi通信。stm32使用dma进行数据接收。在dma中断中
    发表于 12-22 19:29 95次下载
    <b class='flag-5'>stm32</b>f103使用dma和fpga<b class='flag-5'>进行</b><b class='flag-5'>spi</b>通信

    STM32L4 模拟SPI 驱动LCD 240*240屏幕

    ,屏幕驱动ST7789V2,MCU:STM32L431RCT6这些资料在网上都能找到,这里就不提供下载了。上图是开发板连接的硬件SPI对应的引脚,
    发表于 12-22 19:29 22次下载
    <b class='flag-5'>STM32</b>L4  <b class='flag-5'>模拟</b><b class='flag-5'>SPI</b> <b class='flag-5'>驱动</b>LCD 240*240屏幕

    单片机spi接口的使用方法有哪些(spi接口和串口的区别)

    如果单片机没有硬件SPI模块,或者需要额外的IO引脚实现多个SPI设备的通信,可以使用软件
    的头像 发表于 11-10 16:38 3905次阅读

    硬件spi软件spi的区别

    硬件SPI(串行外设接口)和软件SPI是两种不同的SPI传输方式。SPI是一种同步串行数据通信协
    的头像 发表于 12-26 16:55 5926次阅读