您当前的位置:首页 > 电子 > 机器人与智能物联

基于STM32+华为云IOT设计的智能车库管理系统

时间:11-05来源:作者:点击数:

一、项目介绍

随着城市化进程和汽车拥有率的不断提高,停车难的问题也日益凸显。在城市中,停车场是一个非常重要的基础设施,但是传统的停车场管理方式存在很多问题,比如车位难以管理、停车费用不透明等。为了解决这些问题,智能车库管理系统应运而生。

本项目基于STM32+华为云IOT设计的智能车库管理系统,通过红外感应传感器检测停车位的占用情况,将数据上传到华为云物联网平台,并通过微信小程序展示车库的实时停车位情况,包括总停车数量、当前剩余空位数量和车位的编号,并在二维立体图中标注出空闲停车位,方便用户快速找到空闲停车位进行停车。

本系统还包括车牌识别自动计费部分,通过车牌识别技术识别车辆进出停车场的时间,并自动计算停车费用,提高了停车场的管理效率和用户体验。

本项目的实现将大大提高停车场的管理效率和客户体验,为城市交通管理和用户出行提供了更加便捷和高效的解决方案。

image-20230801151049357
image-20230801151056757
image-20230801151104844

二、硬件选型

在本项目中,需要选择合适的硬件来实现智能车库管理系统。根据项目需求,需要选取能够完成以下功能的硬件:

【1】检测停车位是否有车辆存在的传感器模块

【2】能够将传感器数据上传到云平台的通信模块

【3】控制整个系统运行的主控芯片

【4】提供电源支持的电源模块

基于以上需求,最终采用以下硬件:

【1】传感器模块:红外传感器模块,例如红外障碍传感器模块,能够检测车辆是否在停车位上。

【2】通信模块:EC20-4G模块,支持 4G LTE 网络,能够将传感器数据上传到华为云物联网平台。

【3】主控芯片:STM32F103ZET6,具有较高的性能和稳定性,能够满足系统的实时性需求。

【4】电源模块:直流稳压电源模块,能够提供稳定的电源支持,保证系统的正常运行。

三、系统设计思路

【1】硬件设计:

  • 使用STM32F103ZET6作为主控芯片,连接红外感应传感器进行车辆检测。
  • 配置EC20-4G模块与STM32进行通信,通过AT指令或相关协议将检测到的数据上传到华为云物联网平台。

【2】云平台配置:

  • 在华为云物联网平台创建设备实例,将EC20-4G模块作为设备接入。
  • 配置数据流转规则,使得上传的数据能够正确地传输到云平台并且存储。

【3】微信小程序开发:

  • 使用微信小程序开发框架,通过调用华为云物联网平台提供的API接口,获取上传的数据。
  • 对返回的数据进行解析和处理,计算当前车库总停车数量和剩余空位数量。
  • 根据数据绘制二维立体图,并标注出空闲停车位的位置和编号。
  • 实现用户界面显示,展示当前车库状态和方便用户找到空闲停车位。

整体流程如下: 红外感应传感器通过STM32进行车辆检测,检测结果实时上传到华为云物联网平台。微信小程序通过API接口获取上传的数据,并进行分析和处理,计算出车库总停车数量和剩余空位数量。 然后,在界面上绘制二维立体图并标注空闲停车位。用户可以通过微信小程序查看当前车库的状态,从而快速找到空闲停车位进行停车操作。

四、华为云物联网平台部署

华为云官网: https://www.huaweicloud.com/

打开官网,搜索物联网,就能快速找到 设备接入IoTDA

image-20230801151116995

4.1 物联网平台介绍

华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。

使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。

物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。

设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。

业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。

image-20230801151124332
image-20230801151130369

4.2 开通物联网服务

地址: https://www.huaweicloud.com/product/iothub.html

image-20230801151136153

点击总览,查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。

image-20230801151202953

总结:

端口号:   MQTT (1883)| MQTTS (8883)   
接入地址: a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com

根据域名地址得到IP地址信息:

Microsoft Windows [版本 10.0.19045.2965]
(c) Microsoft Corporation。保留所有权利。

C:\Users\11266>ping a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com

