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

STM32CubeMX学习笔记14 ---SPI总线

1. 简介

1.1 SPI总线介绍

        SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。

        SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。

SPI主从模式
        SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps

SPI信号线
SPI接口一般使用四条信号线通信:
SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)

  • MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
  • MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
  • SCLK:串行时钟信号,由主设备产生。
  • CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

硬件上为4根线。

SPI一对一

 SPI一对多

SPI数据发送接收
SPI主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。

首先拉低对应SS信号线,表示与该设备进行通信
主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据
这里要注意,SCLK时钟信号可能是低电平有效,也可能是高电平有效,因为SPI有四种模式,这个我们在下面会介绍
主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。
从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。

SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。

SPI工作模式
根据时钟极性(CPOL)及相位(CPHA)不同,SPI有四种工作模式。
时钟极性(CPOL)定义了时钟空闲状态电平:

  • CPOL=0为时钟空闲时为低电平
  • CPOL=1为时钟空闲时为高电平

时钟相位(CPHA)定义数据的采集时间。

  • CPHA=0:在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样。
  • CPHA=1:在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样。

1.2 W25QXX芯片介绍

W25QXX芯片是华邦公司推出的大容量SPI FLASH产品,该系列有W25Q16/32/62/128等。本例程使用W25Q64,W25Q64容量为64Mbits(8M字节):8MB的容量分为128个块(Block)(块大小为64KB),每个块又分为16个扇区(Sector)(扇区大小为4KB);W25Q64的最小擦除单位为一个扇区即4KB,因此在选择芯片的时候必须要有4K以上的SRAM(可以开辟4K的缓冲区)。W25Q64的擦写周期多达10万次,具有20年的数据保存期限。 下表是W25QXX的常用命令表

 

常用指令:

写使能(Write Enable) (06h)

向FLASH发送0x06 写使能命令即可开启写使能,首先CS片选拉低,控制写入字节函数写入命令,CS片选拉高。

扇区擦除指令(Sector Erase) (0x20h)

 

扇区擦除指令,数据写入前必须擦除对应的存储单元,该指令先拉低/CS引脚电平,接着传输“20H”指令和要24位要擦除扇区的地址。

读命令(Read Data) (03h)

读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。

 

状态读取命令(Read Status Register)

读状态寄存器1(05H),状态寄存器2(35H),状态寄存器3(15H)
写入命令0x05,即可读取状态寄存器的值。

 写入命令(Page Program) (02h)

 

在对W25Q128 FLASH的写入数据的操作中一定要先擦出扇区,在进行写入,否则将会发生数据错误。
W25Q128 FLASH一次性最大写入只有256个字节。
在进行写操作之前,一定要开启写使能(Write Enable)。
当只接收数据时不但能只检测RXNE状态 ,必须同时向发送缓冲区发送数据才能驱动SCK时钟跳变。

2. 硬件设计

LED2指示灯用来提示系统运行状态,S1按键用来控制W25Q64数据写入,S2按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息

  • LED2指示灯
  • S1和S2按键

  • USART1
  • SPI
  • W25Q64

3、STM32CubeMX设置

  • RCC设置外接HSE,时钟设置为72M
  • PE5(LED2)设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
  • PE3,PE4设置为GPIO输入模式、上拉模式
  • 激活SPI2,选择全双工主机模式

不使能硬件NSS
在这里插入图片描述

STM32有硬件NSS(片选信号),可以选择使能,也可以使用其他IO口接到芯片的NSS上进行代替
其中SIP2的片选NSS : SPI2_NSS(PB12)

如果片选引脚没有连接 SPI1_NSS(PA4)或者SPI2_NSS(PB12),则需要选择软件片选

NSS管脚及我们熟知的片选信号,作为主设备NSS管脚为高电平,从设备NSS管脚为低电平。当NSS管脚为低电平时,该spi设备被选中,可以和主设备进行通信。在stm32中,每个spi控制器的NSS信号引脚都具有两种功能,即输入和输出。所谓的输入就是NSS管脚的信号给自己。所谓的输出就是将NSS的信号送出去,给从机。
对于NSS的输入,又分为软件输入和硬件输入。
软件输入:
NSS分为内部管脚和外部管脚,通过设置spi_cr1寄存器的ssm位和ssi位都为1可以设置NSS管脚为软件输入模式且内部管脚提供的电平为高电平,其中SSM位为使能软件输入位。SSI位为设置内部管脚电平位。同理通过设置SSM和SSI位1和0则此时的NSS管脚为软件输入模式但内部管脚提供的电平为0。若从设备是一个其他的带有spi接口的芯片,并不能选择NSS管脚的方式,则可以有两种办法,一种是将NSS管脚直接接低电平。另一种就是通过主设备的任何一个gpio口去输出低电平选中从设备。
硬件输入:
主机接高电平,从机接低电平。

