Управление GSM модулем с AVR (часть 1)

AVR
Идея проекта: спроектировать устройство на базе микроконтроллера 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 и готовым устройством.

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


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


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


А когда с железом было закончено, следом пошёл процесс программирования. Всё написано на Си под 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 командами. Всё зависит от необходимой функции.

В следующей статье поговорим о приёме команд от модуля.
  • +6
  • 23 сентября 2013, 15:10
  • ilus
  • 1
Файлы в топике: gsm_mod_for_Eas.zip

Комментарии (37)

RSS свернуть / развернуть
Поделюсь своим опытом. Во времена DialUp Интернета для работы с модемом (дозвон и т. д.) в этих ваших Юниксах, часто использовали специальную прогу – chat, которой скармливали специальный скрипт. В скрипте (если очень упростить) было написано, что посылать модему и какие варианты ответа от него ждать (и как на них реагировать). Например, на ATDT в ответ мы ожидаем OK, CONNECT (что хорошо) BUSY, NO CARRIER, NO ANSWER и т. д. (что плохо, ошибка). ИМХО, очень удачный и универсальный получался механизм.

Я написал аналогичную функцию для работы с GSM модемом.

Сама функция


//---------------------------------------------------------------------------------------------------------------------
int MODEM_Chat(const char * SendCommand, const char * WaitResult[], DWORD Timeout) {

	WORD ResultsCount = 0;

#ifdef _DBG_MODEM
	if(SendCommand) {
		//To modem log
		_DBG("To modem send: ");
		for(BYTE j = 0; SendCommand[j]; j++) {
			if(SendCommand[j] >= 32) _DBG("%c", SendCommand[j]); else _DBG("[%02X]", SendCommand[j]);
		}
		_DBG("\n");
	}
#endif


	//From modem log
#ifdef _DBG_MODEM
	_DBG("From modem wait: ");
#endif
	for(BYTE i = 0; WaitResult[i]; i++) {
#ifdef _DBG_MODEM
		_DBG("%d - ", i);
		for(BYTE j = 0; WaitResult[i][j]; j++) {
			if(WaitResult[i][j] >= 32) _DBG("%c", WaitResult[i][j]); else _DBG("[%02X]", WaitResult[i][j]);
		}
		_DBG(" ");
#endif
		ResultsCount++;
	}
#ifdef _DBG_MODEM
	_DBG("\n");
#endif

	WORD Offsets[ResultsCount];

	for(BYTE i = 0; WaitResult[i]; i++) {
		Offsets[i] = 0;
	}

	if(SendCommand && *SendCommand) {
		//Flush modem RX
		BYTE dummy;
		while(MODEM_GetChar(&dummy, MODEM_RX_FLUSH_TIMEOUT)) {
#ifdef _DBG_MODEM
			if(dummy >= 32)_DBG("%c", dummy); else _DBG("[%02X]", dummy);
#endif
		}

		if(!MODEM_Send(SendCommand, Timeout)) return ERROR_MODEM_IO_TIMEOUT;
	}

#ifdef _DBG_MODEM
	_DBG(">>");
#endif

	for(;;) {
		BYTE data;
		if(!MODEM_GetChar(&data, Timeout)) break;

#ifdef _DBG_MODEM
		if(data >= 32)_DBG("%c", data); else _DBG("[%02X]", data);
#endif

		for(int i = 0; WaitResult[i]; i++) {
			if(WaitResult[i][Offsets[i]] != data) Offsets[i] = 0;

			if(WaitResult[i][Offsets[i]] == data) {
				Offsets[i]++;
				if(WaitResult[i][Offsets[i]] == 0) {
#ifdef _DBG_MODEM
					_DBG("\nResult: %s\n", WaitResult[i]);
#endif
					return i;
				}
			}
		}
	}
#ifdef _DBG_MODEM
	_DBG("\n");
#endif
	return ERROR_MODEM_IO_TIMEOUT;
}



Реализация, возможно, выглядит страшно, но использовать ее просто. Например, проверка ввода пин


        const char * PinResponses[] = {"CPIN: READY", "ERROR", "BLABLABLA", NULL};
	error = MODEM_Chat("AT+CPIN?\r\n", PinResponses, MODEM_NORMAL_CMD_TIMEOUT);
	if(error == 0) {
		_DBG("[IFNO] PIN READY\n");
	}
	...


PinResponses – это массив возможных ответов.
MODEM_Chat() в данном случае посылает команду «AT+CPIN?\r\n» и ждет ответ. Возвращаемое значение будет
0 – модем ответил «CPIN: READY»
1 – модем ответил «ERROR»
2 — модем ответил «BLABLABLA»
-1 – тамаут (модем ничего не ответил, либо ответ модема не содержится в массиве ожидаемых значений)

Думаю, идея понятна. Получилось ИМХО, очень удобно.
+4
  • avatar
  • e_mc2
  • 23 сентября 2013, 17:20
