搜索
bottom↓
回复: 34

我的51单片机学习笔记----I/O口篇

[复制链接]

出0入0汤圆

发表于 2009-4-7 11:58:03 | 显示全部楼层 |阅读模式
学习单片机快1年了,整理下以前学过的东西,希望能够给其它的人一点帮助。现在回想起大一一个人在宿舍练习汇编,第一次点亮一个LED的时候,依然感觉很兴奋。那段日子很苦,没有人教,没有人问,但是很快乐,尤其是解决了问题以后。在此,祝所有同学学习进步哈~~
文中肯定存在错误,希望各位高手不吝指出,多多帮助我们这些菜鸟。
      
在我们的单片机应用系统中,常常会遇到I/O口不够的情况。譬如说接有外部RAM而且要求有16个以上的按键,8位数码管以上的显示。而且还不包括其它的外围器件。这时整个系统的I/O资源就很吃紧了。系统的扩展性也不好。这时我们就需要考虑对单片机的I/O进行扩展了。
      虽然专门的I/O扩展芯片市场上也有不少,但对于我们一般的应用,没有必要整的那么复杂。用一些简单的移位寄存器芯片一样可以实现我们的目标。下面我们首先来认识一下74HC164这款芯片。这款芯片的作用是把串行输入的数据并行输出。注意,它没有锁存功能,在允许输出的情况下,每一个时钟的上升沿,数据依次从最低位移向最高位。因此,在做数码管的输出显示的时候会出现拖影的想象,在设计此电路时要注意考虑此情况。
下面是它的引脚图。A1,A2是数据输入端,一般情况下两者连在一起,作为串行数据的输入端。Qa----Qh j就是并行数据的输出端了。CLOCK 和RESET分别为时钟和复位端

(原文件名:74HC164引脚图.jpg)

下面我们再看看它的真值表,有了真值表我们才知道如何正确的去编写程序去驱动它(其它复杂的器件还需要对照时序图编写相应的驱动程序)

(原文件名:74HC164真值表.jpg)
呵呵,怎么样,这个表很简单吧,相信大家都能够看的懂。当Reset为低电平时不管时钟为高电平还是低电平也不管输入引脚A1,A2为何值,输出的并行数据均为低电平。当Reset为高电平时,只有在时钟的上升沿,A1A2上的值才被移位输出。看懂了这张表那么剩下的事情就好办多了。
下面我以级联的8块74HC164驱动8位共阴的数码管为例来阐述它的用途。当然它的用途并不仅仅在于此。你可以发挥你的聪明才智去应用它到你的设计中

(原文件名:74hc164级联数码管.jpg)
以上的连接中Reset脚要全部接高电平。所有的Clock引脚都要连接在一块。第一块74HC164的AB引脚接在一块作为串行数据的输入端。第二块74HC164的AB引脚接在第一块74HC164并行数据输出端的H脚上。后面的接法依照第二块的接法依次级联下去。
接好后共引出四根引线。其中电源两根。一根时钟线。一根串行数据输入线。怎么样,节省了不少IO口吧~~
下面看看如何写程序去驱动它。(编译器keil Uv3)
先看看下面的引脚连接及相关宏定义
sbit io_74hc164_SCK = P3^7 ;
sbit io_74hc164_SDA = P3^6 ;

#define IO_74HC164_SCK_HIGH          io_74hc164_SCK = 1 ;
#define IO_74HC164_SCK_LOW           io_74hc164_SCK = 0 ;
#define IO_74HC164_SDA_INPUT         io_74hc164_SDA
下面是数码管的段码表可以根据不同的连接顺序去修改。
/***********************************************************
        a -- 4  b -- 5  c -- 6  d -- 2
        e -- 0  f -- 1  g -- 3  dp -- 7
***********************************************************/
uint8 code DisplayTable[]=
{
         0x77,0x60,0x3D,0x7C,0x6A,0x5E,0x5F,0x70,0x7F,0x7E,0x7B,0x4F,0x17,0x6D,0x1F,0x1B,0x08/*0    1    2    3   4   5    6    7   8    9   a    b    c    d    e    f    -  */
};
void v_74hc164WriteData_f( uint8 Dat )                   //向74HC164写一个字节的内容
{                                                                                                   //即可并行输出该字节
uint8 i = 0 ;
uint8 SendData = Dat ;
for( i = 8 ; i > 0 ; i-- )
{       
IO_74HC164_SCK_LOW
SendData <<= 1 ;
IO_74HC164_SDA_INPUT = CY ;
IO_74HC164_SCK_HIGH
}
}

