day11_界面闪烁处理

问题

由于在刷新的时候,总是先刷新refresh范围内的背景图层,然后再刷新窗口图层,所以会造成闪烁,还有当鼠标设备放在窗口上面的时候,也会造成闪烁

解决

①pMap 一个和屏幕等大的字符数组,来存放当前屏幕的被哪些Sheet占据
当我们刷新图层的时候,需要遍历图层,只有pMap中有该图层,我们才刷新,否则不刷新
②每个图层都有自己的缓冲区,我们是先把要画的内容,写入缓冲区中,然后由该缓冲区写入缓存,refresh图层

Sheet.h

#ifndef __SHEET_H__
#define __SHEET_H__

#include "Memory.h"
//图层的处理函数


//最多256个 图层
#define MAX_SHEETS 256
//使用该图层
#define SHEET_USE 1

//透明图层的结构体
struct SHEET
{
    unsigned char * pu8Buf;//记录图层上 所描绘画内容的地址
    int nBxSize;    //图层的整体大小 由 Bx * By 来表示
    int nBySize;
    int nVx0;//VX VY 表示图层在画面上 位置的坐标
    int nVy0;
    int nColInv;//透明色色号
    int nHeight;//图层高度
    int nFlags;//存放图层的 各种设定信息
    struct SHEET_CONTROL *pCtl;
};

struct SHEET_CONTROL
{
    unsigned char *pu8Vram;//Video Ram 的地址
    unsigned char *pMap;    //整个界面上 图层的分布
    int nXsize;//X Y 代表画面的大小
    int nYsize;
    int nTop;//代表 最上面 图层的高度

    struct SHEET *sheets[MAX_SHEETS];//SHEET的指针数组
    struct SHEET sheets0[MAX_SHEETS];//指向 下面的结构体的地址
};


//初始化 透明图层 控制的结构体
struct SHEET_CONTROL * SheetControlInit(struct MEMORY_MAN *pTemp,
 unsigned char *pu8Vram, int nXsize, int nYsize);

//取得新生成的 未使用图层
struct SHEET_CONTROL * GetSheet(struct SHEET_CONTROL *pCTL);

//设置图层的缓冲区大小 和 透明色的函数
void SheetSetBuf(struct SHEET *pSheet, unsigned char *u8Buf, 
int nXsize, int nYsize, int nColInv);

//设定底板的高度
void SheetUpDown(struct SHEET *pSheet, int Height);

//移动图层 不改变 图层高度 
void SheetSlide(struct SHEET *pSheet, int Vx0, int Vy0);

//释放 已使用图层的内存
void SheetFree(struct SHEET *pSheet);

//刷新图层显示
void SheetRefreshSub(struct SHEET_CONTROL *pCTL, int Vx0, int Vy0,
 int Vx1, int Vy1, int nBegin, int nEnd);

//刷新图层显示
void SheetRefresh(struct SHEET *pSheet, int Bx0, int By0, int Bx1,
 int By1);

//向map中写入图层号码
void SheetRefershMap(struct SHEET_CONTROL *pCTL, int Vx0, int Vy0,
 int Vx1, int Vy1, int nBegin);

#endif

Sheet.c

#include "Sheet.h"
#include "Memory.h"


//初始化 透明图层 控制的结构体
struct SHEET_CONTROL * SheetControlInit(struct MEMORY_MAN *pTemp, 
unsigned char *pu8Vram, int nXsize, int nYsize)
{
    struct SHEET_CONTROL *pCTL;
    int i;

    //为结构体分配内存空间
    pCTL = (struct SHEET_CONTROL *)MemoryManagerMalloc4K(pTemp, 
    sizeof(struct SHEET_CONTROL));

    //如果分配失败的话
    if(0 == pCTL)
    {
        goto err;
    }

    //整个界面的图层的分布
    pCTL->pMap = (unsigned char *)MemoryManagerMalloc4K(pTemp,
     nXsize*nYsize);
    if(0 == pCTL->pMap)
    {
        MemoryManagerFree4K(pTemp, (int)pCTL,
         sizeof(struct SHEET_CONTROL));
        goto err;
    }

    //分配成功
    pCTL->pu8Vram = pu8Vram;
    pCTL->nXsize = nXsize;
    pCTL->nYsize = nYsize;
    pCTL->nTop = -1; //代表 一个图层都没有

    for(i = 0; i < MAX_SHEETS; i++)
    {
        pCTL->sheets0[i].nFlags = 0;//代表 没有使用过
        pCTL->sheets0[i].pCtl = pCTL;
    }


    err:
        return pCTL;

}

//取得新生成的 未使用图层
struct SHEET_CONTROL * GetSheet(struct SHEET_CONTROL *pCTL)
{

    struct SHEET *pSheet;
    int i;

    for(i = 0; i < MAX_SHEETS; i++)
    {
        //该图层 未被使用
        if(0 == pCTL->sheets0[i].nFlags)
        {
            pSheet = &(pCTL->sheets0[i]);
            //标记为使用过了
            pSheet->nFlags = SHEET_USE;
            pSheet->nHeight = -1;//表示隐藏 图层高度还未设置

            return pSheet;
        }
    }


    return 0;
}


//设置图层的缓冲区大小 和 透明色的函数
void SheetSetBuf(struct SHEET *pSheet, unsigned char *pu8Buf,
 int nXsize, int nYsize, int nColInv)
{
    pSheet->pu8Buf = pu8Buf;
    pSheet->nBxSize = nXsize;
    pSheet->nBySize = nYsize;
    pSheet->nColInv = nColInv;

    return ;
}



//移动图层 不改变 图层高度 
void SheetSlide(struct SHEET *pSheet, int Vx0, int Vy0)
{
    struct SHEET_CONTROL *pCTL = pSheet->pCtl;

    int nOldVx0 = pSheet->nVx0;
    int nOldVy0 = pSheet->nVy0;

    pSheet->nVx0 = Vx0;
    pSheet->nVy0 = Vy0;

    //如果该图层 正在被使用
    if(pSheet->nHeight >= 0)
    {
        SheetRefershMap(pCTL, nOldVx0, nOldVy0, pSheet->nBxSize
         + nOldVx0, pSheet->nBySize + nOldVy0, 0);
        SheetRefershMap(pCTL,Vx0, Vy0, pSheet->nBxSize + Vx0,
         pSheet->nBySize + Vy0, pSheet->nHeight);

        //按新图层的信息 重新描绘画面
        SheetRefreshSub(pCTL, nOldVx0, nOldVy0, pSheet->nBxSize
         + nOldVx0, pSheet->nBySize + nOldVy0, 0, pSheet->nHeight-1);
        SheetRefreshSub(pCTL, Vx0, Vy0, pSheet->nBxSize + Vx0, 
        pSheet->nBySize + Vy0, pSheet->nHeight, pSheet->nHeight);
    }

    return ;
}

