TMS320F2812 SPI接口实验:四位数码管动态显示0000~9999
前言
继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寄存器配置要点
| 寄存器 | 位域 | 配置值 | 说明 |
|---|---|---|---|
| SPICCR | CHAR (3-0) | 0xF (15) 或 0x7 (7) | 数据字符长度(本实验用8位段码) |
| SPICCR | CLKPOLARITY (6) | 0 / 1 | 时钟极性,决定SCK空闲电平 |
| SPICCR | SPI SW RESET (7) | 0→1 | 先清零进入复位状态配置,最后置1使能 |
| SPICTL | MASTER_SLAVE (2) | 1 | 主模式 |
| SPICTL | TALK (1) | 1 | 使能发送 |
| SPICTL | CLK_PHASE (0) | 0 | 无延时相位(标准SPI模式) |
| SPIBRR | BRR (6-0) | 0x7F | 波特率 = LSPCLK / (SPIBRR+1) |
| SPIFFTX | SPIFFENA (15) | 1 | 使能FIFO,提高传输效率 |
时钟极性/相位配置需与74HC164匹配:74HC164在CLK上升沿采样数据,因此通常配置CLKPOLARITY=0(空闲低电平)且CLK_PHASE=0(数据在前沿输出)即可。
2.4 数码管段码表(共阴极)
数码管段连接:a~g + dp,对应字节的bit0~bit7(通常bit0=a,bit1=b,…,bit7=dp)。
| 数字 | 段码(共阴极) | 二进制 |
|---|---|---|
| 0 | 0xC0 | 1100 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)控制)。
五、实验总结与心得体会
-
SPI配置要点:
-
必须先清零
SPICCR.7进入复位状态,配置完其他寄存器后再置1使能。 -
时钟极性(
CLKPOLARITY)和相位(CLK_PHASE)必须与外设(74HC164)匹配,否则数据错位。 -
使用FIFO可以提高传输效率,本实验开启了FIFO(
spi_fifo_init)。
-
-
动态扫描的关键:
-
每位点亮时间需一致,避免亮度不均。
-
位选切换时要先关闭当前位,再开启下一位,否则会产生“鬼影”(短暂显示错误数字)。
-
延时时间需平衡:太短会导致亮度低,太长会明显闪烁。通常每位1~5ms为宜。
-
-
段码表的选择:
-
代码中提供的段码是共阳极格式(低电平点亮)。如果你的数码管是共阴极,需要取反(
~msg[i])或重新计算段码。 -
注意小数点的控制(
dp位),本实验未使用。
-
-
SPI引脚配置:
-
TMS320F2812的SPI引脚位于GPIOF0~F3,需通过
GPFMUX寄存器将其配置为外设功能。 -
别忘了
EALLOW保护,否则修改无效。
-
-
改进建议:
-
可以将递增速度改为按键调速,结合之前的键盘实验。
-
添加一个“暂停/继续”按钮,方便观察特定数字。
-
将待显示数字改为可编程的(例如通过串口输入)。
-
本实验为后续驱动更复杂的SPI设备(如AD转换器、DAC、OLED显示屏)打下了扎实的基础。
六、完整代码获取
完整的CCS工程代码,可直接复制到您的工程中:
建议在CCS中创建新工程,将代码放入main.c,并将必要的库文件(DSP281x_Common、DSP281x_Headers)加入编译路径。
七、常见问题排查
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 数码管完全不亮 | 位选IO配置错误或供电问题 | 检查Init_LEDS_Gpio中GPIOB8~B13是否配置为输出,且初始状态为高阻(应置低关闭) |
| 显示乱码 | SPI数据格式错误(时钟相位/极性) | 尝试修改SPICCR的CLKPOLARITY(bit6)或SPICTL的CLK_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与数码管显示
如果本文对您有帮助,欢迎点赞、收藏、转发!
有任何问题或建议,请在评论区留言交流。
更多推荐
所有评论(0)