第23章 ADC模数转换实验

第二十三章 ADC模数转换实验

1. 导入

在前面章节, 我们介绍的都是对数字信号的操作也就是 0 和 1 操作, 本章我们就来讲解如何检测外部模拟信号, 因为我们使用的 STC89C5x 单片机内部不含ADC 接口, 所以需要外接一个 ADC 转换芯片将模拟信号转换成数字信号供单片机处理。 我们开发板上集成了一个 ADC 模数转换电路, 选用的 ADC 芯片是 12 位的AD 芯片-XPT2046。

2. ADC介绍

我们知道 51 单片机系统内部运算时用的全部是数字量, 即 0 和 1, 因此对单片机系统而言, 无法直接操作模拟量, 必须将模拟量转换成数字量。 所谓数字量, 就是用一系列 0 和 1 组成的二进制代码表示某个信号大小的量。 用数字量表示同一个模拟量时, 数字位数可以多也可以少, 位数越多则表示的精度越高,位数越少表示的精度就越低。

ADC( analog to digital converter) 也称为模数转换器, 是指一个将模拟信号转变为数字信号。 单片机在采集模拟信号时, 通常都需要在前端加上 A/D 芯片

更多内容参考:【STM32】ADC的基本原理、寄存器(超基础、详细版)-CSDN博客

下面就来介绍我们开发板上面使用的芯片:

2. XPT2046芯片介绍

XPT2046 是一款 4 线制电阻式触摸屏控制器, 内含 12 位分辨率 125KHz转换速率逐步逼近型 A/D 转换器。XPT2046 支持从 1.5V 到 5.25V 的低电压I/O 接口。 XPT2046 能通过执行两次 A/D 转换查出被按的屏幕位置, 除此之外,还可以测量加在触摸屏上的压力。

XPT2046 是一种典型的逐次逼近型模数转换器( SAR ADC) , 包含了采样/保持、 模数转换、 串口数据 输出等功能。 同时芯片集成有一个 2.5V 的内部参考电压源、 温度检测电路, 工作时使用外部时钟。 XPT2046 可以单电源供电, 电源电压范围为 2.7V~5.5V。 参考电压值直接决定 ADC 的输入范围, 参考电压可以使用内部参考电压, 也可以从外部直接输入 1V~VCC 范围内的参考电压( 要求外部参考电压源输出阻抗低) 。 X、 Y、 Z、 VBAT、 Temp 和 AUX 模拟信号经过片内的控制寄存器选择后进入 ADC, ADC 可以配置为单端或差分模式。 选择 VBAT、 Temp和 AUX 时应该配置为单端模式; 作为触摸屏应用时, 应该配置为差分模式, 这可有效消除由于驱动开关的寄生电阻及外部的干扰带来的测量误差, 提高转换精度。 单端和差分模式输入配置如下图所示:

屏幕截图 2024 06 19 154446

XPT2046 数据接口是串行接口, 其典型工作时序如下图所示, 图中展示的信号来自带有基本串行接口的单片机或数据信号处理器。 处理器和转换器之间的的通信需要 8 个时钟周期, 可采用 SPI、 SSI 和 Microwire 等同步串行接口。 一次完整的转换需要 24 个串行同步时钟( DCLK) 来完成。

前 8 个时钟用来通过 DIN 引脚输入控制字节。 当转换器获取有关下一次转换的足够信息后, 接着根据获得的信息设置输入多路选择器和参考源输入, 并进入采样模式, 如果需要, 将启动触摸面板驱动器。 3 个多时钟周期后, 控制字节设置完成, 转换器进入转换状态。 这时, 输入采样- 保持器进入保持状态, 触摸面板驱动器停止工作( 单端工作模式) 。 接着的 12 个时钟周期将完成真正的模数转换。 如果是度量比率转换方式( SER/DFR= 0) , 驱动器在转换过程中将一直工作, 第 13 个时钟将输出转换结果的最后一位。 剩下的 3 个多时钟周期将用来完成被转换器忽略的最后字节( DOUT 置低) 。

屏幕截图 2024 06 19 154557

在对 XPT2046 进行控制时, 控制字节由 DIN 输入的控制字命令格式如下所示:

屏幕截图 2024 06 19 154647

3. 硬件设计

本实验使用到硬件资源如下:

  • 动态数码管

  • ADC

动态数码管电路在前面章节都介绍过, 这里就不再重复。 下面来看下开发板上 ADC 模块电路, 如下图所示:

屏幕截图 2024 06 19 154747

从上图中可以看出, 该电路是独立的, XPT2046 芯片的控制管脚接至 J33 端子上, XPT2046 芯片的 ADC 输入转换通道分别接入了 AD1 电位器、 NTC1 热敏传感器、 GR1 光敏传感器, 还有一个外接通道 AIN3 接在 DAC 模块的 J52 端子上供外部模拟信号检测

4. 软件设计

本章所要实现的功能是: 数码管上显示 AD 模块采集电位器的电压值。程序框架如下:

  • 编写数码管显示功能

  • 编写 ADC 转换函数

  • 编写主函数

#ifndef _xpt2046_H
#define _xpt2046_H

#include "public.h"

//管脚定义
sbit DOUT = P3^7; // 输出
sbit CLK  = P3^6; // 时钟
sbit DIN  = P3^4; // 输入
sbit CS   = P3^5; // 片选


//函数声明
unsigned int xpt2046_read_adc_value(unsigned char cmd);

#endif
#include "xpt2046.h"
#include "intrins.h"