void v_HexToBcd_f( uint8 *P, uint16 Dat )                          //BCD码的转化
{
uint8 i = 0 ;
uint8 Temp ;
if( Dat >= 40000 ) { i  = 4 ; Dat -= 40000 ; }
if( Dat >= 20000 ) { i += 2 ; Dat -= 20000 ; }  
if( Dat >= 10000 ) { i += 1 ; Dat -= 10000 ; }
*P++ = i ;
i = 0 ;                  

if( Dat >= 8000 ) { i  = 8 ; Dat -= 8000 ; }
if( Dat >= 4000 ) { i += 4 ; Dat -= 4000 ; }
if( Dat >= 2000 ) { i += 2 ; Dat -= 2000 ; }
if( Dat >= 1000 ) { i += 1 ; Dat -= 1000 ; }
*P++ = i ;
i = 0 ;

if( Dat >= 800 )  { i  = 8 ; Dat -= 800 ; }
if( Dat >= 400 )  { i += 4 ; Dat -= 400 ; }
if( Dat >= 200 )  { i += 2 ; Dat -= 200 ; }
Temp = Dat ;                //这里换成8位数据,是为了加快速度
if( Temp >= 100 )  { i += 1 ; Temp -= 100 ; }
*P++ = i ;
i = 0 ;
       
if( Temp >= 80 )   { i  = 8 ; Temp -= 80 ; }
if( Temp >= 40 )   { i += 4 ; Temp -= 40 ; }
if( Temp >= 20 )   { i += 2 ; Temp -= 20 ; }
if( Temp >= 10 )   { i += 1 ; Temp -= 10 ; }
*P++ = i ;
*P = Temp ;          
}
/**************************************************************************
* Function:         void v_74hc164DisplayNumber_f( uint8 data *Seg, uint8 Dot, int16 Dat )                                                                      *
* Description: 在8位数码管数值以及两位自定义字符                                                                                                                                  *
*                                                                                   *                                                                                                                                                      *
* Parameter:        *Seg   :  指向存放自定义字符数据的地址                                                                                                                    *
*                  Dot :  小数点相对数值的显示位置(取值范围1~5,当取0 或者大于5的数值时,小数点不显示)                                *
*                          Dat    :  显示数据(有符号整型数据,取值范围-32768~32767)                                                                                  *
**************************************************************************/
void v_74hc164DisplayNumber_f( uint8 data *Seg, uint8 Dot, int16 Dat )
{
bit zf = 1, OverWrite = 1, zf_lock = 1 ;
uint8  i , j , k = 4 ;
uint8 Buffer[5]   ;
if ( Dat < 0 )
{
zf = 0 ;
Dat = ABS( Dat ) ;         //如果是负数,则取其绝对值,并将负值标志位清0
}
v_HexToBcd_f( Buffer, Dat ) ;        //将数据每个位拆分,放在数组中最高位放在数组的第//一个成员
for( i = 5 ; i >= 2 ; i-- )             //判断数据的位数(如1234,则位数为4)
{
if( Buffer[ 5 - i ] > 0 ) break ;  //判断出最高位不为0即可
}   
if( ( Dot >= i ) && Dot < 6 ) i = Dot ;         //如果小数点打在数字前面,则该数字前面添0.( 数//字12,小数点打在第四位,则合理的显示应该为0.0012)
j = 5 - i ;
for(  ; i >= 1 ; i-- )        //显示数值
{
if( Dot == ( 5 - k ) )        //如果该位有小数,则显示应该加上一个'.'
                         v_74hc164WriteData_f( DisplayTable[ Buffer[ k ] ] | 0x80 ) ;
else
                         v_74hc164WriteData_f( DisplayTable[ Buffer[ k ] ] ) ;
k-- ;       
}
if( zf_lock )        //判断正负,如果为负值则显示'-'号,否则显示空
{          
if( ( zf == 0 )  )
{
v_74hc164WriteData_f( 0x08 ) ;
}
else
{
v_74hc164WriteData_f( 0x00 ) ;
}
zf_lock = 0 ;
}
for( ; j > 0 ; j-- )    //多余的位显示空
{
v_74hc164WriteData_f( 0x00 ) ;
}
v_74hc164WriteData_f( Seg[ 1 ] ) ;           //显示第一个自定义编码的字符
v_74hc164WriteData_f( Seg[ 0 ] ) ;           //显示第二个自定义编码的字符
}

