2017-05-21 20:11:00 dmfylb 阅读数 29930
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    3998 人正在学习 去看看 朱有鹏

    之前说到利用51单片机输出pwm,但是输出的pwm是固定占空比的,如果想要改变占空比,还得回过头来修改程序并重新烧写进单片机。这样做的话不但麻烦、效率低,还又把芯片的可烧写次数减少了一次,虽然51的芯片现在很便宜,但能省点就省点,能不浪费就不浪费,哈哈,开玩笑啦!最主要的还是为了灵活性,为此特地通过加入按键的功能来实现动态修改输出的pwm的占空比。这样在用pwm控制电机的时候就能使用按键来控制电机转速了。废话不多说,见下:

#include<reg51.h>

#define uint unsigned int 
#define uchar unsigned char							 

uchar time; 
double count=26;	//定义占空比,并初始占空比为26%
sbit PWM=P2^0;      //P2^0口输出PWM 
sbit key_add=P3^4;   //电机加速的按键 的I/O口
sbit key_dec=P3^5;   //电机减速的按键 的I/O口


/************函数声明**************/
void delayxms(uint z); //延时
void Motor_add();	  //电机加速,即增加占空比
void Motor_dec();	   //电机减速,即减少占空比
void timer0_init();		//定时器0初始化

/*********主函数********************/
void main()
{	 
	timer0_init();
	delayxms(5);
	while(1)
	{
	 Motor_add();
	 Motor_dec();
	}
}

/****************延时处理**********************/
void delayxms(uint z) //延时xms程序
{
    uint x,y;
	for(y=z;x>0;x--)
		for(y=110;y>0;y--);
}


void Motor_add()  //电机加速
{
  if(key_add==0)
  {
	  delayxms(2); //此处时间不能太长,否者会的中断产生冲突
  if(key_add==0)
  {
	  count+=0.5;	//每按一次加速按键占空比加0.5
   if(count>=32)	    //限定占空比上限
	{
	 count=32;
	}
  }
	while(!key_add);
  }
}

void Motor_dec()//电机减速
{
 if(key_dec==0)
 {
  delayxms(2);//此处时间不能太长,否者会的中断产生冲突
  if(key_dec==0)
  {
  	count-=0.5;	 	//每按一次减速按键占空比减0.5
   if(count<=25)    //限定占空比下限
   {
	count=25;
   }
  }
	while(!key_dec);
   }
}

/***********定时器0初始化***********/
void timer0_init()
{
  TMOD=0x01; //定时器0工作于方式1
  TH0=(65536-10)/256;
  TL0=(65536-10)%256;
  TR0=1;
  ET0=1;
  EA=1;
}

/**************定时0中断处理******************/
void timer0_int() interrupt 1 
{
	
  TR0=0;    //设置定时器初值期间,关闭定时器
  TH0=(65536-10)/256;
  TL0=(65536-10)%256;
  TR0=1;
	 
  time++;
  if(time<count)	  //count为占空比
  {
	PWM=1;	   //输出高电平
  }
  else 
	PWM=0;	
  if(time>=100)
  {
	time=0;
  }
 }


我特地标注了重要部分程序的注释。另外,可以在程序中修改按键每次增加或者减少的占空比,但有的时候发现在按键调节占空比时,不想占空比一直增加或者减少下去,还可以在程序里面修改限定占空比的上下限,当达到上限或下限时,相应的增加或减少占空比的按钮这时就不再增加或者减少占空比了。这样在电机控制时就可以防止不小心让电机速度增加到规定速度以上或以下了。这些在程序注释里面课看见在哪里改。除此之外,还可以加进来很多功能。这里就不举相应的子了。


2015-01-26 13:15:27 hjl240 阅读数 4694
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    3998 人正在学习 去看看 朱有鹏

STM32F411RE Nucleo笔记-按键控制PWM占空比

此次用到STM32F411RENucleo开发板,用到Keil MDK5.12和STM32CubeMx软件。

首先用STM32CubeMx软件配置定时器PWM模式与按键输入IO口。


图1 PWM配置


图2 按键IO口配置

然后生成Keil工程。

用Keil打开。

         首先修改有关PWM方面的代码。

PWM初始化代码如下:

/* TIM10 init function */
void MX_TIM10_Init(void)
{
  TIM_OC_InitTypeDef sConfigOC;

  htim10.Instance = TIM10;
  htim10.Init.Prescaler = 0;
  htim10.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim10.Init.Period = 0;
  htim10.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  HAL_TIM_Base_Init(&htim10);

  HAL_TIM_PWM_Init(&htim10);

  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  HAL_TIM_PWM_ConfigChannel(&htim10, &sConfigOC, TIM_CHANNEL_1);
}

