NUC970的SPI寄存器足够简单,但是没有DMA支持有点遗憾,现在SPI也算高速设备了,一个4线的SPI FLASH都支持100MHz时钟,400Mbit的速度了,靠中断肯定是不行的,目前NUC970所支持的几种存储器里面,估计只能靠TF或者eMMC了,毕竟有DAM支持,nand flash现在用的少,占用IO也多,速度也就那样,块还出奇的大,还要自己做写均衡,坏块管理,eMMC都是BGA封装,目前有个这种的SD NAND 使用的SDIO接口的大容量flash,兼容SD卡驱动,可以实现QFN-8封装的1-2G大容量存储。
//足够简单的SPI寄存器
//SPI========================================================================================================
#define SPI0_BASE (0xB8006200) //寄存器基址
#define SPI1_BASE (0xB8006300) //寄存器基址
typedef struct
{
vu32 CNTRL;
vu32 DIVIDER;
vu32 SSR;
u32 Reserved1;
vu32 DATA[4];
}SPI_TypeDef;
#define SPI0 ((SPI_TypeDef *) SPI0_BASE)
#define SPI1 ((SPI_TypeDef *) SPI1_BASE)
其中可以支持突发4次传输,理论上可以实现一次传输16字节,但是没啥意义呀,在时钟为18MHz的时候,通讯128bit占用时间为7uS,7us中断一次CPU也不划算,还不如直接阻塞,直接阻塞时使用8bit发送与一次发送16个8bit阻塞时间几乎没区别,如果时钟速度更高,这个时间会更短,就更不能用中断了(太频繁,会降低系统实时性),还不如用可剥夺的实时OS直接阻塞,这就是没有DMA的缺点,有DMA时至少读取512直接都不用一次中断,而且效率更高。
//SPI.c
/*************************************************************************************************************
* 文件名: SPI.c
* 功能: NUC970 SPI驱动
* 作者: cp1300@139.com
* 创建时间: 2020-09-01
* 最后修改时间: 2020-09-01
* 详细: 定时器支持
*************************************************************************************************************/
#include "nuc970_system.h"
#include "spi.h"
//SPI基址
static const u32 scg_SPIx_Base[SPI_ChMax] = {SPI0_BASE, SPI1_BASE};
//SPI时钟使能
static const SYS_DEV_CLOCK scg_SPIx_DeviceClockEnable[SPI_ChMax] = {DEV_SPI0, DEV_SPI1};
//外设复位
static const SYS_DEV_RESET scg_SPIx_DeviceReset[SPI_ChMax] = { DEV_RESET_SPI0, DEV_RESET_SPI1};
//IO复用功能
static const GPIO_AF_Type scg_SPIx_AF_GPIO[SPI_ChMax][3] =
{
{GPIO_PB7_SPI0_CLK, GPIO_PB8_SPI0_DATA0, GPIO_PB9_SPI0_DATA1},
{GPIO_PI6_SPI1_CLK, GPIO_PI7_SPI1_DATA0, GPIO_PI8_SPI1_DATA1},
};
/*************************************************************************************************************************
*函数 : bool SPIx_Init(SPI_CH_Type ch, const SPI_CONFIG *pConfig)
*功能 : SPI初始化
*参数 : ch:定时器通道号;pConfig:配置
*返回 : TRUE:初始化成功;FALSE:初始化失败;
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2020-09-01
*最后修改时间 : 2020-09-01
*说明 :
*************************************************************************************************************************/
bool SPIx_Init(SPI_CH_Type ch, const SPI_CONFIG *pConfig)
{
if(ch > (SPI_ChMax - 1)) return FALSE; //端口号超出范围
SYS_DeviceClockEnable(scg_SPIx_DeviceClockEnable[ch], TRUE); //使能时钟
SYS_DeviceReset(scg_SPIx_DeviceReset[ch]); //复位外设
SPIx_Config(ch, pConfig); //SPI配置
//IO初始化
SYS_GPIOx_SetAF(scg_SPIx_AF_GPIO[ch][0]); //CLK 复用功能设置
SYS_GPIOx_SetAF(scg_SPIx_AF_GPIO[ch][1]); //MOSI 复用功能设置
SYS_GPIOx_SetAF(scg_SPIx_AF_GPIO[ch][2]); //MISO 复用功能设置
return TRUE;
}
/*************************************************************************************************************************
*函数 : void SPIx_SetIOMode(SPI_CH_Type ch, SPI_IO_MODE mode)
*功能 : 设置IO模式
*参数 : ch:SPI道号;mode:模式
*返回 : 无
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2020-09-01
*最后修改时间 : 2020-09-01
*说明 :
*************************************************************************************************************************/
void SPIx_SetIOMode(SPI_CH_Type ch, SPI_IO_MODE mode)
{
u32 RegTemp;
SPI_TypeDef *SPIx;
if(ch > (SPI_ChMax - 1)) return; //端口号超出范围
SPIx = (SPI_TypeDef *) scg_SPIx_Base[ch]; //获取设备基址
RegTemp = SPIx->CNTRL;
RegTemp &= ~(BIT22|BIT21|BIT0); //清除之前配置,不要写入BIT0
switch(mode)
{
case SPI_DUAL_IO_MODE: //双IO模式
{
RegTemp |= BIT22;
}break;
case SPI_QUAD_IO_MODE: //四IO模式
{
RegTemp |= BIT21;
}break;
default:break; //单IO模式
}
SPIx->CNTRL = RegTemp;
}
/*************************************************************************************************************************
*函数 : void SPIx_SetMultiIOOut(SPI_CH_Type ch, bool isOutput)
*功能 : 设置多IO模式(双IO或四IO模式)下IO方向
*参数 : ch:SPI道号;isOutput:TRUE:输出方向;FALSE:输入方向
*返回 : 无
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2020-09-01
*最后修改时间 : 2020-09-01
*说明 :
*************************************************************************************************************************/
void SPIx_SetMultiIODirection(SPI_CH_Type ch, bool isOutput)
{
SPI_TypeDef *SPIx;
if(ch > (SPI_ChMax - 1)) return; //端口号超出范围
SPIx = (SPI_TypeDef *) scg_SPIx_Base[ch]; //获取设备基址
if(isOutput) //输出模式
{
SPIx->CNTRL |= BIT20;
}
else
{
SPIx->CNTRL &= ~BIT20;
}
}
/*************************************************************************************************************************
*函数 : void SPIx_SetTranBitLeng(SPI_CH_Type ch, u8 BitLen)
*功能 : 设置单次传输bit长度
*参数 : ch:SPI道号;BitLen:bit长度1-32bit
*返回 : 无
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2020-09-01
*最后修改时间 : 2020-09-01
*说明 :
*************************************************************************************************************************/
void SPIx_SetTranBitLeng(SPI_CH_Type ch, u8 BitLen)
{
SPI_TypeDef *SPIx;
u32 temp;
if(ch > (SPI_ChMax - 1)) return; //端口号超出范围
SPIx = (SPI_TypeDef *) scg_SPIx_Base[ch]; //获取设备基址
temp = BitLen;
if(temp < 1) temp = 1;
if(temp > 32) temp = 32;
SPIx->CNTRL &= ~(0x1F<<3);
SPIx->CNTRL |= temp << 3;
}
/*************************************************************************************************************************
*函数 : void SPIx_SetOneTranCount(SPI_CH_Type ch, u8 Count)
*功能 : 设置单次收发次数
*参数 : ch:SPI道号;Count:1-4次
*返回 : 无
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2020-09-01
*最后修改时间 : 2020-09-01
*说明 : 可以连续多次传输
*************************************************************************************************************************/
void SPIx_SetOneTranCount(SPI_CH_Type ch, u8 Count)
{
SPI_TypeDef *SPIx;
u32 temp;
if(ch > (SPI_ChMax - 1)) return; //端口号超出范围
SPIx = (SPI_TypeDef *) scg_SPIx_Base[ch]; //获取设备基址
temp = Count;
if(temp < 1) temp = 1;
if(temp > 4) temp = 4;
temp -= 1;
SPIx->CNTRL &= ~(0x3<<8);
if(temp > 1)
{
SPIx->CNTRL |= temp << 8;
}
}
/*************************************************************************************************************************
*函数 : void SPIx_Config(SPI_CH_Type ch,const SPI_CONFIG *pConfig)
*功能 : SPI配置
*参数 : ch:SPI道号;pConfig:配置
*返回 : 无
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2020-09-01
*最后修改时间 : 2020-09-01
*说明 : 会回复到单IO模式,并且单次只传输一次,不会使用自动片选
*************************************************************************************************************************/
void SPIx_Config(SPI_CH_Type ch, const SPI_CONFIG *pConfig)
{
u32 RegTemp;
u32 temp1;
SPI_TypeDef *SPIx;
if(ch > (SPI_ChMax - 1)) return; //端口号超出范围
SPIx = (SPI_TypeDef *) scg_SPIx_Base[ch]; //获取设备基址
//CNTRL
RegTemp = 0;
if(pConfig->isClkIdleHigh) //时钟空闲高电平使能
{
RegTemp |= BIT31;
}
temp1 = pConfig->TranSleepCycle;
if(temp1 < 2) temp1 = 2;
if(temp1 > 17) temp1 = 17;
temp1 -= 2;
RegTemp |= temp1 << 12; //发送空闲周期,连续发送时中间空闲几个时钟周期
if(pConfig->isLSB) //低位在前
{
RegTemp |= BIT10;
}
temp1 = pConfig->TranBitLeng;
if(temp1 < 1) temp1 = 1;
if(temp1 > 32) temp1 = 32;
//temp1 -= 1;
RegTemp |= temp1 << 3;
if(pConfig->isTranNegEdge) //发送数据下降沿有效
{
RegTemp |= BIT2;
}
if(pConfig->isReceNegEdge) //接收数据下降沿有效
{
RegTemp |= BIT1;
}
SPIx->CNTRL = RegTemp;
//DIVIDER
RegTemp = 0;
temp1 = pConfig->ClockDivider;
if(temp1 < 2) temp1 = 2;
if(temp1 > 131072)temp1 = 131072;
temp1 = (temp1+1)/2;
RegTemp = temp1 - 1;
SPIx->DIVIDER = RegTemp;
SPIx->SSR = 0; //关闭自动片选
}
/*************************************************************************************************************************
*函数 : void SPIx_SetSpeed(SPI_CH_Type ch, u32 ClockDivider)
*功能 : SPI设置时钟速度
*参数 : ch:SPI道号;ClockDivider:分频系数2-131072
*返回 : 无
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2020-09-01
*最后修改时间 : 2020-09-01
*说明 :
*************************************************************************************************************************/
void SPIx_SetSpeed(SPI_CH_Type ch, u32 ClockDivider)
{
SPI_TypeDef *SPIx;
if(ch > (SPI_ChMax - 1)) return; //端口号超出范围
SPIx = (SPI_TypeDef *) scg_SPIx_Base[ch]; //获取设备基址
if(ClockDivider < 2) ClockDivider = 2;
if(ClockDivider > 131072)ClockDivider = 131072;
ClockDivider = (ClockDivider+1)/2;
ClockDivider = ClockDivider - 1;
SPIx->DIVIDER = ClockDivider;
}
/*************************************************************************************************************************
*函数 : u8 SPIx_ReadWriteByte(SPI_CH_Type ch,u8 TxData)
*功能 : SPI读写一字节
*参数 : ch:SPI道号;TxData:要发送的数据
*返回 : 读取的一字节
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2020-09-01
*最后修改时间 : 2020-09-01
*说明 :
*************************************************************************************************************************/
u8 SPIx_ReadWriteByte(SPI_CH_Type ch,u8 TxData)
{
SPI_TypeDef *SPIx;
if(ch > (SPI_ChMax - 1)) return 0; //端口号超出范围
SPIx = (SPI_TypeDef *) scg_SPIx_Base[ch]; //获取设备基址
SPIx->DATA[0] = TxData;
SPIx->CNTRL |= BIT0; //开始发送数据
nop;nop;nop;nop;
while(SPIx->CNTRL & BIT0); //等待发送完成
return SPIx->DATA[0] & 0xFF; //返回读取到的数据
}
/*************************************************************************************************************************
*函数 : u32 SPIx_ReadWriteData(SPI_CH_Type ch,u32 TxData)
*功能 : SPI读写一次数据
*参数 : ch:SPI道号;TxData:要发送的数据
*返回 : 读取的数据
*依赖 : 底层宏定义
*作者 : cp1300@139.com
*时间 : 2020-09-01
*最后修改时间 : 2020-09-01
*说明 : 数据长度由通讯bit长度决定
*************************************************************************************************************************/
u32 SPIx_ReadWriteData(SPI_CH_Type ch,u32 TxData)
{
SPI_TypeDef *SPIx;
if(ch > (SPI_ChMax - 1)) return 0; //端口号超出范围
SPIx = (SPI_TypeDef *) scg_SPIx_Base[ch]; //获取设备基址
SPIx->DATA[0] = TxData;
while(SPIx->CNTRL & BIT0); //等待发送完成
return SPIx->DATA[0]; //返回读取到的数据
}
//SPI.h
/*************************************************************************************************************
* 文件名: SPI.h
* 功能: NUC970 SPI驱动
* 作者: cp1300@139.com
* 创建时间: 2020-09-01
* 最后修改时间: 2020-09-01
* 详细: 定时器支持
*************************************************************************************************************/
#ifndef _SPI_H_
#define _SPI_H_
#include "nuc970_system.h"
//SPI通道选择
typedef enum
{
SPI_CH0 = 0, //SPI0
SPI_CH1 = 1, //SPI1
}SPI_CH_Type;
#define SPI_ChMax 2 //SPI数量
//IO模式
typedef enum
{
SPI_ONE_IO_MODE = 0, //单IO模式
SPI_DUAL_IO_MODE = 1, //双IO模式
SPI_QUAD_IO_MODE = 2, //四IO模式
}SPI_IO_MODE;
//SPI配置,默认为单IO模式
typedef struct
{
bool isClkIdleHigh; //时钟空闲高电平使能
bool isLSB; //是否低位在前
bool isTranNegEdge; //发送数据下降沿有效
bool isReceNegEdge; //接收数据下降沿有效
u8 TranSleepCycle; //发送空闲周期,连续发送时中间空闲几个时钟周期,设置范围2-17
u8 TranBitLeng; //传输数据bit长度1-32位
u32 ClockDivider; //时钟分频最小2分频,只能是偶数2-131072
}SPI_CONFIG;
//API
bool SPIx_Init(SPI_CH_Type ch, const SPI_CONFIG *pConfig); //SPI初始化
void SPIx_SetSpeed(SPI_CH_Type ch, u32 ClockDivider); //SPI设置时钟速度
void SPIx_Config(SPI_CH_Type ch, const SPI_CONFIG *pConfig); //SPI配置
u8 SPIx_ReadWriteByte(SPI_CH_Type ch,u8 TxData); //SPI读写一字节
//不常用API
void SPIx_SetIOMode(SPI_CH_Type ch, SPI_IO_MODE mode); //设置IO模式
void SPIx_SetMultiIODirection(SPI_CH_Type ch, bool isOutput); //设置多IO模式(双IO或四IO模式)下IO方向
void SPIx_SetTranBitLeng(SPI_CH_Type ch, u8 BitLen); //设置单次传输bit长度
void SPIx_SetOneTranCount(SPI_CH_Type ch, u8 Count); //设置单次收发次数
u32 SPIx_ReadWriteData(SPI_CH_Type ch,u32 TxData); //SPI读写一次数据
#endif //_SPI_H_
//测试
/////////////////////////////////////////////////////////////////////////////////////////////
//W25X64 FLASH支持
//SPI接口设置
#define SPI_FLASH_CS PBout(6) //W25X16片选
#define W25X16_SPI_CH SPI_CH0
//W26X16 硬件接口初始化
void __inline W25X16_HardwaveInit(void)
{
const SPI_CONFIG mConfig = {FALSE, FALSE, FALSE, TRUE, 0, 8, 4};
SPIx_Init(W25X16_SPI_CH, &mConfig);
SYS_GPIOx_SetAF(GPIO_PB6_IO);
SYS_GPIOx_OneInit(GPIOB, 6, OUT_PP, GPIO_IN_NONE); //初始化一个IO
SPI_FLASH_CS = 1;
}
/***********************W25X16相关接口************************/
//W25XXX SPI通信接口
u8 BI_W25_SPI_ReadWrtieByte(u8 data)
{
return SPIx_ReadWriteByte(W25X16_SPI_CH, data);
}
//W25XXX SPI片选控制接口
void BI_W25_SPI_SetCS(u8 CS_Level)
{
SPI_FLASH_CS = CS_Level;
}
//W25XXX 信号量申请
void BI_W25_MutexPen(void)
{
//INT8U err;
//OSMutexPend(g_SysGlobal.w25x16_semp, 0, &err); //申请W25X16信号量
}
//W25XXX 信号量释放
void BI_W25_MutexPost(void)
{
//OSMutexPost(g_SysGlobal.w25x16_semp); //释放W25X16信号量
}
//初始化W25Q128
W25X16_HardwaveInit(); //初始化W25X16底层硬件SPI接口
W25X16_OSMutexCreate(); //W25X16信号量初始化-需要在任务中进行初始化
while(1)
{
g_SysGlobal.FlashId = W25XXX_Init(&g_SysGlobal.W25X16_Handle,
BI_W25_SPI_ReadWrtieByte, //W25XXX SPI通信接口
NULL,//BI_W25_BulkRead, //SPI批量读取接口
NULL,//BI_W25_BulkWrite, //SPI批量写入接口
BI_W25_SPI_SetCS, //W25XXX SPI片选控制接口
Sleep, //系统ms延时
BI_W25_MutexPen, //W25XXX 信号量申请
BI_W25_MutexPost //W25XXX 信号量释放
);
uart_printf("FlashId:%d\r\n",g_SysGlobal.FlashId);
if(g_SysGlobal.FlashId != FLASH_NULL) break;
Sleep(500);
}
完整的工程链接:https://download.csdn.net/download/cp1300/12797399