你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32CubeIDE的窗口看门狗WWDG经验分享

[复制链接]
攻城狮Melo 发布时间:2023-4-6 15:13
一、stm32的WWDG说明( w% H& U6 i& z
       1.1  WWDG特点:

: l& b, i( n& _) g6 x+ X5 K        在前一篇博文介绍独立看门狗时就指出STM32 MCU提供两个看门狗,独立看门狗和窗口看门狗。
0 c; d$ w8 Q' J" ?6 C
. ^  f2 `# A2 H0 \0 k/ @" u        相比独立看门狗,窗口看门狗WWDG有以下特点:( x- n' s6 |: ^2 I* s6 [1 X5 ^
        【1】WWDG计数器依赖于系统时钟,准确来说依赖于系统时钟通过分频、倍频得到的PCLK1时钟,即APB1 peripheral clocks输出频率。
, @8 m! G4 @$ N        【2】WWDG支持中断功能以及早期唤醒中断。
2 ^+ X" W4 R' `
, M3 C. V  o  D% q! E0 t" }) t        1.2 WWDG的cubeMX配置及说明
: m/ ^$ Z1 `5 d  b( v: E/ {3 v. s        现在看一下WWDG在CubeMX中的设置问题,WWDG和IWDG一样,在系统内核栏目开启,开启激活选项后,可以设置5项参数,分别是计数时钟分频值,窗口值、递减计数预设值、早期唤醒中断使能以及中断功能。7 w, M: b# O9 }0 N# }
98991825d78f4fbaa73212a13f1a399f.png
! m; |( e! W$ X$ U. @. \; a0 L' j9 w: P5 B' K
        【1】 一旦WWDG启用,将生成系统重置功能并启动其递减计数器,喂狗时,必须在设定的上限时间和下限时间之间喂狗,如果喂狗时间高于上限时间或者低于下限时间,都会导致看门狗触发重启机制。其中下限时间是系统固定的0X40。上限时间(窗口值)是用户可以定义的,大于0X40同时小于0X7F即可。, S& |4 m" `' t7 Y2 P9 W

- O" d# G0 V/ t' o7 j6 ^# @        【2】程序在达到0x3F值之前刷新计数器(计数器的低六位值,最大值111111=0X3F),到期时触发系统重置(即,当计数器值从0x40向下滚动到0x3F时生成重置,即再次减一时,0x40=0X3F+0X01)。并在计数器达到刷新窗口值之前刷新了计数器值,也会生成MCU重置。这就要求必须在有限的窗口中刷新计数器。另外WWDG启用后,除非通过系统重置,否则无法禁用WWDG。# F8 P9 _6 b2 W9 i; r% N0 I' R; T
' E! T) f* B4 k. F2 q5 I
        【3】如果启用了早期唤醒中断(Early Wakeup Interrupt,EWI)功能,在WWDG到期之前发出警告(即计数器达到0x40时会产生中断)。使用早期唤醒中断(EWI),在生成MCU重置之前可执行特定的安全操作或数据记录。此机制要求在NVIC中启用WWDG中断设置。同样,EWI启用后,EWI中断不能被禁用,除非通过系统重置。中断状态感知可在RCC_CSR寄存器中的WWDGRST标志判断何时发生WWDG重置。/ D4 t6 c0 D) z# K3 Y- ?/ R* G! S
% M: F& n# x7 n$ {& L; }8 w

