当前位置: 首页 > news >正文

蓝桥杯单片机组第十三届初赛试题-程序题(第2批)

题目到官网看即可,有点久了有些细节记不清了,可能以前发的帖子解释详细一点。

这是我单片机初学的时候写的,像代码结构什么的肯定有可以提升的地方,多多包涵,将就看一下。

i2c文件使用官方的,pcf8591函数声明没有摆出来。

main.c

#include <system.H>
// 定义超声波测距相关参数,初始值为3.0和1.5
float hc = 3.0, lc = 1.5; 
// 定义电压上下限,初始值为3.0和1.5
float highc = 3.0, lowc = 1.5; 
// 定义变量v用于存储DAC输出的电压值,voltage用于存储ADC采集的电压值
float v, voltage = 0; 
// sonicflag:超声波测距触发标志;adc:ADC采集结果;waveflag:测距启动标志;ledflag:LED闪烁标志
unsigned char sonicflag = 0, adc = 0, waveflag = 0, ledflag = 0; 
// 定义距离变量,初始值为50
unsigned int distance = 50; 

// 该函数用于设置P2和P0端口的值
// p2:要设置到P2端口高5位的值
// p0:要设置到P0端口的值
void P2_P0(unsigned char p2, unsigned char p0)
{
    P0 = p0;
    // 清除P2端口高5位
    P2 = P2 & 0x1F; 
    // 设置P2端口高5位
    P2 = p2 | P2; 
    P2 = P2 & 0x1F; 
}