//释放 已使用图层的内存
void SheetFree( struct SHEET *pSheet)
{
    if(0 <= pSheet->nHeight)
    {
        SheetUpDown(pSheet, -1);
    }

    //未被使用
    pSheet->nFlags = 0;

    return;
}


//刷新图层显示
void SheetRefresh(struct SHEET *pSheet, int Bx0, int By0, int Bx1, 
int By1)
{
    
    //如果正在显示的话
    if (pSheet->nHeight >= 0)
    {
        SheetRefreshSub(pSheet->pCtl ,pSheet->nVx0+Bx0, 
        pSheet->nVy0+By0, pSheet->nVx0+Bx1, pSheet->nVy0+By1,
         pSheet->nHeight, pSheet->nHeight);
    }
    
}





//向map中写入图层号码
void SheetRefershMap(struct SHEET_CONTROL *pCTL, int Vx0, int Vy0, 
int Vx1, int Vy1, int nBegin)
{
    int nHeight;
    int nBx, nBy;
    int nVx, nVy;
    int Bx0, By0, Bx1, By1;
    unsigned char * pu8Buf;

    unsigned char u8SheetId;
    unsigned char *pMap = pCTL->pMap;

    struct SHEET *pSheet;

    if(0 > Vx0) {Vx0 = 0;}
    if(0 > Vy0) {Vy0 = 0;}
    if(Vx1 > pCTL->nXsize) {Vx1 = pCTL->nXsize;}
    if(Vy1 > pCTL->nYsize)  {Vy1 = pCTL->nYsize;}

    //描绘出需要改变的的图层
    for(nHeight = nBegin; nHeight <= pCTL->nTop; nHeight++)
    {
        pSheet = pCTL->sheets[nHeight];//当前该图层的首地址
        pu8Buf = pSheet->pu8Buf;

        u8SheetId = pSheet - (pCTL->sheets0);
        //进行了减法计算的地址 当做图层号码使用


        Bx0 = Vx0 - pSheet->nVx0;
        By0 = Vy0 - pSheet->nVy0;
        Bx1 = Vx1 - pSheet->nVx0;
        By1 = Vy1 - pSheet->nVy0;

        if(Bx0 < 0) {Bx0 = 0;}
        if(By0 < 0) {By0 = 0;}
        if(Bx1 > pSheet->nBxSize) {Bx1 = pSheet->nBxSize;}
        if(By1 > pSheet->nBySize) {By1 = pSheet->nBySize;}

        for(nBy = By0; nBy < By1; nBy++)
        {
            nVy = pSheet->nVy0 + nBy;

            for(nBx = Bx0; nBx < Bx1; nBx++)
            {
                nVx = pSheet->nVx0 + nBx;

                if(pu8Buf[nBy * pSheet->nBxSize + nBx] != 
                pSheet->nColInv)
                {
                    pMap[nVy * pCTL->nXsize + nVx] = u8SheetId;
                }

            }
        }
    }

    return ;
}


//刷新图层显示 设定了起始图层 和 结束图层
void SheetRefreshSub(struct SHEET_CONTROL *pCTL, int Vx0, int Vy0, 
int Vx1, int Vy1, int nBegin, int nEnd)
{
    int nHeight;
    int nBx, nBy;
    int nVx, nVy;
    int Bx0, By0, Bx1, By1;
    unsigned char * pu8Buf;
    //unsigned char ucChar;
    unsigned char *pMap = pCTL->pMap;
    unsigned char *Vram = pCTL->pu8Vram;
    unsigned char u8SheetId;
    struct SHEET *pSheet;

    if(0 > Vx0) {Vx0 = 0;}
    if(0 > Vy0) {Vy0 = 0;}
    if(Vx1 > pCTL->nXsize) {Vx1 = pCTL->nXsize;}
    if(Vy1 > pCTL->nYsize)  {Vy1 = pCTL->nYsize;}

    //描绘出需要改变的的图层
    for(nHeight = nBegin; nHeight <= nEnd; nHeight++)
    {
        pSheet = pCTL->sheets[nHeight];
        pu8Buf = pSheet->pu8Buf;

        u8SheetId = pSheet - (pCTL->sheets0);
        //进行了减法计算的地址 当做图层号码使用

        Bx0 = Vx0 - pSheet->nVx0;
        By0 = Vy0 - pSheet->nVy0;
        Bx1 = Vx1 - pSheet->nVx0;
        By1 = Vy1 - pSheet->nVy0;

        if(Bx0 < 0) {Bx0 = 0;}
        if(By0 < 0) {By0 = 0;}
        if(Bx1 > pSheet->nBxSize) {Bx1 = pSheet->nBxSize;}
        if(By1 > pSheet->nBySize) {By1 = pSheet->nBySize;}

        for(nBy = By0; nBy < By1; nBy++)
        {
            nVy = pSheet->nVy0 + nBy;

            for(nBx = Bx0; nBx < Bx1; nBx++)
            {
                nVx = pSheet->nVx0 + nBx;

                //如果Map中的SheetId 和 当前图层的SheetId 一致 
                //那我们就在屏幕上描画
                if(pMap[nVy * pCTL->nXsize + nVx] == u8SheetId)
                {
                    Vram[nVy * pCTL->nXsize + nVx] =
                     pu8Buf[nBy * pSheet->nBxSize + nBx];
                }
            }
        }
    }

    return ;
}