正在 Ping a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com [121.36.42.100] 具有 32 字节的数据:
来自 121.36.42.100 的回复: 字节=32 时间=38ms TTL=94
来自 121.36.42.100 的回复: 字节=32 时间=37ms TTL=94
来自 121.36.42.100 的回复: 字节=32 时间=38ms TTL=94
来自 121.36.42.100 的回复: 字节=32 时间=36ms TTL=94

121.36.42.100 的 Ping 统计信息:
    数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 36ms,最长 = 38ms,平均 = 37ms

C:\Users\11266>

image-20230801151212765

MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。

4.3 创建产品

(1)创建产品

点击产品页,再点击左上角创建产品。

image-20230801151220860
(2)填写产品信息

根据自己产品名字填写。

image-20230801151228433
(3)产品创建成功
image-20230801151234528
(4)添加自定义模型

产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。

这个模型就是定义自己设备接下来需要向服务器上传那些数据类型。根据自己的数据类型进行编写。

先点击自定义模型。

image-20230801151243343

再创建一个服务ID。

image-20230801151250227

接着点击新增属性。

image-20230801151255512
image-20230801151300240

4.4 添加设备

产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。

(1)注册设备
image-20230801151308625
(2)根据自己的设备填写
image-20230801151315386
(3)保存设备信息

创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。

image-20230801151322328
(4)设备创建完成

可以点击设备进入到设备详情页面。

image-20230801151331651

4.5 MQTT协议主题订阅与发布

(1)MQTT协议介绍

当前的设备是采用MQTT协议与华为云平台进行通信。

MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。

MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。

华为云的MQTT协议接入帮助文档在这里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html

image-20230801151337395

业务流程:

image-20230801151342879
(2)华为云平台MQTT协议使用限制
描述 限制
支持的MQTT协议版本 3.1.1
与标准MQTT协议的区别 支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msg
MQTTS支持的安全等级 采用TCP通道基础 + TLS协议(最高TLSv1.3版本)
单帐号每秒最大MQTT连接请求数 无限制
单个设备每分钟支持的最大MQTT连接数 1
单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关 3KB/s
MQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝 1MB
MQTT连接心跳时间建议值 心跳时间限定为30至1200秒,推荐设置为120秒
产品是否支持自定义Topic 支持
消息发布与订阅 设备只能对自己的Topic进行消息发布与订阅
每个订阅请求的最大订阅数 无限制
(3)主题订阅格式

帮助文档地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html

image-20230801151350067
image-20230801151355027

对于设备而言,一般会订阅平台下发消息给设备 这个主题。

设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。

如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。

以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down

最终的格式:
$oc/devices/6419627e40773741f9fbdac7_dev1/sys/messages/down

(4)主题发布格式

对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。

这个操作称为:属性上报。

帮助文档地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html

image-20230801151401246

根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:

发布的主题格式:
$oc/devices/{device_id}/sys/properties/report
 
最终的格式:
$oc/devices/6419627e40773741f9fbdac7_dev1/sys/properties/report
发布主题时,需要上传数据,这个数据格式是JSON格式。

上传的JSON数据格式如下:

{
  "services": [
    {
      "service_id": <填服务ID>,
      "properties": {
        "<填属性名称1>": <填属性值>,
        "<填属性名称2>": <填属性值>,
        ..........
      }
    }
  ]
}
根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。

根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"DS18B20":18,"motor_water":1,"motor_oxygen":1,"temp_max":10,"water_hp":130,"motor_food":0,"time_food":0,"oxygen_food":3}}]}

4.6 MQTT三元组

MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。

接下来介绍,华为云平台的MQTT三元组参数如何得到。

(1)MQTT服务器地址

要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。

帮助文档地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home

MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。

根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)

华为云的MQTT服务器地址:114.116.232.138
域名:7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com
华为云的MQTT端口号:1883

注意!!!! 具体要看这里:

image-20230801151408871
(2)生成MQTT三元组

华为云提供了一个在线工具,用来生成MQTT鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/

打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。

下面是打开的页面:

image-20230801151418248

填入设备的信息: (上面两行就是设备创建完成之后保存得到的)

得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。

ClientId 6419627e40773741f9fbdac7_dev1_0_0_2023032108
Username 6419627e40773741f9fbdac7_dev1
Password 861ac9e6a579d36888b2aaf97714be7af6c77017b017162884592bd68b086a6e

4.7 模拟设备登录测试

经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。

(1)填入登录信息

打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。

image-20230801151429390
(2)打开网页查看

完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。