void Delay25us(void)	
{
    unsigned char data i;

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


void Timer1_Isr(void) interrupt 3
{
    // 静态变量,用于记录不同定时任务的计数
    static unsigned int T0 = 0, T1 = 0, T2 = 0, T3 = 0; 
    T0++;
    T1++;
    T2++;
    T3++;
    // 每1ms执行一次数码管扫描
    if (T0 > 1) 
    {
        T0 = 0;
        smg_loop();
    }
    // 每20ms执行一次按键扫描
    if (T1 > 20) 
    {
        T1 = 0;
        key_loop();
    }
    // 每500ms触发一次超声波测距
    if (T2 > 500) 
    {
        T2 = 0;
        sonicflag = 1;
    }
    // 每100ms改变一次LED闪烁标志
    if (T3 > 100) 
    {
        T3 = 0;
        ledflag++;
        ledflag = ledflag % 2;
    }
}

void Timer1_Init(void)		
{
    // 定时器时钟1T模式
    AUXR |= 0x40;			
    TMOD &= 0x0F;			
    // 设置定时初始值
    TL1 = 0x20;				
    // 设置定时初始值
    TH1 = 0xD1;				
    // 清除TF1标志
    TF1 = 0;				
    // 定时器1开始计时
    TR1 = 1;				
    // 使能定时器1中断
    ET1 = 1;				
    // 全局中断使能,两个定时器初始化有一个有这一句就行
    EA = 1;					
}

void Timer0_Init(void)//定时器0给超声波计时间用,不用在初始化的时候打开		
{
    // 定时器时钟12T模式
    AUXR &= 0x7F;			
    // 设置定时器模式
    TMOD &= 0xF0;			
    // 设置定时初始值
    TL0 = 0;				
    // 设置定时初始值
    TH0 = 0;				
    // 清除TF0标志
    TF0 = 0;
}

// 该函数用于根据不同的显示模式更新数码管显示内容
// i:显示模式,0表示电压显示,1表示测距显示,2表示参数显示
void view(unsigned char i)
{
    switch (i)
    {
        case 0: // 电压显示模式
        {
            // 显示特定符号
            smg_set(0, 18); 
            smg_set(1, 17);
            smg_set(2, 17);
            smg_set(3, 17);
            smg_set(4, 17);
            // 显示电压整数部分
            smg_set(5, (unsigned int)(voltage * 100) / 100 + 20); 
            // 显示电压小数点后第一位
            smg_set(6, (unsigned int)(voltage * 100) % 100 / 10); 
            // 显示电压小数点后第二位
            smg_set(7, (unsigned int)(voltage * 100) % 10); 
            break;
        }
        case 2: // 参数显示模式
        {
            smg_set(0, 19); 
            smg_set(1, 17);
            smg_set(2, 17);
            // 显示上限参数整数部分
            smg_set(3, (unsigned char)hc + 20); 
            // 显示上限参数小数部分
            smg_set(4, (unsigned char)(hc * 10) % 10); 
            smg_set(5, 17);
            // 显示下限参数整数部分
            smg_set(6, (unsigned char)lc + 20); 
            // 显示下限参数小数部分
            smg_set(7, (unsigned char)(lc * 10) % 10); 
            break;
        }
        case 1: // 测距显示模式
        {
            smg_set(0, 15); 
            smg_set(1, 17);
            smg_set(2, 17);
            smg_set(3, 17);
            smg_set(4, 17);
            // 根据测距启动标志显示特定字符或距离数据
            smg_set(5, waveflag == 0 ? 10 : 17); 
            smg_set(6, waveflag == 0 ? 10 : distance / 100);
            smg_set(7, waveflag == 0 ? 10 : distance % 100 / 10);
            break;
        }
    }
}

// 超声波测距函数
void untrasonic()
{
    unsigned char i = 0;
    // 当超声波测距触发标志置位时
    if (sonicflag == 1) 
    {
        // 清除触发标志
        sonicflag = 0; 
        // 发送8个25us的超声波脉冲
        for (i = 0; i < 8; i++) 
        {
            tx = 1;
            Delay25us();
            tx = 0;
            Delay25us();
        }
        // 启动定时器0开始计时
        TR0 = 1; 
        // 等待回波信号或定时器0溢出
        while ((rx == 1) && (TF0 == 0)); 
        // 停止定时器0计时
        TR0 = 0; 
        // 如果定时器0未溢出
        if (TF0 == 0) 
        {
            // 读取定时器0的高8位
            distance = TH0; 
            // 左移8位
            distance <<= 8; 
            // 读取定时器0的低8位
            distance |= TL0; 
            // 根据定时器计数值计算距离
            distance = (unsigned int)(distance * 0.017); 
        }
        else
        {
            // 清除定时器0溢出标志
            TF0 = 0; 
            // 距离值置为0
            distance = 0; 
        }
    }
}

// DAC输出函数,根据测距结果设置DAC输出电压
void dac()
{
    // 当测距未启动时
    if (waveflag == 0) 
    {
        // DAC输出0V
        pcf8591_dac(0); 
        v = 0;
    }
    else
    {
        // 当距离在20到80之间时
        if (distance > 20 && distance <= 80) 
        {
            // 计算DAC输出电压
            v = distance / 15.0 - (1 / 3.0); 
            // 设置DAC输出电压
            pcf8591_dac(v); 
        }
        // 当距离在0到20之间时
        else if (distance > 0 && distance <= 20) 
        {
            v = 1.0;
            pcf8591_dac(v);
        }
        else
        {
            v = 5.0;
            pcf8591_dac(v);
        }
    }
}

void main()
{
    // 按键值
    unsigned char value = 0; 
    // 显示模式
    unsigned char mode0 = 0; 
    // 参数设置模式
    unsigned char mode1 = 0; 
    // 临时变量,用于参数交换
    float vtemp = 0; 

    Timer1_Init(); 
    Timer0_Init(); 

    // 初始化LED灯,所有灯熄灭,仅LED1点亮
    led.hex = 0xFF;
    led.b.b0 = 0;
    // 设置P2和P0端口,点亮LED1
    P2_P0(0x80, led.hex); 
    // 关闭蜂鸣器和继电器
    P2_P0(0xA0, 0); 

    while (1)
    {
        // 获取按键值
        value = key_get(); 
        // 按键4按下,切换显示界面
        if (value == 4) 
        {
            mode0++;
            mode0 = mode0 % 3;
            if (mode0 == 1)
            {
                // 测距界面,设置LED状态
                led.b.b0 = 1;
                led.b.b1 = 0;
                led.b.b2 = 1;
            }
            if (mode0 == 2)
            {
                // 参数设置界面,设置LED状态
                led.b.b0 = 1;
                led.b.b1 = 1;
                led.b.b2 = 0;
                // 默认设置上限
                mode1 = 0; 
            }
            if (mode0 == 0) // 退出参数设置,更新上下限参数
            {
                highc = hc;
                lowc = lc;
                // 电压显示界面,设置LED状态
                led.b.b0 = 0;
                led.b.b1 = 1;
                led.b.b2 = 1;
            }
            // 更新LED显示
            P2_P0(0x80, led.hex); 
        }
        // 按键5按下,选择设置上限还是下限
        if (value == 5) 
        {
            // 仅在参数设置界面有效
            if (mode0 == 2) 
            {
                mode1++;
                mode1 = mode1 % 2;
            }
        }
        // 按键6按下,参数值加0.5
        if (value == 6) 
        {
            // 上限设置
            if (mode1 == 0) 
            {
                hc = hc + 0.5;
                if (hc > 5.0) hc = 0.5;
                // 越界后上下限交换
                if (hc < lc) 
                {
                    vtemp = hc;
                    hc = lc;
                    lc = vtemp;
                }
            }
            // 下限设置
            if (mode1 == 1) 
            {
                lc = lc + 0.5;
                // 下限不能高于上限
                if (lc > hc) lc = hc; 
                if (lc > 5.0) lc = 0.5;
            }
        }
        // 按键7按下,参数值减0.5
        if (value == 7) 
        {
            // 上限设置
            if (mode1 == 0) 
            {
                hc = hc - 0.5;
                if (hc < 0.5) hc = 5.0;
            }
            // 下限设置
            if (mode1 == 1) 
            {
                lc = lc - 0.5;
                // 越界后上下限交换
                if (lc < 0.5) 
                {
                    lc = 5.0;
                    if (hc < lc)
                    {
                        vtemp = hc;
                        hc = lc;
                        lc = vtemp;
                    }
                }
                // 下限不能低于上限
                if (hc < lc) hc = lc; 
            }
        }
        // 根据显示模式更新数码管显示
        view(mode0); 
        // 启动ADC采集通道3的电压值
        pcf8591_adc(0x03); 
        // 读取ADC采集结果
        adc = pcf8591_read(); 
        // 计算采集到的电压值
        voltage = 5.0 * (adc / 255.0); 
        // 当电压值在上下限之间时
        if ((voltage < highc) && (voltage > lowc)) 
        {
            // 启动测距
            waveflag = 1; 
            // 执行超声波测距
            untrasonic(); 
            // 设置DAC输出电压
            dac(); 
            // 根据LED闪烁标志控制LED灯
            if (ledflag == 1) 
            {
                led.b.b7 = 0;
                P2_P0(0x80, led.hex);
            }
            else
            {
                led.b.b7 = 1;
                P2_P0(0x80, led.hex);
            }
        }
        else
        {
            // 停止测距
            waveflag = 0; 
        }
    }
}

主函数是程序的入口,主要完成以下工作:

  • 初始化定时器 1 和定时器 0。
  • 初始化 LED 灯和关闭蜂鸣器、继电器。
  • 进入无限循环,不断读取按键值,根据按键值进行显示模式切换、参数设置等操作。
  • 调用 view 函数更新数码管显示。
  • 进行 ADC 电压采集,根据采集到的电压值判断是否启动超声波测距和 DAC 输出。
  • 根据 ledflag 标志位控制 LED 灯的闪烁。

arrkeys.c

#include <STC15F2K60S2.H>

// 该函数用于读取独立按键的状态,返回按下按键对应的编号
// 按键编号对应关系为:P30 -> 7, P31 -> 6, P32 -> 5, P33 -> 4
unsigned key_read()//独立按键
{
    // 用于存储检测到的按键编号,初始值为 0 表示无按键按下
    unsigned char key = 0;
    // 设置按键扫描的端口状态,P44 置 0,P42、P35、P34 置 1
    P44 = 0;
    P42 = 1;
    P35 = 1;
    P34 = 1;

    // 检测 P30 引脚是否为低电平,如果是则表示按键 7 被按下
    if (P30 == 0)
        key = 7;
    if (P31 == 0)
        key = 6;
    if (P32 == 0)
        key = 5;
    if (P33 == 0)
        key = 4;

    // 返回检测到的按键编号
    return key;
}

// 该函数用于对按键进行消抖处理,并返回有效的按键编号
// 这个可能是状态机,通过比较当前和上一次按键状态来判断是否为有效按键按下
unsigned char key_loop()
{
    // 静态变量,用于记录上一次按键的状态,初始值为 0
    static unsigned char nowstate = 0, laststate = 0;
    // 用于存储最终有效的按键编号,初始值为 0 表示无有效按键按下
    unsigned char keynum = 0;

    // 将当前按键状态保存为上一次按键状态
    laststate = nowstate;
    // 调用 key_read 函数获取当前实际的按键状态
    nowstate = key_read();

    // 判断上一次无按键按下,且当前检测到按键 7 被按下,则认为按键 7 是有效按下
    if (laststate == 0 && nowstate == 7)
        keynum = 7;
    if (laststate == 0 && nowstate == 6)
        keynum = 6;
    if (laststate == 0 && nowstate == 5)
        keynum = 5;
    if (laststate == 0 && nowstate == 4)
        keynum = 4;

    // 返回有效的按键编号
    return keynum;
}

// 该函数用于获取经过消抖处理后的有效按键编号
// 调用 key_loop 函数来完成按键消抖和判断,并将结果返回
unsigned char key_get()
{
    // 临时变量,用于存储 key_loop 函数返回的有效按键编号
    unsigned char temp = 0;
    // 调用 key_loop 函数获取有效按键编号
    temp = key_loop();
    // 返回有效按键编号
    return temp;
}
//这是一个必要的函数,好像是如果直接通过上面函数获得键值,就会一直返回按的结果好像一直在按

1. key_read 函数


该函数用于读取独立按键的状态。
首先对按键相关的端口进行初始化设置(P44 = 0; P42 = 1; P35 = 1; P34 = 1;)。
然后依次检测 P30 - P33 引脚的电平状态,如果相应引脚为低电平,则将对应的按键编号赋值给 key 变量。
最后返回按键编号。

2. key_loop 函数


该函数实现了按键消抖功能,使用状态机的思想。
定义两个静态变量 nowstate 和 laststate 分别记录当前按键状态和上一次按键状态。
调用 key_read 函数获取当前按键状态,并更新 nowstate。
通过比较 laststate 和 nowstate,当 laststate 为 0(无按键按下)且 nowstate 为特定按键编号时,将该按键编号赋值给 keynum。
最后返回有效按键编号。

3. key_get 函数


该函数调用 key_loop 函数获取有效按键编号,并将其返回。

smg.c

#include <STC15F2K60S2.H>
// 定义数码管段码表,存放在程序存储器中
// 依次对应 0 - 9、A、b、C、d、E、J、-、无显示、U、P 等字符的段码
// 后面还有一组是带小数点的数字
code unsigned char Seg_Table[] = 
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0x88, //A  10
0x83, //b  11
0xc6, //C  12
0xa1, //d  13
0x86, //E  14
0xc3, //J  15  11000011
0xBF,  //-  16
0xFF,	//none 17
0xC1, //U   18
0x8C,//P  19 1000,1100
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0x08,0x03,0x46,0x21,0x06,0x0e
};
// 定义数码管显示缓冲区数组,初始值全为 17(对应无显示状态)
unsigned char smg_word[8]={17,17,17,17,17,17,17};

