开发板上搭载一个LBEE5KL1YN-814 SiP模块,使用英飞凌的CYW43439双模无线通讯芯片,支持802.11b/g/n Wi-Fi and Bluetooth® 5.2。MCU和模块的接口连接如下。

MQTT(Message Queuing Telemetry Transport)是物联网无线应用中常见的协议,是一种轻量级、支持发布/订阅模式、提供消息队列/消息队列服务的网络协议。用于连接网络带宽受限的设备,比如IoT设备。
英飞凌的驱动库中提供MQTT的功能支持,可以使用提供的接口快速开发MQTT应用。
1、添加MQTT驱动库和相应配置
创建空的应用工程,在library-manager中添加retarget-io(1.8.1 release)库用于支持printf串口重定向、添加wifi-core-freertos-lwip-mbedtls(2.2.1 release)库用于支持wifi模块驱动、FreeRTOS、lwip协议等模块、添加mqtt(4.7.0 release)库用于支持mqtt协议。在添加库时,由于软件库是由git管理的,需要指定库的版本,避免兼容性的问题。

1.1 添加Makefile变量
添加的组件中FreeRTOS、MQTT、Wifi连接都需要提供相应的配置信息用于相应资源的分配。工程使用Makefile文件进行相应的管理,在工程目录中添加configs文件夹用于存放工程配置文件,比如FreeRTOSConfig.h、mqtt_client_config.h、wifi_config.h。Makefile中的INCLUDES变量中添加文件的相对路径包含到编译过程中。
INCLUDES=./configs
其中FreeRTOSConfig.h与处理的内核架构相关,管理该文件时,可以创建COMPONENT_CM4文件夹用于存储Cortex-M4内核对应的FreeRTOSConfig.h。对应的INCLUDES变量中添加相应的路径
INCLUDES+=./configs/COMPONENT_$(CORE)
其中的$(CORE)为ModusToolbox的构建系统中的系统变量,用于管理工程。

在Makefile中添加组件信息以及预定宏
COMPONENTS=FREERTOS LWIP MBEDTLS SECURE_SOCKETS
# Add additional defines to the build process (without a leading -D).
DEFINES=$(MBEDTLSFLAGS) CYBSP_WIFI_CAPABLE CY_RETARGET_IO_CONVERT_LF_TO_CRLF CY_RTOS_AWARE
# Number of milliseconds to wait for a ping response to a ping
DEFINES+= MQTT_PINGRESP_TIMEOUT_MS=5000
# The number of retries for receiving CONNACK
DEFINES+= MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT=2