我们要想显示数值,直接调用这个函数v_74hc164DisplayNumber_f( uint8 data *Seg, uint8 Dot, int16 Dat )就可以了。看看显示效果。

(原文件名:显示效果1.jpg)

(原文件名:显示效果2.jpg)

其中PC是我们显示的自定义字形。小数点的位置是用程序固定打在某处的。以满足某些情况下的特殊要求。

(原文件名:显示效果3.jpg)


仁者见仁,智者见智。相信你弄懂了它的用法,就可以把它灵活的应用到你设计中去了。


下面让我们来认识另外一种芯片。74HC165。看它的名字就知道它和74HC164有那么一点点关系了。我们之前已经知道了74HC164是串行输入并行输出的移位寄存器。而74HC165恰好相反。它是并行输入,串行输出。用来作单片机系统的输入部分的扩展是一个不错的选择。和上面一样。我们在使用之前需要对这个芯片有一个明确的了解。了解一个芯片最好的办法就是看它的DATASHEET了。有一份DATASHEET在手,自己先看看。对它的引脚及功能特性要了解。如果觉得不够的话,还可以上网去搜索一下用过这款芯片的人的使用经验。呵呵,那样可以使你少走不少弯路哦。

(原文件名:74HC165引脚图.jpg)
上面这幅图就是74HC165的引脚图了。其中A~H就是8位并行数据的输入端。Qh 和/Qh 是串行数据的输出端。为什么有两个输出端呢。它们是互补输出的。也就是说一个输出1时另外一个输出的是0,以满足某些应用场合的特殊要求。SER是串行数据的输入端。这其实为我们的将多块芯片级连起来创造了很好的条件。而事实上最后我们确实也是通过这个引脚将多块74HC165级联起来使用。CLK是时钟端。SH/LD是移位和锁存并行数据端。具体的介绍大家可以去看DATASHEET。

(原文件名:74HC165功能描述.jpg)
上面这幅图就是功能描述了。相信大家对这段英文都不是很陌生。如果你看不懂的话可以动手去查字典。想学电子的人不会看英文的资料可不行。这里希望大家不要再抱有什么幻想。基本上看原文的DATASHEET是最好的选择。如果你懒,可以去看别人的翻译版本。但是如果没有翻译的版本呢?
     OK,下面依旧是以一个实例来说明它的用法。我们用两块74HC165级联起来组成的有16个按键的键盘为例来讲解。
下面是电路连接图

(原文件名:74HC165键盘.jpg)
图中J2,J3是两个上拉电阻(8位一体的排阻,阻值取4.7k左右就可以了)。
下面来让我们一起去驱动它吧。





//下面是引脚的连接以及相关必要的宏定义
sbit io_74hc165_SH_LD = P2^0 ;
sbit io_74hc165_CLK         = P2^1 ;
sbit io_74hc165_SDA         = P2^2 ;

#define MAX_NUM_74HC165         2
#define NOKEY                                 0x00
#define KEY_WAIT                                0       
#define KEY_PRESS                                   1
#define KEY_CONFIRM                     2
#define KEY_WAIT_REALSE                  3