( s$ m+ C5 {1 B1 ?, N& f        1.3 WWDG喂狗时间计算
  {: t2 d1 c5 p" t% i# h
        WWDG计数器输入时钟由PCLK1频率(APB1 peripheral clocks输出频率)除以计数时钟分频值得出。' y7 t3 }- J+ ~8 \7 E& t
  D" ^$ e, [& [/ Z$ ?3 q
e61915a0f0ff49f69ba6ec1fa1697eef.png
7 f% [' Q9 r9 G, F1 }8 v& F! G( ]) T  O( l
         WWDG时钟(Hz)=PCLK1/(4096*预分频器);- P- @  ~( @! }& k0 }
* Y; F" \. B1 R7 U
        WWDG周期(mS)=1000/WWDG时钟(Hz);
5 J: u$ T7 [  n! M; f6 T5 U+ e
4 i' Z- j# W: z+ n        WWDG超时MinT(mS)=WWDG周期(mS)*(计数器-窗口),因此最好设置递减计数值≥窗口值。
& F: r, K$ [+ F% l2 R5 w2 O/ g4 z5 a0 y: ~. ?) A1 j- `: h
        WWDG超时MaxT(mS)=WWDG周期(mS)*(计数器-0x40),0x40=0X3F+0X01;
5 R5 q3 x% O3 A6 M0 R( \
/ U4 t$ f' h, n7 J) q        WWDG喂狗时间MinT(mS)<t<MaxT(mS)。) w. Z, w2 Y7 B/ N$ \

* A) e) e) V! ^! J8 j4 v* M二、WWDG工程创建及源码分析# H/ n/ X6 n& E" L& c2 P
       2.1 cubeMX界面配置WWDG
# d4 s3 Y( i8 G; e- s3 B8 [
         本博文基于前面独立看门狗的工程直接增加窗口看门狗功能,并关闭独立看门狗。
2 L% u/ l# I8 V- D" T) _! n! j2 R* T4 W' p
        双击.ioc文件打开cubeMX界面,开启WWDG,设置其参数如下,其中分频值2,窗口值0X64(100),递减计数预设值0X64(100),开启早期唤醒中断:
$ y4 z3 ~$ Y; J, z8 W6 V9 D' H/ D6 c0 u2 y
f1f7497080cd40f1853040eb75b2a652.png
$ s. Y# f$ c+ m- x4 h* p; a( I' Y8 A8 I( X0 z! q1 r: ]
         开启WWDG中断功能3 _& \5 i+ J! X0 a. ]

" B# u8 ?& b: V. K
261f23989a6b41498df65be316df09cf.png
* S1 e2 w1 c, c8 {" G
, D, ^" O5 o" A3 s1 V% u
         APB1时钟输出频率设置10MHz(10 000 000Hz),设置该值小一些,主要是本博文采用按键触发切换喂狗时间间隔,而按键捕获到松开是人为操作,反应较慢,实时性不好体现。- S3 {0 e# t5 E1 C+ L8 A

# V2 @  z5 W; w4 ?: a         2.2 WWDG超时时间实例计算
8 ?# s$ l; m3 A  j        在WWDG的喂狗时间按前面计算公式,喂狗时间范围:
- t9 X" j/ H% I: j
5 a! d1 K- U7 l4 d- v. ]9 u7 C        WWDG周期(mS)=1000ms/10MHz/(4096*8)=3.2768ms。2 b' b  t' ^* N% \+ _( S# `

$ t8 G/ m# ^4 U1 c2 u, l) U8 K        WWDG喂狗时间是在WWDG启动后的t时间内:! }/ k- Q3 f. l: U
$ R( \, X! a5 I$ a- X) w
        t>3.2768ms*(0X64-0X64)=0ms;3 L. Z  c/ `5 r

: N' Z( ^; p  H0 H        t<3.2768ms*(0X64-0X40)=117.9648ms。: a' h8 M. ~1 |: |% _

4 W* `# ^1 [. V! {$ @

2 ]) @) O' X/ y- m. Z% I: i       2.3 工程高级配置! ~7 O) \: S* v. {; f0 g4 Q6 {
         在工程高级配置中,取消WWDG的自动初始化,本博文将自己添加初始化,主要是HLA的WWDG初始化启动就立即进入计数,会给第一次喂狗造成时间差异较大。$ C) k$ e; Q$ \7 e3 F$ m5 v

