写在前面
这一块学起来好惫懒…感觉51这样用起来确实没有Arduino方便, 所以对ADDA模块就是一个简单的理解加上可以移植的代码
理解
我们日常生活中的很多变量都是连续的,如光照等等,但是在单片机里,我们只能用离散的量来表示,比如1,2,3,4,5,它是5个点,表示不了这一条直线。所以我们把单片机里的一个值来对应现实生活中的一个范围,比如1.0对应生活中光照在0.95到1.05这一个范围的值,这也就是AD,即模拟转数字
在这里面,我们可以和尺子做类比,尺子有一个总长度和精确度,同样我们的XPT2046也可以来设置,比如设置5V的电压,精度给12位2进制,那么XPT2046每增加一个数码量,即+1的时候,那么它对应的电压就加了5V/4096 V
通过光敏电阻和温敏电阻,我们可以得到不同的电压阻,利用这一个电压值,再通过XPT2046的逐次逼近法,就可以得到一个个用一个值来对应以段范围的值
那么DA,数字转模拟,我们就用PWM方波来控制舵机来当作例子,首先大家要自行去了解一下PWM方波的一些性质,占空比,频率等等,我们可以利用这些不同的频率,占空比等等来控制我们舵机来转指定的角度。
/*读光敏值*/
/*********************************************************************************
* 【作 者】: 清翔电子
* 【程序功能】: 四位数码管显示AD通道0电压
********************************************************************************/
#include
#include
#define MAIN_Fosc 11059200UL //宏定义主时钟HZ
sbit CS = P3^7;
sbit DCLK = P2^1;
sbit DIN = P2^0;
sbit DOUT = P2^5;
sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选
/*====================================
使用typedef给已有数据类型取别名
====================================*/
typedef unsigned char INT8U;
typedef unsigned char uchar;
typedef unsigned char u8;
typedef unsigned int INT16U;
typedef unsigned int uint;
typedef unsigned int u16;
uint voltage;//电压值
#define AD_CH0 0x94
#define AD_CH1 0xd4
#define AD_CH2 0xa4
#define AD_CH3 0xe4
//通道0光敏cmd:0x94 通道1热敏cmd:0xd4 通道2电位器cmd:0xa4 通道3外部输入AIN3 cmd:0xe4
//共阴数码管段选表0-9
uchar code SMGduan[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};
//数码管位选码
uchar code SMGwei[] = {0xfe, 0xfd, 0xfb, 0xf7};
/*====================================
函数:void Delay_Ms(INT16U ms)
参数:ms,毫秒延时形参
描述:12T 51单片机自适应主时钟毫秒级延时函数
====================================*/
void Delay_Ms(INT16U ms)
{
INT16U i;
do{
i = MAIN_Fosc / 96000;
while(--i); //96T per loop
}while(--ms);
}
/*====================================
函数 :display(uchar i)
参数 :i 显示变量取值0-9999
返回值 :无
描述 :数码管动态显示函数
====================================*/
void display(uint i)
{
uchar q, b, s, g;
static uchar wei;
q = i / 1000;
b = i % 1000 / 100;
s = i % 100 / 10;
g = i % 10;
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = SMGwei[wei];
WE = 0;//锁存位选数据
P0 = 0XFF;//清除断码
switch(wei)
{
case 0: DU = 1; P0 = SMGduan[q] | 0x80; DU = 0; break;//0x80加上小数点
case 1: DU = 1; P0 = SMGduan[b]; DU = 0; break;
case 2: DU = 1; P0 = SMGduan[s]; DU = 0; break;
case 3: DU = 1; P0 = SMGduan[g]; DU = 0; break;
}
wei++;
if(wei == 4)
wei = 0;
}
/*====================================
函数 :SPI_Write(uchar DAT)
参数 :DAT需要发送的数据
返回值 :无
描述 :发送一个字节数据
====================================*/
void SPI_Write(uchar DAT)
{
uchar i;
for(i=0; i<8; i++) //分别写8次,每次写1位
{
DCLK = 0;//拉低时钟总线,允许DIN变化
if(DAT & 0x80)//先写数据最高位
DIN = 1; //写1
else
DIN = 0; //写0
DCLK = 1; //拉高时钟,让从机读DIN
DAT <<= 1; //为发送下一位左移1位
}
}
/*====================================
函数 :ReadByte()
参数 :无
返回值 :返回读出的数据
描述 :
====================================*/
uint SPI_Read()
{
uchar i;
uint DAT;
for(i=0; i<12; i++)//分别读12次,每次读一位
{
DAT <<= 1; //数据左移1位,准备接收一位
DCLK = 1; //拉高时钟总线,读取SDA上的数据
DCLK = 0; //拉低时钟总线,允许从机控制SDA变化
if(DOUT)
DAT |= 0X01;//为1则写1,否则不行执行写1,通过左移补0
}
return(DAT); //返回读出的数据
}
/*====================================
函数 :ReadAD(uchar cmd)
参数 :cmd XPT2046控制字节
返回值 :AD转出的数字量
描述 :读指定通道的输入的模拟量专为数字量
====================================*/
uint ReadAD(uchar cmd)
{
uint DAT;
CS = 0;
SPI_Write(cmd);
DCLK = 0; //拉低时钟总线
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
DAT = SPI_Read();
CS = 1;
return(DAT);//返回读出数据
}
void main()//main函数自身会循环
{
uchar i;
while(1)
{
if(i >= 100)//延时500毫秒 为了使得数码管的示数稳定
{
i = 0;
voltage = ReadAD(AD_CH0); //通道0光敏cmd:0x94 通道1热敏cmd:0xd4 通道2电位器cmd:0xa4 通道3外部输入AIN3 cmd:0xe4
voltage = voltage * 1.2207 ; // (5v/4096 12位)得到1.2207 即每一个数码量对应的电压
}
display(voltage);
Delay_Ms(5);
i++;
}
}
这个直接上代码吧,其中有些函数移植性还不错
以后有机会来补全PWM方波的知识
这个文件是用三个键来控制舵机移动90 0 -90度,用的舵机是SG90
#include
sbit PWMOUT = P1^7; //方波输出通道
sbit KEY1 = P3^0;
sbit KEY2 = P3^1;
sbit KEY3 = P3^2;
unsigned char HighRH = 0; //高电平重载值的高字节
unsigned char HighRL = 0; //高电平重载值的低字节
unsigned char LowRH = 0; //低电平重载值的高字节
unsigned char LowRL = 0; //低电平重载值的低字节
unsigned char KeySta[4] = { //按键的当前状态
1,1,1,1
};
void ConfigPWM(unsigned int fr, unsigned char dc);//启动PWM,fr是频率,dc为占空比
void ClosePWM(); //关闭方波输出
void KeyScan(); //按键扫描函数,需在定时中断中调用
void KeyDriver(); //按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用
void main()
{
unsigned int i;
EA = 1; //开总中断
while (1)
{
//ConfigPWM(100, 5); //频率100Hz,占空比10%
for (i=0; i<40000; i++); //不断发送ConfigPWM中的方波
//ClosePWM();
KeyScan();
KeyDriver();
}
}
/* 配置并启动PWM,fr-频率,dc-占空比 */
void ConfigPWM(unsigned int fr, unsigned char dc)
{
unsigned int high, low;
unsigned long tmp;
tmp = (11059200/12) / fr; //计算一个周期所需的计数值
high = (tmp*dc) / 100; //计算高电平所需的计数值
low = tmp - high; //计算低电平所需的计数值
high = 65536 - high + 12; //计算高电平的重载值并补偿中断延时
low = 65536 - low + 12; //计算低电平的重载值并补偿中断延时
HighRH = (unsigned char)(high>>8); //高电平重载值拆分为高低字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low>>8); //低电平重载值拆分为高低字节
LowRL = (unsigned char)low;
TMOD &= 0xF0; //清零T0 的控制位
TMOD |= 0x01; //配置T0 为模式1
TH0 = HighRH; //加载T0 重载值
TL0 = HighRL;
ET0 = 1; //使能T0 中断
TR0 = 1; //启动T0
PWMOUT = 1; //输出高电平
}
/* 关闭PWM */
void ClosePWM()
{
TR0 = 0; //停止定时器
ET0 = 0; //禁止中断
PWMOUT = 0; //输出低电平
}
/* 按键扫描函数,需在定时中断中调用 */
void KeyScan()
{
unsigned char i;
static unsigned char keybuf[4] = { //按键扫描缓冲区
0xFF, 0xFF, 0xFF, 0xFF
};
//按键值移入缓冲区
keybuf[0] = (keybuf[0] << 1) | KEY1; //按键按下的时候,KEY会变成0XFF,使得keybuf = 0x00
keybuf[1] = (keybuf[1] << 1) | KEY2;
keybuf[2] = (keybuf[2] << 1) | KEY3;
//消抖后更新按键状态
for (i=0; i<3; i++)
{
if (keybuf[i] == 0x00)
{ //连续8 次扫描值为0,即16ms 内都是按下状态时,可认为按键已稳定的按下
KeySta[i] = 0;
}
else if (keybuf[i] == 0xFF)
{ //连续8 次扫描值为1,即16ms 内都是弹起状态时,可认为按键已稳定的弹起
KeySta[i] = 1;
}
}
}
/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */
void KeyDriver()
{
unsigned char i;
static unsigned char backup[4] = {1,1,1,1};//备份按键扫描
for (i=0; i<4; i++) //循环检测4 个按键
{
if (backup[i] != KeySta[i]) //检测按键动作
{ //如果不一样,代表该按键已经被按下
if (backup[i] != 0) //按键按下时执行动作
{
if (i == 0) //按键为0,旋转-90
{
ConfigPWM(50, 3); //频率50Hz,20ms,占空比10%
for (i=0; i<40000; i++);
ClosePWM();
}
else if (i == 1) //按键为1,旋转0
{
ConfigPWM(50, 8); //频率50Hz,20ms,占空比3%
for (i=0; i<40000; i++);
ClosePWM();
}
else if (i == 2) //按键为0,旋转90
{
ConfigPWM(50, 13); //频率50Hz,20ms,占空比8%
for (i=0; i<40000; i++);
ClosePWM();
}
}
backup[i] = KeySta[i]; //刷新前一次的备份值
}
}
}
/* T0 中断服务函数,产生PWM 输出 */
void InterruptTimer0() interrupt 1
{
//如果一开始是高,那么中断到了之后输出低即可完成PWM
if (PWMOUT == 1) //当前输出为高电平时,装载低电平值并输出低电平
{
TH0 = LowRH;
TL0 = LowRL;
PWMOUT = 0;
}
//如果一开始是低,那么中断到了之后输出高即可开始产生PWM
else //当前输出为低电平时,装载高电平值并输出高电平
{
TH0 = HighRH;
TL0 = HighRL;
PWMOUT = 1;
}
}