Pull to refresh

Управление GSM модулем с AVR

Reading time6 min
Views52K
Идея проекта: спроектировать устройство на базе микроконтроллера AVR для управления готовым GSM модулем (я выбрал модуль TC35 от SIEMENS, но можно использовать любой другой, если используется связь через последовательный порт RS232). Устройство должно быть компактным, минимально простым и надёжным.

Отправка заранее записанного в память сообщения на указанный номер должна выполняться после нажатия кнопки. Всего нужно было 6 кнопок для отправки на 6 различных номеров. Для индикации процессов были выбраны 3 светодиода (Ready, Send, Error), но в последствии был добавлен алфавитно цифровой LCD 16x2 (скорее, для отладки устройства, чем для обычного использования).

Проектировалось всё дело на плате Pinboard II (Rev 2) со стандартным процессорным модулем на ATmega16. На готовом устройстве схема была немного другой (и микроконтроллер использовался ATmega8). Программа писалась в AVR Studio 4.19. В проекте были использованы различные заголовочные файлы (#include) для переключения между Pinboard и готовым устройством.

Общая схема системы:

image

Для контроллера была выпилена такая платка:

image

Времени было много, поэтому в последствии я заказал платы у китайцев:

image

А когда с железом было закончено, следом пошёл процесс программирования. Всё написано на Си под AVR Studio 4.19. Полный проект выкладываю в конце статьи, если кому интересен полный код. Но пока поговорим об общении с GSM модулем.

Полный перечень AT команд есть на каждый модуль в его документации. Но отправка сообщения происходит несколькими командами.

//команда:
AT+CMGF=1<enter>
//ответ модуля:
OK<enter>

Переводит модуль в текстовый режим. Цифровой режим я пока не освоил (пока не было необходимости). Ответ модуля на начальных стадиях проекта никак не использовался. Но потом (когда был написан дешифратор команд) служил условием продолжения отправки или сообщения об ошибке протокола. Идём дальше:

команда:
AT+CMGS=(номер телефона)<enter>
ответ модуля:

>
отправляем сообщение:
Hello, GSM module!<ctrl-Z>
ответ модуля:
+CMGS: 62

OK

После набора сообщения, нужно отправить не Enter (0x0D) а CTRL-Z (0x1A). Ответ модуля после отправки содержит порядковый номер отправляемого сообщения.

Для отправки команд в модуль в модуль и получения ответов я использовал два кольцевых буфера со входящими и исходящими индексами.

Для большей понятности кода приведу заголовки:

#define BUF_SIZE 128
#define BUF_MASK (BUF_SIZE-1)
#define IN_BUF_SIZE 64
#define IN_BUF_MASK (IN_BUF_SIZE-1)

volatile char buffer[BUF_SIZE]="";
volatile char inbuf[IN_BUF_SIZE]="$"; //inner buffer of USART
volatile uint8_t ind_in=0, ind_out=0, rxind_out=0, rxind_in=0, mess = 0;

Запись строк или отдельных символов в буфер производилась обычными функциями:

//sending RS232 with interupt
void SendByte(char byte)
        {
        buffer[ind_in++] = byte;
        ind_in &= BUF_MASK;
        }

void SendStr(char *string)
        {
        while (*string!='\n')  //check if End
                {
                SendByte(*string);
                string++;
                }
        }

А отправка производится через обработчик прерывания:

//Sending data from buffer
ISR (USART_UDRE_vect)           
        {
        UDR = buffer[ind_out++];  //запись из буфера
        ind_out &= BUF_MASK;      //проверка маски кольцевого буфера
        if (ind_in == ind_out)  //если буфер уже пуст
                {
                UCSRB &= ~(1<<UDRIE); //disable instrupt UDR empty
                UCSRB |= (1<<RXEN);   //разрешение приёма после окончания отправки
                }
        sei ();
        }

Теперь для отправки нужно записать нужную команду в буфер (включая конечный символ \n), а затем включить прерывания опустошения регистра отправки (UDR):

SendStr("AT+CMGF=1\n");
SendByte(CR);    //отправляем <Enter> (0x0D)
UCSRB &= ~(1<<RXEN);   //Запрещаем приём на время отправки
UCSRB |= (1<<UDRIE);   //Включаем прерывание и происходит отправка

Пока идёт отправка, можно отправить надпись на LCD или просто подождать (delay).
Писать в это время в буфер нельзя. Опытным путём обнаружил, что модуль не успевает обработать сплошной потом команд. А остановка происходит, когда буфер пуст (входящий и исходящие индексы равны).

И таким образом мы отправляем сообщение. В зависимости от нажатой кнопки (в главном цикле я сканирую порт) происходит отправка сообщения:

while (1)
    {
    tmp = PINC;
    switch (tmp)
        {
        case 1:  send_sms(0,NUM1); break;
        case 2:  send_sms(0,NUM2); break;
        case 3:  send_sms(0,NUM3); break;
        //и так далее...
        default: break;
        }
    Ready_Snd (); //перевод обратно в режим готовности
    }

В функцию отправки я посылаю номер выбранного сообщения (их у меня 2 типа) и номер телефона.
Можно даже отправить команду на звонок теми же AT командами. Всё зависит от необходимой функции.

Теперь о получении команд с модуля.

Модуль отправляет множество команд. Например, OK, RING, ERROR…
Иногда нужно, чтобы при получении команды контроллер смог опознать её и выполнить какое-то действие. Например, получен входящий звонок. Модуль при этом отправляет в контроллер:


RING

RING

RING

В зависимости от настроек модуля, может отправлять ещё и номер того, кто звонит. Пока нет никакой программы, контроллер ничего с этим сделать не сможет (в лучшем случае) или (в худшем) сделает что не то, а то и вовсе зависнет (не сможет выйти из прерывания).

Требования к коду обработки:
1. Минимальное количество времени на сохранение полученных команд. Никаких задержек в программе прерывания быть не должно. Потом уже с полученным массивом будем делать что угодно.

2. Сохранение всех полученных команд в одном буфере. Для разделения отдельных будем использовать символ $.

3. Распознавание распространенных команд в числовые коды. Например, OK будет 1, ERROR — 4, RING — 2.

Приведу заголовки из предыдущей статьи с поправками:

#define BUF_SIZE 128   //Исходящий буфер
#define BUF_MASK (BUF_SIZE-1)
#define IN_BUF_SIZE 64 //Входящий буфер
#define IN_BUF_MASK (IN_BUF_SIZE-1)

volatile char buffer[BUF_SIZE]="";
volatile char inbuf[IN_BUF_SIZE]="$"; //inner buffer of USART
volatile uint8_t ind_in=0, ind_out=0, rxind_out=0, rxind_in=0, mess = 0;
volatile uint8_t com_detect=0;  //сюда будет записана обнаруженная команда

#define TIMEOUT 100     //на случай если команда так и не принята

Пишем обработчик прерывания приёма данных:

//recieving Data from RS232
ISR (USART_RXC_vect)            
{
        uint8_t tmp;
        tmp = UDR;
        if (tmp == 0x0D)        //получен конец команды - <enter>
                {
                mess++; //one more message
                inbuf[rxind_in++] = '$'; //вставляем разделитель в буфер
                rxind_in &= IN_BUF_MASK;
                }
    else 
                {
                if (tmp != 0x0A) //очистка непонятного символа с модуля
                        {
                        inbuf[rxind_in++] = tmp;   //записываем в буфер
                        rxind_in &= IN_BUF_MASK;
                        }
                }
        sei ();
}

Теперь у нас все команды записаны в буфере. Можно в свободное время проверить переменную mess и если она не равна нулю — запустить обработчик команды. В самом проекте были добавлены команды для LCD экрана. Здесь я их пропущу за ненадобностью.

void rx_check_in (void)
    {
    uint8_t count=0;
    com_detect = 0;  //обнуление команды (чтобы не мешал предыдущий мусор)
    while (1)
        {
        if (inbuf[rxind_out] != '$') //обнаружен конец команды (разделитель)
                {
                com_detect ^=  inbuf[rxind_out++]; //делаем XOR полученным символам
                rxind_out &= IN_BUF_MASK;
                count++;  //считаем, сколько символов в команде
                }
        else 
                {
                rxind_out++;
                rxind_out &= IN_BUF_MASK;
                code_com (count);           //!! важная часть - раскодировать команду 
                break;
                }
        }
    }

Полученные символы мы пропускаем через мясорубку. Делаем XOR операцию. Получаем таким образом уникальный код (не уверен на счёт уникальности, но пока не подводило). R^I^N^G нам даст 0x12. O^K даст 0x04. Этот код и количество символов (в команде) сохранены в переменных com_detect (глобальная) и count. Теперь запустим обработчик:

void code_com (uint8_t count)
        {
        switch (com_detect)
                {
                case (0x12): if (count == 4) com_detect = 2; break; //R^I^N^G
                case (0x58): if (count == 5) com_detect = 3; break; //ERROR
                case (0x04): if (count == 2) com_detect = 1; break; //OK
                case (0x5C): if (count == 3) com_detect = 4; break; //ATI
                default: com_detect = 0;
                }
        }

Распознали команду. Количество символов я ввёл для надёжности на случай если в длинной команде XOR код совпадёт. Распознаваемые команды можно добавлять. Нужно только подсчитать (или макросом) XOR код желаемой команды и присвоить ей цифру.

Теперь в com_detect у нас полученная команда. Теперь устройство может отреагировать SMS сообщением на полученный звонок:

while (1)
    {
    if (mess != 0) //if we have mess in buffer
        {
        // code 
        mess--;   //minus one
        rx_check_in ();  //распознаём отдельную команду
        if (com_detect == 2)   //если была команда RING (код 2)
            {                                  //Посылаем сообщение
                                                // и принимаем входящие команды (OK)
            if (!send_sms (1,NUM0)) ErrMes (); //если после отправки не было команды OK
            }                                  //тогда выдать сообщение о ошибке протокола
        com_detect = 0; //обнуляем команду
    }

Так можно обрабатывать разные полученные команды.

Итог: устройство умеет отправлять сообщение на телефон и умеет реагировать на различные команды от GSM модуля.


Спасибо за внимание.

Готовый проект для Pinboard II выкладываю как пример.
Tags:
Hubs:
+16
Comments13

Articles