说明
本人使用的是清翔的51单片机开发板,如果型号相同最方便,但是如果型号不同也可以参考,因为芯片都是一样的,只是外设不同而已,使用时只需要对照自己的开发板原理图稍微修改下引脚即可。
本次笔记对应视频教程的第38,39,40集 数模转换与模数转换(理论+实践)
如果笔记之中有任何错误,请在评论区指出,谢谢
目录
一、 AD/DA转换的基本概念
1.1 模拟量和数字量
1.2 主要技术指标
1.2.1 分辨率
就是说假设满量程10V,而12位ADC能存储个不同的数,所以分辨率为10/
1.2.2 量化误差
只有当模拟量变化大小超过了ADC的分辨率,ADC数值才会增加,当模拟量变化大小<分辨率,那么ADC就不会有变化。比如分辨率是2.4m,那么当变化范围在0~2.4mv之间时,ADC数值不会有变化。
1.2.3 其他技术指标
- 偏移误差:输入为0,输出不为0
- 满刻度误差(增益误差):输入达到最大,ADC数值却不是最大。
- 线性度:实际转换与理想直线的偏差
- 绝对精度:实际输入与理论输入差的最大值
- 转换速率:每秒能转换的次数,是完成一次转换所需时间的倒数
板载的比较器是主次逼近式,大约需要8ms
二、主次逼近式ADC的原理
VREF:参考电压
光敏电阻:电阻随光照值迅速减小
热敏电阻:大多为负温度系数负温度系数,又称NTC热敏电阻,是一种电阻值随温度增大而减小的一种传感器电阻。
电位器:实际上就是滑动变阻器
三、XPT2046芯片
XPT2046是一款四线制电阻式触摸屏控制器,内含12位分辨率125KHz转换速率 逐步逼近型A/D转换器,XPT2046支持从1.5V到5.25V的低电压I/O接口,采用三线制SPI通信接口
4路ADC,XP,YP,VBAT,AUX
原理图
数据手册所给时序图
根据原理图
原理图通道 | CH0 | CH1 | CH2 | CH3 |
用途 | 光敏电阻 | 热敏电阻 | 电位器 | 外部输入 |
芯片通道 | XP | YP | VBAT | AUX |
654位 | 001 | 101 | 010 | 110 |
16进制 | 0x94 | 0xD4 | 0xA4 | 0xE4 |
A2,A1,A0配置如图
四、编程 - 使用通道3采集电压
4.1 创建工程
复制上一份工程文件夹,修改名称为“13.数模转换和模数转换”,进入项目文件夹,打开工程文件,删除main.c函数的内容。
4.2 main.c
注意我修改了一下数码管显示代码,我觉得修改后显示数字重叠现象更少,显示更清晰
void SEG_DIS(uchar position, uchar number)
{
P0 = 0xFF;
DU = 0;
WE = 0;
WE = 1;
P0 = we[position - 1];
WE = 0;
P0 = 0x00;
DU = 1;
P0 = du[number];
DU = 0;
delay(1);
}
main.c如下
void main()
{
uchar i;
uint vol; //测量电压存储在这个变量
while(1)
{
//数码管扫描
//第一个数字还要显示小数点,要么在字库数组里面添加小数点的表示,或者不用显示函数直接给数据
P0 = 0xFF;
DU = 0;
WE = 0;
WE = 1;
P0 = 0xfe;
WE = 0;
P0 = 0x00;
DU = 1;
P0 = du[vol / 1000 % 10] | 0x80;
DU = 0;
delay(1);
SEG_DIS(2, vol /100 % 10);
SEG_DIS(3, vol / 10 % 10);
SEG_DIS(4, vol % 10);
i++;
if (i >= 100)
{
i = 0;
vol = ReadAD(AD_CH0);
vol *= 1.2207; //分辨率,5/(2^12) = 1.220703125mV
}
}
}
其实显示第一个数码管的小数点可以用下面这种办法
SEG_DIS(1,vol / 1000 % 10);
SEG_DIS(1,22);
//第1行显示数字,第二行显示小数点
//只需要在字库数组里面添加上小数点的字模就行了
uchar code du[]={
//0 1 2 3 4 5 6 7 8
0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F,
//9 A(10) B(11) C(12) D(13) E(14) F(15) H(16) L(17)
0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x76, 0x38,
//n(18) u(19) -(20) 熄灭(21) .(22)
0x37, 0x3E, 0x40, 0x00, 0x80};
ADC通信部分代码
//写入数据,选择让哪个ADC工作
void ADC_Write(uchar channel)
{
uchar i;
for (i = 0; i < 8; i++)
{
DCLK = 0; //拉低时钟线,允许DIN数据变化
DIN = channel & 0x80; //根据时序图,先发送数据最高位
channel <<= 1;
DCLK = 1;
}
}
//接收ADC芯片返回的12位数值
uint ADC_Read()
{
uchar i;
uint DAT;
for (i = 0; i < 12; i++)
{
DAT <<= 1; //先接收最高位
DCLK = 1; //产生下降沿,让芯片输出1位数字
DCLK = 0;
DAT |= DOUT;
}
return (DAT); //把接收到的数据返回
}
//一个完整的读取ADC值的过程
uint ReadAD(uchar channel)
{
uint DAT;
CS = 0;
ADC_Write(channel);
DCLK = 0;
delay(1);
DAT = ADC_Read();
CS = 1;
return(DAT);
}
4.3 main.h
写函数和变量声明
/************ 定义ADC4个通道 ********************/
#define AD_CH0 0x94 //光敏电阻
#define AD_CH1 0xD4 //热敏电阻
#define AD_CH2 0xA4 //电位器
#define AD_CH3 0xE4 //外部输入
/************** ADC相关函数 *************************/
void ADC_Write(uchar channel);
uint ADC_Read();
uint ReadAD(uchar channel);
/******** ADC **************/
sbit DOUT = P2^5;
sbit DIN = P2^0;
sbit CS = P3^7;
sbit DCLK = P2^1;
这样就可以了。编译下载后,就可以在数码管看到显示的数字。当用CH3的时候,可以用杜邦线把板子上的AIN3排针和5V或者3.3V连接起来看是否工作正常,也可以换成别的CH通道,实现光敏/热敏/电位器调节显示数字。
4.4 现象
用CH3通道,杜邦线连接AIN3和VCC
五、PWM调制
5.1 PWM概述
PWM 是脉宽调制(Pulse Width Modulation)的缩写。它是一种常用的调制技术,用于控制模拟信号的电平。
PWM 基本上是通过将一个周期性信号分为两个部分(通常称为高电平和低电平),来模拟出不同的电平。在一个固定的时间周期内,高电平和低电平的持续时间(也称为脉冲宽度)可以调整,从而使得信号的平均电平产生变化。
PWM 的使用非常广泛,特别是在电子和电气工程领域中。它常被应用于电机控制、电源管理、DA 转换等方面。通过调整脉冲的占空比(高电平占整个周期的百分比),可以精确地控制输出信号的电平,实现对设备的精细控制。
例如,在电机控制中,PWM 可以被用来控制电机的速度和转向。通过改变 PWM 的占空比,可以调节电机的转速。较大的占空比会导致更高的电机转速,而较小的占空比会导致较低的电机转速。
5.2 LM358运放
可见LM358芯片有2路运放,内部原理图如下
开发板上的原理图
输出DAOUT大概60mA,但是仍不足以驱动电机
5.3 定时器产生PWM
原理:通过定时器中断,在中断里面判断是否需要改变引脚电平,从而设定PWM的周期和占空比。
5.4 main.c
可以继续在原来工程写,也可以在新建一个工程我就直接在原来工程上面添加代码了。
在main开头添加一个定时器0初始化
uchar DAC_VAL;//占空比参数 模拟8位DA输出,取值范围0-255
uchar pwm_t;//周期
//周期可以通过debug查看运行时间,简单计算就是pwm_t从0~255一共256个数,定时器设置的溢出时间*256
//简单计算没有考虑进入中断函数的时间,比较粗略
uchar i;
uint vol; //测量电压存储在这个变量
void main()
{
timer0init(0x02, 220,220);
DAC_VAL = 126;
while(1)
{
//数码管扫描
//第一个数字还要显示小数点,要么在字库数组里面添加小数点的表示,或者不用显示函数直接给数据
P0 = 0xFF;
DU = 0;
WE = 0;
WE = 1;
P0 = 0xfe;
WE = 0;
P0 = 0x00;
DU = 1;
P0 = du[vol / 1000 % 10] | 0x80;
DU = 0;
delay(1);
SEG_DIS(2, vol /100 % 10);
SEG_DIS(3, vol / 10 % 10);
SEG_DIS(4, vol % 10);
i++;
if (i >= 100)
{
i = 0;
vol = ReadAD(AD_CH3);
vol *= 1.2207; //分辨率,5/(2^12) = 1.220703125mV
}
}
}
在定时器0的中断函数里面写
//定时器0中断
void timer0() interrupt 1
{
pwm_t++;
if (pwm_t <= DAC_VAL)
DAC_DATA = 1;
else
DAC_DATA = 0;
}
这里面的DAC_DATA等下在main.h里面定义
PWM周期大概可以这么计算:
假设自动重装值是220,从220到255有255-220+1=36个机器周期,也就是36*1.085us,又由于pwn_t是从0到255变化,共256个数值,所以PWM周期=36*1.085*256/1000 000 = 9,999.36/1000 000s,那么频率就是周期分之一, = 100.00640040962621607782898105479Hz,因此可以通过适当配置TH0和pwm_t的范围来改变周期。
5.5 main.h
/******** DAC *******************/
sfr P4 = 0xe8;
sbit DAC_DATA = P4^4;
P4端口在reg52.h里面没有定义,所以我们要自己定义
5.6 现象
使用通道3采集DAOUT的输出,也可以接上跳线帽,让DAOUT的值来控制LED的亮度。
如果要做呼吸灯,可以通过for循环来改变DAC_VAL的值实现。
本次笔记对应视频教程的第38,39,40集 数模转换与模数转换(理论+实践),到此结束。在编程过程中能感受到,使用对应芯片必须要看芯片的数据手册,看引脚说明以及时序图,了解如何配置芯片,才能写出对应的程序。
在编程时要注意数据类型,我在写接收数据的函数的时候,把接收数据类型写成了uchar,结果现象一直不对,找了半个小时才找出来。
|
下次笔记将对应视频教程的第41,42集 数字温度传感器DS18B20(理论+实践)
|
如果笔记之中有任何错误,请在评论区指出,谢谢