VxWorks开发板驱动程序学习之SPI

今天来继续学习S3C2410的SPI接口和DS1390实时时钟。

SPI —— 低电压串行外设接口
DS1390 —— 实时时钟(RTC)芯片

DS1390

先来简单介绍一下DS1390。

低电压串行外设(SPI)实时时钟(RTC)芯片——DS1390——是能够提供百分之一秒、秒、分钟、小时、星期、日期、月份和年份等信息的时钟/日历芯片。少于31天的月份会自动调整月末日期,包括闰年日期调整。时钟可以工作在24小时格式下,也可以工作在带有AM/PM指示的12小时格式下。具有可编程的定时闹钟。可以通过SPI接口进行时间的设置和读取。

先来看看官方datasheet怎么介绍的。

这里写图片描述

基本和前文一样。有温度补偿式的参考电压监视VCC的状态,一旦掉电,就自动禁用总线,并启用备用电源。另外在DS1390上还有一个单个的开漏输出给CPU提供中断或者方波信号(四种可选频率)。

DS1390、DS1391、DS1394都是串行可编程的,SPI兼容、双向总线。

DS1392、DS1393通过3-线式的串行总线通信,另外一个引脚 RST¯¯¯¯¯¯¯ 可用于中断或者复位输出/去抖动输入。

特点:

  • 一 百分之一秒、秒、分、时、天、日期、星期、月份、年份到2100年
  • 二 输出引脚可以配置为中断模式或者方波输出(32.768kHz、8.192kHz、4.096kHz、1Hz)
  • 三 当日实时时钟
  • 四 掉电检测与切换电路
    (以上四个是最常用的,后针对各个不同的型号略有差异,可查看相应数据手册)

再来看看DS1390的封装,常用接法,实物图

封装图:
这里写图片描述

常用接法:
这里写图片描述

实物图:
这里写图片描述

DS1390的时序图就不再往上放了,那样就成了罗列手册了。自己写驱动的时候,也没必要一行一行自己对照时序图写,那样也很难调。网上有现成的驱动代码,拿过来,能调通,存入、读出时间就好了。

SPI of S3C2410A

再来说说S3C2410A的SPI。

SPI—— Serial Peripheral Interface 串行外设接口

S3C2410A有两个SPI接口,每个接口包含两个8位移位寄存器,分别用于发送和接收。SPI数据传输期间,数据同时的移出(发送)和移入(移位寄存器)。8-位串行数据由相应的控制寄存器决定。如果你在发送,则接收数据寄存器中的值无效;如果在接收,则应该发送假的数据 ‘1’.

SPI的4个引脚信号:

  • SCK
  • MISO
  • MOSI
  • nSS0¯¯¯¯¯¯¯¯,nSS1¯¯¯¯¯¯¯¯

来看其中一个SPI接口的框图:
这里写图片描述

另一个接口的框图和这里一模一样。

SPI操作

讲讲SPI的操作。SPI主控端提供时钟,可以向SPPREn 这个寄存器写入合适的值来改变传输波特率。对于一个SPI从设备,则由其他主设备提供时钟。双方时钟一定是同步的。向SPTDATn 寄存器写入数据则开始发送或接收数据。某些情况下nSS应该在写入数据到 SPTDATn前被激活。

编程SPI模块的基本步骤:

  • 设置波特率 SPPREn
  • 配置控制寄存器 SPCONn
  • 写 0xFF 到 SPTDATn 10次来初始化MMC或者SD卡
  • 某个GPIO输入低拉低nSS来激活 MMC或者SD卡
  • 发送数据:检查 REDY=1并写数据到SPTDATn 进行发送
  • 接收数据:
    • SPCONn的TAGD位禁用=正常模式:写0xFF到SPTDATn,然后等REDY=1后从读缓存中读数据
    • SPCONn的TAGD位使能=Tx 自动清除数据模式:确认REDY=1即可直接读数据
  • 拉高GPIO,清除 nSS,deactivate MMC or SD card

S3C2410A的SPI支持的4种传输格式

SPCONn 控制寄存器中有两个功能位 CPOL 和 CPHA.

CPOL控制IDLE状态下的时钟电平,0为低电平;
CPHA控制数据和时钟之间的延迟,0——无延迟;1——数据相对时钟上升沿半个周期出现在总线上

简单的看一下四种格式的波形图:

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

对于几种格式就不再作解释了。更详细的可以直接查看 2410的datasheet.

SPI 的特殊功能寄存器

