嵌入式开发学习(STC51-16-ADC模数转换)

内容

通过ADC转换电路采集电位器AD值,将采集的AD值转换成电压值,通过数码管显示出来;

采集光敏电阻的AD值;

采集热敏电阻的AD值;

采集外部通道AIN3的电压值;

为了方便显示,我们可以通过独立按键k1-k4来切换显示哪种数据;(k1:电位器电压;k2:外部通道AIN3电压;k3:光敏电阻AD值;k4:热敏电阻AD值)

ADC介绍

简介

ADC(analog to digital converter)也称为模数转换器,是指一个将模拟信号转变为数字信号的器件;

单片机在采集模拟信号时,通常都需要在前端加上A/D芯片;

数字量和模拟量

首先我们知道51单片机系统内部运算时用的全部是数字量,即0和1,因此对单片机系统而言,无法直接操作模拟量,必须将模拟量转换成数字量才能操作;

所谓数字量,就是用一系列0和1组成的二进制代码表示某个信号大小的量;

用数字量表示同一个模拟量时,数字位数可以多也可以少,位数越多则表示的精度越高,位数越少表示的精度就越低;

主要技术指标

分辨率

ADC的分辨率是指对于允许范围内的模拟信号,它能输出离散数字信号值的个数;

这些信号值通常用二进制数来存储,因此分辨率经常用比特作为单位,且这些离散值的个数是2的幂指数;

例如:12位ADC的分辨率就是12位,或者说分辨率为满刻度的1/(2^12);

一个10V满刻度的12位ADC能分辨输入电压变化最小值是10V×1/(2^12)=2.4mV;

转换误差

转换误差通常是以输出误差的最大值形式给出,它表示A/D转换器实际输出的数字量和理论上的输出数字量之间的差别,常用最低有效位的倍数表示;

例如给出相对误差≤±LSB/2,这就表明实际输出的数字量和理论上应得到的输出数字量之间的误差小于最低位的半个字;

转换速率

ADC的转换速率是能够重复进行数据转换的速度,即每秒转换的次数;

完成一次A/D转换所需的时间(包括稳定时间),则是转换速率的倒数;

ADC转换原理

AD转换器(ADC)将模拟量转换为数字量通常要经过4个步骤:采样、保持、量化和编码;

采样:所谓采样即是将一个时间上连续变化的模拟量转换为时间上离散变化的模拟量;

如下图所示:
在这里插入图片描述
保持:将采样结果存储起来,直到下次采样,这个过程叫做保持;一般采样器和保持电路一起总称为采样保持电路;

量化:将采样电平归化为与之接近的离散数字电平,这个过程叫做量化;

编码:将量化后的结果按照一定数制形式表示就是编码;

ADC转换方法

将采样电平(模拟值)转换为数字值时,主要有两类方法:直接比较型与间接比较型;

直接比较型

就是将输入模拟信号直接与标准的参考电压比较,从而得到数字量;

常见的有并行ADC和逐次比较型ADC;

间接比较型

输入模拟量不是直接与参考电压比较,而是将二者变为中间的某种物理量在进行比较,然后将比较所得的结果进行数字编码;

常见的有双积分型ADC;

常用方法

比较常用的为逐次比较型ADC和双积分型ADC:

逐次比较型ADC

采用逐次比较法的AD转换器是有一个比较器、DA 转换器、缓冲寄存器和控制逻辑电路组成;

如下图所示:
在这里插入图片描述
基本原理是:从高位到低位逐次试探比较,就像用天平秤物体,从重到轻逐级增减砝码进行试探;

逐次比较法的转换过程是:
1 初始化时将逐次比较寄存器各位清零,转换开始时,先将逐次比较寄存器最高位置1,送入DA转换器,经DA转换后生成的模拟量送入比较器,称为U0,与送入比较器的待转换的模拟量Ux进行比较,若U0 < Ux,该位1被保留,否则被清除;
2 然后再将逐次比较寄存器次高位置1,将寄存器中新的数字量送DA转换器,输出的U0再与Ux比较,若U0 < Ux,该位1被保留,否则被清除;
3 重复此过程,直至比较寄存器最低位;转换结束后,将逐次比较寄存器中的数字量送入缓冲寄存器,得到数字量的输出;
4 逐次比较的操作过程是在一个控制电路的控制下进行的;

