在GPRS模塊(GA6)和STM32F103C8T6單片機上移植MQTT協議

在GPRS模塊(GA6)和STM32F103C8T6單片機上移植MQTT協議

最近幾天折騰了一下,在GPRS模塊(GA6)和STM32F103C8T6單片機上移植MQTT協議,網上找了一些資料,不是很全面,下面結合自己的移植過程記錄一下。

下載MQTT的庫

我移植MQTT的庫:Paho,這個庫支持非常多的平臺,當然也包括了嵌入式平臺:GitHub – paho.mqtt.embedded-c
這裏寫圖片描述
將該庫中的MQTTPacket文件夾下載下來,MQTTPacket文件夾下面主要有三個文件夾,如上圖所示,我們使用的文件主要在src文件夾和samples文件夾中。
src文件夾中存放着MQTT核心功能的代碼,而samples中存放着五個例子:ping.c,ping_nb.c,pub0sub1.c,pub0sub1_nc.c,qos0pub.c和網絡驅動文件(transport.c和transport.h),如下圖所示:
這裏寫圖片描述

移植步驟

1, 準備工作

因爲我板子都是淘寶上買的,給過來很多配套的資料,熟悉下怎麼通過STM32F103C8T6單片機串口連接GPRS模塊(GA6)來發送TCP消息,當然也熟悉下keil 5這個工具,因爲我不是做單片機開發的,第一次玩這個,着實也是折騰了一下,才勉強用Keil 5完成我要的任務,這裏的STM32F103C8T6單片機串口連接GPRS模塊(GA6)來發送TCP消息在Keil 5中建一個工程試試,一般資料中都會有,直接用就可以,後面的移植就基於這個工程來弄。

2,移植

直接把前面我們下載下來的MQTTPacket/src目錄中的所有文件,如下圖所示導入到上面的工程中。
這裏寫圖片描述
我導入之後的結果如下圖所示:
這裏寫圖片描述
這裏只是截取了一個片段,後面我會把整個完成的工程代碼放出。當然上面用於cmake編譯的CMakeLists.txt文件,我們不需要添加進來。導入之後重新編譯是沒有錯誤的,如果前面的工程本來就有錯誤的話,請先修改的沒錯誤。

這樣其實就已經把MQTT庫移植完成了,是不是很簡單,應該說這個的支持比較好,下面我們來把庫samples目錄文件夾中的例子移植進去,我是基於pub0sub1.c這個例子來做的,我先把這個文件的原內容貼出來,方便對比:

/*******************************************************************************
 * Copyright (c) 2014 IBM Corp.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 *
 * The Eclipse Public License is available at
 *    http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 *   http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *    Ian Craggs - initial API and implementation and/or initial documentation
 *    Sergio R. Caprile - clarifications and/or documentation extension
 *******************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "MQTTPacket.h"
#include "transport.h"

/* This is in order to get an asynchronous signal to stop the sample,
as the code loops waiting for msgs on the subscribed topic.
Your actual code will depend on your hw and approach*/
#include <signal.h>

int toStop = 0;

void cfinish(int sig)
{
    signal(SIGINT, NULL);
    toStop = 1;
}

void stop_init(void)
{
    signal(SIGINT, cfinish);
    signal(SIGTERM, cfinish);
}
/* */