查看头文件stm32f4xx_hal_tim.h,可以知道定时器初始化参数的含义如下:




从头文件中可以看出,要想简单配置PWM很简单,只要改变PWM周期的值与脉冲值就好。

比如将改变一下两个语句:

htim10.Init.Period = 1000;

sConfigOC.Pulse = 200;

便将PWM配置为周期为1000个定时器周期,高电平持续时间为200个定时器时钟周期,占空比为200/1000=20%。

 

要想用按键控制PWM占空比,我们只需要修改一下PWM初始化函数,修改如下:



修改此函数之后,前面的函数声明也要改。改为:static void MX_TIM10_Init(int period,intpulse);

要想PWM工作,需要启动PWM。查看头文件stm32f4xx_hal_tim.h,可以看到如下函数:


这是PWM启动与停止的函数。

 

最后看看按键,只需要把按键配置为上拉模式就好。

在按键初始化中,将

GPIO_InitStruct.Pull = GPIO_NOPULL;

改为

GPIO_InitStruct.Pull = GPIO_PULLUP;

接下来便可以写主函数。

int main(void)
{

  /* USER CODE BEGIN 1 */
	int i=100;
  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* System interrupt init*/
  /* Sets the priority grouping field */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_0);
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM10_Init(1000,100);//PWM初始化

  /* USER CODE BEGIN 2 */
	HAL_TIM_PWM_Start(&htim10,TIM_CHANNEL_1);//启动PWM
  /* USER CODE END 2 */

  /* USER CODE BEGIN 3 */
  /* Infinite loop */
  while (1)
  {
		if(!HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13))//如果按键按下
		{
			i=i+100;
			if(i==1000) i=100;
			HAL_TIM_PWM_Stop(&htim10, TIM_CHANNEL_1);//停止PWM
			MX_TIM10_Init(1000,i);//PWM重新配置,周期为1000,高电平持续时间为i
			HAL_TIM_PWM_Start(&htim10,TIM_CHANNEL_1);//启动PWM
			while(!HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13));//等待按键释放
		}
  }
  /* USER CODE END 3 */

}

 

编译程序,下载程序,可以看到现象:


每次按下按键,占空比会增加10%。

                                                                                                                                                      HJL

                                                                                                                                                      2015.1.26










2018-05-06 17:11:05 OverWatcher 阅读数 6445
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    3998 人正在学习 去看看 朱有鹏
51单片机按键控制舵机旋转

---------------------------------------------------------------------------------------------------------------------------------

1.舵机的与单片机的连接

2.PWM波的简单了解

3.程序示例

4.开发程序难点

---------------------------------------------------------------------------------------------------------------------------------

1.舵机的与单片机的连接


    如图所示,舵机正极接单片机vcc,负极接gnd,signal的连接可自定义。

---------------------------------------------------------------------------------------------------------------------------------

2.PWM波的简单了解

    51单片机通过按键来控制舵机正反转,需要通过PWM波来实现,本文仅对如何使用PWM波控制舵机旋转做出解释。

    对于一般的舵机来说,所对应的PWM波的周期为200ms,即单片机信号口输出200ms为一个周期。设信号口输出‘1’的时间为 x ms,剩余时间均输出‘0’,x/200的比值即为占空比的值。单片机通过控制占空比的值来控制舵机的旋转。

    对于常见的舵机来说,占空比的值与对应的角度有以下关系:

    0.5ms/200ms = 0 度

    1.0ms/200ms = 45 度

    1.5ms/200ms = 90 度

    2.0ms/200ms = 135 度

    2.5ms/200ms = 180 度

    在51单片机中,通过定时器来计时,通过信号输出口输出‘1’或‘0’来控制舵机的旋转。

---------------------------------------------------------------------------------------------------------------------------------

3.程序示例

#include<reg52.h>  


sbit key1 = P3^2;  
sbit key2 = P3^3;
sbit pwm1 = P2^0;  


unsigned int target =12,percent = 0;  
  
void Timer0Initial();  
void initial_Timer();  
void delay(unsigned int x);  
void keyscan();  


