当前位置: 首页 > news >正文

stm32-SPI

SPI : serial peripheral interface

主机发送了多少数据,,从机就会返回多少数据??????

极性: 时钟极性,, cpol : clock polarity ,,, 他决定了,spi空闲时,,时钟线SCK默认是低电平还是高电平

  • 有些芯片喜欢时钟默认低
  • 有些芯片喜欢时钟默认高
  • spi为了兼容各种设别,于是允许配置时间空闲时高还是低

CPHA : clock phase : 时钟相位,, spi在第几个时钟边沿“采样数据”

  • 数据变化和数据读取不能同时发生,,,否则,数据还没稳定就被读取,,可能会错,,所以需要一个边沿改变数据,另外一个边沿,读取数据

第一边沿
第二边沿

SPI的四种模式

传输顺序

MSB : most significant bit
LSB : least significant bit

一个字节里面 bit怎么排,,,

数据宽度: spi总线每次传输bit位的个数 ,,, 8bit和16bit


do : 是 miso ,,, master input slave output ,, 接PA6


单片机作为主机

  • txe : transmit buffer empty : 发送缓冲区空,,表示可以写新的数据了
  • rxne : receive buffer not empty : 接收缓冲区非空,,表示收到数据了
  • ovr : overrun : 溢出

  • 2线全双工 : 两根数据线,同时发送和接受
  • 2线只读 : 虽然有两根线,,但是只接收

常用于stm32作为spi从机,,只负责上传数据,,或者读取flash

  • 1线发送: 只使用一根数据线,只发送,,, 比如驱动oled,,只需要显示
  • 1线接收: 1根数据线,只接收

主机1线接收,,从机1线发送,,,一个接受一个发送


根据从机设置SPI的属性,,,

SPI内部是 移位寄存器 shift register,,spi会放入移位寄存器,,每来一个时钟,,整个寄存器移一位

一个时钟周期,,spi传一个bit


作为从机的时候,,nss就是一个片选信号

多主机模式 :

spi总线有多个主机

主机1通过普通IO向 主机2 的NSS输入低电压,,, 主机2就变成了从机,,,,

多主机模式,,,nss被拉低时,,主机身份变成从机,,,

nss必须是高电压,,,否则主机身份就会丢失,,,,,,,


主机每发送一个比特位,,必然会从从机收到一个比特位

#include"stm32f10x.h"#include"delay.h"#include"led.h"#include"key.h"#include"buzzer.h"#include"lightSensor.h"#include"stdio.h"#include"oled.h"#include"counterSensor.h"#include"encoder.h"#include"timer.h"#include"pwm.h"#include"inputCapture.h"#include"ad.h"// direct memory access#include"mydma.h"uint16_tad_value[4];voidapp_onBoardLed_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitTypeDef GPIO_initStruct={0};// 操控引脚GPIO_initStruct.GPIO_Pin=GPIO_Pin_13;GPIO_initStruct.GPIO_Mode=GPIO_Mode_Out_OD;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_Speed=GPIO_Speed_50MHz;// 初始化IO引脚,,GPIO_Init(GPIOC,&GPIO_initStruct);}// general purpose input outputvoidapp_GPIOA_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_initStruct={0};// 操控引脚GPIO_initStruct.GPIO_Pin=GPIO_Pin_5|GPIO_Pin_7;GPIO_initStruct.GPIO_Mode=GPIO_Mode_AF_PP;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_Speed=GPIO_Speed_50MHz;// 初始化IO引脚,,GPIO_Init(GPIOA,&GPIO_initStruct);GPIO_initStruct.GPIO_Pin=GPIO_Pin_6;GPIO_initStruct.GPIO_Mode=GPIO_Mode_IPU;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_Speed=GPIO_Speed_50MHz;// 初始化IO引脚,,GPIO_Init(GPIOA,&GPIO_initStruct);GPIO_initStruct.GPIO_Pin=GPIO_Pin_4;GPIO_initStruct.GPIO_Mode=GPIO_Mode_Out_PP;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_Speed=GPIO_Speed_50MHz;// 初始化IO引脚,,GPIO_Init(GPIOA,&GPIO_initStruct);}voidapp_spi1_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);SPI_InitTypeDef spi_initStruct;// 是master 还是 从机spi_initStruct.SPI_Mode=SPI_Mode_Master;spi_initStruct.SPI_Direction=SPI_Direction_2Lines_FullDuplex;spi_initStruct.SPI_DataSize=SPI_DataSize_8b;spi_initStruct.SPI_CPOL=SPI_CPOL_High;spi_initStruct.SPI_CPHA=SPI_CPHA_2Edge;spi_initStruct.SPI_FirstBit=SPI_FirstBit_MSB;spi_initStruct.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_64;// 软件控制nss,,nss必须为1才是主机,,否则是从机spi_initStruct.SPI_NSS=SPI_NSS_Soft;SPI_Init(SPI1,&spi_initStruct);// nss设置为1SPI_NSSInternalSoftwareConfig(SPI1,SPI_NSSInternalSoft_Set);}voidapp_spi_masterTransmitReceive(SPI_TypeDef*spix,constuint8_t*pDataTx,uint8_t*pDataRx,uint16_tsize){// 闭合总开关SPI_Cmd(spix,ENABLE);SPI_I2S_SendData(spix,pDataTx[0]);for(uint16_ti=0;i<size-1;i++){while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_TXE)==RESET);SPI_I2S_SendData(spix,pDataTx[i+1]);// 读取数据while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE)==RESET);pDataRx[i]=SPI_I2S_ReceiveData(spix);}// 读取最后一个字节的数据while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE)==RESET);pDataRx[size-1]=SPI_I2S_ReceiveData(spix);// 断开总开关SPI_Cmd(spix,DISABLE);}// 常规序列,, 单通道intmain(void){OLED_Init();while(1){}}