int main(int argc, char *argv[])
{
    MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
    int rc = 0;
    int mysock = 0;
    unsigned char buf[200];
    int buflen = sizeof(buf);
    int msgid = 1;
    MQTTString topicString = MQTTString_initializer;
    int req_qos = 0;
    char* payload = "mypayload";
    int payloadlen = strlen(payload);
    int len = 0;
    char *host = "m2m.eclipse.org";
    int port = 1883;

    stop_init();
    if (argc > 1)
        host = argv[1];

    if (argc > 2)
        port = atoi(argv[2]);

    mysock = transport_open(host, port);
    if(mysock < 0)
        return mysock;

    printf("Sending to hostname %s port %d\n", host, port);

    data.clientID.cstring = "me";
    data.keepAliveInterval = 20;
    data.cleansession = 1;
    data.username.cstring = "testuser";
    data.password.cstring = "testpassword";

    len = MQTTSerialize_connect(buf, buflen, &data);
    rc = transport_sendPacketBuffer(mysock, buf, len);

    /* wait for connack */
    if (MQTTPacket_read(buf, buflen, transport_getdata) == CONNACK)
    {
        unsigned char sessionPresent, connack_rc;

        if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, buflen) != 1 || connack_rc != 0)
        {
            printf("Unable to connect, return code %d\n", connack_rc);
            goto exit;
        }
    }
    else
        goto exit;

    /* subscribe */
    topicString.cstring = "substopic";
    len = MQTTSerialize_subscribe(buf, buflen, 0, msgid, 1, &topicString, &req_qos);

    rc = transport_sendPacketBuffer(mysock, buf, len);
    if (MQTTPacket_read(buf, buflen, transport_getdata) == SUBACK)  /* wait for suback */
    {
        unsigned short submsgid;
        int subcount;
        int granted_qos;

        rc = MQTTDeserialize_suback(&submsgid, 1, &subcount, &granted_qos, buf, buflen);
        if (granted_qos != 0)
        {
            printf("granted qos != 0, %d\n", granted_qos);
            goto exit;
        }
    }
    else
        goto exit;

    /* loop getting msgs on subscribed topic */
    topicString.cstring = "pubtopic";
    while (!toStop)
    {
        /* transport_getdata() has a built-in 1 second timeout,
        your mileage will vary */
        if (MQTTPacket_read(buf, buflen, transport_getdata) == PUBLISH)
        {
            unsigned char dup;
            int qos;
            unsigned char retained;
            unsigned short msgid;
            int payloadlen_in;
            unsigned char* payload_in;
            int rc;
            MQTTString receivedTopic;

            rc = MQTTDeserialize_publish(&dup, &qos, &retained, &msgid, &receivedTopic,
                    &payload_in, &payloadlen_in, buf, buflen);
            printf("message arrived %.*s\n", payloadlen_in, payload_in);
        }

        printf("publishing reading\n");
        len = MQTTSerialize_publish(buf, buflen, 0, 0, 0, 0, topicString, (unsigned char*)payload, payloadlen);
        rc = transport_sendPacketBuffer(mysock, buf, len);
    }

    printf("disconnecting\n");
    len = MQTTSerialize_disconnect(buf, buflen);
    rc = transport_sendPacketBuffer(mysock, buf, len);

exit:
    transport_close(mysock);

    return 0;
}

可以看到,其內容比較簡單,其中關於中斷的處理,STM32F103C8T6單片機應該是用不了的,我們可以直接去掉,我是用按鍵來替代的,然後除了下面這個四個接口:

int transport_sendPacketBuffer(unsigned char* buf, int buflen);
int transport_getdata(unsigned char* buf, int count);
int transport_open(char* host, int port);
int transport_close();

是在我們所說網絡驅動文件(transport.c和transport.h)中實現,其他MQTT相關的接口,都在MQTTPacket.h文件中可以找到定義,也就是說我們上面移植的MQTT庫中,下面我們剩下的工作就是完成對這上面4個接口來進行移植。
上面4個接口實現的主要功能是:
1、transport_open的作用是初始化模塊連網的信息、transport_close作用是關閉鏈接。
2、transport_sendPacketBuffer用於發送數據、transport_getdata用於接收數據。
然後用transport.c來實現transport.h中聲明的4個函數。
其實transport_open和transport_close這兩個接口我在移植中也沒用到,最後主要就集中在transport_sendPacketBuffer和transport_getdata這兩個接口上了,爲了方便,我甚至把上面transport.c和transport.h這兩個文件都給刪了,直接把上面兩個要用的接口實現放在我們自己工程的main函數前面進行定義,具體細節我就不說,直接貼我移植之後的代碼:

transport_sendPacketBuffer函數:
int transport_sendPacketBuffer(const char*server_ip_and_port,unsigned char* buf, int buflen)
{
    u8 ret;
    char end_char[2];
    char connect_server_ip_port_cmd[56];

    // 因爲每次讀取消息之前都要設置這個值,所以乾脆放到這個函數中,以便下次讀取
    // 儘量不去動MQTT中的代碼
    first_index_to_read = 21;

    end_char[0] = 0x1A;//結束字符
    end_char[1] = '\0';

    ret = UART2_Send_AT_Command("AT+CIPSTATUS","CONNECT OK",3,1);//查詢連接狀態
    //UART1_Printf("0000 ret %d\n", ret);
    if(ret == 1)//說明服務器處於連接狀態
    {
        ret = UART2_Send_AT_Command("AT+CIPSEND",">",3,1);//開發發送數據
        //UART1_Printf("1111 ret %d\n", ret);
        if(ret == 0)
        {
            return AT_CIPSEND_ERROR;
        }

        UART2_SendU8Array(buf,buflen);
        delays(2);
        ret = UART2_Send_AT_Command_End(end_char,"SEND OK",4,1);//發送結束符,等待返回ok,等待5S發一次,因爲發送數據時間可能較長
        //UART1_Printf("2222 ret %d\n", ret);
        if(ret == 0)
        {
            return END_CHAR_ERROR;
        }
        //clear_uart2_recv_buf();
        return 1;
    }

    ret = UART2_Send_AT_Command("AT+CGATT=1","OK",2,1);//附着網絡
    //UART1_Printf("3333 ret %d\n", ret);
    if(ret == 0)
    {
        return AT_CGATT_ERROR;
    }

    ret = UART2_Send_AT_Command("AT+CGACT=1,1","OK",2,1);//激活網絡
    //UART1_Printf("0000 ret %d\n", ret);
    if(ret == 0)
    {
        return AT_CGATT1_ERROR;
    }

    memset(connect_server_ip_port_cmd,'\0',56);
    strcpy(connect_server_ip_port_cmd,"AT+CIPSTART=\"TCP\",");
    strcat(connect_server_ip_port_cmd,server_ip_and_port);

    ret = UART2_Send_AT_Command(connect_server_ip_port_cmd,"CONNECT OK",4,1);//連接服務器
    //UART1_Printf("0000 ret %d\n", ret);
    if(ret == 0)
    {
        return AT_CIPSTART_ERROR;
    }

    ret = UART2_Send_AT_Command("AT+CIPSEND",">",3,1);//開發發送數據
    //UART1_Printf("0000 ret %d\n", ret);
    if(ret == 0)
    {
        return AT_CIPSEND_ERROR;
    }

    UART2_SendU8Array(buf,buflen);
    delays(2);
    ret = UART2_Send_AT_Command_End(end_char,"SEND OK",4,1);//發送結束符,等待返回ok,等待5S發一次,因爲發送數據時間可能較長
  if(ret == 0)
    {
         return END_CHAR_ERROR;
    }

    return 1;
}

