news 2026/5/26 7:56:55

江科大STM32笔记-SPI

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
江科大STM32笔记-SPI
概念
  • SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
  • 四根通信线:
    • SCK(Serial Clock):时钟线
    • MOSI(Master Output Slave Input):主机输出、从机输入,是主机发送信号的线
    • MISO(Master Input Slave Output):从机输出、主机输入,是主机接收信号的线
    • SS(Slave Select):给每个从机都开辟一条SS,给低电平表示要与你通信
  • 同步,全双工
  • 支持总线挂载多设备(一主多从)

推挽输出,所以最大速率更大

原理实现:基于移位寄存器:

  1. 原始状态

  1. 时钟上升沿来了,进行移位,移出去的位放在通信线上

  1. 下降沿到了,两边对通信线进行采样,输入到各自的最低位

时序逻辑

在外设从机通常有定义一个指令集,主机发送指令码来对从机进行操作:

W25Q64简介

块-扇区-页

先把写入的数据放在页缓冲区(RAM),再有RAM慢慢写到Flash里面

SPI硬件外设
  • STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
  • 可配置8位/16位数据帧、高位先行/低位先行
  • 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
  • 支持多主机模型、主或从操作
  • 可精简为半双工/单工通信
  • 支持DMA
  • 兼容I2S协议
  • STM32F103C8T6 硬件SPI资源:SPI1、SPI2

LSBFIRST:控制高位先行还是地位先行

RDR:存接收到的值

TDR:存要发送的值

TXE:标志位,表示TDR为空,一般就是TDR的值已经放入移位寄存器了

RXNE:标志位,表示RDR非空,一般就是表示把移位寄存器接收到的值已经放入RDR了,这时应该尽快读走RDR

分四步:

  1. 等待TXE为1
  2. 写入发送的数据至TDR
  3. 等待RXNE为1(即等待发送和接收完成)
  4. 读取RDR接收的数据
代码
  1. 软件SPI