双积分型ADC

采用双积分法的AD转换器由电子开关、积分器、比较器和控制逻辑等部件组成;

如下图所示:
在这里插入图片描述

基本原理是:将输入电压变换成与其平均值成正比的时间间隔,再把此时间间隔转换成数字量,属于间接转换;

双积分法AD转换的过程是:
1 先将开关接通待转换的模拟量Vi,Vi采样输入到积分器,积分器从零开始进行固定时间T的正向积分,时间T到后,开关再接通与Vi极性相反的基准电压Vref,将Vref输入到积分器,进行反向积分,直到输出为0V时停止积分;
2 Vi越大,积分器输出电压越大,反向积分时间也越长;
3 计数器在反向积分时间内所计的数值,就是输入模拟电压Vi所对应的数字量,实现了AD转换;

XPT2046芯片介绍

简介

XPT2046是一款4线制电阻式触摸屏控制器,内含12位分辨率、125KHz转换速率、逐步比较型的A/D转换器;

XPT2046支持从1.5V到5.25V的低电压I/O接口;

XPT2046能通过执行两次A/D转换查出被按的屏幕位置,除此之外,还可以测量加在触摸屏上的压力;

内部自带2.5V参考电压,可以作为辅助输入、温度测量和电池监测之用,电池监测的电压范围可以从0V到6V;

XPT2046片内集成有一个温度传感器;

在2.7V的典型工作状态下,关闭参考电压,功耗可小于0.75mW;

工作温度范围为-40℃~+85℃,与ADS7846、TSC2046、AK4182A完全兼容;

主要特性

  • 工作电压范围为1.5V~5.25V
  • 支持1.5V~5.25V的数字 I/O 口
  • 内建2.5V参考电压源
  • 电源电压测量(0V~6V)
  • 内建结温测量功能
  • 触摸压力测量
  • 采用3线制SPI通信接口
  • 具有自动省电功能

芯片管脚说明

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
XPT2046是一种典型的逐次比较型模数转换器(SAR ADC),包含了采样/保持、模数转换、串口数据、输出等功能;

同时芯片集成有一个2.5V的内部参考电压源、温度检测电路,工作时使用外部时钟;

XPT2046可以单电源供电,电源电压范围为2.7V~5.5V;

参考电压值直接决定ADC的输入范围,参考电压可以使用内部参考电压,也可以从外部直接输入1V~VCC范围内的参考电压(要求外部参考电压源输出阻抗低);

模式控制

X、Y、Z、VBAT、Temp和AUX模拟信号经过片内的控制寄存器选择后进入ADC,ADC可以配置为单端或差分模式;

选择VBAT、Temp和AUX时应该配置为单端模式;作为触摸屏应用时,应该配置为差分模式,这可有效消除由于驱动开关的寄生电阻及外部的干扰带来的测量误差,提高转换精度;

单端和差分模式输入配置如下图所示:
在这里插入图片描述

命令格式

在对XPT2046进行控制时,控制字节由DIN输入的控制;

命令格式如下所示:
在这里插入图片描述
在这里插入图片描述

工作时序(SPI读写时序)

(SPI读写时序)

XPT2046数据接口是串行接口,其典型工作时序如下图所示:(图中展示的信号来自带有基本串行接口的单片机或数据信号处理器)
在这里插入图片描述
处理器和转换器之间的的通信需要8个时钟周期,可采用SPI、SSI和Microwire等同步串行接口;

一次完整的转换需要24个串行同步时钟(DCLK)来完成;

前8个时钟用来通过DIN引脚输入控制字节;

当转换器获取有关下一次转换的足够信息后,接着根据获得的信息设置输入多路选择器和参考源输入,并进入采样模式,如果需要,将启动触摸面板驱动器;

3个多时钟周期后,控制字节设置完成,转换器进入转换状态;这时,输入采样-保持器进入保持状态,触摸面板驱动器停止工作(单端工作模式);接着的12个时钟周期将完成真正的模数转换;

如果是度量比率转换方式(SER/DFR=0),驱动器在转换过程中将一直工作,第13个时钟将输出转换结果的最后一位;

剩下的3个多时钟周期将用来完成被转换器忽略的最后字节(DOUT置低);

原理图

在这里插入图片描述
由图可知,控制管脚DIN、CS、DCLK、DOUT分别连接单片机p34、p35、p36、p37口;