主要是GPRS模塊(GA6)通過AT指令來發送TCP數據,每個指令之間的延時,然後怎麼確認每條指令已經完成,具體細節大家在移植的時候要結合自己的芯片進行調試。

transport_getdata函數:
int transport_getdata(unsigned char*buf, int count)
{
    memcpy(buf,&uart2_recv_buf[first_index_to_read],count);
    // 更新讀取的序號,以便下次讀取
    first_index_to_read += count;
    return count;
}

這個函數實現的比較奇怪,因爲GPRS模塊(GA6)收到服務器發回的消息之後,是通過串口發送給STM32F103C8T6單片機的,然後STM32F103C8T6單片機串口是通過串口中斷把GPRS模塊(GA6)發送過來的數據,存放在uart2_recv_buf這個用於接收串口數據的數組中的,然後我們解析服務器發來的數據時,MQTT庫中的這個函數MQTTPacket_read有個參數是以transport_getdata函數的函數指針作爲形參:

/**
 * Helper function to read packet data from some source into a buffer
 * @param buf the buffer into which the packet will be serialized
 * @param buflen the length in bytes of the supplied buffer
 * @param getfn pointer to a function which will read any number of bytes from the needed source
 * @return integer MQTT packet type, or -1 on error
 * @note  the whole message must fit into the caller's buffer
 */
int MQTTPacket_read(unsigned char* buf, int buflen, int (*getfn)(unsigned char*, int))

爲了不改變MQTT庫,所以按照上面的辦法實現了這個接口,雖然不是很嚴謹—把參數count直接返回,但是在我們後面測試的時候,沒發現問題,(有點囉嗦,哈哈,我主要當作自己的學習筆記吧,以後自己看到的時候,能明白自己爲啥當初這麼做。)

最後我們來看看移植完之後的main函數:

