一、原理
设备和外界传输时,是通过USART串口传输的,它是异步全双工串行总线,速度主要是9600, 38400,115200bsp等等,速率不高。
SPI总线就是同步全双通串行总线,最高速率可以达到10M/S
SCLK:时钟线。决定SPI总线上的传输速率
MOSI:主机将数据发送到从机的线路,主机输出,从机输入
MISO:从机将数据发送到主机的线路,主机输入,从机输出
SS/CS:片选信号,当设置为低电平时,主机和从机进行通信
CPOL:时钟极性
- 当CPOL为0时,时钟空闲时电平为低;
- 当CPOL为1时,时钟空闲时电平为高;
CPHA:时钟相位
- 当CPHA为0时,时钟周期的前边缘采集数据(读数据),后边缘输出数据(发送数据)
- 当CPHA为1时,时钟周期的后边缘采集数据(读数据),前边缘输出数据(发送数据)
SPI 模式 | CPOL | CPHA | 空闲状态下的时钟极性 | 用于采样和/或移位数据的时钟相位 |
---|---|---|---|---|
0 | 0 | 0 | 逻辑低电平 | 数据在上升沿采样(读取数),在下降沿(发送数据)切换 |
1 | 0 | 1 | 逻辑低电平 | 数据在下降沿采样,在上升沿切换 |
2 | 1 | 0 | 逻辑高电平 | 数据在下降沿采样,在上升沿切换 |
3 | 1 | 1 | 逻辑高电平 | 数据在上升沿(后边沿)采样,在下降(前边沿)沿切换 |
二、 存储器分类
-
只读存储器 Read Only Memory
-
随机存储器 Random Access Memory
-
FLash
2.1ROM
MASK ROM:厂商在制造过程中设置的数据,不可修改。 PROM: 可编程只读存储器,可以写入一次。也被称为一次性可编程只读存储器。 EPROM: 断电后仍然能够保存数据的只读存储器,但是它可以通过强紫外线擦除。无法电擦除。 EEPROM:可读可写的的ROM,可以按照字节单位进行读写操作。速度特别慢。一般的EEPROM可擦除次数不会超过100W次,体积一般在几十KB~512KB之间。保存事件特别长,至少100年以上。
2.2 RAM
随机存储器,也被称为易失型存储器
RAM与CPU进行数据交换,速度特别快,但是他会掉电丢失。电脑上的内存条,就是RAM的一种,保存系统正在运行过程中的临时数据,当电源关闭失,数据丢失,RAM不保存数据。
静态RAM: static RAM ----SRAM 速度最快,不需要电刷新即可保存数据,但非常昂贵。
动态RAM:dynamic RAM ----DRAM 需要电刷新来保存数据,并且长时间不通电,里面的数据也会丢失。平时电脑里面的内存条,就是DRAM。
RAM存储0还是1由电荷决定,当电容中的电荷大于1/2时,存储器表示的是1,当小于1/2时,存储器表示的是0。
2.3 FLsh
被称为非易失型存储器
结合了RAM和ROM的优点,可以长时间保存。可编程,可读写,断电不会丢失,可以快读的存取数据。平时的U盘,SD卡,机械硬盘都是flash。
特点: 只能够写0,不能够写1。
Flash在擦除的时候,一般只能够做块擦除,擦除动作上把所有的电容充满电荷,此处存储器表示的是1。当写入数据时,可以对1bit位上的电容进行放电操作,放电完毕后,表示0。 flash无法对1bit的电容做充电操作。
写1清零:把flash清零,表示把它内存上的所有bit全部写1。
#include "stm32f4xx.h"
#include "sys.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "beep.h"
#include "sg90.h"
#include "usart.h"
#include "dht11.h"
#include "string.h" //微型标准库
#include "stdio.h"
#define W25Q128_CS PBout(14)
void w25q128_init()
{
GPIO_InitTypeDef a;
SPI_InitTypeDef b;
//初始化GPIO为SPI的复用模式
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB , ENABLE);
//SCLK--PB3,MISO--PB4,MOSI--PB5S
a.GPIO_Mode = GPIO_Mode_AF;//复用模式
a.GPIO_OType= GPIO_OType_PP;//推挽模式
a.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
a.GPIO_PuPd = GPIO_PuPd_UP;//上拉电阻
a.GPIO_Speed = GPIO_High_Speed;//高速模式
GPIO_Init(GPIOB,&a);
//指定PB3,PB4,PB5是复用成了SPI1的外设
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);
a.GPIO_Mode = GPIO_Mode_OUT;//输出模式
a.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOB,&a);
//空闲时,片选信号为高电平
W25Q128_CS = 1;
//使能SPI的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
//初始化SPI的结构体 最大10M/s SPI1在APB2总线上,输入频率84Mhz
//84M至少除以9才能降到10M以下,所以设置为16分频
//根据自己的需求设置的
b.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
b.SPI_Mode = SPI_Mode_Master;//主机
b.SPI_DataSize = SPI_DataSize_8b;//8bit数据
b.SPI_NSS = SPI_NSS_Soft;//软件触发
b.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;//分频
//必须根据通信的模块来设置---W25Q128设置为模式0
b.SPI_CPOL = SPI_CPOL_High;//SCLK空闲为高电平
b.SPI_CPHA = SPI_CPHA_2Edge;//时钟相位是先发送,再接收
b.SPI_FirstBit = SPI_FirstBit_MSB;//高位先出
b.SPI_CRCPolynomial = 0;//没有校验位
SPI_Init(SPI1,&b);
//开启SPI模块
SPI_Cmd(SPI1,ENABLE);
}
//全双工的数据收发--- 8次脉冲
uint8_t w25q128_sendByte(uint8_t val)
{
uint8_t data=0;//接收到的数据
//等待发送寄存器为空
while( SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) != SET);
//发送数据---逐个bit发送,同时接收寄存器开始逐个bit接收数据
SPI_I2S_SendData(SPI1,val);
//等待接收完成-----模式3的情况下,前边沿发送,后边沿接收
//当8个bit全部接收完成时,发送的8bit已经全部发送完毕了
while( SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) != SET);
//从接收数据寄存器中读取收到的数据
data = SPI_I2S_ReceiveData(SPI1);
return data;
}
//读取厂家和设备ID
void w25q128_read_ID()
{
uint32_t MID,DID;
//片选信号拉低
W25Q128_CS = 0;
//主机发送0x90指令
w25q128_sendByte(0x90);
//主机发送 0x00 0000 指令,用时24次脉冲
w25q128_sendByte(0x00);
w25q128_sendByte(0x00);
w25q128_sendByte(0x00);
//主机接收到厂家ID
MID = w25q128_sendByte(0xFF);
//主机接收到设备ID
DID = w25q128_sendByte(0xFF);
//片选信号拉高
W25Q128_CS = 1;
printf("MID=%#x DID=%#x\n",MID,DID);
}
//写使能
void w25q128_write_enable()
{
//拉低片选信号
W25Q128_CS = 0;
//发送指令 0x06
w25q128_sendByte(0x06);
//拉高片选信号
W25Q128_CS = 1;
}
//等待状态寄存器
void w25q128_wait_busy()
{
uint8_t temp;
//拉低片选信号
W25Q128_CS = 0;
//发送指令 0x05
w25q128_sendByte(0x05);
while(1)
{
//发送任意数据,接收到状态寄存器1的值
temp = w25q128_sendByte(0xFF);
//当第0bit---busy位被置0以后,跳出循环
if( (temp & 0x01) == 0)
break;
}
//拉高片选信号
W25Q128_CS = 1;
}
//擦除块
void w25q128_erase_sertor(uint32_t addr)
{
//开启写使能
w25q128_write_enable();
//片选信号拉低
W25Q128_CS = 0;
//传输指令0x20
w25q128_sendByte(0x20);
//传输24bit的地址 高位先出
w25q128_sendByte((addr>>16) &0xFF );
w25q128_sendByte((addr>>8) &0xFF );
w25q128_sendByte(addr &0xFF );
//片选信号拉高
W25Q128_CS = 1;
//等待内建时间结束
w25q128_wait_busy();
printf("sector erase success\n");
}
//从addr地址上读取len长度的字符到buf缓冲区中
void w25q128_read_buf(uint32_t addr,uint8_t *buf,int len)
{
//片选信号拉低
W25Q128_CS = 0;
//传输指令0x03
w25q128_sendByte(0x03);
//传输24bit的地址 高位先出
w25q128_sendByte((addr>>16) &0xFF );
w25q128_sendByte((addr>>8) &0xFF );
w25q128_sendByte(addr &0xFF );
while(len--)
{
*buf = w25q128_sendByte(0xFF);
buf++;//缓冲区的地址累加
}
//片选信号拉高
W25Q128_CS = 1;
printf("w25q128_read_buf success\n");
}
//从addr地址开始写入len长度的buf字符串
void w25q128_write_buf(uint32_t addr,uint8_t *buf,int len)
{
//开启写使能
w25q128_write_enable();
//片选信号拉低
W25Q128_CS = 0;
//发送0x02指令
w25q128_sendByte(0x02);
//发送24bit的地址,高位先出
w25q128_sendByte( (addr>>16) & 0xFF );
w25q128_sendByte( (addr>> 8) & 0xFF );
w25q128_sendByte( addr & 0xFF );
while(len--)
{
w25q128_sendByte(*buf);
buf++;//缓冲区地址偏移
}
//拉高片选信号
W25Q128_CS = 1;
//等待状态寄存器BUSY位被置0
w25q128_wait_busy();
}
int main()
{
int i;
uint8_t Rbuf[32];
//要设置中断分组,否则默认是分组0,---不存在抢占优先级,只有响应优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//设置滴答定时器8分频
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
led_init();
usart1_dma_init();
w25q128_init();
w25q128_read_ID();
// //擦除第0个块的第1个扇区
// w25q128_erase_sertor(0x001000);
//
// //从0x0地址读取32个字节放在Rbuf中
// w25q128_read_buf(0x001000,Rbuf,32);
//
// //打印
// for(i = 0; i<32;i++)
// printf("Rbuf[%d] = %#x\n",i,Rbuf[i]);
//
// //写入数据
// w25q128_write_buf(0x001000,(uint8_t*)"gec-stm32f407",14);
// //读取数据
w25q128_read_buf(0x001000,Rbuf,14);
printf("%s\n",Rbuf);
while(1)
{
//当串口1接收完成以后
if( usart1_flag == 1)
{
//打印串口1接收到的数据
printf("usart1 recv: %s",(char*)usart1_buf);
//还原参数,必须先关闭DMA,才可以清零缓冲区
//关闭DMA,还可以把下一次移动位置的下标放在 buf[0]上
DMA_Cmd(DMA2_Stream2,DISABLE);
memset((char*)usart1_buf,0,128);
usart1_flag = 0;
//清空缓冲区以后,重新打开DMA
DMA_Cmd(DMA2_Stream2,ENABLE);
}
//当串口3接收完成以后----wifi接收到内容
if( usart3_flag == 1)
{
printf("usart3 recv: %s\n",(char*)usart3_buf);
//解析json数据
recvjson();
DMA_Cmd(DMA1_Stream1,DISABLE);
memset((char*)usart3_buf,0,128);
usart3_flag = 0;
DMA_Cmd(DMA1_Stream1,ENABLE);
}
}
}
三、软件SPI
软件SPI也被称为模拟SPI。它指的是利用SPI总线的通信特点,和IO引脚的变化,来模拟SPI通讯。
STM32F407只有3个SPI模块,虽然一个SPI总线上可以挂载多个设备,但实际应用中,可能由于硬件设计的原因,或者工程需要的原因,我们要用3个以上的SPI通讯,就要用到软件模拟。
模拟总线时,对应模块的通讯协议不需要做修改,只需要对开发板上属于GPIO和外设的部分进行修改。
软件模拟时必须考虑该总线的通讯效率,模拟时的脉冲频率,不可以高于总线通讯效率。
#define W25Q128_CS PBout(14)
#define W25Q128_SCLK PBout(3)
#define W25Q128_MOSI PBout(5)
#define W25Q128_MISO PBin(4)
//=======================根据STM32外设SPI写出来的,它需要修改=============
//SCLK设置输出模式,因为stm32是主机,它来控制时钟线
//MOSI设置输出模式,因为它需要主动发送0或者1,主动拉高或者拉低电平
//MISO设置输入模式,因为它需要读取0或者1,接收到高电平或者低电平
void w25q128_init()
{
GPIO_InitTypeDef a;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB , ENABLE);
//SCLK--PB3,,MOSI--PB5
a.GPIO_Mode = GPIO_Mode_OUT;//输出模式
a.GPIO_OType= GPIO_OType_PP;//推挽模式
a.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5;
a.GPIO_PuPd = GPIO_PuPd_UP;//上拉电阻
a.GPIO_Speed = GPIO_High_Speed;//高速模式
GPIO_Init(GPIOB,&a);
//MISO--PB4
a.GPIO_Mode = GPIO_Mode_IN;//输入模式
a.GPIO_Pin = GPIO_Pin_4;//
GPIO_Init(GPIOB,&a);
//CS引脚:若由代码控制收发条件,则设置为输出模式,手动拉高或拉低
// 若由外界设备控制,则设置为输入或者复用模式。
a.GPIO_Mode = GPIO_Mode_OUT;//输出模式
a.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOB,&a);
//通讯模式为SPI mode3,时钟极性为高
W25Q128_SCLK = 1;
//空闲时,片选信号为高电平
W25Q128_CS = 1;
}
//全双工的数据收发--- 8次脉冲
uint8_t w25q128_sendByte(uint8_t val)
{
uint8_t data=0;//接收到的数据
int i;
//SPI mode3 在前边沿发送数据,后边沿接收数据
//时钟极性为高,那么前边沿是下降沿,后边沿是上升沿
//根据模块的资料,高位先出,也就是 val 的高位bit先发送
for(i=0 ;i<8 ; i++)
{
//判断val需要发送0还是发送1
//判断val的7-i位是0还是1,根据结果修改 MOSI的电平
if( (val>>(7-i)) & 0x01 )
{
W25Q128_MOSI = 1;
}
else{
W25Q128_MOSI = 0;
}
//前边沿---发送数据
W25Q128_SCLK = 0;
//延时--保证前边沿发送数据完成
delay_us(5);
//后边沿---读取数据
W25Q128_SCLK = 1;
//延时--保证后边沿读取成功
delay_us(5);
//判断读取到的是0还是1,高位先读取
if( W25Q128_MISO == 1)
{
data |= 1<<(7-i);
}
//如果读到0,应该写1bit的0,但是data初值为0,所以省略
}
return data;
}
int main()
{
int i;
uint8_t Rbuf[32];
//要设置中断分组,否则默认是分组0,---不存在抢占优先级,只有响应优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//设置滴答定时器8分频
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
usart1_dma_init();
w25q128_init();
w25q128_read_ID();
//擦除第0个块的第1个扇区
w25q128_erase_sertor(0x001000);
//从0x0地址读取32个字节放在Rbuf中
w25q128_read_buf(0x001000,Rbuf,32);
//打印
for(i = 0; i<32;i++)
printf("Rbuf[%d] = %#x\n",i,Rbuf[i]);
//写入数据
w25q128_write_buf(0x001000,(uint8_t*)"gec-6818wuhan",14);
//读取数据
w25q128_read_buf(0x001000,Rbuf,14);
printf("%s\n",Rbuf);
}