其中ADC的输入转换通道分别接入了AD1电位器(AIN0)、NTC1热敏传感器(AIN1)、GR1光敏传感器(AIN2),还有一个外接通道AIN3接在DAC(PWM)(数模转换)模块的J52端子上,供外部模拟信号检测;

采集电位器的电压值

思路

按照工作时序(SPI通信)编写读写程序;

根据模式控制的配置图和命令格式,如果要检测AD1电位器,即AIN0/X+管脚输入的模拟信号,则命令配置为(0x94=1001 0100)

得到AD值后,可按照如下公式计算电压值:Vref*ADC_Value/分辨率;

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

然后将该值放大10倍,即保留小数后一位;

最后将得到的电压值转换为数码管段码数据显示;

编码

User

main.c

/*
 * @Description: 采集电位器的电压值
 */
#include "public.h"
#include "smg.h"
#include "xpt2046.h"

void main()
{
	u16 adc_value = 0;
	float adc_vol; // ADC电压值
	u8 adc_buf[3];

	while (1)
	{
		adc_value = xpt2046_read_adc_value(0x94); // 测量电位器
		adc_vol = 5.0 * adc_value / 4096;		  // 将读取的AD值转换为电压
		adc_value = adc_vol * 10;				  // 放大10倍,即保留小数点后一位
		adc_buf[0] = gsmg_code[adc_value / 10] | 0x80;
		adc_buf[1] = gsmg_code[adc_value % 10];
		adc_buf[2] = 0x3e; // 显示单位V
		smg_display(adc_buf, 6);
	}
}

Public

public.h

#ifndef _public_H
#define _public_H

#include "reg52.h"

typedef unsigned int u16; // 对系统默认数据类型进行重定义
typedef unsigned char u8;

void delay_10us(u16 ten_us);
void delay_ms(u16 ms);

#endif

public.c

#include "public.h"

/**
 * @description: 延时函数,ten_us=1时,大约延时10us
 * @param {u16} ten_us 延时倍数
 * @return {*}
 */
void delay_10us(u16 ten_us)
{
	while (ten_us--)
		;
}

/**ms延时函数,ms=1时,大约延时1ms***
 * @param {u16} ms 延时倍数
 * @return {*}
 */
void delay_ms(u16 ms)
{
	u16 i, j;
	for (i = ms; i > 0; i--)
		for (j = 110; j > 0; j--)
			;
}

App/xpt2046

xpt2046.h

#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;	// 片选

// 函数声明
u16 xpt2046_read_adc_value(u8 cmd);

#endif

xpt2046.c

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

/**
 * @description: XPT2046写数据
 * @param {u8} dat 要写入的数据
 * @return {*}
 */
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_();
	}
}

/**
 * @description: XPT2046读数据
 * @return {u16} 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;
}

/**
 * @description: XPT2046读AD数据
 * @param {u8} cmd 命令
 * @return {u16} 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;
}

App/smg

smg.h

#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]; // 使“共阴极数码管显示0~F的段码数据”这个变量定义为外部可用

void smg_display(u8 dat[], u8 pos);

#endif

smg.c

#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};

/**
 * @description: 动态数码管显示函数
 * @param {u8} dat 要显示的数据
 * @param {u8} pos 从左开始第几个位置开始显示,范围1-8
 * @return {*}
 */
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;						  // 消影
	}
}

编译和结果

按F7编译,无错误,生成.hex文件,使用pz-isp将hex文件下载到单片机

结果:成功采集电位器电压值并显示出来;
在这里插入图片描述

采集光敏电阻的AD值

思路

与采集电位器电压类似,不过不需要转换AD值了,输入口不同,所以命令也不同,为0xA4

编码

只有main.c文件不同

User

main.c

/*
 * @Description: 采集电位器的AD值
 */
#include "public.h"
#include "smg.h"
#include "xpt2046.h"

void main()
{
	u16 adc_value = 0;
	u8 adc_buf[4];

	while (1)
	{
		adc_value = xpt2046_read_adc_value(0xA4); // 测量光敏电阻
		adc_buf[0] = gsmg_code[adc_value / 1000];
		adc_buf[1] = gsmg_code[adc_value % 1000 / 100];
		adc_buf[2] = gsmg_code[adc_value % 1000 % 100 / 10];
		adc_buf[3] = gsmg_code[adc_value % 1000 % 100 % 10];
		smg_display(adc_buf, 5);
	}
}

