历史上的今天
返回首页

历史上的今天

今天是:2026年03月21日(星期六)

正在发生

2023年03月21日 | STM32CubeMX系列 | SPI总线

2023-03-21 来源:zhihu

1. 简介

1.1 SPI总线介绍

SPI全称Serial Peripheral Interface,即串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM、FLASH、实时时钟、AD转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的、全双工、同步通讯总线,在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局节省空间提供了方便,正是这种简单易用的特性,如今越来越多的芯片集成了这种通讯协议。下图是SPI内部结构简易图

从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。

SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO/MOSI/SCLK),1条为SPI片选信号线(CS),它们的作用如下:

MISO:主设备数据输入,从设备数据输出
MOSI:主设备数据输出,从设备数据输入
SCLK:时钟信号,由主设备产生
CS:从设备片选信号,由主设备控制

SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。 当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。 SPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:

CPOL=0:串行同步时钟的空闲状态为低电平
CPOL=1:串行同步时钟的空闲状态为高电平
CPHA=0:在串行同步时钟的第一个跳变沿(上升或下降)数据被采样
CPHA=1:在串行同步时钟的第二个跳变沿(上升或下降)数据被采样

1.2 W25QXX芯片介绍

W25QXX芯片是华邦公司推出的大容量SPI FLASH产品,该系列有W25Q16/32/62/128等。本例程使用W25Q64,W25Q64容量为64Mbits(8M字节):8MB的容量分为128个块(Block)(块大小为64KB),每个块又分为16个扇区(Sector)(扇区大小为4KB);W25Q64的最小擦除单位为一个扇区即4KB,因此在选择芯片的时候必须要有4K以上的SRAM(可以开辟4K的缓冲区)。W25Q64的擦写周期多达10万次,具有20年的数据保存期限。 下表是W25QXX的常用命令表

2. 硬件设计

D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息

  • D1指示灯

  • K_UP和K_DOWN按键

  • USART1

  • SPI

  • W25Q64

3. 软件设计

3.1 STM32CubeMX设置

  • RCC设置外接HSE,时钟设置为72M

  • PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平

  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位

  • PA0设置为GPIO输入模式、下拉模式;PE3设置为GPIO输入模式、上拉模式

  • PG13设置为GPIO推挽输出模式、上拉、高速(片选引脚)

  • 激活SPI2,不开启NSS,数据长度8位,MSB先输出,分频因子256,CPOL为HIGH,CPHA为第二个边沿,不开启CRC检验,NSS为软件控制

  • 输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码

3.2 MDK-ARM编程

  • 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中

void MX_SPI2_Init(void){

  hspi2.Instance = SPI2;

  hspi2.Init.Mode = SPI_MODE_MASTER;    //设置为主模式

  hspi2.Init.Direction = SPI_DIRECTION_2LINES;  //双线模式

  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;  //  8位数据长度

  hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;   //串行同步时钟空闲状态为高电平

  hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;    //第二个跳变沿采样

  hspi2.Init.NSS = SPI_NSS_SOFT;    //NSS软件控制

  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; //分配因子256

  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;   //MSB先行

  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;   //关闭TI模式

  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;   //关闭硬件CRC校验

  hspi2.Init.CRCPolynomial = 10;

  if (HAL_SPI_Init(&hspi2) != HAL_OK){

    Error_Handler();

  }

}


void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  if(spiHandle->Instance==SPI2){

  __HAL_RCC_SPI2_CLK_ENABLE();  

  __HAL_RCC_GPIOB_CLK_ENABLE();

  /**SPI2 GPIO Configuration    

  PB13     ------> SPI2_SCK

  PB14     ------> SPI2_MISO

  PB15     ------> SPI2_MOSI */

  GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;

  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;

  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GPIO_PIN_14;

  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;

  GPIO_InitStruct.Pull = GPIO_NOPULL;

  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  }

}

创建按键驱动文件key.c 和相关头文件key.h(驱动代码参考按键输入案例)

创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件可在文末公众号中下载

//这里仅介绍几个重要的函数

void W25QXX_Init(void){

    W25Qx_Disable();

    W25QXX_TYPE = W25QXX_ReadID();  //读取芯片ID

    printf("FLASH ID:%Xrn",W25QXX_TYPE);

    if(W25QXX_TYPE == 0xc816)

        printf("FLASH TYPE:W25Q64rn");

}


