1. SPI的通信原理
SPI既可以做主机也可以做从机。
当做主机时。MOSI,SCK,CS都是作为输出。 而作为从机时。MOSI,SCK,CS都是作为输入。
所以SPI的硬件电路应该实现这样的功能。
2. GD32/STM32的SPI框图
1. GD32框图
如下图做主机的数据流向:
如下图做从机的数据流向:
2. STM32框图
通过一些寄存器的配置来控制电路。跟GD32的差不多。
波特率配置越高,采样越快。SPI的速率越快。
3. SPI的寄存器介绍
1. 控制寄存器0(SPI_CTL0)
2. 控制寄存器1(SPI_CTL1)
3. 状态寄存器(SPI_STAT)
4. 数据寄存器(SPI_DATA)
4. SPI主模式配置
1. 发送数据
先判断发送主机发送缓冲器是否为空。
2. 接收数据
接收数据缓冲器是否为空。如果为空就等待,否则就接收。
5. dome (硬件SPI访问w25Q32)
NSS\SCK\MISO\MOSI 对应的 PA4\PA5\PA6\PA7引脚。
1. 具体的SPI配置步骤。
1. SPI时钟使能,SPI对应的GPIO时钟使能。复用时钟使能。
2. SPI的GOIP配置。
3. SPI的初始化配置
4. SPI使能。
2. 代码实现
spi.h
#ifndef _SPI_H
#define _SPI_H
#include "gd32f10x.h"
void w25qxx_rcu_init(void);
void w25qxx_io_init(void);
void w25qxx_spi_init(void);
#endif
spi.c
#include "spi.h"
// 使能外设时钟
void w25qxx_rcu_init(void)
{
rcu_periph_clock_enable(RCU_GPIOA); //使能GPIOA时钟
rcu_periph_clock_enable(RCU_AF); //使能AF时钟
rcu_periph_clock_enable(RCU_SPI0); //使能SPI0时钟
}
// IO口进行配置,使之复用为SPI0, PA4\PA5\PA6\PA7,NSS\SCK\MISO\MOSI
void w25qxx_io_init(void)
{
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6); // MISO 浮空输入
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_7); // SCK\MOSI 复用推挽
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);// NSS片选口 普通的推挽输出
}
// SPI0初始化
void w25qxx_spi_init(void)
{
spi_parameter_struct spi_struct;
spi_struct.device_mode = SPI_MASTER; /*!< SPI master 做主机*/
spi_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; /*!< SPI transfer type 全双工 */
spi_struct.frame_size = SPI_FRAMESIZE_8BIT; /*!< SPI frame size 一次8字节 */
spi_struct.nss = SPI_NSS_SOFT; /*!< SPI NSS control by software 软件CS */
spi_struct.endian = SPI_ENDIAN_MSB; /*!< SPI big endian or little endian 传输高字节在前*/
spi_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; /*!< SPI clock phase and polarity 空闲低电平 第一个边沿进行采样*/
spi_struct.prescale = SPI_PSC_8; /*!< SPI prescaler factor 8分频*/
spi_init(SPI0, &spi_struct);
}
w25qxx.h
#ifndef _W25QXX_SPI_H
#define _W25QXX_SPI_H
#include "gd32f10x.h"
#include "w25qxx_ins.h"
#include "gd32f10x_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); // page program
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.c
#include "w25qxx.h"
#include "spi.h"
void w25qxx_init(void){
// 使能外设时钟
w25qxx_rcu_init();
// IO口进行配置,使之复用为SPI0, PA4\PA5\PA6\PA7,NSS\SCK\MISO\MOSI
w25qxx_io_init();
// SPI0初始化
w25qxx_spi_init();
// SPI使能
spi_enable(SPI0);
}
// 如果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:
gpio_bit_reset(GPIOA, GPIO_PIN_4);
break;
default:
break;
}
}
void w25qxx_cs_disable(uint8_t cs_id){
switch(cs_id){
case W25QXX_ID_1:
gpio_bit_set(GPIOA, GPIO_PIN_4);
break;
default:
break;
}
}
/*
主从数据交换
*/
uint8_t w25qxx_swap(uint8_t byte_to_send){
while(spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) == RESET){ // 等待SPI发送缓冲器为空
;
}
spi_i2s_data_transmit(SPI0, byte_to_send); // 把数据放到发生缓冲器
while(spi_i2s_flag_get(SPI0, SPI_FLAG_TRANS) == SET){ // 等待通信结束
;
}
while(spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE) == RESET){ // 等待SPI接收缓冲器非空
;
}
return spi_i2s_data_receive(SPI0); /* 把接收到的数据返回(从接收缓冲器里拿出) */
}