zoukankan      html  css  js  c++  java
  • FL2440驱动添加(4)LED 驱动添加

    硬件信息:FL2440板子,s3c2440CPU带四个LED,分别在链接GPB5,GPB6,GPB8,GPB10

    内核版本:linux-3.8.0

    led驱动代码如下:

    值得注意地方地方:

    1,定时器的使用:在include/linux/timer.h下定义struct timer_list

    struct timer_list {
    /*
    * All fields that change during normal runtime grouped to the
    * same cacheline
    */
    struct list_head entry;
    unsigned long expires;
    struct tvec_base *base;
    
    void (*function)(unsigned long);
    unsigned long data;
    
    int slack;
    
    #ifdef CONFIG_TIMER_STATS
    int start_pid;
    void *start_site;
    char start_comm[16];
    #endif
    #ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
    #endif
    };

     详细参考:linux-2.4.18内核定时器的使用

    2,设置GPIO函数有中,linux-3.8.0和linux-3.0.0有所不同,具体可以参考Documentation/arm/Samsung-S3C24XX/GPIO.txt中的说明,只列出遇到变换:

      linux-3.8.0 linux-3.0.0 参数是否变化
    设置GPIO高或低 gpio_set_value() s3c2410_gpio_setpin() 不变
    设置GPIO工作方式 s3c_gpio_cfgpin() s3c2410_gpio_cfgpin() 不变

     

    3,Linux核心几个重要跟时间有关的名词或变数,HZ、tick和jiffies之间关系。

    HZ:Linux核心每隔固定周期会发出timer interrupt ,HZ是用来定义每一秒有几次timer interrupts,我的机器为100 timer interrupts。 

    tick:就是HZ的倒数,以秒表示

    jiffies:Jiffies为Linux核心变数(32位元变数,unsigned long),它被用来纪录系统自开机以来的tick数,每发生一次timer interrupt,Jiffies变数会被加一。

    4,设备节点自动创建函数:class_create()和device_create()

    从linux 内核2.6.24的某个版本之后,devfs不复存在,udev成为devfs的替代。udev处于应用层;加入对udev的支持很简单,以一个字符设备驱动led为例,在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用 device_create创建对应的设备。

    大致用法如下:
    struct class *class = class_create(THIS_MODULE, “led”);
    device_create(class, NULL, MKDEV(major_num, 0), NULL, “led”);
    这样的module被加载时,udev daemon就会自动在/dev下创建led设备文件。
    led驱动代码plat_led.c:

    /*********************************************************************************
    * Copyright: (C) 2011 Guo Wenxue<guowenxue@gmail.com> * All rights reserved. * * Filename: s3c_led.c * Description: This is the common LED driver runs on S3C24XX. * * Version: 1.0.0(10/27/2011~) * Author: Guo Wenxue <guowenxue@gmail.com> * ChangeLog: 1, Release initial version on "10/27/2011 11:39:10 AM" * *******************************************************************************
    */ #include "s3c_driver.h" #define DRV_AUTHOR "Guo Wenxue <guowenxue@gmail.com>" #define DRV_DESC "S3C24XX LED driver" /* Driver version*/ #define DRV_MAJOR_VER 1 #define DRV_MINOR_VER 0 #define DRV_REVER_VER 0 #define DEV_NAME DEV_LED_NAME //#define DEV_MAJOR DEV_LED_MAJOR #ifndef DEV_MAJOR #define DEV_MAJOR 0 /* dynamic major by default */ #define TIMER_TIMEOUT 40 static int debug = DISABLE; static int dev_major = DEV_MAJOR; static int dev_minor = 0; /* ============================ Platform Device part ===============================*/ /* LED hardware informtation structure*/ struct s3c_led_info { unsigned char num; /* The LED number */ unsigned int gpio; /* Which GPIO the LED used */ unsigned char active_level; /* The GPIO pin level(HIGHLEVEL or LOWLEVEL) to turn on or off */ unsigned char status; /* Current LED status: OFF/ON */ unsigned char blink; /* Blink or not */ }; /* The LED platform device private data structure */ struct s3c_led_platform_data { struct s3c_led_info *leds; int nleds; }; /* LED hardware informtation data*/ static struct s3c_led_info s3c_leds[] = { [0] = { .num = 1, .gpio = S3C2410_GPB(5), .active_level = LOWLEVEL, .status = ON, .blink = DISABLE, }, [1] = { .num = 2, .gpio = S3C2410_GPB(6), .active_level = LOWLEVEL, .status = OFF, .blink = DISABLE, }, [2] = { .num = 3, .gpio = S3C2410_GPB(8), .active_level = LOWLEVEL, .status = OFF, .blink = DISABLE, }, [3] = { .num = 4, .gpio = S3C2410_GPB(10), .active_level = LOWLEVEL, .status = OFF, .blink = DISABLE, }, }; /* The LED platform device private data */ static struct s3c_led_platform_data s3c_led_data = { .leds = s3c_leds, .nleds = ARRAY_SIZE(s3c_leds), }; struct led_device { struct s3c_led_platform_data *data; struct cdev cdev; struct class *dev_class; struct timer_list blink_timer; } led_device; static void platform_led_release(struct device * dev) { int i; struct s3c_led_platform_data *pdata = dev->platform_data; dbg_print("%s():%d ", __FUNCTION__,__LINE__); /* Turn all LED off */ for(i=0; i<pdata->nleds; i++) { /* for linux-3.8.0 */ gpio_set_value(pdata->leds[i].gpio, ~pdata->leds[i].active_level); #if 0 /* for linux-3.0.0 check in s3c2410_gpio_setpin() in Documentation/arm/Samsung-S3C24XX/GPIO.txt*/ s3c2410_gpio_setpin(pdata->leds[i].gpio, ~pdata->leds[i].active_level); #endif } } static struct platform_device s3c_led_device = { .name = "s3c_led", .id = 1, .dev = { .platform_data = &s3c_led_data, .release = platform_led_release, }, }; /* ===================== led device driver part ===========================*/ void led_timer_handler(unsigned long data) { int i; struct s3c_led_platform_data *pdata = (struct s3c_led_platform_data *)data; for(i=0; i<pdata->nleds; i++) { if(ON == pdata->leds[i].status) { gpio_set_value(pdata->leds[i].gpio, pdata->leds[i].active_level); } else { gpio_set_value(pdata->leds[i].gpio, ~pdata->leds[i].active_level); } if(ENABLE == pdata->leds[i].blink ) /* LED should blink */ { /* Switch status between 0 and 1 to turn LED ON or off */ pdata->leds[i].status = pdata->leds[i].status ^ 0x01; } /* time clock */ mod_timer(&(led_device.blink_timer), jiffies + TIMER_TIMEOUT); } } static int led_open(struct inode *inode, struct file *file) { struct led_device *pdev ; struct s3c_led_platform_data *pdata; pdev = container_of(inode->i_cdev, struct led_device, cdev); pdata = pdev->data; file->private_data = pdata; return 0; } static int led_release(struct inode *inode, struct file *file) { return 0; } static void print_led_help(void) { printk("Follow is the ioctl() command for LED driver: "); printk("Enable Driver debug command: %u ", SET_DRV_DEBUG); printk("Get Driver verion command : %u ", GET_DRV_VER); printk("Turn LED on command : %u ", LED_ON); printk("Turn LED off command : %u ", LED_OFF); printk("Turn LED blink command : %u ", LED_BLINK); } /* compatible with kernel version >=2.6.38*/ static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct s3c_led_platform_data *pdata = file->private_data; switch (cmd) { case SET_DRV_DEBUG: dbg_print("%s driver debug now. ", DISABLE == arg ? "Disable" : "Enable"); debug = (0==arg) ? DISABLE : ENABLE; break; case GET_DRV_VER: print_version(DRV_VERSION); return DRV_VERSION; case LED_OFF: if(pdata->nleds <= arg) { printk("LED%ld doesn't exist ", arg); return -ENOTTY; } pdata->leds[arg].status = OFF; pdata->leds[arg].blink = DISABLE; break; case LED_ON: if(pdata->nleds <= arg) { printk("LED%ld doesn't exist ", arg); return -ENOTTY; } pdata->leds[arg].status = ON; pdata->leds[arg].blink = DISABLE; break; case LED_BLINK: if(pdata->nleds <= arg) { printk("LED%ld doesn't exist ", arg); return -ENOTTY; } pdata->leds[arg].blink = ENABLE; pdata->leds[arg].status = ON; break; default: dbg_print("%s driver don't support ioctl command=%d ", DEV_NAME, cmd); print_led_help(); return -EINVAL; } return 0; } static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .unlocked_ioctl = led_ioctl, /* compatible with kernel version >=2.6.38*/ }; static int s3c_led_probe(struct platform_device *dev) { struct s3c_led_platform_data *pdata = dev->dev.platform_data; int result = 0; int i; dev_t devno; /* Initialize the LED status */ for(i=0; i<pdata->nleds; i++) { s3c_gpio_cfgpin(pdata->leds[i].gpio, S3C2410_GPIO_OUTPUT); if(ON == pdata->leds[i].status) { gpio_set_value(pdata->leds[i].gpio, pdata->leds[i].active_level); } else { gpio_set_value(pdata->leds[i].gpio, ~pdata->leds[i].active_level); } } /* Alloc the device for driver */ if (0 != dev_major) { devno = MKDEV(dev_major, dev_minor); result = register_chrdev_region(devno, 1, DEV_NAME); } else { result = alloc_chrdev_region(&devno, dev_minor, 1, DEV_NAME); dev_major = MAJOR(devno); } /* Alloc for device major failure */ if (result < 0) { printk("%s driver can't get major %d ", DEV_NAME, dev_major); return result; } /* Initialize button structure and register cdev*/ memset(&led_device, 0, sizeof(led_device)); led_device.data = dev->dev.platform_data; cdev_init (&(led_device.cdev), &led_fops); led_device.cdev.owner = THIS_MODULE; result = cdev_add (&(led_device.cdev), devno , 1); if (result) { printk (KERN_NOTICE "error %d add %s device", result, DEV_NAME); goto ERROR; } led_device.dev_class = class_create(THIS_MODULE, DEV_NAME); if(IS_ERR(led_device.dev_class)) { printk("%s driver create class failture ",DEV_NAME); result = -ENOMEM; goto ERROR; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) device_create(led_device.dev_class, NULL, devno, NULL, DEV_NAME); #else device_create(led_device.dev_class, NULL, devno, DEV_NAME); #endif /* Initial the LED blink timer */ init_timer(&(led_device.blink_timer)); led_device.blink_timer.function = led_timer_handler; led_device.blink_timer.data = (unsigned long)pdata; //pass to led_timer_handler() led_device.blink_timer.expires = jiffies + TIMER_TIMEOUT; add_timer(&(led_device.blink_timer)); printk("S3C %s driver version %d.%d.%d initiliazed. ", DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER, DRV_REVER_VER); return 0; ERROR: printk("S3C %s driver version %d.%d.%d install failure. ", DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER, DRV_REVER_VER); cdev_del(&(led_device.cdev)); unregister_chrdev_region(devno, 1); return result; } static int s3c_led_remove(struct platform_device *dev) { dev_t devno = MKDEV(dev_major, dev_minor); del_timer(&(led_device.blink_timer)); cdev_del(&(led_device.cdev)); device_destroy(led_device.dev_class, devno); class_destroy(led_device.dev_class); unregister_chrdev_region(devno, 1); printk("S3C %s driver removed ", DEV_NAME); return 0; } static struct platform_driver s3c_led_driver = { .probe = s3c_led_probe, .remove = s3c_led_remove, .driver = { .name = "s3c_led", .owner = THIS_MODULE, }, }; static int __init s3c_led_init(void) { int ret = 0; ret = platform_device_register(&s3c_led_device); if(ret) { printk(KERN_ERR "%s:%d: Can't register platform device %d ", __FUNCTION__,__LINE__, ret); goto fail_reg_plat_dev; } dbg_print("Regist S3C LED Platform Device successfully. "); ret = platform_driver_register(&s3c_led_driver); if(ret) { printk(KERN_ERR "%s:%d: Can't register platform driver %d ", __FUNCTION__,__LINE__, ret); goto fail_reg_plat_drv; } dbg_print("Regist S3C LED Platform Driver successfully. "); return 0; fail_reg_plat_drv: platform_driver_unregister(&s3c_led_driver); fail_reg_plat_dev: return ret; } static void s3c_led_exit(void) { dbg_print("%s():%d remove LED platform drvier ", __FUNCTION__,__LINE__); platform_driver_unregister(&s3c_led_driver); dbg_print("%s():%d remove LED platform device ", __FUNCTION__,__LINE__); platform_device_unregister(&s3c_led_device); } module_init(s3c_led_init); module_exit(s3c_led_exit); module_param(debug, int, S_IRUGO); module_param(dev_major, int, S_IRUGO); module_param(dev_minor, int, S_IRUGO); MODULE_AUTHOR(DRV_AUTHOR); MODULE_DESCRIPTION(DRV_DESC); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:S3C24XX_led");




    s3c_driver.h头文件:

    /********************************************************************************
    * Copyright: (C) GuoWenxue <guowenxue@gmail.com>
    * All rights reserved.
    *
    * Filename: s3c_driver.h
    * Description: This is the common head file for S3C24XX common driver
    *
    * Version: 1.0.0(10/29/2011~)
    * Author: Guo Wenxue <guowenxue@gmail.com>
    * ChangeLog: 1, Release initial version on "10/29/2011 11:27:50 AM"
    *
    ********************************************************************************/

    #ifndef __S3C_DRIVER_H
    #define __S3C_DRIVER_H

    #include <linux/version.h>
    #include <linux/kernel.h>
    #include <linux/errno.h>
    #include <linux/init.h>
    #include <linux/slab.h>
    #include <linux/module.h>
    #include <linux/kref.h>
    #include <linux/spinlock.h>
    #include <asm/uaccess.h>
    #include <linux/mutex.h>
    #include <linux/delay.h>

    #include <linux/fs.h>
    #include <linux/device.h>
    #include <linux/i2c.h>
    #include <linux/string.h>
    #include <linux/bcd.h>
    #include <linux/miscdevice.h>
    #include <linux/poll.h>
    #include <linux/irq.h>
    #include <linux/interrupt.h>
    #include <linux/sysfs.h>
    #include <linux/proc_fs.h>
    #include <linux/rtc.h>
    #include <linux/spinlock.h>
    #include <linux/usb.h>
    #include <asm/uaccess.h>
    #include <asm/delay.h>
    #include <linux/syscalls.h> /* For sys_access*/
    #include <linux/platform_device.h>
    #include <linux/unistd.h>
    #include <linux/tty.h>
    #include <linux/tty_flip.h>
    #include <linux/serial.h>
    #include <linux/serial_core.h>
    #include <linux/irq.h>
    #include <mach/regs-gpio.h>
    #include <linux/gpio.h> //linux-3.8.0的GPIO控制

    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
    #include <mach/hardware.h>
    #include <mach/gpio.h>
    #include <asm/irq.h>
    #else
    #include <asm-arm/irq.h>
    #include <asm/arch/gpio.h>
    #include <asm/arch/hardware.h>
    #endif
    #include "plat_ioctl.h"

    /* ===========================================================================
    * S3C24XX device driver common macro definition
    *===========================================================================*/

    #define ENPULLUP 1
    #define DISPULLUP 0

    #define HIGHLEVEL 1
    #define LOWLEVEL 0

    #define INPUT 1
    #define OUTPUT 0

    #define OFF 0
    #define ON 1

    #define ENABLE 1
    #define DISABLE 0

    #define TRUE 1
    #define FALSE 0

    /* ===========================================================================
    * S3C24XX device driver name and Major number define
    *===========================================================================*/


    #define DEV_LED_NAME "led"
    #define DEV_LED_MAJOR 203

    #define DEV_BUTTON_NAME "button"
    #define DEV_BUTTON_MAJOR "211"

    #define DEV_ADC_NAME "adc"
    #define DEV_ADC_MAJOR "212"

    /* ***** Bit Operate Define *****/
    #define SET_BIT(data, i) ((data) |= (1 << (i))) /* Set the bit "i" in "data" to 1 */
    #define CLR_BIT(data, i) ((data) &= ~(1 << (i))) /* Clear the bit "i" in "data" to 0 */
    #define NOT_BIT(data, i) ((data) ^= (1 << (i))) /* Inverse the bit "i" in "data" */
    #define GET_BIT(data, i) ((data) >> (i) & 1) /* Get the value of bit "i" in "data" */
    #define L_SHIFT(data, i) ((data) << (i)) /* Shift "data" left for "i" bit */
    #define R_SHIFT(data, i) ((data) >> (i)) /* Shift "data" Right for "i" bit */


    /* ===========================================================================
    * S3C24XX device driver common function definition
    *===========================================================================*/

    #define SLEEP(x) {DECLARE_WAIT_QUEUE_HEAD (stSleep); if (10 > x) mdelay ((x * 1000));
    else wait_event_interruptible_timeout (stSleep, 0, (x / 10));}

    #define VERSION_CODE(a,b,c) ( ((a)<<16) + ((b)<<8) + (c))
    #define DRV_VERSION VERSION_CODE(DRV_MAJOR_VER, DRV_MINOR_VER, DRV_REVER_VER)
    #define MAJOR_VER(a) ((a)>>16&0xFF)
    #define MINOR_VER(a) ((a)>>8&0xFF)
    #define REVER_VER(a) ((a)&0xFF)

    #define dbg_print(format,args...) if(DISABLE!=debug)
    {printk("[kernel] ");printk(format, ##args);}

    static inline void print_version(int version)
    {
    #ifdef __KERNEL__
    printk("%d.%d.%d ", MAJOR_VER(version), MINOR_VER(version), REVER_VER(version));
    #else
    printf("%d.%d.%d ", MAJOR_VER(version), MINOR_VER(version), REVER_VER(version));
    #endif
    }

    #endif /* __S3C_DRIVER_H */

    plat_ioctl.h头文件:

    /*********************************************************************************
    * Copyright(c) 2011, Guo Wenxue <guowenxue@gmail.com>
    * All ringhts reserved.
    *
    * Filename: s3c_ioctl.h
    * Description: ioctl() cmd argument definition here
    *
    * ChangLog:
    * 1, Version: 1.0.0
    * Date: 2011-08-10
    * Author: guowenxue <guowenxue@gmail.com>
    * Descrtipion: Initial first version
    *
    *
    ********************************************************************************/

    #ifndef __PLAT_IOCTL_H
    #define __PLAT_IOCTL_H

    #include <asm/ioctl.h>

    /*===========================================================================
    * Common ioctl command definition
    *===========================================================================*/

    #define PLATDRV_MAGIC 0x60

    /*===========================================================================
    * ioctl command for all the drivers 0x01~0x0F
    *===========================================================================*/

    /*args is enable or disable*/
    #define SET_DRV_DEBUG _IO (PLATDRV_MAGIC, 0x01)
    #define GET_DRV_VER _IO (PLATDRV_MAGIC, 0x02)

    /*===========================================================================
    * ioctl command for few ioctl() cmd driver 0x10~0x2F
    *===========================================================================*/

    /* LED driver */
    #define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
    #define LED_ON _IO (PLATDRV_MAGIC, 0x19)
    #define LED_BLINK _IO (PLATDRV_MAGIC, 0x1A)
    #define ADC_SET_CHANNEL _IO (PLATDRV_MAGIC, 0x1B)

    /*===========================================================================
    * ioctl command for GPRS driver 0x30~0x4F
    *===========================================================================*/
    #define GPRS_POWERDOWN _IO (PLATDRV_MAGIC, 0x30)
    #define GPRS_POWERON _IO (PLATDRV_MAGIC, 0x31)
    #define GPRS_RESET _IO (PLATDRV_MAGIC, 0x32)
    #define GPRS_POWERMON _IO (PLATDRV_MAGIC, 0x33)
    #define GPRS_CHK_SIMDOOR _IO (PLATDRV_MAGIC, 0x36)
    #define GPRS_SET_DTR _IO (PLATDRV_MAGIC, 0x37)
    #define GPRS_SET_RTS _IO (PLATDRV_MAGIC, 0x38)
    #define GPRS_GET_RING _IO (PLATDRV_MAGIC, 0x39)
    #define SET_PWUP_TIME _IO (PLATDRV_MAGIC, 0x3A)
    #define SET_PWDOWN_TIME _IO (PLATDRV_MAGIC, 0x3B)
    #define SET_RESET_TIME _IO (PLATDRV_MAGIC, 0x3C)
    #define GPRS_CHK_MODEL _IO (PLATDRV_MAGIC, 0x3E)

    /*===========================================================================
    * ioctl command for EEPROM driver 0x50~0x5F
    *===========================================================================*/

    #define LL_POWEROFF _IO (PLATDRV_MAGIC, 0x50)
    #define LL_POWERON _IO (PLATDRV_MAGIC, 0x51)
    #define LL_STOP _IO (PLATDRV_MAGIC, 0x52)
    #define LL_START _IO (PLATDRV_MAGIC, 0x53)
    #define LL_READ _IO (PLATDRV_MAGIC, 0x54)
    #define LL_WRITE _IO (PLATDRV_MAGIC, 0x55)
    #define LL_DATALOW _IO (PLATDRV_MAGIC, 0x56)
    #define LL_ACKNAK _IO (PLATDRV_MAGIC, 0x57)


    #endif /* __PLAT_IOCTL_H */

    Makefil文件:

    ARCH=arm920t
    PROJ=fl2440
    PWD=$(shell pwd)

    ifneq ("${ARCH}", "x86")
    CROSS_COMPILE ?= /opt/buildroot-2011.11/${ARCH}/usr/bin/arm-linux-   /*指定交叉编译器*/
    KERNEL_DIR = ../../kernel/linux-3.8/       /*指定内核路径*/
    else
    KERNEL_DIR = /lib/modules/$(shell uname -r)/build
    endif

    obj-m += plat_led.o

    all:
    make -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules
    @make clear

    clear:
    @rm -f *.o *.cmd *.mod.c
    @rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
    @rm -f .*ko.* *ko.* .*.o.cmd

    clean:
    rm -f *.ko *.o

    led测试程序:
    /*
    ******************************************************************************** * Copyright: (C) 2014 zhouguangfeng<zhouguangfeng91@gmail.com> * All rights reserved. * * Filename: test_plat_led.c * Description: This file is used to test led platform drvier * * Version: 1.0.0(08/25/2014) * Author: zhouguangfeng <zhouguangfeng91@gmail.com> * ChangeLog: 1, Release initial version on "08/25/2014 04:04:26 PM" * ********************************************************************************/ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #define PLATDRV_MAGIC 0x60 #define LED_OFF _IO (PLATDRV_MAGIC, 0x18) #define LED_ON _IO (PLATDRV_MAGIC, 0x19) #define LED_BLINK _IO (PLATDRV_MAGIC, 0x1A) /******************************************************************************** * Description: * Input Args: * Output Args: * Return Value: ********************************************************************************/ int main (int argc, char **argv) { int fd; int i; fd = open("/dev/led", O_RDWR, 0755); if(fd<0) { printf("cannot open. "); goto open_err; } ioctl(fd, LED_BLINK, 0); ioctl(fd, LED_OFF, 1); ioctl(fd, LED_ON, 2); ioctl(fd, LED_OFF, 3); open_err: close(fd); return 0; } /* ----- End of main() ----- */
  • 相关阅读:
    Leetcode 349. Intersection of Two Arrays
    hdu 1016 Prime Ring Problem
    map 树木品种
    油田合并
    函数学习
    Leetcode 103. Binary Tree Zigzag Level Order Traversal
    Leetcode 102. Binary Tree Level Order Traversal
    Leetcode 101. Symmetric Tree
    poj 2524 Ubiquitous Religions(宗教信仰)
    pat 1009. 说反话 (20)
  • 原文地址:https://www.cnblogs.com/xiaoxing/p/3935704.html
Copyright © 2011-2022 走看看