STM32 SPI

目录

软件SPI读写W25Q64

硬件SPI读写W25Q64


CPHA为时钟相位,决定是第几个边沿采样,并不是规定上升沿采样还是下降沿采样(还要看CPOL极性选择)。

W25Q64框图

软件SPI读写W25Q64

MySPI.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header

/**
  * @brief  对选线进行写操作
  * @param  BitValue:对选线进行选中或不选(低电平有效)
  * @retval 无
  */
void MySPI_W_SS(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}

/**
  * @brief  对SCK时钟线进行写操作
  * @param  BitValue:写入高低电平
  * @retval 无
  */
void MySPI_W_SCK(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);
}

/**
  * @brief  对MOSI进行写操作(主机为输出)
  * @param  BitValue:输出的数据位
  * @retval 无
  */
void MySPI_W_MOSI(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}

/**
  * @brief  对从机进行读操作
  * @param  无
  * @retval 读到的数据
  */
uint8_t MySPI_R_MISO(void)
{
    return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}

/**
  * @brief  初始化SPI及其通信引脚
  * @param  无
  * @retval 无
  */
void MySPI_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//输出使用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//输入使用上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    
    MySPI_W_SS(1);//将SS初始化为高电平,不选中从机
    //使用模式0
    MySPI_W_SCK(0);//默认为低电平
}

/**
  * @brief  生成SPI起始条件
  * @param  无
  * @retval 无
  */
void MySPI_Start(void)
{
    MySPI_W_SS(0);//起始条件
}

/**
  * @brief  生成SPI结束条件
  * @param  无
  * @retval 无
  */
void MySPI_Stop(void)
{
    MySPI_W_SS(1);//结束条件
}

/**
  * @brief  使用SPI协议交换一个字节
  * @param  ByteSend:发送的字节
  * @retval 交换得到的字节
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    uint8_t i,ByteReceive = 0x00;
    
    for(i = 0;i < 8;i ++)
    {
        MySPI_W_MOSI(ByteSend & (0x80 >> i));//主机移出数据
        MySPI_W_SCK(1);//拉高进行读取
        if(MySPI_R_MISO() == 1)//主机接收从机发出的数据
            {
                ByteReceive |= (0x80 >> i);
            }
        MySPI_W_SCK(0);//拉低进行移位
    }
    return ByteReceive;
}

//uint8_t MySPI_SwapByte(uint8_t ByteSend)
//{
//    for(i = 0;i < 8;i ++)
//    {
//        MySPI_W_MOSI(ByteSend & 0x80);//主机移出最高位数据
//        ByteSend << 1;  
//        MySPI_W_SCK(1);//拉高进行读取
//        if(MySPI_R_MISO() == 1)//主机接收从机发出的数据
//            {
//                ByteSend |= 0x01;//将读入的直接放在低位
//            }
//        MySPI_W_SCK(0);//拉低进行移位
//    }
//    return ByteSend;
//}

W25Q64.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

/**
  * @brief  初始化SPI协议及引脚
  * @param  无
  * @retval 无
  */
void W25Q64_Init(void)
{
    MySPI_Init();
}

/**
  * @brief  读取厂商以及设备ID
  * @param  MID:厂商ID
  * @param  DID:设备ID
  * @retval 无
  */
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{
    MySPI_Start();//开启SPI时序
    MySPI_SwapByte(W25Q64_JEDEC_ID);//0x9F命令字下一次交换返回厂商以及设备ID
    *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//交换出来的是厂商ID
    *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//交换出来的是设备ID的高八位
    *DID <<= 8;//将其移至高八位
    *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);//交换出来的是设备ID的低八位并进行合并
    MySPI_Stop();//结束SPI时序
}

/**
  * @brief  完成写使能时序
  * @param  无
  * @retval 无
  */
void W25Q64_WriteEnable(void)
{
     MySPI_Start();//开启SPI时序
     MySPI_SwapByte(W25Q64_WRITE_ENABLE);//发送写使能指令
     MySPI_Stop();//结束SPI时序
}

/**
  * @brief  等待状态寄存器Busy位置0
  * @param  无
  * @retval 无
  */