bug1:05Wifi MQTT连接工程,按照示例工程添加项,编译时出现编译报错,提示编译报错无法找到core_http_config.h
在工程的.cyignore文件中添加以下内容,将coreHTTP组件排除在编译过程。对于组件中不需要的内容,可以通过.cyignore文件中添加文件的路径将其排除在编译过程外。
$(SEARCH_aws-iot-device-sdk-embedded-C)/libraries/standard/coreHTTP
1.2 Wifi设置、MQTT设置
wifi_config.h中设置Wifi连接的SSID和对应的密码
/* SSID of the Wi-Fi Access Point to which the MQTT client connects. */
#define WIFI_SSID "MY_WIFI_SSID"
/* Passkey of the above mentioned Wi-Fi SSID. */
#define WIFI_PASSWORD "MY_WIFI_PASSWORD"
mqtt_client_config.h中
/***************** MQTT CLIENT CONNECTION CONFIGURATION MACROS *****************/
/* MQTT Broker/Server address and port used for the MQTT connection. */
#define MQTT_BROKER_ADDRESS "test.mosquitto.org"
#define MQTT_PORT 1883
/* Set this macro to 1 if a secure (TLS) connection to the MQTT Broker is
* required to be established, else 0.
*/
#define MQTT_SECURE_CONNECTION ( 0 )
/* Configure the user credentials to be sent as part of MQTT CONNECT packet */
#define MQTT_USERNAME ""
#define MQTT_PASSWORD ""
/********************* MQTT MESSAGE CONFIGURATION MACROS **********************/
/* The MQTT topics to be used by the publisher and subscriber. */
#define MQTT_PUB_TOPIC "ledstatus"
#define MQTT_SUB_TOPIC "ledstatus"
/* Set the QoS that is associated with the MQTT publish, and subscribe messages.
* Valid choices are 0, 1, and 2. Other values should not be used in this macro.
*/
#define MQTT_MESSAGES_QOS ( 1 )
/* MQTT messages which are published on the MQTT_PUB_TOPIC that controls the
* device (user LED in this example) state in this code example.
*/
#define MQTT_DEVICE_ON_MESSAGE "TURN ON"
#define MQTT_DEVICE_OFF_MESSAGE "TURN OFF"
/******************* OTHER MQTT CLIENT CONFIGURATION MACROS *******************/
/* A unique client identifier to be used for every MQTT connection. */
#define MQTT_CLIENT_IDENTIFIER "psoc6-mqtt-client"
2、MQTT控制开发板LED状态
通过MQTT协议提供的publish/subscribe机制,可以显示开发板外设的状态监控。以开发板上的用户按键和两个LED的控制为例。
2.1 建立网络连接并初始化MQTT协议
网络连接使用到英飞凌的wifi-connection-manager组件库,使用其中的API接口实现Wifi初始化和连接。相关的接口函数以及代码调用如下
cy_rslt_t cy_wcm_init(cy_wcm_config_t *config);
uint8_t cy_wcm_is_connected_to_ap(void);
cy_rslt_t cy_wcm_connect_ap(cy_wcm_connect_params_t *connect_params, cy_wcm_ip_address_t *ip_addr);
/* Configure the Wi-Fi interface as a Wi-Fi STA (i.e. Client). */
cy_wcm_config_t config = {.interface = CY_WCM_INTERFACE_TYPE_STA};
if (CY_RSLT_SUCCESS != cy_wcm_init(&config))
{
printf("\nWi-Fi Connection Manager initialization failed!\n");
goto exit_cleanup;
}
cy_rslt_t result = CY_RSLT_SUCCESS;
cy_wcm_connect_params_t connect_param;
cy_wcm_ip_address_t ip_address;
/* Configure the connection parameters for the Wi-Fi interface. */
memset(&connect_param, 0, sizeof(cy_wcm_connect_params_t));
memcpy(connect_param.ap_credentials.SSID, WIFI_SSID, sizeof(WIFI_SSID));
memcpy(connect_param.ap_credentials.password, WIFI_PASSWORD, sizeof(WIFI_PASSWORD));
connect_param.ap_credentials.security = WIFI_SECURITY;
/* Check if Wi-Fi connection is already established. */
if (cy_wcm_is_connected_to_ap() == 0)
{
/* Connect to the Wi-Fi AP. */
for (uint32_t retry_count = 0; retry_count < MAX_WIFI_CONN_RETRIES; retry_count++)
{
result = cy_wcm_connect_ap(&connect_param, &ip_address);
}
}
指定Wifi连接的类型为Station,并在wifi_config.h指定Wifi连接的SSID和password,调用连接函数即可建立wifi连接。
mqtt协议组件用于实现mqtt协议的初始化、发布和订阅mqtt主题消息。使用到的API和关键的函数调用如下
cy_mqtt_t mqtt_connection;
cy_rslt_t cy_mqtt_init( void );
cy_rslt_t cy_mqtt_create( uint8_t *buffer, uint32_t buff_len,
cy_awsport_ssl_credentials_t *security,
cy_mqtt_broker_info_t *broker_info,
char *descriptor,
cy_mqtt_t *mqtt_handle );
cy_rslt_t cy_mqtt_register_event_callback( cy_mqtt_t mqtt_handle,
cy_mqtt_callback_t event_callback,
void *user_data );
cy_rslt_t cy_mqtt_connect( cy_mqtt_t mqtt_handle, cy_mqtt_connect_info_t *connect_info );
/* Initialize the MQTT library. */
result = cy_mqtt_init();
/* MQTT client identifier string. */
char mqtt_client_identifier[(MQTT_CLIENT_IDENTIFIER_MAX_LEN + 1)] = MQTT_CLIENT_IDENTIFIER;
/* Configure the user credentials as a part of MQTT Connect packet */
if (strlen(MQTT_USERNAME) > 0)
{
connection_info.username = MQTT_USERNAME;
connection_info.password = MQTT_PASSWORD;
connection_info.username_len = sizeof(MQTT_USERNAME) - 1;
connection_info.password_len = sizeof(MQTT_PASSWORD) - 1;
}
/* Generate a unique client identifier with 'MQTT_CLIENT_IDENTIFIER' string
* as a prefix if the `GENERATE_UNIQUE_CLIENT_ID` macro is enabled.
*/
#if GENERATE_UNIQUE_CLIENT_ID
result = mqtt_get_unique_client_identifier(mqtt_client_identifier);
CHECK_RESULT(result, 0, "Failed to generate unique client identifier for the MQTT client!\n");
#endif /* GENERATE_UNIQUE_CLIENT_ID */
/* Set the client identifier buffer and length. */
connection_info.client_id = mqtt_client_identifier;
connection_info.client_id_len = strlen(mqtt_client_identifier);
/* Create the MQTT client instance. */
result = cy_mqtt_create(mqtt_network_buffer, MQTT_NETWORK_BUFFER_SIZE,
security_info, &broker_info,MQTT_HANDLE_DESCRIPTOR,
&mqtt_connection);
if(CY_RSLT_SUCCESS == result)
{
/* Register a MQTT event callback */
result = cy_mqtt_register_event_callback( mqtt_connection, (cy_mqtt_callback_t)mqtt_event_callback, NULL );
}
/* Establish the MQTT connection. */
result = cy_mqtt_connect(mqtt_connection, &connection_info);
在工程的mqtt_client_config.h中指定mqtt连接的用户名,topic信息等通讯参数。调用mqtt初始化、回调注册以及建立连接函数,建立mqtt通讯。
2.2 MQTT发布任务
MQTT协议中的发布功能,可以向MQTT网络中发布消息,传递外设的基本状态。发布消息涉及到的数据结构体以及函数为:
typedef struct cy_mqtt_publish_info
{
cy_mqtt_qos_t qos; /**< Quality of Service for message. */
bool retain; /**< Whether this is a retained message. */
bool dup; /**< Whether this is a duplicate publish message. */
const char *topic; /**< Topic name on which the message is published. */
uint16_t topic_len; /**< Length of topic name. */
const char *payload; /**< Message payload. */
size_t payload_len; /**< Message payload length. */
} cy_mqtt_publish_info_t;
cy_rslt_t cy_mqtt_publish( cy_mqtt_t mqtt_handle, cy_mqtt_publish_info_t *pub_msg );
通过MQTT的发布功能可以将按键点击事件发布到MQTT网络中。初始化按键以及按键的中断回调。
static void publisher_init(void)
{
/* Initialize the user button GPIO and register interrupt on falling edge. */
cyhal_gpio_init(CYBSP_USER_BTN, CYHAL_GPIO_DIR_INPUT,
CYHAL_GPIO_DRIVE_PULLUP, CYBSP_BTN_OFF);
cyhal_gpio_register_callback(CYBSP_USER_BTN, &cb_data);
cyhal_gpio_enable_event(CYBSP_USER_BTN, CYHAL_GPIO_IRQ_FALL,
USER_BTN_INTR_PRIORITY, true);
printf("\nPress the user button (SW2) to publish \"%s\"/\"%s\" on the topic '%s'...\n",
MQTT_DEVICE_ON_MESSAGE, MQTT_DEVICE_OFF_MESSAGE, publish_info.topic);
}
static void isr_button_press(void *callback_arg, cyhal_gpio_event_t event)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
publisher_data_t publisher_q_data;
/* To avoid compiler warnings */
(void) callback_arg;
(void) event;
/* Assign the publish command to be sent to the publisher task. */
publisher_q_data.cmd = PUBLISH_MQTT_MSG;
/* Assign the publish message payload so that the device state toggles. */
if (current_device_state == DEVICE_ON_STATE)
{
publisher_q_data.data = (char *)MQTT_DEVICE_OFF_MESSAGE;
}
else
{
publisher_q_data.data = (char *)MQTT_DEVICE_ON_MESSAGE;
}
/* Send the command and data to publisher task over the queue */
xQueueSendFromISR(publisher_task_q, &publisher_q_data, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
在发布函数中,通过调用消息发布函数,将按键消息发送到MQTT网络中
case PUBLISH_MQTT_MSG:
{
/* Publish the data received over the message queue. */
publish_info.payload = publisher_q_data.data;
publish_info.payload_len = strlen(publish_info.payload);
printf("\nPublisher: Publishing '%s' on the topic '%s'\n",
(char *) publish_info.payload, publish_info.topic);
result = cy_mqtt_publish(mqtt_connection, &publish_info);
if (result != CY_RSLT_SUCCESS)
{
printf(" Publisher: MQTT Publish failed with error 0x%0X.\n\n", (int)result);
/* Communicate the publish failure with the the MQTT
* client task.
*/
mqtt_task_cmd = HANDLE_MQTT_PUBLISH_FAILURE;
xQueueSend(mqtt_task_q, &mqtt_task_cmd, portMAX_DELAY);
}
}
2.3 MQTT订阅
MQTT协议中的订阅功能,可以通过MQTT网络订阅相关的主题,用于接收外部的数据,从而改变开发板中与此订阅相关的设备状态。订阅消息涉及到的数据结构体以及函数为:
typedef struct cy_mqtt_subscribe_info
{
cy_mqtt_qos_t qos; /**< Requested quality of Service for the subscription. */
const char *topic; /**< Topic filter to subscribe to. */
uint16_t topic_len; /**< Length of subscription topic filter. */
cy_mqtt_qos_t allocated_qos; /**< QoS allocated by the broker for the subscription. \ref CY_MQTT_QOS_INVALID indicates subscription failure. */
} cy_mqtt_subscribe_info_t;
cy_rslt_t cy_mqtt_subscribe( cy_mqtt_t mqtt_handle, cy_mqtt_subscribe_info_t *sub_info, uint8_t sub_count );
调用相关的接口,可以订阅MQTT网络中发布的主题,接收其中的数据用于修改开发板上的LED状态,涉及到的关键调用如下
/* Configure the subscription information structure. */
static cy_mqtt_subscribe_info_t subscribe_info =
{
.qos = (cy_mqtt_qos_t) MQTT_MESSAGES_QOS,
.topic = MQTT_SUB_TOPIC,
.topic_len = (sizeof(MQTT_SUB_TOPIC) - 1)
};
result = cy_mqtt_subscribe(mqtt_connection, &subscribe_info, SUBSCRIPTION_COUNT);
case CY_MQTT_EVENT_TYPE_SUBSCRIPTION_MESSAGE_RECEIVE:
{
status_flag |= MQTT_MSG_RECEIVED;
/* Incoming MQTT message has been received. Send this message to
* the subscriber callback function to handle it.
*/
received_msg = &(event.data.pub_msg.received_message);
mqtt_subscription_callback(received_msg);
break;
}
/* Send the command and data to subscriber task queue */
xQueueSend(subscriber_task_q, &subscriber_q_data, portMAX_DELAY);
case UPDATE_DEVICE_STATE:
{
/* Update the LED state as per received notification. */
cyhal_gpio_write(CYBSP_USER_LED, subscriber_q_data.data);
/* Update the current device state extern variable. */
current_device_state = subscriber_q_data.data;
print_heap_usage("subscriber_task: After updating LED state");
break;
}
3、搭建本地MQTT Broker
在PC端安装mosquitto可以方便地建立MQTT服务器,用于MQTT服务测试。

修改安装路径下的mosquitto.conf文件,设置MQTT Borker服务器的监听地址、登录授权配置。
listener 1883 #服务器监听端口
allow_anonymous false #禁止匿名登录
password_file pwfile.example #指定验证信息位置
在命令行中运行下列指令,添加登录ID,根据提示输入密码,完成登录账户的配置
“./mosquitto_passwd -c pwfile.example eeworld
运行.\mosquitto -c .\mosquitto.conf -v运行配置好的服务器。
上述使用的配置信息同步更新到工程的mqtt_client_config.h中以及wifi_config.h的连接设置中。编译通过后,烧录到开发板。
可以看到本地的MQTT服务器与开发板建立连接,接收开发板发送的按键消息。
