Пишем и парсим на ассемблере MCS-51, как на Бейсике +14


Доброго времени суток, уважаемые.

В свободное от работы время увлекаюсь программированием микроконтроллеров, на ассемблере. Пока вожусь в основном со всякими 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.




К сожалению, не доступен сервер mySQL