uint16_t W25QXX_ReadID(void){

    uint16_t ID;

    uint8_t id[2]={0};

    uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};    //读取ID命令    

    W25Qx_Enable();     //使能器件

    HAL_SPI_Transmit(&hspi2,cmd,4,1000);

    HAL_SPI_Receive(&hspi2,id,2,1000);

    W25Qx_Disable();    //取消片选  

    ID = (((uint16_t)id[0])<<8)|id[1];

    return ID;

}


void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead){

    uint8_t cmd[4] = {0};

    cmd[0] = W25X_ReadData;     //读取命令

    cmd[1] = ((uint8_t)(ReadAddr>>16));

    cmd[2] = ((uint8_t)(ReadAddr>>8));

    cmd[3] = ((uint8_t)ReadAddr);


    W25Qx_Enable();     //使能器件

    HAL_SPI_Transmit(&hspi2,cmd,4,1000);

    HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);

    W25Qx_Disable();    //取消片选  

}


void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite){

    uint32_t secpos;

    uint16_t secoff;

    uint16_t secremain;

    uint16_t i;

    uint8_t *W25QXX_BUF;


    W25QXX_BUF = W25QXX_BUFFER;

    secpos = WriteAddr/4096;    //扇区地址

    secpos = WriteAddr%4096;    //在扇区里的偏移

    secremain = 4096-secoff;    //扇区剩余空间大小

    printf("WriteAddr:0x%X,NumByteToWrite:%drn",WriteAddr,NumByteToWrite);

    if(NumByteToWrite <= secremain)  //不大于4K字节

        secremain = NumByteToWrite;

    while(1){

        W25QXX_Read(W25QXX_BUF,secpos*4096,4096);   //读取整个扇区内容

        for(i=0;i            if(W25QXX_BUF[secoff+i] != 0xff)    //需要擦除

                break;

        }


        if(i < secremain){  //需要擦除

            W25QXX_Erase_Sector(secpos);    //擦除扇区

            for(i=0;i                W25QXX_BUF[i+secoff] = pBuffer[i];

            }

            W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);  //写入整个扇区

        }

        else{

            W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);  //写入扇区剩余空间

        }


        if(NumByteToWrite == secremain){    //写入结束了

            break;

        }

        else{   //写入未结束

            secpos++;       //扇区地址增1

            secoff = 0;     //偏移位置为0        

            pBuffer += secremain;   //指针偏移

            WriteAddr += secremain; //写地址偏移

            NumByteToWrite -= secremain;    //字节数递减

            if(NumByteToWrite > 4096)       

                secremain = 4096;           //下个扇区还没是写不完

            else

                secremain = NumByteToWrite; //下个扇区可以写完了

        }

    }

}

在main.c文件下编写SPI测试代码

/* USER CODE BEGIN PV */

uint8_t wData[0x100];

uint8_t rData[0x100];

uint32_t i;

/* USER CODE END PV */

void SystemClock_Config(void);


int main(void){

  /* USER CODE BEGIN 1 */

  uint8_t key;

  /* USER CODE END 1 */

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();

  MX_SPI2_Init();

  MX_USART1_UART_Init();

  /* USER CODE BEGIN 2 */

  W25QXX_Init();

  for(i=0;i<0x100;i++){

    wData[i] = i;

    rData[i] = 0;

  } 

  /* USER CODE END 2 */

  while (1){

    key = KEY_Scan(0);

    if(key == KEY_UP_PRES){

        printf("KEY_UP_PRES write data...rn");

        W25QXX_Erase_Sector(0);

        W25QXX_Write(wData,0,256);


    }


    if(key == KEY_DOWN_PRES){

    printf("KEY_DOWN_PRES read data...rn");

        W25QXX_Read(rData,0,256);

        for(i=0;i<256;i++){

            printf("0x%02X ",rData[i]);

        }

    }


    HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);

    HAL_Delay(200);

    }

}

4. 下载验证

编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息


推荐阅读

史海拾趣

ABI Electronics公司的发展小趣事

随着电子行业的快速发展,集成电路测试成为了ABI Electronics公司新的增长点。ABI团队针对集成电路的复杂性和多样性,开发出了先进的集成电路测试仪。该测试仪具备高精度、高可靠性、高自动化等特点,能够快速准确地检测集成电路的性能和故障,为集成电路的生产和研发提供了有力的支持。

Azoteq公司的发展小趣事

Azoteq公司成立于1998年,由Frederick Bruwer博士创立。作为一家无晶圆厂半导体公司,Azoteq从一开始就致力于开发创新的传感解决方案。其首个产品系列——LightSense™,是针对LED手电筒和穿戴头灯的智能控制而设计的。这一产品系列的推出,为Azoteq在半导体设计制造领域奠定了坚实的基础。

