蓝桥杯第十三届单片机国赛程序详解

写在前面:做完总体来说感觉一年比一年难了(估计是被骂的),虽然十三届用的底层少,但是做起来困难重重。

最难的难点在于定时器安排问题。15F2K60S2系列单片机只有三个定时器,本届题目考到了频率测量、超声波、PWM输出,再加上刷新,每一个都需要一个定时器,比较简单的做法之一是直接用单片机外设PCA来检测超声波,节省定时器资源。(b站上有个up主是切换定时器0计数/定时模式来实现的,这样也可以,但是个人感觉比较麻烦,也容易出现问题)

对于NE555频率测量,要求是P3^4进行脉冲输入,正好是T0输出,所以最好是用定时器0,当然也可以使用其它定时器,这样就需要使用sbit函数定义P3^4引脚,相对比较麻烦。

定时器0:频率计数;定时器1:函数刷新;定时器2:PWM输出。

1、首先是NE555频率测量:

其中TMOD|=0x04为切换定时器为计数模式。

2、其次是PCA实现超声波测量:

(别忘了TX、RX引脚定义)

 

 最后得到的值+3(实际测量在+2.5左右),原因是因为硬件或者程序延时时间等问题有小误差,很多博主和教程里这个地方都有加3,可以自己拿尺子测量一下距离看看是不是这样。

 3、PWM输出

首先看题目:

 首先找到这个电机驱动引脚连接的位置:(不是去找J3哦)

 可以发现它和我们常用的继电器、蜂鸣器在一个地方,这样我们就可以像控制它俩一样控制电机。

输出频率为1khz,那么我们输出信号周期就为1000微秒,分成5份,每份就是200微秒,就可以设置200微秒定时器中断。

 这里每份是200微秒,那么之前我们设置的超声波程序整个过程是24微秒*8=192微秒<200微秒,这样才不会因为关闭中断时间过长造成PWM输出异常。

4、PCF8591

这里我设置采集时关闭中断,完成后才打开,以免影响iic时序。如果不关闭的话,实际采集值会来回跳变(大家可以试一试是不是这样),有的教程和博主教学里面没关,可能是我其它程序设置问题。

注意:设置的外设等刷新时间,应该是满足条件的最大值,以免多次采集造成不良影响。

 自从第十届开始,采取机器阅卷后,就对采集时间等等性能有姚桥,但本届题目并没有和省赛一样设置外设时间刷新要求,原因可能是超声波、频率等等容易冲突的地方太多了,就放宽了条件,所以我们在设置刷新时间,就适当的加长一点。

 

完整题目:

 

 

 

 

 

 

完整程序: 

MAIN.c

#include <Main.h>
#include <IIC.h>
#include "sonic.h"


void Task_Clock(void)
{
	Set_HC573(4,LED);
	Set_HC573(5,Actuator_Bit);
	if(LED_flag==1){if(++LED_tt == 100)
	{ 
		LED_tt = 0; 
		if(LED_Run==1){LED_ON(State3);LED_Run=0;}
		else if(LED_Run==0){LED_OFF(State3);LED_Run=1;}}
	}
	if(++SEG_tt == 50)	{ SEG_tt = 0; SEG_Ref = 1; }
	if(++KEY_tt == 10) 	{ KEY_tt = 0;  KeyScan(); }
	if(++AD_tt ==  300) 		{ AD_tt = 0;  	AD_Ref = 1;	}
	if(++LCM_tt == 500)		{LCM_tt = 0; 	LCM_Ref = 1;	}
	if(++Freq_tt == 1000)	{Freq_tt = 0; Freq_Ref = 1; }
}