void W25Q64_WaitBusy(void)
{
    uint32_t Timeout;
    MySPI_Start();//开启SPI时序
    MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//发送读状态寄存器1指令
    Timeout = 100000;
    while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)//返回回来为寄存器1状态
    {//接收数据Busy位并进行判断,若是Busy则进行定时退出
        Timeout--;
        if(Timeout == 0)
        {
           break;
        }
    }
    MySPI_Stop();//结束SPI时序
}

/**
  * @brief  拼接时序使用页编程(0~256KB)
  * @param  Address:写入的地址
  * @param  DataArray:写入的数组
  * @param  Count:写入数据个数
  * @retval 无
  */
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t Count)
{
    W25Q64_WriteEnable();//写入操作需先进行写使能
    
    uint16_t i;
    MySPI_Start();//开启SPI时序
    MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//发送页编程指令
    //需要传输24位地址,需要六个字节,这里一次只能发两个字节(8位数据)
    MySPI_SwapByte(Address >> 16);//高两位字节先发送
    MySPI_SwapByte(Address >> 8);//中间两位(高位舍弃)
    MySPI_SwapByte(Address);//最后两位
    for(i = 0;i < Count;i ++)
    {
         MySPI_SwapByte(DataArray[i]);//发送写入数据
    }
    MySPI_Stop();//结束SPI时序
    
    W25Q64_WaitBusy();//进行事后等待(写入后芯片进入忙状态)
    //事后最保险,事前效率高
}

/**
  * @brief  指定地址擦除扇区时序(4KB)
  * @param  Address:擦除的位置地址
  * @retval 无
  */
void W25Q64_SectorErase(uint32_t Address)
{
    W25Q64_WriteEnable();//写入操作需先进行写使能
    
    MySPI_Start();//开启SPI时序
    MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);//发送扇区擦除指令(4KB)
    //需要传输24位地址,需要六个字节,这里一次只能发两个字节(8位数据)
    MySPI_SwapByte(Address >> 16);//高两位字节先发送
    MySPI_SwapByte(Address >> 8);//中间两位(高位舍弃)
    MySPI_SwapByte(Address);//最后两位
    MySPI_Stop();//结束SPI时序
    
    W25Q64_WaitBusy();//进行事后等待(写入后芯片进入忙状态)
    //事后最保险,事前效率高
}

/**
  * @brief  指定地址读取数据时序
  * @param  Address:读取的位置地址
  * @param  DataArray:读取的数组
  * @param  Count:读取数据个数
  */
void W25Q64_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t Count)
{
    uint32_t i;
    MySPI_Start();//开启SPI时序
    MySPI_SwapByte(W25Q64_READ_DATA);//发送读取数据指令
    //需要传输24位地址,需要六个字节,这里一次只能发两个字节(8位数据)
    MySPI_SwapByte(Address >> 16);//高两位字节先发送
    MySPI_SwapByte(Address >> 8);//中间两位(高位舍弃)
    MySPI_SwapByte(Address);//最后两位
    for(i = 0;i < Count;i ++)
    {
         DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//返回又有的数据
    }
    MySPI_Stop();//结束SPI时序
}

W25Q64_Ins.h

cpp 复制代码
#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3

#define W25Q64_DUMMY_BYTE							0xFF

#endif

main.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[] = {0x55,0x66,0x77,0x88};
uint8_t ArrayRead[4];

int main(void)
{
	OLED_Init();
	
	W25Q64_Init();
    
    OLED_ShowString(1,1,"MID:   DID:");
    OLED_ShowString(2,1,"W:");
    OLED_ShowString(3,1,"R:");
    
    W25Q64_ReadID(&MID,&DID); 
    OLED_ShowHexNum(1,5,MID,2);
    OLED_ShowHexNum(1,12,DID,4);
    
    W25Q64_SectorErase(0x000000);
    //写入数据前先要擦除扇区否则写入数据可能出错(擦除后数据为0xFF)
    W25Q64_PageProgram(0x0000FF,ArrayWrite,4);
    //将数据写入到扇区(只有1->0,没有0->1)
    W25Q64_ReadData(0x0000FF,ArrayRead,4);
    //读出指定地址的数据到数组(不能进行跨页写入数据,可以跨页读)
    
    OLED_ShowHexNum(2,3,ArrayWrite[0],2);
    OLED_ShowHexNum(2,6,ArrayWrite[1],2);
    OLED_ShowHexNum(2,9,ArrayWrite[2],2);
    OLED_ShowHexNum(2,12,ArrayWrite[3],2);
    
    OLED_ShowHexNum(3,3,ArrayRead[0],2);
    OLED_ShowHexNum(3,6,ArrayRead[1],2);
    OLED_ShowHexNum(3,9,ArrayRead[2],2);
    OLED_ShowHexNum(3,12,ArrayRead[3],2);
	while(1)
	{
			
	}
	
}
硬件SPI读写W25Q64

