SPI 通信协议

1. SPI通信

1. 什么是SPI通信协议

SPI 通信协议_第1张图片

2. SPI的通信过程  

在一开始会先把发送缓冲器的数据(8位)。一次性放到移位寄存器里。 移位寄存器会一位一位发送出去。但是要先放到锁存器里。然后从机来读取。从机的过程也一样。当移位寄存器的数据全部发送完。其实是主机与从机交换了数据。这时一次性把移位寄存器的数据放到接收缓冲器中。SPI 通信协议_第2张图片

SPI 通信协议_第3张图片 

3. SPI的几种模式 

CPOL时钟极性决定空闲时的高低电平。设置为1。空闲就是高电平。设置为0。空闲就是低电平。

CPHA时钟相位什么时候进行采样。设置为0。在第一个时钟边沿采集。所以在采集前要准备好数据。设置为1。在第二个时钟边沿采集。

SPI 通信协议_第4张图片

SPI 通信协议_第5张图片 

SPI 通信协议_第6张图片 

SPI 通信协议_第7张图片 

SPI 通信协议_第8张图片 

具体采用那种模式。查看从机手册支持哪种SPI模式来进行选择。

4. SPI的具体连接模式。

一主一从

SPI 通信协议_第9张图片

一主多从 (通过哪个从机的片选使能来选择。如果某个从机选中。其他的从机就不起作用)

SPI不支持多主机的。 

SPI 通信协议_第10张图片 

对从机比较多那么相应的片选引脚也需要更多。可以采用菊花链连接来减少I/o口。从而节省资源。

但这种连接对于SPI通信也会变的复杂。SPI 通信协议_第11张图片

 5. SPI于IIC的比较

SPI 通信协议_第12张图片 

1. W25Q32介绍

当MCU资源不够用就可以外接一个W25Qxx芯片。

1. W25Q32的命名。

SPI 通信协议_第13张图片

 2. W25Q32的引脚。

SPI 通信协议_第14张图片

 WP:写保护引脚  在进行写操作时先要取消写保护才能进行写操作。

 /HOLD or /RESET:如果这个引脚有效。即使片选有效。W25Q32也不会生效相当于高组态。

其他引脚:CS DO DI  CLK  GND  VCC。

3. 存储划分的方便管理

对SPI发送的指令然后驱动电路对存储数据进行操作。

SPI 通信协议_第15张图片 

SPI 通信协议_第16张图片 4. w25Q32具体硬件图。 

SPI 通信协议_第17张图片 

5. W25Q32的寄存器

 只看状态寄存器1的低两位其余的自行查手册。

SPI 通信协议_第18张图片

 6. 指令操作。(具体看手册指令的作用以及指令发送怎么获取数据)

 SPI 通信协议_第19张图片

 1. 读指令

SPI 通信协议_第20张图片

具体的伪代码 

SPI 通信协议_第21张图片 

2. 擦除

SPI 通信协议_第22张图片 

SPI 通信协议_第23张图片 

SPI 通信协议_第24张图片 

3. 写指令 

SPI 通信协议_第25张图片

3.  dome 实现SPI读取W25Q32

1. 程序设计 

 SPI 通信协议_第26张图片

SPI 通信协议_第27张图片  

2. 写数据注意事项

 SPI 通信协议_第28张图片

SPI 通信协议_第29张图片 

SPI 通信协议_第30张图片 

SPI 通信协议_第31张图片 

3. 具体的接线 

因为是模拟SPI所以引脚可以任意设定。

这里选择是SCL = PA5 ,MOSI = PA7 ,MISO = PA6 ,CS = PA4。

SPI 通信协议_第32张图片

 soft_spi.h

#ifndef _SOFT_SPI_H
#define _SOFT_SPI_H
/**
filename: soft_spi.h

**/
#include "gd32f10x.h"
#include "systick.h"

//定义表示具体IO口的资源宏(模拟SPI的选用的引脚)
#define SPI_PORT GPIOA
#define SPI_MOSI GPIO_PIN_7
#define SPI_MISO GPIO_PIN_6
#define SPI_SCK  GPIO_PIN_5
#define SPI_CS   GPIO_PIN_4