void SheetUpDown(struct SHEET *pSheet, int Height)
{
    struct SHEET_CONTROL *pCTL = pSheet->pCtl;
    int h;
    int nOldHeight = pSheet->nHeight;

    pSheet->nHeight = Height;

    //如果指定的高度 太高 或者 过低 则 进行修正
    if(Height > (pCTL->nTop + 1))
    {
        Height = pCTL->nTop + 1;
    }

    if(Height < -1)
    {
        Height = -1;
    }

    //进行sheets的重新排列 

    //如果新设置的高度 比从前 低的话
    if(nOldHeight > Height)
    {
        if(Height >= 0)
        {
            //向后 搬迁
            for(h = nOldHeight; h > Height; h--)
            {
                pCTL->sheets[h] = pCTL->sheets[h-1];
                pCTL->sheets[h]->nHeight = h;
            }

            pCTL->sheets[Height] = pSheet;


            SheetRefershMap(pCTL,  pSheet->nVx0, pSheet->nVy0, 
            pSheet->nVx0+pSheet->nBxSize,
             pSheet->nVy0+pSheet->nBySize, Height+1);
            //按新图层的信息 重新描绘画面
            SheetRefreshSub(pCTL, pSheet->nVx0, pSheet->nVy0,
             pSheet->nVx0+pSheet->nBxSize, 
             pSheet->nVy0+pSheet->nBySize, Height+1, nOldHeight);
        }

        //就是要隐藏图层了
        else
        {
            if(pCTL->nTop > nOldHeight)
            {
                for(h = nOldHeight; h < pCTL->nTop; h++)
                {
                    pCTL->sheets[h] = pCTL->sheets[h+1];
                    pCTL->sheets[h]->nHeight = h;
                }
            }
            pCTL->nTop--;

            SheetRefershMap(pCTL, pSheet->nVx0, pSheet->nVy0, 
            pSheet->nVx0+pSheet->nBxSize,
             pSheet->nVy0+pSheet->nBySize, 0);
            //按新图层的信息 重新描绘画面
            SheetRefreshSub(pCTL, pSheet->nVx0, pSheet->nVy0, 
            pSheet->nVx0+pSheet->nBxSize, 
            pSheet->nVy0+pSheet->nBySize, 0, nOldHeight-1);
        }
        
    }
    else if(nOldHeight < Height)//比以前高
    {
        if(nOldHeight >= 0)//以前 就是显示状态
        {
            for(h = nOldHeight; h < Height; h++)
            {
                pCTL->sheets[h] = pCTL->sheets[h+1];
                pCTL->sheets[h]->nHeight = h;
            }

            pCTL->sheets[h] = pSheet;
        }
        else//由隐藏状态 转变为 显示状态
        {
            for(h = pCTL->nTop; h >= Height; h--)
            {
                pCTL->sheets[h+1] = pCTL->sheets[h];
                pCTL->sheets[h+1]->nHeight = h+1;
            }

            pCTL->sheets[Height] = pSheet;
            pCTL->nTop++;
        }

        SheetRefershMap(pCTL, pSheet->nVx0, pSheet->nVy0, 
        pSheet->nVx0+pSheet->nBxSize, 
        pSheet->nVy0+pSheet->nBySize, Height);
        //按新图层的信息 重新描绘画面
         SheetRefreshSub(pCTL, pSheet->nVx0, pSheet->nVy0,
          pSheet->nVx0+pSheet->nBxSize, 
          pSheet->nVy0+pSheet->nBySize, Height, Height);

    }

    return ;
}
#include <reg51.h> #include <string.h> #include <intrins.h> // 引脚定义 sbit RS = P2^6; sbit RW = P2^5; sbit E = P2^7; sbit LED = P2^0; // LED引脚(P2^0) sbit k1 = P3^1; // 时/年/闹钟时 sbit k2 = P3^0; // 分/月/闹钟分 sbit k3 = P3^2; // 秒/日/闹钟开关 sbit k4 = P3^3; // 切换显示页面(1-年月日/2-温度/3-闹钟/4-原界面) sbit DQ = P3^7; // 温度传感器引脚 sbit BUZZER = P3^6; // 蜂鸣器引脚 // 时间和日期变量 char year = 24, month = 8, day = 25; // 年(2024),月,日 char hour = 8, minute = 0, second = 0; // 时,分,秒 unsigned int count = 0; // 定时器计数 // 闹钟变量 char alarm_hour = 7; char alarm_minute = 30; bit alarm_enabled = 1; bit alarm_ringing = 0; bit alarm_triggered = 0; // 防止重复触发 // 温度相关变量 float temperature = 25.0; // 当前温度值 bit temp_alarm = 0; // 温度报警标志 bit sensor_status = 0; // 传感器状态 // 页面显示变量 - K4按1-4次对应0-3 unsigned char current_page = 0; // 0-原时间界面,1-年月日调整,2-温度界面,3-闹钟调整 // 星期字符串 char *weekdays[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; // 按键状态变量 bit k1_pressed = 0; bit k2_pressed = 0; bit k3_pressed = 0; bit k4_pressed = 0; // 长按检测变量 unsigned char k1_press_count = 0; unsigned char k2_press_count = 0; unsigned char k3_press_count = 0; // 整点报时相关 bit chime_done = 0; unsigned char chime_count = 0; // 整点报时计数器 // 蜂鸣器控制 unsigned char buzzer_mode = 0; // 0-关闭,1-闹钟,2-温度,3-整点,4-按键提示 unsigned int buzzer_timer = 0; // 蜂鸣器计时器 bit buzzer_state = 0; // 蜂鸣器当前状态 // 温度读取定时 unsigned int temp_read_timer = 0; // 温度读取定时器(ms) bit temp_read_flag = 0; // 温度读取标志 // LED控制变量(新增) unsigned int led_timer = 0; // LED闪烁计时器 bit led_state = 0; // LED当前状态(0-灭,1-亮) // 显示缓存 char str1[17]; // 第一行显示缓存 char str2[17]; // 第二行显示缓存 bit display_update = 1; // 显示更新标志 // 全局变量用于日期和时间计算 char last_day = 0; char weekday = 0; char last_hour = 0; // 蜂鸣器控制宏定义(适配NPN三极管) #define BUZZER_ON BUZZER = 0 #define BUZZER_OFF BUZZER = 1 // -------------------------- 精确延时函数(基于11.0592MHz晶振) -------------------------- // 10us延时 void delay_10us(unsigned int t) { while(t--) { _nop_();_nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_();_nop_(); } } // ms延时 void delay_ms(unsigned int ms) { unsigned int i, j; for(i = 0; i < ms; i++) for(j = 0; j < 114; j++); } // -------------------------- DS18B20通信时序 -------------------------- // 单总线初始化 unsigned char OneWire_Init(void) { unsigned char i; unsigned char AckBit; DQ=1; DQ=0; i = 247;while (--i); // Delay 500us DQ=1; i = 32;while (--i); // Delay 70us AckBit=DQ; i = 247;while (--i); // Delay 500us return AckBit; } // 单总线发送一位 void OneWire_SendBit(unsigned char Bit) { unsigned char i; DQ=0; i = 4;while (--i); // Delay 10us DQ=Bit; i = 24;while (--i); // Delay 50us DQ=1; } // 单总线接收一位 unsigned char OneWire_ReceiveBit(void) { unsigned char i; unsigned char Bit; DQ=0; i = 2;while (--i); // Delay 5us DQ=1; i = 2;while (--i); // Delay 5us Bit=DQ; i = 24;while (--i); // Delay 50us return Bit; } // 单总线发送一个字节 void OneWire_SendByte(unsigned char Byte) { unsigned char i; for(i=0;i<8;i++) { OneWire_SendBit(Byte&(0x01<<i)); } } // 单总线接收一个字节 unsigned char OneWire_ReceiveByte(void) { unsigned char i; unsigned char Byte=0x00; for(i=0;i<8;i++) { if(OneWire_ReceiveBit()){Byte|=(0x01<<i);} } return Byte; } // DS18B20指令 #define DS18B20_SKIP_ROM 0xCC #define DS18B20_CONVERT_T 0x44 #define DS18B20_READ_SCRATCHPAD 0xBE // DS18B20开始温度变换 void DS18B20_ConvertT(void) { OneWire_Init(); OneWire_SendByte(DS18B20_SKIP_ROM); OneWire_SendByte(DS18B20_CONVERT_T); } // DS18B20读取温度 float DS18B20_ReadT(void) { unsigned char TLSB,TMSB; int Temp; float T; OneWire_Init(); OneWire_SendByte(DS18B20_SKIP_ROM); OneWire_SendByte(DS18B20_READ_SCRATCHPAD); TLSB=OneWire_ReceiveByte(); TMSB=OneWire_ReceiveByte(); Temp=(TMSB<<8)|TLSB; T=Temp/16.0; return T; } // 温度读取 float read_temp(void) { unsigned int wait_cnt = 0; float temp = 999.0; // 默认错误值 if(OneWire_Init() != 0) { // 传感器不存在 return 999.0; } DS18B20_ConvertT(); // 启动转换 while(wait_cnt < 80) { // 等待400ms delay_ms(5); wait_cnt++; } temp = DS18B20_ReadT(); // 读取温度 // 范围校验 if(temp < -55 || temp > 125) { return 999.0; } return temp; } // 传感器测试 bit test_temp_sensor(void) { unsigned char i; bit result = 1; for(i = 0; i < 3; i++) { if(OneWire_Init() != 0) result = 0; delay_ms(100); } return result; } // -------------------------- LCD显示优化 -------------------------- void sentcmd(unsigned char cmd) { RS = 0; RW = 0; P0 = cmd; E = 1; _nop_(); _nop_(); E = 0; delay_10us(5); } void sentdata(unsigned char dat) { RS = 1; RW = 0; P0 = dat; E = 1; _nop_(); _nop_(); E = 0; delay_10us(5); } // 固定显示16个字符 void display(char str[], char i) { unsigned char j; if(i == 1) { sentcmd(0x80); // 第一行 } else { sentcmd(0xC0); // 第二行 } delay_ms(1); for(j = 0; j < 16; j++) { sentdata(str[j]); delay_ms(1); } } void init() { delay_ms(20); BUZZER_OFF; LED = 0; // 初始化LED为灭(新增) // LCD初始化 sentcmd(0x38); delay_ms(5); sentcmd(0x38); delay_ms(5); sentcmd(0x01); delay_ms(2); sentcmd(0x0C); delay_ms(1); // 开显示,关光标 sentcmd(0x06); delay_ms(1); // 增量显示 } // -------------------------- 时间日期计算优化 -------------------------- bit is_leap_year(char y) { int year_full = 2000 + y; return ((year_full % 4 == 0 && year_full % 100 != 0) || (year_full % 400 == 0)) ? 1 : 0; } char get_days_in_month(char y, char m) { if(m == 2) return is_leap_year(y) ? 29 : 28; if(m == 4 || m == 6 || m == 9 || m == 11) return 30; return 31; } char calculate_weekday(char y, char m, char d) { char month_adj, year_adj; int year_full, j, h; year_full = 2000 + y; if (m < 3) { month_adj = m + 12; year_full--; } else { month_adj = m; } year_adj = year_full % 100; j = year_full / 100; h = (d + ((13 * (month_adj + 1)) / 5) + year_adj + (year_adj / 4) + (j / 4) - 2 * j) % 7; return h < 0 ? h + 7 : h; } // -------------------------- 蜂鸣器控制 -------------------------- void set_buzzer(unsigned char mode) { buzzer_mode = mode; buzzer_timer = 0; buzzer_state = 0; chime_count = 0; switch(mode) { case 0: // 关闭 BUZZER_OFF; break; case 1: // 闹钟(500ms周期) case 2: // 温度报警(200ms周期) case 3: // 整点报时(250ms周期) case 4: // 按键提示(100ms) BUZZER_ON; buzzer_state = 1; break; } } // -------------------------- 定时器0中断 -------------------------- void time() { TMOD = 0x01; // 定时器0模式1(16位) TH0 = 0xFC; // 初值:1ms中断(11.0592MHz) TL0 = 0x18; ET0 = 1; // 使能定时器0中断 EA = 1; // 开总中断 TR0 = 1; // 启动定时器0 } void timer0_isr() interrupt 1 using 1 { TH0 = 0xFC; TL0 = 0x18; count++; led_timer++; // LED计时器累加(新增) // 1ms中断×1000 = 1秒 if(count >= 1000) { count = 0; second++; chime_done = 0; // 秒→分→时→日→月→年进位 if(second >= 60) { second = 0; minute++; if(minute >= 60) { minute = 0; hour++; if(hour >= 24) { hour = 0; day++; if(day > get_days_in_month(year, month)) { day = 1; month++; if(month > 12) { month = 1; year++; if(year >= 100) year = 0; } } } } } } // 蜂鸣器控制 buzzer_timer++; switch(buzzer_mode) { case 1: // 闹钟(500ms周期) if(buzzer_timer >= 500) { buzzer_timer = 0; buzzer_state = !buzzer_state; if(buzzer_state) BUZZER_ON; else BUZZER_OFF; } break; case 2: // 温度报警(200ms周期) if(buzzer_timer >= 200) { buzzer_timer = 0; buzzer_state = !buzzer_state; if(buzzer_state) BUZZER_ON; else BUZZER_OFF; } break; case 3: // 整点报时(250ms周期,响3次) if(buzzer_timer >= 250) { buzzer_timer = 0; buzzer_state = !buzzer_state; if(buzzer_state) BUZZER_ON; else BUZZER_OFF; if(++chime_count >= 6) { // 3次响+3次停 chime_count = 0; set_buzzer(0); } } break; case 4: // 按键提示(100ms) if(buzzer_timer >= 100) { set_buzzer(0); } break; } // LED闪烁控制(新增):温度报警时500ms闪烁一次,否则灭 if(temp_alarm) { if(led_timer >= 500) { // 500ms切换一次LED状态 led_timer = 0; led_state = !led_state; LED = led_state; } } else { LED = 0; // 非温度报警时LED灭 led_timer = 0; // 重置计时器 led_state = 0; // 重置状态 } // 温度读取定时 temp_read_timer++; } // -------------------------- 按键检测优化 -------------------------- void key() { // K4按键逻辑:按1-4次循环切换页面 if(k4 == 0) { delay_ms(20); // 消抖 if(k4 == 0) { current_page++; // 每次按下计数加1 if(current_page >= 4) { // 按到第4次回到原界面 current_page = 0; } set_buzzer(4); // 按键提示音 display_update = 1; // 更新显示 delay_ms(300); // 防连按 } } // 根据当前页面处理其他按键 switch(current_page) { // 页面0:原时间显示界面(K4按4次) case 0: // 调整时间 if(k1 == 0) // 调整小时 { delay_ms(20); if(k1 == 0) { hour++; if(hour >= 24) hour = 0; display_update = 1; set_buzzer(4); delay_ms(300); } } if(k2 == 0) // 调整分钟 { delay_ms(10); if(k2 == 0) { minute++; if(minute >= 60) minute = 0; display_update = 1; set_buzzer(4); delay_ms(200); } } if(k3 == 0) // 调整秒钟 { delay_ms(20); if(k3 == 0) { second++; if(second >= 60) second = 0; display_update = 1; set_buzzer(4); delay_ms(300); } } break; // 页面1:年月日调整界面(K4按1次) case 1: if(k1 == 0) // 调整年份 { delay_ms(20); if(k1 == 0) { year++; if(year >= 100) year = 0; display_update = 1; set_buzzer(4); delay_ms(300); } } if(k2 == 0) // 调整月份 { delay_ms(20); if(k2 == 0) { month++; if(month > 12) month = 1; display_update = 1; set_buzzer(4); delay_ms(300); } } if(k3 == 0) // 调整日期 { delay_ms(20); if(k3 == 0) { day++; if(day > get_days_in_month(year, month)) day = 1; display_update = 1; set_buzzer(4); delay_ms(300); } } break; // 页面2:温度界面(K4按2次) case 2: // 温度界面无按键操作,仅显示 break; // 页面3:闹钟调整模式(K4按3次) case 3: if(k1 == 0) // 调整闹钟小时 { delay_ms(20); if(k1 == 0) { alarm_hour++; if(alarm_hour >= 24) alarm_hour = 0; display_update = 1; set_buzzer(4); delay_ms(300); } } if(k2 == 0) // 调整闹钟分钟 { delay_ms(20); if(k2 == 0) { alarm_minute++; if(alarm_minute >= 60) alarm_minute = 0; display_update = 1; set_buzzer(4); delay_ms(300); } } if(k3 == 0) // 切换闹钟开关 { delay_ms(20); if(k3 == 0) { alarm_enabled = !alarm_enabled; display_update = 1; set_buzzer(4); delay_ms(300); } } break; } // 停止闹钟/温度报警(任意按键) if(alarm_ringing || temp_alarm) { if(k1 == 0 || k2 == 0 || k3 == 0 || k4 == 0) { delay_ms(10); if(k1 == 0 || k2 == 0 || k3 == 0 || k4 == 0) { alarm_ringing = 0; temp_alarm = 0; set_buzzer(0); delay_ms(300); } } } } // -------------------------- 显示更新函数 -------------------------- void update_display() { unsigned char i; int int_part; // 温度整数部分 unsigned int dec_part; // 温度小数部分 // 星期计算(仅当日期变化时) if(day != last_day) { weekday = calculate_weekday(year, month, day); last_day = day; } // 页面显示逻辑 switch(current_page) { // 页面0:原时间界面(K4按4次) case 0: // 第一行:2024-08-25 SUN str1[0] = '2'; str1[1] = '0'; str1[2] = (year / 10) + '0'; str1[3] = (year % 10) + '0'; str1[4] = '-'; str1[5] = (month / 10) ? (month / 10) + '0' : '0'; str1[6] = (month % 10) + '0'; str1[7] = '-'; str1[8] = (day / 10) ? (day / 10) + '0' : '0'; str1[9] = (day % 10) + '0'; str1[10] = ' '; str1[11] = weekdays[weekday][0]; str1[12] = weekdays[weekday][1]; str1[13] = weekdays[weekday][2]; for(i = 14; i < 16; i++) str1[i] = ' ';//剩下位置与空格补齐 // 第二行:08:00:00 str2[0] = (hour / 10) ? (hour / 10) + '0' : '0'; str2[1] = (hour % 10) + '0'; str2[2] = ':'; str2[3] = (minute / 10) ? (minute / 10) + '0' : '0'; str2[4] = (minute % 10) + '0'; str2[5] = ':'; str2[6] = (second / 10) ? (second / 10) + '0' : '0'; str2[7] = (second % 10) + '0'; str2[8] = ' '; str2[9] = ' '; str2[10] = ' '; str2[11] = 'Y'; str2[12] = 'X'; str2[13] = 'Q'; for(i = 14; i < 16; i++) str2[i] = ' '; break; // 页面1:年月日调整界面(K4按1次) case 1: // 第一行:Date Set str1[0] = 'Y'; str1[1] = 'e'; str1[2] = 'a'; str1[3] = 'r'; str1[4] = ':'; str1[5] = ' '; str1[6] = ' '; str1[7] = ' '; for(i = 8; i < 16; i++) str1[i] = ' '; // 第二行:2024-08-25 str2[0] = '2'; str2[1] = '0'; str2[2] = (year / 10) + '0'; str2[3] = (year % 10) + '0'; str2[4] = '-'; str2[5] = (month / 10) ? (month / 10) + '0' : '0'; str2[6] = (month % 10) + '0'; str2[7] = '-'; str2[8] = (day / 10) ? (day / 10) + '0' : '0'; str2[9] = (day % 10) + '0'; for(i = 10; i < 16; i++) str2[i] = ' '; break; // 页面2:温度界面(K4按2次) case 2: // 第一行:Temperature: str1[0] = 'T'; str1[1] = 'e'; str1[2] = 'm'; str1[3] = 'p'; str1[4] = 'e'; str1[5] = 'r'; str1[6] = 'a'; str1[7] = 't'; str1[8] = 'u'; str1[9] = 'r'; str1[10] = 'e'; str1[11] = ':'; for(i = 12; i < 16; i++) str1[i] = ' '; // 第二行:显示带小数的温度 memset(str2, ' ', 16); if(temperature == 999.0) { str2[7] = 'E'; str2[8] = 'R'; str2[9] = 'R'; // 错误提示 } else if(temperature < 0) { str2[6] = '-'; int_part = (int)(-temperature); dec_part = (unsigned int)((-temperature - int_part) * 10); str2[7] = (int_part / 10) + '0'; str2[8] = (int_part % 10) + '0'; str2[9] = '.'; str2[10] = dec_part + '0'; str2[11] = 'C'; } else { str2[6] = ' '; int_part = (int)temperature; dec_part = (unsigned int)((temperature - int_part) * 10); str2[7] = (int_part / 10) + '0'; str2[8] = (int_part % 10) + '0'; str2[9] = '.'; str2[10] = dec_part + '0'; str2[11] = 'C'; } break; // 页面3:闹钟调整模式(K4按3次) case 3: // 第一行:Alarm Set [ON/OFF] str1[0] = 'A'; str1[1] = 'l'; str1[2] = 'a'; str1[3] = 'r'; str1[4] = 'm'; str1[5] = ':'; str1[6] = ' '; str1[7] = ' '; str1[8] = ' '; str1[9] = ' '; str1[10] = ' '; str1[11] = alarm_enabled ? 'O' : 'F'; str1[12] = alarm_enabled ? 'N' : 'F'; str1[13] = ' '; for(i = 14; i < 16; i++) str1[i] = ' '; // 第二行:07:30 memset(str2, ' ', 16); str2[7] = (alarm_hour / 10) ? (alarm_hour / 10) + '0' : '0'; str2[8] = (alarm_hour % 10) + '0'; str2[9] = ':'; str2[10] = (alarm_minute / 10) ? (alarm_minute / 10) + '0' : '0'; str2[11] = (alarm_minute % 10) + '0'; break; } // 刷新LCD显示 display(str1, 1); display(str2, 2); display_update = 0; } // -------------------------- 主函数 -------------------------- void main() { unsigned char i; // 初始化显示缓存为空格 for(i = 0; i < 16; i++) { str1[i] = ' '; str2[i] = ' '; } str1[16] = '\0'; str2[16] = '\0'; // 硬件初始化 init(); // LCD初始化 time(); // 定时器0初始化 sensor_status = test_temp_sensor(); // 检测温度传感器 DS18B20_ConvertT(); // 上电先转换一次温度 delay_ms(500); // 等待转换完成 while(1) { // 每1秒读取一次温度 if(temp_read_timer >= 1000 && !temp_read_flag) { temp_read_timer = 0; DS18B20_ConvertT(); delay_ms(75); temperature = DS18B20_ReadT(); // 温度报警判断(5℃~35℃为正常范围) temp_alarm = (temperature != 999.0 && (temperature < 5 || temperature > 35)) ? 1 : 0; temp_read_flag = 1; display_update = 1; } if(temp_read_flag && temp_read_timer > 100) { temp_read_flag = 0; } // 整点报时 if(minute == 0 && second == 0 && hour != last_hour) { set_buzzer(3); last_hour = hour; } // 闹钟判断 if(alarm_enabled && !alarm_ringing) { if(hour == alarm_hour && minute == alarm_minute && second == 0 && !alarm_triggered) { alarm_ringing = 1; alarm_triggered = 1; set_buzzer(1); } } else if(second != 0) { alarm_triggered = 0; } // 按键检测 key(); // 更新显示 if(display_update) { update_display(); } } }保留代码全部功能精简代码
09-04
这是一个按键的代码 void ALM_Key_Pro(PRESS_MODE_TypeDef Press_Mode) { if(SysData.Cal_Mode) { if(SysData.Freq_Weight == C_WEIGHT) { SysData.Freq_Weight = A_WEIGHT; FREQ_WEIGHT_A; Disp_All(); Delayms(2000); } else { SysFlag.Cal_En = 1; Cal_Pro(); } return; } switch(SysData.Sys_State) { case MAIN_STATE: switch(Press_Mode) { case SHORT_PRESS: if(Press_Mode != SHORT_PRESS)break; //报警开关 if(SysData.ALM_EN == ENABLE) { Close_Alm(); SysData.ALM_EN = DISABLE; LED_2_OFF; //yanggj } else { SysData.ALM_EN = ENABLE; } SysInfo.AlmPara.Struct_Para.Alm_Switch = SysData.ALM_EN; Save_Param(&SysInfo.AlmPara, sizeof(SysInfo.AlmPara)); break; case LONG_PRESS: Close_Alm(); SysData.WaitReleaseKey = 0; SysData.Save_Cnt = 0; SysData.Sys_State = SET_STATE; SysData.Set_State = SET_ALM; SysData.SET_Value = SysData.Alm / 10; SysFlag.DispSetEn = 1; Time_Set.year = Time_Dec.year; Time_Set.month = Time_Dec.month; Time_Set.day = Time_Dec.day; Time_Set.hour = Time_Dec.hour; Time_Set.minute = Time_Dec.minute; Time_Set.second = Time_Dec.second; break; } break; case SET_STATE://设置状态 switch(SysData.Set_State) { case SET_ALM: SysData.Alm = SysData.SET_Value * 10; SysInfo.AlmPara.Struct_Para.Alm_Value = SysData.Alm; SysData.SET_Value = SysInfo.AlmPara.Struct_Para.Alm_Time; SysData.Set_State = SET_ALM_TIME; LED_2_OFF; Save_Param(&SysInfo.AlmPara, sizeof(SysInfo.AlmPara)); break; case SET_ALM_TIME: SysData.Alm_Time = SysData.SET_Value; SysInfo.AlmPara.Struct_Para.Alm_Time = SysData.Alm_Time; SysData.Set_State = SET_ALM_Type; SysData.SET_Value = SysInfo.AlmPara.Struct_Para.Alm_Type; Save_Param(&SysInfo.AlmPara, sizeof(SysInfo.AlmPara)); break; case SET_ALM_Type: SysData.Alm_Type = SysData.SET_Value; SysInfo.AlmPara.Struct_Para.Alm_Type = SysData.Alm_Type; switch(SysData.Alm_Type) { case 1: Set_Buzz_Pluse(BUZZ_0_LEVEL); break; case 2: Set_Buzz_Pluse(BUZZ_1_LEVEL); break; case 3: Set_Buzz_Pluse(BUZZ_2_LEVEL); break; } SysData.Set_State = SET_YEAR; SysData.SET_Value = Time_Set.year; Save_Param(&SysInfo.AlmPara, sizeof(SysInfo.AlmPara)); break; case SET_YEAR: Time_Set.year = SysData.SET_Value; Time_Hex.year = ((Time_Set.year / 10) << 4) | (Time_Set.year % 10); Save_TimeDate(); SysData.SET_Value = Time_Set.month; SysData.Set_State = SET_MONTH; break; case SET_MONTH: Time_Set.month = SysData.SET_Value; Time_Hex.month = ((Time_Set.month / 10) << 4) | (Time_Set.month % 10); Save_TimeDate(); SysData.SET_Value = Time_Set.day; SysData.Set_State = SET_DAY; break; case SET_DAY: Time_Set.day = SysData.SET_Value; Time_Hex.day = ((Time_Set.day / 10) << 4) | (Time_Set.day % 10); Save_TimeDate(); SysData.SET_Value = Time_Set.hour; SysData.Set_State = SET_HOUR; break; case SET_HOUR: Time_Set.hour = SysData.SET_Value; Time_Hex.hour = ((Time_Set.hour / 10) << 4) | (Time_Set.hour % 10); Save_TimeDate(); SysData.SET_Value = Time_Set.minute; SysData.Set_State = SET_MINUTE; break; case SET_MINUTE: Time_Set.minute = SysData.SET_Value; Time_Hex.minute = ((Time_Set.minute / 10) << 4) | (Time_Set.minute % 10); Save_TimeDate(); SysData.SET_Value = Time_Set.second; SysData.Set_State = SET_SECOND; break; case SET_SECOND: Time_Set.second = SysData.SET_Value; Time_Hex.second = ((Time_Set.second / 10) << 4) | (Time_Set.second % 10); Save_TimeDate(); //保存时间日期 if(SysData.ALM_EN == ENABLE) //yanggj { LED_2_ON; } else { LED_2_OFF; } Close_Alm(); SysData.Save_Cnt = 0; SysData.Sys_State = MAIN_STATE; SysData.Set_State = SET_ALM; SysData.WaitReleaseKey = 0; break; case SET_ALM_CLOCK_HOUR: SysData.Set_State = SET_ALM_CLOCK_MINUTE; SysData.SET_Value = SysInfo.AlarmClockInfo.minute; break; case SET_ALM_CLOCK_MINUTE: SysData.Set_State = SET_ALM_CLOCK_EN; //SysData.SET_Value = SysInfo.AlarmClockInfo.enabled; break; case SET_ALM_CLOCK_EN: //SysInfo.AlarmClockInfo.enabled = SysData.SET_Value; SysData.Set_State = SET_ALM; SysData.Sys_State = MAIN_STATE; Para_Save(); break; } break; } } void Disp_Set(void) { u32 Tmp; if((SysData.Sys_State != SET_STATE) || ((SysData.Sys_State == SET_STATE) && (SysData.Set_State > SET_SECOND))) return; if((SysFlag.FlickEn) && (SysFlag.LCDFlick == 0) && ((SysData.Set_State == SET_ALM_TIME) || (SysData.Set_State == SET_ALM_Type) || (SysData.Set_State == SET_ALM))) { Disp_Clear(); return; } SysFlag.DispSetEn = 0; Tmp = SysData.SET_Value; HexToBcd(Tmp); Fill_Data(); if(MainData[0] == 0) { MainData[0] = 10;//空 if((SysData.Set_State == SET_ALM_TIME) || (SysData.Set_State == SET_ALM_Type) || (SysData.Set_State == SET_ALM)) { if(MainData[1] == 0) MainData[1] = 10; } if(SysFlag.LCDFlick) { switch(SysData.Set_State) { case SET_YEAR: MainData[0] = 18;//年 break; case SET_MONTH: MainData[0] = 19;//月 break; case SET_DAY: MainData[0] = 20;//日 break; case SET_HOUR: MainData[0] = 21;//时 break; case SET_MINUTE: MainData[0] = 22;//分 break; case SET_SECOND: MainData[0] = 23;//秒 break; } } } // MainData[2]=Font_TAB_10_8_6[MainData[0]]; // MainData[1]=Font_TAB_11_9_7[MainData[1]]; // MainData[0]=Font_TAB_10_8_6[MainData[2]]; Disp_LCD(); } 这个是对应的显示的代码 #ifndef __TYPE_H #define __TYPE_H #include "main.h" typedef enum { RANG_70DB = 0x00, RANG_100DB = 0x01, RANG_130DB = 0x02 } MEA_RANGE_TypeDef; //typedef enum //{ // POINT_100DB = 0x00, // POINT_70DB = 0x01, // POINT_40DB = 0x02 //} CAL_POINT_TypeDef; typedef enum { SHORT_PRESS = 0x00, LONG_PRESS = 0x01, } PRESS_MODE_TypeDef; typedef enum { A_WEIGHT = 0x00, C_WEIGHT = 0x01, } FREQ_WEIGHT_TypeDef; typedef enum { FAST_RESP = 0x00, SLOW_RESP = 0x01, } FREQ_RESP_TypeDef; typedef enum { MAIN_STATE = 0x00, SET_STATE = 0x01, } SYS_STATE_TypeDef; typedef enum { SET_ALM = 0x00 , SET_ALM_TIME , SET_ALM_Type , SET_YEAR , SET_MONTH , SET_DAY , SET_HOUR , SET_MINUTE , SET_SECOND , SET_ALM_CLOCK_HOUR , SET_ALM_CLOCK_MINUTE , SET_ALM_CLOCK_EN , } SET_STATE_TypeDef; typedef enum { NO_OUT_RANGE = 0x00, OVER_RANGE = 0x01, UNDER_RANGE = 0x02, } OUT_RANGE_TypeDef; typedef struct { u8 KeyAge : 8; u8 KeyPress : 4; u8 KeyProcessed : 4; GPIO_Module *KeyPort; uint16_t KeyPin; void (*fun) (PRESS_MODE_TypeDef); } SysKey; typedef struct { u8 First_Press; u8 Mea_OK; u8 EEPROM_ERR; u8 SPIFlashErr; u8 Cal_Finish; // u8 Cal_DB_Finish; u8 Cal_En; u8 DispSetEn; u8 LCDFlick; u8 FlickEn; u8 RecFull; u8 Alarms; u8 UpOver; u8 ClockSoundEn; u8 GetRec; u8 BiBiBi; u8 Nop; u8 GetDate; u8 Test; u8 PWD, PWDErr; u8 EraseDoing; u8 UsbCmdLen; u8 SensorType; //温湿度传感器类型 u8 DMA_OK; u8 shutdown; u8 showLcd; } FLAG_TYPE; typedef struct { double ADC_VALUE[3]; double ADC_Sum_Value; u32 ADC_INT_Value; u16 ADC_CNT; u16 DB_Disp; u32 ADC_INT_Value_TAB[8]; u32 CAL_INT_Value; u16 Disp_ALM; //CAL u8 ADC_TAB_CNT; u8 Cal_Mode; //系统状态 // CAL_POINT_TypeDef Cal_DB_Point; FREQ_WEIGHT_TypeDef Freq_Weight; FREQ_RESP_TypeDef Freq_Resp; SYS_STATE_TypeDef Sys_State; OUT_RANGE_TypeDef Mea_Range; MEA_RANGE_TypeDef Adc_Range; SET_STATE_TypeDef Set_State; FunctionalState ALM_EN; u8 SaveCnt_2s; u16 Wait; u8 DispDB_Cnt; u8 CalEn; s16 SET_Value; u16 Save_2s_Data[14]; u32 Start_Rec;//1开始 但是程序中所有的变量 统一从0开始 所以在使用时要-1 u32 End_Rec;//1开始 但是程序中所有的变量 统一从0开始 所以在使用时要-1 u8 Cal_Step; u8 Save_Cnt; u8 FlashNum; u8 WaitReleaseKey; u32 OverCntt[3]; u8 Alm_Time; u8 Alm_Type; u16 Alm; u16 MaxDB_Save; u8 SaveTime2s; u8 DMA_Group; u8 BattCnt; u16 BattBuff[64]; u32 Batt; u8 Units; // 0: 摄氏度, 1: 华氏度 } DATA_TYPE; typedef struct { unsigned char year ; unsigned char month ; unsigned char day ; unsigned char hour ; unsigned char minute ; unsigned char second ; } DATE_TIME; typedef struct { unsigned char hour ; unsigned char minute ; unsigned char second ; unsigned char enabled ; } TIME_INFO; typedef union { u32 Mem; struct { u16 Alm_Value : 16; u8 Alm_Switch : 4; u8 Alm_Type : 4; u8 Alm_Time : 8; } Struct_Para; } UNION_PARA; typedef struct { u16 ID; u16 ChipStart; u32 REC_HalfPages;//从0开始 0至MAX_REC_Pages-1 int16_t Cal_Adc[3]; // int16_t Cal_DB[3]; int32_t Cal_Adc_Squ[3]; UNION_PARA AlmPara; u8 PWRON_Erase; u8 AlarmClockOk;//产生报警 TIME_INFO AlarmClockInfo; } SYS_INFO; //128 typedef struct { u8 Header; u8 Machine_State; u8 Year ; u8 Month ; u8 Day ; u8 Hour ; u8 Minute ; u8 Second ; u16 Result[60]; //Noise_Result: Freq_Weight + Freq_Respon + DB_Value 存储用 } STORAGE; #endif 这个是定义的结构和变量 void UP_Key_Pro(PRESS_MODE_TypeDef Press_Mode) { u16 Tmp; switch(SysData.Sys_State) { case MAIN_STATE: // if(SysData.Cal_Mode) break; // if(Press_Mode!=SHORT_PRESS)break; // // switch(SysData.Freq_Weight) // { // case A_WEIGHT: // SysData.Freq_Weight = C_WEIGHT; // FREQ_WEIGHT_C; // break; // // case C_WEIGHT: // SysData.Freq_Weight = A_WEIGHT; // FREQ_WEIGHT_A; // break; // } // SysData.Save_Cnt=0; // Disp_State(DISP_FREQ_WEIGHT); break; case SET_STATE: if(Press_Mode == LONG_PRESS) { KEy[Num_Up].KeyProcessed = 0; Tmp = KeyOffset; } else { Tmp = 1; } // if(SysFlag.DispSetEn) // { // break; // } SysFlag.FlickEn = 0; SysData.SET_Value += Tmp; SysData.WaitReleaseKey = 0; Check_Set(); Disp_AlarmClockSet(); SysFlag.DispSetEn = 1; break; } } void DOWN_Key_Pro(PRESS_MODE_TypeDef Press_Mode) { u16 Tmp; switch(SysData.Sys_State) { case MAIN_STATE: //主界面温度切换 if(Press_Mode==LONG_PRESS) { SysData.Units = !SysData.Units; Tmp = KeyOffset; } break; case SET_STATE: if(Press_Mode==LONG_PRESS) { KEy[Num_Down].KeyProcessed=0; Tmp = KeyOffset; } else { Tmp=1; } SysFlag.FlickEn=0;//长按不允许闪烁 SysData.SET_Value-=Tmp; SysData.WaitReleaseKey=0; Check_Set(); Disp_AlarmClockSet(); SysFlag.DispSetEn=1; break; } } 这个是上下键的代码 现在的要求是,原来进入到设置页面时,第一个调整的值是噪音的报警值,第二个是报警时长,第三个是报警模式,现在要把第一个调整的值改成温湿度的报警值设置,其它不变,怎么修改
06-20
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值