在一开始会先把发送缓冲器的数据(8位)。一次性放到移位寄存器里。 移位寄存器会一位一位发送出去。但是要先放到锁存器里。然后从机来读取。从机的过程也一样。当移位寄存器的数据全部发送完。其实是主机与从机交换了数据。这时一次性把移位寄存器的数据放到接收缓冲器中。
CPOL时钟极性决定空闲时的高低电平。设置为1。空闲就是高电平。设置为0。空闲就是低电平。
CPHA时钟相位什么时候进行采样。设置为0。在第一个时钟边沿采集。所以在采集前要准备好数据。设置为1。在第二个时钟边沿采集。
具体采用那种模式。查看从机手册支持哪种SPI模式来进行选择。
一主一从
一主多从 (通过哪个从机的片选使能来选择。如果某个从机选中。其他的从机就不起作用)
SPI不支持多主机的。
对从机比较多那么相应的片选引脚也需要更多。可以采用菊花链连接来减少I/o口。从而节省资源。
当MCU资源不够用就可以外接一个W25Qxx芯片。
WP:写保护引脚 在进行写操作时先要取消写保护才能进行写操作。
/HOLD or /RESET:如果这个引脚有效。即使片选有效。W25Q32也不会生效相当于高组态。
其他引脚:CS DO DI CLK GND VCC。
对SPI发送的指令然后驱动电路对存储数据进行操作。
只看状态寄存器1的低两位其余的自行查手册。
具体的伪代码
因为是模拟SPI所以引脚可以任意设定。
这里选择是SCL = PA5 ,MOSI = PA7 ,MISO = PA6 ,CS = PA4。
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的写操作是比较复杂的。比如要考虑写的大小是否大于一页.以及在写之前要判断是否擦除。因为要先擦除才能写入。