/* SPI的模式 */
#define SPI_MODE0 0
#define SPI_MODE1 1
#define SPI_MODE2 2
#define SPI_MODE3 3

void soft_spi_init(void);                  // spi通信准备,初始化
void soft_spi_init_io(void);               // 初始化spi通信用到的io口
void soft_spi_init_mode(uint8_t spi_mode); // 初始化spi通信模式,时钟相位和时钟极性

void soft_spi_begin(void);               // 开始spi通信
void soft_spi_w_cs(uint8_t bit_value);   // 写片选cs口

uint8_t soft_spi_swap(uint8_t byte_to_send); /* 交换数据 */

void soft_spi_w_sck(uint8_t bit_value);   // 写时钟口
void soft_spi_w_mosi(uint8_t bit_value);  // 写MOSI口
uint8_t soft_spi_r_miso(void);            // 读MISO口

void soft_spi_end(void);  // 结束spi通信

#endif

soft_spi.c

#include "soft_spi.h"

uint8_t clock_polar;
uint8_t clock_phase;

// spi通信准备,初始化
void soft_spi_init(void){
	soft_spi_init_io();
	soft_spi_init_mode(SPI_MODE0);
}

// 初始化spi通信用到的io口
void soft_spi_init_io(void){
	rcu_periph_clock_enable(RCU_GPIOA); /* 使能GPIOA时钟 */
	gpio_init(SPI_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SPI_MOSI|SPI_SCK|SPI_CS); /* MOSI, SCK, CS 配置成推挽输出  */
	gpio_init(SPI_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, SPI_MISO); /* MISO 输入模式  */
}

// 初始化spi通信模式,时钟相位和时钟极性
void soft_spi_init_mode(uint8_t spi_mode){
	switch(spi_mode){
		case SPI_MODE0:
			clock_polar = 0;
			clock_phase = 0;
			break;
		case SPI_MODE1:
			clock_polar = 0;
			clock_phase = 1;
			break;
		case SPI_MODE2:
			clock_polar = 1;
			clock_phase = 0;
			break;
		case SPI_MODE3:
			clock_polar = 1;
			clock_phase = 1;
			break;
		default:
			break;
	}
}

// 开始spi通信
void soft_spi_begin(void){
	soft_spi_w_cs((bit_status)0);  /* 把从机的片选拉低 */
}

// 写片选cs口
void soft_spi_w_cs(uint8_t bit_value){
	gpio_bit_write(SPI_PORT, SPI_CS, (bit_status)bit_value);
}

/*
重要函数,spi主机的移位寄存器与spi从机移位寄存器交换数值
*/
uint8_t soft_spi_swap(uint8_t byte_to_send){
	uint8_t byte_receive = 0x00;
	uint8_t i;
	
	for(i = 0; i < 8; i++){
		soft_spi_w_sck(clock_polar ? 1:0); /* 先有一个SCLghh空闲 */ 
		delay_1us(1);
 
		if(clock_phase){
			if(soft_spi_r_miso() == 1)byte_receive |= (0x80 >> i); /* 采集数据 */
			soft_spi_w_sck(clock_phase ? 0 : 1);
			delay_1us(1);
			soft_spi_w_mosi(byte_to_send & (0x80 >> i)); /* 先写 */
			
		}else{
			soft_spi_w_mosi(byte_to_send & (0x80 >> i)); /* 先写 */
			soft_spi_w_sck(clock_phase ? 0 : 1); /* 产生沿 */
			delay_1us(1);
			if(soft_spi_r_miso() == 1)byte_receive |= (0x80 >> i); /* 采集数据 */
		}
		
	}
	
	return byte_receive;
}

// 写时钟口
void soft_spi_w_sck(uint8_t bit_value){
	gpio_bit_write(SPI_PORT, SPI_SCK, (bit_status)bit_value);
}