static u8_Read74hc165_f( void )
{
        uint8 i, j  ;
        uint8 KeyAddress[ MAX_NUM_74HC165 ] ;
        uint8 ReadReturn ;
         io_74hc165_SH_LD = 0 ;                    //锁存并行数据开始
         io_74hc165_SDA = 1 ;                  //准备读串行数据(也起到延时作用)
         io_74hc165_SH_LD = 1 ;                  //锁存并行数据结束
         for( j = 0 ; j < MAX_NUM_74HC165 ; j++ )
         {       
                   for( i = 8 ; i >= 1 ; i-- )
                 {                  
                         io_74hc165_CLK = 0 ;                                          //时钟拉低                                                 
                        if( io_74hc165_SDA == 0 )break ;                //有键按下,数据为1
                        io_74hc165_CLK = 1 ;                                         //时钟拉高       
                 }
                 KeyAddress[ j ] = i ;                //有键压下,则i的取值在1~8之间,无键压下,i = 0  
         }
         for( j = 0 ; j < MAX_NUM_74HC165 ; j++ )
         {
                 if( KeyAddress[ j ] == 0 ) ReadReturn = 0x00 ;
                else
                {
                        ReadReturn = KeyAddress[ j ] + j * 8 ;
                        break ;
                }
         }
        return ReadReturn ;

}

下面的这个函数就是读键盘的函数了。
uint8 u8_ReadKeyboard74hc165_f( void )
{
        static uint8 KeyState = KEY_WAIT ;
        uint8 KeyTemp = NOKEY, KeyValue = NOKEY ;
        KeyTemp = u8_Read74hc165_f() ;

        switch( KeyState )
        {
                case KEY_WAIT :         if( KeyTemp == NOKEY ) KeyState = KEY_WAIT ;
                                                        else KeyState = KEY_PRESS ; break ;

                case KEY_PRESS :         if( KeyTemp == NOKEY ) KeyState = KEY_WAIT ;
                                                        else                                   KeyState = KEY_CONFIRM ; break ;

                case KEY_CONFIRM :         if( KeyTemp == NOKEY ) KeyState = KEY_WAIT ;
                                                        else                                  
                                                        {       
                                                                KeyState = KEY_WAIT_REALSE ;
                                                                KeyValue = KeyTemp ;                 
                                                        } break ;
case KEY_WAIT_REALSE :if( KeyTemp != NOKEY ) KeyState = KEY_WAIT_REALSE ;
                                                        else                                  
                                                        {
                                                                KeyState = KEY_WAIT ;   
                                                        }break ;         
                default : break ;
        }
        return KeyValue ;
}

我们在主函数中调用u8_ReadKeyboard74hc165_f( ) 就可以得到键值了。

(原文件名:键盘显示键值1.jpg)

上图我用74HC164级联的数码管来显示74HC165键盘的键值的情况。当按第二个键时显示的是2.

(原文件名:键盘显示键值2.jpg)
到此单片机的IO口的扩展告一段落。希望这两个小例子能够给你一点启发。能够给你在以后的学习及设计中带来一点灵感。

文中错误之处还望各位高手不吝指正。

阿莫论坛20周年了!感谢大家的支持与爱护!!

曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……

出0入0汤圆

 楼主| 发表于 2009-4-7 21:42:27 | 显示全部楼层
不是吧~~看都没人看~~

出0入0汤圆

发表于 2009-4-7 22:13:32 | 显示全部楼层
不错。很多用595扩展I/O

出0入0汤圆

发表于 2009-4-7 22:35:56 | 显示全部楼层
楼主 谢谢,不错

出0入0汤圆

发表于 2009-4-8 10:16:05 | 显示全部楼层
谢谢楼主的分享   正学习呢

出0入0汤圆

发表于 2009-5-10 20:33:45 | 显示全部楼层
很好,学习了!

出110入12汤圆

发表于 2009-5-31 18:20:37 | 显示全部楼层
谢谢,学习

出0入0汤圆

发表于 2009-7-10 20:13:47 | 显示全部楼层
不错 谢谢楼主~~

出0入0汤圆