// 该函数用于设置 P2 端口高 5 位和 P0 端口的值
// p2: P2 端口高 5 位要设置的值
// p0: P0 端口要设置的值
void P2andP0(unsigned char p2, unsigned char p0)//在main.c里解释过了
{
    P0 = p0;
    P2 = P2 & 0x1F;
    P2 = p2 | P2;
    P2 = P2 & 0x1F;
}

// 该函数用于设置数码管显示缓冲区中指定位置的显示内容
// pos: 数码管的位置(0 - 7)
// word: 要显示的字符在段码表中的索引
void smg_set(unsigned char pos, unsigned char word)
{
    // 将指定位置的显示内容设置为 word
    smg_word[pos] = word;
}

// 该函数用于实现数码管的动态扫描显示
void smg_loop()
{
    // 静态变量,用于记录当前扫描到的数码管位置,初始值为 0
    static unsigned char i = 0;
    // 选中第 i 个数码管进行显示,0xc0 用于选中数码管位选锁存器
    P2andP0(0xc0, 0x01 << i);
    // 将 smg_word[i] 对应的段码值通过段码表查找出来并显示
    P2andP0(0xe0, Seg_Table[smg_word[i]]);
    // 将当前位置的显示内容设置为无显示状态,避免余晖影响
    smg_word[i] = 17;
    // 扫描位置加 1
    i++;
    // 当扫描到第 8 个数码管后,将扫描位置重置为 0,开始下一轮扫描
    if (i >= 8) i = 0;
}