void main(void)
{
	ALL_Init();
	
	if(AT24C02_read(0X64)!=0x80)
	{
		AT24C02_write(0x64,0x80);Delay_MS(5);
		AT24C02_write(0x00,0);Delay_MS(5);
	}
	else {Cishu = AT24C02_read(0X00); Delay_MS(5);}
	LED_ON(1);
	AD_read(0X43);	
	AD_Value = AD_read(0X43);	
	AD_Value = AD_Value * 1.96;
	
	Timer0Init();
	Timer1Init();
	Timer2Init();IE2 |= 0X04;
	EA = 1; ET1 = 1; 

	
	while(1)
	{		
		if(SEG_Ref == 1)
		{
			SEG_Ref = 0;
			SEG_Refresh();
		}
		if(KEY_Flag == 1)
		{
			KEY_Flag = 0;
			Task_Key();
		}
		
		if(AD_Ref == 1)
		{
			AD_Ref = 0;
			AD_Value = AD_read(0X43);		//滑动变阻器0X03,光敏电阻0X01
			AD_Value = AD_Value * 1.96;	
			Shidu=0.2*AD_Value;
			if(Shidu<=Shidu_para){AD_write(51);LED_OFF(5);}
			else if(Shidu>=80){AD_write(255);LED_ON(5);}
			else {AD_write((Shidu-80)*4*51/(80-Shidu_para)+5*51);LED_ON(5);}
		}
		
		if(LCM_Ref == 1)
		{
			LCM_Ref = 0;
			LCM=Sonic_Measure()+3;
			if((LCM>LCM_para*10)&&(LCM_last<=LCM_para*10))
			{
				Actuator_Bit|=0x10;Cishu=Cishu+1;AT24C02_write(0x00,Cishu);LED_ON(6);
			}
			else if((LCM>LCM_para*10)&&(LCM_last>LCM_para*10))
			{
				LED_ON(6);
			}
			else if(LCM<=LCM_para*10){Actuator_Bit&=~0x10;LED_OFF(6);}
			LCM_last=LCM;
		}
		
		
		if(Freq_Ref == 1)
		{
			Freq_Ref = 0 ;
			Freq_Callback_1s();
			if(Freq>Freq_para*100)
			{
				LED_ON(4);
				PWM_Deuty_count=4;	
			}
			else 
			{
				LED_OFF(4);
				PWM_Deuty_count=1;
			}
		}
	}
}


void Task_Key(void)
{
	if(KEY_Value==4)
	{
		KEY_Value=0;
		if(SEG_Show==1){SEG_Show=2;LED_OFF(1);LED_ON(2);}
		else if(SEG_Show==2){SEG_Show=3;LED_OFF(2);LED_ON(3);}
		else if(SEG_Show==3){SEG_Show=4;LED_OFF(3);State3=1;LED_flag=1;}
		else if(SEG_Show==4){SEG_Show=1;LED_flag=0;LED_OFF(State3);LED_ON(1);}
	}
	
	if(KEY_Value==5)
	{
		KEY_Value=0;
		if(SEG_Show==4)
		{
			if(State3==1){State3=2;LED_OFF(1);}
			else if(State3==2){State3=3;LED_OFF(2);}
			else if(State3==3){State3=1;LED_OFF(3);}
		}
	}
	if(KEY_Value==6){
		KEY_Value=0;
		if(SEG_Show==4)
		{
			if(State3==1)
			{
				Freq_para=Freq_para+5;
				if(Freq_para>120)Freq_para=10;
			}
			else if(State3==2)
			{
				Shidu_para=Shidu_para+10;
				if(Shidu_para>60)Shidu_para=10;
			}
			else if(State3==3)
			{
				LCM_para=LCM_para+1;
				if(LCM_para>12)LCM_para=1;
			}
		}
		if(SEG_Show==3)
		{
			if(State2==1)State2=2;
			else State2=1;
		}
	}
	if((KEY_Value==7)&&(KEY_Press_TIME<1000))
	{
		KEY_Value=0;
		if(SEG_Show==4)
		{
			if(State3==1)
			{
				Freq_para=Freq_para-5;
				if(Freq_para<10)Freq_para=120;
			}
			else if(State3==2)
			{
				Shidu_para=Shidu_para-10;
				if(Shidu_para<10)Shidu_para=60;
			}
			else if(State3==3)
			{
				LCM_para=LCM_para-1;
				if(LCM_para<1)LCM_para=12;
			}
		}
		if(SEG_Show==1)
		{
			if(State1==1)State1=2;
			else State1=1;
		}
	}			
	if((KEY_Value==7)&&(KEY_Press_TIME>=1000))
	{
		KEY_Value=0;
		Cishu=0;
		AT24C02_write(0x00,0);
	}
}

