原理图
ADDA模块
写在前面
这一块学起来好惫懒…感觉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 <reg52.h>
#include <intrins.h>#define MAIN_Fosc 11059200UL //宏定义主时钟HZsbit 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; //写1elseDIN = 0; //写0DCLK = 1; //拉高时钟,让从机读DINDAT <<= 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:0xe4voltage = voltage * 1.2207 ; // (5v/4096 12位)得到1.2207 即每一个数码量对应的电压}display(voltage);Delay_Ms(5);i++;}
}
PWM方波控制舵机
这个直接上代码吧,其中有些函数移植性还不错
以后有机会来补全PWM方波的知识
这个文件是用三个键来控制舵机移动90 0 -90度,用的舵机是SG90
#include <reg52.h>
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 为模式1TH0 = HighRH; //加载T0 重载值TL0 = HighRL;ET0 = 1; //使能T0 中断TR0 = 1; //启动T0PWMOUT = 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 = 0x00keybuf[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
{
//如果一开始是高,那么中断到了之后输出低即可完成PWMif (PWMOUT == 1) //当前输出为高电平时,装载低电平值并输出低电平{
TH0 = LowRH;TL0 = LowRL;PWMOUT = 0;}//如果一开始是低,那么中断到了之后输出高即可开始产生PWMelse //当前输出为低电平时,装载高电平值并输出高电平{
TH0 = HighRH;TL0 = HighRL;PWMOUT = 1;}
}