system.h

// 防止头文件被重复包含,这是一种常见的预处理器技巧
// 如果 _system_h_ 未被定义,则执行下面的代码并定义 _system_h_
#ifndef _system_h_
#define _system_h_

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

//结构体和联合体部分是给led和嗡鸣器用的,详细的解释在以前帖子有
// 定义一个位域结构体 bits
// 该结构体将一个字节拆分为 8 个独立的位,分别用 b0 - b7 表示
typedef struct
{
    unsigned char b0:1;  // 第 0 位
    unsigned char b1:1;  // 第 1 位
    unsigned char b2:1;  // 第 2 位
    unsigned char b3:1;  // 第 3 位
    unsigned char b4:1;  // 第 4 位
    unsigned char b5:1;  // 第 5 位
    unsigned char b6:1;  // 第 6 位
    unsigned char b7:1;  // 第 7 位
}bits;

// 定义一个联合体 hextobin
// 联合体的特点是所有成员共享同一块内存空间
// 这里的成员包括前面定义的位域结构体 b 和一个无符号字符型变量 hex
typedef union
{
    bits b;       
    unsigned char hex;  
}hextobin;

// 定义两个 hextobin 类型的变量 led 和 buzz
// led 控制 LED 灯,buzz 控制蜂鸣器
hextobin led,buzz;

