单片机SPI功能应用笔记

judy的头像

一些单片机内置硬件
PI功能,如STC15F2K60S2和昇泉MPC82G516,本文以后者为例,记述了用SPI硬件向4位数码显示模块发送串行数据的应用过程。

一、 SPI协议简介

SPI是英语Serial Peripheral interface的缩写,中文是串行外围设备接口。主要应用在MPU与 EEPROM、FLASH、实时时钟、AD转换器等外设之间传输串行数据。

SPI总线占用四根线,分别是时钟线SPICLK、数据线MISO和MOSI、以及从机选择线/SS,如下图。主机是发起传输的设备,也即产生时钟脉冲的设备,从机在脉冲的作用下被动的接收或发送数据。SPI是双向传输协议,主机和从机在脉冲同步下是互为收发数据的。如果数据仅从主机向从机单向传送,则可省略MISO线。

单片机SPI功能应用笔记

总线上至少配置一主一从,也可以挂接多个主机或从机。主机多由MPU担任。总线可配置为:

1. 一主一从:可直接将从机SS端接低电平,可节省MPU一个IO口。

2. 二个主机: 2个MPU都配置成主机时,两机SS端直连。发起传输的主机拉低SS线,能把对方强制改为从机。

3. 一主多从:主机用不同I/O口接各从机SS端来实现片选。

二、 硬件介绍

1. 本应用采用一主一从配置。主机是单片机,从机是4位数码管显示模块。该模块不具备完整SPI功能,但能接收SPI串行数据。另外,模块需要占用单片机一个I/O来锁存输出信号。

2. 单片机MPU采用昇泉MPC82G516,内置硬件SPI功能,各引脚功能是:P1.7为脉冲SPICLK,P1.5为数据MOSI。P1.4为片选SS,P1.6为数据MISO。芯片属于51系列,与STC15系列大同小异。但其独特之处在于内置OCD接口(即片上调试功能,On-Chip Debug),可以实现不占用片上资源的在系统调试,能真正实现单步仿真。

3. 4位数码显示模块,采用2片595驱动,分别用于输出数码管的段码和位码。模块有5根针脚,除了电源VCC和GND,3根数据线分别是时钟线CLK、数据线DIO和锁存RCK。如果需要,用2个模块级联可实现8位显示。

单片机SPI功能应用笔记

三、 发送流程

1. SPI功能由单片机内置硬件实现,每次可发送一个字节。发送指令很简单,只要向SPI数据寄存器SPIDAT写入一个字节即可。单片机执行到该指令后,硬件会自动按照初始化配置,输出脉冲和数据。

2. SPI收发由硬件自动控制进行,发送一个字节的时间,根据SPI时钟频率的设置,约占15-450个机器周期。SPI硬件发完一个字节会置位SPIF标志,并申请中断(如果中断允许)。因此不能连续写SPI,当前一字节未发完时写入数据时,就会造成冲突。发生冲突后,SPI硬件置位冲突标志位WCOL,丢弃新写入的数据,继续完成原字节的发放。

3. SPIF与WCOL是SPI状态寄存器SPISTAT的2个标志位,编程时要注意其特点是“置1清0”。
SPSTAT (地址=84H, SPI状态寄存器, 复位值=00xx,xxxxB)
7.SPIF( 传输完成标志)
6.WCOL (写冲突标志)

4. SPI硬件单次只发送一个字节。连续发送多个字节,需要用户编程实现,方法一是写指令后,将SPIF置1清0,再循环判断SPIF,等SPIF为1后再发下一字节;方法二是使用SPI中断,在中断服务程序清SPIF,再发下一字节。

四、 SPI参数配置

参数配置通过控制寄存器SPICTL来实现,各控制位的定义如下表及说明。

SPCTL (地址=85H, SPI控制寄存器, 复位值=0000,0100B)

单片机SPI功能应用笔记

1. 单片机作主机,因此SSIG=1,MSTR=1。

2. 脉冲和相位极性CPOL和CPHA根据从机接收特性来确定。595芯片在“时钟CLK上升沿时采样移入数据”,假设使用正脉冲,则上升沿是其前沿,因此参数取00。如果采用负脉冲,则上升沿为后沿,参数取11。

3. 其他参数比较容易确定:SPEN=1启用SPI功能。DORD根据从机电路确定。SPR1、SPR0取00、01、10、11时,SPI时钟频率为3M、750K、187.5K和93.75K,而595芯片时钟频率可达25M。

