1.SPI总线及W25QXX芯片
1.1 SPI总线简介
SPI全称Serial Peripheral Interface,即串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM、FLASH、实时时钟、AD转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的、全双工、同步通讯总线,在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局节省空间提供了方便,正是这种简单易用的特性,如今越来越多的芯片集成了这种通讯协议。下图是SPI内部结构简易图
从上图可以看出,主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输,寄存器通过MOSI信号线将字节传送给从设备,从设备也将自已的移位寄存器中的内容通过MISO信号线返回给主设备。这样两个移位寄存器中的内容就被交换。外设的写操作和读操作时同步完成的,如果只进行写操作,主设备只需要忽略接收到的字节,如果主设备要进行读操作,就必须发送一个空字节来引发从设备的传输。
SPI接口一般使用4条线通讯,单向传输时也可以使用3条线,其中3条线为SPI总线(MISO,MOSI,SCLK),1条为SPI片选信号线(CS),它们的作用如下:
*MISO:主设备数据输入,从设备数据输出
*MOSI:主设备数据输出,从设备数据输入
*SCLK:时钟信号,由主设备产生
*CS:从设备片选信号,由主设备控制
SPI使用MOSI/MISO信号线来传输数据,使用SCLK信号线进行数据同步。MOSI/MISO数据线在SCLK的每个时钟周期传输1位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或LSB先行没有硬性规定,但是两个SPI通讯设备之间必须使用同样的协定,一般都会采用MSB先行模式。
当有多个SPI从设备与SPI主设备相连时,设备的MOSI/MISO/SCLK信号线并联到相同的SPI总线上,即无论有多少个从设备,都共同使用者3条总线;而每个从设备都有独立的1条CS信号线,该信号线独占主设备的一个引脚,即有多少个从设备就有多少条片选信号线。当主设备要选择从设备时,把该从设备的CS信号线设置为低电平,该从设备即被选中(片选有效),接着主设备开始与从设备进行SPI通讯。
SPI总线根据时钟极性(CPOL)和时钟相位(CPHA)的配置不同,可以有四种工作方式:
*MISO:主设备数据输入,从设备数据输出
*MOSI:主设备数据输出,从设备数据输入
*SCLK:时钟信号,由主设备产生
*CS:从设备片选信号,由主设备控制
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的常用命令表
2.硬件设计
D1指示灯用来提示系统运行状态,K_UP按键用来控制W25Q64数据写入,K_DOWN按键用来控制W25Q64数据读取,串口1用来打印写入和读取的数据信息
*指示灯D1
*USART1串口
*W25Q64
*K_UP和K_DOWN按键
3.软件设计
3.1 STM32CubeMX设置
➡️ RCC设置外接HSE,时钟设置为72M
➡️ PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
➡️ USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
➡️ PA0设置为GPIO输入模式、下拉模式;PE3设置为GPIO输入模式、上拉模式
➡️ PG13设置为GPIO推挽输出模式、上拉、高速(片选引脚)
➡️ 激活SPI2,不开启NSS,数据长度8位,MSB先输出,分频因子256,CPOL为HIGH,CPHA为第二个边沿,不开启CRC检验,NSS为软件控制
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
3.2 MDK-ARM软件编程
➡️ 在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;//分配因子256
hspi2.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_SCK
PB14 ------> SPI2_MISO
PB15 ------> 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);
}
}
➡️ 创建按键驱动文件key.c 和相关头文件key.h,参考按键输入例程
➡️ 创建包含W25Q64芯片的相关操作函数及驱动函数的文件w25qxx.c和w25qxx.h,这里仅介绍几个重要的函数,源文件下载方式见文末介绍
//这里仅介绍几个重要的函数
void W25QXX_Init(void){
W25Qx_Disable();
W25QXX_TYPE = W25QXX_ReadID();//读取芯片ID
printf("FLASH ID:%Xrn",W25QXX_TYPE);
if(W25QXX_TYPE == 0xc816)
printf("FLASH TYPE:W25Q64rn");
}
uint16_t W25QXX_ReadID(void){
uint16_t ID;
uint8_t id[2]={0};
uint8_t cmd[4] = {W25X_ManufactDeviceID,0x00,0x00,0x00};//读取ID命令
W25Qx_Enable();//使能器件
HAL_SPI_Transmit(&hspi2,cmd,4,1000);
HAL_SPI_Receive(&hspi2,id,2,1000);
W25Qx_Disable();//取消片选
ID = (((uint16_t)id[0])<<8)|id[1];
return ID;
}
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);
HAL_SPI_Receive(&hspi2,pBuffer,NumByteToRead,1000);
W25Qx_Disable();//取消片选
}
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; //扇区地址
secpos = WriteAddr%4096; //在扇区里的偏移
secremain = 4096-secoff; //扇区剩余空间大小
printf("WriteAddr:0x%X,NumByteToWrite:%drn",WriteAddr,NumByteToWrite);
if(NumByteToWrite <= secremain) //不大于4K字节
secremain = NumByteToWrite;
while(1){
W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读取整个扇区内容
for(i=0;i break; } if(i < secremain){//需要擦除 W25QXX_Erase_Sector(secpos);//擦除扇区 for(i=0;i } W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区 } else{ W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写入扇区剩余空间 } if(NumByteToWrite == secremain){//写入结束了 break; } else{ //写入未结束 secpos++; //扇区地址增1 secoff = 0; //偏移位置为0 pBuffer += secremain; //指针偏移 WriteAddr += secremain; //写地址偏移 NumByteToWrite -= secremain;//字节数递减 if(NumByteToWrite > 4096) secremain = 4096; //下个扇区还没是写不完 else secremain = NumByteToWrite;//下个扇区可以写完了 } } } ➡️ 在main.c文件下编写SPI测试代码 /* USER CODE BEGIN PV */ uint8_t wData[0x100]; uint8_t rData[0x100]; uint32_t i; /* USER CODE END PV */ int main(void){ /* USER CODE BEGIN 1 */ uint8_t key; /* USER CODE END 1 */ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI2_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ W25QXX_Init(); for(i=0;i<0x100;i++){ wData[i] = i; rData[i] = 0; } /* USER CODE END 2 */ while (1){ key = KEY_Scan(0); if(key == KEY_UP_PRES){ printf("KEY_UP_PRES write data...rn"); W25QXX_Erase_Sector(0); W25QXX_Write(wData,0,256); } if(key == KEY_DOWN_PRES){ printf("KEY_DOWN_PRES read data...rn"); W25QXX_Read(rData,0,256); for(i=0;i<256;i++){ printf("0x%02X ",rData[i]); } } HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0); HAL_Delay(200); } } 4.下载验证 编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到W25Q64芯片内,当按下K_DOWN按键后读取W25Q64芯片的值,同时串口打印出相应信息
上一篇:玩转STM32CubeMX | 红外遥控
下一篇:玩转STM32CubeMX | DAC数模转换
推荐阅读最新更新时间:2024-11-17 13:21
推荐帖子
- dsp初学
- dsp初学者是否一定要先学单片机呢?dsp初学不需要吧,是不同的,dsp的功能强大的多,编程的话主要用c,可以喝汇编混合编程,和单片机没有很大的联系!回复楼主wuhong26的帖子希望能对dsp初学者能有点帮助https://home.eeworld.com.cn/?uid-...wspace-itemid-13762看看shicong的建议回复楼主wuhong26的帖子不一定非得先学单片机,但是最好还是先学习一下微机原理,这样可以入门更快一些,否则会很慢,而
- wuhong26 DSP 与 ARM 处理器
- 【得捷电子Follow me第2期】 任务提交
- 视频链接:https://training.eeworld.com.cn/video/37926一、介绍很高兴能够参加这次活动,这次活动使用的是AdafruitESP32-S3TFTFeather开发板,搭载ESP32-S3,包含蓝牙,wifi,还有一块tft高清屏幕。开发工具是Vscode和Thonny。这次活动我做了以下四个任务 任务1:控制屏幕显示中文 任务2:网络功能使用 任务3:控制WS2812B 任务4分任务1:日历&时钟我在七月
- eew_9QATv7 DigiKey得捷技术专区
- EVC下将string型转成char*类型
- EVC下将string型转成char*类型.CStringstr(_T(01234567));char*ch=str.GetBuffer(0);这在VC6.0中编译没任何问题.但EVC下编译出错.errorc2440:initializing:cannotconvertfrom\'unsignedshort*\'to\'char*\'请问怎么解决?EVC下将string型转成char*类型GetBuffer返回的是UNICODE(TCH
- 123456ZJ 嵌入式系统
- keil,如何看debug时候,的idata内容
- 有memorywindows在address中输入d:0xf0,发现好像看到的是sfr,而不是高128字节的数据(因为我已经执行了,所有数据区清零,可是在memorywindows看到的依旧是FF???)keil,如何看debug时候,的idata内容用i:0xXXXX输入i:0就可以了这里i和d的区别是什么????????引用3楼cstt60777的回复:这里i和d的区别是什么????????data:固定指前面0x00-0x7f的128个RAM,可以用acc
- sealove518 嵌入式系统
- 向大家讨教有效的版本管理
- 一直以来没有版本管理的概念,都是在取得一定的进展之后甚至每天做一次保存,这样的话文件保存越来越多,一段时间之后各个版本也捋不清楚状况了,并且还不能删除就怕哪天要回退到解放前。所以这两天看了一下关于版本管理的介绍,有svn和git。做了简单的比较,觉得svn相对简单易懂一点,所以安装了一个试试手。请教各位大神,个人和公司都是怎么进行版本管理的,一起分享下向大家讨教有效的版本管理不懂帮顶,顶完睡觉,公司用的SVN,但是我倾向于git,虽然我不会git。一直在用svn,还行
- elvike 综合技术交流
- 【求助】怎么将路由协议嵌入到CE的协议栈
- rt请各位前辈向晚辈提供一个将路由协议嵌入CE的解决方案。晚辈刚刚接到这样一项工作。从来没有接触过。想问一下各位前辈,需要学那些东西,做什么准备。实现的整个流程大体上是什么。。。麻烦给介绍以下。。。先谢谢各位前辈了。。。【求助】怎么将路由协议嵌入到CE的协议栈无语!!!人肉google比google流行多了。。。被鄙视了...着实不太懂...麻烦给个说法...
- weiyingwu 嵌入式系统
设计资源 培训 开发板 精华推荐
- LT6656ACS6-1.25、1.25V 微控制器电压基准和稳压器的典型应用
- STM32F103C8T6
- NCP1207AADAPGEVB,24 W 适配器评估板
- 用于便携式消费电子产品的 1.24 至 20V DC 至 DC 单路输出电源
- 使用 Analog Devices 的 LTC3130EUDC-1 的参考设计
- 电池管理控制
- TC7106 ADC用于集成电路温度传感器的典型应用
- STM32F10xxx ADC应用电路使用STM32F10xxx ADC_IN11和ADC_IN14转换
- 使用 Richtek Technology Corporation 的 RT8128C 的参考设计
- 使用 ON Semiconductor 的 LV4900H 的参考设计