SPCON0
SPCON1
控制寄存器进行模式选择、SCK使能、主/从选择、时钟极性(CPOL)选择、时钟相位(CPHA)选择、TAGD使能(Tx Auto Garbage Data mode)

SPSTA0
SPSTA1
状态寄存器只用了低三位作状态标志位,最常用的是最低位 (REDY)

SPPIN0
SPPIN1
SPI的引脚控制寄存器,也只用了低三位。具体功能查手册。

SPPRE0
SPPRE1
SPI的波特率预分频寄存器,共用低八位作为SPI时钟速率,计算工式为:

Baudrate=PCLK/2/(Prescalervalue+1)

由此,该寄存器的值设置应为:
(volatileUINT8)rSPPRE0=freq/(PCLK/2)1;

其中freq 为波特率值。注意:波特率值应该小于25MHz

SPIDTA0
SPIDAT1
发送数据寄存器

SPRDAT0
SPRDAT1
接收数据寄存器

VxWorks开发板S3C2410A上SPI驱动DS1390

在这之前,先来看看,DS1390与板子的连接。

这里写图片描述

S3C2410A的SPI1接口的4个引脚信号(GPG[5-8])
SPIMISO
SPIMOSI
SPICLK
SPICS
分别与实时时钟芯片DS1390的 DOUT、DIN、SCLK、 CS¯¯¯¯¯ 进行连接。

SPI驱动代码学习

先看几个功能性的常量和宏定义以及硬件访问寄存器。

// 几个常用的数据类型
typedef unsigned char       BYTE;
typedef unsigned short      WORD;
typedef unsigned int        UINT;
typedef unsigned int        DWORD;      /* 32-bit CPU       */
typedef unsigned char       BOOLEAN;

// 收发寄存器和REDY位, 此处用到的 rSPTDAT1等 在头文件<2410addr.h>中找
#define  rSPTDAT    (*(volatile unsigned int *)(rSPTDAT1))       
#define  rSPRDAT    (*(volatile unsigned int *)(rSPRDAT1))       
#define  rDATREAD  ((*(volatile unsigned int *)(rSPSTA1))&1)     

#define CN_TIMEOUT_RW    10000

typedef struct {                                // BCD码方式时标数据结构
    BYTE            byYear_L;                   // 年低位
    BYTE            byYear_H;                   // 年高位
    BYTE            byMonth;                    
    BYTE            byDay;                      
    BYTE            byHour;                     
    BYTE            byMinute;                   
    BYTE            bySecond;                   
    BYTE            byMS_L;                     // 毫秒 低位
    BYTE            byMS_H;                     // 毫秒 高位
    BYTE            byDate;                     // 星期
}tagTimeBCD, *tagPTimeBCD;

tagPTimeBCD    tTimeBcd;                 // 定义了一个全局的结构体指针变量

由于相应的S3C2410用的GPIO口为GPG口,先来看一下GPG口有没有什么特别之处。

上datasheet:
这里写图片描述

GPIOG口共16根口线,GPG[5-7]刚好有第二功能用来进行SPI口通信。

这里写图片描述

GPGCON[31:0] 控制寄存器共32位,每2-bit控制一根口线的功能模式。

这里写图片描述

这里写图片描述

来看驱动代码:

// 函数名称: SpiTimeInit
// 函数功能: SPI口初始化
static void SpiTimeInit()
{
    DWORD   dwValReg;

    // 配置GPG[5-7]为利用的SPI功能、GPG[8]为普通输出
    M_CPU_REG_READ(  rGPGCON,  dwValReg );
    dwValReg = dwValReg & (~(3<<10)) & (~(3<<12)) & (~(3<<14)) & (~(3<<16));
    dwValReg = dwValReg | (3<<10) | (3<<12) | (3<<14) | (1<<16);
    M_CPU_REG_WRITE( rGPGCON,  dwValReg );

    SpiTimeDrv_En(FALSE);

    // SPPRE1
    dwValReg = 24;  // 设置波特率为 1Mbsp,需要用上面提到的公式提前计算好
    M_CPU_REG_WRITE( rSPPRE1,  dwValReg );

    // SPCON1
    dwValReg = 0|(1<<1)|(0<<2)|(1<<3)|(1<<4)|(0<<5);
    M_CPU_REG_WRITE( rSPCON1,  dwValReg );

    return;
}

//
///??? 没有搞懂这里返回值为什么要用 static 类型 ???
//

其中:

// 不同类型数据的硬件访问(读写)方式
#define M_CPU_REG_READ(r,result)     ((result)=*(volatile unsigned int *)(r))
#define M_CPU_REG_WRITE(r,data)      (*((volatile unsigned int *)(r)) = (data))

