STM32/GD32/AT32 透過AT命令解析工具

PlayerPencil發表於2024-11-21
  • 這是一個針對 STM32/GD32/AT32 等晶片的 AT 命令解析工具。其主要功能是從串列埠接收以 “\r\n” 結尾的命令資料,並將其解析為對應的命令,然後根據命令對映表呼叫相應的處理函式來執行特定操作。

1. 使用方式

  1. 將串列埠的資料放入 bsp_parseCommand 函式,並且傳入陣列和資料長度,此函式會自動儲存傳入的資料,所以不需要一次傳入一包完整的資料。如果資料超出緩衝區大小,會自動清零。

  2. 修改命令對映表,將需要解析的命令字串和命令的回撥函式傳入,在匹配到字串之後就會自動呼叫回撥函式

    CommandMapping commandMap[MAX_COMMAND_COUNT] = {
        {"AT+COMMAND1", handleATCommand1},
        {"AT+COMMAND2", handleATCommand2},
        {"AT+RESET", handleATReset},
        // 檢視韌體資訊和重啟原因
        {"AT+FirmwareInfo", PrintFirmwareInfoC},
        // 檢視任務資訊
        {"AT+TaskInfo", PrintTaskInfo},
        {NULL, NULL} // 結束標記
    };
    
  3. 如果需要修改命令長度的緩衝區,請修改 MAX_COMMAND_LENGTH 的定義

  4. 如果需要修改命令對映表陣列的大小,請修改 MAX_COMMAND_COUNT

  5. 可以在匹配的字串後新增引數,在回撥中可自行解析。具體看下方程式碼

2. 執行流程

  • 資料接收函式
    • 進入條件:從串列埠接收到字元資料。
    • 狀態判斷:每次接收到一個字元後,會進行判斷。
      • 如果接收到的字元是 \n 或者 \r,且緩衝區索引 bufferIndex 大於 0,意味著接收到了可能完整的命令(以 \r\n 結尾),此時進入 “命令解析執行狀態”。
      • 如果接收到的字元不是 \r\n,並且緩衝區索引 bufferIndex 小於 MAX_COMMAND_LENGTH - 1,則將該字元存入命令緩衝區 commandBuffer,並將 bufferIndex 加 1,繼續保持在 “資料接收狀態” 等待下一個字元輸入。
      • 如果接收到的字元不是 \r\n,但緩衝區索引 bufferIndex 已經達到 MAX_COMMAND_LENGTH - 1,表示命令緩衝區即將溢位,此時輸出 “Command buffer overflow” 提示資訊,然後將 bufferIndex 重置為 0,繼續等待新的字元輸入,仍處於 “資料接收狀態”。
  • 命令解析函式:
    • 進入條件:在 “資料接收狀態” 下接收到 \n 或者 \r 且緩衝區有有效資料(bufferIndex > 0)。
    • 狀態描述
      • 首先將命令緩衝區 commandBuffer 中的字元序列以 \0 結尾,形成完整的字串形式的命令。
      • 然後透過 parseAndExecuteCommand 函式遍歷命令對映表 commandMap,對比接收到的命令字串與對映表中的命令。
        • 如果找到匹配的命令,輸出 “Executing command: [具體命令]” 資訊,並呼叫對應的處理函式執行相關操作,之後進入 “初始狀態”,等待下一輪的串列埠資料輸入。
        • 如果遍歷完整個命令對映表都沒有找到匹配的命令,輸出 “Unknown command: [具體命令]” 資訊,然後進入 “初始狀態”,繼續等待新的串列埠資料輸入。

3. 程式碼例程