// xpt2046写入数据
void xpt2046_wirte_data(unsigned char dat)
{
    unsigned char i;

    CLK = 0; // 时钟拉低
    _nop_();
    for(i = 0; i < 8; i++) // 循环8次,每次传输一位,共一个字节
    {
        DIN = dat >> 7;// 先传高位再传低位
        dat <<= 1;     // 将低位移到高位
        CLK = 0;       // CLK由低到高产生一个上升沿,从而写入数据
        _nop_();    
        CLK = 1;
        _nop_();
    }
}

// XPT2046读数据
unsigned int xpt2046_read_data(void)
{
    unsigned char i;
    unsigned int dat = 0;

    CLK = 0; // 时钟拉低
    _nop_();
    for(i = 0;i < 12; i++) // 循环12次,每次读取一位,大于一个字节数,所以返回值类型是u16
    {
        dat <<= 1;
        CLK = 1;
        _nop_();
        CLK = 0; // CLK由高到低产生一个下降沿,从而读取数据
        _nop_();
        dat |= DOUT; // 先读取高位,再读取低位。    
    }
    return dat;    
}

// XPT2046读AD数据
unsigned int xpt2046_read_adc_value(unsigned char cmd)
{
    unsigned char i;
    unsigned int adc_value=0;

    CLK = 0; // 先拉低时钟
    CS  = 0; // 使能XPT2046
    xpt2046_wirte_data(cmd); // 发送命令字
    for(i = 6; i > 0; i--); // 延时等待转换结果
    CLK = 1;
    _nop_();
    CLK = 0; // 发送一个时钟,清除BUSY
    _nop_();
    adc_value = xpt2046_read_data();
    CS = 1; // 关闭XPT2046
    return adc_value;
}
  1. 初始化时钟

CLK = 0; // 时钟拉低

  • 在数据传输开始前,将时钟信号拉低,为数据传输做准备。
  1. 循环传输数据位

for (i = 0; i < 8; i++)

  • 循环 8 次,每次传输数据的一个比特位,共一个字节。
  1. 取数据的最高位

DIN = (dat & 0x80) >> 7;

  • dat & 0x80 取出数据的最高位(即数据的第 7 位),>> 7 将其移到最低位,然后赋值给 DIN(数据输入)。
  1. 左移数据

dat <<= 1;

  • 将数据左移一位,为下一位的数据传输做好准备。
  1. 时钟信号的上升沿

CLK = 0; // CLK拉低 _nop_(); CLK = 1; // CLK拉高 _nop_();

  • CLK 从低电平拉高,产生时钟信号的上升沿,这个上升沿用于同步数据传输。

关于 _nop_()

  • _nop_() 是一个空操作指令,它的作用是让 CPU 暂停一下,确保前面的操作稳定。它通常用来调整时序,确保数据在时钟信号的上升沿时已经稳定。

  1. 初始化时钟

CLK = 0; // 时钟拉低 _nop_();

  1. 循环读取数据

for(i = 0; i < 12; i++)

  • 循环 12 次,以读取 12 位数据。
  1. 数据左移

dat <<= 1;

  1. 时钟信号生成

CLK = 1; _nop_(); CLK = 0; _nop_();

  1. 读取数据

dat |= DOUT;

  • 读取数据线 DOUT 的值,并将其与当前的 dat 进行按位或操作,将读取的位合并到 dat 中。

  1. 初始化时钟和选择信号

CLK = 0; // 先拉低时钟 CS = 0; // 使能XPT2046

  1. 发送命令

xpt2046_wirte_data(cmd); // 发送命令字

  1. 等待转换完成

for(i = 6; i > 0; i--);

  • 这个循环用于产生延时,等待 ADC 完成数据转换。具体延时时间需要根据实际硬件和 XPT2046 的要求调整。
  1. 清除 BUSY 状态

CLK = 1; _nop_(); CLK = 0; // 发送一个时钟,清除BUSY _nop_();

  1. 读取数据

adc_value = xpt2046_read_data();

  1. 关闭 XPT2046

CS = 1; // 关闭XPT2046

  1. 返回值

return adc_value;

5. 小结

对照注释这个实验应该还是比较简单的,整体简单解释一下:

根据前面 XPT2046 时序图可知, XPT2046 完成一个完整的转换需要 24 个串行时钟, 也就是需要 3 个字节的 SPI 时钟。 XPT2046 前 8 个串行时钟, 是接收 1 个字节的转换命令, 接收到转换命令了之后, 然后使用 1 个串行时钟的时间来完成数据转换( 当然在编写程序的时候, 为了得到精确的数据, 你可以适当的延时一下) , 然后返回 12 个字节长度( 12 个字节长度也计时 12 个串行时钟) 的转换结果。 然后最后 4 个串行时钟返回 4 个无效数据, 可以忽略。xpt2046_read_adc_value 函数就是按照这个时序实现。 至于xpt2046_wirte_data 和 xpt2046_read_data 函数是 IO 口模拟的 SPI 读写时序,与 DS1302 的读写是一样的。

主函数代码非常简单, 首先调用外设头文件, 然后定义一些变量存储 AD 值和电压值, 进入 while 循环, 读取电位器的 AD 值, 根据 XPT2046 单端模式输入配置表可知, 要采集 X+脚通道的信号, 则配置值为 0X94。 得到 AD 值后, 可按照如下公式计算电压值: Vref*ADC_Value/分辨率。

其中 Vref 是参考电压, XPT2046 接入的是 5V, ADC_Value 是读取的 AD 值,分辨率是 ADC 的位数( 212=4096) 。

经过公式计算后会得到小数, 所以定义了一个 float 型变量 adc_vol 存储,然后将该值放大 10 倍, 目的是保留小数后一位。 最后将得到的电压值转换为数码管段码数据显示。


2024.7.23 第一次修订

2024.8.23 第二次修订,后期不再维护

posted @ 2024-08-23 09:40  hazy1k  阅读(8)  评论(0编辑  收藏  举报