* ^$ l: f" y) R* u
1bfc3d8fa7d24e54a97dbc87ab8f4317.png
' T; K" T* Y# ?% _1 N5 `8 O) h
- u$ _5 U# q) L  ~/ v% ^        生成代码输出。* G4 q& v9 }" a' Z9 u7 G
7 W0 P1 f& Q5 l9 W. W3 g0 v
三、WWDG源码分析及应用
/ ]- [9 f5 J2 n4 |; g3 G        3.1 WWDG的HLA库源码分析

6 j. u0 p! G3 B  z+ g0 q        WWDG会在Core源码目录下的Inc及Src目录,分别生成wwdg.h和wwdg.c驱动文件。在wwdg.c文件中,主要定义了MX_WWDG_Init函数和HAL_WWDG_MspInit函数。MX_WWDG_Init主要做两件事情,一是将CubeMX上配置的参数传递给WWDG缓存Init和生成WWDG句柄Instance,二是调用HLA库的HAL_WWDG_Init来实现真正的初始化设定。HAL_WWDG_MspInit是HLA内的弱函数,根据实际配置CubeMX会生成新的函数覆盖原来的弱函数,而在HAL_WWDG_Init函数中会调用到HAL_WWDG_MspInit函数。
2 |% K' k* v# H" R8 n" t
* R9 ]1 a: g. [5 P( x! N7 e5 p4 E
2b4c2b5b42104381bb7a213382fff3ca.png
/ a5 X2 d+ ], `7 V( z
, p* M8 ^& X$ y$ E1 @" w) q         在stm32l4xx_hal_wwdt.c源文件中定义了HAL_WWDG_Init函数,它做以下事情:诊断配置参数是否合规(如果不是采用CubeMX配置,而是自己手动配置或调整过参数的,这里可能异常);调用HAL_WWDG_MspInit函数完成WWDG时钟以及中断初始化;最后将依据参数写入WWDG寄存器。
/ C; W) q) E) s  K! u  m8 E9 L  v# x/ s+ q" |
136b6b9e484a4fc2ad6b049467a4542a.png
8 w' }) w. O' ^: M( A1 U$ t/ z- n- ?9 o1 x; ~0 N9 b
         程序在依据参数将WWDG计数器值写入WWDG寄存器CR,将窗口值和时钟分频值写入WWDG寄存器CFR内。) l7 u, o7 S. D+ O% x

; L. w; ~: Z# x# Y) i5 M
d799e52574e943baa64598274903a5ed.png
& O- N* m6 [* }- a; `' K8 f/ L) ?' \& i1 i; Y* r
eb67a4bdb772428e92f464444c98c2a1.png
- R+ J, O0 J9 T$ ]2 P$ U1 E& B; O  k
         再回到wwdg.c内,HAL_WWDG_MspInit函数实现了WWDG时钟启动设置和中断初始及启动设置。" S. c4 i  q: G7 e# G! E