// 该函数用于获取按键的值
unsigned char key_get();
// 该函数可能用于按键扫描和消抖处理
unsigned char key_loop();

// 该函数用于数码管的动态扫描显示
void smg_loop();

// 该函数用于设置数码管指定位置显示的内容
// pos: 数码管的位置(0 - 7)
// word: 要显示的字符在段码表中的索引
void smg_set(unsigned char pos, unsigned char word);

// 定义两个位变量 tx 和 rx
// tx 连接到 P1 端口的第 0 位,可能用于发送信号
// rx 连接到 P1 端口的第 1 位,可能用于接收信号
sbit tx=P1^0;
sbit rx=P1^1;

// 该函数用于数码管的显示设置
// loc: 数码管的位置
// word: 要显示的内容
void smg_view(unsigned char loc, unsigned char word);

#endif

pcf8591.h

#include <iic.H>
// 定义一个无符号字符型变量 ack,用于存储 I2C 通信中的应答信号
unsigned char ack = 0;

// 该函数用于通过 I2C 总线向 PCF8591 芯片的 DAC(数模转换)通道写入数据
// word: 要写入 DAC 通道的数据,范围是 0 - 255
void pcf8591_dac(unsigned char word)
{
    I2CStart();
    // 发送 PCF8591 芯片的写地址(0x90),表示接下来要向芯片写入数据
    I2CSendByte(0x90);
    // 等待 PCF8591 芯片返回应答信号
    I2CWaitAck();
    // 发送控制字节 0x40,选择 DAC 输出功能
    I2CSendByte(0x40);
    // 等待 PCF8591 芯片返回应答信号
    I2CWaitAck();
    // 发送要写入 DAC 通道的具体数据
    I2CSendByte(word);
    // 等待 PCF8591 芯片返回应答信号
    I2CWaitAck();
    I2CStop();
}

