蓝桥杯15单片机--专项问题:多数暂存、平均值、最大值、最小值问题

该程序实现了超声波测距数据的存储、计算和显示,包括最大值、最小值和平均值的实时更新。数据存储在50个元素的数组中,每次新增数据后更新平均值并检查是否需要更新最大值和最小值。若达到50个数据点,会进行数组前移。此外,程序还涉及按键切换显示模式和超声波信号的发送与接收处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在第十二届国赛和刚刚过去的第十四届省赛中,遇到了一个新问题:多数暂存、平均值、最大值、最小值问题。

其实这个要求并不难,就是平均值显示那个地方容易想不出来。其他的认真想想就可以。

第十二届国赛具体要求如下:

首先,这一部分数据的存储肯定要用数组去做,我们定义一个可以存放50个数据的数组,distance[n]

 值得注意的是:我们前面要用xdata来存储。如果不加的话,默认使用data型,那么数据内存就会超标,产生错误。

unsigned int xdata distance[50]=0;

最大值和最小值很简单,把第一个数当作最大值和最小值,然后每进去一个数进行比较,如果大于最大值或者小于最小值就把新数值化为最大值或最小值。

if(n==0) {LCM_min=distance[0];LCM_max=distance[0];}

if(distance[n]>LCM_max) {LCM_max=distance[n];}

else if(distance[n]<LCM_min) {LCM_min=distance[n];}

平均值的话,由于牵扯到小数显示,所以要定义两个变量:一个小数平均值(进行计算)和整数平均值(进行显示)。计算出小数平均值,然后乘10转换成整数形式,再进行小数显示。

LCM_all=LCM_all+distance[n];

LCM_pingjun_xiaoshu=LCM_all/(n+1);

LCM_pingjun=(int)(LCM_pingjun_xiaoshu*10);

最后一步就是数组数据的更新:如果读取的数据超过 50个,将数据依次前移,去掉最前面那一个数据。

if(n==50)
{
    for(i=1;i<=49;i++)
	{
		distance[i-1]=distance[i];
	}
	n=49;
}

最后放上程序源码

程序要求:读取超声波测量到的50个距离数据,并且满50个之后进行更新。按键S4切换显示平均值、最大值、最小值。

#include <STC15F2K60S2.H>
#include <intrins.H>

sbit TX = P1^0;
sbit RX = P1^1;

#define uchar unsigned char
#define uint unsigned int
	
uchar code tab[]={0XC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90,//0-9
									0X40,0X79,0X24,0X30,0X19,0X12,0X02,0X78,0X00,0X10,//0.-9.
                  0XBF,0XFF,0X8E,0XC1};//- 关 F U
uchar Smg_num=0;
uchar Smg[]={0,7,10,5,3,10,1,0};

unsigned int LCM_tt = 0 ;//超声波采集时间的计数 间隔多长时间进行一次超声波测量
bit LCM_Ref = 0 ;//超声波开始测量的标志位
unsigned int LCM = 0 ;//超声波测量的距离  单位:cm
unsigned int LCM_Time = 0 ;//超声波从发送到接收返回信号的时间

uint xdata distance[50]=0;
float LCM_pingjun_xiaoshu=0.0f;
uint LCM_pingjun=0,LCM_min=0,LCM_max=0,caiji_tt=0;
bit caiji_flag=0;
uchar mode=0,n=0,i=0;
long LCM_all=0;

void Allinit();
void Delayms(uint ms);
void Keyscan();
void Timer2Init(void);		//1毫秒@12.000MHz
void Smg_work();
void Send_wave();
void Csb_work();
void Delay16us();
void Timer1Init(void);