Немного поясню почему я решил именно так реализовать чат. Можно было проще, сохранять полученные символы в буффер, а потом просто сравнивать буффер с возможными вариантами ответа. Но я хотел обрабатывать данные на лету, без сохранения ответа в буффер. Поэтому реализовал именно так.

Приведу еще один пример использования – отправка SMS для SIM300D


//---------------------------------------------------------------------------------------------------------------------
error_t _MODEM_SendSMS(const char * PhoneNumber, const char * Message) {
	const char * SmsResponce[] = {"OK", ">", "ERROR", NULL};


	if(!MODEM_Send("AT+CMGS=\"", MODEM_NORMAL_CMD_TIMEOUT)) {
		_DBG("[ERR] MODEM send data timeout\n");
		return ERROR_MODEM_IO_TIMEOUT;
	}

	if(!MODEM_Send(PhoneNumber, MODEM_NORMAL_CMD_TIMEOUT)) {
		_DBG("[ERR] MODEM send data timeout\n");
		return ERROR_MODEM_IO_TIMEOUT;
	}

	int ChatResult = MODEM_Chat("\"\r", SmsResponce, MODEM_NORMAL_CMD_TIMEOUT);
	if(ChatResult != 1) {
		_DBG("[ERR] MODEM invalid SMS mode response\n");
		return ERROR_MODEM_UNEXPECTED_RESPONSE;
	}

	if(!MODEM_Send(Message, MODEM_NORMAL_CMD_TIMEOUT)) {
		_DBG("[ERR] MODEM send data timeout\n");
		return ERROR_MODEM_IO_TIMEOUT;
	}

	ChatResult = MODEM_Chat("\x1A", SmsResponce, MODEM_SEND_SMS_TIMEOUT);
	if(ChatResult != 0)  {
		_DBG("[ERR] MODEM SMS sending error %d\n", ChatResult);
		return ERROR_MODEM_UNEXPECTED_RESPONSE;
	}

	return ERROR_SUCCESS;
}

+2
Я так понял, у вас эта программа на PC работает?
0
Я так понял, у вас эта программа на PC работает?

Нет, это код с МК. Правда реализация не самая универсальная – используется массив с переменной длинной (Variable length array).
0
У меня используется кольцевой буфер. Но реализация отправки не оптимальная. Не получается отправлять из буфера одновременно (имею ввиду чередование операций) с записью. То порт обгоняет, то программа на второй круг уходит и сбой получается.
0
Не совсем понял, что значит

То порт обгоняет, то программа на второй круг уходит

Если у вас в модеме включено «эхо», то в ответ на команду вы прочитаете саму команду (эхо) + ответ модема. Если команда достаточно большая (эхо + ответ больше чем размер RX буфера) вы получите ситуацию, когда когда произойдет «переполнение» кольцевого буфера («голова» обгонит «хвост», ваша реализация это допускает) и вы можете потерять часть ответа.
0
Эхо включено, но пока отправка пакета команды не будет закончена, приём данных выключен. Только после отправки последнего символа, включается приём ответа.

Не получается записать несколько символов в буфер, начать отправлять (чтобы сэкономить процессорное время), а тем временем записывать в буфер остальную часть отправки.

Получается, что пока в буфер поступают данные, UDR уже успевает всё отправить (что на мой взгляд странно — последовательный порт работает быстрее, чем процессор перекидывает в буфер?) входящие и выходящие индексы буфера становятся равны и отправка останавливается (то есть буфер пуст).
0
Не получается записать несколько символов в буфер, начать отправлять (чтобы сэкономить процессорное время), а тем временем записывать в буфер остальную часть отправки.


Без глубокого погружения в код тяжело разобраться. На первый взгляд, меня смущает неатомарность операций.

Смотрите, вот код записи в ТХ буфер:

void SendByte(char byte)
        {
        buffer[ind_in++] = byte;
        ind_in &= BUF_MASK;
        }


buffer[ind_in++] — инкремент «головы»
ind_in &= BUF_MASK — зацикливание «головы» (организация кольца)

что произойдет если прерывание произойдет между первой и второй операцией?

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

ISR (USART_UDRE_vect)           
        {
        UDR = buffer[ind_out++];  //запись из буфера
        ind_out &= BUF_MASK;      //проверка маски кольцевого буфера
        if (ind_in == ind_out)  //если буфер уже пуст
                {
...
                }
...
        }


Если прерывание произойдет после ind_in &= BUF_MASK — то будет сравниваться (if (ind_in == ind_out)) «зацикленный» ind_in с «зацикленным» ind_out. Все ОК.

А вот если прерывание произойдет между операциями buffer[ind_in++] и ind_in &= BUF_MASK — то будут сравниваться «не зацикленный» ind_in с «зацикленным» ind_out. Что не есть ОК. Модификация хвоста и головы должны быть атомарными операциями.

