OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 3148|回复: 12

基于STM32单片机1602显示电子时钟

[复制链接]

23

主题

86

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
299
金钱
299
注册时间
2019-7-20
在线时间
108 小时
发表于 2019-7-20 08:42:45 | 显示全部楼层 |阅读模式
1、本系统采用1602显示时分秒,4个按键可以调整时间,一个按键是选择按键,一个按键是加,一个按键是减,一个按键是退出按键。
2、当选择按键按下的时候,该选择位闪烁。
主函数代码:
#include "delay.h"
#include "sys.h"
#include "1602.h"
#include "timer.h"
#include "key.h"
u8  table1[]="liyang          " ;
u8  table2[]="9876543210654321" ;
signed char  miao,fen,shi;
u8 flag_100ms=0;
u16 count_ms=0;
u8 S1num;
/*******************主函数************************/
int main(void)
{
    //u8 t;
  delay_init();//延时函数初始化
  LCD1602_Init();//LCD1602初始化函数
  TIM3_Int_Init(999,71);//1MS
   TIM4_Int_Init(999,71);//1MS
  KEY_Init(); //按键初始化
   #IF 0   //注释掉程序
    LCD_Write_Command(0x80);//第一行的首地址
       for(i=0;i<16;i++)
     {
         LCD_Write_Date(table1);
        delay_ms(3);
    }
     LCD_Write_Command(0xc0);//第二行的首地址
     for(i=0;i<16;i++)
      {
        LCD_Write_Date(table2);
      delay_ms(3);
      }
   #endif
    while(1)
    {
  
      display(shi,fen,miao);
   keyscan();
  }
  

}
定时器代码
void TIM3_Int_Init(u16 arr,u16 psc)
{
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能

//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位

TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器
TIM_Cmd(TIM3, ENABLE);  //使能TIMx      
}
//3中断服务程序
void TIM3_IRQHandler(void)   //TIM3中断
{
static u16 count=0;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
  {
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志
     count++;
     count_ms++;
     if(count_ms==500)
    {
      count_ms=0;
       flag_100ms=~flag_100ms;
    }
     if(count==1000)
    {
     count=0;
     miao++;
       if(miao==60)
      {
        miao=0;
         fen++;
         if(fen==60)
        {
          fen=0;
           shi++;
           if(shi==24)
           shi=0;
        }
      }
    }
   
  }
}
//通用定时器4中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM4_Int_Init(u16 arr,u16 psc)
{
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能

//定时器TIM4初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位

TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); //使能指定的TIM4中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;  //TIM4中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;  //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器
TIM_Cmd(TIM4, ENABLE);  //使能TIMx      
}
//定时器4中断服务程序
void TIM4_IRQHandler(void)   //TIM3中断
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
  {
    TIM_ClearITPendingBit(TIM4, TIM_IT_Update  );  //清除TIMx更新中断标志
     count_ms++;
     if(count_ms==500)
    {
      count_ms=0;
       flag_100ms=~flag_100ms;
    }
  }
}
1602代码
#include"1602.h"
#include"sys.h"
#include "delay.h"
extern u8 S1num;
extern u8 flag_100ms;
/*******************LCD1602初始化************************/
void LCD1602_Init(void)
{      
     GPIO_InitTypeDef  GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOA, ENABLE);  //使能PA,PC端口时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10;     //LCD1602的三根控制线rs,rw,en
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
     GPIO_Init(GPIOE, &GPIO_InitStructure);//根据设定参数初始化GPIOE      
   
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;//LCD1602的8跟数据线        //LED1-->PE.5 端口配置, 推挽输出
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//IO口速度为50MHz
     GPIO_Init(GPIOA, &GPIO_InitStructure);//根据设定参数初始化GPIOE
    LCD_Write_Command(0x01);//清除屏幕显示
    delay_ms(2);

    LCD_Write_Command(0x38);//设置LCD两行显示,一个数据由5*7点阵表示,数据由8跟线传输
     delay_ms(2);
  
    LCD_Write_Command(0x06);//设定输入方式,增量不移位
    delay_ms(2);
    LCD_Write_Command(0x0c);//开整体显示,关光标,不闪烁
    delay_ms(2);
}
/*******************写指令函数************************/
void LCD_Write_Command(u8 com)      
{
  GPIO_Write(GPIOA, 0X00FF&com);//该函数一般用来往一次性一个GPIO的多个端口设置
rs=0; //写指令函数和写数据函数其实可以并一起价格 BOOL变量判断是写指令就rs=0写数据就rs=1。
rw=0;
en=0;  //写指令     输入:RS=L,RW=L,E=下降沿脉冲
  delay_ms(2);
  en=1;
delay_ms(2);
  en=0;
}
/*******************写数据函数************************/
void LCD_Write_Date(u8 date)         
{  
  GPIO_Write(GPIOA,0X00FF&date);//GPIOA的第八位用来作为8位数据口
rs=1;
rw=0;
en=0;  //写指令     输入:RS=L,RW=L,E=下降沿脉冲
  delay_ms(2);
  en=1;
delay_ms(2);
  en=0;
}
/******************************************************************************
函数名称:LCD1602_MoveToPosition
函数功能:将液晶的光标移动到指定的位置
入口参数:x-液晶显示的行数,范围0-1
   x = 0:在液晶的第一行
   x = 1:在液晶的第二行
    y-液晶显示的列数,范围0-15
      y = 0:在液晶的第一列
   y = 1:在液晶的第二列
   ......
   y = 15:在液晶的第十六列
返回值:无
备注:通过指定x,y的值可以将液晶的光标移动到指定的位置
*******************************************************************************/
void LCD1602_MoveToPosition(u8 x,u8 y)
{
if(0 == x)
  LCD_Write_Command(0x80 | y);    //光标定位到第一行的y列
if(1 == x)
  LCD_Write_Command(0xC0 | y);    //光标定义到第二行的y列
}
/******************************************************************************
函数名称:LCD1602_DisplayOneCharOnAddr
函数功能:在指定的位置上显示指定的字符
入口参数:x-液晶显示的行数,范围0-1
   x = 0:在液晶的第一行
   x = 1:在液晶的第二行
    y-液晶显示的列数,范围0-15
      y = 0:在液晶的第一列
   y = 1:在液晶的第二列
   ......
   y = 15:在液晶的第十六列
    ucData-要显示的字符数据
返回值:无
备注:确保x,y的取值要在指定的范围内
*******************************************************************************/
void LCD1602_DisplayOneCharOnAddr(u8 x,u8 y,u8 ucData)
{
LCD1602_MoveToPosition(x,y);   //光标位置
  LCD_Write_Date(ucData);   //写入数据
}
/******************************************************************************
函数名称:LCD1602_DisplayString
函数功能:显示字符串
入口参数:ucStr-字符串的首地址
返回值:无
备注:无
*******************************************************************************/
void LCD1602_DisplayString(u8 *ucStr)
{
while(*ucStr != '\0')    //字符串结束之前,循环显示
{
   LCD_Write_Date(*ucStr);  //依次写入每一个字符
   ucStr++;         //指针增加
}
}
/*****************控制光标函数********************/
void write_guanbiao(u8 hang,u8 add,u8 date)
{  
if(hang==1)   
  LCD_Write_Command(0x80+add);
else
  LCD_Write_Command(0x80+0x40+add);
if(date == 1)
  LCD_Write_Command(0x0f);     //显示光标并且闪烁
else
  LCD_Write_Command(0x0c);   //关闭光标
}
void display(u8 aa,u8 bb,u8 cc)
{
   if(S1num!=3)
  {
   LCD1602_DisplayOneCharOnAddr(1,0,aa/10+0x30);
   LCD1602_DisplayOneCharOnAddr(1,1,aa%10+0x30);
  }
  else
  {
   if(flag_100ms==0)
   {
    LCD1602_DisplayOneCharOnAddr(1,0,' ');
    LCD1602_DisplayOneCharOnAddr(1,1,' ');
  
   
   }
   else
   {
      LCD1602_DisplayOneCharOnAddr(1,0,aa/10+0x30);
      LCD1602_DisplayOneCharOnAddr(1,1,aa%10+0x30);
   }
  }
    LCD1602_DisplayOneCharOnAddr(1,2,':');
  if(S1num!=2)
  {
   LCD1602_DisplayOneCharOnAddr(1,3,bb/10+0x30);
   LCD1602_DisplayOneCharOnAddr(1,4,bb%10+0x30);
  }
  else
  {
   if(flag_100ms==0)
   {
    LCD1602_DisplayOneCharOnAddr(1,3,' ');
    LCD1602_DisplayOneCharOnAddr(1,4,' ');
  
   
   }
   else
   {
      LCD1602_DisplayOneCharOnAddr(1,3,bb/10+0x30);
      LCD1602_DisplayOneCharOnAddr(1,4,bb%10+0x30);
   }
  }
   LCD1602_DisplayOneCharOnAddr(1,5,':');
  if(S1num!=1)
  {
   LCD1602_DisplayOneCharOnAddr(1,6,cc/10+0x30);
   LCD1602_DisplayOneCharOnAddr(1,7,cc%10+0x30);
  }
  else
  {
   if(flag_100ms==0)
   {
    LCD1602_DisplayOneCharOnAddr(1,6,' ');
    LCD1602_DisplayOneCharOnAddr(1,7,' ');
   }
   else
   {
      LCD1602_DisplayOneCharOnAddr(1,6,cc/10+0x30);
      LCD1602_DisplayOneCharOnAddr(1,7,cc%10+0x30);
   }
  }
}