#define M_CPU_BYTE_READ(r,result)    ((result)=*(volatile unsigned char *)(r))
#define M_CPU_BYTE_WRITE(r,data)     (*((volatile unsigned char *)(r)) =(data))

#define M_CPU_REG_OR(r,result)       (*(volatile unsigned int *)(r) |= result))
#define M_CPU_REG_AND(r,result)      (*(volatile unsigned int *)(r) &=result)
#define M_CPU_REG_GET( r )           (*(volatile unsigned int *)(r))

// GPG[8]普通输出实现的片选功能
static void SpiTimeDrv_En( BOOLEAN bEn )
{
    if( bEn )       // 拉低使能
    {
        M_CPU_REG_AND( rGPGDAT,  (~(1<<8)) );
    }
    else            // 拉高禁用
    {
        M_CPU_REG_OR ( rGPGDAT,  (1<<8) );
    }
    return;
}
// 函数名称:SpiTimeGet
// 函数功能:获取时间
static BOOLEAN SpiTimeGet( tagPTimeBCD ptTimeBcd )
{
    WORD    wLoop,j;
    BYTE    byData[8];
    BOOLEAN bOk = TRUE;

    memset(ptTimeBcd,0,sizeof(tagTimeBCD)); // 清空结构体指针指向的内存
    SpiTimeDrv_En(TRUE);                    // 片选DS1390实时时钟芯片

    rSPTDAT = 0x00;          // 写入的第一个字节高位表示读写(0读,1写)后续位为地址
    for(wLoop=0;wLoop<CN_TIMEOUT_RW;wLoop++)     // 超时判断
    {
        if( rDATREAD ) break;
    }
    if( wLoop ==CN_TIMEOUT_RW ) bOk = FALSE;  // 参照SPI时序修改CN_TIMEOUT_RW

    // 写0xFF到SPTDAT,并读回来。实际的读操作,循环移位寄存器
    // 有一次超时,则返回 FALSE
    for(j=0;j<8;j++)
    {
        if( bOk == FALSE ) break;
        rSPTDAT = 0xFF;                         // 从2410写数据到DS1390
        for(wLoop=0;wLoop<CN_TIMEOUT_RW;wLoop++)
        {
            if( rDATREAD ) break;
        }
        if( wLoop ==CN_TIMEOUT_RW ) bOk = FALSE;
        else byData[j] = rSPRDAT;               // 读回时间数据
    }

    if( bOk )
    {
        ptTimeBcd->byYear_H = 0x20;
        ptTimeBcd->byMS_L = (byData[0]&0x0F)<<4; // 毫秒低位
        ptTimeBcd->byMS_H = (byData[0]&0xF0)>>4; // 毫秒高位
        ptTimeBcd->bySecond = byData[1];           
        ptTimeBcd->byMinute = byData[2];           
        ptTimeBcd->byHour   = byData[3];           
        ptTimeBcd->byDate   = byData[4];         // 星期   
        ptTimeBcd->byDay    = byData[5];           
        ptTimeBcd->byMonth  = byData[6];           
        ptTimeBcd->byYear_L = byData[7];           
    }   
    // 从这里可以看出 DS1390 的存储格式:
    // 毫秒低---毫秒高,秒,分,时,星期,日,月,年低位 共8个unsigned char的数据

    SpiTimeDrv_En(FALSE);     // 读完一次实时时间就不再操作DS1390,即片选禁用
    return(bOk);              // 与VxWorks的函数保持统一的返回状态形式
}

有了获取时间,那设置时间就是一样的了,只不过方向反过来。

// 函数名称:SpiTimeSet
// 函数功能:设置 DS1390 实时时钟芯片的当前时刻时间
static BOOLEAN SpiTimeSet( tagPTimeBCD ptTimeBcd )
{
    WORD    wLoop,j;
    BYTE    byData[8];
    BOOLEAN bOk = TRUE;

    SpiTimeDrv_En(TRUE);         // 片选DS1390芯片

    byData[0] = 0x81;   // 写入的第一个字节高位表示读写(0读,1写)后续位为地址
    byData[1] = ptTimeBcd->bySecond;    
    byData[2] = ptTimeBcd->byMinute;    
    byData[3] = ptTimeBcd->byHour;      
    byData[4] = 0x00;                   // 星期,信息无效,随便写入
    byData[5] = ptTimeBcd->byDay;       // 日
    byData[6] = ptTimeBcd->byMonth;     
    byData[7] = ptTimeBcd->byYear_L;    // 年低位

    for(j=0;j<8;j++)
    {
        rSPTDAT = byData[j];            // 注意写入DS1390顺序和读出顺序比较
        for(wLoop=0;wLoop<CN_TIMEOUT_RW;wLoop++)
        {
            if( rDATREAD ) break;
        }
        if( wLoop ==CN_TIMEOUT_RW )     // 一次写入超时则直接返回失败
        {
            bOk = FALSE;
            break;
        }
    }

    SpiTimeDrv_En(FALSE);                // 禁用片选 DS1390
    return bOk;
}

