- •6.5.4. Кодирование программы
- •Реализация логических конструкций структурного программирования
- •Кодирование программы для ввода данных в озу
- •6.5.5. Тестирование и отладка программы
- •6.5.6. Занесение программы на рабочий носитель
- •6.5.7. Оформление документации на программу
- •6.6. Комплексная отладка микропроцессорной системы
- •Заключение
- •Список рекомендуемых источников
Кодирование программы для ввода данных в озу
Пример 6.13:
Выполнить кодирование программы работы устройства для ввода данных в ОЗУ.
Для решения поставленной задачи необходимо использовать результаты проектирования программы, полученные в примерах 6.4 6.7. При этом принятое кодирование данных представлено в таблице 6.4, а алгоритмы программы и программных модулейна рис. 6.376.50. Из их анализа следует, что многие модули выполняют ввод/вывод данных, что требует разработки архитектуры ввода/вывода проектируемого устройства для их кодирования. Архитектура ввода данных приведена на рис. 6.57, а архитектура выводана рис. 6.58.
Для уменьшения аппаратурных затрат двоичные индикаторы "Тип ввода" ("Адрес"/"Данные") подключены к свободным разрядам Q7,Q6 клавиатурного порта вывода KbdPort. Клавиатурный порт ввода имеет то же имя KbdPort (тот же адрес), что и клавиатурный порт вывода, так как обращение к ним осуществляется разными управляющими сигналами (см. рис. 6.57,а).
Рис. 6.57. Архитектура ввода устройства для ввода данных в ОЗУ:
а) ввод с клавиатуры; б) ввод управляющих переключателей
Состояние управляющих переключателей вводится через порт ввода ModePort с нулевым кодированием активных значений (см. рис. 6.57,б).
а) б)
Рис. 6.58.Архитектура вывода устройства для ввода данных в ОЗУ:
а) вывод на индикаторы дисплеев; б) таблица преобразования
В связи с использованием статической индикации каждый из знакосинтезирующих индикаторов дисплеев "Адрес" и "Данные" подключен к отдельному порту вывода. При этом порт младшего разряда дисплея "Адрес" имеет имя (адрес) ADispPort, а дисплея "Данные" DDispPort. Адреса портов более старших разрядов последовательно увеличиваются на 1 относительно адреса младшего порта (см. рис. 6.58,а).
Свечение сегмента индикатора в приведенной схеме вызывается нулевым значением управляющего сигнала. С учетом этого в таблице на рис. 6.58,б представлены управляющие 7-сегментные коды, обеспечивающие отображение соответствующего графического знака в разряде дисплея. При этом отображаемые символы шестнадцатиричных цифр приведены в колонке Hex в стандартном начертании. Эта таблица используется для преобразования двоичного кода отображаемой 16-ричной цифры из формата 8421 в семисегментный код.
Имея алгоритмы программы и программных модулей (см. рис. 6.7-6.50), структуру данных (см. табл. 6.4) и архитектуру ввода/вывода (см. рис. 6.57, 6.58), можно полностью разработать исходный текст проектируемой программы.
;ПРОГРАММА РАБОТЫ УСТРОЙСТВА ДЛЯ ВВОДА ДАННЫХ В ОЗУ
NAMEDataInputProgram
; ОПИСАНИЕ ПОИМЕНОВАННЫХ КОНСТАНТ
KbdPort = 0 ;Адреса портов
ModePort = 1 ;архитектуры
ADispPort = 2 ;ввода/вывода
DDispPort = 6
NMAX = 50 ;Константа подавления дребезга
; ОПИСАНИЕ ДАННЫХ
Data1 SEGMENTAT 1000h
Mode DB ? ;Структура
InType DB? ;данных
AddrInc DB? ;программы
AddrDec DB?
KbdImage DB4DUP(?)
NextDig DB?
Addr DW?
BufData DB?
DataDisp DB2DUP(?)
AddrDisp DB 4DUP(?)
EmpKbd DB?
KbdErr DB?
RAMErrD DB?
RAMErrA DB ?
; ОПИСАНИЕ СЛУЖЕБНЫХ ЯЧЕЕК
ATstBuff DW17DUP(?) ;Буфер адресного теста
MesBuff DB? ;Буфер сообщения "Тип ввода"
OldAddrModif DB? ;Буфер старой модификации
Data1 ENDS;адреса
Data2 SEGMENTAT 2000h
RAMData DW32768DUP(?) ;Формируемые данные
Data2 ENDS
; ОПИСАНИЕ СТЕКА
Stack SEGMENTAT 1000h
ORG 100h ;Смещение за зону данных
DW10 DUP(?)
StkTop LABEL WORD
Stack ENDS
; ОПИСАНИЕ ВЫПОЛНЯЕМЫХ ДЕЙСТВИЙ
Code SEGMENT
ASSUMECS:Code, DS:Data1, ES:Data2, SS:Stack
; ОПИСАНИЕ ПРОГРАММНЫХ МОДУЛЕЙ
; Модуль "Функциональная подготовка"
FuncPrep PROCNEAR;ГСА по рис. 6.50
MOV InType, 0 ;Тип ввода="Адрес"
MOV KbdErr, 0 ;Очистка флага ошибки
;ввода с клавиатуры
MOV BX, 0 ;Очистка адреса
MOV Addr, BX
MOV AL, ES:[BX] ;Чтение байта из RAMData ;по нулевому адресу
MOV BufData, AL ;Запись байта в буфер
MOV OldAddrModif, 18h ;Старые команды "+1", ;"1" отсутствуют
RET
FuncPrep ENDP
; 1. Модуль "Тестовый контроль ОЗУ по ШД"
DTstContr PROC NEAR ;ГСА по рис. 6.39
;Подготовка
MOV RAMErrD, 0 ;Сброс флага ошибки
LEA BX, RAMData ;Загрузка адреса и
MOV CX, LENGTHRAMData ;счетчика циклов
DTC2:MOV AX, ES:[BX] ;Сохранение ячейки
MOV ES:[BX], 5555h ;Запись в ячейку
CMP ES:[BX], 5555h ;Записано ?
JNE DTC1 ;Переход, если нет
MOV ES:[BX], 0AAAAh ;Запись в ячейку
CMP ES:[BX], 0AAAAh ;Записано ?
JNE DTC1 ;Переход, если нет
MOV ES:[BX], AX ;Восстановление ячейки
ADD BX, 2 ;Модификация адреса
LOOP DTC2 ;Все ячейки ? Переход, ;если нет
JMP SHORT DTC3 ;На выход
DTC1:MOV RAMErrD, 0FFh ;Установка флага ошибки
DTC3:RET
DTstContr ENDP
; 2. Модуль "Тестовый контроль ОЗУ по ША"
ATstContr PROC NEAR;ГСА по рис. 6.40
;Подготовка к записи
MOV RAMErrA, 0 ;Сброс флага ошибки
MOV BX, 0 ;Загрузка адреса ячейки влияния STC ;и счетчика циклов
LEA SI, ATstBuff ;Загрузка адреса буфера
ATC1:MOV AX, [BX] ;Сохранение ячейки
MOV [SI], AX ;влияния
MOV [BX],BX ;Запись адреса в ячейку влияния
ADD SI, 2 ;Модификация адреса буфера
RCL BX, 1 ;Модификация адреса ячейки ;влияния и счетчика циклов
OR BX, BX ;Все ячейки влияния ?
JNZ ATC1 ;Переход, если нет
;Подготовка к анализу
MOV BX, 0 ;Загрузка адреса ячейки влияния
STC ;и счетчика циклов
LEA SI, ATstBuff ;Загрузка адреса буфера
ATC3:MOV AX, [BX] ;Чтение из ячейки влияния
CMP AX, BX ;Содержимое = адресу ?
JNE ATC2 ;Переход, если нет
MOV AX, [SI] ;Восстановление ячейки MOV [BX], AX ;влияния
ADD SI, 2 ;Модификация адреса буфера
RCL BX, 1 ;Модификация адреса ячейки ;влияния и счетчика циклов
OR BX, BX ;Все ячейки влияния ?
JNZ ATC3 ;Переход, если нет
JMP SHORT ATC4 ;На выход
ATC2: MOV RAMErrA, 0FFh;Установка флага ошибки
ATC4:RET
ATstContr ENDP
;3. Модуль "Вывод сообщений об ошибках"
ErMesOut PROC NEAR;ГСА по рис. 6.41
CMP KbdErr, 0FFh ;Ошибка клавиатуры ?
JZ EMO1 ;Переход, если да
CMP RAMErrD, 0FFh ;Ошибка ОЗУ по ШД ?
JZ EMO2 ;Переход, если да
CMP RAMErrA, 0ffh ;Ошибка ОЗУ по ША ?
JNZ EMO3 ;Переход, если нет
; Вывод сообщения об отказе ОЗУ по ША "ErОП A"
CALL ErrRAM ;Вывод сообщения ErОП
MOV AL, 11h ;Вывод знака А
OUT DDispPort, AL
JMP SHORT EMO3 ;На выход
; Вывод сообщения об отказе ОЗУ по ШД "ErОП d"
EMO2:CALL ErrRAM ;Вывод сообщения ErОП
MOV AL, 85h ;Вывод знака d
OUT DDispPort, AL
JMP SHORT EMO3 ;На выход
; Вывод сообщения об ошибке ввода с клавиатуры "Err НБ"
EMO1:MOV AL, 61h ;Вывод знака E
OUT ADispPort+3, AL
MOV AL, 0F5h ;Вывод знака r
OUT ADispPort+2, AL
OUT ADispPort+1, AL ;Вывод знака r
MOV AL, 0FFh ;Гашение знака
OUT ADispPort, AL
MOV AL, 91h ;Вывод знака H
OUT DDispPort+1, AL
MOV AL, 41h ;Вывод знака Б
OUT DDispPort, AL
EMO3:RET
ErMesOut ENDP
; Подмодуль "Вывод сообщения ErОП"
ErrRAM PROC
MOV AL, 61h ;Вывод знака E
OUT ADispPort+3, AL
MOV AL, 0F5h ;Вывод знака r
OUT ADispPort+2, Al
MOV AL, 03h ;Вывод знака О
OUT ADispPort+1, AL
MOV AL, 13h ;Вывод знака П
OUT ADispPort, AL
MOV AL, 0FFh ;Гашение знака
OUT DDispPort+1, AL
RET
ErrRAM ENDP
; 4. Модуль "Ввод режимов"
ModeInput PROC NEAR;ГСА по рис. 6.42,а
;Подготовка
MOV Mode, 0 ;Режим="Ввод"
MOV AddrInc, 0 ;Сброс флагов
MOV AddrDec, 0 ;модификации адреса
IN AL, ModePort ;Ввод переключателей
MOV DX, ModePort ;Передача параметра
CALL VibrDestr ;Гашение дребезга
TEST AL, 01h ;"Режим" = "Просмотр"?
JNZ MI1 ;Переход, если нет
MOV Mode, 0FFh ;Режим = "Просмотр"
JMP SHORT MI2
MI1:TEST AL, 02h ;"Тип ввода" = "Адрес"?
JZ MI2 ;Переход, если да
TEST AL, 04h ;"Тип ввода" = "Данные"?
JNZ MI3 ;Переход, если нет
MOV InType, 0FFh ;Тип ввода = "Данные"
JMP SHORT MI3
MI2:MOV InType, 0 ;Тип ввода = "Адрес"
MI3:MOV AH, OldAddrModif ;Чтение старых значений
;сигналов "+1", "1"
MOV OldAddrModif, AL ;Сохранение текущих ;значений сигналов"+1","1"
XOR AL, AH ;Выделение ПФ сигналов
AND AL, AH ;"+1","1"
TEST AL, 08h ;ПФ"Адрес"="+1"?
JNZ MI4 ;Переход, если нет
MOV AddrInc, 0FFh ;Модификация адреса="+1"
MI4:TEST AL, 10h ;ПФ"Адрес"="1"?
JNZ MI5 ;Переход, если нет
MOV AddrDec, 0FFh ;Модификация адреса="1"
MI5:RET
ModeInput ENDP
; Подмодуль "Гашение дребезга"
VibrDestr PROC;ГСА по рис. 6.42,в
VD1:MOV AH, AL ;Сохранение исходного состояния
MOV BH, 0 ;Сброс счетчика повторений
VD2: IN AL, DX ;Ввод текущего состояния
CMP AH, AL ;Текущее состояние=исходному?
JNE VD1 ;Переход, если нет
INC BH ;Инкремент счетчика повторений
CMP BH, NMAX ;Конец дребезга?
JNE VD2 ;Переход, если нет
MOV AL, AH ;Восстановление местоположения
RET ;данных
VibrDestr ENDP
; 5. Модуль "Вывод сообщения о типе ввода"
InTpMesOut PROC NEAR;ГСА по рис. 6.43
MOV AL, 40H ;Сообщение "Тип ввода"="Адрес"
CMP InType, 0 ;Тип ввода = "Адрес"?
JE ITMO1 ;Переход, если да
MOV AL, 80h ;Сообщение "Тип ввода"="Данные"
ITMO1:MOV MesBuff, AL ;Сохранение сообщения
;"Тип ввода"
OR AL, 0Fh ;Вывод сообщения
OUT KbdPort, AL ;"Тип ввода"
RET
InTpMesOut ENDP
; 6. Модуль "Ввод с клавиатуры"
KbdInput PROC NEAR;ГСА по рис. 6.44
;Подготовка
LEA SI, KbdImage ;Загрузка адреса,
MOV CX, LENGTHKbdImage ;счетчика циклов и
MOV BL, 0FEh ;номера исходной строки
KI4:MOV AL, BL ;Выбор строки
AND AL, 3Fh ;Объединение номера с
OR AL, MesBuff ;сообщением "Тип ввода"
OUT KbdPort, AL ;Активация строки
IN AL, KbdPort ;Ввод строки
AND AL, 0Fh ;Включено?
CMP AL, 0Fh
JZ KI1 ;Переход, если нет
MOV DX, KbdPort ;Передача параметра
CALL VibrDestr ;Гашение дребезга
MOV [SI], AL ;Запись строки
KI2:IN AL, KbdPort ;Ввод строки
AND AL, 0Fh ;Выключено?
CMP AL, 0Fh
JNZ KI2 ;Переход, если нет
CALL VibrDestr ;Гашение дребезга
JMP SHORTKI3
KI1:MOV [SI], AL ;Запись строки
KI3:INC SI ;Модификация адреса
ROL BL, 1 ;и номера строки
LOOP KI4 ;Все строки? Переход, если нет
RET
KbdInput ENDP
; 7. Модуль "Контроль ввода с клавиатуры"
KbdInContr PROC NEAR;ГСА по рис. 6.45
;Подготовка
LEA BX, KbdImage ;Загрузка адреса
MOV CX, 4 ;и счетчика строк
MOV EmpKbd, 0 ;Очистка флагов
MOV KbdErr, 0
MOV DL, 0 ;и накопителя
KIC2:MOV AL, [BX] ;Чтение строки
MOV AH, 4 ;Загрузка счетчика битов
KIC1:SHR AL, 1 ;Выделение бита
CMC ;Подсчет бита
ADC DL, 0
DEC AH ;Все биты в строке?
JNZ KIC1 ;Переход, если нет
INC BX ;Модификация адреса строки
LOOP KIC2 ;Все строки? Переход, если нет
CMP DL, 0 ;Накопитель = 0?
JZ KIC3 ;Переход, если да
CMP DL, 1 ;Накопитель = 1?
JZ KIC4 ;Переход, если да
MOV KbdErr, 0FFh ;Установка флага ошибки
JMP SHORTKIC4
KIC3:MOV EmpKbd, 0FFh ;Установка флага
;пустой клавиатуры
KIC4:RET
KbdInContr ENDP
; 8. Модуль "Преобразование очередной цифры"
NxtDigTrf PROC NEAR;ГСА по рис. 6.46
CMP EmpKbd, 0ffh ;Пустая клавиатура?
JZ NDT1 ;Переход, если да
CMP KbdErr, 0FFh ;Ошибка клавиатуры?
JZ NDT1 ;Переход, если да
;Подготовка
LEA BX, KbdImage ;Загрузка адреса
MOV DX, 0 ;Очистка накопителей
;кода строки и столбца
NDT3: MOV AL, [BX] ;Чтение строки
AND AL, 0Fh ;Выделение поля клавиатуры
CMP AL, 0Fh ;Строка активна?
JNZ NDT2 ;Переход, если да
INC DH ;Инкремент кода строки
INC BX ;Модификация адреса
JMP SHORTNDT3
NDT2:SHR AL, 1 ;Выделение бита строки
JNC NDT4 ;Бит активен? Переход, если да
INC DL ;Инкремент кода столбца
JMP SHORTNDT2
NDT4:MOV CL, 2 ;Формирование двоичного
SHL DH, CL ;кода цифры
OR DH, DL
MOV NextDig, DH ;Запись кода цифры
NDT1:RET
NxtDigTrf ENDP
; 9. Модуль "Формирование информации"
InfoForm PROC NEAR;ГСА по рис. 6.47
MOV AL, KbdErr ;Есть ошибки?
OR AL, RAMErrD
OR AL, RAMErrA
JNZ InF1 ;Переход, если да
CMP EmpKbd, 0FFh ;Пустая клавиатура?
JNE InF2 ;Переход, если нет
MOV BX, Addr ;Чтение адреса
CMP AddrInc, 0FFh ;Инкремент адреса?
JNE InF3 ;Переход, если нет
INC BX ;Инкремент адреса
JMP SHORTInF4
InF3:CMP AddrDec, 0ffh ;Декремент адреса?
JNE InF1 ;Переход, если нет
DEC BX ;Декремент адреса
InF4:CMP Mode, 0 ;Режим = "Ввод"?
JNE InF5 ;Переход, если нет
MOV DL, BufData ;Чтение данных из буфера
MOV SI, Addr ;Запись данных по
MOV ES:[SI], DL ;старому адресу
JMP SHORTInF5
InF2:CMP InType, 0 ;Тип ввода="Адрес"?
JNE InF6 ;Переход, если нет
MOV BX, Addr ;Чтение адреса
MOV CL, 4 ;Сдвиг адреса на
SHL BX, CL ;тетраду
OR BL, NextDig ;Включение очередной
;цифры в адрес
InF5:MOV DL, ES:[BX] ;Чтение данных по новому адресу
MOV Addr, BX ;Запись нового адреса
JMP SHORTInF7
InF6:MOV DL, BufData ;Чтение данных из буфера
MOV CL, 4 ;Сдвиг данных на тетраду
SHL DL, CL
OR DL, NextDig ;Включение очередной
;цифры в данные
InF7:MOV BufData, DL ;Запись новых данных в буфер
InF1:RET
InfoForm ENDP
; 10. Модуль "Формирование массива отображения"
DispForm PROC NEAR;ГСА по рис. 6.48
; Этот модуль реализован для формирования лишь одного массива отобра-
;жения. Формирование двух массивов DataDisp и AddrDisp должно осуществля-
;ться путем его двукратного вызова с передачей соответствующих параметров
;перед каждым вызовом
; Входные параметры:
; BX адрес входных данных
; SI адрес массива отображения
; CH количество цифр в массиве
MOV AL, KbdErr ;Есть ошибки?
OR AL, RAMErrD
OR AL, RAMErrA
JNZ DF1 ;Переход, если да
MOV AX, [BX] ;Чтение данных
DF2:MOV DX, AX ;Копирование данных
AND AL, 0Fh ;Выделение младшей тетрады
MOV [SI], AL ;Запись в память
MOV AX, DX ;Восстановление данных
MOV CL, 4 ;Сдвиг данных
SHR AX, CL ;на тетраду
DEC CH ;Все цифры
JNZ DF2 ;Переход, если нет
DF1:RET
DispForm ENDP
; 11. Модуль "Вывод числовой информации"
NumInfOut PROC NEAR ;ГСА по рис. 6.49
; Этот модуль реализован для вывода информации из одного массива отобра-
;жения на соответствующий знакосинтезирующий дисплей. Вывод информации
;на оба дисплея ("Адрес", "Данные") должен осуществляться путем его двукрат-
;ного вызова с передачей соответствующих параметров перед каждым вызовом.
; Входные параметры:
; SI адрес входного массива отображения
; DX номер младшего порта дисплея
; CX количество выводимых знаков
MOV AL, KbdErr ;Есть ошибки?
OR AL, RAMErrD
OR AL, RAMErrA
JNZ NIO1 ;Переход, если да
LEA BX, TrfTabl ;Загрузка адреса таблицы
;преобразования
NIO2:MOV AL, [SI] ;Чтение цифры
XLAT ;Преобразование цифры
OUT DX, AL ;Вывод цифры в порт
INC SI ;Модификация адреса
INC DX ;и номера порта
LOOP NIO2 ;Все цифры? Переход, если нет
NIO1:RET
NumInfOut ENDP
; Таблица преобразования (см. рис. 6.58,б)
TrfTabl DB03h,9Fh,25h,0Dh,99h,49h,41h,1Fh
DB01h,09h,11h,0C1h,63h,85h,61h,71h
; МАКРОУРОВЕНЬ ПРОГРАММЫ ;ГСА по рис. 6.38
Start:
;Системная подготовка
MOV AX, Data1 ;Инициализация
MOV DS, AX ;сегментных
MOV AX, Data2 ;регистров
MOV ES, AX
MOV AX, Stack
MOV SS, AX
LEA SP, StkTop ;и указателя стека
CALL FuncPrep ;Функциональная подготовка
Cont:CALL DTstContr ;Тест ОЗУ по ШД
CALL ATstContr ;Тест ОЗУ по ША
CALL ErMesOut ;Вывод сообщений об ошибках
CALL ModeInput ;Ввод режимов
CALL InTpMesOut ;Вывод сообщения о типе ввода
CALL KbdInput ;Ввод с клавиатуры
CALL KbdInContr ;Контроль ввода с клавиатуры
CALL NxtDigTrf ;Преобразование очередной
;цифры
CALL InfoForm ;Формирование информации
LEA BX, BufData ;Передача параметров
LEA SI, DataDisp ;для формирования массива
MOV CH, 2 ;отображения данных
CALL DispForm ;Формирование массива
;отображения
LEA BX, Addr ;Передача параметров
LEA SI, AddrDisp ;для формирования массива
MOV CH, 4 ;отображения адреса
CALL DispForm ;Формирование массива
;отображения
LEA SI, DataDisp ;Передача параметров
MOV DX, DDispPort ;для вывода информации на
MOV CX, 2 ;дисплей "Данные"
CALL NumInfOut ;Вывод числовой информации
LEA SI, AddrDisp ;Передача параметров для
MOV DX, ADispPort ;вывода информации
MOV CX, 4 ;на дисплей "Адрес"
CALL NumInfOut ;Вывод числовой информации
JMP Cont ;Замыкание программного кольца
; Две нижеследующих строки необходимы для начального запуска програм-
;мы в составе МПС. Они не используются при работе с программой в составе
;персонального компьютера.
ORGSTART_OFFSET ;Эти строки обеспечивают
JMP FAR PTRStart ;начальный запуск программы
;при включении устройства
Code ENDS
ENDStart
Пусковая команда программы JMP FAR PTRStart должна располагаться в ячейках ПЗУ с физическим адресом FFFF0h, который первым появляется на ША после включения питания. Для этого в исходном тексте программы она должна находиться по адресу трансляции с внутрисегментным смещением START_OFFSET, которое с учетом базового (начального) адреса сегмента программного кода обеспечит формирование требуемого пускового физического адреса. Учитывая принцип вычисления физического адреса памяти (см. подраздел 2.2.6) и размещение ПЗУ в самых старших адресах, можно получить формулу для вычисления смещения STARTOFFSET, которая имеет вид:
STARTOFFSET = VROM 16,
где VROM объем ПЗУ МПС в байтах.
Необходимый объем памяти для размещения программного кода определяется после первоначальной трансляции программы с произвольным числовым значением смещения STARTOFFSET. После этого с учетом используемых ИМС постоянной памяти выбирается объем ПЗУ VROMи точно рассчитывается значение смещения STARTOFFSET. Далее оно вносится в исходный текст программы, и выполняется ее повторная трансляция.
Пример 6.14:
Определить внутрисегментное стартовое смещение STARTOFFSET для программы ввода данных в ОЗУ, если в результате ее первоначальной трансляции объем сегмента программного кода составил 804 байта.
Для размещения сегмента программного кода размером 804 байта целесообразно использовать ПЗУ объемом 2 кбайт, построенное на ИМС К573РФ5. Отсюда VROM = 2К = 2048. Представляя все числа в 16-ричной форме, получим
VROM =800h, 16=10h, STARTOFFSET = 800h10h=7F0h.
В процессе трансляции и компоновки выявляются и исправляются все синтаксические ошибки, содержащиеся в исходном тексте разрабатываемой программы. В результате этого будет получен исходный текст и листинг программы, не содержащие синтаксических ошибок. Однако, это отнюдь не значит, что полученная программа свободна от ошибок вообще. Все остальные ошибки выявляются и устраняются на этапе тестирования и отладки программы.