正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

23

主题

86

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
299
金钱
299
注册时间
2019-7-20
在线时间
108 小时
 楼主| 发表于 2019-7-20 08:48:40 | 显示全部楼层
刚学STM32,用内部定时器做了一个电子时钟,STM32功能特别强大,希望自己能支持下来!
回复 支持 反对

使用道具 举报

23

主题

86

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
299
金钱
299
注册时间
2019-7-20
在线时间
108 小时
 楼主| 发表于 2019-7-20 22:53:21 | 显示全部楼层
库函数开发的小结:
我们一般使用ST库对外设进行初始化,一般有以下布骤:
1)定义一个XXX_InitTypeDef类型的初始化结构体。
2)根据自己的需求,向这些初始化结构体的成员写入特定的控制参数。
3)填充好结构体之后,把这个结构体作为输入参数调用相应的外设库函数XXX _Init();从而实现向寄存器写入控制参数,并配置好外设。
回复 支持 反对

使用道具 举报

23

主题

86

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
299
金钱
299
注册时间
2019-7-20
在线时间
108 小时
 楼主| 发表于 2019-7-23 08:39:42 | 显示全部楼层
volatile变量的学习:
1 C语言的这个关键字来修饰,为的是让编译器不要优化这个变量,这样每次用到这个变量时都要回到相应变量的内存中去取值,而volatile字面的意思就是“可变的,不确定”。
2 不使用volatile关键字修饰的变量在被访问的时候可能会直接从CPU的寄存器中取出 (因为这个变量被访问过,也就是说之前就从内存中取出这个变量的值保存到某个CPU寄存器中),之所以直接从寄存器中取值而不去内存中取值,这是编译器优化的结果,访问CPU寄存器的速度比访问内存快的多!
回复 支持 反对