64Mbit ⇒ 8M byte ,

一个扇区
一个扇区划分多个 页 ,,,一个页,,,256byte


flash不能直接改 0 —》 1.,,,, 而擦除操作通常是大范围的,,,所以必须划分,,页,扇区,块

w25Q64: 属于 SPI NOR flash ,,, 外部nor flash,,

  • 页 page : 最小编程单位,,写入单位
    不可能一次性写入全部数据,,写一段就会停一下,写一段会停一下,,这里的一段就是一个页page,,,写的最小单位
  • 扇区 :4kb,,, 最常用的擦除单位,, 擦除速度还行,浪费空间小

flash来说,,安全性最重要,,,flash中有一把锁,,,将锁打开,,,,
扇区擦除 和 页编程 都需要时间,,,,


主机发 0x05, 回收到 状态寄存器1,, 8个bit位,,随后一位就是busy,,是否不空闲


spi协议只规定:

  • 时钟怎么走
  • bit怎么转
  • 什么时候采样

sck
mosi
miso
cpol
cpha
msb
一拍传1bit

spi只是规定 双方同时交换bit,,不是,双方数据必须相同


spi协议 底层
w25Q64通信协议 是上层

w25Q64规定,,收到什么之后,,后面怎么解释,,是返回寄存器值,,还是清除扇区