左键对应的软件片选引脚,选择GPIO_Output(输出模式),然后点击GPIO,设置一下备注。

我这里虽然PB12是SPI2的硬件片选NSS,但是我想用软件片选,所以关闭了硬件NSS

PB12设置为GPIO推挽输出模式、上拉、高速(片选引脚)

SPI配置默认如下

SPI配置中设置格式为Motorla格式,数据长度为8bit,MSB先输出。

分频为256分频,则波特率为140KBits/s,CPOL设置为HIGH,CPHA设置为第二个边沿。

不开启CRC检验,NSS为软件控制。

其他为默认设置。

  • 输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码

4、程序编程

在stm32f1xx_hal_spi.h头文件中可以看到spi的操作函数。分别对应轮询,中断和DMA三种控制方式。

 

  • 轮询: 最基本的发送接收函数,就是正常的发送数据和接收数据
  • 中断: 在SPI发送或者接收完成的时候,会进入SPI回调函数,用户可以编写回调函数,实现设定功能
  • DMA: DMA传输SPI数据

利用SPI接口发送和接收数据主要调用以下两个函数:

HAL_StatusTypeDef  HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据
/*
*hspi: 选择SPI1/2,比如&hspi1,&hspi2
*pData : 需要发送的数据,可以为数组
Size: 发送数据的字节数,1 就是发送一个字节数据
Timeout: 超时时间,就是执行发送函数最长的时间,超过该时间自动退出发送函数
*/
HAL_StatusTypeDef  HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据
/*
*hspi: 选择SPI1/2,比如&hspi1,&hspi2
*pData : 接收发送过来的数据的数组
Size: 接收数据的字节数,1 就是接收一个字节数据
Timeout: 超时时间,就是执行接收函数最长的时间,超过该时间自动退出接收函数
*/

SPI接收回调函数:

 HAL_SPI_TransmitReceive_IT(&hspi1, TXbuf,RXbuf,CommSize);

当SPI上接收出现了 CommSize个字节的数据后,中断函数会调用SPI回调函数:

   HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)

 用户可以重新定义回调函数,编写预定功能即可,在接收完成之后便会进入回调函数

  • 在spi.c文件下可以看到SPI2的初始化函数,片选管脚的初始化在gpio.c中
void MX_SPI2_Init(void){hspi2.Instance = SPI2;hspi2.Init.Mode = SPI_MODE_MASTER;    //设置为主模式hspi2.Init.Direction = SPI_DIRECTION_2LINES;  //双线模式hspi2.Init.DataSize = SPI_DATASIZE_8BIT;  //  8位数据长度hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;   //串行同步时钟空闲状态为高电平hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;    //第二个跳变沿采样hspi2.Init.NSS = SPI_NSS_SOFT;    //NSS软件控制hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; //分配因子256hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;   //MSB先行hspi2.Init.TIMode = SPI_TIMODE_DISABLE;   //关闭TI模式hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;   //关闭硬件CRC校验hspi2.Init.CRCPolynomial = 10;if (HAL_SPI_Init(&hspi2) != HAL_OK){Error_Handler();}
}void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle){GPIO_InitTypeDef GPIO_InitStruct = {0};if(spiHandle->Instance==SPI2){__HAL_RCC_SPI2_CLK_ENABLE();  __HAL_RCC_GPIOB_CLK_ENABLE();/**SPI2 GPIO Configuration    PB13     ------> SPI2_SCKPB14     ------> SPI2_MISOPB15     ------> SPI2_MOSI */GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_14;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);}
}

 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h