编译和结果

按F7编译,无错误,生成.hex文件,使用pz-isp将hex文件下载到单片机

结果:成功采集光敏电阻的AD值
在这里插入图片描述

采集采集热敏电阻的AD值

思路

与采集光敏电阻AD值类似,命令为0xD4

编码

只有main.c文件不同

User

main.c

/*
 * @Description: 采集热敏电阻的AD值
 */
#include "public.h"
#include "smg.h"
#include "xpt2046.h"

void main()
{
	u16 adc_value = 0;
	u8 adc_buf[4];

	while (1)
	{
		adc_value = xpt2046_read_adc_value(0xD4); // 测量热敏电阻
		adc_buf[0] = gsmg_code[adc_value / 1000];
		adc_buf[1] = gsmg_code[adc_value % 1000 / 100];
		adc_buf[2] = gsmg_code[adc_value % 1000 % 100 / 10];
		adc_buf[3] = gsmg_code[adc_value % 1000 % 100 % 10];
		smg_display(adc_buf, 5);
	}
}

编译和结果

按F7编译,无错误,生成.hex文件,使用pz-isp将hex文件下载到单片机

结果:成功采集热敏电阻的AD值
在这里插入图片描述

采集外部通道AIN3的电压值

思路

与采集电位器电压值类似,命令为0xE4

编码

只有main.c文件不同

User

main.c

/*
 * @Description: 采集外部通道AIN3的电压值
 */
#include "public.h"
#include "smg.h"
#include "xpt2046.h"

void main()
{
	u16 adc_value = 0;
	float adc_vol; // ADC电压值
	u8 adc_buf[3];

	while (1)
	{
		adc_value = xpt2046_read_adc_value(0xE4); // 测量外部输入通道AIN3
		adc_vol = 5.0 * adc_value / 4096;		  // 将读取的AD值转换为电压
		adc_value = adc_vol * 10;				  // 放大10倍,即保留小数点后一位
		adc_buf[0] = gsmg_code[adc_value / 10] | 0x80;
		adc_buf[1] = gsmg_code[adc_value % 10];
		adc_buf[2] = 0x3e; // 显示单位V
		smg_display(adc_buf, 6);
	}
}

编译和结果

按F7编译,无错误,生成.hex文件,使用pz-isp将hex文件下载到单片机

结果:成功采集外部通道AIN3的电压值
在这里插入图片描述

使用独立按键来切换显示内容

思路

我们只需要让按键按下时切换显示内容,因此我们需要中断函数来实现需求;

我这里选择外部中断0实现,下降沿触发方式,在中断函数里实现切换的功能;

定义静态全局变量key,确保能记录上次按下的值;

因为我们需要使用k3键连接的p32端口来实现外部中断,所以需要在中断函数里先消除k3键的影响;

由于进入中断函数,数码管控制程序可能来不及消影,所以要在中断函数再次消影确保显示正确;

退出中断函数时也要消除k3键的影响,否则可能会连续进入中断函数;

编码

额外加上独立按键检测程序;

User

main.c

/*
 * @Description: 使用独立按键来切换显示内容:先按下k3(相当于设置键),再按下k1-k4,显示需要的内容
 */
#include "public.h"
#include "smg.h"
#include "key.h"
#include "xpt2046.h"

static u8 key = KEY1_PRESS; // 定义全局变量,记录上次按下按键key的值,默认为key1的值

/**
 * @description: 外部中断0配置函数
 * @return {*}
 */
void exti0_init(void)
{
	IT0 = 1; // 跳变沿触发方式(下降沿)
	EX0 = 1; // 打开INT0的中断允许
	EA = 1;	 // 打开总中断
}

/**
 * @description: 外部中断0中断函数
 * @return {*}
 */
void exti0() interrupt 0 // 中断号必须对应上
{
	delay_10us(1000); // 消抖
	if (KEY3 == 0)	  // 再次判断K3键是否按下
	{
		SMG_A_DP_PORT = 0x00; // 数码管消影
		delay_ms(300);		  // 等待0.3s,消除key3的影响
		while (1)			  // 设置死循环,只要没按键按下就一直循环
		{
			key = key_scan(1);
			delay_10us(1000); // 消抖
			if (key)
			{
				delay_ms(300); // 等待0.3s,消除按下按键的影响
				return;		   // 确认有按键按下后就返回,继续执行主函数
			}
		}
	}
}

