前言

继GPIO键盘输入实验之后,我们迎来了DSP的串行外设接口(SPI)实验。本次实验通过TMS320F2812的SPI模块驱动74HC164移位寄存器,控制四位数码管实现从0000到9999的递增循环显示。这是一个典型的SPI主模式通信应用,代码结构清晰,非常适合入门SPI开发。


一、实验概述

1.1 实验目的

  • 理解SPI同步串行通信的工作原理(主从模式、时钟极性、数据格式)

  • 掌握TMS320F2812 SPI模块的寄存器配置(SPICCR、SPICTL、SPIBRR等)

  • 学习74HC164移位寄存器驱动数码管的硬件连接

  • 掌握数码管动态扫描显示的原理与软件实现

  • 熟练使用CCS开发环境进行DSP程序设计

1.2 实验环境

  • 硬件:SEED-DPS2812M开发板 + 外设板(四位数码管模块)、XDS510仿真器、PC

  • 软件:Windows + Code Composer Studio (CCS)

  • 核心外设:TMS320F2812 SPI模块、74HC164移位寄存器、共阴极数码管


二、实验原理

2.1 SPI串行通信基础

SPI(Serial Peripheral Interface)是一种同步串行通信接口:

  • 同步通信:发送方同时提供时钟信号(SCK)和数据(MOSI),接收方在时钟边沿采样数据

  • 全双工:可同时发送和接收(但本实验仅使用发送)

  • 高速:速度远快于异步SCI,适合短距离、高吞吐量的外设通信

TMS320F2812的SPI模块支持主/从模式,本实验配置为主模式,时钟由DSP产生。

2.2 硬件连接方案

DSP (SPI主设备)          -->    外设板
------------------------------------------
GPIOF0 (SPISIMO)         -->    74HC164的DS (串行数据输入)
GPIOF1 (SPICLK)          -->    74HC164的CLK (移位时钟)
GPIOB8~B13 (普通IO)      -->    四位独立位选控制 (高电平选中)

74HC164工作原理

  • 每来一个CLK上升沿,将DS上的数据移入移位寄存器

  • 8个CLK后,8位段码数据并行输出到数码管的a~g、dp引脚

  • 配合位选信号(哪一位数码管的公共端有效),即可点亮对应位

数码管显示方式

  • 静态显示:每一位都需要独立的段码驱动,I/O占用多

  • 动态扫描:分时轮流点亮每一位,利用人眼视觉暂留实现同时显示的效果

本实验采用动态扫描:循环选择位1~位4,依次发送对应的段码,每位置延时约几毫秒,肉眼看不到闪烁。

2.3 SPI寄存器配置要点

寄存器位域配置值说明
SPICCRCHAR (3-0)0xF (15) 或 0x7 (7)数据字符长度(本实验用8位段码)
SPICCRCLKPOLARITY (6)0 / 1时钟极性,决定SCK空闲电平
SPICCRSPI SW RESET (7)0→1先清零进入复位状态配置,最后置1使能
SPICTLMASTER_SLAVE (2)1主模式
SPICTLTALK (1)1使能发送
SPICTLCLK_PHASE (0)0无延时相位(标准SPI模式)
SPIBRRBRR (6-0)0x7F波特率 = LSPCLK / (SPIBRR+1)
SPIFFTXSPIFFENA (15)1使能FIFO,提高传输效率

时钟极性/相位配置需与74HC164匹配:74HC164在CLK上升沿采样数据,因此通常配置CLKPOLARITY=0(空闲低电平)且CLK_PHASE=0(数据在前沿输出)即可。

2.4 数码管段码表(共阴极)

数码管段连接:a~g + dp,对应字节的bit0~bit7(通常bit0=a,bit1=b,…,bit7=dp)。

数字段码(共阴极)二进制
00xC01100 0000 (注意:共阴极实际为高电平点亮,但代码里0xC0是共阳极码?检查代码)