五、 数据寄存器的读写

数据寄存器SPIDAT,地址=86H, 复位值=0000,0000B。因其内部SPI硬件实际有3个寄存器,如下图,两个缓冲器共享同一地址,但读写时操作的是不同的寄存器。

发送方向是单缓冲,只有一个发送缓冲器,因此写指令SPIDAT=X执行时,X写入的是该缓冲器,同时启动发送,数据逐位移出到MOSI线上。

接收方向是双缓冲,数据先移入接收移位寄存器,然后传输到并行读数缓冲器中。计时执行读指令X=SPIDAT,读到的X就是该缓冲器的值。该缓冲器在下一字节接收到后被刷新,因此必须及时读取。双缓冲的设计让用户不用担心读到正在移位的中间值。

单片机SPI功能应用笔记

六、 实际传输时序图解

模块采用了动态扫描驱动方式,每隔1ms刷新显示1位数码管,下图就是用逻辑分析仪采集的一次刷新时序,可见主机发送了2个字节,前一字节是段码,后一字节是位码。图中标示了一个字节发送的各个步骤。

单片机SPI功能应用笔记

第1行信号为SPI时钟,时钟极性设置为CPOL=0,表示待机时为低电平,脉冲为高电平。时钟相位设置为CPHA=0,表示脉冲前沿为上升沿(从机采样读取数据)、脉冲后沿为下降沿(主机放数据)。

第2行信号为SPI数据,在逻辑分析仪软件中指定SPI协议后,软件能识别出数据内容并用蓝色条标示。

第3、4两行与SPI协议无关,用于观察写指令及进出中断的时间。第5行是595芯片的锁存信号,上升沿时锁存并输出。

该传输采用了较慢的时钟频率,可以看出传输占时较长。如果MPU任务繁重,应采用中断方式来处理连续发送。

七、 SPI的脉冲、相位极性

SPI协议允许用户定义脉冲和相位极性,共有4种组合。使用哪种输出,取决于从机。下面列出了4种组合发送时序。根据595“上升沿采样”的特性,组合 00、11都符合要求,实测组合10也能正常驱动显示模块,但组合01无效。

单片机SPI功能应用笔记

单片机SPI功能应用笔记

单片机SPI功能应用笔记

脉冲1相位0组合:负脉冲(待机高电平),前沿(下降沿)采样。此时,主机是下降沿采样,但模块的595总是在上升沿时采样,之所以这种方式模块也能识别出正确数据,是因为主机的数据是在脉冲后沿(上升沿)后变更的,此时从机已完成采样。
单片机SPI功能应用笔记

八、 调试中问题记录及分析

1. 逻辑分析仪采集RCK信号时出现干扰(出现少量随机低电平脉冲)

用逻辑分析仪测量,RCK路多出现干扰,但显示模块工作正常。RCK平时高电平,脉冲为低电平。干扰为随机低电平脉冲。采取以下办法查找问题:
1) 开必板:更换不同晶振、更换开发板、改用MPU其他IO口
2) 分析仪:切换不同输入端、采集点从MPU引脚改到显示模块上、重启分析仪、设置不同采样频率
3) 线路:将RCK采集线远离时钟线等
4) 软件:设置不同的SPI时钟、加大RCK锁存脉冲宽度

但问题依旧。查逻辑分析仪资料,得知:逻辑分析仪有三个重要参数:阈值电压、采样率和采样深度。阈值电压:区分高低电平的间隔。逻辑分析仪和单片机都是数字电路,它在读取外部信号的时候,多高电压识别成高电平,多高电压识别成低电平是有一定限制的。比如一款逻辑分析仪,阈值电压是:0.7~1.4V,那么当它采集外部的数字电路信号的时候,高于1.4V识别为高电平,低于0.7V识别为低电平。

开发板供电来自仿真器,怀疑供电问题导致干扰处高电平电压偏低。当给开发板单独供电后,逻辑分析仪采集信号正常!

2. 中断入口编号问题

写SPI中断服务程序时,从单片机数据手册查到中断号为9#。但调试程序时,始终无法进入中断,后来想到手机中断编号从#1到#14,而C语言可能是从#0到#13,将中断入口号修改为8,程序正常。

九、 代码(查询方式)

给出查询方式的代码,程序执行效果是4位数码管显示按秒加法计数值。