发表于 2010-4-22 22:56:19 | 显示全部楼层
mark一下,可能会用到,谢谢楼主

出0入0汤圆

发表于 2010-4-24 18:51:05 | 显示全部楼层
楼主蛮辛苦的

出0入0汤圆

发表于 2010-4-24 23:14:45 | 显示全部楼层
thank you.
here we are...

出0入0汤圆

发表于 2010-4-27 12:24:05 | 显示全部楼层
学习了
回复【10楼】to_destiny 笑谈
-----------------------------------------------------------------------

学习了

出0入0汤圆

发表于 2010-5-23 18:54:08 | 显示全部楼层
学习了……

出0入0汤圆

发表于 2010-5-23 23:49:39 | 显示全部楼层
回复【12楼】zlulu2006
学习了……
-----------------------------------------------------------------------

学习了

出0入0汤圆

发表于 2010-5-24 00:07:00 | 显示全部楼层
好东西,收藏了

出0入0汤圆

发表于 2010-5-25 10:59:54 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-8-9 14:16:47 | 显示全部楼层
很好啊 。不像书上写的扩展要8255

出0入0汤圆

发表于 2010-8-9 15:07:21 | 显示全部楼层
有点看不懂啊 。新手啊
能不能再讲详细点,怎么驱动的啊?

出0入0汤圆

发表于 2010-8-9 21:54:00 | 显示全部楼层
获益匪浅啊!

出0入0汤圆

发表于 2010-8-30 17:28:54 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-8-30 20:56:22 | 显示全部楼层
刚开始学,谢谢啊

出0入0汤圆

发表于 2010-8-30 21:32:14 | 显示全部楼层
多谢

出0入0汤圆

发表于 2010-10-14 10:08:10 | 显示全部楼层
最近我也用用165做了个按键扩展.
用3片165级联扫描25个按键.杯具的是,我的按键上没有接上拉.结果...

有没有办法能不接上拉也能扫描出按键来呢?

出0入0汤圆

发表于 2010-10-15 01:05:45 | 显示全部楼层
谢谢分享!!

出0入0汤圆

发表于 2010-10-15 03:03:55 | 显示全部楼层
"学习单片机快1年了"

that is a very inefficient way of using the chip.

1) the best approach is to use 8 data pins (for 7 segments + dp) for all LEDs, and the turn each led on from one pin on hc164. so if you were to drive 16 leds, you need 2 hc164, driven by two pins (one for serial data + 1 for clock), + 8 pins.

2) if you cannot afford to have a 8 data pins, you can use another hc164, driven by two pins (again, one for serial data + 1 for clock) that deliver segment data to all LEDs, and another two pins to drive all the leds. so for a total of 16 leds, you need four data lines, and 3 leds: one for segment data, and two for 16 leds.

出0入0汤圆

发表于 2010-10-15 06:04:08 | 显示全部楼层
here is a quick demo of the concept.

it uses two hc164 to drive up to 8 7-segment leds (3 leds in our example).

=========code================
//using hc164 to drive common anode 7-segment leds
//each led is turned on by hc164


#include <regx51.h>
#include "gpio.h"
#include "delay.h"

//hardware configuration
#define LED_PORT                P2
#define LED_DDR                        P2
#define LED_PINs                0xff        //led data pins on P2. data pins from all 7-segment leds tied together
//pin mapping:
//P0 -> A,
//P1 -> B,
//P2 -> C,
//P3 -> D,
//P4 -> E,
//P5 -> F,
//P6 -> G
//P7 -> DP (not used)

#define HC164_PORT                P3
#define HC164_DDR                P3
#define HC164_SDI                (1<<3)        //hc164 serial data in
#define HC164_CLK                (1<<0)        //hc164 clock
#define HC164_MAX                2                //number of HC164 daisy-chained. each HC164 drives 8 7-segment leds
//end hardware configuration

#define LED_ON(val)                LED_PORT =~(val)        //common anode, 0-> led on, 1-> led off
#define LED_OFF()                LED_ON(0x00)        //turn off all led segments

