• msp430G2553学习纲领文件


    msp

    一:端口配置
       1:P1DIR   设置为1,相应管脚为输出。设置为0.相应管脚                              为输入状态。
       2:P1IE      设置为1,相应管脚具有中断功能。设置为0,                            相应管脚没有中断功能。
       3:P1IES   设置为1,选择下降沿触发方式,设置为0,选                            择上升沿触发方式。
       4:P1IFG    P1端口的中断标志寄存器,如果P1端口当某                            个管脚设置成中断管脚,当有中断触发时,想应比特为1 ;                              如果没有中断触发,相应比特为0.
    5: P1IN   P1端口输入寄存器,在输入模式下,读取该寄                            存器相应管脚上的数据。
    6: P1OUT   P1端口的输出去寄存器,在输出模式下,如                            果该寄存器相应比特设置为1时,相应管脚输出高电平;                            如果该寄存器相应比特为0时,相应管脚输出低电平。
    7: P1SEL寄存器  P1端口功能选择寄存器,该寄存器主要                            控制P1端口的I/O管脚作为一般I/O还是外围模块的功能                            端口,该寄存器的相应比特为1时候,相应管脚为外围功能                           模块,当该寄存器为0时,相应管脚为一般I/O管脚。
     
     
    二  LaunchPad 写程序的必要头文件和格式:
    /*===================================================
    #include"msp430g2553.h"
    Void main()
    {
        WDTCTL=WDTPW+WDTHOLD;//关闭看门狗。
        //WDTPW 是看门狗的密码,写错了会导致系统复位。
       **(程序)
    }
    =====================================================*/
     
     
    三  点亮LED
      仅仅是对IO 口的输入输出操作。与51 很不相同。
    P1DIR|=BIT0;//设置P1.0 为输出方向。===P1DIR|=0x01;
    //{P1DIR=BIT0;是设置P1.0 为输出,其他全部为输入方向。}
    //注意:LaunchPad 中很多操作是与,或,非等操作组成,时刻注意。
    P1OUT|=BIT0;//这条指令就是设置P1.0 输出为高电平。
    这样,就点亮了LED(接在P1.0 上的LED);
    具体程序:
    #include"msp430g2553.h"
    Void main()
    {
    WDTCTL=WDTPW+WDTHOLD;
    P1DIR|=BIT0;
    P1OUT|=BIT0;
    While(1);
    }
     
    四: 闪烁LED
    LaunchPad 上面自带有2 个LED,一个接在P1.0 上,一个接在P1.6 上。
    我们用2 个交替闪烁。
    #include"msp430g2553.h"
    Void main();第一个字母大写
    {
    WDTCTL=WDTPW+WDTHOLD;
    P1DIR|=BIT0+BIT6;//设置P1.0 和P1.6 为输出
    P1OUT|=BIT0;//线让LED0 亮。
    While(1)
    {
    Unsigned int i=50000;
    While(i--);
    P1OUT^=0x41;//对P1.0 和P1.6 取反,所以LED0 和LED1 会交替闪烁。
    }
    }
    五:中断系统
    LaunchPad 的中断系统功能相当强大,51 只有5 个中断源,2 个定时,2 个外部,
    一个串行口。但是LaunchPad 的中断源几乎是所有的引脚和所用的定时器。
     
    在这里,最重要的就是中断向量的判断了。
    定时器一般都是:
    vector=TIMER0_A0_VECTOR
    vector=TIMER0_A1_VECTOR
    vector=TIMER1_A0_VECTOR
    vector=TIMER1_A1_VECTOR
    引脚中断的向量:
    vector=PORT1_VECTOR;    P1 口的中断向量。
    判断是哪个引脚的话,有2 种办法:
    举例子:P1.3 和P1.4 都是中断的输入引脚。现在进了中断,我如何判断是那个引
    脚引起的呢?
    第一种方法:
    vector=PORT1_VECTOR
    __interruput void Port1 (void)
    {
    If(P1IFG&BIT3);判断的是P1.3 产生的中断。
    {
    ;要执行的函数。
    P1IFG=0x00;//清0 中断标志位。
    }
    If(P1IFG&BIT4)
    {
    ;P1.4 产生的中端,执行相应的函数。
    P1IFG=0x00;
    }
    }
    第二种方法:
    vector=PORT1_VECTOR
    __interruput void Port1 (void)
    {
    P1IFG&=BIT3+BIT4;//因为只用到了P1.0 和P1.4,其他的中断标志全部清零。
    (或者:P1IFG=P1IFG&0x18)
    Switch(P1IFG)
    {
    Case 0x08: vector=3;break; //P1.3 产生的中断
    Case 0x10:vector=4;break;//P1.4 产生的中断
    }
    }
     
     
    切记:进入中断函数后要做的第一件事是,清除中断标志。
    例程:
    #include<msp430g2553.h>
    Void main(void)
    {
      WDTCTL=WDTPW+WDTHOLD;
      P1DIR|=BIT0;  //                            设置P1.0输出
      P1IES |=BIT3;  //     设置从高到底跳变触发
      P1IFG&=~BIT3//                清除中断标志位
      P1IE |=BIT3   //                   使P1.3能中断
      _BIS_SR(LPM_bits+GIE);//              启动LMP4节能模式
    }
    #proagma vector=PORT1_VECTOR
    _interrupt  void Port_1(void)
    {
       If(P1IFG&BIT3)
      {
    P1OUT^=BIT0;   
    P1IFG&=~BIT3;
      }
    }
    程序一开始
    1   WDTCTL=WDTPW+WDTHOLD;  HOLD住看门狗
    2   P1DIR|=BIT0;将P1.0              设置为输出口。lunchpad上的P1.O接有一个LED.
     
    3. 接下来到P1IES |= BIT3; 在上一节中已经介绍了,P1IES 寄存器是中断沿选择寄存器。这里是选择位下降沿触发中断。
     
    4. P1IFG &= ~BIT3; 为清除中断标志,保证程序正常运行,当然此句可以不写,这里只是做为例子
     
    5. P1IE |= BIT3; 在上一节中已经介绍了,P1IE寄存器是使能中断事件发生的寄存器。
     
    • _BIS_SR(LPM4_bits + GIE);这里使程序进入最低功耗(LPM4)状态。靠中断来触发唤醒CPU,在文章开始已经介绍有,假如在中断函数中没有写有退出低功耗状态的指令,程序会在进入低功耗的下一句中卡死,不再运行下去。另外_BIS_SR(GIE); 为打开总中断的意思。
     
    • 接下来到中断函数的编写。以此为例,详细介绍中断函数的编写。如上所示,中断函数编写的规则为
    #pragma vector= 中断向量源
    __interrupt void 函数名(void)
     
    摁住“Ctrl  +  左键”点击PORT1_VECTOR即可查看到所有的“中断向量”
    在上面的中断向量中,加黑的位中断向量源,写入中断函数编写语法规则里面即可。而函数名则可以任意编写。比如我要编写一个有定时器1,CCR0寄存器溢出产生的中断,则可以这样编写
    #pragma vector= TIMER1_A0_VECTOR
    __interrupt void T1A0Int(void)
    {
    //程序代码。。。
    }
    假如是多IO输入中断,则如下所写。
    vector=PORT1_VECTOR
    __interrupt void Port1()
    {
     
    //以下为参考处理程序,不使用的端口应当删除其对于中断源的判断。
    if((P1IFG&BIT0) == BIT0)
    {
    P2OUT&=~BIT0; //处理P1IN.0中断
    P1IFG &= ~BIT0; //清除中断标志
    //以下填充用户代码
    }
    else if((P1IFG&BIT1) ==BIT1)
    {
    P2OUT&=~BIT1; //处理P1IN.1中断
    P1IFG &= ~BIT1; //清除中断标志
    //以下填充用户代码
    }
    else if((P1IFG&BIT2) ==BIT2)
    {
    P2OUT&=~BIT2; //处理P1IN.2中断
    P1IFG &= ~BIT2; //清除中断标志
    //以下填充用户代码
    }
    else if((P1IFG&BIT3) ==BIT3)
    {
    //处理P1IN.3中断
    P1IFG &= ~BIT3; //清除中断标志
    //以下填充用户代码
    }
    else if((P1IFG&BIT4) ==BIT4)
    {
    P2OUT&=~BIT4; //处理P1IN.4中断
    P1IFG &= ~BIT4; //清除中断标志
    //以下填充用户代码
    }
    else if((P1IFG&BIT5) ==BIT5)
    {
    //处理P1IN.5中断
    P1IFG &= ~BIT5; //清除中断标志
    //以下填充用户代码
    }
    else if((P1IFG&BIT6) ==BIT6)
    {
    //处理P1IN.6中断
    P1IFG &= ~BIT6; //清除中断标志
    //以下填充用户代码
    }
    else
    {
    //处理P1IN.7中断
    P1IFG &= ~BIT7; //清除中断标志
    //以下填充用户代码
    }
    LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽
    }
    六   定时器模块
     
    文先,介绍几个英文缩写的意思以及一些注意的地方。
    1. Timer0/1 定时器0/1,在User's Guide中用的是TimerA/B,所指的也是Timer0/1 。G2553Datasheet中用的是Timer0/1 ,本文以G2553Datasheet为准。全文以Timer0为例,Timer1类同。
     
     
    2. TAxR(x = 0/1)定时器x对应的计数器,这是一个只读寄存器。硬件自动驱动计数。
     
     
    • EQUyy = 0/1/2)计数事件发生寄存器,当TAxR  =  TAxCCRyEQUy1
     
    定时器简介
    MSPG2553共有两个定时器,Timer0、Timer1,他们都是十六位的定时、计数器,内含三个捕获、比较寄存器。两个定时器均支持多个捕获、PWM输出、间歇性计时,定时器包含多个中断源,可以是计数溢出中断、捕获中断等等。
    定时器包含:
    同步十六位定时,计数器运行模式。
    时钟源从MCLK、SMCLK、ACLK任意选择
    三个比较,捕获寄存器。
    中断向量寄存器能快速解码的所有定时器中断
     
    Timer0组成框图
    下面简要介绍一下该硬件框图的意思,从左上角看,首先是一个时钟源选择寄存器TASSELx,通过该寄存器选择定时器的时钟源,选择了时钟源后有一个分频器Divider,相应的设置寄存器是IDx,再过来就到一个定时器的核心部分,一个16位的定时器TAR。其右侧有一个定时器的计数模块,MCx寄存器用来设置计数模式。接下来,TAR正下方有三个横线,右侧标有CCR0、CCR1、CCR2,意思是CCR1、CCR0的框图和下方CCR2的框图是一样的。此处省略不写。在CCR中,左上角为一个捕获源选择寄存器。可以从CCI2A、CCI2B、GND或者VCC选择捕获源,选择捕获源后有一个选择捕获模式寄存器Capture Mode,然后过来有一个捕获溢出状态寄存器COV,SCS同步/异步捕获模式选择位,然后连接到捕获比较寄存器。下方为模式选择寄存器,具体设置可以查看相应的寄存器设置。
    这里仅是大概介绍一下Timer0的寄存器,具体的设置使用还看参考相应的寄存器并结合例程慢慢学习理解。
     
     
    定时器运行方式
    下面简要重点介绍定时器计数模块的四种模式以及7种输出模式。
    Timer0有一个在不断计数的只读寄存器TA0R。计数器的计数模式共有四种,
     
    停止模式(Stop mode)、连续增计数模式(Up mode)、递增计数模式(continuous mode)、增减计数模式(Up/down mode)。由上图可知,这四种模式可以通过MCx寄存器进行设置。
    以上四种模式可以由下图可以很好理解。
     
     
    1. Stop模式计数器不工作。
    2. 连续计数模式为计数器从零开始连续增计数一直到0xFFFF即65535,然后又重新从零开始计数。
    3. 递增计数模式与连续计数模式仅有一点点区别,递增模式为计数器连续增加到TA0CCR0(即图中的CCR0)中的值后又重新从零开始计数。TA0CCR0的值时可以在程序中直接赋值的。
    4. 递增递减模式也很好理解,计数器从零开始计数到CCR0后又自动减数,到零后又增计数,就像三角波一样。
    每一个捕获比较模块都有一个输出单元,这个输出单元专门用来产生以下如PWM的波形信号,每一个输出单元都可以通过配置OUTMOD寄存器的值来设定八种信号输出模式,
     
     
     
    接下来再介绍一下定时器的捕获/比较功能,具体应查看技术手册。
    捕获模式
    捕获模式可以用来速度计算或时间测量.CCIxA ,CCIxB的捕获源可以连接到外部引脚或者内部信号,可以设定CCIDx,CMx,位让寄存器捕获上升,下降,或者两个信号的边缘.输入信号的电平可以通过CCI位的读取.
    当设置寄存器CAP=1时,使能捕获模块.
    比较模式
    比较模式设置CAP = 0的情况向,比较模式用于产生PWM信号。或者在指定时间里输出终端信号,当TAxR计数到TACCRx时
     
    建立起CCIFG位
     
    中断事件发生标志位EQUx=1
     
    EQUx的隐含改变将影响输出模式
    输入信号CCI被锁上SCCI
     
     
     
     
     
    • 增计数模式下的输出
       

     
    2. 递增计数模式下的输出
     
     
    *
     
    • 第增/第减计数模式下的输出
     
     
    **************************************************************** /
      / ******************************************************************
       *                    TACTL寄存器,Timer_A 控制寄存器
       * TASSEL_x:TA时钟源选择寄存器
       *     00 TACLK
       *     01 ACLK
       *     10 SMCLK
       *     11 INCLK
       * IDx:     时钟源分频寄存器。为输入时钟分频选择
       *       00  /1
       *       01  /2
       *       10  /4
       *       11  /8
       *  * ************************************************************** /
       *              定时计数模块 =四中模式+7种输出方式
    / *****************************************************************
       *
       * MCx:    计数模式寄存器   模式控制,当TA不用于节省功耗时,将MCx=00h
       *     00  停止模式:定时器停止
       *     01  增模式  :定时器计数到TACCR0
       *     10  连续模式:定时器计数到0FFFFh
       *     11  增减模式:定时器计数到TACCR0 然后减到000h
       *
       * TACLR:  定时器清零。置位时会复位TAR,时钟分频和计数方向。
       *     TACLR位会自动复位并读出值为零。
       *
       * TAIE:   TA中断允许。改为允许TAIFG中断请求
       *     0 中断禁止
       *     1 中断允许
       * TAIFG:  TA中断标志位
       *     0 无中断挂起
       *     1 中断挂起
       * ************************************************************** /
    / *****************************************************************
    *
    *
       *             TACCTLx,捕获比较控制寄存器
       * CMx:  捕获模式
       *   00 不捕获
       *   01 上升沿捕获
       *   10 下降沿捕获
       *   11 上升和下降同时捕
       * CCISx: 捕获比较选择,改为选择TACCRx的输入信号
       *   00 CCIxA
       *   01 CCIxB
       *   10 GND
       *   11 VCC
       * SCS:  同步捕获源,改为用于将捕获通信和同步时钟
       *   0 异步捕获
       *   1 同步捕获
       * SCCI:同步的捕获/比较输入,所选择的输入信号由EQUx信号所存,
       *   并可通过该位读取
       * CAP:  捕获模式
       *   0 比较模式
       *   1 捕获模式
     
       *OUTMODx:输出模式位,对TACCR0无效
       *         000 OUT 位的值
       *         001 置位
       *         010 翻转/复位
       *         011 复位/复位
       *         100 翻转
       *         101 复位
       *         110 翻转/置位
       *         111 复位/置位
       * CCIE: 捕获比较中断允许位
       *   0 中断禁止
       *   1 中断允许
       * CCI:  捕获比较输入
       * OUT:  对于输出模式0,该位直接控制输出状态
       * COV:  捕获溢出位。该位表示一个捕获溢出发生,由软件复位
       * CCIFG:捕获比较中断标志位
       *   0 没有中断挂起
       *   1 有中断挂起
       * ***************************************************************/
    #include <msp430g2553.h>
     
    unsigned  int A=10,  B=20 ;  void main (void)
    {
      WDTCTL = WDTPW + WDTHOLD;
     
      TACTL|=TASSEL_2+TACLR+MC_1+ID_3;  // SMCLK时钟 ;定时器清零;增模式   8分频
      CCTL0=CCIE;                       //捕获中断允许
      CCR0=B;                         //TACCR0 装载值
      CCTL1=OUTMOD_7;                   //输出模式复位
      CCR1=A;
    // BCSCTL1
      P1DIR=BIT6;
      P1SEL=BIT6;
     
    // _EINT();                          //使能所有中断
      while(1);
    }
     
    /*#pragma vector=TIMER0_A0_VECTOR
    __interrupt void  ta0_isr(void)
    {
      unsigned  int i;
      for(i=0;i<num;i++)
        pwm=i;
    }*/
     
    
    定时器中断
    这里以定时器0为例,定时器1同。
    定时器的中断可有定时器TA0CCR0溢出产生,也可由TA0CCRx(x =1/2)溢出产生、捕获/比较事件发生引起的中断,前者有一个专用的中断向量,TIMER0_A0_VECTOR,而后者用的TIMER0_A1_VECTOR,至于是哪一个中断时间发生,还要根据标志位来判断。
     
     
     
    下面以官方例程LaunchPad Lab2为例介绍定时器A的操作。
     
    例一
    #include <msp430g2553.h>
    void main(void)
    {
    WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
    if (CALBC1_1MHZ ==0xFF || CALDCO_1MHZ == 0xFF)
    {
    while(1); // If calibration constants erased, trap CPU!!
    }
    BCSCTL1 = CALBC1_1MHZ; // Set range
    DCOCTL = CALDCO_1MHZ; // Set DCO step + modulation
    BCSCTL3 |= LFXT1S_2; // LFXT1 = VLO
    P1DIR = 0x40; // P1.6 output (green LED)
    P1OUT = 0; // LED off
    IFG1 &= ~OFIFG; // Clear OSCFault flag
    BCSCTL1 |= DIVA_3; // ACLK = VLO/8
    BCSCTL2 |= SELM_3 + DIVM_3 + DIVS_3; // MCLK = DCO/8, SMCLK = DCO/8
    // Configure TimerA
    TACTL = TASSEL_1 + MC_1 + TAIE; // Source: ACLK, UP mode
    CCR0 = 5100; //Timer count 5100
    CCR1 = 2000; //Timer count 100
    CCTL0 = CCIE; //CCR0 interrupt enabled
    CCTL1 = CCIE; //CCR1 interrupt enabled
    _BIS_SR(GIE);
    for(;;);
    }
    // Timer A0 interrupt service routine
    #pragma vector=TIMER0_A0_VECTOR
    __interrupt void Timer_A0 (void)
    {
    P1OUT |= BIT6; // P1.6 output High
    }
    // Timer A1 Interrupt Vector (TA0IV) handler
    #pragma vector=TIMER0_A1_VECTOR
    __interrupt void Timer_A1(void)
    {
    switch( TA0IV )
    {
    case 2: P1OUT &= ~BIT6; // P1.6 output Low
    break;
    case 10:
    break;
    }
    }
    程序一开始关闭看门狗,if语句作为时钟校准的范例,可以删去。BCSCTL3 |= LFXT1S_2; 选择超低频时钟源。然后设定输出口,清除中断标志,时钟源分频设定,接下里组建定时器A,即定时器0,在详细介绍代码之前,首先看头文件关于定时器相关寄存器的设定。
     
    例二
      下面举一个无需中断服务函数、硬件自动实现产生两路PWM的例子。
    代码很简单,初始化一下即可。
    #include<msp430g2553.h>
    void Set_TimerB_PWM(void)
    {
    //使用系统初始化时的默认时钟1MHz,定时器B专门用于产生PWM 波形。
    TA1CTL = TASSEL_2 + MC_1 + TACLR;//使用系统次主机SMCKL、增计数模式、清楚定时器B时钟
    TA1CCR0 = 5001 - 1;//在1MHz的主频率下,1*10^6/5000=200Hz的中断频率
    TA1CCR1 = 3751 - 2;//当寄存器TACCR1的值小于3750时,输出口保持高电平。5000*3/4=3750,此路产生3:1的PWM波形。
    TA1CCR2 = 1251 - 2;//当寄存器TACCR1的值小于1250时,输出口保持高电平。5000*1/4=1250,产生1:3的PWM波
    TA1CCTL1 = OUTMOD_7;//输出模式7,计数器计数到5000计数器自动置位,无需中断服务子函数。
    TA1CCTL2 = OUTMOD_7;//输出模式7,计数器计数到5000计数器自动置位,无需中断服务子函数。
    P2SEL |= BIT1 + BIT5;//只有这两路可选(为什么是这两路?在G2553Datasheet中有特别指明)。做第二功能使用(PWM输出)
    P1DIR |= BIT6; //电机控制口CTL//这里与本例无关
    P1OUT &= ~BIT6; //start with 0 -->IN2,4为1,灭//这里与本例无关
    }
     
      初始化时钟后直接调用该函数即可。
    我这里使用的是定时器B(即Timer1)。详细的介绍见以上备注。  
     
    七  时钟配置
     
      时钟源:

         外部晶体振荡器
         超低频率振荡器(VLO)
         数字控制振荡器(DCO)

      时钟信号:


         ACLK :Auxiliary clock.辅助时钟。
         MCLK :Master clock主时钟。
         SMCLK :Second Master clock次主机时钟。
     
    内部晶体振荡器产生时钟后经过DCOR、SCG0、RSELx、DCO等各个寄存器为MCLK、SMCLK提供时钟源
     
    内部时钟还有一个超低频率内置晶体振荡器(VLO)在上图的最上方。可作为低频时钟源。
     
    另外一个部分是系统的外部时钟,外部晶振经过LFXT等各个寄存器设置后可以为MCLK、ACLK提供时钟源。
    上图中SELM、SELS为时钟源选择寄存器。
     
    上图中DIVA、DIVM、DIVS都是分频器,时钟源可以经过1/2/4/8分频后为CPU提供时钟,以降低功耗。
     
       



    ADC10的时钟部分框图



    Timer_A的结构框图
     
         Timer_A不能选择MCLK作为Timer_A的时钟


    CPU是处理器的核心部分,它使用的时钟始终是MCLK。
     
     
    上电后,系统默认使用的主系统时钟MCLK和子系统时钟SMCLK是同为DCOCLK产生的1MHz时钟,而辅助时钟ACLK则为内部VLOCLK产生的12KHz时钟
     
    MSP430低功耗模式
        单片机中,功耗最低的单片机要MSP430单片机,这是做手持设备最优选择,MSP430中,用到5种低功耗,LPM0,LPM1,LPM2,LPM3,LPM4,这五种低功耗各种解释如下 :
    LPM0:CPU停止工作,MCLK时钟停止,SMCLK、ACLK时钟还在工作。
    LPM1:CPU停止工作,MCLK时钟停止,在活动模式如果DCO没有作为MCLK和SMCLK时钟时,则直流发生器被禁止,否则就保持活动状态,SMCLK、ACLK时钟依然还在工作。
    LPM2:CPU停止工作,MCLK、SMCLK时钟停止工作,如果DCO没有作为MCLK、SMCLK,自动被禁止直流发生器保持有效,ACLK还处于工作中。
    LPM3:CPU停止工作,MCLK、SMCLK时钟停止工作,DCO时钟也停止工作,仅ACLK时钟还处于工作状态。
    LPM4:CPU停止工作,MCLK、SMCLK时钟停止工作,DCO时钟也停止工作,ACLK也停止工作。此时功耗最低。
    一般情况下,处理器进入低功耗模式后,由中断来唤醒,外部中断或内部中断。
    如果想进入低功耗1,则程序可以为:_BIS_SR(LPM1_bits + GIE);退出低功耗1,则程序可以为:LPM1_EXIT; 进入其他低功耗和退出低功耗一样。
    低功耗执行的一个过程:程序从main函数入口开始执行程序,当遇到进入低功耗程序时,如:_BIS_SR(LPM1_bits + GIE);此时相当于下面的程序处于停止状态不再执行,当有一个中断来到,则会进入中断处理程序,自动退出低功耗,如果在中断中没有没有退出低功耗,当中断服务程序执行完成后,又会重新进入低功耗。
  • 相关阅读:
    20145321《网络对抗技术》逆向与Bof基础
    20145321 《信息安全系统设计基础》课程总结
    20145319 《网络渗透》免考-恶意代码隐藏相关技术
    20145319 《网络渗透》免考—逆向脱壳技术
    20145319 《网络渗透》免考—病毒感染
    20145319 《网络渗透》免考—进程隐藏
    20145319 《网络渗透》免考—远程注入
    20145319 《网络渗透》免考—API拦截技术
    20145319 《网络渗透》免考—任务栏隐藏
    20145319 《计算机病毒》实践三
  • 原文地址:https://www.cnblogs.com/guochaoxxl/p/14129154.html
Copyright © 2020-2023  润新知