2018学生科协硬件方向培训——SPI、ADC
本周培训主要内容都在这里啦,大家记得自己尝试操作操作哦:
目录
SPI简介
SPI总线概念
- SPI接口的全称是“Serial Peripheral Interface”,意为串行外围接口
- SPI接口主要应用在
EEPROM
,FLASH
,实时时钟
,AD转换器
,还有数字信号处理器
和数字信号解码器
之间。 - SPI接口是在CPU和外围低速器件之间进行同步串行数据传输,在主器件的移位脉冲下,数据按位传输,高位在前,低位在后,为
全双工通信
,数据传输速度总体来说比I2C总线要快,速度可达到几Mbps。
SPI接口是以主从方式工作的,这种模式通常有一个主器件和一个或多个从器件,其接口包括以下四种信号:
- MOSI
主器件数据输出,从器件数据输入
- MISO
主器件数据输入,从器件数据输出
- SCLK
时钟信号,由主器件产生
- /CS
从器件使能信号,由主器件控制,低电平有效
(图中NSS)
模拟SPI编程规则
模拟SPI就是按照SPI标准的时序,采用单片机发出时钟信号,发出或接受信息的过程
模拟SPI写函数
根据以上时序图我们可以写出以下函数
void SPI_Write(uchar dat)//dat为需要写入的指令
{
uchar i;
CLK = 0;
for(i=0; i<8; i++)//dat为8位所以循环8次
{
DIN = dat >> 7; //放置最高位 DIN为数据管脚可自行定义
dat <<= 1;//SPI自高向低位传输所以每一次向右移动一位
CLK = 0; //模拟时钟信号,上升沿放置数据
CLK = 1;
}
}
模拟SPI读函数
uint SPI_Read(void) //返回dat值,此函数返回了12位,所以uchar会溢出
{
uint i, dat=0;
CLK = 0;
for(i=0; i<12; i++) //接收12位数据
{ //i表示接收位数
dat <<= 1;
CLK = 1; //模拟时钟信号
CLK = 0;
dat |= DOUT; //接收数据的管脚
}
return dat;
}
ADC简介
AD/DA的概念
模拟信号(Analog signal):
主要是与离散的数字信号相对的连续信号。模拟信号是指信息参数在给定范围内表现为连续的信号。 或在一段连续的时间间隔内,其代表信息的特征量可以在任意瞬间呈现为任意数值的信号。
——(https://baike.baidu.com/item/%E6%A8%A1%E6%8B%9F%E4%BF%A1%E5%8F%B7/706796)数字信号(Digital signal):
是离散时间信号(discrete-time signal)的数字化表示,通常可由模拟信号(analog signal)获得。数字信号指自变量是离散的、因变量也是离散的信号,这种信号的自变量用整数表示,因变量用有限数字中的一个数字来表示。
——(https://baike.baidu.com/item/%E6%95%B0%E5%AD%97%E4%BF%A1%E5%8F%B7/915663?fr=aladdin)AD: 模数转换,将模拟信号变成数字信号,便于数字设备处理。
DA: 数模转换,将数字信号转换为模拟信号与外部世界接口。
ADC主要参数
- ADC的分辨率
- 量化误差
- 偏移误差
- 满刻度误差
- 线性度
- 绝对精度
- 转换速率
- 在这里我们详细讲一下ADC的分辨率
ADC的分辨率是指使输出数字量变化一个相邻数码所需输入模拟电压的变化量。常用二进制的位数表示。
例如12位ADC的分辨率就是12位,或者说分辨率为满刻度的1/(2^12)。一个10V满刻度的12位ADC能分辨输入电压变化最小值是10V×1/(2^12 )=2.4mV。
利用SPI协议读取XPT2046的AD转化值
下图为TPX2046采集电位器电压信号的演示
XPT2046简介
- XPT2046 是一款4 线制电阻式触摸屏控制器,内含12 位分辨率125KHz 转换速率
逐步逼近
A/D 转换器。(有关ADC的类型大家可以自行百度)下表为管脚属性。
XPT2046编程
在操作芯片时,首先我们需要确定操作的时序,在用户手册中,我们可以找到这样的时序图:
我们可以看出,与XPT2046的通信主要有以下几个步骤
- 将
CS
信号拉低 - 通过
DIN
发送同步信号的指令(8位) BUSY
被拉高(注意,BUSY高电平持续一个脉冲周期)DOUT
返回数据CS
信号被拉高,数据传输结束
理解了时序图之后,我们需要借助控制命令表来确定我们所需要的命令
- 这次我们需要读取的用的是单通道ADC值,并将芯片设置为节能模式,所以我们的控制字应为
1 _ _ _ 0100
(空缺位置,可以用A2 A1 A0控制AD转化的通道)
以下为XPT2046编程代码
uint Read_AD_Data(uchar cmd)//写入命令,函数中返回uint型
{
uchar i;
uint AD_Value;
CLK = 0;//初始化时钟信号为低电平
CS = 0;//拉低片选信号
SPI_Write(cmd);//这里的函数在SPI部分
_nop_(); //等待芯片稳定
_nop_();
CLK = 0; //拉低时钟信号,以便之后读取AD值
_nop_();
_nop_();
AD_Value=SPI_Read();//读取AD值这里的函数在SPI部分
CS = 1;
return AD_Value;
}
培训代码
- 需要连接P0到数码管
- P2.2,P2.3,P2.4分别连接LSA,LSB,LSC
- P3.4,P3.5,P3.6,P3.7分别连接到DIN,CS,CLK,DOUT
- 可以根据原理图调整 A2 A1 A0的值检测不同通道的AD值
/**************************************************************************************
2018科协硬件培训——电位器AD实验
实现现象: 下载程序后数码管前4位显示电位器检测的AD值,范围是0-4095,一般达不到最大
***************************************************************************************/
#include"reg52.h"
#include"intrins.h"
#define uchar unsigned char
#define uint unsigned int
#define ulong unsigned long
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
sbit DOUT = P3^7; //输出
sbit CLK = P3^6; //时钟
sbit DIN = P3^4; //输入
sbit CS = P3^5; //片选
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
u8 disp[4];
u8 code smgduan[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
void SPI_Write(uchar dat);
uint SPI_Read(void);
uint Read_AD_Data(uchar cmd);
void DigDisplay();
void delay(u16 i);
void datapros();
void main()
{
while(1)
{
datapros();
DigDisplay();
}
}
void SPI_Write(uchar dat)
{
uchar i;
CLK = 0;
for(i=0; i<8; i++)
{
DIN = dat >> 7;
dat <<= 1;
CLK = 0;
CLK = 1;
}
}
uint SPI_Read(void)
{
uint i, dat=0;
CLK = 0;
for(i=0; i<12; i++)
{
dat <<= 1;
CLK = 1;
CLK = 0;
dat |= DOUT;
}
return dat;
}
uint Read_AD_Data(uchar cmd)
{
uchar i;
uint AD_Value;
CLK = 0;
CS = 0;
SPI_Write(cmd);
for(i=6; i>0; i--); //延时等待转换结果
CLK = 1; //发送一个时钟周期,清除BUSY
_nop_();
_nop_();
CLK = 0;
_nop_();
_nop_();
AD_Value=SPI_Read();
CS = 1;
return AD_Value;
}
void DigDisplay()
{
u8 i;
for(i=0;i<4;i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0):
LSA=0;LSB=0;LSC=0; break;//显示第0位
case(1):
LSA=1;LSB=0;LSC=0; break;//显示第1位
case(2):
LSA=0;LSB=1;LSC=0; break;//显示第2位
case(3):
LSA=1;LSB=1;LSC=0; break;//显示第3位
}
P0=disp[i];//发送数据
delay(100); //间隔一段时间扫描
P0=0x00;//消隐
}
}
void delay(u16 i)
{
while(i--);
}
void datapros()
{
u16 temp;
static u8 i;
if(i==50)
{
i=0;
temp = Read_AD_Data(0x94); // AIN0 电位器
}
i++;
disp[0]=smgduan[temp/1000];//千位
disp[1]=smgduan[temp%1000/100];//百位
disp[2]=smgduan[temp%1000%100/10];//十位
disp[3]=smgduan[temp%1000%100%10];
}