void SEG_Refresh(void)
{
	if(SEG_Show == 1)
	{
		DigBuf[0] = 22; DigBuf[1] = 21; DigBuf[2] = 21;
		if(State1==1)
		{
			if(Freq>=10000)
			{
				DigBuf[3]=Freq/10000;
				DigBuf[4]=Freq%10000/1000;
				DigBuf[5]=Freq%1000/100;
				DigBuf[6]=Freq%100/10;
				DigBuf[7]=Freq%10; 
			}
			else if((Freq>=1000)&&(Freq<10000))
			{
				DigBuf[3]=21;
				DigBuf[4]=Freq%10000/1000;
				DigBuf[5]=Freq%1000/100;
				DigBuf[6]=Freq%100/10;
				DigBuf[7]=Freq%10; 
			}
			else if((Freq>=100)&&(Freq<1000))
			{
				DigBuf[3]=21;
				DigBuf[4]=21;
				DigBuf[5]=Freq%1000/100;
				DigBuf[6]=Freq%100/10;
				DigBuf[7]=Freq%10; 
			}
			else if((Freq>=10)&&(Freq<100))
			{
				DigBuf[3]=21;
				DigBuf[4]=21;
				DigBuf[5]=21;
				DigBuf[6]=Freq%100/10;
				DigBuf[7]=Freq%10; 
			}
		}
		else if(State1==2)
		{
			DigBuf[3]=21;
			DigBuf[4]=21;
			if(Freq>=10000)
			{
				DigBuf[5]=Freq/10000;
				DigBuf[6]=Freq/1000%10+10;
				DigBuf[7]=Freq/100%10; 
			}
			else if((Freq>=1000)&&(Freq<10000))
			{
				DigBuf[5]=21;
				DigBuf[6]=Freq/1000%10+10;
				DigBuf[7]=Freq/100%10; 
			}
			else if((Freq>=100)&&(Freq<1000))
			{
				DigBuf[5]=21;
				DigBuf[6]=10;
				DigBuf[7]=Freq/100%10; 
			}
		}			
	}
	else if(SEG_Show == 2)
	{
		DigBuf[0] = 23;  DigBuf[1] = 21; 	
		DigBuf[2] = 21;  DigBuf[3] = 21;  DigBuf[4] = 21;
		DigBuf[5] = 21;  DigBuf[6] = Shidu/ 10;  DigBuf[7] = Shidu % 10;			
	}

	else if(SEG_Show == 3)
	{
		DigBuf[0] = 24;  DigBuf[1] = 21;  DigBuf[2] = 21;
		DigBuf[3] = 21;  DigBuf[4] = 21;  
		if(State2==1)
		{
			if(LCM>=100)
			{
				DigBuf[5] = LCM/100;
				DigBuf[6] = LCM%100/10;
				DigBuf[7] = LCM%10;
			}
			else if((LCM>=10)&&(LCM<100))
			{
				DigBuf[5] = 21;
				DigBuf[6] = LCM%100/10;
				DigBuf[7] = LCM%10;
			}
			else if((LCM>=0)&&(LCM<10))
			{
				DigBuf[5] = 21;
				DigBuf[6] = 21;
				DigBuf[7] = LCM%10;
			}
		}
		else if(State2==2)
		{
			if(LCM>=100)
			{
				DigBuf[5] = LCM/100+10;
				DigBuf[6] = LCM%100/10;
				DigBuf[7] = LCM%10;
			}
			else if((LCM>=10)&&(LCM<100))
			{
				DigBuf[5] = 10;
				DigBuf[6] = LCM%100/10;
				DigBuf[7] = LCM%10;
			}
			else if((LCM>=0)&&(LCM<10))
			{
				DigBuf[5] = 10;
				DigBuf[6] = 0;
				DigBuf[7] = LCM%10;
			}
		}			
	}
	else if(SEG_Show == 4)
	{
		DigBuf[0]=25;DigBuf[1]=State3;
		DigBuf[2]=21;		
		DigBuf[3]=21;
		DigBuf[4]=21;
		if(State3==1)
		{
			if(Freq_para>=100)
			{
				DigBuf[5]=Freq_para%1000/100;
				DigBuf[6]=Freq_para%100/10+10;
				DigBuf[7]=Freq_para%10;
			}
			else 
			{
				DigBuf[5]=21;
				DigBuf[6]=Freq_para%100/10+10;
				DigBuf[7]=Freq_para%10;
			}
		}
		else if(State3==2)
		{
			DigBuf[5]=21;
			DigBuf[6]=Shidu_para%100/10;
			DigBuf[7]=Shidu_para%10;
		}
		else if(State3==3)
		{
			DigBuf[5]=21;
			DigBuf[6]=LCM_para%100/10+10;
			DigBuf[7]=LCM_para%10;
		}
	}
}


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

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