void main()
{
	Allinit();
	Timer1Init();
	Timer2Init();
	while(1)
	{
		if(caiji_flag==1)//每1秒采集一次
		{
			Csb_work();
			distance[n]=LCM;//存储数据进数组
			
			// 最小值 最大值 平均值 的确定
			if(n==0) {LCM_min=distance[0];LCM_max=distance[0];}
			if(distance[n]>LCM_max) {LCM_max=distance[n];}
			else if(distance[n]<LCM_min) {LCM_min=distance[n];}
			LCM_all=LCM_all+distance[n];
			LCM_pingjun_xiaoshu=LCM_all/(n+1);
			LCM_pingjun=(int)(LCM_pingjun_xiaoshu*10); 
			if(LCM_pingjun>=9999) LCM_pingjun=9999;
			n=n+1;
			
			// 读取的数据超过 50个,将数据依次前移,去掉最前面那一个数据
			if(n==50)
			{
				for(i=1;i<=49;i++)
				{
					distance[i-1]=distance[i];
				}
				n=49;
			}
			caiji_flag=0;
		}
		
		Smg_work();
		Keyscan();
		//Keyscan16();
		//Delayms(5);
	}
}

void Smg_work()
{
	if(mode==0)
	{
		Smg[0]=22;Smg[1]=1;Smg[2]=21;Smg[3]=21;
		Smg[4]=LCM_max/1000;Smg[5]=LCM_max%1000/100;
		Smg[6]=LCM_max%100/10;Smg[7]=LCM_max%10;
	}
	else if(mode==1)
	{
		Smg[0]=22;Smg[1]=2;Smg[2]=21;Smg[3]=21;
		Smg[4]=LCM_pingjun/1000;Smg[5]=LCM_pingjun%1000/100;
		Smg[6]=LCM_pingjun%100/10;Smg[7]=LCM_pingjun%10;
	}
	else if(mode==2)
	{
		Smg[0]=22;Smg[1]=3;Smg[2]=21;Smg[3]=21;
		Smg[4]=LCM_min/1000;Smg[5]=LCM_min%1000/100;
		Smg[6]=LCM_min%100/10;Smg[7]=LCM_min%10;
	}
	else if(mode==3)
	{
		Smg[0]=distance[n-2]/1000;Smg[1]=distance[n-2]%1000/100;
		Smg[2]=distance[n-2]%100/10;Smg[3]=distance[n-2]%10;
		Smg[4]=distance[n-1]/1000;Smg[5]=distance[n-1]%1000/100;
		Smg[6]=distance[n-1]%100/10;Smg[7]=distance[n-1]%10;
	}
}

void Send_wave()//发送超声波信号
{
	uchar i=8;
	
	do
	{
		TX=1;  Delay16us();
		TX=0;  Delay16us();
	}
	while(i--);
}

void Csb_work()//这个放在 while 循环里面
{
	if(LCM_Ref==1)
	{
		LCM_Ref=0;
		Send_wave();
		TR1=1;
		while((RX==1)&&(TF1==0));//如果RX没有接收到低电平(返回信号),就一直等待;或者检测中断标志位,没有溢出中断
		//当检测到返回信号,或者一直没有接收到返回信号但是中断溢出了,程序就继续往下进行。
		TR1=0;
		if(TF1==1)//这种情况是 一直没有接收到返回信号但是溢出中断了
		{
			TF1=0; LCM=9999;//表示没有检测到返回信号了,数据超出范围
		}
		else
		{
			LCM_Time = TH1;
			LCM_Time <<= 8;
			LCM_Time |= TL1;//读出超声波从发送到接收信号所用的时间,但这读出来的是定时器计数的值,所以下面我们要对其进行换算
			LCM=(uint)(LCM_Time*17/1000);
			//定时器计数一次需要 1/1000000秒(1/12000000/12)(12M晶振),总共计数了 LCM_Time次
			//LCM_Time * 1/1000000 * 17000(34000/2) 距离单位是厘米
		}
		TL1=0X00;
		TH1=0X00;//清零方便下次计数	
	}
}

void Delay16us()		//@12.000MHz
{
	unsigned char i;

	_nop_();
	_nop_();
	i = 45;
	while (--i);
}