使用道具 举报

23

主题

86

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
299
金钱
299
注册时间
2019-7-20
在线时间
108 小时
 楼主| 发表于 2019-7-24 20:47:10 | 显示全部楼层
经过这几天的学习,总结一下,STM32单片机带IDLE中断,所以利用这个中断,可以接收不定长字节的数据。
1、IDLE中断什么时候发生?
IDLE就是串口收到一帧数据后,发生的中断。什么是一帧数据呢?比如说给单片机一次发来1个字节,或者一次发来8个字节,这些一次发来的数据,就称为一帧数据,也可以叫做一包数据。
如何判断一帧数据结束,就是我们今天讨论的问题。因为很多项目中都要用到这个,因为只有接收到一帧数据以后,你才可以判断这次收了几个字节和每个字节的内容是否符合协议要求。
2、如何配置好IDLE中断?
下面我们就配置好串口IDLE中断吧。
串口CR1寄存器,其中,对bit4写1开启IDLE中断,对bit5写1开启接收数据中断。这个语句, USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启空闲中断
(RXNE中断和IDLE中断的区别?
当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。)
这是状态寄存器,当串口接收到数据时,bit5就会自动变成1,当接收完一帧数据后,bit4就会变成1.
需要注意的是,在中断函数里面,需要把对应的位清0,否则会影响下一次数据的接收。比如RXNE接收数据中断,只要把接收到的一个字节读出来,就会清除这个中断。
回复 支持 反对

使用道具 举报

23

主题

86

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
299
金钱
299
注册时间
2019-7-20
在线时间
108 小时
 楼主| 发表于 2019-8-6 08:52:49 | 显示全部楼层

#include "delay.h"
#include "sys.h"
#include "timer.h"
u8 flag_2ms=0;
unsigned char  LedChar[] = {  //数码管显示字符转换表
0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
unsigned char LedBuff[8];
#define dula PDout(8)
#define wela PDout(9)
/*******************数码管初始化************************/
void Display_Init(void)
{             
     GPIO_InitTypeDef  GPIO_InitStructure;
         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE, ENABLE);         //使能PA,PC端口时钟
         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9 ;                       
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        //IO口速度为50MHz
         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
       
     GPIO_Init(GPIOD, &GPIO_InitStructure);//根据设定参数初始化GPIOE                                         
         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;//LCD1602的8跟数据线                             //LED1-->PE.5 端口配置, 推挽输出
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//IO口速度为50MHz
     GPIO_Init(GPIOE, &GPIO_InitStructure);//根据设定参数初始化GPIOE
}
/* LED动态扫描刷新函数,需在定时中断中调用 */
void LedScan()
{
    static unsigned char posit=0;
        //P0 = 0x00; //消影
        GPIO_Write(GPIOE,0x00);
        dula=1;
        dula = 0;
        GPIO_Write(GPIOE,~(0x01<<posit));

        wela = 1;
        wela = 0;
        //P0 = LedChar[LedBuff[posit]];
        GPIO_Write(GPIOE,LedChar[LedBuff[posit]]);
        dula=1;
        dula = 0;
        if(++posit>=8) posit=0;
}