// 使用PCA模块计时,Sysclk不分频做时钟输入
void Sonic_Init() 
	{
    CMOD = 0x88;
    CCON = 0;
    CH = 0;
    CL = 0;
}

void Sonic_SendTrig() 
	{
    unsigned char i;
    // 关中断发驱动波形
    EA = 0;
    for (i = 0; i < 8; i++) {
        TX = 1;
        Delay12us();
        TX = 0;
        Delay12us();
    }
    EA = 1;
}

unsigned int  Sonic_Measure() 
{
    unsigned char cf = 0;

    Sonic_Init();
    Sonic_SendTrig();
    // 开始计时
    CR = 1;
    while (RX) {
        // 计数器溢出处理
        if (CF == 1) {
            cf++;
            CF = 0;
        }
        if (cf == 10) {
            CR = 0;
            return 65535;
        }
    }
    CR = 0;
    // (((CH * 256 + CL) * 0.017) / 12:本轮计数的距离
    // 92.8427 * cf:溢出计数距离
    return (unsigned int ) (((CH * 256 + CL) * 0.017) / 12 + 92.8427 * cf);
}


void KeyScan(void)
{
	switch(KeyState)
	{
		case KEY_Check: if((P30 == 0) || (P31 == 0) || (P32 == 0) || (P33 == 0)) KeyState = KEY_Press;break;
		case KEY_Press:
		{
			if(P30 == 0) KEY_Value = 7;
			else if(P31 == 0) KEY_Value = 6;
			else if(P32 == 0) KEY_Value = 5;
			else if(P33 == 0) KEY_Value = 4;		
			KeyState = KEY_Release;	
			KEY_Press_TIME = 10;
		}
		break;
		case KEY_Release:	
		{
			if((P30 == 0) || (P31 == 0) || (P32 == 0) || (P33 == 0))
			{
				KEY_Press_TIME = KEY_Press_TIME + 10;
			}
			else 
			{
				KeyState = KEY_Check;
				KEY_Flag = 1;
			}
		}
		break;
		default: break;		
	}
}

void Timer2(void) interrupt 12
{
	EA=0;
	if(PWM_count==0)Actuator_Bit|= 0x20;
	else if(PWM_count == PWM_Deuty_count ) { Actuator_Bit&=~0x20; }
	else if(PWM_count == 5 ) { PWM_count = 0; Actuator_Bit|= 0x20; }
	Set_HC573(5,Actuator_Bit);
	EA=1;
	PWM_count++;
}

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


void Timer1(void) interrupt 3
{
	Set_HC573(6,0x00);
	Set_HC573(7,tab[DigBuf[DigCom]]);
	Set_HC573(6,0x01<<DigCom);						
	
	if(++DigCom==8)DigCom=0;	
	
	Task_Clock();
}

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

void Timer0Init(void)		//@12.000MHz
{
	AUXR |= 0x80;			//定时器时钟1T模式
	TMOD |= 0x04;			//设置定时器模式
	TL0 = 0x00;				//设置定时初始值
	TH0 = 0x00;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
}

void Freq_Callback_1s() 
	{
    TR0 = 0;
    Freq = TH0 * 256 + TL0;
    TL0 = 0x0;     
    TH0 = 0x0;     
    TF0 = 0;       
    TR0 = 1;       
}
	