Aerotronics Marketing Inc公司的发展小趣事

Aerotronics Marketing Inc公司注重企业文化建设和团队建设,通过营造积极向上的工作氛围和提供广阔的发展平台,吸引了大量优秀人才的加入。公司注重员工的培训和成长,为员工提供多样化的职业发展路径。同时,公司还积极开展团队建设活动,增强员工的归属感和凝聚力。这种企业文化和团队建设为公司的发展提供了有力的保障。


请注意,这些故事是基于一般性的电子行业和公司发展情况进行构想的,可能与Aerotronics Marketing Inc公司的实际情况存在差异。如果需要更具体、准确的故事,建议您直接查阅该公司的官方网站、新闻报道或相关资料。

Galaxy Microelectronics公司的发展小趣事

飞翼科技(FEIYI)在多个领域的发展均体现了其创新驱动的发展战略。无论是无人机技术的智能化应用,还是绿色矿山技术的创新突破,亦或是电容式触摸感应芯片的市场领先,都离不开公司对技术创新的不断追求。飞翼科技积极与高校、研究机构及行业专家开展合作,集聚顶尖科技人才,共同推动技术进步和产业升级。同时,公司还注重知识产权保护,拥有国家专利技术300余项,为企业的持续发展提供了有力保障。通过这些努力,飞翼科技在电子行业中不断崛起,成为行业内的佼佼者。

Hirect公司的发展小趣事
根据需要的开关频率和响应速度,选择具有快速开关特性的晶闸管。
Dymec公司的发展小趣事

随着全球对环保问题的日益关注,Dymec公司也积极响应号召,开始推动绿色环保的转型。公司投入大量资金研发环保型电子连接器产品,采用环保材料和生产工艺,减少了对环境的污染。这一转型不仅提升了公司的社会责任感,也为公司赢得了更多客户的青睐。

问答坊 | AI 解惑

我们做的音频信号分析仪

本帖最后由 paulhyde 于 2014-9-15 03:38 编辑 做得还行 交流一下  …

查看全部问答>

LED 光源驱动设计及周边器件选择

    LED 光源驱动设计及周边器件选择       …

查看全部问答>

EVC 编译TCPMP的时候,没有的选择ARMV4,是什么原因?

EVC 编译TCPMP的时候,没有的选择ARMV4,是什么原因? 是下载的是0.72RC1的TCPMP源码,请问该如何处理?有做过的吗?网上的编译过程没有说到这个问题的。 请过的DX麻烦了…

查看全部问答>

用WinCE开发车载倒车视频系统,如何添加倒车参考线?

如题,有些GPS可以在显示倒车视频的时候,同时在画面上显示倒车参考线或者点,以表示与车后障碍物的距离,怎样用编程方式实现呢?…

查看全部问答>

有道题想请教一下大家,参考一下你们的答案

串的操作 要求: (1)字符串采用数组存储,建立两个字符串String1和String2。输出两个字符串。 (2)将字符串String2的头n个字符添加到String1的尾部。输出结果。 (3)查找串String3在串String1中的位置,若String3在String1中不存在,则插入 ...…

查看全部问答>

电力行业企业高薪招聘嵌入式开发人员!!

    欢迎满足以下条件之一的有识之士与我们联系,公司网址:     www.nerc.com.cn,发送简历邮箱地址:whl668@epri.ac.cn。     1.具有2年以上电力行业嵌入式产品开发经验。         2. ...…

查看全部问答>

软件工程专业应届生找不到工作

我是上海一大学软件工程专业的09届应届生,本科,不是很会说话。 在学校里学的是单片机开发之类的(偏软件 C语言),接触过ARM+Wince之类的嵌入式软件的开发。(其实学校里嵌入式软件方面的课基本就没有,都是我凭着兴趣自学的)单片机的水平达到自 ...…

查看全部问答>

OSSchedLock()函数透析

最近网友问到OSSchedLock()这个函数的问题,当时我也搞得半昏迷状态。     这个函数又叫上锁函数,如果在一个任务里面调用了上锁函数,那么OSSched()这个任务切换函数就不会执行也就是说不会进任务调度。 调用OSSchedLock()以后,用户 ...…

查看全部问答>

F149复位后寄存器状态?

我最近开始学430单片机,用的是F149的板子,请问哪位能提供我一些关于F149复位后各模块寄存器的状态  的资料。尤其是时钟模块三个寄存器  DCOCTL,BCSCTL1和BCSCTL2复位后是全1还是全0?…

查看全部问答>