// 写MOSI口
void soft_spi_w_mosi(uint8_t bit_value){
	gpio_bit_write(SPI_PORT, SPI_MOSI, (bit_status)bit_value);
}

// 读MISO口
uint8_t soft_spi_r_miso(void){
	return gpio_input_bit_get(SPI_PORT, SPI_MISO);    
}

// 结束spi通信
void soft_spi_end(void){
	
	soft_spi_w_sck(clock_polar ? 1:0); /* 根据模式来选择空闲电平 */
	soft_spi_w_cs((bit_status)1); /* 片选拉高 */
}

 w25qxx_spi.h

#ifndef _W25QXX_SPI_H
#define _W25QXX_SPI_H

#include "gd32f10x.h"
#include "w25qxx_ins.h"
#include "soft_spi.h"

#define W25QXX_ID_1           1

#define W25QXX_SR_ID_1        1
#define W25QXX_SR_ID_2        2
#define W25QXX_SR_ID_3        3

void w25qxx_init(void);

void w25qxx_wait_busy(void);
uint8_t w25qxx_read_sr(uint8_t sregister_id);  // 读状态寄存器
	
void w25qxx_read(uint8_t *p_buffer, uint32_t read_addr, uint16_t num_read_bytes);

void w25qxx_write(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes);
void w25qxx_write_nocheck(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes);
void w25qxx_write_page(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes); 

void w25qxx_erase_sector(uint32_t sector_addr);
void w25qxx_erase_chip(void);

void w25qxx_write_enable(void);
void w25qxx_write_disable(void);

void w25qxx_power_down(void);
void w25qxx_wake_up(void);

void w25qxx_cs_enable(uint8_t cs_id);
void w25qxx_cs_disable(uint8_t cs_id);
uint8_t w25qxx_swap(uint8_t byte_to_send);


#endif

 w25qxx_spi.c

 

#include "w25qxx_spi.h"

/* w25Q的初始化 */
void w25qxx_init(void){
	soft_spi_init();
}

// 如果SR-1的BUSY位为1的话,一直等待,直到BUSY位为0,结束等待
// 判断是否在忙
void w25qxx_wait_busy(void){
	while((w25qxx_read_sr(W25QXX_SR_ID_1) & 0x01) == 0x01){
		;
	}
}

// 读状态寄存器
uint8_t w25qxx_read_sr(uint8_t sregister_id){
	uint8_t command, result;
	switch(sregister_id){
		case W25QXX_SR_ID_1:
			command = W25QXX_READ_STATUS_REGISTER_1;
		break;
		case W25QXX_SR_ID_2:
			command = W25QXX_READ_STATUS_REGISTER_2;
		break;
		case W25QXX_SR_ID_3:
			command = W25QXX_READ_STATUS_REGISTER_3;
		break;
		default:
			command = W25QXX_READ_STATUS_REGISTER_1;
		break;
	}
	
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(command);
	result = w25qxx_swap(0xFF);
	w25qxx_cs_disable(W25QXX_ID_1);
	
	return result;
}

// 读flash的数据
// *p_buffer 读回的数据的存放位置
void w25qxx_read(uint8_t *p_buffer, uint32_t read_addr, uint16_t num_read_bytes){
	uint16_t i;
	
	w25qxx_cs_enable(W25QXX_ID_1);
	
	w25qxx_swap(W25QXX_READ_DATA); //发送读数据的指令
	w25qxx_swap(read_addr >> 16);  //发送24bit地址
	w25qxx_swap(read_addr >> 8);
	w25qxx_swap(read_addr);
	
	for(i=0; i < num_read_bytes; i++){
		p_buffer[i] = w25qxx_swap(0xFF);
	}
	
	w25qxx_cs_disable(W25QXX_ID_1);
}