#ifndef _W25QXX_H_
#define _W25QXX_H_#include "main.h"
#include "spi.h"
#include "usart.h"#define W25Q64 	0XC816extern uint16_t W25QXX_TYPE;	
//修改CS片选引脚,W25Qx_Enable(),W25Qx_Disable()分别为使能和失能SPI设备,即拉低和拉高/CS电平
#define W25Qx_Enable()				HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET)
#define W25Qx_Disable() 			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET)#define W25X_WriteEnable				0x06 
#define W25X_WriteDisable				0x04 
#define W25X_ReadStatusReg1			    0x05 
#define W25X_ReadStatusReg2			    0x35 
#define W25X_ReadStatusReg3			    0x15 
#define W25X_WriteStatusReg1            0x01 
#define W25X_WriteStatusReg2            0x31 
#define W25X_WriteStatusReg3            0x11 
#define W25X_ReadData					0x03 
#define W25X_FastReadData				0x0B 
#define W25X_FastReadDual				0x3B 
#define W25X_PageProgram				0x02 
#define W25X_BlockErase					0xD8 
#define W25X_SectorErase				0x20 
#define W25X_ChipErase					0xC7 
#define W25X_PowerDown					0xB9 
#define W25X_ReleasePowerDown		    0xAB 
#define W25X_DeviceID					0xAB 
#define W25X_ManufactDeviceID			0x90 
#define W25X_JedecDeviceID			    0x9F 
#define W25X_Enable4ByteAddr            0xB7
#define W25X_Exit4ByteAddr              0xE9void W25QXX_Init(void);
uint16_t W25QXX_ReadID(void);  	    		
uint8_t  W25QXX_ReadSR(uint8_t regno);             
void W25QXX_Write_SR(uint8_t regno,uint8_t sr);   
void W25QXX_Write_Enable(void);  		
void W25QXX_Write_Disable(void);	
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead); void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite); 
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);void W25QXX_Erase_Chip(void);    	  	
void W25QXX_Erase_Sector(uint32_t Dst_Addr);	
void W25QXX_Wait_Busy(void);           	
void W25QXX_PowerDown(void);        	
void W25QXX_WAKEUP(void);	#endif

 