void main()
{
	u16 adc_value = 0;
	float adc_vol; // ADC电压值
	u8 adc_buf[4];

	exti0_init(); // 外部中断0配置

	while (1)
	{
		switch (key)
		{
		case KEY1_PRESS:
		{
			adc_value = xpt2046_read_adc_value(0x94); // 测量电位器
			adc_vol = 5.0 * adc_value / 4096;		  // 将读取的AD值转换为电压
			adc_value = adc_vol * 10;				  // 放大10倍,即保留小数点后一位
			adc_buf[0] = gsmg_code[adc_value / 10] | 0x80;
			adc_buf[1] = gsmg_code[adc_value % 10];
			adc_buf[2] = 0x3e; // 显示单位V
			smg_display(adc_buf, 6);
		}
		break;
		case KEY2_PRESS:
		{
			adc_value = xpt2046_read_adc_value(0xE4); // 测量外部输入通道AIN3
			adc_vol = 5.0 * adc_value / 4096;		  // 将读取的AD值转换为电压
			adc_value = adc_vol * 10;				  // 放大10倍,即保留小数点后一位
			adc_buf[0] = gsmg_code[adc_value / 10] | 0x80;
			adc_buf[1] = gsmg_code[adc_value % 10];
			adc_buf[2] = 0x3e; // 显示单位V
			smg_display(adc_buf, 6);
		}
		break;
		case KEY3_PRESS:
		{
			adc_value = xpt2046_read_adc_value(0xA4); // 测量光敏电阻
			adc_buf[0] = gsmg_code[adc_value / 1000];
			adc_buf[1] = gsmg_code[adc_value % 1000 / 100];
			adc_buf[2] = gsmg_code[adc_value % 1000 % 100 / 10];
			adc_buf[3] = gsmg_code[adc_value % 1000 % 100 % 10];
			smg_display(adc_buf, 5);
		}
		break;
		case KEY4_PRESS:
		{
			adc_value = xpt2046_read_adc_value(0xD4); // 测量热敏电阻
			adc_buf[0] = gsmg_code[adc_value / 1000];
			adc_buf[1] = gsmg_code[adc_value % 1000 / 100];
			adc_buf[2] = gsmg_code[adc_value % 1000 % 100 / 10];
			adc_buf[3] = gsmg_code[adc_value % 1000 % 100 % 10];
			smg_display(adc_buf, 5);
		}
		break;
		default:
		{
			adc_value = xpt2046_read_adc_value(0x94); // 测量电位器
			adc_vol = 5.0 * adc_value / 4096;		  // 将读取的AD值转换为电压
			adc_value = adc_vol * 10;				  // 放大10倍,即保留小数点后一位
			adc_buf[0] = gsmg_code[adc_value / 10] | 0x80;
			adc_buf[1] = gsmg_code[adc_value % 10];
			adc_buf[2] = 0x3e; // 显示单位V
			smg_display(adc_buf, 6);
		}
		break;
		}
	}
}

App/key

key.h

#ifndef _key_H
#define _key_H

#include "public.h"

// 定义独立按键控制脚
sbit KEY1 = P3 ^ 1;
sbit KEY2 = P3 ^ 0;
sbit KEY3 = P3 ^ 2;
sbit KEY4 = P3 ^ 3;

// 使用宏定义独立按键按下的键值
#define KEY1_PRESS 1
#define KEY2_PRESS 2
#define KEY3_PRESS 3
#define KEY4_PRESS 4
#define KEY_UNPRESS 0

u8 key_scan(u8 mode);

#endif

key.c

#include "key.h"

/**
 * @description: 检测独立按键是否按下,按下则返回对应键值
 * @param {u8} mode mode=0:单次扫描按键,mode=1:连续扫描按键
 * @return {u8} k1到k5的键值1-5,0表示没有按键按下
 */