// 
uint8_t W25QXX_Buffer[4096];  //用来存放从sector读出的bytes
void w25qxx_write(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes){
	uint32_t sec_num;
	uint16_t sec_remain;
	uint16_t sec_off;
	uint16_t i;
	
	sec_num	= write_addr / 4096;              //要写入的位置处在第sec_num个扇区上
	sec_off = write_addr % 4096;
	
	sec_remain = 4096 - sec_off;
	
	if(num_write_bytes <= sec_remain){
		w25qxx_read(W25QXX_Buffer, sec_num * 4096, 4096);  //扇区的数据读出来
		
		for(i = 0; i < sec_remain; i++){
			if(W25QXX_Buffer[i + sec_off] != 0xFF)  //说明这个扇区的第i+sec_off位没有擦除
				break;
		}
		
		if(i < sec_remain){ // 扇区没有擦除
			w25qxx_erase_sector(sec_num * 4096);
			for(i = 0; i < sec_remain; i++){
				W25QXX_Buffer[i + sec_off] = p_buffer[i];
			}
			w25qxx_write_nocheck(W25QXX_Buffer, sec_num * 4096, 4096);
		}else{              // 扇区sec_remain部分是擦除过的
			w25qxx_write_nocheck(p_buffer, write_addr, num_write_bytes);
		}
	}else{
		w25qxx_read(W25QXX_Buffer, sec_num * 4096, 4096);  //扇区的数据读出来
		
		for(i = 0; i < sec_remain; i++){
			if(W25QXX_Buffer[i + sec_off] != 0xFF)  //说明这个扇区的第i+sec_off位没有擦除
				break;
		}
		
		if(i < sec_remain){ // 扇区没有擦除
			w25qxx_erase_sector(sec_num * 4096);
			for(i = 0; i < sec_remain; i++){
				W25QXX_Buffer[i + sec_off] = p_buffer[i];
			}
			w25qxx_write_nocheck(W25QXX_Buffer, sec_num * 4096, 4096);
		}else{              // 扇区sec_remain部分是擦除过的
			w25qxx_write_nocheck(p_buffer, write_addr, sec_remain);
		}
		
		write_addr += sec_remain;
		p_buffer += sec_remain;
		num_write_bytes -= sec_remain;
		w25qxx_write(p_buffer, write_addr, num_write_bytes);
	}
		
	//判断读出来的数据是否都为0xFF
	//扇区是否删除
	//判断是否跨页
}

// 调用之前先确保扇区删除
void w25qxx_write_nocheck(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes){
	uint16_t page_remain = 256 - write_addr % 256;
	
	if(num_write_bytes <= page_remain){
		w25qxx_write_page(p_buffer, write_addr, num_write_bytes);
	}else{
		w25qxx_write_page(p_buffer, write_addr, page_remain);
		p_buffer += page_remain;
		write_addr += page_remain;
		num_write_bytes -= page_remain;
		w25qxx_write_nocheck(p_buffer, write_addr, num_write_bytes);
	}
}

// page program
// 保证没有跨页写的前提下调用此函数往某个页上写内容
void w25qxx_write_page(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes){
	uint16_t i;
	
	w25qxx_write_enable();
	
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_PAGE_PROGRAM);
	w25qxx_swap(write_addr >> 16);  //发送24bit地址
	w25qxx_swap(write_addr >> 8);
	w25qxx_swap(write_addr);
	
	for(i = 0; i < num_write_bytes; i++){
		w25qxx_swap(p_buffer[i]);
	}
	w25qxx_cs_disable(W25QXX_ID_1);
	
	w25qxx_wait_busy();
}

/* 擦除一个扇区 */
void w25qxx_erase_sector(uint32_t sector_addr){
	w25qxx_write_enable();
	
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_SECTOR_ERASE_4KB);
	w25qxx_swap(sector_addr >> 16);
	w25qxx_swap(sector_addr >> 8);
	w25qxx_swap(sector_addr);
	w25qxx_cs_disable(W25QXX_ID_1);
	
	w25qxx_wait_busy();
}

/* 擦除全部 */
void w25qxx_erase_chip(void){
	w25qxx_write_enable();
	
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_CHIP_ERASE);
	w25qxx_cs_disable(W25QXX_ID_1);
	
	w25qxx_wait_busy();
}
/* 使能写保护 */
void w25qxx_write_enable(void){
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_WRITE_ENABLE);
	w25qxx_cs_disable(W25QXX_ID_1);
}