void Timer1Init(void)		//0微秒@12.000MHz
{
	AUXR &= 0xBF;		//定时器时钟12T模式
	TMOD &= 0x0F;		//设置定时器模式
	TL1 = 0x00;		//设置定时初始值
	TH1 = 0x00;		//设置定时初始值
	TF1 = 0;		//清除TF1标志
	//TR1 = 1;		//定时器1开始计时
}

void timer2() interrupt 12
{
	P0=0XFF;
	P2|=0XC0;//打开位选573   U8
	P2&=0XDF;
	P2&=0X1F;
	
	P0=0XFF;
	P2|=0XE0;
	P2&=0XFF;//打开段选573   U7
	P2&=0X1F;

	P0=(1<<Smg_num);
	P2|=0XC0;//打开位选573   U8
	P2&=0XDF;
	P2&=0X1F;
	
	P0=tab[Smg[Smg_num]];
	P2|=0XE0;
	P2&=0XFF;//打开段选573   U7
	P2&=0X1F;
	if(++Smg_num==8) Smg_num=0;
	
	if(++LCM_tt==300)//每三百毫秒进行一次超声波检测
	{
		LCM_tt=0;LCM_Ref=1;
	}
	
	if(caiji_flag==0)
	{
		caiji_tt++;
		if(caiji_tt==1000)
		{
			caiji_flag=1;caiji_tt=0;
		}
	}
}

void Timer2Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x04;		//定时器时钟1T模式
	T2L = 0x20;		//设置定时初始值
	T2H = 0xD1;		//设置定时初始值
	AUXR |= 0x10;		//定时器2开始计时

	IE2|=0X04;EA=1;
}

void Keyscan()
{
	if(P30==0)
	{
		Delayms(5);
		if(P30==0)
		{
			Smg[0]=1;
		}
		while(!P30);
	}
	
	else if(P31==0)
	{
		Delayms(5);
		if(P31==0)
		{
			Smg[1]=2;
		}
		while(!P31);
	}
	
	else if(P32==0)
	{
		Delayms(5);
		if(P32==0)
		{
			Smg[2]=3;
		}
		while(!P32);
	}
	
	else if(P33==0)
	{
		Delayms(5);
		if(P33==0)
		{
			if(mode==0) mode=1;
			else if(mode==1) mode=2;
			else if(mode==2) mode=3;
			else if(mode==3) mode=0;
		}
		while(!P33);
	}
}

void Allinit()
{
	P2|=0XA0;P2&=0XBF;P0=0X00;
	
	P2|=0X80;P2&=0X9F;P0=0XFF;
	
	P2|=0XC0;P2&=0XDF;P0=0XFF;
	P2|=0XE0;P2&=0XFF;P0=0XFF;
}

void Delayms(uint ms)
{
	uint i,j;
	for(i=ms;i>0;i--)
	for(j=845;j>0;j--);
}

