玩转STM32CubeMX | SPI总线

发布者:梅花居士最新更新时间:2021-10-14 来源: eefocus关键字:STM32CubeMX  SPI总线  W25QXX 手机看文章 扫描二维码
随时随地手机看文章

1.SPI总线及W25QXX芯片

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)的配置不同,可以有四种工作方式:

*MISO:主设备数据输入,从设备数据输出


*MOSI:主设备数据输出,从设备数据输入


*SCLK:时钟信号,由主设备产生


*CS:从设备片选信号,由主设备控制



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


*USART1串口


*W25Q64


*K_UP和K_DOWN按键


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 */

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芯片的值,同时串口打印出相应信息

关键字:STM32CubeMX  SPI总线  W25QXX 引用地址:玩转STM32CubeMX | SPI总线

上一篇:玩转STM32CubeMX | 红外遥控
下一篇:玩转STM32CubeMX | DAC数模转换

推荐阅读最新更新时间:2024-11-17 13:21

基于STM32CUBEMX驱动TMOS模块STHS34PF80(4)----中断获取信号
概述 HS34PF80的数据准备信号提供了一种机制,允许设备在新的测量数据可读取时通知系统,并触发同步操作,通过正确配置相关寄存器,可以确保系统及时捕获和处理来自设备的新数据,从而提高整体性能和响应能力。 检测人体的存在和动作,并通过特定的通信接口发送检测结果。 最近在弄ST和瑞萨RA的课程,需要样片的可以加群申请:615061293 。 样品申请 https://www.wjx.top/vm/OhcKxJk.aspx# 视频教程 https://www.bilibili.com/video/BV1NF41117S6/ 参考Demo https://github.com/STMicroelectronics/STMems_S
[单片机]
基于<font color='red'>STM32CUBEMX</font>驱动TMOS模块STHS34PF80(4)----中断获取信号
STM32CubeMX系列 | PWM输出
1. PWM简介 脉冲宽度调制(PWM,Pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。即对脉冲宽度的控制,PWM原理如下图示: 图中我们假定定时器是工作在向上计数PWM模式,且当CNT CCRx时输出0,当CNT = CCRx时输出1,那么就可以得到如上的PWM示意图:当CNT CCRx时,IO口输出低电平;当CNT = CCRx时,IO口输出高电平;当CNT值达到ARR的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值就可以改变PWM输出的占空比,改变ARR的值就可以改变PWM输出的频率 输出模式有两种:PWM1和PWM2 输
[单片机]
STM32CubeMX系列 | 定时器中断
1. 定时器中断简介 STM32的定时器功能十分强大,有高级定时器(TIM1和TIM8)、通用定时器(TIM2~TIM5)和基本定时器(TIM6和TIM7);本实验主要介绍难度适中的通用定时器,通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。 它适用于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)。 使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。 每个定时器都是完全独立的,没有互相共享任何资源。 通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括: 16位向上、向下、向上/向下自动装载计数器 16位
[单片机]
STM32开发笔记19: STM32CubeMX中定时器的配置方法
单片机型号:STM32L053R8T6 本文介绍在STM32CubeMX进行定时器的配置,产生固定时间中断的方法,以TIM2为例,步骤如下: 1、使能TIM2,指定时钟源。 2、查看数据手册,确定该定时器的内部数据总线,本文所引用的定时器内部数据总线为APB1。 3、在时钟配置中确认所选定时器的内部数据总线的时钟频率,我这里是32MHz。 4、在配置页中,选中相应的时钟,在Prescaler中输入预分频系数,在Counter Period中输入溢出系数。例如,本例中时钟为32MHz/32=1MHz,也就是一个周期为1us,我选择溢出系数为1000,则产生中断的时间为1ms。
[单片机]
STM32开发笔记19: <font color='red'>STM32CubeMX</font>中定时器的配置方法
STM32双缓冲机制初始化(使用STM32CubeMX
1.使用STM32CubeMX配置的串口引脚设置和dma的设置会生成在usart.c。 1)如果DMA接收想采用循环缓冲区的方式,可以直接将RX-DMA设置成Circle方式,然后数据就会硬件上自动实现环形缓冲区的功能,省了不少时间。 2)DMA在采用Normal模式的时候,当一次任务完成后,DMA- DMA_BufferSize自动清零,并且DMA自动停止。如果想再次设置DMA的BufferSize的话,必须要进行如下操作: step1:DMA_CMD(DMAx_Channely,DISABLE); step2: 设置DMA_BufferLen step3:DMA_CMD(DMAx_Channely
[单片机]
STM32CubeMX GPIO模拟I2C读写M24C64
一、先了解一下硬件的连接,I2C_SDA和I2C_SCL分别接STM32的PB9、PB6 二、粗阅一下M24C64的数据手册,得知器件地址和存储器地址,器件地址是8bit,而存储器地址是16bit 三、下面是M24C64的写时序 四、下面是M24C64的读时序 五、下面是程序编写流程 六、看看时序参数 七、好啦!需要的知识点差不多都提到了开始搬砖 1、用STM32CubeMX配置生成工程,并打开工程。(具体怎么用这个软件这里不讲) 2、在我的工程里是这样配置的 《1》配置USART3,用打印读出来的数据与写入的是否一致 《2》配置PB6、PB9为开漏输出模式,配置如下: void MX_GPIO_Init(void)
[单片机]
<font color='red'>STM32CubeMX</font> GPIO模拟I2C读写M24C64
STM32CUBEMX】增加自定义 Groups 问题
背景 最近在使用 STM32CUBEMX 生成工程,想新建几个 Groups 放自己的文件。 平常自建的工程,我都是在 Manage Project Items 里面增加的。在使用STM32CUBEMX 生成的工程,发现每次在Manage Project Items 里面增加Groups,Keil 直接就崩了,压根就建不上。下图中的InterFace就是我自建的 Groups ,点击 “OK” ,Keil 就会崩掉。 解决方法 可以右键“Target XX”,下拉菜单里面有个选项“Add Groups”,使用“Add Groups”就可以新建 Groups。 在新建完后,可以在 Manage Project Items
[单片机]
【<font color='red'>STM32CUBEMX</font>】增加自定义 Groups 问题
STM32CubeMX:PWM
芯片:STM32F103C8T6 应用管脚: 输出:PA0、PA1 TIM2 CH2通道 实现PA1管脚50HZ(20ms)可调PWM输出,系统TIMx_CNT=8MHZ=8000 000HZ,设置TIMx_PSC=800-1,那么TIMx_ARR=(TIMx_CNT/TIMx_PSC)*定时器时间=(8000000/800)*0.02=200,TIMx_ARR=200-1。 配置界面 TIM2配置 开启NVIC中断配置 程序中增加PWM可调占空比应用函数 /* USER CODE BEGIN 4 */ /** * @brief 调整PWM占空比 * @param value为占空比 val
[单片机]

推荐帖子

dsp初学
dsp初学者是否一定要先学单片机呢?dsp初学不需要吧,是不同的,dsp的功能强大的多,编程的话主要用c,可以喝汇编混合编程,和单片机没有很大的联系!回复楼主wuhong26的帖子希望能对dsp初学者能有点帮助https://home.eeworld.com.cn/?uid-...wspace-itemid-13762看看shicong的建议回复楼主wuhong26的帖子不一定非得先学单片机,但是最好还是先学习一下微机原理,这样可以入门更快一些,否则会很慢,而
wuhong26 DSP 与 ARM 处理器
【得捷电子Follow me第2期】 任务提交
视频链接:https://training.eeworld.com.cn/video/37926一、介绍很高兴能够参加这次活动,这次活动使用的是AdafruitESP32-S3TFTFeather开发板,搭载ESP32-S3,包含蓝牙,wifi,还有一块tft高清屏幕。开发工具是Vscode和Thonny。这次活动我做了以下四个任务 任务1:控制屏幕显示中文 任务2:网络功能使用 任务3:控制WS2812B 任务4分任务1:日历&时钟我在七月
eew_9QATv7 DigiKey得捷技术专区
EVC下将string型转成char*类型
EVC下将string型转成char*类型.CStringstr(_T(01234567));char*ch=str.GetBuffer(0);这在VC6.0中编译没任何问题.但EVC下编译出错.errorc2440:initializing:cannotconvertfrom\'unsignedshort*\'to\'char*\'请问怎么解决?EVC下将string型转成char*类型GetBuffer返回的是UNICODE(TCH
123456ZJ 嵌入式系统
keil,如何看debug时候,的idata内容
有memorywindows在address中输入d:0xf0,发现好像看到的是sfr,而不是高128字节的数据(因为我已经执行了,所有数据区清零,可是在memorywindows看到的依旧是FF???)keil,如何看debug时候,的idata内容用i:0xXXXX输入i:0就可以了这里i和d的区别是什么????????引用3楼cstt60777的回复:这里i和d的区别是什么????????data:固定指前面0x00-0x7f的128个RAM,可以用acc
sealove518 嵌入式系统
向大家讨教有效的版本管理
一直以来没有版本管理的概念,都是在取得一定的进展之后甚至每天做一次保存,这样的话文件保存越来越多,一段时间之后各个版本也捋不清楚状况了,并且还不能删除就怕哪天要回退到解放前。所以这两天看了一下关于版本管理的介绍,有svn和git。做了简单的比较,觉得svn相对简单易懂一点,所以安装了一个试试手。请教各位大神,个人和公司都是怎么进行版本管理的,一起分享下向大家讨教有效的版本管理不懂帮顶,顶完睡觉,公司用的SVN,但是我倾向于git,虽然我不会git。一直在用svn,还行
elvike 综合技术交流
【求助】怎么将路由协议嵌入到CE的协议栈
rt请各位前辈向晚辈提供一个将路由协议嵌入CE的解决方案。晚辈刚刚接到这样一项工作。从来没有接触过。想问一下各位前辈,需要学那些东西,做什么准备。实现的整个流程大体上是什么。。。麻烦给介绍以下。。。先谢谢各位前辈了。。。【求助】怎么将路由协议嵌入到CE的协议栈无语!!!人肉google比google流行多了。。。被鄙视了...着实不太懂...麻烦给个说法...
weiyingwu 嵌入式系统
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved