华东理工大学-信息工程-电子系统设计实践

前言

华理信工大三期末周的电子系统设计实践课,要写一个温度控制系统,现在开源,只要你智力正常,仔细看看绝对能看懂,如果不想看,直接把代码拷到keil上编译就行,到时候把pid调一下展示就行了,提早写完日志和报告,到时候验收时要交报告和日志。
事先声明,代码总量1500行,答辩中的答辩,但是可以完美运行,无bug,有bug你私信,我打视频电话教你改,pid调的也还行,总共有tp,run,con,pa,pid和ch功能,ch这个功能是我自己加的,用来修改密码和pid的三个参数,很重要,最好留着吧,要删的话你大概率会出bug。如果真的闲的慌,那你可以用指针和结构体等高级的数据结构来写代码,这才是真的牛,不然写半天还是育鸿班编程。

keil设置

设置晶振频率

先把keil设置成这样

把优化等级调到最高

这个level改成9,优化等级高一点,不然code超过8192了烧不进flash,flash就8K
code optimization

成功编译后样子

成功编译后长这个样子,这应该没人能做不出来吧,嵌入式课上都学过keil哈。
成功编译后

文件结构

在这里插入图片描述

代码

main.c

#include <reg52.h>
#include <stdio.h>
#include "HD7279.h"
#include "temperature.h"
#include "run.h"
#include "control.h"
#include "pa.h"
#include "24C16.h"
#include "pid.h"
#include "password.h"

sbit key = P1^6;

extern unsigned char runk;
extern char pwm_value[11];
extern char pacon_t[2];

unsigned int tmr=0;

void main(){
	unsigned char k=0,key_number=0xff;
	Init_7279();
	Init_24c16();
	pacon_t[0]=(char)readbyte_24c16(10);//初始化低温
	pwm_value[0]=(char)readbyte_24c16(0);//初始化第一个占空比
	//save_all_data();//给24c16装入初值用的
	while(1){	
		send_byte(CMD_RESET);
		do{
			displaymenu(k);
			if (!key){
				key_number=read7279(READ); 
				switch(key_number){
					case 0x3a:{//右上按键
						k++;
						if(k==6) k=0;
						displaymenu(k);
					}break;
					case 0x3b:{//左上按键
						k--;
						if(k==-1) k=5;
						displaymenu(k);
					}break;
					case 0x38:{//右下按键
						switch(k){
							case 0:tp();break;
							case 1:run();break;
							case 2:{con();write7279(BLINK,0xff);clean0123();}break;
							case 3:{password(0);write7279(BLINK,0xff);}break;//pa功能
							case 4:pid();break;
							case 5:{password(1);write7279(BLINK,0xff);}break;//改密码功能
							default:break;
						}
					}break;
					case 0x39:{//左下按键
						clean0123();
					}break;
				}
				while (!key); //代表上升沿触发
			}
		}while(key_number!=0);
	}
}

HD7279.c HD7279.h

#include <reg52.h>
#include "HD7279.h"
#include "delay.h"

sbit cs = P1^4;
sbit dat = P1^7;
sbit motor = P1^2;
sbit clk = P1^5;

extern unsigned char undecode[10];
//菜单
unsigned char menu[6][4]={{0xa9,0xf1,0x01,0x00},{0x21,0x2c,0x25,0x01},{0xb8,0x2d,0x25,0x01},//tP- run- Con-
						  {0xf1,0xf5,0x01,0x00},{0xf1,0xa0,0x6d,0x01},{0xb8,0xe5,0x01,0x00}};//PA- PId- CH-
	
void Init_7279(){
	cs=0;
	send_byte(CMD_RESET);
	motor=0;
}
void send_byte( unsigned char out_byte){
	 unsigned char i;
	 cs=0;
	 long_delay();
	 for (i=0;i<8;i++){
		 if (out_byte&0x80){ 
			dat=1;
		 }
		 else{
			dat=0;
		 }
		 clk=1;
		 short_delay();
		 clk=0;
		 short_delay();
		 out_byte=out_byte<<1;
	 }
	 dat=0;
}
unsigned char receive_byte(){
	unsigned char i, in_byte;
	dat=1; 
	long_delay();
	for (i=0;i<8;i++){
		clk=1;
		short_delay();
		in_byte=in_byte*2;
		if (dat){
			in_byte=in_byte|0x01;
		}
		clk=0;
		short_delay();
	}
	dat=0;
	return (in_byte);
}
unsigned char read7279(unsigned char command){
	send_byte(command);
	return(receive_byte());
}
void write7279(unsigned char cmd, unsigned char dat){
	send_byte(cmd);
	send_byte(dat);
}
void displaymenu(unsigned char k){
	unsigned char a;
	for(a=0;a<4;a++){
		write7279(UNDECODE+a+4,menu[k][a]);
	}
	write7279(UNDECODE,undecode[k+1]);
}
void clean0123(){
	unsigned char i;
	for(i=0;i<4;i++){
		//write7279(HIDE,0xi);
		write7279(UNDECODE+i,0x00);
	}
}
//void donothing(){}

#ifndef __HD7279_H__
#define __HD7279_H__

//以下是宏定义
#define CMD_RESET 0xa4
#define CMD_TEST 0xbf
#define DECODE0 0x80
#define DECODE1 0xc8
#define UNDECODE 0x90
#define READ 0x15       //读键盘数据指令
#define RTL_CYCLE 0xa3  //循环左移
#define RTR_CYCLE 0xa2  //循环右移
#define RTL 0xa1        //左移

#define RTR 0xa0        //右移
#define HIDE 0x98       //消隐控制
#define SEGON 0xe0      //段点亮指令
#define SEGOFF 0xc0     //段关闭指令
#define BLINK 0x88      //闪烁控制

//以下是函数声明,可有可无
void Init_7279();
void send_byte(unsigned char);
unsigned char receive_byte();
unsigned char read7279(unsigned char);
void write7279(unsigned char,unsigned char);
void displaymenu(unsigned char);
void clean0123();
//void donothing();

#endif 

delay.c delay.h

#include "delay.h"

void delay(unsigned char time){
	unsigned char i;
	unsigned int j;
	for (i=0;i<time;i++){
		for(j=0;j<0x390;j++);
	}
}
void long_delay(void){
	unsigned char i;
	for (i=0;i<0x41;i++);
}
void short_delay(void){
	unsigned char i;
	for (i=0;i<8;i++);
}
void delay_us(unsigned char us){
	unsigned char i;
	for(i=0;i<us;i++);
}
#ifndef __DELAY_H__
#define __DELAY_H__

void delay(unsigned char);
void long_delay(); 
void short_delay();
void delay_us(unsigned char);

#endif 

temperature.c temperature.h

在读取 DS18B20 时,由于整个系统只有一片芯片,因此可以用 0xcc 指令跳过读 ROM 的阶段,直接读取存储在 RAM 中的 12 温度值,以提高读取速度,读取成功后需要乘 0.0625,由于我们只需要精确到小数点后两位,因此我并没有使用 float 来存储这个温度值,而是将这个 float 类型的温度值乘 100 再加 5 后使用一个 int 类型的变量来存储,这样可以为后续的展示温度和计算 pid 算法的占空比带来方便,更关键的是,使用 int 类型只占用两个字节的空间,而 float 类型占用 4 个字节的空间,这种优化可以节省宝贵的 ROM 和 RAM 空间。

#include <reg52.h>
#include "temperature.h"
#include "HD7279.h"
#include "delay.h"

sbit ds = P1^3;
sbit key = P1^6;

extern unsigned int Tnow;
//数字0到9
unsigned char undecode[10] = {0xfc,0x44,0x79,0x5d,0xc5,0x9d,0xbd,0x54,0xfd,0xdd};//0xfe,0x46,0x7b,0x5f,0xc7,0x9f,0xbf,0x56,0xff,0xdf};

void tp(){
	unsigned char i=0,number;
	while(!i){
		displaytemp(0);
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x38:{
					displaytemp(0);
				}break;
				case 0x39:{
					i=1;
					clean0123();
				}break;
				default:break;
			}
			while(!key&&i==0); 
		}
	}
}
void dsdelay(unsigned int z){
	unsigned int x,y;
	for(x=z;x>0;x--)
		for(y=110;y>0;y--);
}
void Init_ds(){
	unsigned int i;
	ds=0;
	i=103;while(i>0) i--;
	ds=1;
	i=4;while(i>0) i--;
}
void writeds(unsigned char dsdat){
	unsigned int i;
	unsigned char j;
	bit testb;
	for(j=1;j<=8;j++){
		testb=dsdat&0x01;
		dsdat=dsdat>>1;
		if(testb){
			ds=0;
			i++;i++;
			ds=1;
			i=8;while(i>0) i--;
		}
		else{
			ds=0;
			i=8;while(i>0) i--;
			ds=1;
			i++;i++;
		}
	}
}
bit readds_bit(){
	unsigned int i;
	bit dsdat;
	ds=0;
	i++;
	ds=1;
	i++;i++;
	dsdat=ds;
	i=8;while(i>0) i--;
	return dsdat;
}
unsigned char readds_byte(){
	unsigned char i,j,dsdat;
	dsdat=0;
	for(i=1;i<=8;i++){
		j=readds_bit();
		dsdat=(j<<7)|(dsdat>>1);
	}
	return dsdat;
}
unsigned int readds(){
	unsigned int temp;
	unsigned char a,b;
	float f_temp;
	Init_ds();
	dsdelay(1);
	writeds(0xcc);//跳过rom指令
	writeds(0xbe);//从ram中读数据
	a=readds_byte();
	b=readds_byte();
	temp=b;
	temp<<=8;
	temp=temp|a;
	f_temp=temp*0.0625;
	//Tnow=f_temp;//给pid用
	//temp=f_temp*10+0.5;
	temp=f_temp*100+5;//保留小数点后两位
	//f_temp=f_temp+0.05;
	//f_temp=f_temp+0.5;//有用的
	return temp;
}
void tempchange(){
	Init_ds();
	dsdelay(1);
	writeds(0xcc);
	writeds(0x44);
}
void displaytemp(unsigned char up_down_flag){
	unsigned char a,b,c,d;
	unsigned int tp;
	tempchange();
	tp=readds();
	Tnow=tp;
	a=Tnow/1000;
	b=Tnow%1000/100;
	c=Tnow%1000%100/10;
	d=Tnow%1000%100%10;
	write7279(UNDECODE+up_down_flag,undecode[a]);
	write7279(UNDECODE+up_down_flag+1,undecode[b]+2);
	write7279(UNDECODE+up_down_flag+2,undecode[c]);
	write7279(UNDECODE+up_down_flag+3,undecode[d]);
}
#ifndef __TEMPERTURE_H__
#define __TEMPERTURE_H__

void tp();
void dsdelay(unsigned int);
void Init_ds();
void writeds(unsigned char);
bit readds_bit();
unsigned char readds_byte();
unsigned int readds();
void tempchange();
void displaytemp(unsigned char);

#endif 
 

run.c run.h

电机测试功能使用了定时器 0 的中断来产生 PWM 波,定时器 0 每中断 100 次,P1.2引脚有 70 次为高电平,则 PWM 波的占空比为 70%,而定时器 0 灌装的初值为 500,递增计数,每中断一次需要 500 个机器周期,则 PWM 波的频率为 20Hz,周期是 50ms,计算公式自己查书。

#include <reg52.h>
#include "run.h"
#include "HD7279.h"
#include "delay.h"
#include "24C16.h"

sbit key = P1^6;
sbit motor = P1^2;
sbit green = P2^1;

extern unsigned int tmr;
extern unsigned char undecode[10];

unsigned char runk=0;//测试单序号0,1,2...
char pwm_value[11]={0};//10个给run用,1个给control用

void run(){
	unsigned char number,i=0;
	bit sec_flag=0;
	while(!i){
		displayrunmenu();
		if(sec_flag==0){//防止一下子跳到三级菜单
			pwm_value[runk]=(char)readbyte_24c16(runk);
			displayrunpwm_value(pwm_value[runk]);
			sec_flag=1;
			delay(50);
		}
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x38:{
					pwm();
				}break;
				case 0x3a:{
					runk++;
					if(runk==10) runk=0;
					pwm_value[runk]=(char)readbyte_24c16(runk);
					displayrunpwm_value(pwm_value[runk]);
				}break;
				case 0x3b:{
					runk--;
					if(runk==-1) runk=9;
					pwm_value[runk]=(char)readbyte_24c16(runk&0x7f);
					displayrunpwm_value(pwm_value[runk]);
				}break;
				case 0x39:{
					i=1;
					clean0123();
				}break;
			}
			while(!key&&i==0); 
		}
	}
}
void pwm(){
	unsigned char number;
	unsigned char pwint=0;//电机输出pwm波的退出标志
	displayrunmenu();
	TMOD=0x01;
	TH0=(65536-300)/256;//赋初值定时
	TL0=(65536-300)%256;//机器周期为1.08us
	EA=1;//开总中断
	ET0=1;//开定时器0中断
	TR0=1;//启动定时器0 
	while(!pwint){
		if(!key){
			number=read7279(READ);
			if(number==0x39)
				pwint=1;
			while(!key); 
		}
	}
	motor=0;
	green=1;
	EA=0;
	ET0=0;
	TR0=0;
}
void displayrunmenu(){
	unsigned char b,c;
	write7279(UNDECODE+4,0x21);//r
	write7279(UNDECODE+5,0x01);//-
	if(runk+1<10){
		write7279(UNDECODE+6,0x00);
		write7279(UNDECODE+7,undecode[runk+1]);
	}
	else{
		b=(runk+1)/10;
		c=(runk+1)%10;
		write7279(UNDECODE+6,undecode[b]);
		write7279(UNDECODE+7,undecode[c]);
	}
}
void displayrunpwm_value(char pv){
	unsigned char shiwei,gewei;
	if(pv==100){
		write7279(UNDECODE,0x00);
		write7279(UNDECODE+1,undecode[1]);
		write7279(UNDECODE+2,undecode[0]);
		write7279(UNDECODE+3,undecode[0]);
	}
	else if(pv<10){
		write7279(UNDECODE,0x00);
		write7279(UNDECODE+1,0x00);
		write7279(UNDECODE+2,0x00);
		write7279(UNDECODE+3,undecode[pv]);
	}
	else{
		shiwei=pv/10;
		gewei=pv%10;
		write7279(UNDECODE,0x00);
		write7279(UNDECODE+1,0x00);
		write7279(UNDECODE+2,undecode[shiwei]);
		write7279(UNDECODE+3,undecode[gewei]);//改这个undecode
	}
}
void pwmint()interrupt 1{
	TH0=(65536-500)/256;//赋初值定时
	TL0=(65536-500)%256;
	tmr++;
	if(tmr>=100) tmr=0;
	if(tmr<=pwm_value[runk]){
		motor=1;
		green=0;//绿灯亮
	}
	else{
		motor=0;
		green=1;//绿灯不亮
	}
}
#ifndef __RUN_H__
#define __RUN_H__

void displayrunmenu();
void displayrunpwm_value(unsigned char);
void run();
void pwm();
void pwmint();

#endif 

control.c control.h

见pid

#include <reg52.h>
#include "HD7279.h"
#include "control.h"
#include "run.h"
#include "temperature.h"
#include "24C16.h"

sbit key = P1^6;
sbit motor = P1^2;
sbit green =P2^1;

extern unsigned char interrupt3_flag;//决定定时器1中断是执行con算法还是pid算法的运算结果
extern unsigned char undecode[10];
extern char pwm_value[11];
extern unsigned char runk;
extern unsigned char pidint_flag;
extern unsigned int Tnow;

char pacon_t[2]={24,30};//0存储低温,1存储高温
//unsigned int pwmtime=0;//定时器,为18432时定时2秒,

void con(){
	unsigned char number,runk_tmp;
	bit i=0;
	runk_tmp=runk;
	runk=10;
	while(!i){
		if(!key){
			number=read7279(READ);
			if(number==0x38){
				pacon_t[0]=(char)readbyte_24c16(10);
				pacon_t[1]=(char)readbyte_24c16(11);
				interrupt3_flag=1;//执行conshow
				TMOD=0x01;
				TH0=(65536-500)/256;//赋初值定时
				TL0=(65536-500)%256;//机器周期为1.08us
				TH1=0;//赋初值定时
				TL1=1;//机器周期为1.08us
				EA=1;//开总中断
				ET0=1;//开定时器0中断
				TR0=1;//启动定时器0
				ET1=1;//开定时器1中断
				TR1=1;//启动定时器1
				PT1=1;//定时器T1为高优先级
				PT0=0;//定时器T0为高优先级
				while(!i){
					if(!key){
						number=read7279(READ);
						if(number==0x39){
							i=1;
						}
						while(!key); 
					}
				}
				motor=0;
				green=1;
				EA=0;
				ET0=0;
				TR0=0;
				TMOD=0x00;
				ET1=0;
				TR1=0;	
				PT0=0;
				PT1=0;
			}	
			while(!key&&i==0); 
		}
	}
	runk=runk_tmp;
	clean0123();
}
void displayconzkb(){
	unsigned char a,b;
	float zkb;
	if(Tnow>=pacon_t[1]*100){
		pwm_value[10]=100;
	}
	else if(Tnow>=pacon_t[0]*100){
		zkb=50+50*(Tnow-pacon_t[0]*100)/(pacon_t[1]*100-pacon_t[0]*100);
		zkb=zkb*10;
		a=zkb/100;
		zkb=zkb-a*100;
		b=zkb/10;
		pwm_value[10]=(a*10+b);
	}
	else{
		pwm_value[10]=0;
	}
	displayrunpwm_value(pwm_value[10]);
}
#ifndef __CONTROL_H__
#define __CONTROL_H__

void con();
void displayconzkb();
//void conshow();

#endif 

pa.c pa.h

参数设置和密码修改的设计思路在结构较为相似,都是要先调用输入密码函数,输入密码成功后密码函数会根据入参来判断应进入参数设置功能还是修改密码功能。
如果进入参数设置(pa)功能,那么我们可以修改的参数为 pid 设定温度值、run 中各个工艺测试单的 PWM 波占空比以及 con 计算占空比时的上下温度限。
如果进入密码修改(ch)功能,我们可以修改的参数为密码以及 pid 的三个参数 kp、ki、kd。
管理员在修改完参数后,如果按下退出键(左下按键),那么会保存修改值至 24C16 中并退回至上一级菜单,如果不想修改,那么按下进入键(右下按键)就可以一键返回原来值,这时候退出就不会改变原来各个参数的值。

#include <reg52.h>
#include "pa.h"
#include "HD7279.h"
#include "delay.h"
#include "run.h"
#include "24C16.h"

sbit key = P1^6;

extern char pacon_t[2];//温度
extern unsigned char runk;
extern char pwm_value[11];//工艺单
extern unsigned char undecode[10];
extern unsigned char menu[5][4];
extern unsigned int Tset;

char pamenuk=0;//二级菜单指示位
unsigned char pamenu[3][4]={{0x00,0x21,0x2c,0x25},{0x00,0xb8,0x2d,0x25},{0x00,0xf1,0xa0,0x6d}};//参数设置子菜单

//二级菜单,PA功能PA-:Con/run/pid
void pa(){
	unsigned char number,i=0;
	bit sec_flag=0;
	while(!i){
		displaypamenu();
		if(sec_flag==0){//防止一下子跳到三级菜单
			sec_flag=1;
			delay(50);
		}
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x38:{	
					switch(pamenuk){
						case 0:parun();break;
						case 1:pacon();break;
						case 2:papid();break;
					}
				}break;
				case 0x3a:{
					pamenuk++;
					if(pamenuk==3) pamenuk=0;
					//displaypamenu();
				}break;
				case 0x3b:{
					pamenuk--;
					if(pamenuk==-1) pamenuk=2;
					//displaypamenu();
				}break;
				case 0x39:{
					i=1;
					clean0123();
				}break;
			}
			while(!key&&i==0); 
		}
	}
}
//三级菜单,改变工艺单的序号功能:P- [序号]
void parun(){
	unsigned char number,i=0;
	bit thired_flag=0;
	while(!i){
		display_pa_run(0);
		if(thired_flag==0){//防止一下子跳到四级菜单
			thired_flag=1;
			delay(50);
			pwm_value[runk]=(char)readbyte_24c16(runk);
		}	
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x3a:{
					runk++;
					if(runk==10) runk=0;
					pwm_value[runk]=(char)readbyte_24c16(runk);
				}break;
				case 0x3b:{
					runk--;
					if(runk==-1) runk=9;
					pwm_value[runk]=(char)readbyte_24c16(runk);
				}break;
				case 0x38:{
					parun_changevalue();
				}break;
				case 0x39:{
					i=1;
					clean0123();
				}break;
			}
			while(!key&&i==0); 
		}
	}
}
//四级菜单,改变不同工艺测试单的值:A- [序号]
void parun_changevalue(){
	unsigned char number,i=0;
	unsigned char count=0,long_press_flag=0;
	char pwm_value_temp=pwm_value[runk];
	while(!i){
		display_pa_run(1);
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x38:{//确认键,不变
					pwm_value[runk]=pwm_value_temp;
					display_pa_run(1);
				}break;
				case 0x3a:{
					while(!key){//长按按键程序
						delay(40);
						pwm_value[runk]++;
						long_press_flag++;
						if(long_press_flag>=2){
							pwm_value[runk]=pwm_value[runk]+count;
							if(pwm_value[runk]>100)pwm_value[runk]=100;
							count++;
						}
						displayrunpwm_value(pwm_value[runk]);
					}
					long_press_flag=0;
					count=0;		
				}break;
				case 0x3b:{
					while(!key){
						delay(40);
						pwm_value[runk]--;
						long_press_flag++;
						if(long_press_flag>=2){
							pwm_value[runk]=pwm_value[runk]-count;
							if(pwm_value[runk]<0)pwm_value[runk]=0;
							count++;
						}
						displayrunpwm_value(pwm_value[runk]);
					}
					long_press_flag=0;
					count=0;
				}break;
				case 0x39:{//退出键,同时覆盖
					i=1;
					clean0123();
					writebyte_24c16((unsigned char)pwm_value[runk],runk);
				}break;
			}
			while(!key&&i==0); 
		}
	}
}
//三级菜单,电机调速功能Con
void pacon(){
	unsigned char number,i=0,conk=0;
	bit thired_flag=0;
	while(!i){
		display_pa_con(conk);
		if(thired_flag==0){//防止一下子跳到四级菜单
			pacon_t[0]=readbyte_24c16(10);
			pacon_t[1]=readbyte_24c16(11);
			thired_flag=1;
			delay(50);
		}	
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x3a:{
					conk++;
					if(conk==2) conk=0;
				}break;
				case 0x3b:{
					conk--;
					if(conk==-1) conk=1;
				}break;
				case 0x38:{
					pacon_changevalue(conk);
				}break;
				case 0x39:{
					i=1;
					clean0123();
				}break;
			}
			while(!key&&i==0); 
		}
	}
}
//四级菜单,改变不同电机调速的值:PA-b,PA-F
void pacon_changevalue(unsigned char conk){
	unsigned char number,i=0;
	unsigned char count=0,long_press_flag=0;
	unsigned char t_value_temp=pacon_t[conk];
	while(!i){
		display_pa_con_cv(conk);
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x38:{//确认键,不改变值
					pacon_t[conk]=t_value_temp;
					display_pa_con_cv(conk);
				}break;
				case 0x3a:{
					while(!key){//长按按键程序
						delay(40);
						pacon_t[conk]++;
						long_press_flag++;
						if(long_press_flag>=2){
							pacon_t[conk]=pacon_t[conk]+count;
							if(pacon_t[conk]>100)pacon_t[conk]=100;
							count++;
						}
						displayrunpwm_value(pacon_t[conk]);
					}
					long_press_flag=0;
					count=0;		
				}break;
				case 0x3b:{
					while(!key){
						delay(40);
						pacon_t[conk]--;
						long_press_flag++;
						if(long_press_flag>=2){
							pacon_t[conk]=pacon_t[conk]-count;
							if(pacon_t[conk]<0)pacon_t[conk]=0;
							count++;
						}
						displayrunpwm_value(pacon_t[conk]);
					}
					long_press_flag=0;
					count=0;
				}break;
				case 0x39:{//退出键,同时覆盖
					i=1;
					clean0123();
					writebyte_24c16((unsigned char)pacon_t[conk],conk+10);
				}break;
			}
			while(!key&&i==0); 
		}
	}
}
//三级菜单,电机调速功能pid
void papid(){
	unsigned char number,i=0;
	unsigned char count=1,long_press_flag=0;
	unsigned int Tset_temp;
	Tset=readint_24c16(16);
	Tset_temp=Tset;
	display_pa_pid();
	while(!i){
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x38:{//确认键,不改变值
					Tset=Tset_temp;
					display_pa_pid();
				}break;
				case 0x3a:{
					while(!key){//长按按键程序
						delay(40);
						Tset=Tset+10;
						long_press_flag++;
						if(long_press_flag>=2){
							Tset=Tset+count*10;
							if(Tset>4000)Tset=4000;//不能大于40度
							count++;
						}
						display_pa_pid();
					}
					long_press_flag=0;
					count=1;		
				}break;
				case 0x3b:{
					while(!key){
						delay(40);
						Tset=Tset-10;
						long_press_flag++;
						if(long_press_flag>=2){
							Tset=Tset-count*10;
							if(Tset<500)Tset=500;
							count++;
						}
						display_pa_pid();
					}
					long_press_flag=0;
					count=1;
				}break;
				case 0x39:{//退出键,同时覆盖
					i=1;
					clean0123();
					writeint_24c16(Tset,16);
				}break;
			}
			while(!key&&i==0); 
		}
	}
}
//显示PA的二级菜单
void displaypamenu(){
	unsigned char a;
	for(a=0;a<4;a++){
		write7279(UNDECODE+a,pamenu[pamenuk][a]);
	}
	for(a=0;a<4;a++){
		write7279(UNDECODE+4+a,menu[3][a]);
	}
}
//显示PA_run工艺单测试三级菜单和四级菜单
void display_pa_run(unsigned char PA){
	unsigned char a,b,c;
	unsigned char parunmenu[3]={0xf1,0x01};//PA=0时前三位:P-
	if(PA==1){//此时在修改某工艺单的值
		parunmenu[0]=0xf5;//显示的是A-
	}
	for(a=0;a<2;a++){
		write7279(UNDECODE+a+4,parunmenu[a]);
	}
	if(runk+1<10){
		write7279(UNDECODE+6,0x00);
		write7279(UNDECODE+7,undecode[runk+1]);
	}
	else{
		b=(runk+1)/10;
		c=(runk+1)%10;
		write7279(UNDECODE+6,undecode[b]);
		write7279(UNDECODE+7,undecode[c]);
	}
	displayrunpwm_value(pwm_value[runk]);//显示现在某个工艺单的值
}
//显示PA_con电机调速三级菜单和四级菜单
void display_pa_con(unsigned char bF){
	unsigned char a;
	unsigned char paconmenu[4]={0xf1,0xf5,0x01,0xad};//bF=0时:PA-b,低温
	if(bF==1){//bF=1时:PA-F,高温
		paconmenu[3]=0xb1;
	}
	for(a=0;a<4;a++){
		write7279(UNDECODE+a+4,paconmenu[a]);
	}
	displayrunpwm_value(pacon_t[bF]);
}
//显示改变电机温度的四级菜单:A- F,A- b
void display_pa_con_cv(unsigned char bF){
	unsigned char a;
	unsigned char paconmenu[4]={0xf5,0x01,0x00,0xad};//bF=0时:A- b,低温
	if(bF==1){//bF=1时:A- F,高温
		paconmenu[3]=0xb1;
	}
	for(a=0;a<4;a++){
		write7279(UNDECODE+a+4,paconmenu[a]);
	}
	displayrunpwm_value(pacon_t[bF]);
}
//显示改变pid设定温度的程序A- P
void display_pa_pid(){
	unsigned char a,b,c;
	a=Tset/1000;
	b=Tset%1000/100;
	c=Tset%1000%100/10;
	write7279(UNDECODE+0,0x00);
	write7279(UNDECODE+1,undecode[a]);
	write7279(UNDECODE+2,undecode[b]+2);
	write7279(UNDECODE+3,undecode[c]);
	write7279(UNDECODE+4,0xf5);
	write7279(UNDECODE+5,0x01);
	write7279(UNDECODE+6,0x00);
	write7279(UNDECODE+7,0xf1);
}
#ifndef __PA_H__
#define __PA_H__

void pa();
void parun();
void pacon();
void papid();
void displaypamenu();
void display_pa_run(unsigned char);
void display_pa_con(unsigned char);
void display_pa_con_cv(unsigned char);
void pacon_changevalue(unsigned char);
void parun_changevalue();
void display_pa_pid();

#endif 

password.c password.h

在本系统中,输入密码的方式采用的是按位输入,共四位,密码存储在 24C16 中,占
用四个字节,选择输入第 x(0<x<5)位,则第 x 位闪烁就闪烁,右上按键进行位的循环选择,然后使用左上按键进行密码的循环加,从而实现四位密码的输入,按下确认按键(右下按键)即进行密码判决,若输入正确,则进入修改参数界面,若输入错误,则需重新输入。

#include <reg52.h>
#include "pa.h"
#include "HD7279.h"
#include "24C16.h"
#include "password.h"
#include "delay.h"
#include "change.h"

sbit key = P1^6;

extern unsigned char undecode[10];

void password(unsigned char pc){
	unsigned char number,i=0,a=0;
	unsigned char pass_wei=0;
	unsigned char pass_ture[4]={0},pass_my[4]={0};
	bit sec_flag=0;
	while(!i){
		if(sec_flag==0){//防止一下子跳到三级菜单
			displaypass_input(pass_wei,pass_my);
			sec_flag=1;
			delay(50);
		}
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x38:{	
					for(a=0;a<4;a++){
						pass_ture[a]=readbyte_24c16(a+12);
						if(pass_my[a]!=pass_ture[a]){
							display_err();
							sec_flag=0;
							break;
						}
					}
					if(a==4){
						display_pass();		
						if(pc==0)pa();
						else if(pc==1){
							clean0123();
							change();
						}
						i=1;
						clean0123();
					}
				}break;
				case 0x3a:{
					pass_wei++;
					if(pass_wei==4) pass_wei=0;
					displaypass_input(pass_wei,pass_my);
					//displaypamenu();
				}break;
				case 0x3b:{
					pass_my[pass_wei]++;
					if(pass_my[pass_wei]==10) pass_my[pass_wei]=0;
					displaypass_input(pass_wei,pass_my);
					//displaypamenu();
				}break;
				case 0x39:{
					i=1;
					clean0123();
				}
			}
			while(!key&&i==0); 
		}
	}
}
void displaypass_input(unsigned char pass_wei,unsigned char pass_my[]){
	unsigned char a=0;
	for(a=0;a<4;a++){
		write7279(UNDECODE+a,undecode[pass_my[a]]);
		write7279(BLINK,0xff);
	}
	write7279(BLINK,0xff-(0x01<<pass_wei));
}
void display_err(){
	write7279(BLINK,0xff);//全部不闪烁
	write7279(0x90,0xb9);//Err
	write7279(0x91,0x21);
	write7279(0x92,0x21);
	write7279(0x93,0x00);
	delay(400);
}
void display_pass(){
	write7279(BLINK,0xff);//全部不闪烁
	write7279(0x90,0xf1);//PASS
	write7279(0x91,0xf5);
	write7279(0x92,0x9d);
	write7279(0x93,0x9d);
	delay(400);
}
#ifndef __PASSWORD_H__
#define __PASSWORD_H__

void password(unsigned char);
void displaypass_input(unsigned char ,unsigned char *);
void display_err();
void display_pass();

#endif 

change.c change.h

#include <reg52.h>
#include "24C16.h"
#include "HD7279.h"
#include "password.h"
#include "change.h"
#include "delay.h"

sbit key = P1^6;
sbit green = P2^1;

extern unsigned char undecode[10];

extern int kpid[3];

void change(){
	unsigned char number,i=0,chk=0;
	bit sec_flag=0;
	kpid[0]=(int)readint_24c16(18);
	kpid[1]=(int)readint_24c16(20);
	kpid[2]=(int)readint_24c16(22);
	displaychmenu(chk);
	while(!i){
		if(sec_flag==0){//防止一下子跳到三级菜单
			sec_flag=1;
			delay(50);
		}
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x38:{	
					if(chk==3)ch_pwd();
					else ch_pid(chk);//ch为0是p,ch为1是i,ch为2是d
					displaychmenu(chk);
				}break;
				case 0x3a:{
					chk++;
					if(chk==4) chk=0;
					displaychmenu(chk);
				}break;
				case 0x3b:{
					chk--;
					if(chk==-1) chk=3;
					displaychmenu(chk);
				}break;
				case 0x39:{
					i=1;
					clean0123();
				}break;
			}
			while(!key&&i==0); 
		}
	}
}
//改变24C16中的pid的值
void ch_pid(unsigned char chk){
	unsigned char number,i=0;
	unsigned char count=1,long_press_flag=0;
	int kpid_temp;
	bit thired_flag=0;
	green = 0;
	kpid_temp=kpid[chk];
	display_ch_pid(kpid[chk]);
	while(!i){
		if(thired_flag==0){//防止一下子跳到四级菜单
			thired_flag=1;
			delay(50);
		}	
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x38:{//确认键,不改变值
					kpid[chk]=kpid_temp;
					display_ch_pid(kpid[chk]);
				}break;
				case 0x3a:{
					while(!key){//长按按键程序
						delay(40);
						kpid[chk]=kpid[chk]+1;
						long_press_flag++;
						if(long_press_flag>=2){
							kpid[chk]=kpid[chk]+count;
							count++;
						}
						if(kpid[chk]>400)kpid[chk]=400;//kp,ki,kd不能大于400
						display_ch_pid(kpid[chk]);
					}
					long_press_flag=0;
					count=2;		
				}break;
				case 0x3b:{
					while(!key){
						delay(40);
						kpid[chk]=kpid[chk]-1;
						long_press_flag++;
						if(long_press_flag>=2){
							kpid[chk]=kpid[chk]-count;
							count++;
						}
						if(kpid[chk]<0)kpid[chk]=0;
						display_ch_pid(kpid[chk]);
					}
					long_press_flag=0;
					count=2;
				}break;
				case 0x39:{//退出键,同时覆盖
					i=1;
					clean0123();
					writeint_24c16((unsigned int)kpid[chk],18+chk*2);
					green = 1;
				}break;
			}
			while(!key&&i==0); 
		}
	}
}
//改变24C16中的密码的值
void ch_pwd(){
	unsigned char number,i=0,a=0;
	unsigned char pass_wei=0;
	unsigned char pass_new[4]={0};
	pass_new[0]=readbyte_24c16(12);//pass[0]的存储位置,详见24C16.c的第151行
	pass_new[1]=readbyte_24c16(13);
	pass_new[2]=readbyte_24c16(14);
	pass_new[3]=readbyte_24c16(15);
	displaypass_input(pass_wei,pass_new);
	while(!i){	
		if(!key){
			number=read7279(READ);
			switch(number){
				case 0x38:{	
					pass_new[0]=readbyte_24c16(12);//pass[0]的存储位置,详见24C16.c的第151行
					pass_new[1]=readbyte_24c16(13);
					pass_new[2]=readbyte_24c16(14);
					pass_new[3]=readbyte_24c16(15);
					displaypass_input(pass_wei,pass_new);
				}break;
				case 0x3a:{
					pass_wei++;
					if(pass_wei==4) pass_wei=0;
					displaypass_input(pass_wei,pass_new);
				}break;
				case 0x3b:{
					pass_new[pass_wei]++;
					if(pass_new[pass_wei]==10) pass_new[pass_wei]=0;
					displaypass_input(pass_wei,pass_new);
				}break;
				case 0x39:{
					delay_us(150);
					writebyte_24c16(pass_new[0],12);//退出就覆写
					delay_us(150);
					writebyte_24c16(pass_new[1],13);
					delay_us(150);
					writebyte_24c16(pass_new[2],14);
					delay_us(150);
					writebyte_24c16(pass_new[3],15);
					write7279(BLINK,0xff);
					i=1;
					clean0123();
				}
			}
			while(!key&&i==0); 
		}
	}
}
//二级菜单,C- 
void displaychmenu(unsigned char chk){
	write7279(UNDECODE+4,0xb8);
	write7279(UNDECODE+5,0x01);
	if(chk==0){//C- P
		write7279(UNDECODE+6,0x00);
		write7279(UNDECODE+7,0xf1);	
	}
	if(chk==1){//C- I
		write7279(UNDECODE+6,0x00);
		write7279(UNDECODE+7,0xa0);
	}
	if(chk==2){//C- d
		write7279(UNDECODE+6,0x00);
		write7279(UNDECODE+7,0x6d);
	}
	if(chk==3){//C-PA
		write7279(UNDECODE+6,0xf1);
		write7279(UNDECODE+7,0xf5);
		clean0123();
	}
	if(chk<3){
		display_ch_pid(kpid[chk]);
	}
}
void display_ch_pid(int kpid){
	unsigned char baiwei,shiwei,gewei;
	if(kpid>=100){
		baiwei=kpid/100;
		shiwei=kpid%100/10;
		gewei=kpid%100%10;
		write7279(UNDECODE,0x00);
		write7279(UNDECODE+1,undecode[baiwei]);
		write7279(UNDECODE+2,undecode[shiwei]);
		write7279(UNDECODE+3,undecode[gewei]);
	}
	else if(kpid<10){
		write7279(UNDECODE,0x00);
		write7279(UNDECODE+1,0x00);
		write7279(UNDECODE+2,0x00);
		write7279(UNDECODE+3,undecode[kpid]);
	}
	else{
		shiwei=kpid/10;
		gewei=kpid%10;
		write7279(UNDECODE,0x00);
		write7279(UNDECODE+1,0x00);
		write7279(UNDECODE+2,undecode[shiwei]);
		write7279(UNDECODE+3,undecode[gewei]);//改这个undecode
	}
}
#ifndef __CHANGE_H__
#define __CHANGE_H__

void change();
void ch_pid(unsigned char);
void ch_pwd();
void displaychmenu(unsigned char);
void display_ch_pid(int);

#endif

24C16.c 24C16.h

#include <REG52.H>
#include "delay.h"
#include "24C16.h"

void Init_24c16(){
	sda=1;
	scl=1;
}
void I2C_start(){
	sda=1;
	delay_us(5);
	scl=1;
	delay_us(5);
	sda=0;
	delay_us(5);
}

void I2C_stop(){
	sda=0;
	delay_us(5);
	scl=1;
	delay_us(5);
	sda=1;
	delay_us(5);
}

void I2C_ack(){
	unsigned char i;
	scl=1;
	delay_us(5);
	while((sda==1)&&(i<200))i++;
	scl=0;
	delay_us(5);
}

void I2C_noack(){
	sda = 1;
	delay_us(5);
	scl = 1;
	delay_us(5);
	scl = 0;
	delay_us(5);
}
void writebyte_I2C(unsigned char input){
	unsigned char i;
	scl = 0;
	for(i=0;i<8;i++){
		if(input&0x80)sda = 1;
		else sda = 0;
		input = input<<1;
		delay_us(5);
		scl = 1;
		delay_us(5);
		scl = 0;
		delay_us(5);
	}
	sda = 1;
	delay_us(5);
}

unsigned char readbyte_I2C(){
	unsigned char i,rbyte;
	scl = 0;
	delay_us(5);
	sda = 1;
	delay_us(5);
	for(i=0;i<8;i++){
		scl = 1;
		delay_us(5);
		rbyte = rbyte<<1;
		if(sda)rbyte++;
		scl = 0;
		delay_us(5);
	}
	return rbyte;
}

void writebyte_24c16(unsigned char dat,unsigned char addr){
	Init_24c16();
	I2C_start();
	writebyte_I2C(WriteDeviceAddress);
	I2C_ack();
	writebyte_I2C(addr);
	I2C_ack();
	writebyte_I2C(dat);
	I2C_ack();
	I2C_stop();
}

unsigned char readbyte_24c16(unsigned char addr){
	unsigned char output;
	Init_24c16();
	I2C_start();
	writebyte_I2C(WriteDeviceAddress);
	I2C_ack();
	writebyte_I2C(addr);
	I2C_ack();
	I2C_start();
	writebyte_I2C(ReadDeviceAddress);
	I2C_ack();
	output = readbyte_I2C();
	I2C_noack();
	I2C_stop();
	return output;
}
void writeint_24c16(unsigned int dat,unsigned char start_addr){
	unsigned char output_h,output_l;
	output_h=dat>>8;
	output_l=dat;
	delay_us(150);
	writebyte_24c16(output_h,start_addr);//先写高字节再写低字节
	delay_us(150);
	writebyte_24c16(output_l,start_addr+1);
}
//读int,占用两个字节
unsigned int readint_24c16(unsigned char start_addr){
	unsigned char output_h,output_l;
	unsigned int output=0;
	output_h=readbyte_24c16(start_addr);//先读高字节再读低字节
	output_l=readbyte_24c16(start_addr+1);
	output=output_h<<8;
	output=output|output_l;
	return output;
}
//在你的人生中只调用一次即可,故打上注释了,开始时调用一次,初始化一下就行可以打上注释了
/*void save_all_data(){
	delay_us(150);
	writebyte_24c16(10,0);
	delay_us(150);
	writebyte_24c16(20,1);
	delay_us(150);
	writebyte_24c16(30,2);
	delay_us(150);
	writebyte_24c16(40,3);
	delay_us(150);
	writebyte_24c16(50,4);
	delay_us(150);
	writebyte_24c16(60,5);
	delay_us(150);
	writebyte_24c16(70,6);
	delay_us(150);
	writebyte_24c16(80,7);
	delay_us(150);
	writebyte_24c16(90,8);
	delay_us(150);
	writebyte_24c16(100,9);
	delay_us(150);
	writebyte_24c16(24,10);//低温
	delay_us(150);
	writebyte_24c16(32,11);//高温
	delay_us(150);
	writebyte_24c16(0,12);//pass[0]
	delay_us(150);
	writebyte_24c16(1,13);//pass[1]
	delay_us(150);
	writebyte_24c16(2,14);//pass[2]
	delay_us(150);
	writebyte_24c16(3,15);//pass[3]
	delay_us(150);
	writeint_24c16(2680,16);//temperset
	delay_us(150);
	writeint_24c16(110,18);//kp
	delay_us(150);
	writeint_24c16(15,20);//ki
	delay_us(150);
	writeint_24c16(15,22);//kd
}*/
#ifndef __24C16_H__
#define __24C16_H__

sbit scl = P1^1;
sbit sda = P1^0;

#define WriteDeviceAddress 0xa0
#define ReadDeviceAddress 0xa1

unsigned char readbyte_24c16(unsigned char);
void writebyte_24c16(unsigned char,unsigned char);
unsigned char readbyte_I2C();
void writebyte_I2C(unsigned char);
void I2C_noack();
void I2C_ack();
void I2C_stop();
void I2C_start();
void Init_24c16();
//void save_all_data();
unsigned int readint_24c16(unsigned char);
void writeint_24c16(unsigned int,unsigned char);

#endif 

pid.c pid.h

其中恒温控制和转速计算的设计思路在结构上是相同的,只是计算占空比的算法不同,要实现的目的不同。
我使用了两个定时器:定时器 0 和定时器 1,定时器 0 用来控制 P1.2 引脚生成占空比在实时改变的 PWM 波,功能和 run 功能中的一样,定时器 1 中断时会读取一下 DS18B20 的温度,并根据 con 或 pid 算法来进行占空比计算,达到所需功能。定时器 1 的优先级比定时器 0 的优先级高,这样可以保证在采样温度和计算占空比时不会被生成 PWM 的中断打断,定时器 1 的中断周期比定时器 0长,这样能实现定时器读取温度和计算占空比时对电机的转速产生较小的影响。
定时器 1 的中断周期为 262ms,也就是 262ms 采一次温度值并根据 PID算法或 con 算法计算占空比,定时器 0 的中断周期与 run 中的中断周期保持一致,都是0.5ms。
使用的 pid 算法为增量式 pid,计算方式可以用函数迭代的方式实现。
kp,ki,kd 调节时应讲究技巧,先将 ki,kd 设置为 0,调 kp,kp 可以让控制对象快速到达目标值附近,但是由于误差越来越小,只有 kp 的作用会造成静差的存在,这时候应调节 ki,ki 会对累计的误差量进行放大,从而消除静差,但是又会带来震荡,这时候就应加上 kd 的作用,减小震荡。在调试中,ki 太高和太低都不合理,太低则收敛时间过长,太高则在收敛值附近会存在微小震荡,只有找到最佳值才可以让温度快速收敛且产生较小的震荡。

#include <reg52.h>
#include "temperature.h"
#include "pid.h"
#include "HD7279.h"
#include "control.h"
#include "run.h"
#include "temperature.h"
#include "24C16.h"

sbit key = P1^6;
sbit motor = P1^2;
sbit green =P2^1; 

extern unsigned char undecode[10];
extern char pwm_value[11];//要用到pwm_value[10]这个空间
extern unsigned char runk;

unsigned char interrupt3_flag=0;//决定定时器1中断是执行con算法还是pid算法的运算结果
int kpid[3]={110,15,15};//140 20 20找个自动化苦力给你调
unsigned int Tset=2680,Tnow=0;//存储设定温度和现在的温度
int e_before[3]={50,60,80};//pid算法用的,存储温度误差值
unsigned char pidint_flag=0;//存储刷新一次数码管所需定时器1中断的次数

void pid(){
	unsigned char number,runk_tmp;
	bit i=0;
	runk_tmp=runk;
	runk=10;
	Tset=readint_24c16(16);
	kpid[0]=readint_24c16(18);
	kpid[1]=readint_24c16(20);
	kpid[2]=readint_24c16(22);
	while(!i){
		if(!key){
			number=read7279(READ);
			if(number==0x38){
				interrupt3_flag=0;
				TMOD=0x11;
				TH0=(65536-500)/256;//赋初值定时
				TL0=(65536-500)%256;//机器周期为1.08us
				TH1=0;//赋初值定时
				TL1=1;//机器周期为1.08us
				EA=1;//开总中断
				ET0=1;//开定时器0中断
				TR0=1;//启动定时器0
				ET1=1;//开定时器1中断
				TR1=1;//启动定时器1
				PT1=1;//定时器T1为高优先级
				PT0=0;//定时器T0为高优先级
				while(!i){
					if(!key){
						number=read7279(READ);
						if(number==0x39){
							i=1;
						}
						while(!key); 
					}
				}
				motor=0;
				green=1;
				EA=0;
				ET0=0;
				TR0=0;
				ET1=0;
				TR1=0;	
				PT0=0;
				PT1=0;
			}	
			while(!key&&i==0); 
		}
	}
	runk=runk_tmp;//把runk再变回去,咱只是借用了pwm_value[10]这个空间,别打扰人家run功能
	clean0123();//不能写在i=1后面,不服劲你就试试,看看会出现什么问题
}
//核心pid算法
char pid_getpwm(char ui,int Tnow,int Tset,int kp,int ki,int kd){
	int u_delta;
	e_before[2] = e_before[1];
	e_before[1] = e_before[0];
	e_before[0] = Tnow-Tset;
	u_delta = kp*(e_before[0]-e_before[1])+ki*e_before[0]+kd*(e_before[0]-2*e_before[1]+e_before[2]);//增量式pid
	ui = ui+(char)(u_delta/100);
	if(ui>100) ui=100;//占空比不能大于100
	else if(ui<0) ui=0;//占空比不能小于0
	if(e_before[0]>=20)ui=100;//wtf!这简直就是天才
	return ui;
}
//定时器1中断,执行con算法或pid算法
void con_pid_show() interrupt 3{
	TH1=0x00;//赋初值定时
	TL1=0x01;
	pidint_flag++;
	if(pidint_flag==2){
		displaytemp(4);//在数码管上面展现温度
		switch(interrupt3_flag){
			//执行pid算法得出的占空比
			case 0:{
				pwm_value[10]=pid_getpwm(pwm_value[10],Tnow,Tset,kpid[0],kpid[1],kpid[2]);
				displayrunpwm_value(pwm_value[10]);
			}break;
			//执行con算法得出的占空比
			case 1:{
				displayconzkb();
			}break;
		}
		pidint_flag=0;	
	}
}
#ifndef __PID_H__
#define __PID_H__

void pid();
char pid_getpwm(char,int,int,int,int,int);

#endif

要考研的你就最好别花大量的时间搞这个了,除非你有新活

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值