/*******************主函数************************/
int main(void)
{

        delay_init();//延时函数初始化
        TIM3_Int_Init(999,71);//1MS
        Display_Init();
    while(1)
    {
                LedBuff[0] =1;  //用户码显示
        LedBuff[1] =2;
                LedBuff[2] =3;  //用户码显示
        LedBuff[3] =4;
            LedBuff[4] =5;  //用户码显示
        LedBuff[5] =6;
                LedBuff[6] =7;  //用户码显示
        LedBuff[7] =8;
                if(flag_2ms)
                {
                        flag_2ms=0;
                        LedScan();       
                }
        }
               

}
总结,这个程序驱动8位数码管,驱动芯片用两片74HC573,PD8接控制数码管段码锁存器LE接口,PD9接控制数码位锁存器的LE接口,数据接PE0-8。

回复 支持 反对

使用道具 举报

2

主题

15

帖子

0

精华

新手上路

积分
46
金钱
46
注册时间
2019-8-3
在线时间
9 小时
发表于 2019-8-13 14:12:02 | 显示全部楼层
u8  table1[]="liyang          " ; u8  table2[]="9876543210654321" ; u8 flag_100ms=0; u16 count_ms=0; u8 S1num;您好,刚开始学32,想控制LCD1602,您能告诉我一下这几个定义是什么意思啊
回复 支持 反对

使用道具 举报

0

主题

131

帖子

0

精华

初级会员

Rank: 2

积分
175
金钱
175
注册时间
2019-7-1
在线时间
6 小时
发表于 2019-8-13 14:29:17 | 显示全部楼层
谢谢楼主分享
IIS7站长  http://www.iis7.com/
回复 支持 反对

使用道具 举报

23

主题

86

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
299
金钱
299
注册时间
2019-7-20
在线时间
108 小时
 楼主| 发表于 2019-8-14 18:18:32 | 显示全部楼层
ZYL1111111 发表于 2019-8-13 14:12
u8  table1[]="liyang          " ; u8  table2[]="9876543210654321" ; u8 flag_100ms=0; u16 count_ms=0; ...

我这几个变量是按键调时钟用到了,S1num功能按键按下的次数,flag_100ms但按键按下的,该位置闪烁
回复 支持 反对

使用道具 举报

23

主题

86

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
299
金钱
299
注册时间
2019-7-20
在线时间
108 小时
 楼主| 发表于 2019-8-16 13:47:32 | 显示全部楼层
今天我们讲下,我们在项目开发中,经常会遇到想在GPIOA的低8位输出,但输出时不影响高8位的端口,这时候我们改怎么办呢?
我们定义一个变量,  u16 tmp;         //定义一个临时变量
          tmp = GPIOE->ODR & 0xFF00;     //读取GPIOE输出值,并将低8位清零
             GPIOE->ODR = tmp | 0x00; //低8位置为数码管缓冲区数值后输出到GPIOE(高8位保持原值不变)
GPIOE->ODR寄存器用于输出,是支持读操作,和写操作的。但GPIOE->IDR寄存器只能读操作。
回复 支持 反对

使用道具 举报

2

主题

6

帖子

0

精华

新手上路

积分
39
金钱
39
注册时间
2019-6-2
在线时间
10 小时
发表于 2019-9-15 22:51:36 | 显示全部楼层
  flag_100ms=~flag_100ms;
想知道这句代码的含义,不知道楼主方便给讲讲呗。谢谢楼主了,看了你的代码收益颇深
回复 支持 反对

使用道具 举报

0

主题

4

帖子

0

精华

新手入门

积分
11
金钱
11
注册时间
2019-11-22
在线时间
4 小时
发表于 2019-11-23 17:34:26 | 显示全部楼层
过来看看
回复 支持 反对

使用道具 举报

0

主题

12

帖子

0

精华

新手入门

积分
17
金钱
17
注册时间
2019-5-20
在线时间
2 小时
发表于 2019-11-28 14:34:53 | 显示全部楼层
感谢分享
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-5-25 00:45

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表