/* 失能写保护 */
void w25qxx_write_disable(void){
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_WRITE_DISABLE);
	w25qxx_cs_disable(W25QXX_ID_1);
}

// 低电量休眠
void w25qxx_power_down(void){
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_POWER_DOWN);
	w25qxx_cs_disable(W25QXX_ID_1);
}

// 唤醒
void w25qxx_wake_up(void){
	w25qxx_cs_enable(W25QXX_ID_1);
	w25qxx_swap(W25QXX_RELEASE_POWER_DOWN_HPM_DEVICE_ID);
	w25qxx_cs_disable(W25QXX_ID_1);
}

/*
brief:使能片选引脚cs
cs_id: cs引脚的序号,即第几个w25qxx flash
*/
void w25qxx_cs_enable(uint8_t cs_id){
	switch(cs_id){
		case W25QXX_ID_1:
			soft_spi_begin();
		break;
		default:
			break;
	}
}

// brief:失能片选引脚cs
void w25qxx_cs_disable(uint8_t cs_id){
	switch(cs_id){
		case W25QXX_ID_1:
			soft_spi_end();
		break;
		default:
			break;
	}
}

/*
**** 交换数据
*/
uint8_t w25qxx_swap(uint8_t byte_to_send){
	return soft_spi_swap(byte_to_send);
}

 w25qxx_ins.h (w25Q32的指令集)

#ifndef _W25QXX_INS_H
#define _W25QXX_INS_H

#define W25QXX_WRITE_ENABLE							        0x06
#define W25QXX_WRITE_DISABLE						        0x04
#define W25QXX_READ_STATUS_REGISTER_1				        0x05
#define W25QXX_READ_STATUS_REGISTER_2				        0x35
#define W25QXX_READ_STATUS_REGISTER_3				        0x15
#define W25QXX_READ_DATA							        0x03
#define W25QXX_READ_UNIQUE_ID						        0x4B
#define W25QXX_WRITE_STATUS_REGISTER_1				        0x01
#define W25QXX_WRITE_STATUS_REGISTER_2                      0x31
#define W25QXX_WRITE_STATUS_REGISTER_3                      0x11 
#define W25QXX_PAGE_PROGRAM							        0x02
#define W25QXX_QUAD_PAGE_PROGRAM					        0x32
#define W25QXX_BLOCK_ERASE_64KB						        0xD8
#define W25QXX_BLOCK_ERASE_32KB						        0x52
#define W25QXX_SECTOR_ERASE_4KB						        0x20
#define W25QXX_CHIP_ERASE							        0xC7
#define W25QXX_ERASE_SUSPEND						        0x75
#define W25QXX_ERASE_RESUME							        0x7A
#define W25QXX_POWER_DOWN							        0xB9
#define W25QXX_HIGH_PERFORMANCE_MODE				        0xA3
#define W25QXX_CONTINUOUS_READ_MODE_RESET			        0xFF
#define W25QXX_RELEASE_POWER_DOWN_HPM_DEVICE_ID             0xAB
#define W25QXX_MANUFACTURER_DEVICE_ID				        0x90
#define W25QXX_JEDEC_ID								        0x9F
#define W25QXX_FAST_READ							        0x0B
#define W25QXX_FAST_READ_DUAL_OUTPUT				        0x3B
#define W25QXX_FAST_READ_DUAL_IO					        0xBB
#define W25QXX_FAST_READ_QUAD_OUTPUT				        0x6B
#define W25QXX_FAST_READ_QUAD_IO					        0xEB
#define W25QXX_OCTAL_WORD_READ_QUAD_IO				        0xE3
#define W25QXX_DUMMY_BYTE							        0xFF

#endif

 对于w25Q3的写操作是比较复杂的。比如要考虑写的大小是否大于一页.以及在写之前要判断是否擦除。因为要先擦除才能写入。

你可能感兴趣的:(GD32F10X,单片机)