#include "w25qxx.h"
#include <stdio.h>uint16_t W25QXX_TYPE;void W25QXX_Init(void)
{W25Qx_Disable();//MX_SPI2_Init();W25QXX_TYPE = W25QXX_ReadID(); //读取芯片IDprintf1("FLASH ID:%X\r\n",W25QXX_TYPE);if(W25QXX_TYPE == 0xc816)printf1("FLASH TYPE:W25Q64\r\n");
}//读取芯片ID	
uint16_t W25QXX_ReadID(void)
{uint16_t ID;uint8_t id[2]={0};uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令  0x90  W25Qx_Enable();//芯片使能HAL_SPI_Transmit(&hspi2,cmd,4,1000);//spi发送读取ID命令,超时1000msHAL_SPI_Receive(&hspi2,id,2,1000); //spi读取ID存放在数组id中,超时1000msW25Qx_Disable(); //取消片选  ID = (((uint16_t)id[0])<<8)|id[1];return ID;
}//读取片区
uint8_t  W25QXX_ReadSR(uint8_t regno)
{uint8_t byte=0,cmd=0;switch(regno){case 1:cmd = W25X_ReadStatusReg1;break;case 2:cmd = W25X_ReadStatusReg2;break;case 3:cmd = W25X_ReadStatusReg3;break;default:cmd = W25X_ReadStatusReg1;break;}W25Qx_Enable();HAL_SPI_Transmit(&hspi2,&cmd,1,1000);HAL_SPI_Receive(&hspi2,&byte,1,1000);W25Qx_Disable();return byte;
}//写片区
void W25QXX_Write_SR(uint8_t regno,uint8_t sr)
{uint8_t cmd=0;switch(regno){case 1:cmd = W25X_WriteStatusReg1;break;case 2:cmd = W25X_WriteStatusReg2;break;case 3:cmd = W25X_WriteStatusReg3;break;default:cmd = W25X_WriteStatusReg1;break;}W25Qx_Enable();HAL_SPI_Transmit(&hspi2,&cmd,1,1000);HAL_SPI_Receive(&hspi2,&sr,1,1000);W25Qx_Disable();
}//写使能	
void W25QXX_Write_Enable(void)
{uint8_t cmd = W25X_WriteEnable; //写使能命令 0x06W25Qx_Enable();HAL_SPI_Transmit(&hspi2,&cmd,1,1000);W25Qx_Disable();
}void W25QXX_Write_Disable(void)
{uint8_t cmd = W25X_WriteDisable; //写失能命令 0x04W25Qx_Enable();HAL_SPI_Transmit(&hspi2,&cmd,1,1000);W25Qx_Disable();	
}void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{uint8_t cmd[4] = {0};cmd[0] = W25X_ReadData; //读取命令cmd[1] = ((uint8_t)(ReadAddr>>16));cmd[2] = ((uint8_t)(ReadAddr>>8));cmd[3] = ((uint8_t)ReadAddr);W25Qx_Enable();HAL_SPI_Transmit(&hspi2,cmd,4,1000);if(HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000) != HAL_OK){printf1("SPI read failed!\r\n");}W25Qx_Disable();
}void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{uint8_t cmd[4] = {0};if(NumByteToWrite > 256){NumByteToWrite = 256;printf1("写数据量过大,超过一页的大小!\n");}W25QXX_Write_Enable();W25Qx_Enable();cmd[0] = W25X_PageProgram;cmd[1] = ((uint8_t)(WriteAddr>>16));cmd[2] = ((uint8_t)(WriteAddr>>8));cmd[3] = ((uint8_t)WriteAddr);HAL_SPI_Transmit(&hspi2,cmd,4,1000);HAL_SPI_Transmit(&hspi2,pBuffer,NumByteToWrite,1000);W25Qx_Disable();W25QXX_Wait_Busy();
}void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{uint16_t pageremain;pageremain = 256 - WriteAddr%256;if(NumByteToWrite <= pageremain)pageremain = NumByteToWrite;while(1){W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);if(NumByteToWrite == pageremain)break;else{pBuffer += pageremain;WriteAddr += pageremain;NumByteToWrite -= pageremain;if(NumByteToWrite > 256)pageremain = 256;elsepageremain = NumByteToWrite;}}
}uint8_t W25QXX_BUFFER[4096]={0};
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{uint32_t secpos;uint16_t secoff;uint16_t secremain;uint16_t i;uint8_t *W25QXX_BUF;W25QXX_BUF = W25QXX_BUFFER;secpos = WriteAddr/4096;  //扇区地址secoff = WriteAddr%4096;  //在扇区里的偏移secremain = 4096-secoff;	//扇区剩余空间大小printf1("WriteAddr:0x%X,NumByteToWrite:%d\r\n",WriteAddr,NumByteToWrite);if(NumByteToWrite <= secremain)  //不大于4K字节secremain = NumByteToWrite;while(1){W25QXX_Read(W25QXX_BUF,secpos*4096,4096); //读取整个扇区内容for(i=0;i<secremain;i++) //校验数据 {if(W25QXX_BUF[secoff+i] != 0xff) //需要擦除break;}if(i < secremain)   //需要擦除{W25QXX_Erase_Sector(secpos);  //擦除扇区for(i=0;i<secremain;i++){printf1("4\r\n");W25QXX_BUF[i+secoff] = pBuffer[i];}W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096); //写入整个扇区}else{W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);	  //写入扇区剩余空间}if(NumByteToWrite == secremain) //写入结束了break;else //写入未结束{secpos++;  //扇区地址增1secoff = 0;  //偏移位置为0  pBuffer += secremain; //指针偏移WriteAddr += secremain; //写地址偏移NumByteToWrite -= secremain; //字节数递减if(NumByteToWrite > 4096)secremain = 4096;   //下个扇区还没是写不完elsesecremain = NumByteToWrite; //下个扇区可以写完了}}
}void W25QXX_Erase_Chip(void)
{uint8_t cmd = W25X_ChipErase;W25QXX_Write_Enable();W25QXX_Wait_Busy();W25Qx_Enable();HAL_SPI_Transmit(&hspi2,&cmd,1,1000);W25Qx_Disable();W25QXX_Wait_Busy();
}//擦除扇区	
void W25QXX_Erase_Sector(uint32_t Dst_Addr)
{uint8_t cmd[4] = {0};Dst_Addr *= 4096;W25QXX_Write_Enable();//写使能W25QXX_Wait_Busy();W25Qx_Enable();cmd[0] = W25X_SectorErase;cmd[1] = ((uint8_t)(Dst_Addr>>16));cmd[2] = ((uint8_t)(Dst_Addr>>8));cmd[3] = ((uint8_t)Dst_Addr);HAL_SPI_Transmit(&hspi2,cmd,4,1000);W25Qx_Disable();W25QXX_Wait_Busy();	
}void W25QXX_Wait_Busy(void)
{while((W25QXX_ReadSR(1)&0x01)==0x01);
}void W25QXX_PowerDown(void)
{uint8_t cmd = W25X_PowerDown;W25Qx_Enable();HAL_SPI_Transmit(&hspi2,&cmd,1,1000);W25Qx_Disable();HAL_Delay(1);
}void W25QXX_WAKEUP(void)
{uint8_t cmd = W25X_ReleasePowerDown;W25Qx_Enable();HAL_SPI_Transmit(&hspi2,&cmd,1,1000);W25Qx_Disable();HAL_Delay(1);
}
  •  在main.c文件下编写SPI测试代码