点击详情页面,可以看到上传的数据。

到此,云平台的部署已经完成,设备已经可以正常上传数据了。

五、代码设计

5.1 EC20-4G模块AT指令介绍

EC20模块是一种常用的无线通信模块,支持MQTT协议。

下面MQTT相关的AT指令:

【1】AT+QMTCONN:用于建立与MQTT服务器的连接。

  • 功能:通过指定MQTT服务器的地址、端口、客户端ID、用户名和密码等参数,建立与MQTT服务器的连接。

【2】AT+QMTDISC:用于断开与MQTT服务器的连接。

  • 功能:断开与MQTT服务器的连接。

【3】AT+QMTPUB:用于发布MQTT消息。

  • 功能:指定MQTT主题和消息内容,将消息发布到MQTT服务器。

【4】AT+QMTSUB:用于订阅MQTT主题。

  • 功能:订阅指定的MQTT主题,接收该主题下的消息。

【5】AT+QMTUNS:用于取消订阅MQTT主题。

  • 功能:取消对指定MQTT主题的订阅。

【6】AT+QMTRECV:用于接收MQTT消息。

  • 功能:接收从MQTT服务器发送的消息。

这些是EC20模块MQTT协议相关的AT指令。使用这些指令,可以在EC20模块上实现MQTT设备的登录、主题订阅、主题发布以及消息接收等功能。

5.2 EC20连接IOT平台

以下是使用STM32和EC20通过MQTT协议连接物联网平台并实现主题订阅和发布的实现代码:

#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"

// 定义串口波特率
#define BAUD_RATE 115200

// 定义UART接收缓冲区大小
#define UART_RX_BUFFER_SIZE 256

// 定义MQTT服务器地址和端口号
#define MQTT_SERVER_ADDRESS "mqtt.example.com"
#define MQTT_SERVER_PORT 1883

// 定义MQTT客户端ID
#define MQTT_CLIENT_ID "example_client"

// 定义MQTT订阅的主题
#define MQTT_SUB_TOPIC "example_topic"

// 定义MQTT发布的主题
#define MQTT_PUB_TOPIC "example_topic"

// 定义UART接收缓冲区和索引
char uartRxBuffer[UART_RX_BUFFER_SIZE];
volatile uint16_t uartRxBufferIndex = 0;

// UART中断处理函数
void USART1_IRQHandler(void)
{
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        // 读取接收到的数据
        char data = USART_ReceiveData(USART1);

        // 将数据存入接收缓冲区
        uartRxBuffer[uartRxBufferIndex] = data;
        uartRxBufferIndex++;

        // 处理接收到的数据
        // 这里可以根据需要进行相关操作,例如解析MQTT消息等

        // 清除接收中断标志位
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

// 初始化UART1配置
void UART1_Config(void)
{
    USART_InitTypeDef USART_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 使能USART1和GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    // 配置USART1引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;     // TX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;    // RX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置USART1
    USART_InitStructure.USART_BaudRate = BAUD_RATE;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    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);

    // 配置USART1中断
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // 使能USART1接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

    // 使能USART1
    USART_Cmd(USART1, ENABLE);
}

// 向UART1发送数据
void UART1_SendData(char *data)
{
    while (*data)
    {
        // 发送数据
        USART_SendData(USART1, *data++);
        
        // 等待发送完成
        while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
    }
}

// 连接MQTT服务器
void MQTT_Connect(void)
{
    // 构造MQTT CONNECT报文
    char connectPacket[256];
    sprintf(connectPacket,
            "\x10\x12\x00\x04MQTT\x04\x02\x00\x3C\x00%s",
            MQTT_CLIENT_ID);

    // 发送MQTT CONNECT报文
    UART1_SendData(connectPacket);
}

// 订阅MQTT主题
void MQTT_Subscribe(void)
{
    // 构造MQTT SUBSCRIBE报文
    char subscribePacket[256];
    sprintf(subscribePacket,
            "\x82\x0F\x00\x01\x00\x0C"MQTT_SUB_TOPIC"\x00\x00");

    // 发送MQTT SUBSCRIBE报文
    UART1_SendData(subscribePacket);
}

// 发布MQTT消息
void MQTT_Publish(char *message)
{
    // 构造MQTT PUBLISH报文
    char publishPacket[256];
    sprintf(publishPacket,
            "\x30\x10\x00\x0D"MQTT_PUB_TOPIC"%s",
            message);

    // 发送MQTT PUBLISH报文
    UART1_SendData(publishPacket);
}

