51单片机——ADC数模转换实验(二)

目录

[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) 开发板上的触摸屏 接触摸屏,测你点屏幕的位置
  1. NTC1(热敏电阻)+ R26(10K 电阻)→ 测温度 NTC 是 "温度越高、电阻越小" 的元件,温度变化时,AIN1引脚的电压会跟着变 ------XPT2046 测这个电压,就能算出当前温度(比如 25℃、30℃)。

  2. R27(100K 电阻)→ 测电位器 / 电池电压 比如接个电位器(拧的旋钮),拧动时AIN2引脚的电压会变,芯片能测出这个电压;也能接电池,测电池还剩多少电。

  3. 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
  1. adc_value/10:取放大后数值的 "十位"(即电压的整数位);
    • gsmg_code[adc_value/10]:查数码管段码(比如 2→0x5b);
    • |0x80:0x80 是小数点 DP 的段码(二进制 10000000),"或运算" 后会点亮小数点→比如 0x5b|0x80=0xdb,数码管显示 "2."。
  2. adc_value%10:取放大后数值的 "个位"(即电压的小数位)→比如 25%10=5→gsmg_code [5]=0x6d,显示 "5"。
  3. 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 位显示电压 + 单位)。

注意:

  1. 指令 0x94 不能乱改:改了会采集错误的信号(比如 0x90 测 X 轴,0x80 测温);
  2. 参考电压要匹配 :如果开发板 XPT2046 的参考电压是 3.3V,公式要改成3.3*adc_value/4096,否则电压计算错误;
  3. 0x80 的作用:共阴极数码管的小数点段码是 0x80,共阳极是 0x7f(如果数码管是共阳极,这里要改);
  4. 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
相关推荐
田甲3 小时前
EasyScale单总线数字调光
单片机·嵌入式硬件
电子工程师-C513 小时前
基于51单片机的环境监测及窗帘控制系统
单片机·嵌入式硬件·51单片机
ACP广源盛139246256733 小时前
GSV2231G@ACP#2231G产品规格详解及产品应用分享
嵌入式硬件·计算机外设·音视频
星一工作室3 小时前
STM32项目分享:基于stm32的旋转书架
stm32·单片机·嵌入式硬件
qq_401700414 小时前
单片机如何控制电机
单片机·嵌入式硬件
清风6666664 小时前
基于单片机的篮球比赛计时与比分控制系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
悠哉悠哉愿意5 小时前
【嵌入式学习笔记】从单片机到嵌入式过渡
笔记·单片机·嵌入式硬件·学习
Coder_Boy_5 小时前
【人工智能应用技术】-基础实战-小程序应用(基于springAI+百度语音技术)智能语音控制-单片机环境搭建
stm32·单片机·嵌入式硬件
d111111111d5 小时前
C语言中,malloc和free是什么,在STM32中使用限制是什么,该如何使用?
c语言·开发语言·笔记·stm32·单片机·嵌入式硬件·学习