打印
[牛人杂谈]

NUC970(ARM9)裸机SPI驱动

[复制链接]
787|12
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
aoyi|  楼主 | 2021-6-5 19:11 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
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直接都不用一次中断,而且效率更高。


使用特权

评论回复
沙发
aoyi|  楼主 | 2021-6-5 19:11 | 只看该作者
//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},
};



使用特权

评论回复
板凳
aoyi|  楼主 | 2021-6-5 19:12 | 只看该作者
/*************************************************************************************************************************
*函数                :        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;
}



使用特权

评论回复
地板
aoyi|  楼主 | 2021-6-5 19:13 | 只看该作者
/*************************************************************************************************************************
*函数                :        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;
}



使用特权

评论回复
5
aoyi|  楼主 | 2021-6-5 19:14 | 只看该作者
/*************************************************************************************************************************
*函数                :        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;
        }
}



使用特权

评论回复
6
aoyi|  楼主 | 2021-6-5 19:14 | 只看该作者
/*************************************************************************************************************************
*函数                :        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;
}



使用特权

评论回复
7
aoyi|  楼主 | 2021-6-5 19:15 | 只看该作者

/*************************************************************************************************************************
*函数                :        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;
        }
}



使用特权

评论回复
8
aoyi|  楼主 | 2021-6-5 19:15 | 只看该作者

/*************************************************************************************************************************
*函数                :        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;                                                                                                        //关闭自动片选
}

使用特权

评论回复
9
aoyi|  楼主 | 2021-6-5 19:17 | 只看该作者
/*************************************************************************************************************************
*函数                :        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;
}



使用特权

评论回复
10
aoyi|  楼主 | 2021-6-5 19:19 | 只看该作者
/*************************************************************************************************************************
*函数                :        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;                                                                                //返回读取到的数据
}


使用特权

评论回复
11
aoyi|  楼主 | 2021-6-5 19:20 | 只看该作者
/*************************************************************************************************************************
*函数                :        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];                                                                                        //返回读取到的数据
}



使用特权

评论回复
12
aoyi|  楼主 | 2021-6-5 19:21 | 只看该作者
//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_


使用特权

评论回复
13
aoyi|  楼主 | 2021-6-5 19:22 | 只看该作者
//测试

/
//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);
        }


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

100

主题

3306

帖子

3

粉丝