u8 key_scan(u8 mode)
{
	static u8 key = 1;

	if (mode)
		key = 1;														// 连续扫描按键
	if (key == 1 && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0)) // 任意按键按下
	{
		delay_10us(1000); // 消抖
		key = 0;
		if (KEY1 == 0)
			return KEY1_PRESS;
		else if (KEY2 == 0)
			return KEY2_PRESS;
		else if (KEY3 == 0)
			return KEY3_PRESS;
		else if (KEY4 == 0)
			return KEY4_PRESS;
	}
	else if (KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1) // 无按键按下
	{
		key = 1;
	}
	return KEY_UNPRESS;
}

编译和结果

按F7编译,无错误,生成.hex文件,使用pz-isp将hex文件下载到单片机

结果:默认显示电位器电压值,按下k3键后停止显示,再次按下k1-k4键切换要显示的内容;
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/62457.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C高级 作业 day3 8/4

1.整理思维导图 2.判断家目录下&#xff0c;普通文件的个数和目录文件的个数 1 #!/bin/bash2 arr(ls -l ~ | cut -d r -f 1 | grep -w d )3 arr1(ls -l ~ | cut -d r -f 1 | grep -w -)4 echo "目录文件个数为 ${#arr[*]}"5 echo "普通文件个数为 ${#arr1[*]}&q…

小程序开发趋势:探索人工智能在小程序中的应用

第一章&#xff1a;引言 小程序开发近年来取得了快速的发展&#xff0c;成为了移动应用开发的重要一环。随着人工智能技术的飞速发展&#xff0c;越来越多的企业开始探索如何将人工智能应用于小程序开发中&#xff0c;为用户提供更智能、便捷的服务。本文将带您一起探索人工智能…

40.利用欧拉法求解微分方程组(matlab程序)

1.简述 求解微分方程的时候&#xff0c;如果不能将求出结果的表达式&#xff0c;则可以对利用数值积分对微分方程求解&#xff0c;获取数值解。欧拉方法是最简单的一种数值解法。前面介绍过MATLAB实例讲解欧拉法求解微分方程&#xff0c;今天实例讲解欧拉法求解一阶微分方程组。…

js-7:javascript原型、原型链及其特点

1、原型 JavaScript常被描述为一种基于原型的语言-每个对象拥有一个原型对象。 当试图访问一个对象的属性时&#xff0c;它不仅仅在该对象上搜寻&#xff0c;还会搜寻该对象的原型&#xff0c;以及该对象的原型的原型&#xff0c;依次层层向上搜索&#xff0c;直到找到一个名字…

vs导出和导入动态库和静态库

1. 动态库和导出和导入 1.1 动态库的导出 1. 创建新项目 新建新项目&#xff0c;选择动态链接库&#xff08;DLL&#xff09;。 填写项目名称&#xff0c;并选择项目保存的路径&#xff0c;然后点击创建。 创建完成后&#xff0c;会自动生成如下所示文件&#xff0c;可以根据…

LabVIEW使用DSA技术从X射线图像测量肺气容量

LabVIEW使用DSA技术从X射线图像测量肺气容量 相衬X射线&#xff08;PCX&#xff09;成像技术利用相邻介质之间折射率的微小差异来增强传统X射线成像通常不可见的物体的边界。事实证明&#xff0c;这一进展在一系列生物医学和材料科学中非常有益于材料表征、疾病检测以及解剖形…

C语言 — 动态内存管理(动态内存函数)

前言 本期分为三篇介绍动态内存管理相关内容&#xff0c;关注博主了解更多 博主博客链接&#xff1a;https://blog.csdn.net/m0_74014525 本期介绍动态内存函数&#xff0c;函数如何使用、函数格式、在使用在所需要的注意点及C/C程序的内存开辟区域 系列文章 第一篇&#xff…

RGB_LCD简介

文章目录 前言一、LCD原理1、构造2、分辨率3、像素格式4、LCD接口5、ATK-7016 的屏幕接口原理图 二、LCD时序1、时间参数2、行场时序1、行显示时序&#xff1a;2、场显示时序&#xff1a;3、数据同步模式4、像素时钟 三、总结四、参考资料 前言 LCD (Liquid Crystal Display)是…

数据结构——双链表

我宁愿靠自己的力量&#xff0c;打开我的前途&#xff0c;而不愿求有力者垂青 文章目录 双线向链表各接口函数名或变量名 双向链表接口实现源码 快速索引【头文件及函数声明】 双向链表接口实现 双向链表的构造分析 双向链表的定义及初始化 双向链表的插入和删除 往期…