外部接口

同前面的KEY一样,SPI驱动程序也要为其他任务提供接口函数。这里主要是针对DS1390写的几个操作函数。

// 函数名称:timeget
// 函数功能:获取并输出DS1390的当前时间
STATUS timeget(void)
{
    tagPTimeBCD pTime= (UINT8 *)malloc(10);   // 把10改成 sizeof(tagTimeBCD) 可读性会更强些

    SpiTimeGet(pTime);

    // 要注意 logMsg 和 printf 的区别
    logMsg( "现在时间20%x年%x月%x日%x时%x分%x秒\n",
        pTime->byYear_L,pTime->byMonth, pTime->byDay,
        pTime->byHour,pTime->byMinute,pTime->bySecond);

    return OK;
}
logMsg

此处来讲讲 logMsg 函数。在VxWorks API Reference 的 OS Libraries 里面按字母序排好了所有的库函数。其中L打头的有

  • ledLib —— line-editing library
  • loadLib —— object module loader
  • loginLib —— user login/password subroutine library
  • logLib —— message logging library
  • lstLib —— doubly linked list subroutine library

先直接来看 logLib,帮助文件地址为X:/Tornado2.2/docs/vxworks/ref/logLib.html#top
其中X为Tornado开发环境安装目录。

每一个库的帮助信息中包含 NAME, ROUTINES, DESCRIPTION, … , INCLUDE FILES, SEE ALSO等项,基本和MATLAB等的帮助信息类似,容易学习使用。

5.5版本的VxWorks的logLib库包含了六个函数,分别是:

logInit( ) - initialize message logging library
logMsg( ) - log a formatted error message
logFdSet( ) - set the primary logging file descriptor
logFdAdd( ) - add a logging file descriptor
logFdDelete( ) - delete a logging file descriptor
logTask( ) - message-logging support task

可以看到其中 logMsg()函数主要作用是用来日志记录一个格式化的错误消息。看一下logMsg的具体描述:

logMsg()
int logMsg
    (
    char * fmt,               /* format string for print */
    int    arg1,              /* first of six required args for fmt */
    int    arg2,
    int    arg3,
    int    arg4,
    int    arg5,
    int    arg6
    )

DESCRIPTIOIN
This routine logs a specified message via the logging task. This routine's syntax is similar to printf( ) -- a format string is followed by arguments to format. However, the logMsg( ) routine takes a char * rather than a const char * and requires a fixed number of arguments (6).

The task ID of the caller is prepended to the specified message.

// 此处可以看到了,logMsg 的语法和 printf 的语法很相似。但 logMsg 以 char* 为参数,而 printf 以 const char* 为参数,并且 logMsg 要求固定的6个参数,调用者任务的ID也被注入到指定的消息中。

还应该注意的是,logMsg()并不直接产生输出到 logging stream,而是输出到 logging task 的消息队列,因此 logMsg() 可以用到中断函数中。

同时,logTask() 并不在调用 logMsg()函数的时候 解释参数,而是在实际 logging 的时刻解释参数,因此,logMsg() 的参数不应当指向 volatile 实体对象。

非常重要的是, logMsg() 会检测自身是否是运行在中断上下文。如果是在中断环境中,则继续运行;如果是在某个任务中调用了 logMsg() ,则该任务会被阻塞在 logMsg() 函数中。logMsg 用于中断环境中输出字符串消息

作为对比的是IO函数 printf 则不可以在中断函数中使用, printf 输出到一个串口(经重定向)。

printf()是将信息输出到标准输出设备(STDIN/STDOUT)中,如果此时设备正在工作,那么就会发生阻塞.

logMsg()是使用消息队列的方式,它将信息地址发送到队列,由专门的任务将信息打印出来.

EXAMPLE
If the following code were executed by task 20:

{
name = "GRONK";
num = 123;

logMsg ("ERROR - name = %s, num = %d.\n", name, num, 0, 0, 0, 0);
}

the following error message would appear on the system log:
0x180400 (t20): ERROR - name = GRONK, num = 123.

