Writing a GSM modem driver?
Asked Answered
S

1

11

I've been working on an application which uses a GSM modem for one of two things; check its status using the built in HTTP stack by sending a GET request to the server, or sending data to the server (using UDP). I have tried several different methods to keep this as reliable as possible, and I'm finally ready to ask for help.

My application is written for the SIMCOM908 module and the PIC18 platform (I'm using a PIC18 Explorer for development).

So the problem is sometimes the modem is busy doing something, and misses a command. As a human, I would see that and just resend the command. Adding a facility for my MCU to timeout and resend isn't an issue.

What is an issue is that the modem sends unsolicited responses after different events. When the modem changes registration status (with the cell tower) it would respond with +CGREG: 1, ... or when the GPS is ready GPS Ready. These responses can happen at any time, including in the middle of a command (like creating an IP connection).

This is a problem, because I haven't thought of a way to deal with this. My application needs to send a command (to connect to the server for example, AT+CIPSTART="UDP","example.com",5000) This command will response with 'OK', and then when the command has finished 'CONNECT OK'. However, I need to be able to react to the many other possible responses, and I haven't figured out a way of doing this. What do I need to do with my code to; wait for a response from the modem, check the response, perform an action based on that response?

I am code limited (being an 8-bit microcontroller!) and would like the keep repetition to a minimum. How can I write a response function that will take a response from the GSM module (solicited or now) and then let the rest of my program know what is happening?

Ideally, I'd like to do something with those responses. Like keep an internal state (when I hear GPS Ready, I know I can power the GPS etc.

Maybe there are some things I should think about, or maybe there's an open source project that already solves this problem?

Here's what I have so far:

/* 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;
}

A simple command is any AT command that is specifically looking for OK or ERROR in response. Something like AT. However, I also use it for more advanced commands like AT+CPIN? because it means I will have captured the whole response, and can further search for the +CPIN: READY. However, none of this actually response to the unsolicited responses. In fact, the gsm_sendCommand() function will return early when the unsolicited response is received.

What a good way to manage complex, occasionally unsolicited, status messages like this? Please take note that this application is written in C, and runs on an 8bit microcontroller!

Sparoid answered 3/2, 2013 at 11:19 Comment(2)
You probably have to implement a state machine (FSM) : newstate = oldstate [ message ], and maybe consider the other end to be state machine, too.Pursuit
That will be part of it I think. However, a full FSM in this case doesn't seem like the best option. I need to track a binary state of a couple of different unsolicited codes rather than the multiple states of a single system. I will simply have a couple of global boolean types to keep track.Sparoid
C
3

Having to handle both unsolicited messages as well as responses to requests in the same data stream is difficult since you will need to demultiplex the incoming stream and dispatch the results to the appropriate handler. It's a bit like an interrupt handler in that you have to drop what you were doing and handle this other bit of information which you were not necessarily expecting.

Some modules have a secondary serial port which can also be used for messages. If this is possible you could have unsolicited messages only appear on a single serial port while the main port is for your AT commands. This may not be possible, and some GSM modules will not support the complete command set on a secondary port.

Perhaps a better approach is to just disable unsolicited messages. Most commands all the state to be requested. eg While waiting for registration, instead of waiting for an unsolicited registration message to appear, simply poll the module for the current registration state. This allows you to always be in control, and you only have to handle the responses for the command just sent. If you're waiting for multiple events you can poll in a loop for each item in turn. This will generally make the code simpler as you only have to handle a single response at a time. The downside is that your response times are limited by your polling rate.

If you're set on continuing with the unsolicited message approach, I'd suggest implementing a small queue for unsolicited messages. While waiting for responses to a command, if the response does not match the command, just push the response on a queue. Then, when you've either received a response to your AT command or timed out you can process the unsolicited message queue afterwards.

Contagium answered 3/2, 2013 at 11:56 Comment(4)
That sounds like a really excellent way to go. I know with this module that the secondary serial port is no good for sending commands. So far, I've really only seen GPS Ready and +CGREG: 1 messages. I know you can disable the CGREG one. My old code used to poll, and that was good because it meant that if I was to missed the unsolicited response, my code wouldn't still be waiting for it.Sparoid
So while I'm not keen on the idea of implementing a queue as such (being a fairly memory limited application) disabling the unsolicited responses is not completely possible. I dislike the idea of adding processing ability to all of the command functions to account for the possibility of being interrupted so the queue makes a lot more sense. Thank you for your answer!Sparoid
@Sparoid Hopefully your queue can be small. You could probably arrange for the queue to be on the stack if you wanted by having your gsm_sendCommand take an additional couple of parameters for char* buf and size_t bufLen. Functions that call gsm_sendCommand could allocate a temporary buffer on the stack, and after gsm_sendCommand had returned, process the queue. Many ways to do this.Contagium
With my compiler, I don't think there's any difference between declaring a buffer to use, or dynamically allocating it with malloc and free. I have plenty of flash with my controller, just not much RAM. I'll look into the buffer if flash becomes a problem (along with replacing printf with something simpler!)Sparoid

© 2022 - 2024 — McMap. All rights reserved.