uint8_t wData[0x100];
uint8_t rData[0x100];
uint32_t i;
unsigned char tx_buf[256];int main(void)
{
uint8_t key;W25QXX_Init();for(i=0;i<0x100;i++){wData[i] = 0xff-i;rData[i] = 0;} while (1){key = KEY_Scan(0);if(key == 1){printf1("S1 write data...\r\n");W25QXX_Erase_Sector(0);//擦除扇区W25QXX_Write(wData,0,256);printf1("S1 write data success\r\n");}if(key == 2){printf1("S2 read data...\r\n");W25QXX_Read(rData,0,256);for(i=0;i<256;i++){printf1("0x%02X ",rData[i]);}}HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);HAL_Delay(500);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

5、下载验证

编译无误下载到开发板后,可以看到LED2指示灯不断闪烁,当按下S1按键后数据写入到W25Q64芯片内,当按下S2按键后读取W25Q64芯片的值,同时串口打印出相应信息

 

相关文章:

STM32CubeMX学习笔记14 ---SPI总线

1. 简介 1.1 SPI总线介绍 SPI 是英语Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。 SPI&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在…...

Gson(List<Object>转String 、String转List<Object>)

要在Java项目中使用Gson库&#xff0c;你需要添加相应的依赖项。以下是在Maven项目的pom.xml文件中添加Gson依赖的示例&#xff1a; <dependencies><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId>&l…...

uniapp路由跳转的方式

1. uniapp路由跳转的方式 1.1. uni.navigateTo保留当前页面&#xff0c;跳转到应用内的某个页面&#xff0c;使用uni.navigateBack可以返回到原页面。 uni.navigateTo({url:./index/index });注意&#xff1a; &#xff08;1&#xff09;页面跳转路径有层级限制&#xff0c;不…...

使用Python模拟绘制自由落体运动过程中的抛物线

目录 一、引言 二、自由落体运动的基本原理 三、使用Python模拟自由落体运动 四、扩展功能&#xff1a;添加速度曲线和动画效果 五、总结与展望 一、引言 自由落体运动是物理学中最基础的运动形式之一&#xff0c;它描述了一个物体在仅受重力作用下的运动轨迹。在这个…...

批量爬取网站图片脚本

不分文件夹 import requests from bs4 import BeautifulSoup import os from concurrent.futures import ThreadPoolExecutordef download_image(img_url):# 检查图片后缀是否为.jpg或.jpegif img_url.lower().endswith((.jpg, .jpeg)):try:img_response requests.get(img_ur…...

scrapy 爬虫:多线程爬取去微博热搜排行榜数据信息,进入详情页面拿取第一条微博信息,保存到本地text文件、保存到excel

如果想要保存到excel中可以看我的这个爬虫 使用Scrapy 框架开启多进程爬取贝壳网数据保存到excel文件中&#xff0c;包括分页数据、详情页数据&#xff0c;新手保护期快来看&#xff01;&#xff01;仅供学习参考&#xff0c;别乱搞_爬取贝壳成交数据c端用户登录-CSDN博客 最终…...

网络、UDP编程

1.网络协议模型: OSI协议模型 应用层 实际发送的数据 表示层 发送的数据是否加密 会话层 是否建立会话连接 传输层 数据传输的方式&#xff08;数据报、流式&#xff09; 网络层 …...

VSCode安装与使用

1、下载地址&#xff1a;Documentation for Visual Studio Code 在 VS Code 中使用 Python - 知乎 (zhihu.com) 自动补全和智能感知检测、调试和单元测试在Python环境(包括虚拟环境和 conda 环境)之间轻松切换 在 VS Code 中安装插件非常的简单&#xff0c;只需要打开 VS Code…...

进程和线程的区别与联系

进程和线程是计算机系统中两个重要的概念&#xff0c;它们在操作系统中扮演着不同的角色&#xff0c;并有着不同的特点和用途。以下是详细信息&#xff1a; 进程。进程是操作系统中资源分配的基本单位&#xff0c;它包括程序、数据和进程控制块。每个进程都有自己的地址空间&a…...

6、Redis-KV设计、全局命令和安全性

目录 一、value设计 二、Key设计 三、全局命令——针对所有key 四、安全性 一、value设计 ①是否需要排序&#xff1f;需要&#xff1a;Zset ②需要缓存的数据是单个值还是多个值&#xff1f; 单个值&#xff1a;简单值---String&#xff1b;对象值---Hash多个值&#x…...

python之海龟绘图

海龟绘图&#xff08;turtle&#xff09;是一个Python内置的绘图库&#xff0c;也被称为“Turtle Graphics”或简称“Turtles”。它采用了一种有趣的绘图方式&#xff0c;模拟一只小海龟在屏幕上爬行&#xff0c;而小海龟爬行的路径就形成了绘制的图形。这种绘图方式最初源自20…...

Java实战:Spring Boot 实现异步记录复杂日志

日志记录是软件开发中非常重要的一环&#xff0c;它可以帮助我们快速定位问题、监控程序运行状态等。在 Spring Boot 应用中&#xff0c;异步记录日志是一种常见的需求。本文将详细介绍如何在 Spring Boot 中实现异步记录复杂日志&#xff0c;包括异步日志的基本原理、实现方式…...

“色狼”用英语怎么说?柯桥日常英语,成人英语口语学习

最近有粉丝问我"色狼"英文翻译是啥 首先声明不是"colour wolf"哈 关于“色狼”的英文表达有很多 快和C姐一起来看看吧&#xff01; 1.pervert 这个单词的意思是变态、色狼 是对性变态者最直观的描述 He is such a pervert&#xff01; I saw him lo…...

Docker前后端项目部署

目录 一、搭建项目部署的局域网 二、redis安装 三、MySQL安装 四、若依后端项目搭建 4.1 使用Dockerfile自定义镜像 五、若依前端项目搭建 一、介绍前后端项目 一张图带你看懂ruoyi的前后端项目部署 得出结论&#xff1a;需要4台服务器&#xff0c;都处于同一个局域网中…...

如何快速的搭建一个小程序

要快速搭建一个小程序&#xff0c;你可以按照以下步骤进行&#xff1a; 明确目标和需求&#xff1a;在开始搭建小程序之前&#xff0c;首先明确你的小程序的主要功能、目标用户以及希望实现的业务需求。这将帮助你更好地规划和设计小程序。选择小程序平台&#xff1a;根据你的…...

STM32自学☞AD多通道

涉及到的硬件有&#xff1a;光敏传感器&#xff0c;热敏传感器&#xff0c;红外对射传感器&#xff0c;电位器 通过adc将他们采集的模拟信号转换为数值 ad.c文件 #include "stm32f10x.h" #include "stm32f10x_adc.h" #include "ad.h" #inc…...

微服务之商城系统

一、商城系统建立之前的一些配置 1、nacos Nacos是一个功能丰富的开源平台&#xff0c;用于配置管理、服务发现和注册、健康检查等&#xff0c;帮助构建和管理分布式系统。 在linux上安装nacos容器的命令&#xff1a; docker run --name nacos-standalone -e MODEstandalone …...

安卓玩机工具推荐----高通芯片9008端口读写分区 备份分区 恢复分区 制作线刷包 工具操作解析

上期解析了下adb端口备份分区的有关操作 安卓玩机工具推荐----ADB状态读写分区 备份分区 恢复分区 查看分区号 工具操作解析 在以往的博文中对于高通芯片机型的分区读写已经分享了很多。相关类似博文 安卓备份分区----手动查询安卓系统分区信息 导出系统分区的一些基本操作 …...

全量知识系统问题及SmartChat给出的答复 之16 币圈生态链和行为模式

Q.42 币圈生态链和行为模式 我认为&#xff0c;上面和“币”有关的一系列概念和技术&#xff0c;按设计模式的划分 &#xff0c;整体应该都属于行为模式&#xff0c;而且应该囊括行为模式的所有各个方面。 而行为又可以按照三种不同的导向&#xff08;以目的或用途为导向、过…...

【MOMO_Tips】批量将word转换为PDF格式

批量将word转换为PDF格式 1.打开文件–>选项–>自定义功能区–>开发工具–>确定 2.点开开发工具&#xff0c;选择第一个visual basic 3.进入页面后找到插入–>模块&#xff0c;就可以看到这样的画面之后将下列vba代码复制粘贴到模块中 Sub ConvertWordsToPd…...

【JSON2WEB】08 Amis的事件和校验

【JSON2WEB】01 WEB管理信息系统架构设计 【JSON2WEB】02 JSON2WEB初步UI设计 【JSON2WEB】03 go的模板包html/template的使用 【JSON2WEB】04 amis低代码前端框架介绍 【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成 【JSON2WEB】06 JSON2WEB前端框架搭建 【J…...

抖店类目报白什么意思?什么类目需要报白?这次给你讲明白!

我是电商珠珠 不少新手在选择类目的时候&#xff0c;有些类目却无法选择&#xff0c;系统显示需要报白才可以。那什么是报白&#xff1f;怎么报白&#xff1f;今天我就一次性给你们讲清楚。 抖店类目报白什么意思&#xff1f; 根据官方的说法&#xff0c;报白就是针对一些比…...

<C++>【继承篇】

​ ✨前言✨ &#x1f393;作者&#xff1a;【 教主 】 &#x1f4dc;文章推荐&#xff1a; ☕博主水平有限&#xff0c;如有错误&#xff0c;恳请斧正。 &#x1f4cc;机会总是留给有准备的人&#xff0c;越努力&#xff0c;越幸运&#xff01; &#x1f4a6;导航助手&#x1…...

size_t 和double相乘怎么转换size_t

在C中&#xff0c;size_t和double可以直接相乘&#xff0c;结果会自动转换为double类型。如果你想要得到的结果是size_t类型&#xff0c;你需要进行显式类型转换。但是要注意&#xff0c;double转size_t可能会丢失小数部分&#xff0c;只保留整数部分。 以下是一个例子&#x…...

C# 的一些好用的语法糖介绍

C# 中有很多语法糖&#xff08;Syntactic sugar&#xff09;&#xff0c;它们是一些语言特性&#xff0c;使得编写代码更加简洁、易读、更具表现力。 Lambda 表达式&#xff1a; Lambda 表达式允许你编写简洁的匿名函数。例如&#xff1a; Func<int, int, int> add (a…...

驱动开发面试复习

创建字符设备 1 创建设备号 alloc_chrdev_region 2.创建cdev cdev_init 3.添加一个 cdev,完成字符设备注册到内核 cdev_add 4.创建类 class_create 5.创建设备 device_create 1.内核空间与用户空间数据 copy_from_user 和copy_to_user 俩个函数来完成。 copy_from_user 函数…...

监测数据计算与换算:确保工程安全的关键步骤

在工程项目中&#xff0c;监测数据的计算与换算是一项至关重要的工作&#xff0c;它直接关系到工程的安全与稳定。本文将从确定基准值、数据计算与换算、异常值处理以及数据存储等方面&#xff0c;探讨监测数据计算与换算的主要工作内容。 添加图片注释&#xff0c;不超过 140 …...

Vue项目实战--空间论坛(1)

环境准备 安装好node.js,Vue后 添加插件 router---路由&#xff0c;多页面的应用 vuex---在多个组件之间维护同一个数据 添加依赖 bootstrap---美工 popperjs/core vue项目介绍 views-----对应vue文件&#xff0c;页面 router-----路由&#xff0c;页面&#xff0c;c…...

linux内核驱动——字符设备实现两个终端单向收发

linux内核驱动——字符设备实现两个终端单向收发 参考 Ubuntu18.04添加内核模块&#xff08;字符设备&#xff09; 创建内核驱动文件chat_dev.c&#xff1a; #include <linux/init.h> //定义了module_init #include <linux/module.h> //最基本的头文件&a…...

读取CSV数据并写入MySQL

import pandas as pd #import tushare as ts from sqlalchemy import create_engineimport baostock as bs #### 登陆系统 #### lg bs.login() # 显示登陆返回信息 print(login respond error_code:lg.error_code) print(login respond error_msg:lg.error_msg) #### 获取沪深…...