int main(void)
{
    // 初始化UART1配置
    UART1_Config();
    
    // 连接MQTT服务器
    MQTT_Connect();
    
    // 订阅MQTT主题
    MQTT_Subscribe();
    
    while (1)
    {
        // 处理其他任务
        
        // 发布MQTT消息
        MQTT_Publish("Hello, world!");
        
        // 等待一段时间
        delay_ms(1000);
    }
}

在代码中,通过UART1与EC20进行通信,并实现了MQTT的连接、订阅和发布功能。UART1_SendData函数用于向UART1发送数据,MQTT_Connect函数用于连接MQTT服务器,MQTT_Subscribe函数用于订阅MQTT主题,MQTT_Publish函数用于发布MQTT消息。

5.3 获取设备影子数据(API接口)

帮助文档:https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html

设备影子介绍:

设备影子是一个用于存储和检索设备当前状态信息的JSON文档。
每个设备有且只有一个设备影子,由设备ID唯一标识
设备影子仅保存最近一次设备的上报数据和预期数据
无论该设备是否在线,都可以通过该影子获取和设置设备的属性

简单来说:设备影子就是保存,设备最新上传的一次数据。

我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。

如果对接口不熟悉,可以先进行在线调试:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow

在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。

image-20230801151441245

设备影子接口返回的数据如下:

{
 "device_id": "6419627e40773741f9fbdac7_dev1",
 "shadow": [
  {
   "service_id": "stm32",
   "desired": {
    "properties": null,
    "event_time": null
   },
   "reported": {
    "properties": {
     "DS18B20": 18,
     "motor_water": 1,
     "motor_oxygen": 1,
     "temp_max": 10,
     "water_hp": 130,
     "motor_food": 0,
     "time_food": 0,
     "oxygen_food": 3
    },
    "event_time": "20230321T081126Z"
   },
   "version": 0
  }
 ]
}

5.4 修改设备属性(API接口)

地址: https://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html

接口说明

设备的产品模型中定义了物联网平台可向设备下发的属性,应用服务器可调用此接口向指定设备下发属性。平台负责将属性以同步方式发送给设备,并将设备执行属性结果同步返回。

修改设备属性的接口,可以让服务器给设备下发指令,如果需要控制设备。

在线调试地址:

https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=UpdateProperties

修改设备属性是属于同步命令,需要设备在线才可以进行调试,先使用MQTT客户端登录服务器,模拟设备上线。

然后进行调试,测试数据远程下发给设备。

【1】利用MQTT客户端先登录设备 (这是同步命令,必须在线才能调试)

image-20230801151450774

【2】点击调试

image-20230801151457386
{"services":{"temp_max":100}}

【4】可以看到,MQTT客户端软件上已经收到了服务器下发的消息

image-20230801151505222

由于是同步命令,服务器必须要收到设备的响应才能顺利完成一个流程,设备响应了服务器才能确定数据下发成功。

六、总结

基于STM32和华为云IOT的智能车库管理系统可以实现停车位智能展示功能,方便用户快速找到空闲停车位。系统的核心部分是主控芯片STM32F103ZET6和红外传感器,通过红外传感器检测停车位是否有车辆存在,并实时将检测数据上传到华为云物联网平台。

在物联网平台上,可以创建一个设备模型,包括车库总停车数量、当前剩余空位数量和车位的编号等属性。每个停车位对应一个设备,通过红外传感器的检测结果更新对应停车位的状态。EC20-4G模块负责将这些数据发送到华为云物联网平台,实现与云端的通信。

微信小程序作为用户界面,通过调用华为云物联网平台提供的接口,获取设备上传的数据并进行处理分析,在界面上显示当前车库总停车数量、当前剩余空位数量和车位的编号。可以使用二维立体图的形式,将停车位的状态进行展示,标注出空闲的停车位,方便用户快速找到空位进行停车。

通过红外传感器和EC20模块实时上传数据到华为云物联网平台,微信小程序通过调用接口获取数据并展示在界面上,实现停车位智能展示功能。这种系统可以提高停车场的利用率,提供用户友好的体验,同时也为停车场管理者提供了实时监控和数据分析的便利。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