MySPI.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header

/**
  * @brief  对选线进行写操作
  * @param  BitValue:对选线进行选中或不选(低电平有效)
  * @retval 无
  */
void MySPI_W_SS(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}



/**
  * @brief  初始化SPI及其通信引脚
  * @param  无
  * @retval 无
  */
void MySPI_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//打开SPI1外设时钟
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//SS使用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//SCK,MOSI输出使用复用推挽输出(使用了引脚的复用功能)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//MISO输入使用上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    
    SPI_InitTypeDef SPI_InitStructure;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//SPI模式(主机/从机)
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//SPI裁剪引脚功能(双线全双工)
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//配置数据帧(8位)
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//配置高位先行还是低位先行(高位先行)
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//配置SCK频率(72MHz / 128)
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//时钟极性(默认高电平)配置模式0
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//对第几个边沿进行采样(从第一个边沿进行采样)
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//软件NSS
    SPI_InitStructure.SPI_CRCPolynomial = 7;//配置CRC校验多项式
    SPI_Init(SPI1,&SPI_InitStructure);
    
    SPI_Cmd(SPI1,ENABLE);//开启硬件SPI1
    
    MySPI_W_SS(1);//默认高电平
}

/**
  * @brief  生成SPI起始条件
  * @param  无
  * @retval 无
  */
void MySPI_Start(void)
{
    MySPI_W_SS(0);//起始条件
}

/**
  * @brief  生成SPI结束条件
  * @param  无
  * @retval 无
  */
void MySPI_Stop(void)
{
    MySPI_W_SS(1);//结束条件
}

/**
  * @brief  使用SPI协议交换一个字节
  * @param  ByteSend:发送的字节
  * @retval 交换得到的字节
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) != SET);//等待TXE为1(为空)
    
    SPI_I2S_SendData(SPI1,ByteSend);//将数据写入到TDR,ByteSend转入移位寄存器
    
    while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) != SET);//等待RXNE为1,表示接收到一个字节
    
    return SPI_I2S_ReceiveData(SPI1);//发送完成读取RDR接收到的数据
}

其他文件并未修改,与之前一样,效果与软件SPI一致。

相关推荐
杰克逊的日记9 天前
MCU编程
单片机·嵌入式硬件
Python小老六9 天前
单片机测ntc热敏电阻的几种方法(软件)
数据库·单片机·嵌入式硬件
懒惰的bit9 天前
STM32F103C8T6 学习笔记摘要(四)
笔记·stm32·学习
HX科技9 天前
STM32给FPGA的外挂FLASH进行升级
stm32·嵌入式硬件·fpga开发·flash·fpga升级
Suagrhaha9 天前
驱动入门的进一步深入
linux·嵌入式硬件·驱动
国科安芯9 天前
基于ASP4644多通道降压技术在电力监测系统中集成应用与发展前景
嵌入式硬件·硬件架构·硬件工程
Li Zi9 天前
STM32 ADC(DMA)双缓冲采集+串口USART(DMA)直接传输12位原始数据到上位机显示并保存WAV格式音频文件 收藏住绝对实用!!!
经验分享·stm32·单片机·嵌入式硬件
进击的程序汪9 天前
触摸屏(典型 I2C + Input 子系统设备)从设备树解析到触摸事件上报
linux·网络·嵌入式硬件
damo王10 天前
Zephyr 系统深入解析:SoC 支持包结构与中断调度器调优实践
单片机·嵌入式硬件·zephyr
逼子格10 天前
硬件工程师笔试面试高频考点汇总——(2025版)
单片机·嵌入式硬件·面试·硬件工程·硬件工程师·硬件工程师真题·硬件工程师面试