//本程序用SPI功能驱动4位数码管显示模块。使用SPI接口线:
// P1.7 SPICLK 脉冲 与模块上SCLK相连
// P1.5 MOSI 数据线 与模块上DIO相连
// 另外使用P1.3输出595的锁存信号,与模拟上的RCLK相连
// 595使用要点:脉冲SCK上升沿时采集移入数据,锁存RCK上升沿时锁存输出
// SPI主机传送模式,时钟和相位极性设置为00或11时均可

#include
unsigned char code LedChar[] = { //数码管段码字符转换表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E};
unsigned char code segbit[]={ //8位数码管位码表,本例根据电路仅用4位
0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
unsigned char LedBuff[4]={0,0,0,0}; //数码管显示缓冲区

sbit RCK=P1^3; //锁存信号,上升沿有效
sbit LED=P1^0; //标记1,用于调试
sbit LED_P11=P1^1; //标记2,用于调试
sbit LED_P12=P1^2; //标记3,用于调试

unsigned char i = 0; //动态扫描的索引
unsigned int cnt = 0; //记录 T0 中断次数
unsigned long sec = 0; //记录经过的秒数

void LED_OUT(unsigned char X)
{
LED_P11=!LED_P11; //
SPIDAT = X; //写一个字节到SPI发送缓冲区
while(!(SPISTAT & 0x80)); //查询SPIF标志位,为0时循环等待,为1时退出往下
LED_P11=!LED_P11;
SPISTAT = 0x80; //对SPIF标志位 写1置0
}

void Timer0_isr(void) interrupt 1 using 1
{
TH0 = 0xFC; //T0初值,定时 1ms
TL0 = 0x18;
cnt++; //计数值自加 1
//以下代码动态扫描刷新1位数码管显示
i++; //i从0到3循环,每次刷新1位显示
if (i == 0)
{ LED_OUT(LedBuff[0]);LED_OUT(0x01); } //发送段码、位码
else if (i == 1)
{ LED_OUT(LedBuff[1]);LED_OUT(0x02); }
else if (i == 2)
{ LED_OUT(LedBuff[2]);LED_OUT(0x04); }
else if (i == 3)
{ LED_OUT(LedBuff[3]);LED_OUT(0x08);i=0xFF; } ; //修改循环变量i
RCK=0; //向595芯片发锁存脉冲信号
RCK=1;
}

void main()
{
TMOD = 0x01; //设置T0为模式1:16位定时器,脉冲默认12T模式
TH0 = 0xFC; //为 T0 赋初值 0xFC67,定时 1ms
TL0 = 0x18;
//设置SPI控制寄存器:
SPICTL=0xD0; //高4位1101:SSIG:内控模式,1:由MSTR决定主从模式。
// SPEN:SPI功能开关,1:打开
// DORD:SPI数据顺序,1:LSB,0:MSB
// MSTR:主从机模式,1:主机
//低4位0000:CPOL时钟极性,0待机低电平,前沿上升沿,后沿下降沿
// CPHA:时钟相位,当SSIG=1时,CPHA必须为0
// SPR1/SPR0:时钟速度分别为4/16/64/1 28分频
//AUXIE |= 0x01; //开启SPI中断,APUXIE.0位为ESPI
//AUXIP |= 0x01; //SPI中断优先级为1级
//AUXIPH |= 0x01; //SPI中断优先级提为3级
//IP &= 0xFD; //T0中断优先级为0级
//IPH &= 0xFD; //T0中断优先级为0级

EA=1; //开总中断
ET0=1; //开T0中断
TR0 = 1; //启动 T0
i=0; //初始化三个全局变量
cnt=0;
sec=0;
LedBuff[0] = 0xC0; //初始化4个显示缓存
LedBuff[1] = 0xC0;
LedBuff[2] = 0xC0;
LedBuff[3] = 0xC0;
while (1)
{
if (cnt >= 1000) //T0溢出1000次为1秒
{
cnt = 0; //计数值复位
sec++; //秒计数自加 1
LED=~LED; //指示灯反相(P1.0如果接有LED可观察到闪烁)
//秒值转换为4位十进数,再查表得到段码送缓存
LedBuff[0] = LedChar[sec];
LedBuff[1] = LedChar[sec/10];
LedBuff[2] = LedChar[sec/100];
LedBuff[3] = LedChar[sec/1000];
}
}
}

转自:咕咚的博客