音频客观感知MOS对比,对ViSQOL、PESQ、MosNet(神经网络MOS分)和polqa一致性对比和可信度验证

原创&#xff1a;转载需附链接&#xff1a; https://blog.csdn.net/qq_37100442/article/details/132057139?spm1001.2014.3001.5502 一、背景 Mos分评价音质重要指标&#xff0c;最近也有很多机构和公司在研究适合自己的评价体系。目前Mos分主要分为主观评测和客观感知评价。…

智能仪表板DevExpress Dashboard v23.1亮点 - 增强对自定义导出的支持

DevExpress Dashboard v23.1版本增强了自定义导出到Excel的功能等&#xff0c;欢迎下载最新版本体验&#xff01; DevExpress Dashboard v23.1正式版下载(Q技术交流&#xff1a;523159565&#xff09; 所有平台 导出自定义仪表板项目到Excel 用户现在可以在WinForms和Web应…

危大工程智慧工地源码,微服务+Java+Spring Cloud +UniApp +MySql 物联网、人工智能、视频AI分析

一套智慧工地管理平台源码&#xff0c;PC端移动APP端可视货数据管理端源码 智慧工地可视化系统利用物联网、人工智能、云计算、大数据、移动互联网等新一代信息技术&#xff0c;通过工地中台、三维建模服务、视频AI分析服务等技术支撑&#xff0c;实现智慧工地高精度动态仿真&a…

C++ 多线程:std::future

std::future std::future 简介示例1博客引用来源 std::future 简介 我们前面介绍的std::thread 是C11中提供异步创建多线程的工具&#xff0c;只能是异步运行任务&#xff0c;却无法获取任务执行的结果&#xff0c;一般都是依靠全局对象&#xff0c;全局对象在多线程下是及其不…

4 三组例子,用OpenCV玩转图像-AI-python

读取&#xff0c;缩放&#xff0c;旋转&#xff0c;写入图像 首先导入包&#xff0c;为了显示导入matplotlib/为了在matplotlib显示 导入CV2/查看版本 导入图片/查看图片类型 图片数组 数组大小 对于opencv通道顺序蓝色B、绿色G、红色R matplotlib通道顺序为 红色R、绿色G、蓝…

快速修复应用程序中的问题的利器—— Android热修复

热修复技术在Android开发中扮演着重要的角色&#xff0c;它可以帮助开发者在不需要重新发布应用程序的情况下修复已经上线的应用程序中的bug或者添加新的功能。 一、热修复是什么&#xff1f; 热修复&#xff08;HotFix&#xff09;是一种在运行时修复应用程序中的问题的技术…

彻底弄清楚Minor GC和Major GC及Full GC

系列资源汇总 https://gitee.com/xiayi/java-docs 每日一句 每一日你所付出的代价都比前一日高&#xff0c;因为你的生命又消短了一天&#xff0c;所以每一日你都要更用心。 前提概要 对于JVM而言&#xff0c;最难能够掌握的就是GC回收部分的研究和探索。 而对于虚拟机而言根据…

前端页面--视觉差效果

代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><link rel"stylesheet" href"https://un…

如何实现基于场景的接口自动化测试用例?

自动化本身是为了提高工作效率&#xff0c;不论选择何种框架&#xff0c;何种开发语言&#xff0c;我们最终想实现的效果&#xff0c;就是让大家用最少的代码&#xff0c;最小的投入&#xff0c;完成自动化测试的工作。 基于这个想法&#xff0c;我们的接口自动化测试思路如下…

【Linux】【预】配置网络IP,挂载网络目录

【Linux】【预】配置网络IP&#xff0c;挂载网络目录 1. 配置查看IP2.配置Linux中的IP3. 串口连接开发板&#xff0c;配置 1. 配置查看IP a . 查看ipifconfig如下操作&#xff0c;其中的&#xff1a;192.168.252.140就是它的IP b . 使用xmodem 连接到虚拟机&#xff0c;最后点…

Docker搭建zookeeper

问题背景 前言 本文参考自&#xff1a;docker-compose快速搭建Zookeeper集群还有一种更加详细更加全面的部署方式&#xff1a;Docker之docker-compose一键部署Zookeeper集群&#xff0c;但笔者还未验证&#xff0c;先记录下来 搭建 安装docker-ce 此处不赘述 安装docker-co…