$ F% l1 G: ~5 U" E5 |
d081f9832148473daa7a3f8b7bb2009c.png   m! ]  M* d, [1 l6 J7 q
( V: V' V* _) b% T7 p
         HAL_NVIC_EnableIRQ启动后,如果有WWDG中断事件时,当中断服务例程触发HAL_WWDG_IRQHandler时,将自动清除标志,并执行HAL_WWDG_WakeupCallback用户回调函数报警。该回调函数在stm32l4xx_hal_wwdt.c定义,是个弱函数,用户可以通过自定义回调AL_WWDG_WakeupCallback来添加自己的代码。例如本文将在wwdg.c文件内重新定义该函数:
  o8 S- a& v, Q5 x' w6 q4 A
  1. /* USER CODE BEGIN 1 */
    , c, {! O( d3 I* u( G* V
  2. void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)1 x3 k9 @0 J6 R/ R2 z' \
  3. {
    3 x$ G( O& `$ e$ I3 {; g2 I9 U
  4.         printf("I know, but do nothing!\r\n");
    1 M& R% v8 Q$ r; E5 ~
  5. }
    8 i# _: A/ F. j) r
  6. /* USER CODE END 1 */
复制代码

& q% d; I5 _2 X/ G: r+ r+ T4 V         3.2 延时函数设计8 I6 D2 |" r+ `+ }3 ~# Q. w
        在ICore目录下添加delay目录,并在该目录下创建delay.h和delay.c源文件,实现自定义延时函数,主要满足本博文更高精度的延时要求。
: x+ B; z9 t# ~6 \$ \$ \  U/ C, f3 `$ K. {$ ~; x" R& ?
        delay.h,声明微妙、毫秒、秒的延时函数。
6 E$ S# [1 y/ `1 s2 v, Z- n4 ]
  1. #ifndef DELAY_DELAY_H_: r* O/ {7 N! p! ], e3 }
  2. #define DELAY_DELAY_H_
    # Y& B7 g) }6 @) ?- E3 p) {" O7 v

  3. 4 D7 P. G! W/ ]& u: e" e! N
  4. #include "stm32l4xx_hal.h" //HAL库文件声明
    . ?) e" c5 }# m5 L
  5. 2 C& p6 V0 s% [6 E  @
  6. void delay_us(uint32_t us);        //延时微妙8 Q* l7 d* c/ h! K% {
  7. void delay_ms(uint32_t ms);        //延时毫秒
    4 c" j9 y* L0 y
  8. void delay_s(uint32_t s);        //延时秒
    : @+ ^" q# g6 Q) U' i

  9. : F  b  V  `9 L" X: f9 [
  10. #endif /* DELAY_DELAY_H_ */
复制代码

: C! [: v+ n) t; w8 d! |        delay.c,3 ?, G! ^3 P7 k3 J1 e3 s; E5 V+ k
  1. #include "delay.h"
    + |. o4 X  r! h1 X7 i  R7 X2 L2 W

  2. 3 Y: ^# T& \1 B
  3. #define MFP_VAL 8000000    //80MHz
    & f8 ?- T8 R$ m; h  Y

  4. 4 H3 j# L3 }+ W4 L3 t; i; `1 B
  5. void delay_us(uint32_t us) //利用CPU循环实现的非精准应用的微秒延时函数
      N6 K" ~+ ?4 y7 S, k& o4 E
  6. {
    ' e- y7 ^8 |6 B* V
  7.     uint32_t delay = (HAL_RCC_GetHCLKFreq() /  MFP_VAL* us); //使用HAL_RCC_GetHCLKFreq()函数获取主频值,经算法得到1微秒的循环次数3 z# l# H+ Q5 d8 D3 \
  8.     while (delay--); //循环delay次,达到1微秒延时4 n' i/ W! n8 \, X7 k5 l) z7 t
  9. }
    0 Y+ m7 R) i- N! d; t- G6 W

  10. $ @) i7 P( S  ?) f% n: \
  11. void delay_ms(uint32_t ms){ //mS毫秒级延时程序
    ) B$ j# C8 B6 T
  12.         while( ms-- != 0){4 L. D1 S9 `0 i# X1 _
  13.                 delay_us(1000);        //调用1000微秒的延时# O& p, r  Z) Q. f1 z: O
  14.         }
    " U3 x; _* }% F' b$ o
  15. }4 H. D4 I  B3 H  J+ M( C- R  `

  16. 0 i4 i3 j. u) U; z2 O& h+ J
  17. void delay_s(uint32_t s){ //S秒级延时程序, Z% ^# n: q0 R
  18.         while( s-- != 0){
    : H5 H7 R$ p2 p# H# b
  19.                 delay_ms(1000);        //调用1000毫秒的延时
    " w; Q/ ~+ z1 `# n
  20.         }) X! a2 w% ]( {6 J: |& k5 \
  21. }
复制代码
/ t2 k* f& K, ]8 E$ _) w
        3.3 WWDG使用程序设计
) h0 X" P. i/ `% u# U        在本博文中,我们设计通过按键切换喂狗时间间隔,来测试喂狗窗口及时间间隔效果。# l/ A& g, V  y5 [  Z

