编写一个GSM调制解调器驱动程序?

11
我一直在开发一个应用程序,其中使用GSM调制解调器进行以下两种操作之一:使用内置的HTTP堆栈通过向服务器发送GET请求来检查其状态,或者使用UDP将数据发送到服务器。我尝试了几种不同的方法,以使这个过程尽可能可靠,并且现在我终于准备寻求帮助。
我的应用程序是为SIMCOM908模块和PIC18平台编写的(我正在使用PIC18 Explorer进行开发)。
问题在于有时调制解调器正忙于执行某些操作,会错过命令。作为人类,我会看到这一点并重新发送命令。添加MCU超时并重新发送的功能不是问题。
问题在于,调制解调器会在不同事件之后发送未经请求的响应。当调制解调器更改注册状态(与基站)时,它会响应+CGREG: 1, ...,或者当GPS就绪时GPS Ready。这些响应可以在任何时间发生,包括在命令中间(例如创建IP连接)。
这是个问题,因为我还没有想到如何处理这个问题。我的应用程序需要发送一个命令(例如连接到服务器,AT+CIPSTART="UDP","example.com",5000)。此命令将响应“OK”,然后当命令完成时响应“CONNECT OK”。然而,我需要能够对许多其他可能的响应做出反应,但我还没有想出一种方法来实现这一点。我需要在我的代码中做什么,才能等待调制解调器的响应,检查响应并根据该响应执行操作?
我的代码受限(是8位微控制器!),我希望将重复次数最小化。我该如何编写一个响应函数,以接收GSM模块(已请求或未请求)的响应,然后让我的程序知道正在发生什么?
理想情况下,我想对这些响应做一些事情。例如保持内部状态(当我听到GPS Ready时,我知道可以开启GPS等)。
也许有些事情我应该考虑,或者也许有一个开源项目已经解决了这个问题?
以下是我目前的进展:
/* Command responses */
enum {
    // Common
    OK = 0,
    ERROR,
    TIMEOUT,
    OTHER,
    // CGREG
    NOT_REGISTERED,
    // CGATT
    NOT_ATTACHED,
    // Network Status
    NO_NETWORK,
    // GPRS status
    NO_ADDRESS,
    // HTTP ACTION
    NETWORK_ERROR,
    // IP Stack State
    IP_INITIAL,
    IP_STATUS,
    IP_CONFIG,
    UDP_CLOSING,
    UDP_CLOSED,
    UDP_CONNECTING
} gsmResponse;

int gsm_sendCommand(const char * cmd) {
    unsigned long timeout = timer_getCurrentTime() + 5000;

    uart_clearb(GSM_UART); // Clear the input buffer
    uart_puts(GSM_UART, cmd); // Send the command to the module
    while (strstr(bf2, "\r") == NULL) { // Keep waiting for a response from the module
        if (timeout < timer_getCurrentTime()) { // Check we haven't timed out yet
            printf("Command timed out: %s\r\n", cmd);
            return TIMEOUT;
        }
    }
    timer_delay(100); // Let the rest of the response be received.

    return OK;
}

int gsm_simpleCommand(const char * cmd) {
    if (gsm_sendCommand(cmd) == TIMEOUT)
        return TIMEOUT;

    // Getting an ERROR response is quick, so if there is a response, this will be there
    if (strstr(bf2, "ERROR") != NULL)
        return ERROR;

    // Sometimes the OK (meaning the command ran) can take a while
    // As long as there wasn't an error, we can wait for the OK
    while (strstr(bf2, "OK") == NULL);
    return OK;
}

一个简单的命令是指特定寻找响应中的OKERROR的任何AT命令。例如AT。然而,我还会使用它来执行更高级的命令,比如AT+CPIN?,因为这意味着我将捕获整个响应,并可以进一步搜索+CPIN: READY。但是,这些都不能响应未经请求的响应。事实上,当收到未经请求的响应时,gsm_sendCommand()函数将会提前返回。
如何管理这种复杂而偶尔未经请求的状态消息?请注意,此应用程序是用C编写的,并在8位微控制器上运行!

1
你可能需要实现一个状态机(FSM): newstate = oldstate [ message ],并且也许要考虑另一端也是状态机。 - wildplasser
我想那将是其中的一部分。然而,在这种情况下,一个完整的FSM似乎不是最佳选择。我需要跟踪几个不同的自发代码的二进制状态,而不是单个系统的多个状态。我将只使用几个全局布尔类型来进行跟踪。 - dantheman
1个回答

3
同时处理未经请求的消息和对请求的响应的数据流是很困难的,因为您需要对传入的流进行解复用,并将结果分派给适当的处理程序。这有点像中断处理程序,因为您必须放下手头的工作并处理这另一部分信息,而您不一定希望得到这些信息。
一些模块具有可用于消息的次要串行端口。如果可能,您可以只在单个串行端口上显示未经请求的消息,而主端口则用于AT命令。这可能不可能,某些GSM模块将不支持次要端口上的完整命令集。
也许更好的方法是仅禁用未经请求的消息。大多数命令都需要请求状态。例如,在等待注册时,不要等待未经请求的注册消息出现,而是轮询当前的注册状态。这使您始终处于控制之下,并且您只需处理刚发送的命令的响应。如果您正在等待多个事件,可以按顺序轮询每个项目。这通常会使代码更简单,因为您只需一次处理一个响应。缺点是响应时间受轮询速率限制。
如果您计划继续使用未经请求的消息方法,建议实现一个小队列用于未经请求的消息。在等待对命令的响应时,如果响应与命令不匹配,只需将响应推入队列中。然后,在收到AT命令的响应或超时后,可以处理未经请求的消息队列。

这听起来是一种非常出色的方法。我知道使用这个模块时,次要串口不适合用于发送命令。到目前为止,我只看到过 GPS Ready+CGREG: 1 的消息。我知道你可以禁用 CGREG。我的旧代码以前是轮询的方式,这很好,因为这意味着如果我错过了不请自来的响应,我的代码也不会一直等待它。 - dantheman
1
虽然我不太喜欢实现队列的想法(因为这是一个相当内存有限的应用程序),但禁用未经请求的响应并不完全可行。我不喜欢在所有命令函数中添加处理能力以应对可能的中断,所以队列更有意义。谢谢你的回答! - dantheman
1
希望您的队列可以很小。如果您愿意让队列在堆栈上,您可能可以通过为 char* bufsize_t bufLen 添加额外的参数来设置您的 gsm_sendCommand。调用 gsm_sendCommand 的函数可以在堆栈上分配一个临时缓冲区,在 gsm_sendCommand 返回后处理队列。有很多方法可以实现这一点。 - Austin Phillips
使用我的编译器,我认为在声明要使用的缓冲区和使用malloc和free进行动态分配之间没有任何区别。我的控制器有足够的闪存,但RAM很少。如果闪存成为问题(以及用更简单的东西替换printf),我会考虑缓冲区的问题。 - dantheman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接