可以看到,当调用logMsg时实际的参数不足6个时,即可用 0 代入。

RETURN
The number of bytes written to the log queue, or EOF if the routine is unable to write a message.
返回信息几乎和 printf 一致。

这里再加一小段他人的使用经验小结:

LogMsg 利用消息队列将用户所发的消息传送给LogTask,然后由LogTask将其显示在屏幕或者其他输出设备上。而VxWorks默认的LogTask的任务优先级很高,这就直接导致了任务的切换。
切换是这样发生的,假设用户任务usrTask的优先级是51级(通常要低于网络任务50级,一般在100级以后),而我记忆中的LogTask是1级任务,仅次于中断响应。当usrTask调用LogMsg的时候,LogTask解除阻塞状态,获得CPU资源,而usrTask则排队到就绪任务队列去了。LogTask释放CPU资源后,就绪队列中的第一个任务开始执行,usrTask则继续在就绪队列里等待,呵呵。
所以在通常情况下,在中断处理函数中使用LogMsg就可以了,在任务中使用LogMsg是近乎画蛇添足的举动。

再来看看第二个接口函数 timeset :

STATUS timeset(UINT8 year,UINT8 month,UINT8 day,UINT8 hour,UINT8 minute,UINT8 second)
{
    tagPTimeBCD pTime = (UINT8 *)malloc(10);   // malloc(sizeof(tagTimeBCD))
    logMsg("DrvTime_SetTime!\n",0,0,0,0,0,0);  // 格式串后面没有实际参数,6个全0即可

    pTime->bySecond     = second;
    pTime->byMinute     = minute;
    pTime->byHour       = hour;
    pTime->byDay        = day;
    pTime->byMonth      = month;
    //pTime->byWeek       = 0x00;              // byDate 星期 信息不用写入
    pTime->byYear_L     = year; 

    logMsg("输入时间20%x年%x月%x日%x时%x分%x秒\n",
        pTime->byYear_L,pTime->byMonth,pTime->byDay,
        pTime->byHour,pTime->byMinute,pTime->bySecond);

    SpiTimeSet(pTime);
    return OK;
}

/
/// 按照上面的说法,这里两个接口函数中的 logMsg 是不是都应该换成 printf 更合适一些,现在还不懂,后续搞明白了再做说明
/

两个接口函数完了,再来看看SPI时钟任务主函数:

// 函数功能:SPI时钟任务主函数
void Time_Main( void )
{
    tTimeBcd =(UINT8 *)malloc(10);   // malloc(sizeof(tagTimeBCD));
    SpiTimeInit();
    while(1)
    {
        SpiTimeGet(tTimeBcd);        // 获取时间后存到全局指针 tTimeBcd 指向的内存中
        taskDelay(6);                // 任务主动延时,让出CPU
    }
}

好了,SPI驱动DS1390就暂时讲到这里。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
VxWorks PCIe驱动程序开发 PDF是一本关于VxWorks操作系统下开发PCI Express接口设备驱动程序的PDF格式电子书籍。该书涵盖了各种相关的主题,包括PCIe总线结构、PCIe设备的驱动开发方法、VxWorks操作系统中PCIe驱动程序的编写和调试等内容。 首先,PCIe总线结构是现代计算机系统中用于高速数据传输的通信总线之一。了解PCIe总线结构对于进行PCIe设备驱动程序的开发至关重要。本书详细介绍了PCIe总线的基本知识、术语和工作原理,帮助读者了解该总线的特点和性能。 其次,本书详细介绍了在VxWorks操作系统中开发PCIe设备驱动程序的方法。它涵盖了驱动程序的基本概念、VxWorks驱动程序框架、设备初始化和资源分配、驱动程序的读写操作以及中断处理等方面的内容。读者可以通过本书学习到如何使用VxWorks提供的API和工具来开发高效可靠的PCIe驱动程序。 最后,在开发过程中,驱动程序的调试是非常重要的一步。本书提供了一些常用的调试技术和工具,帮助开发人员快速定位和解决问题。此外,本书还介绍了一些常见的PCIe设备的驱动程序开发案例,并给出了相应的源码和演示程序,帮助读者更好地理解和应用书中的知识。 总的来说,VxWorks PCIe驱动程序开发PDF是一本全面而实用的教材,适合有一定嵌入式系统开发经验的工程师和学者阅读。通过学习该书,读者可以掌握在VxWorks操作系统中开发PCIe设备驱动程序的基本原理和方法,提高驱动程序开发的效率和质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

轻蓝雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值