/*************************************************************************************************************************
 * CopyRight:           PlayerPencil FreeRtos通用庫/不使用作業系統也可以使用  
 * Filename:            bsp_command.c
 * ContentIntroduction:   
 *                      該檔案實現了從串列埠接收 \r\n 結尾的命令,並根據命令呼叫相應的處理函式
 *                      CommandMapping commandMap[MAX_COMMAND_COUNT],為命令對映表,用於儲存命令和處理函式的對映關係
 *                      在呼叫命令之後,也會把匹配到的字串傳入,可以匹配後面的引數。如:
 *                      const char *volumeStr = command + strlen("AT+MP3Volume=");
 *                      uint8_t volume = atoi(volumeStr);volume就為需要的值
 *                      使用者呼叫bsp_parseCommand,可以傳入1個或者多個字元,當檢測到 \r\n 時,會呼叫相應的處理函式。裡面有一個緩衝區
 *                      根據 MAX_COMMAND_LENGTH 來設定,MAX_COMMAND_COUNT為最大的命令數量-1.
 *                     
 *                      
 *  FileHistory:
 *   VersionNumber      Date:           Author:             Content:
 *       01a           2024-11-17       PlayerPencil         建立了該檔案,完成了基礎功能的測試
 *      
 * 
 * 
 * 
 *****************************************************************************************************************************/

#include "bsp_command.h"
#include <string.h>
#include "MP3_Player.h"

#define MAX_COMMAND_LENGTH          20
#define MAX_COMMAND_COUNT           15

// 命令緩衝區
static char commandBuffer[MAX_COMMAND_LENGTH];
static uint8_t bufferIndex = 0;


void handleATCommand1(const char *command) {
    printf("Handling AT+COMMAND1: %s\n", command);
}

void handleATCommand2(const char *command) {
    printf("Handling AT+COMMAND1: %s\n", command);
}

void handleATReset(const char *command) {
    // 系統復位
    printf("Handling AT+RESET: %s\n", command);
    NVIC_SystemReset();
}

void PrintTaskInfo(const char *command) {
    // 動態分配記憶體
    char *taskListBuffer = (char *)pvPortMalloc(512);
    printf("\r\n");
    printf("\r\n*************** Task Information ***************\r\n");
    if (taskListBuffer == NULL) {
        printf("Failed to allocate memory for task list buffer\n");
        return;
    }
    size_t freeHeapSize = xPortGetFreeHeapSize();
    size_t minEverFreeHeapSize = xPortGetMinimumEverFreeHeapSize();
    vTaskList(taskListBuffer);
    printf("TaskName   TaskState   Priority  stack    TaskNum\r\n");
    printf("%s", taskListBuffer);
    printf("\r\n");
    DEBUG_PRINTF(LEVEL_DEBUG, "Current free heap size: %u bytes\n", (unsigned int)freeHeapSize);
    DEBUG_PRINTF(LEVEL_DEBUG, "Minimum ever free heap size: %u bytes\n", (unsigned int)minEverFreeHeapSize);
    printf("*************** Task Information ***************\r\n");
    printf("\r\n");
    vPortFree(taskListBuffer);
}

void PrintFirmwareInfoC(const char *command)
{
    PrintFirmwareInfo();
}

void handleATMP3Command(const char *command) {
    // 提取歌曲名
    const char *songName = command + strlen("AT+MP3=");
    // printf("Playing song: %s\n", songName);
    // 在這裡新增播放歌曲的程式碼
    // playSong(songName);
    if (!MP3_Play(songName)) {
        // printf("Failed to play song: %s\n", songName);
        DEBUG_PRINTF(LEVEL_ERROR, "Failed to play song: %s\n", songName);
    }else{
        // printf("Playing song: %s\n", songName);
        DEBUG_PRINTF(LEVEL_INFO, "Playing song: %s\n", songName);
    }
}

void handleATMP3_Pause(const char *command) {
    DEBUG_PRINTF(LEVEL_INFO, "MP3 Pause\n");
    MP3_Pause();
}

void handleATMP3_Resume(const char *command) {
    DEBUG_PRINTF(LEVEL_INFO, "MP3 Resume\n");
    MP3_Resume();
}


void handleATMP3_Stop(const char *command) {
    DEBUG_PRINTF(LEVEL_INFO, "MP3 Stop\n");
    MP3_Stop();
}

void handleATMP3Time(const char *command) {
    uint16_t time = MP3_GetTime();
    DEBUG_PRINTF(LEVEL_INFO, "Current play time: %d seconds\n", time);
}