检查提供的代码unsigned char msg[10]={0xC0,0xf9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
这些值是共阳极段码(低电平点亮)。如果实验板上是共阴极数码管,则需要取反或修改段码表。通常外设板会匹配代码中的段码,直接使用即可。


三、软件设计

3.1 程序流程图

开始
  ↓
系统初始化(InitSysCtrl)
  ↓
GPIO配置:SPI功能引脚(GPIOF0~F3) + 位选IO (GPIOB8~B13)
  ↓
SPI初始化(复位→配置→释放复位)
SPI FIFO初始化
  ↓
主循环:
  DisData从0递增,循环0~9999
    ↓
  拆分为千/百/十/个位
    ↓
  动态扫描4位:
    选择当前位(Sellect_Bit)
    发送段码(spi_xmit)
    延时(人眼暂留)
    ↓
  DisData++,若>9999则归零

3.2 关键代码解析

(1)位选宏定义与GPIO初始化
// 位选IO宏定义(GPIOB8~B13)
#define SET_BIT4  GpioDataRegs.GPBSET.bit.GPIOB8 = 1
#define RST_BIT4  GpioDataRegs.GPBCLEAR.bit.GPIOB8 = 1
// ...类似定义BIT3(B9), BIT2(B10), BIT1(B13)

void Init_LEDS_Gpio(void)
{
    EALLOW;
    // 配置GPIOB8~B13为输出
    GpioDataRegs.GPBSET.bit.GPIOB8 = 1;
    GpioMuxRegs.GPBMUX.bit.CAP4Q1_GPIOB8 = 0;  // GPIO功能
    GpioMuxRegs.GPBDIR.bit.GPIOB8 = 1;         // 输出
    // ... 同理配置B9,B10,B13
    EDIS;
    // 初始时关断所有位选
    RST_BIT1; RST_BIT2; RST_BIT3; RST_BIT4;
}
(2)SPI初始化(关键)
void spi_init()
{
    // 1. 进入复位状态(SPI SW RESET = 0)
    SpiaRegs.SPICCR.all = 0x004F;   // 0x4F = 0b01001111
    // 位分析:CHAR=1111→16bit?实际段码是8bit,这里配置16bit也可以,发送低8位有效
    // CLKPOLARITY=1(?0x4F中bit6=1,即时钟极性为上升沿输出?需要对照手册)
    // 实际上0x004F: bit7=0 (复位), bit6=1 (时钟极性=1), bit5-0=0x0F (16位字符)
    
    // 2. 配置SPICTL:主模式、使能发送、时钟相位0
    SpiaRegs.SPICTL.all = 0x0006;   // 0b0110: bit2=1(主模式), bit1=1(使能发送), bit0=0(无相位延时)
    
    // 3. 设置波特率:SPIBRR = 127,波特率 = LSPCLK / (127+1)
    SpiaRegs.SPIBRR = 0x007F;
    
    // 4. 释放复位状态(SPI SW RESET = 1)
    SpiaRegs.SPICCR.all = 0x00DF;   // 0xDF = 0b11011111,bit7=1,其他保留原配置
}

注意:实际应用中,若使用8位段码,建议将SPICCR的CHAR位域配置为7(8位字符),代码中使用了16位(CHAR=0xF),虽然也能工作(发送低8位),但为了规范可以修改为 0x000F(复位态8位)→ 0x008F(释放)。

(3)SPI发送函数
void spi_xmit(Uint16 a)
{
    SpiaRegs.SPITXBUF = a;   // 写入发送缓冲区,自动启动发送
    // 主模式下,发送完成后SPI INT标志位置位,本实验不等待中断
}
(4)动态扫描与拆数
void DisData_Trans(Uint16 data)
{
    DisData_Bit[3] = data / 1000;          // 千位
    DisData_Bit[2] = data % 1000 / 100;    // 百位
    DisData_Bit[1] = data % 100 / 10;      // 十位
    DisData_Bit[0] = data % 10;            // 个位
}

void Sellect_Bit(Uint16 i)
{
    switch(i) {
        case 0: RST_BIT4; SET_BIT1; break;  // 先关前一位,再开当前位
        case 1: RST_BIT1; SET_BIT2; break;
        case 2: RST_BIT2; SET_BIT3; break;
        case 3: RST_BIT3; SET_BIT4; break;
    }
}
(5)主循环
void main(void)
{
    InitSysCtrl();
    // 配置SPI引脚为外设功能
    EALLOW;
    GpioMuxRegs.GPFMUX.all = 0x000F;   // GPIOF0~F3为SPI功能
    EDIS;
    Init_LEDS_Gpio();   // 初始化位选IO
    DINT; InitPieCtrl(); InitPieVectTable();  // 中断默认处理(本实验未用中断)
    spi_fifo_init();
    spi_init();
    
    Uint16 DisData = 0;
    while(1) {
        DisData_Trans(DisData);
        for(Loop=0; Loop<4; Loop++) {
            Sellect_Bit(Loop);
            spi_xmit(msg[DisData_Bit[Loop]]);
            delay1(25000);   // 约几毫秒的延时,调整扫描频率
        }
        DisData++;
        if(DisData > 9999) DisData = 0;
    }
}

四、实验现象

  • 下载程序后,四位数码管从 0000 开始,逐1递增显示,到达 9999 后归零,循环往复。

  • 数码管显示清晰、稳定,无肉眼可见的闪烁(动态扫描效果良好)。

  • 递增速度适中(约每秒变化数十次,具体由delay1(25000)控制)。


五、实验总结与心得体会

  1. SPI配置要点

    • 必须先清零SPICCR.7进入复位状态,配置完其他寄存器后再置1使能。

    • 时钟极性(CLKPOLARITY)和相位(CLK_PHASE)必须与外设(74HC164)匹配,否则数据错位。

    • 使用FIFO可以提高传输效率,本实验开启了FIFO(spi_fifo_init)。

  2. 动态扫描的关键

    • 每位点亮时间需一致,避免亮度不均。

    • 位选切换时要先关闭当前位,再开启下一位,否则会产生“鬼影”(短暂显示错误数字)。

    • 延时时间需平衡:太短会导致亮度低,太长会明显闪烁。通常每位1~5ms为宜。

  3. 段码表的选择

    • 代码中提供的段码是共阳极格式(低电平点亮)。如果你的数码管是共阴极,需要取反(~msg[i])或重新计算段码。

    • 注意小数点的控制(dp位),本实验未使用。

  4. SPI引脚配置

    • TMS320F2812的SPI引脚位于GPIOF0~F3,需通过GPFMUX寄存器将其配置为外设功能。

    • 别忘了EALLOW保护,否则修改无效。

  5. 改进建议

    • 可以将递增速度改为按键调速,结合之前的键盘实验。

    • 添加一个“暂停/继续”按钮,方便观察特定数字。

    • 将待显示数字改为可编程的(例如通过串口输入)。

本实验为后续驱动更复杂的SPI设备(如AD转换器、DAC、OLED显示屏)打下了扎实的基础。


六、完整代码获取

完整的CCS工程代码,可直接复制到您的工程中:

建议在CCS中创建新工程,将代码放入main.c,并将必要的库文件(DSP281x_CommonDSP281x_Headers)加入编译路径。


七、常见问题排查

问题现象可能原因解决方法
数码管完全不亮位选IO配置错误或供电问题检查Init_LEDS_Gpio中GPIOB8~B13是否配置为输出,且初始状态为高阻(应置低关闭)
显示乱码SPI数据格式错误(时钟相位/极性)尝试修改SPICCRCLKPOLARITY(bit6)或SPICTLCLK_PHASE(bit0)
数字闪烁严重扫描频率过低减少delay1中的延时值,例如改为5000~10000
某一位不显示位选硬连接虚焊或IO口损坏用万用表测量对应GPIOB引脚电平变化
数字只递增到999后归零拆数算法错误或数据类型问题检查DisData类型是否为Uint16(最大65535),DisData_Trans中千位计算正确性

八、参考资料

  • TMS320F2812 Data Manual (SPRS174)

  • TMS320F281x SPI Reference Guide (SPRU059)

  • 74HC164 Datasheet (NXP/TI)

  • 《DSP原理与应用》课程讲义 - 第5周 SPI与数码管显示


如果本文对您有帮助,欢迎点赞、收藏、转发!
有任何问题或建议,请在评论区留言交流。

更多推荐