51单片机学习总结(八)ADDA(XPT2046)和PWM方波控制舵机

原理图

在这里插入图片描述在这里插入图片描述

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	//宏定义主时钟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方波控制舵机

这个直接上代码吧,其中有些函数移植性还不错
以后有机会来补全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 为模式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;
	}
}

猜你喜欢

转载自blog.csdn.net/scarecrow_sun/article/details/106584812