void handleATMP3Volume(const char *command) {
    // 提取音量值
    const char *volumeStr = command + strlen("AT+MP3Volume=");
    DEBUG_PRINTF(LEVEL_INFO, "Setting volume to: %s\n", volumeStr);
    uint8_t volume = atoi(volumeStr);
    DEBUG_PRINTF(LEVEL_INFO, "Setting volume to: %d\n", volume);
    MP3_Pause();
    VS1053_SetVolume(255-volume, 255-volume);
    MP3_Resume();
}

// 定義命令對映表
CommandMapping commandMap[MAX_COMMAND_COUNT] = {
    {"AT+COMMAND1", handleATCommand1},
    {"AT+COMMAND2", handleATCommand2},
    {"AT+RESET", handleATReset},
    // 檢視韌體資訊和重啟原因
    {"AT+FirmwareInfo", PrintFirmwareInfoC},
    // 檢視任務資訊
    {"AT+TaskInfo", PrintTaskInfo},
    // 播放MP3
    {"AT+MP3=", handleATMP3Command},
    // MP3 暫停播放
    {"AT+MP3Pause", handleATMP3_Pause},
    // MP3 繼續播放
    {"AT+MP3Resume", handleATMP3_Resume},
    // MP3 停止播放
    {"AT+MP3Stop", handleATMP3_Stop},
    // 檢視當前播放時間
    {"AT+MP3Time", handleATMP3Time},
    // 設定播放音量
    {"AT+MP3Volume=", handleATMP3Volume},
    {NULL, NULL} // 結束標記
};


// 解析並執行命令
void parseAndExecuteCommand(const char *command) {
    for (int i = 0; commandMap[i].command != NULL; i++) {
        if (strncmp(command, commandMap[i].command, strlen(commandMap[i].command)) == 0) {
            printf("Executing command: %s\n", command);
            commandMap[i].handler(command);
            return;
        }
    }
    printf("Unknown command: %s\n", command);
}

// 處理逐字元或多字元輸入
void bsp_parseCommand(const uint8_t *input, uint8_t length) {
    for (uint8_t i = 0; i < length; i++) {
        if (input[i] == '\n' || input[i] == '\r') {
            // 命令結束,解析命令
            if (bufferIndex > 0) {
                commandBuffer[bufferIndex] = '\0'; // 終止字串
                parseAndExecuteCommand(commandBuffer);
                bufferIndex = 0; // 重置緩衝區索引
            }
        } else if (bufferIndex < MAX_COMMAND_LENGTH - 1) {
            // 儲存字元到緩衝區
            commandBuffer[bufferIndex++] = input[i];
        } else {
            // 緩衝區溢位處理
            printf("Command buffer overflow\n");
            bufferIndex = 0; // 重置緩衝區索引
            break;
        }
    }
}

/*************************************************************************************************************************
 * CopyRight:           PlayerPencil FreeRtos通用庫  
 * Filename:            bsp_command.h
 * ContentIntroduction:   
 *                      該檔案實現了從串列埠接收 \r\n 結尾的命令,並根據命令呼叫相應的處理函式
 *                      CommandMapping commandMap[MAX_COMMAND_COUNT],為命令對映表,用於儲存命令和處理函式的對映關係
 *                      在呼叫命令之後,也會把匹配到的字串傳入,可以匹配後面的引數。如:
 *                      const char *volumeStr = command + strlen("AT+MP3Volume=");
 *                      uint8_t volume = atoi(volumeStr);volume就為需要的值
 *                     
 *                      
 *  FileHistory:
 *   VersionNumber      Date:           Author:             Content:
 *       01a           2024-11-17       PlayerPencil         建立了該檔案,完成了基礎功能的測試
 *      
 * 
 * 
 * 
 * 
 *****************************************************************************************************************************/
#ifndef __BSP_COMMAND_H
#define __BSP_COMMAND_H
#include "bsp.h"


typedef void (*CommandHandler)(const char *command);

typedef struct {
    const char *command;
    CommandHandler handler;
} CommandMapping;


void bsp_parseCommand(const uint8_t *input, uint8_t length);
#endif // !__BSP_COMMAND_H

相關文章