本文代码均来正点原子标准例程
声明:本文不是教学文章,可能也不适合初学者阅读
不知为什么,最近总蹦出有很多想法(可能是工作太闲了)一会想学这,一会想学那,这不,突然想复习一下STM32
了。
我好久以前就学过正点原子的课程,还买过一些开发板,但现在手上只有一个核心板了,就暂且凑合着用吧。
我是个喜欢制定计划的人,既然有了想法,那就得制定一个学习计划,估摸了一下,明天要上班,现在已经中午了,所以我只有一个下午加一个晚上的时间。哎?,工作之后发现学习的时间太少了,所以,既然是复习,那就不搞那么多弯弯绕绕了,直接针对正点原子的代码,通过代码学习STM32,那些啥原理的,通通给我抛到九霄云外去,以后有机会慢慢整。
话不多说,开始整活,先准备一下硬件:
就一个核心板,太寒酸了,还好有个屏幕撑撑场面。核心板的MCU型号为STM32F103ZET6。
有了硬件,就差代码了。
下图是正点原子的入门篇视频,我就按照这个顺序来学一遍(没有硬件支持的话,就只能跳过了,如OLED),寄存器版的就不考虑了,太麻烦。
虽然从教学视频的目录上看感觉实验多得有些吓人,但打开工程文件夹一看,嘿嘿,舒服了。?,这么一点,一下午就能搞完。
就在我窃喜的时候,看了一眼时间,时间不多了,抓紧了?。。。
光看主函数,觉得他和51一样简单,就是初始化和设置GPIO的高低,但实际上它们有本质区别,毕竟一个是8位,一个是32位。下面我们来一行行地分析吧。
int main(void){ delay_init(); //初始化延时函数 LED_Init(); //初始化LED端口 while(1) { GPIO_ResetBits(GPIOB,GPIO_Pin_5); //LED0对应引脚GPIOB.5拉低,亮 等同LED0=0; GPIO_SetBits(GPIOE,GPIO_Pin_5); //LED1对应引脚GPIOE.5拉高,灭 等同LED1=1; delay_ms(300); //延时300ms GPIO_SetBits(GPIOB,GPIO_Pin_5); //LED0对应引脚GPIOB.5拉高,灭 等同LED0=1; GPIO_ResetBits(GPIOE,GPIO_Pin_5); //LED1对应引脚GPIOE.5拉低,亮 等同LED1=0; delay_ms(300); //延时300ms }}
//初始化延迟函数//SYSTICK的时钟固定为HCLK时钟的1/8//SYSCLK:系统时钟void delay_init(){ SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8 fac_us=SystemCoreClock/8000000; //为系统时钟的1/8 fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数 }
第一个函数delay_init()
,不像51里直接用一个while实现延时,这里的延时由滴答定时器实现。Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
SysTick_CLKSourceConfig
是一个库函数,作用是配置滴答定时器的时钟源。
STM32 有5个时钟源:HSI、HSE、LSI、LSE、PLL。
①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。WDG
④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。RTC
⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。
倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。
STM32时钟源的知识还是挺多的,我自己现在也不是很清楚(得专门抽空学学),但我知道如果没有做配置,系统默认时钟频率是最高频率——本平台为72MHz
system_stm32f10x.c
里有以下内容,先记录一下,以后再分析。
#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)/* #define SYSCLK_FREQ_HSE HSE_VALUE */ #define SYSCLK_FREQ_24MHz 24000000#else/* #define SYSCLK_FREQ_HSE HSE_VALUE *//* #define SYSCLK_FREQ_24MHz 24000000 */ /* #define SYSCLK_FREQ_36MHz 36000000 *//* #define SYSCLK_FREQ_48MHz 48000000 *//* #define SYSCLK_FREQ_56MHz 56000000 */#define SYSCLK_FREQ_72MHz 72000000#endif
下面看看滴答定时器时钟源配置的库函数源码,可以看出它的时钟源只能为SysTick_CLKSource_HCLK_Div8
或SysTick_CLKSource_HCLK
,那么问题来了,什么是HCLK:
HCLK :AHB总线时钟,由系统时钟SYSCLK 分频得到,一般不分频,等于系统时钟
刚刚提到系统时钟为72M,所以SysTick_CLKSource_HCLK_Div8 就是72/8=9M。
/** * @brief Configures the SysTick clock source. * @param SysTick_CLKSource: specifies the SysTick clock source. * This parameter can be one of the following values: * @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source. * @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source. * @retval None */void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource){ /* Check the parameters */ assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource)); if (SysTick_CLKSource == SysTick_CLKSource_HCLK) { SysTick->CTRL |= SysTick_CLKSource_HCLK; } else { SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; }}
配置完了滴答定时器的时钟,delay_init函数内还有两行:
fac_us=SystemCoreClock/8000000; //为系统时钟的1/8 fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数
fac_us表示微秒的计时因子,即滴答计时器重载值为1*fac_us时,计时时间为1us(可以看后面的delay_us函数),fac_ms为fac_us的1000倍,自然就是1ms了。
之前我们提到
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
即滴答定时器定时器频率为9M(72/8),9M意味着定时器1秒计数9000000,那么1毫秒计数就为9000,1微秒为9。这代表什么?计9次数为1us,这个9就是1微秒的计数因子(fac_us),即fac_us(72000000/8000000=9)代表1us。n微秒则为n * fac_us。
终于到了本实验的主角——LED(GPIO)
//初始化PB5和PE5为输出口.并使能这两个口的时钟 //LED IO初始化void LED_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能PB,PE端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5 GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 端口配置, 推挽输出 GPIO_Init(GPIOE, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高 }
概括一下配置GPIO的步骤:
RCC_APB2PeriphClockCmd(...)
GPIO_Pin
GPIO_Mode
GPIO_Speed
GPIO_Init(..)
GPIO_SetBits(..)
或GPIO_ResetBits(...)
库函数注释中标明了时钟总线上的外设,GPIOB和GPIOE都在APB2总线上
/** * @brief Enables or disables the High Speed APB (APB2) peripheral clock. * @param RCC_APB2Periph: specifies the APB2 peripheral to gates its clock. * This parameter can be any combination of the following values: * @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB, * RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE, * RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1, * RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1, * RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3, * RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17, * RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11 * @param NewState: new state of the specified peripheral clock. * This parameter can be: ENABLE or DISABLE. * @retval None */void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState){ /* Check the parameters */ assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph)); assert_param(IS_FUNCTIONAL_STATE(NewState)); if (NewState != DISABLE) { RCC->APB2ENR |= RCC_APB2Periph; } else { RCC->APB2ENR &= ~RCC_APB2Periph; }}
如果想快速查到某外设的时钟总线,可以参考《STM32中文参考手册》存储器和总线架构章节:
下面是GPIO_InitTypeDef
结构体定义
/** * @brief GPIO Init structure definition */typedef struct{ uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define */ GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins. This parameter can be a value of @ref GPIOSpeed_TypeDef */ GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIOMode_TypeDef */}GPIO_InitTypeDef;
LED_init中,LED0
的GPIO_Pin
为GPIOB5,LED1
为GPIOE5;
模式都选择了推挽输出
推挽输出的最大特点是可以真正能真正的输出高电平和低电平,在两种电平下都具有驱动能力。
由LED的原理图可以知道它们为共阳极,所以默认要将IO拉高。
其他细节感兴趣的可以自己去研究?。
fac_ms刚刚在延时函数初始化中已经介绍,滴答定时器SysTick每计时fac_ms次,则表示1ms,所以nms*fac_ms表示计时nms毫秒。SysTick->LOAD
为定时器的重载值,SysTick->VAL
表示计数值,还要注意:滴答定时器是倒数计数的。SysTick->CTRL
为控制寄存器,第16位可以用来检测是否倒数到0。
void delay_ms(u16 nms){ u32 temp; SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit) SysTick->VAL =0x00; //清空计数器 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数 do { temp=SysTick->CTRL; }while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达 SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器 SysTick->VAL =0X00; //清空计数器 }
对于操作寄存器,经常要用到位操作,如SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk
中,SysTick_CTRL_ENABLE_Msk表示1,SysTick->CTRL|=1的作用是将CTRL寄存器的最低位置1,而不影响其他高19位(0或任何二进制数,都会是它自己);
而SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;
的作用是将CTRL最低位置0,0x00000001按位取反后为0xfffffffe,该数与任何32位数按位与(&),都不会影响高31位,因为1和任何二进制数进行与运算都等于它自己。
本来想写位带操作的,但看了看时间,就放弃了
GPIO引脚控制函数就不提了,之前在LED_Init()函数里已经见过。
实验效果——红绿灯交替闪烁。
主函数中LED0、LED1和BEEP代表的是GPIO的位段(本文忽略这个概念),把它当做51里对GPIO的位操作就行了。
与上一个实验相比,本实验多了按键模块和蜂鸣器模块。
int main(void) { vu8 key=0; delay_init(); //延时函数初始化 LED_Init(); //LED端口初始化 KEY_Init(); //初始化与按键连接的硬件接口 BEEP_Init(); //初始化蜂鸣器端口 LED0=0; //先点亮红灯 while(1) { key=KEY_Scan(0); //得到键值 if(key) { switch(key) { case WKUP_PRES: //控制LED1翻转 LED1=!LED1; BEEP = !BEEP; break; case KEY0_PRES: //同时控制LED0翻转 LED0=!LED0; BEEP = !BEEP; break; } }else delay_ms(10); } }
与LED_Init()类似,配置步骤相同(配置步骤见LED_Init()介绍部分)。
void KEY_Init(void) //IO初始化{ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);//使能PORTA,PORTE时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;//KEY0 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //设置成下拉输入 GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE2,3,4 //初始化 WK_UP-->GPIOA.0 下拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0}
开发板上有两个按键,KEY_UP
和KEY0
,都是一端接高电平,一端接IO,所以模式设置为下拉输入
,KEY_UP对应的GPIO引脚为GPIOA0,KEY0对应的引脚为GPIOE4。IO时钟都挂载在APB2上。
开发板上并没有蜂鸣器,我选择了外接一个蜂鸣器,同样接在PB8引脚上。初始化配置步骤和LED与KEY相同,模式为推挽输出
,由于我的蜂鸣器低电平有效,所以初始化中还需把IO电平拉高。
//初始化PB8为输出口.并使能这个口的时钟 //蜂鸣器初始化void BEEP_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //BEEP-->PB.8 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度为50MHz GPIO_Init(GPIOB, &GPIO_InitStructure); //根据参数初始化GPIOB.8 GPIO_SetBits(GPIOB,GPIO_Pin_8);//输出0,关闭蜂鸣器输出}
该函数中,mode
表示模式,为0表示短按
,为1表示长按
。局部静态变量key_up
默认为1,表示按键处于空闲状态(松开)。
如果选择短按,在按键处于空闲状态时,检测到KEY0
和WK_UP
中任意一个按键被按下,则将key_up
置0,在此期间不处理其他按键判断,函数返回值为按键值或0(无按键);当按键松开,程序再次运行到按键扫描函数中时,key_up
置为1,按键再次回到空闲状态。
如果选择长按,则key_up
恒为1,无论是否有按键正处于按下状态,每次进入KEY_Scan函数都进行按键判断,这样就实现了按键的长按检测。
u8 KEY_Scan(u8 mode){ static u8 key_up=1;//按键按松开标志 if(mode)key_up=1; //支持连按 if(key_up&&(KEY0==1||WK_UP==1)) { delay_ms(10);//去抖动 key_up=0; if(KEY0==1)return KEY0_PRES; else if(WK_UP==1)return WKUP_PRES; }else if(KEY0==0&&WK_UP==0)key_up=1; return 0;// 无按键按下}
与前两个实验相比,串口实验增加了NVIC中断配置、串口初始化配置。main函数实现的功能为:单片机不停地向串口发送提示性数据,如果有外部设备通过串口向单片机发送数据(以“/r/n“作为结束符),单片机接收数据并返回给外部设备。
int main(void) { u16 t; u16 len; u16 times=0; delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 uart_init(115200); //串口初始化为115200 LED_Init(); //LED端口初始化 KEY_Init(); //初始化与按键连接的硬件接口 while(1) { if(USART_RX_STA&0x8000) { len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度 printf("/r/n您发送的消息为:/r/n/r/n"); for(t=0;t<len;t++) { USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束 } printf("/r/n/r/n");//插入换行 USART_RX_STA=0; }else { times++; if(times%5000==0) { printf("/r/n战舰STM32开发板 串口实验/r/n"); printf("正点原子@ALIENTEK/r/n/r/n"); } if(times%200==0)printf("请输入数据,以回车键结束/n"); if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行. delay_ms(10); } } }
NVIC:嵌套向量中断控制器,NVIC_PriorityGroupConfig
函数是中断优先级的分组配置函数。
/** * @brief Configures the priority grouping: pre-emption priority and subpriority. * @param NVIC_PriorityGroup: specifies the priority grouping bits length. * This parameter can be one of the following values: * @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority * 4 bits for subpriority * @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority * 3 bits for subpriority * @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority * 2 bits for subpriority * @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority * 1 bits for subpriority * @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority * 0 bits for subpriority * @retval None */void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup){ /* Check the parameters */ assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup)); /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */ SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;}
中断优先级分为抢占式优先级
和响应优先级
,抢占优先级越高的先处理,当两个中断向量的抢占优先级相同时,如果两个中断同时到达, 则先处理响应优先级高的中断。
如果对两种优先级的位数分配进行分组,可以分为5组(0~4),分组配置是在寄存器SCB->AIRCR中配置:
main函数中,分组为:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
注:这个分组只是设置STM32中断的两种优先级可选范围,比如0组中,没有抢占优先级,一般情况(学习过程中)该配置设置为2组就行了。另外,这个分组是全局的,所以一个程序中只需要配置一次,多次配置可能会导致未知错误。
串口初始化函数里不仅有GPIO初始化,还有UART初始化和NVIC初始化。
void uart_init(u32 bound){ //GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟 //USART1_TX GPIOA.9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9 //USART1_RX GPIOA.10初始化 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 //USART 初始化设置 USART_InitStructure.USART_BaudRate = bound;//串口波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口1 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断 USART_Cmd(USART1, ENABLE); //使能串口1 }
从原理图可知串口的发送IO为GPIOA9,接收IO为GPIOA10,TX(PA9)设置为复用推挽输出(PA9为复用引脚,可以通过设置复用推挽输出完成USART_TX功能的配置,另外还可以通过配合复用寄存器方式实现复用,如PWM实验),RX设置为浮空输入。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/121284.html
摘要:此文章用于解决开发板的模块中文字库加载问题,也可用于其它关于中文字库无法加载的问题。如下图,已经完成了中文字库烧入,无需再挂载。 正点原子stm32mini板lor...
摘要:使用实现连网实现巴法云物联网使用硬件程序思路基于正点原子的测试程序在巴法云物联网创建的主题初始化代码比较简陋主函数代码如果想用串口助手调试,接线方法如下使用硬件我这里使用的是正点原子家的开发板精英版和模块。 ...
摘要:严格地说,应该是模仿实验。为什么觉得无从下手,看资料没有头绪经验总结看资料需要计划耐心和速度这里所谓的资料包括书籍文档。建议有报销条件的同学自己设计一块板子学习。无法报销的同学,可以选购一款开发板学习。 STM32系列基于专为要求高性能、低成本、低功耗的嵌入式应用专门设计的ARMCortex...
摘要:基于开发的软件包导师汪礼超学员崔林威摘要腾讯物联网操作系统是腾讯面向物联网领域开发的实时操作系统,具有低功耗,低资源占用,模块化,可裁剪等特性。图中断函数处理进行生成工程配置,按如下界面进行配置,最后点击,并点击。 ...
阅读 2720·2023-04-26 02:28
阅读 2549·2021-09-27 13:36
阅读 3122·2021-09-03 10:29
阅读 2750·2021-08-26 14:14
阅读 2100·2019-08-30 15:56
阅读 830·2019-08-29 13:46
阅读 2608·2019-08-29 13:15
阅读 453·2019-08-29 11:29