#define HC164_STROBE()        {IO_SET(HC164_PORT, HC164_CLK); IO_CLR(HC164_PORT, HC164_CLK);}        //strobe the clk

const unsigned char code ascii_table[]= {
        0x3f,                                //0
        0x06,                                //1
        0x5b,                                //2
        0x4f,                                //3
        0x66,                                //4
        0x6d,                                //5
        0x7d,                                //6
        0x07,                                //7
        0x7f,                                //8
        0x6f                                //9
        };

//for faster switching, consider using delay_us()
//this allows user to specify display duration for individual digits
const unsigned char code hc164_delay[8*HC164_MAX] = {        //delay for each bit / led
        10,                                        //10ms delay for led0
        10,
        0,
        0,
        0,
        0,
        0,
        0,                                        //no delay if no led attached to that bit
        0,                                        //2nd hc164 starts here
        0,
        0,
        10,
        0,
        0,
        0,
        0
        };

unsigned char vRAM[8*HC164_MAX]="9100000000020000";        //to be displayed


void mcu_init(void) {
        LED_OFF();                                                        //turn off all leds
        IO_OUT(LED_DDR, LED_PINs);                        //led_pins as output

        IO_CLR(HC164_PORT, HC164_SDI | HC164_CLK);        //_sdi/clk pins low
        IO_OUT(HC164_DDR, HC164_SDI | HC164_CLK);        //sdi/clk as output
}

int main(void) {
        unsigned char i;
        mcu_init();                                                        //reset the mcu
        while (1) {
                LED_OFF();                                                //turn leds off
                IO_SET(HC164_PORT, HC164_SDI);        //send the initial 1
                for (i=0; i<8*HC164_MAX; i++) {
                        HC164_STROBE();                                //strobe the clk
                        //display char
                        LED_ON(ascii_table[vRAM-'0']);
                        delay_ms(hc164_delay);        //some delay to allow the leds to be on
                        LED_OFF();                                        //turn off leds before shifting in the next digit
                        IO_CLR(HC164_PORT, HC164_SDI);        //send the next bit
                }

        }
}
==========end===============

出0入0汤圆

发表于 2010-10-15 06:06:54 | 显示全部楼层
here is the simulation of it.


(原文件名:20. 7-segment HC164.PNG)

basically, the shift registers are only used to turn on individual 7-segment leds. the display data is put on P2 for all LEDs but since only one led is on at any given time, no cross-display of data on the wrong led.

the code can be easily modified to display more leds, or to drive led matrix - you just need to treat each column in a led matrix as one 7-segment led here.

出0入0汤圆

发表于 2010-10-15 06:08:24 | 显示全部楼层
this particular piece of code utilizes all of LED_PORT (P2 in this case) to display data. you obviously can use a shift register to do that, saving 8-2 pins.

all you need to change in that case is the LED_ON() macro, and the rest of the code remains the same.

obviously, this code is very portable to other mcus.

出0入0汤圆

发表于 2010-10-15 19:57:27 | 显示全部楼层
学习了,谢谢楼主啊。。。正在学习

出0入0汤圆

发表于 2010-10-18 17:48:47 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-12-2 16:28:51 | 显示全部楼层
楼主原来我看你的思路没明白
今天我明白了,但是我发现你把简单的东西讲的太复杂了。
其实就是串行中断的0方式,用作同步移位寄存器使用
也就是串行输入,并行输出,(需要有串入并出的移位寄存器如CD4049,74LS164,74HC164等)
或者是并行输入,串行输出。
需要注意的是数据是低位在前,高位在后。

出0入0汤圆

发表于 2010-12-2 16:55:08 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-1-24 11:00:58 | 显示全部楼层
状态机用的好啊
温故而知新啊
又看了一遍
还的慢慢消化

出0入0汤圆

发表于 2011-3-31 23:08:21 | 显示全部楼层
回复【楼主位】jchqxl 红金龙吸味
-----------------------------------------------------------------------

菜鸟飘过,明天继续学习

出0入0汤圆

发表于 2012-4-5 16:50:23 | 显示全部楼层
mark    呵呵
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-8-27 01:14

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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