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
相关推荐
国科安芯9 小时前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
茯苓gao11 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉14 小时前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者15 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间16 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious16 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng20 小时前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计21 小时前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
KaifuZeng1 天前
通信与接口协议面试一、基本概念
嵌入式硬件·通信与接口协议
wuyk5551 天前
24. C 语言模块化:不是拆几个.c 文件那么简单
c语言·开发语言·stm32·单片机