<think>我们使用51单片机(如STC89C52)通常通过ADC(模数转换器)读取电位器的电压值,然后根据这个值来控制PWM输出的占空比。这里假设我们使用的ADC是12位(0-4095),但注意题目中说最小值为4095,最大值为0,这意味着电位器拧到一端时ADC值为4095,另一端为0。我们需要将这个值映射到PWM的占空比(0%-100%)。 注意:51单片机通常没有硬件PWM,所以我们需要使用定时器来模拟PWM。我们假设使用定时器0,并且PWM频率为1kHz(周期1ms),那么我们可以将定时器中断设置为100us(0.1ms)一次,这样每个周期有10个中断(10级)。这样,占空比就可以从0(0%)到10(100%)分为10级(或者更细的分辨率,但这里为了简单,我们使用10级,即占空比步进为10%)。如果需要更高的分辨率,可以调整中断时间和周期内的中断次数。 步骤: 1. 初始化定时器0,设置中断时间(例如100us)。 2. 初始化ADC(假设使用ADC0804或类似的ADC芯片,或者使用内部ADC,这里以外部ADC为例,实际连接可能通过并行或串行方式读取,这里简化,假设我们有一个函数可以读取12位ADC值)。 3. 在定时器中断中实现PWM输出。设置一个全局变量pwm_count用于计数(0-9循环),另一个全局变量pwm_duty表示占空比(0-10,表示在一个周期中高电平持续的中断次数)。当pwm_count小于pwm_duty时,输出高电平,否则输出低电平。 4. 在主循环中读取ADC值,并将ADC值映射到占空比。由于ADC值范围是0-4095,但题目要最小值为4095(对应占空比0%),最大值为0(对应占空比100%),所以我们需要将ADC值反转并映射到0-100%(这里我们映射到0-10,因为我们的PWM有10级)。 映射公式: 由于ADC值从0到4095,我们想要当ADC=0时,占空比=100%(即duty=10),ADC=4095时,占空比=0%(duty=0)。所以: duty = 10 - (ADC_value * 10) / 4095 但是这样计算会有小数,我们可以使用整数运算,并注意四舍五入。或者我们可以这样: duty = (4095 - ADC_value) * 10 / 4095 # 这样当ADC_value=0时,duty=4095*10/4095≈10,当ADC_value=4095时,duty=0 但是注意整数除法会截断,所以我们可以加上一半的除数来四舍五入: duty = ( (4095 - ADC_value) * 10 + 2047 ) / 4095 # 2047是4095的一半,这样四舍五入 但是我们的分辨率只有10级,所以也可以不用四舍五入,直接除以409.5,但整数除法只能先乘后除。所以: duty = ( (4095 - ADC_value) * 10 ) / 4095 但是这样计算的结果可能不是整数,我们可以用整数除法,然后取整。由于我们最终需要的是一个0-10的整数,我们可以这样: duty = ( (4095 - ADC_value) * 10 + 4095/2 ) / 4095; // 四舍五入 但是注意,4095/2=2047.5,取整为2047,所以: duty = ( (4095 - ADC_value) * 10 + 2047 ) / 4095; 这样计算出来的duty在0到10之间。 然而,由于51单片机的计算能力有限,我们也可以使用移位等操作来优化,但4095不是2的幂,所以不好优化。另一种方法是使用查表法,但这里我们为了简单,直接计算。 但是注意:如果ADC_value是0,那么(4095-0)*10=40950,加上2047等于42997,除以4095等于10.49,整数除法会得到10。如果ADC_value=4095,那么(0*10+2047)/4095=0.5,整数除法得到0。所以符合要。 然而,由于我们的PWM只有10级,所以占空比的分辨率是10%。如果要更高的分辨率,我们可以增加PWM的级数,比如将周期分为100份(中断时间改为10us,周期1ms需要100次中断),这样占空比分辨率就是1%。但这里为了说明,我们使用10级。 代码结构: 1. 包含必要的头文件,定义引脚。 2. 全局变量:pwm_count(当前PWM计数),pwm_duty(当前占空比,0-10)。 3. 初始化定时器0,设置100us中断一次(假设使用12MHz晶振,定时器0方式1,定时100us需要初始值65536-100=65436,即0xFF9C)。 4. 编写ADC读取函数(这里假设有一个函数read_adc()返回12位的ADC值,实际中需要根据硬件编写)。 5. 主函数中初始化定时器和中断,然后进入循环,不断读取ADC并计算pwm_duty。 6. 定时器中断服务函数中,pwm_count从0到9循环,根据pwm_duty控制PWM输出引脚。 注意:由于51单片机的中断响应时间,定时器0的中断时间不能设置得太短,否则会一直中断。这里100us中断一次,中断服务程序必须很短。 下面是一个简化的代码示例(使用Keil C51语法):</think>### 解决方案:将电位器AD值映射为PWM百分比(51单片机) 以下是完整的C代码实现,使用定时器生成PWM波,并将ADC值(4095~0)映射为PWM占空比(0%~100%)。代码基于STC89C52单片机,使用12位ADC(如ADS1015)和定时器0实现。 ```c #include <reg52.h> #include <intrins.h> #define ADC_MAX 4095 // ADC最大值 #define PWM_PERIOD 100 // PWM周期点数(分辨率) sbit PWM_PIN = P1^0; // PWM输出引脚(P1.0) sbit ADC_CS = P2^0; // ADC片选引脚(示例) sbit ADC_CLK = P2^1; // ADC时钟引脚 sbit ADC_DOUT = P2^2; // ADC数据输出 unsigned int pwm_duty = 0; // PWM占空比(0~PWM_PERIOD) unsigned int adc_value = 0; // 存储ADC值 // 初始化定时器0(用于PWM生成) void Timer0_Init() { TMOD &= 0xF0; // 设置定时器0为模式1(16位定时器) TMOD |= 0x01; TH0 = 0xFF; // 初始定时值(约100us中断) TL0 = 0x9C; ET0 = 1; // 开启定时器0中断 EA = 1; // 开启总中断 TR0 = 1; // 启动定时器0 } // 模拟SPI读取12位ADC值(伪代码) unsigned int Read_ADC() { unsigned int val = 0; ADC_CS = 0; // 使能ADC // 模拟SPI时序读取16位(实际取12位) for(char i=0; i<16; i++) { ADC_CLK = 0; _nop_(); if(ADC_DOUT) val |= (1 << (15-i)); // 读取数据位 ADC_CLK = 1; } ADC_CS = 1; // 禁用ADC return (val >> 4) & 0xFFF; // 取高12位(根据实际ADC调整) } // 映射函数:将ADC值(4095~0)转换为PWM占空比(0~100) unsigned int Map_ADCTOPWM(unsigned int adc) { // 公式:PWM = (ADC_MAX - adc) * PWM_PERIOD / ADC_MAX unsigned long temp = (unsigned long)(ADC_MAX - adc) * PWM_PERIOD; return temp / ADC_MAX; // 整数除法 } void main() { Timer0_Init(); // 初始化定时器 while(1) { adc_value = Read_ADC(); // 读取ADC值 pwm_duty = Map_ADCTOPWM(adc_value); // 转换为PWM占空比 // 添加延时稳定读取(根据实际需调整) for(int i=0; i<1000; i++); } } // 定时器0中断服务函数(PWM生成) void Timer0_ISR() interrupt 1 { static unsigned int pwm_counter = 0; // PWM计数器 TH0 = 0xFF; // 重装定时值(保持100us中断) TL0 = 0x9C; pwm_counter++; if(pwm_counter >= PWM_PERIOD) pwm_counter = 0; // PWM输出控制 PWM_PIN = (pwm_counter < pwm_duty) ? 1 : 0; } ``` ### 代码解释 1. **ADC读取(Read_ADC函数)** - 使用模拟SPI时序读取12位ADC值(0~4095) - 实际开发需根据具体ADC芯片(如ADS1015/MCP3201)修改时序 2. **映射函数(Map_ADCTOPWM)** - 核心公式:`PWM = (4095 - adc) * 100 / 4095` - 将ADC值反转(4095→0% PWM, 0→100% PWM) - 使用`unsigned long`防止乘法溢出 3. **PWM生成机制** - 定时器0每100us中断一次(周期=100us×100=10ms→100Hz PWM) - `pwm_counter`循环计数(0~99) - 当计数值小于`pwm_duty`时输出高电平 4. **关键参数** - `PWM_PERIOD=100`:提供1%占空比分辨率 - 定时器初值`0xFF9C`:12MHz晶振下约100us - 可调整`PWM_PERIOD`提高分辨率(需同步改定时) ### 注意事项 1. **ADC方向处理**:代码中`ADC_MAX - adc`实现值反转(4095→0%) 2. **防溢出处理**:映射计算使用`(unsigned long)`避免乘法溢出 3. **实际应用**: - 若ADC电压范围与PWM需不符,需调整映射公式 - 高精度场景建议使用硬件PWM模块(如STC12系列) 4. **优化建议**: - 添加ADC滤波(如移动平均) - 使用查表法替代实时计算提高效率
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值