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一致。

相关推荐
scan17 小时前
单片机串口接收状态机STM32
stm32·单片机·串口·51·串口接收
Qingniu017 小时前
【青牛科技】应用方案 | RTC实时时钟芯片D8563和D1302
科技·单片机·嵌入式硬件·实时音视频·安防·工控·储能
Mortal_hhh8 小时前
VScode的C/C++点击转到定义,不是跳转定义而是跳转声明怎么办?(内附详细做法)
ide·vscode·stm32·编辑器
深圳市青牛科技实业有限公司9 小时前
【青牛科技】应用方案|D2587A高压大电流DC-DC
人工智能·科技·单片机·嵌入式硬件·机器人·安防监控
Mr.谢尔比10 小时前
电赛入门之软件stm32keil+cubemx
stm32·单片机·嵌入式硬件·mcu·信息与通信·信号处理
LightningJie10 小时前
STM32中ARR(自动重装寄存器)为什么要减1
stm32·单片机·嵌入式硬件
鹿屿二向箔10 小时前
STM32外设之SPI的介绍
stm32
西瓜籽@10 小时前
STM32——毕设基于单片机的多功能节能窗控制系统
stm32·单片机·课程设计
远翔调光芯片^1382879887213 小时前
远翔升压恒流芯片FP7209X与FP7209M什么区别?做以下应用市场摄影补光灯、便携灯、智能家居(调光)市场、太阳能、车灯、洗墙灯、舞台灯必看!
科技·单片机·智能家居·能源
极客小张14 小时前
基于STM32的智能充电桩:集成RTOS、MQTT与SQLite的先进管理系统设计思路
stm32·单片机·嵌入式硬件·mqtt·sqlite·毕业设计·智能充电桩