void keyscan()  
{
delay(30);
if(key1 == 0)  
  {  
delay(10);  
    if(key1 == 0)  
    {  
if(target >= 5)  
target -= 1;   
    }    
  }  
  
  if(key2 == 0)  
  {  
delay(10);  
    if(key2 == 0)  
{  
if(target <= 25)  
target += 1;   
    }  
  }  

  
void initial()  
{  
key1 = 1;  
  key2 = 1;  
  initial_Timer();  
}  


void Timer0Initial()  
{  
  TH0 = (65536-65)/256;  
  TL0 = (65536-65)%256;  

  
void initial_Timer()  
{  
  EA = 1; 
  ET0 = 1; 
  TMOD = 0x01;
  Timer0Initial();  
  TR0 = 1;
}  
  
void delay(unsigned int x)  
{  
  unsigned int i,j;  
  for(i=x;i>0;i--)  
    for(j=110;j>0;j--); 



void main()  
{  
  initial();  
  while(1)  
  {  
    keyscan();  
  }  
}


void Timer0() interrupt 1 using 0  

percent+=1;
if(percent<=target)
pwm1=1;
else
pwm1=0;
if(percent==200)
percent=0;
  Timer0Initial();  

}

    注释:

    P3^3和P3^2为独立按键,P2^0为输出信号口。

---------------------------------------------------------------------------------------------------------------------------------

4.开发程序难点

    在实际进行操作时,常常会遇到两种情况而导致程序无法正常运行:

    1>程序运行速度与舵机转速的不匹配

        在实际操作时,由于舵机的转动与程序中target的增加并不是同步进行的,这就会导致松开按键时,由于程序运行速度远大于舵机转动速度,target的值已经到达25,但是舵机直转到90度位置,松开按键后会继续旋转。

        解决方法为,在keyscan函数中添加延时语句delay()来使程序运行速度变慢,通过多次尝试使得舵机旋转角度与target的值同步。

    2>0.5ms到2.5ms的区间并不能使得舵机从0度转到180度

        不同的舵机可能由于制作精度的区别而导致误差,可以通过调整PWM波的占空比来确定具体的角度。

        比如,我使用的舵机从0到180度实际的PWM波的ms为6ms~27ms,我室友的舵机从0到180度实际的PWM波为4~18ms。

2019-06-11 16:54:44 weixin_38679924 阅读数 921
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    3998 人正在学习 去看看 朱有鹏

项目描述:
由三个按键分别控制LED的开关、变亮、变暗。灯的亮度调节采用单片机产生的PWM实现。
C语言代码如下:

/*-----------------------------
FileName:PWMDimming.h
Function: 头文件
Author: Zhang Kaizhou
Date: 2019-6-11 16:49:42
------------------------------*/
#include <reg52.h>
#include <string.h>
#define uchar unsigned char
#define uint unsigned int
#define PERIOD 100 // PWM信号周期为 100ms

/*主模块端口定义*/
sbit led = P1^7;
sbit onOff = P3^4;
sbit addLight = P3^5;
sbit decLight = P3^6;

/*LCD1602显示模块端口定义*/
sbit lcdrs = P1^0;
sbit lcdrw = P1^1;
sbit lcden = P2^5;

/*主模块函数声明*/
void delay(uint xms);
void keyScan();
void dimming();
void generatePWM();

/*LCD1602显示模块函数声明*/
void LCDInit();
void display(uchar dat);
void writeCommand(uchar command);
void writeData(uchar dat);
/*-------------------------------------------------
Function: 单片机PWM调光
Description: 按键控制LED灯开关,四个亮度等级的调光;
LCD1602实时显示LED灯的亮度等级。
Author: Zhang Kaizhou
Date: 2019-6-11 15:37:10
--------------------------------------------------*/
#include "PWMDimming.h"

/*全局变量定义*/
uchar brightness = 0; // 亮度标志
uint count = 0;
bit onFlag = 0; // 0-关灯 1-开灯

void main(){
	LCDInit();
	led = 1; // 上电时灯处于关闭状态
	while(1){
		keyScan();
		display(brightness);
		dimming();
		generatePWM();
	}
}

/*键盘扫描函数*/
void keyScan(){
	if(!onOff){ // 开关灯
		delay(5);
		if(!onOff){
			while(!onOff);
			led = onFlag;
			onFlag = ~onFlag;
			if(onFlag){
				brightness = 1; // 开灯初始亮度为1
			}else{
				brightness = 0;
				led = 1;
			}
		}
	}else if(onFlag){ // 灯处于开的状态
		if(!addLight){ // 变亮
			delay(5);
			if(!addLight){
				if(brightness < 4){
					while(!addLight);
					brightness++;
				}
			}
		}
		if(!decLight){ // 变暗
			delay(5);
			if(!decLight){
				if(brightness > 1){
					while(!decLight);
					brightness--;
				}
			}
		}
	}
}

/*调光程序*/
void dimming(){
	switch(brightness){
		case 1: count = 80; break; // 80%占空比
		case 2: count = 90; break; // 90%占空比
		case 3: count = 95; break; // 95%占空比
		case 4: count = 100; break; // 100%占空比
		default: break;
	}
}

/*产生PWM信号*/
void generatePWM(){
	while(onOff && addLight && decLight && brightness){
		led = 0;
		delay(count);
		led = 1;
		delay(PERIOD - count);
	}
}

/*延时函数*/
void delay(uint xms){
	uint i, j;
	for(i = xms; i > 0; i--)
		for(j = 110; j > 0; j--);
}
/*-----------------------------
FileName:display.c
Function: LCD1602显示函数
Author: Zhang Kaizhou
Date: 2019-6-11 16:49:02
------------------------------*/
#include "PWMDimming.h"
/*显示信息数组*/
uchar code table0[] = {"PWMDmimming"};
uchar code table1[] = {"Brightness:"};
uchar code table2[] = {"01234"};
uchar num = 0;

/*初始化LCD1602的设置*/
void LCDInit(){
	uchar i;
	lcden = 0; // 拉低使能端,准备产生使能高脉冲信号
	writeCommand(0x38); // 显示模式设置(16x2, 5x7点阵,8位数据接口)
	writeCommand(0x0c); // 开显示,不显示光标
	writeCommand(0x06); // 写一个字符后地址指针自动加1
	writeCommand(0x01); // 显示清零,数据指针清零
	
	/*LCD上电界面*/
	writeCommand(0x80); // 将数据指针定位到第一行首
	for(i = 0; i < strlen(table0); i++){
		writeData(table0[i]);
		delay(5);
	}
	
	writeCommand(0x80 + 0x40); // 将数据指针定位到第二行首
	for(i = 0; i < strlen(table1); i++){
		writeData(table1[i]);
		delay(5);
	}
}

/*LCD显示函数*/
void display(uchar dat){
	writeCommand(0x80 + 0x40 + strlen(table1));
	writeData(table2[dat]);
	delay(5);
}

/*写指令函数*/
void writeCommand(uchar command){
	lcdrs = 0; // 命令选择
	lcdrw = 0;
	P0 = command;
	delay(5);
	
	lcden = 1; // 产生一个正脉冲使能信号
	delay(5);
	lcden = 0;
}

/*写数据函数*/
void writeData(uchar dat){
	lcdrs = 1; // 数据选择
	lcdrw = 0;
	P0 = dat;
	delay(5);
	
	lcden = 1;
	delay(5);
	lcden = 0;
}

扩展:
1.可根据需求增加或减少LED灯的亮度等级;
2.可通过改变个亮度等级对应的PWM的占空比来改变个亮度等级对应的灯的亮度;
3.因为此处PWM调制主要是根据占空比的不同控制LED的亮度,因此用软件延时即可。

2018-11-02 11:33:59 weixin_42670445 阅读数 14837
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    3998 人正在学习 去看看 朱有鹏

    之前尝试用单片机控制42步进电机正反转,电机连接导轨实现滑台前进后退,在这里分享一下测试程序及接线图,程序部分参考网上找到的,已经实际测试过,可以实现控制功能。

    所用硬件:步进电机及驱动器、STC89C52单片机、直流电源

1、硬件连接图

              

  • 注意:上图为共阳极接法,实际连接参考总体线路连接。
  • 驱动器信号端定义:

PUL+:脉冲信号输入正。( CP+ )

PUL-:脉冲信号输入负。( CP- )

DIR+:电机正、反转控制正。

DIR-:电机正、反转控制负。

EN+:电机脱机控制正。

EN-:电机脱机控制负。

  • 电机绕组连接

A+:连接电机绕组A+相。

A-:连接电机绕组A-相。

B+:连接电机绕组B+相。

B-:连接电机绕组B-相。

  • 电源连接

VCC:电源正端“+”

GND:电源负端“-”

注意:DC直流范围:9-32V。不可以超过此范围,否则会无法正常工作甚至损坏驱动器.

  • 总体线路连接

输入信号共有三路,它们是:①步进脉冲信号PUL+,PUL-;②方向电平信 号DIR+,DIR-③脱机信号EN+,EN-。输入信号接口有两种接法,可根据 需要采用共阳极接法或共阴极接法。

在这里我采用的是共阴极接法:分别将 PUL-,DIR-,EN-连接到控制系统的地端(接入单片机地端); 脉冲输入信号通过PUL+接入单片机(代码中给的P2^6脚),方向信号通过DIR+接入单片机(代码中给的P2^4脚),使能信号通过EN+接 入(不接也可,代码中未接,置空)。按键连接见代码,分别用5个按键控制电机启动、反转、加速、减速、正反转。

注意:接线时请断开电源,电机接线需注意不要错相,相内相间短路, 以免损坏驱动器。

2、代码

#include<reg51.h> 
#define MotorTabNum 5
unsigned char T0_NUM;
sbit K1 = P3^5;        // 启动
sbit K2 = P3^4;        // 反转
sbit K3 = P3^3;        // 加速
sbit K4 = P3^2;        // 减速
sbit K5 = P3^1;        //正反转

sbit FX      = P2^4;     // 方向
//sbit MotorEn = P2^5;     // 使能
sbit CLK     = P2^6;     // 脉冲

int table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x40};           

unsigned char g_MotorSt = 0;     // 
unsigned char g_MotorDir = 0;    // 
unsigned char MotorTab[7] = {12, 10, 8, 6, 4, 2,1};

signed char g_MotorNum = 0;

void delayms(xms);
void mDelay(unsigned int DelayTime);                
void T0_Init();

void KeyScan(void);



void main(void)
{
        T0_Init();            
 //       MotorEn = 0;     // 
        FX = 0;
        while(1)
        {
                KeyScan();              // 
        }


}

void T0_Init()
{
        TMOD = 0x01;
        TH0 = (65535-100)/256;  // 1ms
        TL0 = (65535-100)%256;
        EA = 1;
        ET0 = 1;
//        TR0 = 1; 

}

void T0_time() interrupt 1
{
//        TR0 = 0;
        TH0 = (65535-100)/256;   
        TL0 = (65535-100)%256;
        T0_NUM++;
        if(T0_NUM >= MotorTab[g_MotorNum])        // 
        {
                T0_NUM = 0;
                CLK=CLK^0x01;               //   
        }
//        TR0 = 1;
}         


//-----????---------------------
void KeyScan(void)
{
        if(K1 == 0)
        {
                delayms(10);   
                if(K1 == 0)
                {
                        g_MotorSt = g_MotorSt ^ 0x01;
                      //  MotorEn ^= 1;
                        TR0 = 1; 
						FX ^= 0;   //反转
                }
        }

        if(K2 == 0)
        {
                delayms(10);   //正转
                if(K2 == 0)
                {    
                        g_MotorDir = g_MotorDir ^ 0x01;
                        FX ^= 1;    //加速
                }
        }

        if(K3 == 0)  // 
        {
                delayms(5);   //加速
                if(K3 == 0)
                {   
                        g_MotorNum++;
                        if(g_MotorNum > MotorTabNum)
                                g_MotorNum = MotorTabNum;
                }
        }

        if(K4 == 0)  // 
        {
                delayms(5);   // 减速
                if(K4 == 0)
                {       
                    g_MotorNum--;
                    if(g_MotorNum < 0)
                    g_MotorNum = 0;
                }
        }

		if(K5 == 0)  // 
		{
				delayms(10);   // 正反转
				if(K5 == 0)
				{      
				    g_MotorSt = g_MotorSt ^ 0x01;
					g_MotorDir = g_MotorDir ^ 0x01;
                    MotorEn ^= 1;
                    TR0 = 1;
			        while(1)
					{
                       FX ^= 1;    //													        
                       delayms(90000);
					   FX ^= 0;    //
					   delayms(90000);
					}
				}
		}
}

void delayms(xms)//延时
{
         unsigned int x,y;
         for(x=xms;x>0;x--)
                 for(y=110;y>0;y--);
}

3、常见问题解答

  • 控制信号高于5v一定要串联电阻,否则可能会烧坏驱动器控制接口电路。
  • 接通电源后如果驱动器灯亮,但是无法控制电机旋转,考虑控制部分驱动能力不足或者驱动器所设置的驱动电流不够(我就遇到过这种情况,后来通过调高驱动器限制电流解决的此问题)。如果调高驱动电流步进电机仍无法转动,查看电路板上的按键有没有接对,程序中按键引脚可根据电路板设计的按键引脚连接自行改动。
  • 判断步进电机四条线的定义:将任意两条线接在一起,用手旋转电机,如果有阻力,则两条线是同一相。用相同方法测试另外两条线是否是同一相。确定同相的两条线任意接入两相接口,如果旋转方向相反只需换相即可。
  • 如需调试或增加功能可联系QQ:1932900294。
  • 增加启停及加减速功能代码:https://download.csdn.net/download/weixin_42670445/11978165

PWM51单片机呼吸灯

博文 来自: Zach_z

51单片机舵机控制

阅读数 479

没有更多推荐了,返回首页