#include"stm32f10x.h"#include"delay.h"#include"led.h"#include"key.h"#include"buzzer.h"#include"lightSensor.h"#include"stdio.h"#include"oled.h"#include"counterSensor.h"#include"encoder.h"#include"timer.h"#include"pwm.h"#include"inputCapture.h"#include"ad.h"// direct memory access#include"mydma.h"uint16_tad_value[4];voidapp_onBoardLed_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitTypeDef GPIO_initStruct={0};// 操控引脚GPIO_initStruct.GPIO_Pin=GPIO_Pin_13;GPIO_initStruct.GPIO_Mode=GPIO_Mode_Out_OD;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_Speed=GPIO_Speed_50MHz;// 初始化IO引脚,,GPIO_Init(GPIOC,&GPIO_initStruct);}// general purpose input outputvoidapp_GPIOA_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_initStruct={0};// 操控引脚GPIO_initStruct.GPIO_Pin=GPIO_Pin_5|GPIO_Pin_7;GPIO_initStruct.GPIO_Mode=GPIO_Mode_AF_PP;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_Speed=GPIO_Speed_50MHz;// 初始化IO引脚,,GPIO_Init(GPIOA,&GPIO_initStruct);GPIO_initStruct.GPIO_Pin=GPIO_Pin_6;GPIO_initStruct.GPIO_Mode=GPIO_Mode_IPU;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_Speed=GPIO_Speed_50MHz;// 初始化IO引脚,,GPIO_Init(GPIOA,&GPIO_initStruct);// 对片选引脚,,写一个高电压,,, 后面变成低电压了,,才是读取从机GPIO_initStruct.GPIO_Pin=GPIO_Pin_4;GPIO_initStruct.GPIO_Mode=GPIO_Mode_Out_PP;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_Speed=GPIO_Speed_50MHz;// 初始化IO引脚,,GPIO_Init(GPIOA,&GPIO_initStruct);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);}voidapp_spi1_init(void){app_GPIOA_init();RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);SPI_InitTypeDef spi_initStruct;// 是master 还是 从机spi_initStruct.SPI_Mode=SPI_Mode_Master;spi_initStruct.SPI_Direction=SPI_Direction_2Lines_FullDuplex;spi_initStruct.SPI_DataSize=SPI_DataSize_8b;spi_initStruct.SPI_CPOL=SPI_CPOL_Low;spi_initStruct.SPI_CPHA=SPI_CPHA_1Edge;spi_initStruct.SPI_FirstBit=SPI_FirstBit_MSB;spi_initStruct.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_64;// 软件控制nss,,nss必须为1才是主机,,否则是从机spi_initStruct.SPI_NSS=SPI_NSS_Soft;SPI_Init(SPI1,&spi_initStruct);// nss设置为1SPI_NSSInternalSoftwareConfig(SPI1,SPI_NSSInternalSoft_Set);SPI_Cmd(SPI1,ENABLE);}voidapp_spi_masterTransmitReceive(SPI_TypeDef*spix,constuint8_t*pDataTx,uint8_t*pDataRx,uint16_tsize){// // 闭合总开关// SPI_Cmd(spix,ENABLE);SPI_I2S_SendData(spix,pDataTx[0]);for(uint16_ti=0;i<size-1;i++){while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_TXE)==RESET);SPI_I2S_SendData(spix,pDataTx[i+1]);// 读取数据while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE)==RESET);pDataRx[i]=SPI_I2S_ReceiveData(spix);}// 读取最后一个字节的数据while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE)==RESET);pDataRx[size-1]=SPI_I2S_ReceiveData(spix);// // 断开总开关// SPI_Cmd(spix,DISABLE);}voidapp_w25Q64_saveByte(uint8_tbyte){uint8_tbuffer[10];buffer[0]=0x06;// 写使能 spi发送 0x06 ===》 write enable ,,,// 一旦真正执行写或者擦除后,,会自动关闭写使能,,,后面还需要重新打开GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 扇区擦除 0x20 + 24位地址buffer[0]=0x20;buffer[1]=0x00;buffer[2]=0x00;buffer[3]=0x00;GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);app_spi_masterTransmitReceive(SPI1,buffer,buffer,4);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 等待空闲while(1){buffer[0]=0x05;// 激活从机GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);// 发送0x05获取寄存器数据 ===> 不同命令后面跟的数据格式不同,,是协议app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);// 获取寄存器的值buffer[0]=0xff;app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 不忙 ===》 清除扇区结束了if((buffer[0]&0x01)==0){break;}}// 写使能 ===》 防止误写,每次写操作前,必须明确授权一次GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);buffer[0]=0x06;app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 页编程 0x02 写数据 + 24位地址 ===》 数据要写到哪个位置GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);buffer[0]=0x02;buffer[1]=0x00;buffer[2]=0x00;buffer[3]=0x00;buffer[4]=byte;app_spi_masterTransmitReceive(SPI1,buffer,buffer,5);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 等待空闲while(1){buffer[0]=0x05;// 激活从机GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);// 发送0x05获取寄存器数据 ===> 不同命令后面跟的数据格式不同,,是协议app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);// 获取寄存器的值buffer[0]=0xff;app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 不忙 ===》 清除扇区结束了if((buffer[0]&0x01)==0){break;}}}uint8_tapp_w25Q64_loadByte(void){uint8_tbuffer[10];buffer[0]=0x03;buffer[1]=0x00;buffer[2]=0x00;buffer[3]=0x00;// 0x03读取数据GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);app_spi_masterTransmitReceive(SPI1,buffer,buffer,4);// 接受一个字节buffer[0]=0xff;app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);returnbuffer[0];}voidtest_w25q64_id(void){uint8_ttx[4];uint8_trx[4];tx[0]=0x9F;tx[1]=0xFF;tx[2]=0xFF;tx[3]=0xFF;GPIO_ResetBits(GPIOA,GPIO_Pin_4);app_spi_masterTransmitReceive(SPI1,tx,rx,4);GPIO_SetBits(GPIOA,GPIO_Pin_4);OLED_ShowHexNum(1,1,rx[1],2);OLED_ShowHexNum(2,1,rx[2],2);OLED_ShowHexNum(3,1,rx[3],2);}uint8_ta=0;// 常规序列,, 单通道intmain(void){OLED_Init();OLED_ShowString(1,1,"hehe:");app_spi1_init();// test_w25q64_id();app_w25Q64_saveByte(0x12);a=app_w25Q64_loadByte();OLED_ShowHexNum(2,1,(uint32_t)a,4);while(1){}}
http://www.jsqmd.com/news/913536/

