stm32标准库和HAL库的对比学习8.《学习spi通信协议,软件与硬件输出》

        本人是大一的学生,学习了一段时间的stm32,此系列博客为个人的学习笔记,方便个人复习,如有错误或问题,非常非常欢迎大家来大力指正。

     HAL库的知识参考keysking的教程,标准库参考江科大的教材。本视频的部分图也来自keysking和江科大视频中的图片,代码也是参考他们的大家如果要学习强烈推荐他们的视频

SPI有4条通讯线。SCK(SCLK,CLK,CK)串行时钟线。MOSI(DO)主出从入,MISO(DI)主入从出。SS(NSS,CS)从机选择。ss有几个从机接几条线.

起始条件:ss从高电平切换到低电平,低电平有效

终止条件:ss从低电平切换到高电平

当ss为高电平(未启动)输出口都为高阻态。

spi的本质就是在一个时序内互相同时交换数据,这次传输的指令下一次就会响应,对方也要用数据进行交换。

TXE是发送缓冲器空闲标志,1为空,0为非空

RXNE是接收缓冲器非空中断标志位,1为非空,0为空

而有两种模式

模式0在ss下拉时,已经将数据移出,数据通过发送指令+读写数据的模式来驱动从机

关于W25Q64的知识:

FLASH中由块组成,块又可以分为很多个扇区,扇区可以分为很多的页(256字节)

软件SPI的实现:

输出引脚为推挽输出(sck,MOSI,ss),输出引脚为推挽输出(sck,MOSI,ss),输入引脚为浮空或者上拉输入(MISO)

void spi_ss(uint8_t spiBit)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)spiBit);
}

void spi_sck(uint8_t spiBit)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)spiBit);
}

void spi_mosi(uint8_t spiBit)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)spiBit);
}

uint8_t spi_miso(void)
{
    return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}
void SOFTSPI_init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);		
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//输出引脚为推挽输出(sck,MOSI,ss),输入引脚为浮空或者上拉输入(MISO)
	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;//输出引脚为推挽输出(sck,MOSI,ss),输入引脚为浮空或者上拉输入(MISO)
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    spi_ss(1);//保持结束状态ss置高位
    spi_sck(0);//sck不输出
}

定义开始和结束

void spi_start()
{
    spi_ss(0);//ss下拉开始
}

void spi_stop()
{
    spi_ss(1);//上拉结束
}

因为spi实质上是数据的交换,所以就定义个交换函数

uint8_t spi_swap(uint8_t ByteSend)
{
    uint8_t i,ByteReceive=0x00;
    for ( i = 0; i < 8; i++)
    {
        spi_mosi(ByteSend & (0x80>>i));//传输自己寄存器的各个数值
        spi_sck(1);//上升沿读走
        if(spi_miso()==1)//如果接收到高电平
        {
            ByteReceive=ByteReceive|(0x80>>i);    //一个一个读取
        }
      spi_sck(0);
    }
 return ByteReceive;
}//各个模式改变一下相位和顺序就行

spi就定义好了

接下来写w25q64

读取ID,写使能,busy位判断

void w25q64READID(uint8_t *MID,uint16_t *DID)
{
	spi_start();
	spi_swap(W25Q64_JEDEC_ID);//首先传指令,告诉从机接下来(下一次)要传给我特定信息
	*MID=spi_swap(W25Q64_DUMMY_BYTE);
//这一次从机就发送特定信息,但是主机要拿信息给他换,所以可以给个FF就行
	
	*DID=spi_swap(W25Q64_DUMMY_BYTE);
	*DID=*DID<<8;//将did移动到高8位,然后将后面的数据读入
	*DID=*DID|spi_swap(W25Q64_DUMMY_BYTE);
	spi_stop();
}

void w25q64_writeenable()
{
	spi_start();
	spi_swap(W25Q64_WRITE_ENABLE);//开启写入使能,写入,删除都要开启一遍
	spi_stop();
}
void w25q64_waitBusy()
{
	spi_start();
	spi_swap(W25Q64_READ_STATUS_REGISTER_1);//读状态寄存器1
	while((spi_swap(W25Q64_DUMMY_BYTE) & 0x01)==0x01);	//最后一位是busy位,判断是否busy,是就将进程卡住
	spi_stop();
}

以下是功能函数

void w25q64_delect(uint32_t Address)//删除页
{
	spi_start();
	w25q64_writeenable();//涉及写入操作的开头要开写使能
	spi_swap(W25Q64_SECTOR_ERASE_4KB);//删除页4kb
	spi_swap(Address>>16);//第一次高24位
	spi_swap(Address>>8);//第二次高16位
	spi_swap(Address);//第三次低8位
	spi_stop();
	w25q64_waitBusy();
}

void w25q64_write(uint32_t Address,uint8_t *DataArray,uint16_t count)
{
	uint16_t i;
	//事前等待,不忙的时候执行以后的事,读和写都要放
	w25q64_writeenable();
	spi_start();
	spi_swap(W25Q64_PAGE_PROGRAM);//写页数据
	spi_swap(Address>>16);
	spi_swap(Address>>8);
	spi_swap(Address);
	for ( i = 0; i < count; i++)
	{
		spi_swap(DataArray[i]);
	}
	spi_stop();
	w25q64_waitBusy();//事后等待busy,不忙就退出,写入操作后写入
	
}
void w25q64_Read(uint32_t Address,uint8_t *DataArray,uint32_t count)//没有页的限制
{
	uint32_t i;
	spi_start();
	spi_swap(W25Q64_READ_DATA);//写页数据
	spi_swap(Address>>16);
	spi_swap(Address>>8);
	spi_swap(Address);
	for ( i = 0; i < count; i++)
	{
		DataArray[i]=spi_swap(W25Q64_DUMMY_BYTE);
	}
	spi_stop();
}

硬件的实现:

原理图

左上角是TDR和RDR寄存器(数据的传输),左下是分频设置。

右边是标志位寄存器和配置的寄存器,NSS脚是设计多主机的,我们一般一主机就继续用软件输出ss就行。

简化版:

有两种模式(非连续传输模式和连续传输,这里是非连续)

 根据时序写交换代码,,根据基本结构配置寄存器和引脚

和软件的区别主要是定义和交换函数:

void HARDSPI_init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//开启GPIOA的时钟
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
		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);						//将PA1和PA2引脚初始化为推挽输出

		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_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;//分频系数
		SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;//时钟极性,1边是0,2边是1
		SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;//着两个是配置模式的,采样输入边缘,low 0,hight 1

		SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;//数据帧
		SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;//高位or低位先行(高)
		SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//双线全双工
		SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;//我们使用软件模拟ss所以用软件模式
		SPI_InitStructure.SPI_CRCPolynomial=7;
		SPI_Init(SPI1,&SPI_InitStructure);
		
		SPI_Cmd(SPI1,ENABLE);
    spi_ss(1);
//    spi_sck(0);
}

交换函数就只要在读取标志位,然后传输完就接收

uint8_t spi_swap(uint8_t ByteSend)
{
		while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET);//等待TXE标志位置1(空)	,自动清除	
		SPI_I2S_SendData(SPI1,ByteSend);	
		while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET);//等待接收完成,RXNE有数据就是非空,就代表传输结束
		return SPI_I2S_ReceiveData(SPI1);
}

一开始,要发送寄存器空闲时才能发送,所以我们要在TXE为0(非空)时等待到TXE=1,然后就发送。

之后是要判断RXNE是否接收到数据,所以我们要在RXNE为0(空)时等待到RXNE=1,然后就读取

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值