void SPI_Software_Init(void) //软件SPI初始化 { //1. GPIOA时钟使能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //2. GPIOA配置 PA4-SS PA5-SCK PA6-MISO PA7-MOSI GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; //PA4,PA5,PA7 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //PA6 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //3. 初始化引脚状态 Software_SPI_W_SS(1); //SS拉高 Software_SPI_W_SCK(0); //SCK拉低 }
void Software_SPI_W_SS(uint8_t BitValue) //对SS引脚进行电平控制 { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //PA4-SS } void Software_SPI_W_SCK(uint8_t BitValue) //对SCK引脚进行电平控制 { GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); //PA5-SCK } void Software_SPI_W_MOSI(uint8_t BitValue) //对MOSI引脚进行电平控制 { GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); //PA7-MOSI } uint8_t Software_SPI_R_MISO(void) //读取MISO引脚电平状态 { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); //PA6-MISO }
void Software_SPI_Start(void) //启动SPI传输 { Software_SPI_W_SS(0); //拉低SS,启动SPI传输 } void Software_SPI_Stop(void) //停止SPI传输 { Software_SPI_W_SS(1); //拉高SS,停止SPI传输 } //使用模式0,CPOL=0,CPHA=0 uint8_t Software_SPI_SwapByte(uint8_t ByteSend) //软件SPI发送并接收一个字节数据,ByteSend:表示移位寄存器的值 { uint8_t i; for(i = 0;i<8;i++) { Software_SPI_W_MOSI(ByteSend & 0x80); //发送数据,高位先行 ByteSend <<= 1; //数据左移一位,准备发送下一位 Software_SPI_W_SCK(1); //拉高时钟,产生上升沿 if(Software_SPI_R_MISO()) //读取MISO引脚状态 { ByteSend |= 0x01; //接收数据,最低位置1 } Software_SPI_W_SCK(0); //拉低时钟,准备下一位 } return ByteSend; //返回接收到的数据 }
//初始化W25Q64芯片 void Software_W25Q64_Init(void) { //初始化软件SPI SPI_Software_Init(); } //读取W25Q64芯片ID,MID:制造商ID指针,DID:设备ID指针 void Software_W25Q64_ReadID(uint8_t *MID, uint16_t *DID) { Software_SPI_Start(); //启动SPI传输 Software_SPI_SwapByte(W25Q64_JEDEC_ID); //发送读取ID命令 *MID = Software_SPI_SwapByte(W25Q64_DUMMY_BYTE); //读取制造商ID *DID = Software_SPI_SwapByte(W25Q64_DUMMY_BYTE)<<8; //读取设备ID高8位 *DID |= Software_SPI_SwapByte(W25Q64_DUMMY_BYTE); //读取设备ID低8位 Software_SPI_Stop(); //停止SPI传输 } //写使能 void Software_W25Q64_WriteEnable(void) / { Software_SPI_Start(); //启动SPI传输 Software_SPI_SwapByte(W25Q64_WRITE_ENABLE); //发送写使能命令 Software_SPI_Stop(); //停止SPI传输 } //等待W25Q64芯片忙标志位清除 void Software_W25Q64_WaitBusy(void) { uint32_t Timeout = 100000; uint8_t Status = 0; Software_SPI_Start(); //启动SPI传输 Software_SPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //发送读取状态寄存器1命令 do { Status = Software_SPI_SwapByte(W25Q64_DUMMY_BYTE); //读取状态寄存器1的值 Timeout--; if(Timeout == 0) break; //超时退出 }while(Status & 0x01); //等待忙标志位清除 Software_SPI_Stop(); //停止SPI传输 } //页编程,Address:目标地址,DataArray:数据数组指针,Length:数据长度,最大256字节 void Software_W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Length) { Software_W25Q64_WriteEnable(); //写使能 Software_SPI_Start(); //启动SPI传输 Software_SPI_SwapByte(W25Q64_PAGE_PROGRAM); //发送页编程命令 Software_SPI_SwapByte((Address >> 16) & 0xFF); //发送地址高8位 Software_SPI_SwapByte((Address >> 8) & 0xFF); //发送地址中8位 Software_SPI_SwapByte(Address & 0xFF); //发送地址低8位 for(uint16_t i=0; i<Length; i++) { Software_SPI_SwapByte(DataArray[i]); //发送数据 } Software_SPI_Stop(); //停止SPI传输 Software_W25Q64_WaitBusy(); //事后等待写入完成 } //扇区擦除,Address:目标地址 void Software_W25Q64_SectorErase(uint32_t Address) { Software_W25Q64_WriteEnable(); //写使能 Software_SPI_Start(); //启动SPI传输 Software_SPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //发送扇区擦除命令 Software_SPI_SwapByte((Address >> 16) & 0xFF); //发送地址高8位 Software_SPI_SwapByte((Address >> 8) & 0xFF); //发送地址中8位 Software_SPI_SwapByte(Address & 0xFF); //发送地址低8位 Software_SPI_Stop(); //停止SPI传输 Software_W25Q64_WaitBusy(); //事后等待擦除完成 } //读取数据,Address:目标地址,DataArray:数据数组指针,Length:数据长度 void Software_W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Length) { Software_SPI_Start(); //启动SPI传输 Software_SPI_SwapByte(W25Q64_READ_DATA); //发送读取数据命令 Software_SPI_SwapByte((Address >> 16) & 0xFF); //发送地址高8位 Software_SPI_SwapByte((Address >> 8) & 0xFF); //发送地址中8位 Software_SPI_SwapByte(Address & 0xFF); //发送地址低8位 for(uint32_t i=0; i<Length; i++) { DataArray[i] = Software_SPI_SwapByte(W25Q64_DUMMY_BYTE); //读取数据 } Software_SPI_Stop(); //停止SPI传输 }
  1. 硬件SPI