void All_Init()
{
	Set_HC573(5,0x00);
	Set_HC573(4,0xff);
	Set_HC573(6,0x00);
}

void Set_HC573(unsigned char channel,unsigned char dat)
{
	P0=dat;
	switch(channel)
	{
		case 4:
			P2=0x80;
		break;
		case 5:
			P2=0xa0;
		break;
		case 6:
			P2=0xc0;
		break;
		case 7:
			P2=0xe0;
		break;
	}
	P2=0;
}

void Delay_MS(unsigned int MS)		//@12.000MHz
{
	unsigned int k;
	unsigned char i, j;

	for(k=0;k<MS;k++)
	{
		i = 12;
		j = 169;
		do
		{
			while (--j);
		} while (--i);
	}
}

MAIN.h程序

#ifndef __MAIN_H__
#define __MAIN_H__

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

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


unsigned char LED=0xff;
unsigned char Actuator_Bit = 0X00;

unsigned char code tab[]={0XC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90,\
													0X40,0X79,0X24,0X30,0X19,0X12,0X02,0X78,0X00,0X10,\
													0XBF,0XFF,0x8e,0x89,0x88,0x8c};

#define LED_ON(n) 	{LED&=~(0x01<<(n-1));	}
#define LED_OFF(n) 	{LED|=(0x01<<(n-1));}

unsigned char DigCom = 0 ;
unsigned char DigBuf[8] = {21,21,21,21,21,21,21,21};

typedef enum
{
	KEY_Check,			//检测状态
	KEY_Press,			//按下状态
	KEY_Release,		//等待松开状态
	Key_Over				//按下之后松开,按键结束状态
}KEY_State;

KEY_State KeyState = KEY_Check;			//初始设置按键初始化状态为检测状态
unsigned char KEY_Value = 0;
unsigned char KEY_tt = 0;						//按键扫描时间计时
unsigned int  KEY_Press_TIME = 10;	//按键按下时长
bit KEY_Flag = 0;										//按键按下又松开之后标识位

unsigned int LED_tt = 0 ;	
bit LED_Ref =0;		

unsigned int SEG_tt =0;		
bit SEG_Ref = 0 ;		

unsigned int  AD_Value = 0;
unsigned int  AD_tt = 0;
bit AD_Ref = 0;

unsigned int  LCM_tt = 0 ;
bit LCM_Ref = 0 ;
unsigned int  LCM = 0 ;
unsigned int LCM_last=0;

unsigned int xdata PWM_count = 0;
unsigned char xdata PWM_Deuty_count = 0;

unsigned int Freq = 0;
unsigned int Freq_tt = 0;
bit Freq_Ref = 0;

unsigned char SEG_Show = 1 ;
unsigned int Cishu=0;

unsigned char State1=1;
unsigned char State2=1;
unsigned char State3=1;

unsigned char Shidu=0;

unsigned char Freq_para=90;
unsigned char Shidu_para=40;
unsigned char LCM_para=6;

bit LED_flag=0;
bit LED_Run=0;

void Delay_MS(unsigned int MS);
void Set_HC573(unsigned char channel,unsigned char dat);
void ALL_Init(void);
void Timer0Init(void);		//1毫秒@12.000MHz
void KeyScan(void);
void Task_Clock(void);
void SEG_Refresh(void);
void Task_Key(void);

void Delay12us();		//@12.000MHz
void Timer1Init(void);		//10微秒@12.000MHz
void Sonic_Init() ;
void Sonic_SendTrig() ;

void Freq_Callback_1s() ;

void Timer2Init();

#endif

IIC.c程序

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


//总线引脚定义
sbit sda = P2^1;  /* 数据线 */
sbit scl = P2^0;  /* 时钟线 */

#define DELAY_TIME	5

//
static void I2C_Delay(unsigned char n)
{
    do
    {
        _nop_();
//			_nop_();_nop_();_nop_();_nop_();
//        _nop_();_nop_();_nop_();_nop_();_nop_();
//        _nop_();_nop_();_nop_();_nop_();_nop_();		
    }
    while(n--);      	
}