' Y1 c8 q8 }: c
/ o) F% k6 X) Q3 w' A2 }5 v
        在main.c源文件中,添加驱动头文件,注意由于在CubeMX取消了自动初始化WWDG,因此需要手动添加wwdg.h头文件:4 X0 B) o! G) |& g6 q/ ^; O: @
  1. /* Private includes ----------------------------------------------------------*/. x( D: b$ b9 T3 r6 {  J% x5 G4 k
  2. /* USER CODE BEGIN Includes */
    ! i7 h: {7 V' g  R$ S, O
  3. #include "wwdg.h"
    9 H, _/ I6 D- H  \8 ~
  4. #include "../../ICore/key/key.h"
    8 G' v) r$ G' d
  5. #include "../../ICore/led/led.h"8 ?9 Z8 Z/ f1 A# b8 P* C
  6. #include "../../ICore/print/print.h"
    * B, g. h" J/ l' h- ]4 l1 c
  7. #include "../../ICore/usart/usart.h"" ^9 Z# P. e  y9 ]. O4 J, g
  8. #include "../../ICore/delay/delay.h"
    # o* B* E% v9 u
  9. /* USER CODE END Includes */
复制代码
9 Y+ @# S* {% ?' ?* r. K( ^
        在main主函数前,声明WWDG句柄,用于喂狗函数是调用5 l! |& f4 E9 E: w+ P& p
  1. /* Private user code ---------------------------------------------------------*/
    , s7 E& C6 r2 L0 p' P5 f
  2. /* USER CODE BEGIN 0 */
    " U, e3 |5 K1 _# e5 l+ k) L. f# l( L; S
  3. extern WWDG_HandleTypeDef hwwdg;% n0 p5 v% ?2 s. w: L% T/ P7 n
  4. /* USER CODE END 0 */
复制代码
4 v1 k' g7 C  p: n8 x. V. ~8 {- r
        在main主函数内,设置各外设功能及手动初始化WWDG驱动。
/ b  V. [6 P, d- W* r/ Y
  1.   /* Initialize all configured peripherals */( J* h  w' G' J9 T
  2.   MX_GPIO_Init();( [) c, I4 d% B# D
  3.   MX_LPUART1_UART_Init();  j0 W3 O$ e3 K" h; Q7 N2 X% K  Q% b, D
  4.   /* USER CODE BEGIN 2 */
      O' D5 Z* |. u! r5 p) A
  5.   ResetPrintInit(&hlpuart1);! W  i1 P% |9 G5 F6 J
  6.   HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断
    ; ~8 ]. O5 ^; o
  7.   HLPUSART_RX_STA = 0;
    % y+ T% k* r, @9 b/ ~8 L* C: \- Z
  8.   //1 o  e6 \9 i# {! V
  9.   printf("app restart now!\r\n");3 h/ f% I1 E+ b; i0 Y4 Y( \
  10.   uint8_t wdt_flag = 1;
    8 j6 p5 k+ p- j. l) k8 d
  11.   uint8_t printf_flag = 1;8 ?1 m, v# M9 n
  12.   MX_WWDG_Init();
    ' x# n8 \7 B6 y6 f$ U
  13.   /* USER CODE END 2 */
复制代码
! T- b) O! h& i% y7 h7 e8 y
        在main函数主循环体内,进行喂狗测试,默认是10微妙喂狗一次,按键0停止喂狗,按键1间隔10毫秒喂狗,按键2间隔1秒喂狗。注意前面计算得知0<t<117.9648ms内喂狗有效。按键1间隔10毫秒喂狗主要考虑到按键按下及松开时间效应问题,因此不能设置太大,否则可能因为按键动作耽搁时间太久无效。在实际项目中,我们通常会采用独立线程持续喂狗,各种异常触发打断喂狗来设计。
, y7 l$ l9 q/ p' j1 w/ [7 ~- q
  1. /* Infinite loop */5 B' j* J/ K. r; {6 O
  2.   /* USER CODE BEGIN WHILE */' G" R3 W' b- K4 C
  3.   while (1)
    , t: i1 U+ T) F; v2 E
  4.   {
    & U4 S  G( U5 g- D% Q5 {7 a
  5.           if(1==wdt_flag){- U  O/ x2 o! u" }$ l
  6.                   HAL_WWDG_Refresh(&hwwdg);
    ( e, W' ^6 [9 g
  7.                   delay_us(10);//等待
    " f+ U8 P4 [, ]9 W( y: ?
  8.           }
    # F8 ^/ L( L. y- P( c9 O
  9.           if(2==wdt_flag){0 A/ M* @# h/ t  G) N4 ~( a1 P
  10.                   HAL_WWDG_Refresh(&hwwdg);
    5 v' s+ P; p5 M/ H. `2 Y
  11.                     delay_ms(10);//等待4 E' d5 ]: m# J, H( T0 \) G
  12.             }+ c7 b$ V8 N8 E- C
  13.           if(3==wdt_flag){
    / y: y2 _* Z6 `' ~! R/ T
  14.                   HAL_WWDG_Refresh(&hwwdg);
    ; i0 f2 q3 j2 D2 f9 ^" {
  15.                   delay_s(1);//等待3 a% b: w/ J% X. U* g: m
  16.           }7 n' k. Y: ~8 @
  17.           if(printf_flag)
      n! k& L5 h- I
  18.           {7 b' x# d2 {( _( r
  19.                   printf("current wdt_flag=%u!\r\n",wdt_flag);) K  z5 F8 W# w: t' i% ~6 a
  20.                   printf_flag = 0;
    + S; G9 s% U) o3 U+ K% {
  21.           }
    6 b7 u) V* e5 C( `2 y
  22.           if(KEY_0())
    / W& x9 ~) X1 I" p+ y! E2 [0 d" d8 |
  23.           {
    + F/ M0 N1 i. i$ A! k
  24.                   wdt_flag = 0;6 O( e* a* ]5 d. p# _# L" s
  25.                   printf("WWDG_Refresh stop!\r\n");6 r8 Y9 ]4 J2 e3 E+ V6 o; r+ R
  26.                   delay_us(1);//等待
    5 e; M) p  g& L6 C
  27.                   printf_flag =1;
    1 ~# {) D) |$ T2 {9 l7 E
  28.           }  k% c$ u, B+ ~
  29.           if(KEY_1())
    9 y; U+ o, r2 S! ~
  30.             {
    3 h3 M4 E$ G1 j: Z
  31.                   wdt_flag = 2;
    * o. f' v" z+ D1 l+ y
  32.                   printf("WWDG_Refresh before WIN Time!\r\n");4 [0 c8 V6 ]5 V' f3 l& M! Y
  33.                   delay_us(1);//等待% S/ x( g6 G; a  ~# n
  34.                   printf_flag =1;
    2 N8 n& |& f* d( `* ^  P" T
  35.             }: U) V! I+ w( r
  36.           if(KEY_2())
    5 @/ Q& I/ `5 s. y1 J: q
  37.             {
    ! ~. _' m4 X, s1 D+ u! [
  38.                   wdt_flag = 3;
    " p' {7 B( i) `; C6 n8 [5 J
  39.                   printf("WWDG_Refresh after 0X40 Time!\r\n");7 k  k; L0 Y% m: i0 m/ Y
  40.                   delay_us(1);//等待
    : a0 B5 [; \& O( T
  41.                   printf_flag =1;
    * Q3 ~/ q. O! l1 e1 ?+ C% R7 b
  42.             }
    6 z' ^3 k# i3 h& `) f  S
  43.     /* USER CODE END WHILE */
复制代码

* B; e) W7 T0 J# E; s0 L四、编译及测试( s/ y1 v" C8 P% ?/ r: D: K( h  L: w
        4.1 编译4 }8 p9 l# t" E# n! k8 C8 [0 _. F

/ ?# B' a; {" Y% Q
d6fbffd0d40342e296933a45a64771c8.png
2 V7 }  x; |$ o
& D0 C* {- h. `) l+ {2 I
" ~& T* t, X" r/ Z/ F8 @, ?
         4.2 测试
" \$ a3 f0 Q+ O! C, I& P, {( T* Z  h
        打开串口助手,连接上开发板,按键0、1、2观察效果:' @, Q' y8 \& v- S9 H& M. i

$ Z* K* S1 D. ^2 v' y
5886dd71a550426786ef791de6427f51.png - ~* h0 y! n& a  H  l/ _
" m  p3 A+ C& {
————————————————
9 \/ K5 p* |( y: ~版权声明:py_free-物联智能
7 Q- i. P* a8 }" ^: u! E+ S如有侵权请联系删除
5 w4 v- _. M8 \/ Y1 g+ R8 N9 g5 q2 W. W1 `5 i
' T- }5 c) x1 Z4 U; x" z( G* \. j
013d642e04b445829bf93b4b52820792.png
赞 收藏 评论0 发布时间:2023-4-6 15:13

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版