void Hardware_SPI_Init(void) //硬件SPI初始化 { //1. SPI1和GPIOA时钟使能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE); //2. GPIOA配置 PA4-SS PA5-SPI1_SCK PA6-SPI1_MISO PA7-SPI1_MOSI GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; //PA5,PA7 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //PA6 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //3. SPI1配置 SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主模式 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线全双工 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位数据帧格式 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位先行 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率预分频128 //SPI模式 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟悬空低 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //第1个时钟沿捕获数据 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS软件管理 SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值7 SPI_Init(SPI1, &SPI_InitStructure); //4. 使能SPI1 SPI_Cmd(SPI1, ENABLE); //5. 初始化引脚状态 Hardware_SPI_W_SS(1); //SS拉高 }
void Hardware_SPI_W_SS(uint8_t BitValue) //对SS引脚进行电平控制 { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //PA4-SS }
void Hardware_SPI_Start(void) //启动SPI传输 { Hardware_SPI_W_SS(0); //拉低SS,启动SPI传输 } void Hardware_SPI_Stop(void) //停止SPI传输 { Hardware_SPI_W_SS(1); //拉高SS,停止SPI传输 } //使用模式0,CPOL=0,CPHA=0 uint8_t Hardware_SPI_SwapByte(uint8_t ByteSend) //硬件SPI发送并接收一个字节数据,ByteSend:表示移位寄存器的值 { //1.等待发送缓冲区空,即TXE变1 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); //2.发送数据,将数据写入发送缓冲区TDR SPI_I2S_SendData(SPI1, ByteSend); //发送数据 //3.等待接收缓冲区非空,即RXNE变1 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); //4.读取接收到的数据 return SPI_I2S_ReceiveData(SPI1); //返回接收到的数据 }
//初始化W25Q64芯片 void Hardware_W25Q64_Init(void) { //初始化硬件SPI Hardware_SPI_Init(); } //读取W25Q64芯片ID,MID:制造商ID指针,DID:设备ID指针 void Hardware_W25Q64_ReadID(uint8_t *MID, uint16_t *DID) { Hardware_SPI_Start(); //启动SPI传输 Hardware_SPI_SwapByte(W25Q64_JEDEC_ID); //发送读取ID命令 *MID = Hardware_SPI_SwapByte(W25Q64_DUMMY_BYTE); //读取制造商ID *DID = Hardware_SPI_SwapByte(W25Q64_DUMMY_BYTE)<<8; //读取设备ID高8位 *DID |= Hardware_SPI_SwapByte(W25Q64_DUMMY_BYTE); //读取设备ID低8位 Hardware_SPI_Stop(); //停止SPI传输 } //写使能 void Hardware_W25Q64_WriteEnable(void) { Hardware_SPI_Start(); //启动SPI传输 Hardware_SPI_SwapByte(W25Q64_WRITE_ENABLE); //发送写使能命令 Hardware_SPI_Stop(); //停止SPI传输 } //等待W25Q64芯片忙标志位清除 void Hardware_W25Q64_WaitBusy(void) { uint32_t Timeout = 100000; uint8_t Status = 0; Hardware_SPI_Start(); //启动SPI传输 Hardware_SPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //发送读取状态寄存器1命令 do { Status = Hardware_SPI_SwapByte(W25Q64_DUMMY_BYTE); //读取状态寄存器1的值 Timeout--; if(Timeout == 0) break; //超时退出 }while(Status & 0x01); //等待忙标志位清除 Hardware_SPI_Stop(); //停止SPI传输 } //页编程,Address:目标地址,DataArray:数据数组指针,Length:数据长度,最大256字节 void Hardware_W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Length) { Hardware_W25Q64_WriteEnable(); //写使能 Hardware_SPI_Start(); //启动SPI传输 Hardware_SPI_SwapByte(W25Q64_PAGE_PROGRAM); //发送页编程命令 Hardware_SPI_SwapByte((Address >> 16) & 0xFF); //发送地址高8位 Hardware_SPI_SwapByte((Address >> 8) & 0xFF); //发送地址中8位 Hardware_SPI_SwapByte(Address & 0xFF); //发送地址低8位 for(uint16_t i=0; i<Length; i++) { Hardware_SPI_SwapByte(DataArray[i]); //发送数据 } Hardware_SPI_Stop(); //停止SPI传输 Hardware_W25Q64_WaitBusy(); //事后等待写入完成 } //扇区擦除,Address:目标地址 void Hardware_W25Q64_SectorErase(uint32_t Address) { Hardware_W25Q64_WriteEnable(); //写使能 Hardware_SPI_Start(); //启动SPI传输 Hardware_SPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //发送扇区擦除命令 Hardware_SPI_SwapByte((Address >> 16) & 0xFF); //发送地址高8位 Hardware_SPI_SwapByte((Address >> 8) & 0xFF); //发送地址中8位 Hardware_SPI_SwapByte(Address & 0xFF); //发送地址低8位 Hardware_SPI_Stop(); //停止SPI传输 Hardware_W25Q64_WaitBusy(); //事后等待擦除完成 } //读取数据,Address:目标地址,DataArray:数据数组指针,Length:数据长度 void Hardware_W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Length) { Hardware_SPI_Start(); //启动SPI传输 Hardware_SPI_SwapByte(W25Q64_READ_DATA); //发送读取数据命令 Hardware_SPI_SwapByte((Address >> 16) & 0xFF); //发送地址高8位 Hardware_SPI_SwapByte((Address >> 8) & 0xFF); //发送地址中8位 Hardware_SPI_SwapByte(Address & 0xFF); //发送地址低8位 for(uint32_t i=0; i<Length; i++) { DataArray[i] = Hardware_SPI_SwapByte(W25Q64_DUMMY_BYTE); //读取数据 } Hardware_SPI_Stop(); //停止SPI传输 }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/26 6:50:37

