Projecto de
Arquitectura de Computadores
Sistema de Gestão de Comboios
IST - Taguspark
2005/2006
1 – Objectivos
Este projecto pretende exercitar as componentes fundamentais de uma cadeira de
Arquitectura de Computadores, nomeadamente a programação em linguagem assembly,
os periféricos e as interrupções.
O objectivo deste projecto é implementar um sistema de gestão com dois comboios
sobre um circuito predefinido, com semáforos e sensores, de modo que os comboios
possam circular nesse circuito sem chocarem.
2 – Especificação do projecto
2.1 – O circuito de comboios
A figura seguinte representa o circuito das maquetas a usar neste trabalho, podendo verse:
• os elementos do circuito, que podem ser:
- simples (troço de linha recta):
- agulhas, em forma de Y (uma entrada, duas saídas), que podem ser
colocados em duas posições (esquerda e direita, vista por quem entra no
cruzamento pelo ponto comum);
• os semáforos, numerados (incluindo os da passagem de nível). Cada
semáforo tem quatro cores possíveis (verde, amarelo, vermelho e cinzento –
esta última correspondente ao semáforo apagado);
• os sensores, numerados. Quando uma locomotiva passa por um sensor, gera
dois eventos, correspondentes a cada um dos topos (frente e trás);
• dois comboios, com uma locomotiva (vermelha) e 2 ou 3 carruagens, em que
se assume que:
− os comboios só andam para a frente (no sentido contrário ao dos
ponteiros do relógio), o que significa que só as agulhas nos elementos 1 e
22H podem afectar o percurso dos comboios;
− só há três velocidades: nula, média (3) e alta (7)
• uma estação, onde os comboios devem parar para receber/largar passageiros.
1
2.2 – Troços, semáforos e sensores
O circuito está dividido logicamente em 4 troços, delimitados por semáforos, que
correspondem à unidade mínima de controlo do circuito. Os semáforos 4 e 5 não são
para os comboios, mas para carros numa passagem de nível.
A zona das agulhas (com duas entradas e duas saídas) não é um troço, mas de qualquer
modo constitui uma zona que não pode ser percorrida simultaneamente pelos comboios.
O conjunto das agulhas só tem dois estados possíveis, correspondentes a trocar de oval
(interna  externa e vice-versa).
Os semáforos dão indicação visual se um comboio pode prosseguir ou não. Sempre que
um comboio entrar num troço, o semáforo final do troço anterior deve ficar vermelho.
Cada troço (ou zona das agulhas) só pode ser percorrido por um comboio de cada vez.
Um comboio só pode avançar para um troço se este estiver livre.
Um semáforo a verde indica que um comboio pode prosseguir à velocidade máxima,
enquanto um semáforo amarelo indica que a velocidade máxima permitida é a média.
Neste circuito, todos os semáforos são verde/vermelhos, com excepção do semáforo que
antecede a estação (o 3), que é amarelo/vermelho, e dos da passagem de nível (4 e 5),
que são cinzento/vermelho, um ao contrário do outro (para a luz vermelha alternar entre
um e outro).
Cada troço tem tipicamente 3 sensores de passagem (inicial, médio e final). As
excepções são:
•
o troço da estação, que não tem sensor inicial,
•
o troço que termina no semáforo 0, que tem dois sensores intermédios.
2
2.3 – Funcionamento global do sistema
É a passagem das locomotivas pelos sensores que deve determinar o estado dos
semáforos. Assim:
•
quando um comboio avança para um troço, o semáforo que o precede deve
mudar para vermelho mal a locomotiva passa pelo sensor inicial do novo troço;
•
no entanto, o troço do qual o comboio acabou de sair (troço anterior) só deve ser
libertado quando o comboio estiver já totalmente dentro do novo troço. Isto
significa que, num dado instante, um comboio pode ter dois troços reservados
para si.
O sensor em que se considera que um comboio já está totalmente dentro do troço
depende das suas características. No troço da estação, por exemplo, que é pequeno, tal
só deve ser feito no sensor final. Nos outros, todavia, pode logo ser feito no sensor
intermédio.
No caso particular da zonas das agulhas, um comboio só pode avançar se:
•
a zona das agulhas estiver reservada para esse comboio
•
o troço seguinte à zona das agulhas também estiver reservado para esse
comboio.
É muito importante que só se reserve o conjunto (agulhas + troço seguinte) e não
apenas de um deles, senão os comboios podem bloquear-se mutuamente (como
exercício, veja em que caso isto pode suceder).
A zona das agulhas deve ser libertada quando o troço que a precede (relativo ao
comboio que acabou de passar por essa zona) também for libertado.
Os comboios devem andar à velocidade máxima, excepto quando:
1. passarem num sensor intermédio de um troço cujo semáforo no seu final está a
vermelho (caso em que devem reduzir para velocidade média), ou
2. passarem num sensor final imediatamente antes de um semáforo amarelo (caso
em que devem reduzir para velocidade média), ou
3. chegarem a um sensor final imediatamente antes de um semáforo vermelho
(caso em que devem parar até o semáforo deixar de estar a vermelho), ou
4. no troço da estação (em que deve parar durante algum tempo, após o qual o
comboio só deve arrancar quando o semáforo final desse troço não estiver a
vermelho).
Um comboio em velocidade média deve passar à máxima mal passe num sensor em que
não se verifique uma das condições anteriores.
Quando um comboio passa pelo sensor de entrada na estação, deve escrever “Estação:
comboio #” na 1ª linha do LCD, em que “#” é o número do comboio que entrou na
estação. Quando esse comboio sai da estação (passa no sensor após a estação ou o topo
de trás da locomotiva passa no sensor final do troço da estação – à escolha), a linha do
LCD deve ficar em branco.
Quando um comboio passa pelo sensor antes da passagem de nível (sensor 6), os
semáforos desta (normalmente ambos a cinzento) começam a piscar, com vermelho
alternadamente num e noutro, com uma cadência de 0,5 segundos para cada lado.
Quando passar pelo sensor a seguir (sensor 9), os dois semáforos voltam a ficar
3
cinzentos (indicando o fim da passagem do comboio). A passagem do comboio na
passagem de nível deve ser indicada na 2ª linha do LCD (com “Passagem de nível:
comboio #”, em que “#” é o número do comboio a passar).
3 – Implementação
3.1 – Circuito a usar no projecto
O circuito a montar no simulador é fornecido (ficheiro proj.cmod) e é semelhante ao já
utilizado nos trabalhos anteriores, mas ao qual foi acrescentado um relógio de tempo
real, que gera um sinal de relógio de tempo real1, programável (já está pré-programado
para um período de 0,5 seg).
A15..A0
BGT
BRT
WAIT
0
INTA
INT3
INT2
INT1
INT0
16
16
PEPE
A15..A0
8
DATA_I
DATA_P
DATA_I
DATA_P
8
RD
WR
RD
WR
CS
A15
BA
RESET
MemBank
BA
CLOCK
5
ADDRESS
RD
Real-time
Clock
Reset
WR
Clock
8
CS
DATA
Pista de
comboios
RESET
Interrupt
3.2 – Estrutura de processos
O programa tem de estar continuamente a monitorizar o estado de cada comboio e a sua
passagem pelos sensores, gerir temporizações (tempo de paragem na estação, semáforos
da passagem de nível que acendem e apagam), ao mesmo tempo que tem de estar atento
aos comandos de interface do utilizador (botões de pressão e barras de velocidade).
É um conjunto de actividades que se pretendem simultâneas, e algumas delas com
razoável independência. Em software, isto conduz à noção de processo. Um processo é
uma espécie de um programa independente dos restantes processos, quase como se
tivesse um processador só para si. Isto permite ao programador pensar apenas num
processo de cada vez, o que é muito mais simples do que misturar as actividades todas.
Assim, poderemos ter pelo menos os seguintes processos:
• um processo para cada um dos comboios (porque têm de ter comportamento
independente do outro);
1
Este módulo difere do relógio normal no facto de este último não ser de tempo real (o que por
um lado permite que o PEPE funcione à velocidade máxima conseguida pelo computador que
corre o simulador, mas por outro não permite implementar temporizações de valor fixo).
4
•
•
•
um processo gestor do sistema, que trate dos semáforos e da reserva dos
troços;
um que tome conta dos semáforos da passagem de nível;
um que consiga fazer temporizações (para medir o tempo de paragem na
estação).
Estas são as actividades que precisam de ser executadas ao “mesmo tempo” que as
restantes. Na realidade, o processador só consegue executar uma instrução de cada vez,
pelo que o que acontece é o processador executar algumas instruções de cada processo
antes de passar ao próximo, voltando depois ao primeiro processo depois de ter
executado parte de cada um dos vários processos. Algo do género:
ciclo:
CALL processo_comboio_0
CALL processo_comboio_1
CALL gestor_sistema
CALL passagem_nível
CALL temporizador
JMP ciclo
Cada processo é uma rotina, que ao ser chamada faz algum processamento desse
processo e depois retorna, NÃO SE BLOQUEANDO INTERNAMENTE (p.ex. ficando
em ciclo à espera da ocorrência de determinado acontecimento) para permitir que as
rotinas dos outros processos sejam também executadas.
Optimização: quando há processos “iguais”, como é o caso dos comboios, apenas deve
existir uma rotina que controla o movimento de um comboio. em vez de estar a repetir o
código para os dois comboios. Deve, para isso, chamar-se a rotina passando-lhe um ou
mais valores como parâmetros; neste caso em particular, deve-se passar para esta rotina
o número que identifica o comboio.
Um aspecto fundamental é que um dado processo não pode bloquear o programa, com
por exemplo um ciclo de espera incluído na própria rotina (senão as outras rotinas não
eram executadas). Se um processo quiser esperar que uma dada posição de memória
tenha um dado valor, por exemplo, não deve fazer:
; rotina que implementa o processo 1
processo1:
Lê posição de memória
CMP com valor pretendido
JNZ processo1
...
RET
; vê se valor é o esperado
; se ainda não é, vai tentar de novo
; processamento caso o valor for o esperado
; acabou, regressa
mas sim
; rotina que implementa o processo 1
processo1:
Lê posição de memória
CMP com valor pretendido
JNZ fim
...
fim:
RET
; vê se valor é o esperado
; se ainda não é, vai tentar de novo
; processamento caso o valor for o esperado
; acabou, regressa. Há-de voltar no próximo
; ciclo
A espera deve ser externa à rotina e não interna. Dito de outra forma, a espera deve
incluir uma volta aos restantes processos, o que permite esperar sem bloquear todo o
sistema.
5
O mais natural é cada processo implementar uma máquina de estados. Por exemplo, o
processo que trata de um comboio tem de saber se a locomotiva está parada junto a um
semáforo vermelho, parada na estação, a avançar, etc., pois o comportamento a adoptar
depende do estado em que está.
Para permitir que um dado processo tenha um comportamento diferente consoante a
situação utiliza-se uma variável de estado que identifica o estado (identificado por um
número, único para cada estado) em que o processo se encontra, ficando com uma
estrutura deste género:
; rotina que implementa o processo 1
processo1:
Lê variável com o estado do processo
estado0:
CMP com 0
; vê se é o estado 0
JNZ estado1
…
; faz o processamento do estado 0
estado = novo valor ; indica qual o próximo estado
JMP fim
; acabou execução do estado 0
estado1:
estado2:
estado3:
fim:
CMP com 1
JNZ estado2
…
estado = novo valor
JMP fim
; vê se está no estado 1
CMP com 2
JNZ estado3
…
estado = novo valor
JMP fim
; vê se está no estado 2
...
...
RET
; etc. Outros estados
; faz o processamento do estado 1
; indica qual o próximo estado
; acabou execução do estado 1
; faz o processamento do estado 2
; indica qual o próximo estado
; acabou execução do estado 2
; sai, para permitir que os outros processos corram
Quando esta rotina é executada, os testes ao estado agulham para a parte do código que
processa esse estado. Em seguida sai (retorna), e quando for executada novamente (após
uma volta completa ao ciclo principal), já a variável “estado” terá eventualmente um
novo valor (nada impede que seja o mesmo), o que permite ao processo, na prática,
evoluir no seu comportamento.
Nota: convém que as variáveis de estado sejam concretizadas com posições de memoria
e não com registos; se utilizar registos é preciso que fique ciente que esses registos
ficam dedicados a essa função e não poderão ser utilizados para outros propósitos (a não
ser que recorra a armazenamentos temporários na pilha que complicam
desnecessariamente o código).
A comunicação entre processos, se houver, faz-se por meio de variáveis em
memória, em que um processo vai lendo uma dada variável até que outro lá
coloque o valor que ele à espera (por exemplo está a 0 até que o outro
processo lá coloque 1). Isto pode servir para passar informação ao mesmo
tempo que pode sincronizar os dois processos.
6
3.3 – Estrutura dos dados
A descrição de que troços há, a que troço pertence a cada sensor, se um dado troço tem
estação ou não, o que deve ser feito em cada sensor quando lá passa um comboio, etc,
deve ser feita em tabelas, em que para cada troço, sensor, semáforo, etc, se coloque
informação relevante.
Deve também notar-se que o estado dos semáforos não pode ser lido do módulo dos
comboios. Se for preciso ler este estado, deve sempre ter-se em memória o valor que se
pretende ter para cada um dos semáforos, estado esse que deve ser colocado no valor
pretendido e depois escrito no porto conveniente. Idem para as agulhas.
Para construir estas tabelas sugere-se o uso das directivas STRING, WORD e/ou
TABLE.
3.4 – Interrupções
O relógio de tempo real gera um sinal que se repete de 0,5 em 0,5 segundos, gerando
assim duas interrupções INT1 por segundo. Esta interrupção deve ser usada para gerar
as temporizações de paragem dos comboios nas estações e do ritmo de alternância dos
semáforos da passagem de nível.
Os eventos de passagem das locomotivas pelos sensores devem ser detectados pela
interrupção INT0 e não estar sempre a ler o porto 0EH do módulo dos comboios.
Note-se que (em relação a INT0):
• cada passagem completa da locomotiva por um sensor gera dois eventos (ou
duas interrupções);
• continua a ser necessário ir ler o evento em si no porto 0DH depois de saber
que há um evento;
• para ler um evento é preciso ler dois bytes no porto 0DH (ver manual do
módulo dos comboios) e não apenas um (isto é necessário para suportar mais
sensores do que um só byte permitia).
As rotinas de atendimento das interrupções devem essencialmente actualizar variáveis
(nomeadamente, os bytes de informação para os comboios) e não fazer processamento
propriamente dito. Os processos é que devem depois ler essas variáveis e actuar em
conformidade.
ATENÇÃO: os níveis de sensibilidade das interrupções têm de ser programados usando
o registo RCN (ver manual do PEPE). A interrupção INT0 deve estar programada para
reagir ao nível 1 (gera interrupções enquanto o pino estiver a 1), enquanto a interrupção
INT1 deve apenas reagir ao flanco de 01 (uma só vez em cada flanco).
3.5 – Desenvolvimento do software
A primeira coisa a fazer é estruturar o programa, identificando as rotinas a usar e
fazendo um fluxograma (ou um algoritmo) para cada uma delas.
Devem fazer-se pequenos passos de cada vez, testando o que já estiver feito. Para testar
um programa já com alguma complexidade devem testar-se pequenas partes de cada
vez, em vez de testar tudo junto. É que quando não funcionar fica-se sem saber qual
parte é a responsável pelo erro… Ainda por cima, este é um sistema de tempo real, em
que os comboios andam e geram eventos de passagem pelos sensores
7
independentemente do PEPE. Por isso, é impossível testar o sistema completo em
single-step, por exemplo.
Recomenda-se o teste das partes individuais (cada processo, por exemplo) de forma
simulada, com pequenos programas que testam partes do programa ou actuando
directamente em variáveis na memória, se necessário. Desta forma, é muito mais fácil o
todo funcionar bem.
Dado que a manipulação de interrupções é uma tarefa não trivial, cujo debug é difícil de
realizar, sugere-se que, num primeiro passo, desenvolva o código sem recorrer a
interrupções; isto implica que a leitura dos sensores terá de ser realizada por polling no
ciclo de processamento principal; implica também que não existem temporizações e,
portanto:
•
a passagem de nível não funciona nesta 1ª fase;
•
a paragem na estação deve ser simulada (p. ex. à custa de uma rotina que, após
cada chamada, decrementa um contador – quando este chegar a zero passou a
temporização pretendida)
Numa 2ª fase, coloque apenas uma das interrupções a funcionar, e verifique o
funcionamento pretendido. Apenas quando esta interrupção estiver operacional avance
para a realização da segunda. Uma hipótese é colocar em funcionamento a rotina de
serviço do INT0 e só depois a do relógio de tempo real.
8
Download

Projecto de