В свободное от работы время увлекаюсь программированием микроконтроллеров, на ассемблере. Пока вожусь в основном со всякими PIC(12,16) и AVR, но и MCS-51 не брезгую, тем более что именно с них я собственно и начал. Уровень мой — «вечно начинающий». Это типа светодиодиком уже умею мигать, даже по таймеру.
Сейчас поставил себе задачу написать программу для взаимодействия с GSM модулем SIM900. Для этого необходимо уметь слать что-то в UART, и принимать что-то оттуда.
По грамотному на все это дело заводится 2 кольцевых буфера: на прием, и на передачу. Вроде как классика жанра, ничего нового не придумаешь. Но я такой человек, что не люблю костыли, но при этом обожаю велосипеды. Захотелось придумать свой. Итак:
1. У меня не мультизадачная система, предполагаеться отправка 1 команды в модуль, затем ожидание 1 ответа. А значит отправку уже можно делать не на прерываниях, а просто в основном цикле. Обойдемся без буфера на отправку.
2. Ответ от модуля меня устраивает только один (хотя если проверять например уровень сигнала, это уже не так), и я ожидаю, каким он должен быть. Поэтому я решил обойтись без буфера и парсить тоже прямо в основном цикле.
3. И чтобы все было удобно и красиво, оформить все это дело в макросы.
Основой моего кода стал пример из статьи «МК для начинающих» из журнала «Радиолюбитель», №3 за 2008г.
Сперва «печатаем»:
; Тип: процедура, вызываемая в макросе
; Передаваемые параметры: String,Destiny_
; Возвращаемое значение: отсутствует
; Данная процедура позволяет строчить как на Бейсике, примеры:
; Print 'First string',UART ; Вывод в UART
; Print 'Second string',LCD ; Вывод на LCD
UART equ 0
LCD equ 1
Print macro String;,Destiny_ ; String - строка для вывода, Destiny_ - пункт назначения: Display или UART
mov destiny,#Destiny_
call Output ; При вызове процедуры мы автоматически заныкаем в стек адрес следующей команды. По факту это будет адрес 1 символа строки
db String,0Dh,0Ah,0
endm
Print_data segment data
rseg Print_data
destiny: ds 1
Print_code segment code
rseg Print_code
Output: ; Описываем процедуру, вызываемую из макроса
pop DPH ; Извлекаем из стека в DPTR адрес возврата,
pop DPL ; он понадобится нам в дальнейшем
Loop_Output:
clr A
movc A, @A+DPTR ; Получаем очередной символ из строки
inc DPTR ; Сразу берем следующий символ
jnz Output_Symbol ; Если не конец строки, продолжаем вывод
jmp @A+DPTR ; Иначе возврат
Output_Symbol:
;------ Тут выводим очередной символ на дисплей или в UART -------------------------
push ACC ; Сохраним содежимое акумулятора, там очередной символ из строки
mov A, Destiny
jz Out_UART ; Смотрим пункт назначения строки
Out_LCD:
pop ACC ; Восстановим символ из строки
call Out_2_LCD ; Выводим на дисплей
jmp Loop_Output ; Повторяем цикл вывода
Out_UART:
pop ACC ; Восстановим символ из строки
jnb TI, $ ; Ждем окончания предыдущей передачи
clr TI ; Предыдущий байт ушел, сбросим флаг передачи
mov SBUF, A ; Отправим байт
jmp Loop_Output ; Повторяем цикл вывода
В сравнении с оригиналом я только добавил возможность вывода в несколько «точек назначения».
Увидев как получилось просто и красиво, я решил сделать и парсинг подобным образом. Вот что получилось:
; Тип: процедура, вызываемая в макросе
; Передаваемые параметры: String,Label,waiting_value
; Возвращаемое значение: jmp на Label в случае ошибки парсинга String
; Данная процедура позволяет парсить легко и непринужденно, примеры:
; Parsing 'First string',UART_Error,1000 ; Ждем строку из UART на протяжении 1000 мс, если ошибка парсинга, переход на метку UART_Error
; Parsing 'Second string',UART_Error,0 ; Ждем строку из UART, если приходит с ошибкой, переход на метку UART_Error, если не приходит вообще ничего - ждем вечно
Parsing macro String,Label,waiting_value ; String - строка для сравнения, Label - метка для перехода в случае ошибки, waiting_value - время ожидания каждого символа строки в Х*100 мс (при 11,0592 MHz)
mov parsing_delay,#waiting_value ; Минимальное время ожидания - 100 мс, максимальное - 25,5 сек
call Intput ; При вызове процедуры мы автоматически заныкаем в стек адрес следующей команды. По факту это будет адрес 1 символа строки
db String,0Dh,0Ah,0
jbc parsing_error,Label ; Проверяем признак ошибки парсинга
endm
Parsing_data segment data
rseg Parsing_data
parsing_delay: ds 3
Parsing_bit segment bit
rseg Parsing_bit
parsing_error: dbit 1
Parsing_code segment code
rseg Parsing_code
Intput: ; Описываем процедуру, вызываемую из макроса
clr RI ; Сбросим признак последнего принятого байта, чтобы всякий мусор принятый до этого момента, не приводил к ошибке парсинга
pop DPH ; Извлекаем из стека в DPTR адрес возврата,
pop DPL ; там находиться адрес 1 символа строки
Loop_Intput:
clr A
movc A, @A+DPTR ; Получаем очередной символ из строки
inc DPTR ; Сразу берем следующий символ
jnz Wait ; Если не конец строки, проверяем
jmp @A+DPTR ; А теперь маленький грязный хак. В DPTR у нас адрес команды после строки, вернемся по этому адресу
Wait: ; Принимаем и сравниваем полученный символ с символом из строки
push ACC ; Сохраним символ из флеша
mov A, parsing_delay
jz Always_Wait ; Если задержка ожидания = 0, ждем входящий байт неограничено долго
mov parsing_delay+2,#0
mov parsing_delay+1,#90 ; Задержка расчитана на кварц 11,0592 МГц
Loop_Wait:
jb RI, Stop_Wait ; Если принят байт, прекращаем ожидание
djnz parsing_delay+2,Loop_Wait
jb RI, Stop_Wait
djnz parsing_delay+1,Loop_Wait
jb RI, Stop_Wait
mov parsing_delay+1,#90 ; Задерка расчитана на кварц 11,0592 МГц
djnz parsing_delay,Loop_Wait ; Цикл ожидания входящего байта
pop ACC ; Время ожидания байта истекло, восстановливаем АСС
jmp Error_Intput ; Выход по превышению времени парсинга
Always_Wait:
jnb RI, $ ; Ждем входящий байт
Stop_Wait:
mov parsing_delay,SBUF ; Используем ячейку задержки для сравнения полученого символа с символом из строки
clr RI
pop ACC ; Восстановим символ из флеша
cjne A,parsing_delay,Error_Intput ; Если хоть 1 полученный символ не совпал, на выход без восстановления АСС
jmp Loop_Intput ; Иначе повторяем цикл сравнения
Error_Intput: ; Выход по ошибке парсинга, либо превышению времени ожидания
clr A
movc A, @A+DPTR
inc DPTR
jnz Error_Intput ; Ищем конец строки
setb parsing_error ; Устанавливаем признак ошибки парсинга
jmp @A+DPTR ; А теперь маленький грязный хак. В DPTR у нас адрес команды после строки, вернемся по этому адресу
Кварц 11,0592 МГц позволяет «принтить» и парсить строки на скорости 57600 bps прямо в основном цикле без каких-либо пропусков символов. При кварце в 22,1184 Мгц ожидаемо можно будет проделывать аналогичное на скорости 115200 bps.
Комментарии (5)
d_nine
05.09.2017 18:39На 115200 bps проблем быть не должно, там где кварц позволяет сам использую, правда был один пациент Атмеловский, который с любым кварцем выше 9600 bps не прыгал. На отечественном 1882ВЕ53У столкнулся с другой белой: стояла задача хранить n записей (по 8 байт каждая) в EEPROM ПЗУ с использованием сдвига при получении новой записи. При постраничной записи возникла проблема, начиная с 4-й записи в последний байт каждой записи пишется ересь (причем везде одинаковая). Использовать побайтовую запись желания нет, ибо время на "перетасовку" и длинна кода вырастает до неприличного. Пробовал просто писать по тем же адресам как страницы заранее заготовленные, так и побайтово — все корректно отрабатывается. Есть мысли с чем это может быть связано?
Alexeyslav
06.09.2017 10:08Первым делом проверил бы не переполняется ли стек…
d_nine
07.09.2017 12:52Переполнения нет, это первым делом и проверил, чтобы началось переполнение надо писать 240+ записей, но на этот случай заранее ограничение написано. Ещё наткнулся на неприятный костыль: что бы считать дамп EEPROM ПЗУ фитоновским программатором приходиться после заливки прошивки принудительно писать в нее пустой массив ( OxFF ) иначе не снимает дамп (отображает как пустой).
ulole
07.09.2017 21:18Непонятно, куда и что вы пишете — пустой массив в ПЗУ? Иначе не снимает дамп… Может и снимает, те самые нули, что перед этим туда записали. Или не читает свежезалитую прошивку? Поясните. Я сам много лет юзаю фитоновский программатор(старый, LPT-шный), и ни разу он меня не подводил. Сколько залоченых авр-ок он воскресил — не счесть…
Alexeyslav
Кстати, заодно такой подход использования ресурсов сбивает с толку дизассемблер…