中国行政区划数据实战手册:从零开始构建你的GIS项目

还在为找不到准确的中国行政区划数据而烦恼吗&#xff1f;这个项目为你提供了一套完整的四级行政区划Shapefile数据&#xff0c;从国家到区县一应俱全&#xff0c;让你轻松开启地理信息系统之旅&#xff01; 【免费下载链接】ChinaAdminDivisonSHP 项目地址: https://gitcod…

作者头像 李华
网站建设 2026/5/26 5:54:24

Comics Downloader:终极漫画批量下载神器完整指南

Comics Downloader&#xff1a;终极漫画批量下载神器完整指南 【免费下载链接】comics-downloader tool to download comics and manga in pdf/epub/cbr/cbz from a website 项目地址: https://gitcode.com/gh_mirrors/co/comics-downloader 还在为寻找心仪漫画资源而烦…

作者头像 李华
网站建设 2026/5/25 9:51:21

高校官宣!发一篇EI位同3区,这几本EI源刊最快一周审稿,沾边就录

部分学校目前已明确规定&#xff1a;发一篇EI期刊论文视同为SCI三区&#xff01;本期&#xff0c;小编给大家介绍几本目前进展较顺的EI期刊合集&#xff0c;适合时间紧张的学者&#xff0c;供各位投稿参考&#xff01; EI源刊——最快1周录用 【期刊概况】位于最新EI目录内 …

作者头像 李华
网站建设 2026/5/25 18:40:21

鸣潮智能辅助工具终极指南:3步实现游戏自动化,彻底解放双手

鸣潮智能辅助工具终极指南&#xff1a;3步实现游戏自动化&#xff0c;彻底解放双手 【免费下载链接】ok-wuthering-waves 鸣潮 后台自动战斗 自动刷声骸上锁合成 自动肉鸽 Automation for Wuthering Waves 项目地址: https://gitcode.com/GitHub_Trending/ok/ok-wuthering-wa…

作者头像 李华
网站建设 2026/5/26 5:40:36

Java计算机毕设之基于springboot的老人健康信息管理系统的设计与实现基于 SpringBoot 的社区智慧养老监护管理平台系统设计与实现(完整前后端代码+说明文档+LW,调试定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华