З. Ы. А зачем в конце каждого обработчика прерывания вы явно разрешаете прерывания через sei()?
0
попробую запретить прерывания, пока обрабатывается индекс.

З. Ы. А зачем в конце каждого обработчика прерывания вы явно разрешаете прерывания через sei()?

Наверное что-то упустил. В конце прерывания компилятор сам разрешает прерывания?
0
Хотя, наверное, я перемудрил. В случае вашей реализации — все должно отработать корректно. Неатомарность есть, но в вашей реализации это не должно быть критичным.

 В конце прерывания компилятор сам разрешает прерывания?

В данном случае — да. Хотя ISR  это макрос и там не так все просто. Можно почитать здесь.
0
Попробую самый верный способ проверить — на практике…
0
Ага… СМС на английском — это халява… Несколько строк. А по русски отправить? Вот тут пляски будут! :)
Символы конца строки и перевода строки могут быть переопределены командами модуля, поэтому просто "/n" лучше не использовать, константу ввести, для начала.
0
спасибо, "\n" используется только в контроллере. В модуль не отправляются.
0
Кстати, не понятно зачем в контроллере Вы используете '\n' но в модем его не передаете

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

SendStr("AT+CMGF=1\n");
SendByte(CR);    //отправляем <Enter> (0x0D)

Конец строки лучше искать по 0x00, '\n', в данном случае, лишний.
0
Использую этот символ исключительно как конец строки. Можно использовать и 0x00. И 0x255 тоже.
Я знаю многих, кто в асме на 8051 используют для этого $.
Наверное тут дело вкуса.
0
Наверное тут дело вкуса.

Нет. В стандарте С все «строки» — это null-terminated string (иногда говорят zero-terminated string). Компилятор сам добавляет в конец каждой «строки» 0x00. Если мы пишем char * a = «ab», то, на самом деле выделится 3 байта 'a', 'b', '\0'. Компилятор сам добавляет нулевой символ в конец. И нет смысла придумывать свой ограничитель строки. В Вашем случае, получается лишний байт (т. к. после \n компилятор добавляет 0х00).
+1
Попробую сделать так. Сам был бы рад уменьшить прогу байт на 20 ;)
0
Это дело привычки:) В ДОС при выводе строки через INT21 строка должна была заканчиваться $.
0
  • avatar
  • PRC
  • 24 сентября 2013, 09:52
ну к /r тоже относится…
Так смотрел как отправлять смс на русском?
0
На русском очень хитро все. Где-то на хабре была отличная статья.
0
Специально зарегистрировался чтоб ответить.

Сам реализовывал подобный проект для управления удаленными установками.
СМС сообщения отправку реализовал в pdu режими, хотя можно отправлять и в текстовом. Для этого нужно просто посылать конвертированый текст в UCS2 кодировки. Тривиально реализуется.

Реализовал парсер команд в виде конечного автомата. Автор статьи правда лучше все это сделал.
0
Уверяю вас, что можно сделать ещё лучше
0
поделитесь советом?
0
А где платы заказывали?
0
  • avatar
  • foxit
  • 23 сентября 2013, 21:31
Заказывал здесь.
0
Расскажите плиз поподробнее про этот сервис, как заказывать, сколько стоит, качество и т.д.
+1
А эта работает www.sky-macau.com/
0
Заказывал по e-mail переписке.
Стоило всё 40 долларов. Изначально был договор на 10 плат, но пришло 15 штук и ещё пакетик светодиодов (возможно потому, что пришлось два раза узнавать, когда будут платы). По времени обещали 10-15 рабочих дней, но вышло полтора месяца.
Качество работы вполне приличное. Сравнивал с платами Pinboard (тоже красные), если честно, мне свои понравились больше.
0
1.Сколько стоит одна такая плата?
2.Долго ждать пришлось?
3.Ссылка чета не работает.
0
Отличный проект!
Второй или третий курс?
0
  • avatar
  • x893
  • 24 сентября 2013, 00:36
Рабочий курс ;)
0
спасибо, было интересно почитать. Немного порезали глаза rs-232 (правда?!) и , но с последним Вы позже таки пояснили, что это CR.
0
грёбаный в нос парсер!
<enter>
во втором случае имеется в виду
0
Подскажите пожалуйста.
А где заказывали платы? Минимальная партия? Сроки? Цены?
0
рекомендую простой алгоритм.
п.1) Повторяйте за мной «Я буду читать комментарии»
п.2) Прочитать комментарии
п.3) повторять до просветления.
А вообще, тысячи их. Что на ибее, что (почти) всем известные itead/seed studio.
+1
У кого-то скачиваются файлы из топиков тем? у меня не получяется(((
0
  • avatar
  • Tron
  • 01 марта 2017, 19:27
Пробуй до тех пор, пока не повезет получить ответ сервера, не запоротый глюками мемкеша.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.