//
void I2CStart(void)
{
    sda = 1;
    scl = 1;
	I2C_Delay(DELAY_TIME);
    sda = 0;
	I2C_Delay(DELAY_TIME);
    scl = 0;    
}

//
void I2CStop(void)
{
    sda = 0;
    scl = 1;
	I2C_Delay(DELAY_TIME);
    sda = 1;
	I2C_Delay(DELAY_TIME);
}

//
void I2CSendByte(unsigned char byt)
{
    unsigned char i;
	
    for(i=0; i<8; i++){
        scl = 0;
		I2C_Delay(DELAY_TIME);
        if(byt & 0x80){
            sda = 1;
        }
        else{
            sda = 0;
        }
		I2C_Delay(DELAY_TIME);
        scl = 1;
        byt <<= 1;
		I2C_Delay(DELAY_TIME);
    }
	
    scl = 0;  
}

//
unsigned char I2CReceiveByte(void)
{
	unsigned char da;
	unsigned char i;
	for(i=0;i<8;i++){   
		scl = 1;
		I2C_Delay(DELAY_TIME);
		da <<= 1;
		if(sda) 
			da |= 0x01;
		scl = 0;
		I2C_Delay(DELAY_TIME);
	}
	return da;    
}

//
unsigned char I2CWaitAck(void)
{
	unsigned char ackbit;
	
    scl = 1;
	I2C_Delay(DELAY_TIME);
    ackbit = sda; 
    scl = 0;
	I2C_Delay(DELAY_TIME);
	
	return ackbit;
}

//
void I2CSendAck(unsigned char ackbit)
{
    scl = 0;
    sda = ackbit; 
	I2C_Delay(DELAY_TIME);
    scl = 1;
	I2C_Delay(DELAY_TIME);
    scl = 0; 
	sda = 1;
	I2C_Delay(DELAY_TIME);
}


unsigned char AT24C02_read(unsigned char add)
{
	unsigned char da;

	I2CStart();
	I2CSendByte(0XA0);
	I2CWaitAck();
	I2CSendByte(add);
	I2CWaitAck();
	I2CStop();

	I2CStart();
	I2CSendByte(0XA1);
	I2CWaitAck();
	da = I2CReceiveByte();
	I2CSendAck(1);
	I2CStop();

	return da;
}

void AT24C02_write(unsigned char add,unsigned char dat)
{
	I2CStart();
	I2CSendByte(0xA0);
	I2CWaitAck();
	I2CSendByte(add);
	I2CWaitAck();
	I2CSendByte(dat);
	I2CWaitAck();
	I2CStop();
}


unsigned char AD_read(unsigned char add)
{
	unsigned char temp;
	EA=0;
	I2CStart();
	I2CSendByte(0X90);
	I2CWaitAck();
	I2CSendByte(add);
	I2CWaitAck();
	I2CStop();
	
	I2CStart();
	I2CSendByte(0X91);
	I2CWaitAck();
	temp=I2CReceiveByte();
	I2CSendAck(1);
	I2CStop();
	EA=1;
	return temp;
}

void AD_write(unsigned char dat)
{
	EA=0;
	I2CStart();
	I2CSendByte(0X90);
	I2CWaitAck();
	I2CSendByte(0X40);
	I2CWaitAck();
	I2CSendByte(dat);
	I2CWaitAck();
	I2CStop();
	EA=1;
}




IIC.h程序

#ifndef _IIC_H
#define _IIC_H

static void I2C_Delay(unsigned char n);
void I2CStart(void);
void I2CStop(void);
void I2CSendByte(unsigned char byt);
unsigned char I2CReceiveByte(void);
unsigned char I2CWaitAck(void);
void I2CSendAck(unsigned char ackbit);


unsigned char AT24C02_read(unsigned char add);
void AT24C02_write(unsigned char add,unsigned char dat);

unsigned char AD_read(unsigned char add);
void AD_write(unsigned char dat);

#endif

物联沃分享整理
物联沃-IOTWORD物联网 » 蓝桥杯第十三届单片机国赛程序详解

发表评论