目录
[3. 硬件设计](#3. 硬件设计)
[4. 软件设计](#4. 软件设计)
[4.1 smg.h 与 smg.c](#4.1 smg.h 与 smg.c)
[4.2 xpt2406.c 与 xpt2406.h](#4.2 xpt2406.c 与 xpt2406.h)
[4.3 main.c](#4.3 main.c)
[4.4 public.h 与 public.c](#4.4 public.h 与 public.c)
3. 硬件设计
(1)动态数码管
(2)ADC模块
ADC模块电路如下所示:
DOUT 脚与 DS18B20 温度传感器均连接到单片机的 P37,因此这两个外设资源不能同时使用


| XPT2046 引脚 | 接 51 的哪个引脚? | 作用(大白话) |
|---|---|---|
| BUSY(引脚 1) | 芯片给单片机发 "我正在算数据,别催" 的提示 | |
| DIN(引脚 2) | P34 | 单片机给芯片发 "测触摸屏 / 测温度" 的指令(相当于 "下单") |
| CS(引脚 3) | P35 | 单片机 "喊芯片干活"(拉低 = 喊它,拉高 = 让它歇着) |
| DCLK(引脚 4) | P36 | 单片机控制 "聊天节奏"(像打拍子,保证双方同步说话) |
| VCC(引脚 5) | 开发板电源(VCC) | 给芯片供电(让它活着) |
| GND(引脚 10) | 开发板地线(GND) | 芯片的 "接地脚"(电路必须接的) |
| XP/YP/XN/YN(引脚 6-9) | 开发板上的触摸屏 | 接触摸屏,测你点屏幕的位置 |
-
NTC1(热敏电阻)+ R26(10K 电阻)→ 测温度 NTC 是 "温度越高、电阻越小" 的元件,温度变化时,
AIN1引脚的电压会跟着变 ------XPT2046 测这个电压,就能算出当前温度(比如 25℃、30℃)。 -
R27(100K 电阻)→ 测电位器 / 电池电压 比如接个电位器(拧的旋钮),拧动时
AIN2引脚的电压会变,芯片能测出这个电压;也能接电池,测电池还剩多少电。 -
AD1(接线端子)→ 接你自己的传感器 比如接个 "光敏电阻"(亮度越亮、电阻越小),插在
AD1上,芯片就能测亮度变化。
4. 软件设计
实验目的:数码管上显示AD模块采集器电位器的电压值
4.1 smg.h 与 smg.c
smg.c
cpp
#include "smg.h"
//共阴极数码管显示0~F的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
/*******************************************************************************
* 函 数 名 : smg_display
* 函数功能 : 动态数码管显示
* 输 入 : dat:要显示的数据
pos:从左开始第几个位置开始显示,范围1-8
* 输 出 : 无
*******************************************************************************/
void smg_display(u8 dat[],u8 pos)
{
u8 i=0;
u8 pos_temp=pos-1;
for(i=pos_temp;i<8;i++)
{
switch(i)//位选
{
case 0: LSC=1;LSB=1;LSA=1;break;
case 1: LSC=1;LSB=1;LSA=0;break;
case 2: LSC=1;LSB=0;LSA=1;break;
case 3: LSC=1;LSB=0;LSA=0;break;
case 4: LSC=0;LSB=1;LSA=1;break;
case 5: LSC=0;LSB=1;LSA=0;break;
case 6: LSC=0;LSB=0;LSA=1;break;
case 7: LSC=0;LSB=0;LSA=0;break;
}
SMG_A_DP_PORT=dat[i-pos_temp];//传送段选数据
delay_10us(100);//延时一段时间,等待显示稳定
SMG_A_DP_PORT=0x00;//消音
}
}
smg.h
cpp
#ifndef _smg_H
#define _smg_H
#include "public.h"
#define SMG_A_DP_PORT P0 //使用宏定义数码管段码口
//定义数码管位选信号控制脚
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
extern u8 gsmg_code[17];
void smg_display(u8 dat[],u8 pos);
#endif
cpp
#include "smg.h" // 数码管相关的头文件(里面应该定义了LSC/LSB/LSA/SMG_A_DP_PORT等引脚、延时函数)
// 共阴极数码管显示0~F的段码数据(核心!记这个对应关系)
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
u8:是unsigned char的简写(无符号字符型,0~255);
gsmg_code数组:共阴极数码管的 "显示字典" ------
数组下标 0→对应显示数字 0→段码 0x3f;下标 1→显示 1→段码 0x06;
下标 15→显示 F→段码 0x71;(段码是控制数码管哪几段亮的二进制数,比如 0x3f=0b00111111,对应数码管 a~f 段亮,就是数字 0)。
cpp
void smg_display(u8 dat[],u8 pos) // 动态显示函数
{
u8 i=0;
u8 pos_temp=pos-1; // 把"从第pos位开始"转换成数组下标(数组从0开始,比如pos=1→下标0)
for(i=pos_temp;i<8;i++) // 从指定位置开始,遍历8个数码管
{
switch(i)//位选:选中第i个数码管(让它亮)
{
case 0: LSC=1;LSB=1;LSA=1;break; // 选中第1个数码管(最左边)
case 1: LSC=1;LSB=1;LSA=0;break; // 选中第2个
case 2: LSC=1;LSB=0;LSA=1;break; // 选中第3个
case 3: LSC=1;LSB=0;LSA=0;break; // 选中第4个
case 4: LSC=0;LSB=1;LSA=1;break; // 选中第5个
case 5: LSC=0;LSB=1;LSA=0;break; // 选中第6个
case 6: LSC=0;LSB=0;LSA=1;break; // 选中第7个
case 7: LSC=0;LSB=0;LSA=0;break; // 选中第8个(最右边)
}
// 段选:给选中的数码管送要显示的段码
SMG_A_DP_PORT=dat[i-pos_temp];// dat是要显示的数组,比如dat[0]是第1个要显示的数
delay_10us(100);// 延时1ms(10*100us),让数码管亮一会儿,人眼能看到
SMG_A_DP_PORT=0x00;// 消隐:清空段码,避免切换时出现重影
}
}
位选(LSC/LSB/LSA) :这 3 个引脚是数码管的 "选通脚"------ 通过高低电平组合,选中 8 个数码管中的某一个(比如LSC=1,LSB=1,LSA=1就只让第 1 个数码管亮);
段选(SMG_A_DP_PORT):是数码管的 "段脚"(控制 a~g 和小数点 DP),给这个端口送段码,选中的数码管就显示对应数字;
消影(SMG_A_DP_PORT=0x00):切换数码管时清空段码,防止前一个数码管的光残留,导致显示模糊。
4.2 xpt2406.c 与 xpt2406.h
xpt2406.c
cpp
#include "xpt2046.h"
#include "intrins.h"
/*******************************************************************************
* 函 数 名 : xpt2046_wirte_data
* 函数功能 : XPT2046写数据
* 输 入 : dat:写入的数据
* 输 出 : 无
*******************************************************************************/
void xpt2046_wirte_data(u8 dat)
{
u8 i;
CLK = 0;
_nop_();
for(i=0;i<8;i++)//循环8次,每次传输一位,共一个字节
{
DIN = dat >> 7;//先传高位再传低位
dat <<= 1;//将低位移到高位
CLK = 0;//CLK由低到高产生一个上升沿,从而写入数据
_nop_();
CLK = 1;
_nop_();
}
}
/*******************************************************************************
* 函 数 名 : xpt2046_read_data
* 函数功能 : XPT2046读数据
* 输 入 : 无
* 输 出 : XPT2046返回12位数据
*******************************************************************************/
u16 xpt2046_read_data(void)
{
u8 i;
u16 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_read_adc_value
* 函数功能 : XPT2046读AD数据
* 输 入 : cmd:指令
* 输 出 : XPT2046返回AD值
*******************************************************************************/
u16 xpt2046_read_adc_value(u8 cmd)
{
u8 i;
u16 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;
}
xpt2046.h
cpp
#ifndef _smg_H
#define _smg_H
#include "public.h"
#define SMG_A_DP_PORT P0 //使用宏定义数码管段码口
//定义数码管位选信号控制脚
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
extern u8 gsmg_code[17];
void smg_display(u8 dat[],u8 pos);
#endif
cpp
// 示例:xpt2046.h里的引脚定义(你开发板的实际引脚以自己的头文件为准)
sbit CLK = P3^7; // 时钟引脚(对应之前讲的DCLK)
sbit DIN = P3^5; // 单片机→XPT2046的指令脚(对应DIN)
sbit DOUT = P3^4; // XPT2046→单片机的数据线(对应DOUT)
sbit CS = P3^6; // 片选脚(对应$\overline{CS}$)
u8 = unsigned char(0~255),u16 = unsigned int(0~65535,存 12 位数据够用);
_nop_()是 51 的空操作函数(短延时,让硬件有时间反应)
① 函数 1:xpt2046_wirte_data(给 XPT2046 "发指令")
函数的核心是按 "高位先发" 规则,用 CLK 上升沿同步,逐位发送 8 位指令给 XPT2046
cpp
void xpt2046_wirte_data(u8 dat)
{
u8 i;
CLK = 0; // 时钟先拉低,初始化
_nop_(); // 短延时,等硬件响应
for(i=0;i<8;i++)// 发8位指令(1个字节),循环8次
{
DIN = dat >> 7; // 取dat的最高位,放到DIN引脚(XPT2046要先收高位)
dat <<= 1; // dat左移1位,下一位变成最高位(比如0101→1010)
CLK = 0; // 👇 注释小错:实际是"CLK从0→1产生上升沿",触发XPT2046读DIN
_nop_();
CLK = 1;
_nop_();
}
}
把单片机的 8 位 "指令"(比如 "测触摸屏 X 轴""测温度")通过 DIN引脚发给 XPT2046,按 "高位先传" 的规则( XPT2046 的通信协议规定:接收数据时必须先收最高位(第 7 位),再依次收低位(第 6 位→第 0 位) ,就像我们读数字 "1234" 要先读 1,再读 2、3、4,不能倒着读。) ,用CLK时钟同步(每发 1 位,CLK 就跳一下,告诉 XPT2046 "该读这一位了")
假设要发送的指令 dat=0x90(二进制10010000,测触摸屏 X 轴的指令):
| 代码行 | 执行动作(以 dat=0x90 为例) | 通俗解释 |
|---|---|---|
CLK = 0; |
CLK 引脚拉低(电平 0) | 先把 "节奏拍子" 归零 |
_nop_(); |
空等 1us | 等硬件反应过来 |
for(i=0;i<8;i++) |
开始循环 8 次,发 8 位数据 | 一次发 1 位,8 次发完 1 个字节 |
DIN = dat >> 7; |
第一次循环:dat=10010000 → 右移 7 位 = 1 → DIN=1第二次循环:dat 左移后 = 00100000 → 右移 7 位 = 0 → DIN=0...第八次循环:最后 1 位发送 | 每次只取当前 dat 的最高位,放到 DIN 引脚(XPT2046 只认这一位) |
dat <<= 1; |
第一次循环:dat=10010000 → 左移 1 位 = 00100000第二次循环:dat=00100000 → 左移 1 位 = 01000000... | 把下一位 "推" 到最高位,方便下次读取 |
CLK = 0; |
CLK 保持 0 | 准备敲 "拍子" |
CLK = 1; |
CLK 从 0→1(上升沿) | 敲一下拍子,告诉 XPT2046"快读 DIN 上的数!" |
注意:
① _nop_()的作用:51 单片机执行_nop_()约 1 微秒(us),是给硬件留的 "反应时间"------ 如果去掉,CLK 跳变太快,XPT2046 来不及读 DIN 引脚的电平,会导致数据传错
② CLK 上升沿触发:XPT2046 只在CLK从 0→1 的瞬间读取 DIN 的值,其他时间 DIN 怎么变都没用
二、函数 2:xpt2046_read_data(从 XPT2046 "收结果")
按照 XPT2046 的通信规则,逐位读取 12 位模数转换结果(0~4095),并把结果返回。12 位数据超过了 8 位(u8)的存储范围,所以用 u16(16 位)来存
cpp
u16 xpt2046_read_data(void)
{
u8 i; // 循环计数器(数读取的位数)
u16 dat=0; // 存12位结果(u16=0~65535,够装12位;用u8会丢数据)
CLK = 0; // 时钟先拉低,初始化通信节奏
_nop_(); // 空操作延时≈1us,等硬件稳定(XPT2046需要反应时间)
// 循环12次:每次读1位,12次读完所有ADC结果
for(i=0;i<12;i++)
{
dat <<= 1; // 把dat左移1位,腾出「最低位」装新读到的数(核心!)
CLK = 1; // 时钟拉高,告诉XPT2046「准备输出下一位数据」
_nop_();
CLK = 0; // 核心:时钟从1→0产生「下降沿」,触发XPT2046把1位数据放到DOUT引脚
_nop_();
dat |= DOUT; // 读取DOUT引脚的电平(0/1),装进dat的最低位(先读高位、后读低位)
}
return dat; // 返回完整的12位ADC结果
}
从 **DOUT **引脚读取 XPT2046 转换好的 12 位数字量,按 "高位先读" 的规则 ,用CLK时钟同步,最后返回完整的 12 位结果。
注意:
① 为什么用|=而不是=?
如果用dat = DOUT;:每次都会把dat清空,只留当前位,最后只剩最后 1 位(全错);
用dat |= DOUT;:只修改最右的空位,保留之前装的所有位,逐步累加出完整的 12 位。
② 为什么先左移,再装数据?
因为 XPT2046 是 "高位先输出",先读的高位要放在dat的 "左边(高位)",后读的低位放 "右边(低位)"。比如先读的 1(最高位),左移 12 次后会跑到dat的第 11 位(16 格盒子的第 11 格),刚好对应 12 位数据的最高位,不会乱序。
直接调换顺序绝对不行:会导致最高位丢失、数据整体错位,读出来的 ADC 值完全错误;
老老实实按 "左移→触发→装数据" 的顺序写,这是贴合 XPT2046 通信规则、最不容易出错的写法
三、函数 3:xpt2046_read_adc_value(完整读取 ADC 值,直接调用)
整合 "选中芯片→发指令→等转换→读数据→关闭芯片" 的完整流程,是新手实际调用的核心函数 ------ 只需要传 "指令 cmd",就能拿到对应的 12 位 ADC 结果
这个函数是 XPT2046 通信的 "上层封装",把底层读写函数和通信时序整合,新手只需传指令cmd就能拿到 ADC 值
核心逻辑:选中芯片→发指令→等转换→清 BUSY→读数据→关闭芯片,顺序不能乱
cpp
u16 xpt2046_read_adc_value(u8 cmd)
{
u8 i;
u16 adc_value=0;
CLK = 0;// 初始化时钟线(拉低),对应XPT2046通信时序的Idle状态
CS = 0;// 拉低片选脚(CS低电平有效),选中XPT2046(喊芯片"干活")
xpt2046_wirte_data(cmd);// 给芯片发8位功能指令(比如测X轴/温度)
for(i=6; i>0; i--);// 简易延时(≈6us),等XPT2046完成模数转换(替代检测BUSY引脚)
CLK = 1;
_nop_();
CLK = 0;// CLK产生1个上升沿+下降沿,清除芯片的BUSY状态(确保数据稳定)
_nop_();
adc_value=xpt2046_read_data();// 读取12位ADC转换结果
CS = 1;// 拉高片选脚,关闭XPT2046(通信结束,省电)
return adc_value;// 返回最终的12位ADC值(0~4095)
}
| 代码行 | 核心作用 | 新手必懂的原因 |
|---|---|---|
CLK = 0; |
初始化时钟线 | XPT2046 的通信要从 CLK 低电平开始,就像聊天前先 "安静下来" |
CS = 0; |
使能 XPT2046 | CS 是 "片选脚",低电平 ="选中这个芯片";如果 CS 不拉低,后续发指令、读数据都无效(芯片根本不搭理你) |
xpt2046_wirte_data(cmd); |
发功能指令 | 告诉芯片 "这次要测什么"(比如 cmd=0x90 测 X 轴,cmd=0x80 测温度),对应时序图的 "发指令阶段" |
for(i=6; i>0; i--); |
等待转换 | XPT2046 收到指令后,需要≈6us 时间把模拟电压转成数字(ADC 转换);这个空循环是 "新手友好版"------ 不用检测 BUSY 引脚,简单粗暴但够用 |
CLK = 1; _nop_(); CLK = 0; |
清除 BUSY | XPT2046 转换完成后,BUSY 引脚会拉低,但这里用 "CLK 跳一下" 的方式强制清除 BUSY 状态,确保接下来读数据时,芯片输出的是 "完整的 12 位结果",不是半成品 |
adc_value=xpt2046_read_data(); |
读转换结果 | 调用底层读数据函数,把 12 位数字量读出来,存到 adc_value 里 |
CS = 1; |
关闭芯片 | 拉高 CS,芯片回到 Idle 状态(省电),也避免后续误触发(比如其他信号干扰 CLK/DIN) |
① 延时循环for(i=6; i>0; i--)可以改吗?
可以!这是代码里最灵活的地方:
为什么是 6?:12MHz 晶振的 51 单片机,1 个机器周期 = 1us,i=6≈6us,刚好满足 XPT2046 的最小转换时间;
什么时候改大?:如果读出来的 ADC 值乱跳、不准(比如触摸屏坐标忽大忽小),把 i 改成 8/10/15,给芯片更多转换时间;
什么时候改小?:晶振是 11.0592MHz(周期≈1.085us),i=6≈6.5us,也够用,不用改。
短延时用空循环,长延时用延时函数:嵌入式底层编程的通用习惯 ------ 短延时(μs 级)用空循环,长延时(ms 级)用标准延时函数。
② 清除 BUSY 的操作CLK=1;_nop_();CLK=0;能省吗?
绝对不能省!
- 省略后,可能读到 XPT2046 "还在转换中" 的无效数据(比如前 8 位是对的,后 4 位是随机 0/1);
- 这一步是 "兼容时序图" 的关键,对应时序图里 "发送时钟清除 BUSY" 的步骤,确保数据输出稳定。
4.3 main.c
核心流程:读取ADC值(0x94指令)→ 5V参考电压转换 → 放大10倍保留1位小数 → 组装带小数点的段码 → 数码管显示X.XV;
cpp
void main()
{
u16 adc_value=0; // 存12位ADC值(0~4095)
float adc_vol; // 存实际电压(比如2.5V,浮点型才能存小数)
u8 adc_buf[3]; // 数码管显示缓存:[0]整数位+小数点 [1]小数位 [2]单位V
while(1) // 死循环:持续采集+显示(嵌入式程序必须,否则只执行一次)
{
// 步骤1:读取电位器的ADC值(核心!指令0x94是关键)
adc_value=xpt2046_read_adc_value(0x94);// 例子:返回2048
/* 指令0x94解释:二进制10010100
A2A1A0=110 → 测外部模拟输入(电位器接的AIN0/AUXIN引脚)
PD1PD0=00 → 智能省电模式(之前讲的最优模式)
*/
// 步骤2:ADC值转实际电压(12位ADC通用公式)
adc_vol=5.0*adc_value/4096; // 例子:5.0*2048/4096=2.5V
/* 公式原理:
12位ADC的最大值4095对应参考电压5V,最小值0对应0V
必须写5.0(浮点型),写5会变成整数运算(2048*5/4096=2,丢失小数)
*/
// 步骤3:放大10倍,保留1位小数(适配数码管显示)
adc_value=adc_vol*10; // 例子:2.5*10=25(把小数转成整数,方便拆分)
/* 为什么放大?:
数码管不能直接显示小数点,把2.5V转成25,拆成2(十位=整数位)和5(个位=小数位)
若不放大:2.5直接取整是2,丢失小数位,显示"2.0V",不精准
*/
// 步骤4:组装数码管段码(核心!拆位+加小数点)
// 例子:adc_value=25 → 25/10=2(整数位),gsmg_code[2]=0x5b(数字2的段码)
// |0x80:0x80是小数点的段码(二进制10000000),或运算后点亮小数点→0x5b|0x80=0xdb(显示"2.")
adc_buf[0]=gsmg_code[adc_value/10]|0x80;
// 例子:25%10=5(小数位),gsmg_code[5]=0x6d(数字5的段码,显示"5")
adc_buf[1]=gsmg_code[adc_value%10];
// 0x3e:共阴极数码管显示"V"的段码(二进制00111110),对应电压单位
adc_buf[2]=0x3e; // 显示单位V
// 步骤5:数码管显示(从第6位开始显示3个元素)
smg_display(adc_buf,6);
/* 显示效果:
8位数码管,第6位显示"2.",第7位显示"5",第8位显示"V"
最终:□□□□□2.5V(前5位空,后3位显示电压+单位)
*/
}
}
① 指令 0x94是什么意思?
cpp
adc_value=xpt2046_read_adc_value(0x94);//测量电位器
0x94是二进制10010100,对应 XPT2046 的功能选择规则:
A2A1A0=110:测外部模拟输入(AUXIN,对应电位器的引脚);
PD1PD0=00:智能省电模式(之前讲的最优模式);
这是 "指定 XPT2046 采集电位器电压" 的专用指令,替换成其他指令(比如 0x90)就会测触摸屏 X 轴,而非电位器。
② ADC 值转实际电压(核心公式)
cpp
adc_vol=5.0*adc_value/4096;//将读取的AD值转换为电压
XPT2046 的参考电压是 5V(开发板默认接 5V);
12 位 ADC 的数值范围是0~4095(2¹²-1),对应电压范围0~5V;
电压计算公式:实际电压 = 参考电压 × ADC值 / 最大ADC值;比如 ADC 值 = 2048 → 电压 = 5.0×2048/4096=2.5V(刚好一半)。
注意:必须写5.0而非5------5是整数,计算会取整(比如 2048×5/4096=2,而非 2.5),5.0是浮点型,保证计算精度。
③ 处理电压值,适配数码管显示
cpp
adc_value=adc_vol*10;//放大10倍,即保留小数点后一位
数码管无法直接显示小数点,用 "放大 10 倍" 的方式拆分整数 / 小数位
比如电压 = 2.5V → 2.5×10=25 → 整数位 = 25/10=2,小数位 = 25%10=5 → 数码管显示 "2.5"。
如果不放大:直接取整会丢失小数(2.5→2),显示不精准;
放大 10 倍:刚好保留 1 位小数,适配数码管的显示逻辑。
④ 组装数码管显示的段码
cpp
adc_buf[0]=gsmg_code[adc_value/10]|0x80; // 整数位+小数点
adc_buf[1]=gsmg_code[adc_value%10]; // 小数位
adc_buf[2]=0x3e;//显示单位V
adc_value/10:取放大后数值的 "十位"(即电压的整数位);gsmg_code[adc_value/10]:查数码管段码(比如 2→0x5b);|0x80:0x80 是小数点 DP 的段码(二进制 10000000),"或运算" 后会点亮小数点→比如 0x5b|0x80=0xdb,数码管显示 "2."。
adc_value%10:取放大后数值的 "个位"(即电压的小数位)→比如 25%10=5→gsmg_code [5]=0x6d,显示 "5"。0x3e:是字母 "V" 的共阴极数码管段码(二进制 00111110),对应数码管显示 "V"(电压单位)。
⑤ 数码管显示
cpp
smg_display(adc_buf,6); // 从第6位开始显示adc_buf的3个元素
smg_display(数组, 起始位置):之前讲的动态显示函数;- 从第 6 位开始显示:数码管共 8 位,第 6 位显示 "X.",第 7 位显示 "X",第 8 位显示 "V"→最终效果:
□□□□□2.5V(前 5 位空,后 3 位显示电压 + 单位)。
注意:
- 指令 0x94 不能乱改:改了会采集错误的信号(比如 0x90 测 X 轴,0x80 测温);
- 参考电压要匹配 :如果开发板 XPT2046 的参考电压是 3.3V,公式要改成
3.3*adc_value/4096,否则电压计算错误; - 0x80 的作用:共阴极数码管的小数点段码是 0x80,共阳极是 0x7f(如果数码管是共阳极,这里要改);
- 0x3e 是 V 的段码:如果数码管显示乱码,检查段码是否匹配(共阴 / 共阳)。
4.4 public.h 与 public.c
public.c
cpp
#include "public.h"
/*******************************************************************************
* 函 数 名 : delay_10us
* 函数功能 : 延时函数,ten_us=1时,大约延时10us
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
/*******************************************************************************
* 函 数 名 : delay_ms
* 函数功能 : ms延时函数,ms=1时,大约延时1ms
* 输 入 : ms:ms延时时间
* 输 出 : 无
*******************************************************************************/
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
public.h
cpp
#ifndef _public_H
#define _public_H
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
typedef unsigned long u32;
void delay_10us(u16 ten_us);
void delay_ms(u16 ms);
#endif