int main(void)
{
    u16 key_value= 0, retry_count = 5;
    unsigned short submsgid;
    unsigned char buf[MQTT_MAX_BUF_SIZE];
    unsigned char sessionPresent, connack_rc;
    const char* payload = "mypayload";
    int payloadlen = strlen(payload);
    int ret = 0,len = 0,req_qos = 0,msgid = 1,loop = 1,granted_qos,subcount;

    MQTTString topicString = MQTTString_initializer;
    MQTTPacket_connectData data = MQTTPacket_connectData_initializer;

    SysTick_Init_Config();   //系統滴答時鐘初始化
    GPIO_Config();           //GPIO初始化
    Key_GPIO_Config();
    USART2_Init_Config(115200);  //串口2初始化
    Timer2_Init_Config();        //定時器2初始化

    USART1_Init_Config(115200);//UART1用作串口調試信息

    UART1_SendString("系統啓動.......................\r\n");

    //等待系統上電後的穩定
    delays(STABLE_TIMES);

    data.clientID.cstring = "me";
    data.keepAliveInterval = 20;
    data.cleansession = 1;
    data.username.cstring = "testuser";
    data.password.cstring = "testpassword";

    len = MQTTSerialize_connect(buf, MQTT_MAX_BUF_SIZE, &data);
    UART1_Printf("1111 len %d\n", len);
    ret = transport_sendPacketBuffer(MQTT_SERVER_IP_AND_PORT, buf, len);
    if( ret != 1 ){
        UART1_Printf("1111 transport_sendPacketBuffer Error %d\n", ret);
        return -1;
    }
    /* wait for connack */
    if (MQTTPacket_read(buf, MQTT_MAX_BUF_SIZE, transport_getdata) != CONNACK)
    {
        UART1_Printf("MQTTPacket_read != CONNACK\n");
        return -1;
    }
    if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, MQTT_MAX_BUF_SIZE) != 1 || connack_rc != 0)
    {
        UART1_Printf("Unable to connect, return code %d\n", connack_rc);
        return -1;
    }
    topicString.cstring = "substopic";
    len = MQTTSerialize_subscribe(buf, MQTT_MAX_BUF_SIZE, 0, msgid, 1, &topicString, &req_qos);
    UART1_Printf("2222 len %d\n", len);
    ret = transport_sendPacketBuffer(MQTT_SERVER_IP_AND_PORT, buf, len);
    if( ret != 1 ){
        UART1_Printf("2222 transport_sendPacketBuffer Error %d\n", ret);
        return -1;
    }

    if (MQTTPacket_read(buf, MQTT_MAX_BUF_SIZE, transport_getdata) != SUBACK)   /* wait for suback */
    {
        UART1_Printf("MQTTPacket_read != SUBACK\n");
        return -1;
    }

    MQTTDeserialize_suback(&submsgid, 1, &subcount, &granted_qos, buf, MQTT_MAX_BUF_SIZE);
    if (granted_qos != 0)
    {
        UART1_Printf("granted qos != 0, %d\n", granted_qos);
        return -1;
    }

    /* loop getting msgs on subscribed topic */
    topicString.cstring = "pubtopic";

    while( loop ){ 

        key_value = Key_Down_Scan();
        // add by james_xie 20171221 ,for debug
        UART1_Printf("key_value %d\r\n",key_value);
        // 按鍵KEY1即PA5被按下則跳出循環
        if( key_value == 0x0001 << 5 ){
            UART1_Printf("Key1 is pressed down , exit \r\n");
            loop = 0;
        }
        /* transport_getdata() has a built-in 1 second timeout,
        your mileage will vary */
        if (MQTTPacket_read(buf, MQTT_MAX_BUF_SIZE, transport_getdata) == PUBLISH)
        {
            int qos,payloadlen_in;
            unsigned char dup,retained;
            unsigned short msgid;
            unsigned char* payload_in;
            MQTTString receivedTopic;

            MQTTDeserialize_publish(&dup, &qos, &retained, &msgid, &receivedTopic,
                    &payload_in, &payloadlen_in, buf, MQTT_MAX_BUF_SIZE);
            UART1_Printf("message arrived %.*s\n", payloadlen_in, payload_in);
        }

        UART1_Printf("publishing reading\n");
        len = MQTTSerialize_publish(buf, MQTT_MAX_BUF_SIZE, 0, 0, 0, 0, topicString, (unsigned char*)payload, payloadlen);
        UART1_Printf("33333 len %d\n", len);
        // 重複發送5次,如果5次都不成功,則退出程序
        while( retry_count > 0 ){
            ret = transport_sendPacketBuffer(MQTT_SERVER_IP_AND_PORT, buf, len);
            // 如果發送成功則跳出循環
            if( ret == 1 ){
                break;
            }
            delays(1);
        }
        if( !retry_count && ret != 1 ){
             UART1_Printf("3333 transport_sendPacketBuffer Error %d\n", ret);
             break;
        }
        // 每次發送之後,等兩秒
        delays(2);
    }

    UART1_Printf("disconnecting\n");
    len = MQTTSerialize_disconnect(buf, MQTT_MAX_BUF_SIZE);
    ret = transport_sendPacketBuffer(MQTT_SERVER_IP_AND_PORT, buf, len);
    if( ret != 1 ){
        UART1_Printf("4444 transport_sendPacketBuffer Error %d\n", ret);
        return -1;
    }
}

其實整個流程完全沒動,直接把例程中的main函數內容copy過來,然後進行了一下必要的修改,主要是模塊的初始化相關的東西。
到此整個移植過程就已經完成,相對比較簡單。

3,測試

因爲GPRS模塊(GA6)的IP地址是公網IP,所以我們測試得找個公網服務器來進行測試,至於Mosquitto環境搭建,不知道的可以看我前面的一篇文章—-MQTT學習——Centos7上安裝Mosquitto和使用
下面可以看看服務器的打印:
這裏寫圖片描述
這裏寫圖片描述
這是服務器那邊的打印,可以看到已經工作正常,後續有時間深入研究下MQTT協議的過程。

源碼我上傳到這裏—http://download.csdn.net/download/xj178926426/10172099
大家有興趣可以下載下來看看,模塊間串口怎麼連接在註釋中寫的比較詳細,應該下下來就直接可用。

發佈了114 篇原創文章 · 獲贊 52 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章