相关文章:

  • 实时库存准确率从82%跃升至99.6%,Lindy自动化配置清单,含7个不可跳过的校验节点
  • 别再傻傻分不清了!Unity编辑器开发中EditorWindow、Editor、PropertyDrawer到底怎么选?
  • 电路设计实战:从元器件选型到PCB制作与调试全流程解析
  • 用遗传算法自动找LQR最优Q和R矩阵,MATLAB一键跑通闭环仿真
  • Arduino实时时钟RTC模块DS3231应用指南:从硬件连接到代码实现
  • 智驱监管 无感赋能|黎阳之光人员无感技术升级海关旅检模式
  • 揭秘Anthropic最新融资路演PPT:8个被刻意隐藏的数据陷阱,90%技术决策者已踩坑
  • 免费在线3D查看器终极指南:浏览器中轻松预览和测量任何3D设计文件
  • 告别CAN总线8字节限制:手把手教你用AUTOSAR CanTp模块搞定ISO 15765长报文传输
  • 基于Arduino与多传感器的手语翻译手套:从硬件搭建到算法实现
  • STM32F103用W5500直连OneNet做远程温控与继电器开关,带全套KEIL工程和驱动源码
  • Anthropic CLI(Claude Code)启动报错 422 完整解决办法
  • WindowResizer技术指南:使用Windows API实现窗口强制调整的完整解决方案
  • 【语音】笔记
  • 保姆级教程:用MIM搞定MMSegmentation v1.1.0 + MMCV 2.0.0rc4的完整安装流程(附CUDA 11.1环境检查)
  • 明穆宗 朱载坖
  • MindSpore Transformers 断点续训功能原理
  • 旅游管理毕设实战包:SpringBoot后端+Vue前端,含可运行源码、万字论文文档、部署教程与答辩PPT
  • 双FA自动耦合:从技术原理到量产效能飞跃
  • 儿童电动车辅助开关与PVC支撑框架改装指南:为特殊需求儿童打造专属座驾
  • 为什么我的频谱图纵坐标是负的?从dB/Hz单位聊聊信号处理中的对数变换
  • Claude用户手册制作(含可复用的Figma交互原型+Notion自动化工作流)
  • 安达发|电线电缆行业aps自动排产:从人工排程之困到智能驱动之变
  • sd卡的照片在电脑上删除之后能还原吗,介绍6种恢复技巧和视频演示,让你的数据轻松找回!
  • 视频教程|云端CAE实战 —— HyperMesh 管道配件仿真前处理
  • Claude产品需求文档实战模板(含可下载Figma+Notion双版本)
  • Linux 文件权限超详细详解(读懂权限标识、数字权限、特殊权限、chmod/chown)
  • MongoDB副本集配置
  • 2026年广东数据中心建设正当时,这些宝藏建设公司不容错过!
  • 《冰雪重制版》热血 165/166 区开服公告 福利活动指南