清翔零基础教你学51单片机_个人学习笔记(13)_数模转换与模数转换(理论+实践)

说明

本人使用的是清翔的51单片机开发板,如果型号相同最方便,但是如果型号不同也可以参考,因为芯片都是一样的,只是外设不同而已,使用时只需要对照自己的开发板原理图稍微修改下引脚即可。

本次笔记对应视频教程的第38,39,40集 数模转换与模数转换(理论+实践)

如果笔记之中有任何错误,请在评论区指出,谢谢

目录

一、 AD/DA转换的基本概念

 1.1 模拟量和数字量

 1.2 主要技术指标

1.2.1 分辨率

 1.2.2 量化误差

1.2.3 其他技术指标 

 二、主次逼近式ADC的原理

三、XPT2046芯片

 四、编程 - 使用通道3采集电压

4.1 创建工程

4.2 main.c

main.c如下

ADC通信部分代码 

4.3 main.h

4.4 现象

五、PWM调制

5.1 PWM概述

5.2 LM358运放

5.3 定时器产生PWM

5.4 main.c

5.5 main.h

5.6 现象

 ​编辑


 

一、 AD/DA转换的基本概念

 1.1 模拟量和数字量

来源于视频截图
来源于视频截图

 1.2 主要技术指标

1.2.1 分辨率

 就是说假设满量程10V,而12位ADC能存储$2^{12}$个不同的数,所以分辨率为10/$2^{12}$

 1.2.2 量化误差

只有当模拟量变化大小超过了ADC的分辨率,ADC数值才会增加,当模拟量变化大小<分辨率,那么ADC就不会有变化。比如分辨率是2.4m,那么当变化范围在0~2.4mv之间时,ADC数值不会有变化。

1.2.3 其他技术指标 

  1.  偏移误差:输入为0,输出不为0
  2. 满刻度误差(增益误差):输入达到最大,ADC数值却不是最大。
  3. 线性度:实际转换与理想直线的偏差
  4. 绝对精度:实际输入与理论输入差的最大值
  5. 转换速率:每秒能转换的次数,是完成一次转换所需时间的倒数

板载的比较器是主次逼近式,大约需要8ms

 二、主次逼近式ADC的原理

 VREF:参考电压

 光敏电阻:电阻随光照值迅速减小

热敏电阻:大多为负温度系数负温度系数,又称NTC热敏电阻,是一种电阻值随温度增大而减小的一种传感器电阻。

电位器:实际上就是滑动变阻器

三、XPT2046芯片

XPT2046是一款四线制电阻式触摸屏控制器,内含12位分辨率125KHz转换速率 逐步逼近型A/D转换器,XPT2046支持从1.5V到5.25V的低电压I/O接口,采用三线制SPI通信接口

 4路ADC,XP,YP,VBAT,AUX

原理图

 数据手册所给时序图

 根据原理图

原理图通道CH0CH1CH2CH3
用途光敏电阻热敏电阻电位器外部输入
芯片通道XPYPVBATAUX
654位001101010110
16进制0x940xD40xA40xE4

 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(理论+实践)

|

如果笔记之中有任何错误,请在评论区指出,谢谢

单片机数模转换程序 将da#include //52系列单片机头文件 #include #define uchar unsigned char #define uint unsigned int sbit dula=P2^6; //申明U1锁存器的锁存端 sbit wela=P2^7; //申明U2锁存器的锁存端 sbit adwr=P3^6; //定义AD的WR端口 sbit adrd=P3^7; //定义AD的RD端口 sbit led=P2^5; sbit DAC0832_CS = P3^2; sbit DAC0832_WR = P3^6; sbit AD_CS=P0^7; uchar code table[]={ 0x3f,0x06,0x5b,0x4f, 0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c, 0x39,0x5e,0x79,0x71}; uchar weima[] = {0xff,0xfe,0xfd,0xfb,0xf7,0xef,0xdf}; void delayms(uint xms) { uint i,j; for(i=xms;i>0;i--) //i=xms即延时约xms毫秒 for(j=110;j>0;j--); } void display(uchar bai,uchar shi,uchar ge) //显示子函数 { dula=1; P0=table[bai]|0x80; //送段选数据 dula=0; P0=0xff; //送位选数据前关闭所有显示,防止打开位选锁存时 wela=1; //原来段选数据通过位选锁存器造成混乱 P0=0x7e; //送位选数据 wela=0; delayms(1); //延时 dula=1; P0=table[shi]; dula=0; P0=0xff; wela=1; P0=0x7d; wela=0; delayms(1); dula=1; P0=table[ge]; dula=0; P0=0xff; wela=1; P0=0x7b; wela=0; delayms(1); } /*void displays(uchar a ,uchar b,uchar c) //显示子函数 { dula=1; P0=table[a]; //送段选数据 dula=0; P0=0xff; //送位选数据前关闭所有显示,防止打开位选锁存时 wela=1; //原来段选数据通过位选锁存器造成混乱 P0=0x77; //送位选数据 wela=0; delayms(1); //延时 dula=1; P0=table[b]; dula=0; P0=0xff; wela=1; P0=0x6f; wela=0; delayms(1); dula=1; P0=table[c]; dula=0; P0=0xff; wela=1; P0=0x5f; wela=0; delayms(1); } */ void displays(uchar shuzi,uchar weizhi,bit dp) { dula=1; if(dp) P0 = table[shuzi]|0x80; else P0 = table[shuzi]; dula=0; wela=1; P0 = weima[weizhi]; wela=0; } void main() // 主程序 { uint ad; uchar A1,A2,A3,adval; AD_CS=1; //置CSAD为0,选通ADCS 以后不必再管ADCS DAC0832_CS = 0; DAC0832_WR = 0; while(1) { wela=1; P0=0x7f; wela=0; adwr=1; _nop_(); adwr=0; //启动AD转换 _nop_(); adwr=1; P1=0xff; //读取P1口之前先给其写全1 adrd=1; //选通ADCS _nop_(); adrd=0; //AD读使能 _nop_(); adval=P1; ad=adval*1.941; //AD数据读取赋给P1口 adrd=1; A1=ad/100; //分出百,十,和个位 A2=ad0/10; A3=ad; P1=(~adval); P0=adval; displays(adval/100,4,0); delayms(1); displays(adval0/10,5,0); delayms(1); displays(adval,6,0); delayms(1); display(A1,A2,A3); } } ad的数据相互转换
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值