// 该函数用于通过 I2C 总线从 PCF8591 芯片读取 ADC(模数转换)数据
// 返回值: 读取到的 ADC 数据,范围是 0 - 255
unsigned char pcf8591_read()
{
    // 定义一个无符号字符型变量 datas,用于存储读取到的 ADC 数据
    unsigned char datas = 0;
    I2CStart();
    // 发送 PCF8591 芯片的读地址(0x91),表示接下来要从芯片读取数据
    I2CSendByte(0x91);
    // 等待 PCF8591 芯片返回应答信号,并将应答信号存储在 ack 变量中
    ack = I2CWaitAck();
    // 从 PCF8591 芯片接收 ADC 数据
    datas = I2CReceiveByte();
    // 向 PCF8591 芯片发送非应答信号(1),表示不再接收数据
    I2CSendAck(1);
    I2CStop();
    return datas;
}

// 该函数用于通过 I2C 总线向 PCF8591 芯片发送 ADC 通道选择命令
// add: 要选择的 ADC 通道地址
void pcf8591_adc(unsigned char add)
{
    I2CStart();
    // 发送 PCF8591 芯片的写地址(0x90),表示接下来要向芯片写入数据
    I2CSendByte(0x90);
    // 等待 PCF8591 芯片返回应答信号,并将应答信号存储在 ack 变量中
    ack = I2CWaitAck();
    // 发送要选择的 ADC 通道地址
    I2CSendByte(add);
    // 等待 PCF8591 芯片返回应答信号,并将应答信号存储在 ack 变量中
    ack = I2CWaitAck();
    I2CStop();
}

相关文章:

  • 我用AI做数据分析之数据清洗
  • 【python】连接Jira获取token以及jira对象
  • C++-----------酒店客房管理系统
  • Maven下载安装IDEA使用MavenJava在pom.xml配置教程
  • Python--多线程
  • 探索ChatGPT背后的前端黑科技
  • 机器学习数学基础:26.随机变量分布详解
  • Python用PyMC3马尔可夫链蒙特卡罗MCMC对疾病症状数据贝叶斯推断
  • 一阶微分方程的解法与通解式全解析
  • gitlab无法登录问题
  • 周考考题(学习自用)
  • 【webview Android】视频获取首帧为封面
  • Niginx笔记
  • 本地部署DeepSeek(Mac版本,带图形化操作界面)
  • 广东茂名能源国际会议(IS-ESE 2025)
  • 备战蓝桥杯 Day1 回顾语言基础
  • 数字化转型的深度思考与最佳实践
  • Linux查找占用的端口,并杀死进程的简单方法
  • 智慧升级,赋能未来——开启安全高效与绿色低碳新篇章
  • 深入Flask:如何优雅地处理HTTP请求与响应
  • 网站收录查询接口/seo学徒是做什么
  • 商城网页设计html和css代码/seo优化报价
  • 温州哪里有网站建设/宁波seo网站排名优化公司
  • 网站建设大型/怎么做网络营销推广啊
  • 做兼职的网站打字员/石家庄百度搜索优化
  • 一是加强了网站建设/成都最新消息今天