13 Comentários

Arquitetura de desenvolvimento de software – parte II

arquitetura desenvolvimento de software

Na parte I comentamos sobre o processo de desenvolvimento mais simples: one-single-loop. Este modelo é bastante rápido e praticamente não insere sobrecarga de processamento ou memória. No entanto, o maior problema com esta arquitetura é a criação de rotinas que envolvam leituras de informação ou execuções periódicas. Em geral as leituras envolvem algum tipo de atraso, como a conversão de um sinal analógico ou a recepção de uma mensagem. O problema com as execuções periódicas, é que nem sempre o loop tem tempo constante, podendo variar seu período a cada execução. Mesmo quando o loop apresenta uma constância, o cálculo deste tempo não é simples de fazer, além de se alterar com cada nova adição ou remoção de código e funcionalidade.

Entre as soluções disponíveis para resolver estes problemas, uma se destaca: as interrupções. Elas permitem que uma função pré-definida seja executada sempre que um evento acontecer. Assim, é possível programar um sistema de modo que ele responda aos eventos e não apenas execute operações de modo sequencial. No entanto, é necessário que o hardware tenha suporte às interrupções. Entre as interrupções mais comuns implementadas pelos fabricantes temos:

  • Temporizadores/relógios: Acontece em tempos pré-definidos;
  • Entradas digitais: Sempre que há mudança no valor de uma porta ou de um terminal;
  • Entradas analógicas: Quando o processo de conversão terminou;
  • Comunicação: Na recepção de uma mensagem/byte ou quando o sistema está disponível para enviar uma informação.

Além das interrupções geradas pelos periféricos internos, alguns fabricantes oferecem a possibilidade de gerar uma interrupção por um evento externo, disponibilizando um terminal especificamente para isso.

No desenvolvimento orientado à interrupções, as funcionalidades do sistema são codificadas em funções que são executadas como resposta aos eventos. Esta abordagem reduz drasticamente a latência na resposta aos eventos. No entanto, alguns cuidados devem ser tomados.

A interrupção pausa o fluxo do programa principal, executa a função pré-definida e retorna. Com exceção do tempo transcorrido, o programa principal não percebe a pausa. Se a interrupção for algo recorrente, como uma função de controle temporizada, o tempo gasto na interrupção pode fazer com que o loop principal seja executado muito lentamente, ou até mesmo não seja executado.

De modo geral, é importante que as funções que serão executadas nas interrupções sejam breves. Sempre que possível, deixe o processamento pesado no programa principal.

Vamos para um exemplo prático: um interpretador de comandos via comunicação serial. Utilizando a arquitetura one-single-loop podemos projetar o sistema do seguinte modo:

O problema com essa abordagem é que as funções VerificaCRC() e a ExecutaAção() podem consumir tempo demais, fazendo com que alguns dos dados recebidos pela serial sejam perdidos. Utilizando a interrupção de chegada de byte na serial evita-se esse problema.

O problema de perder algum dado da serial é resolvido, no entanto outro problema é inserido. Como as duas funções, a principal e a interrupção, operam com o buffer, é possível que o buffer seja alterado na interupção enquanto a função principal calcula o CRC da mensagem. Isto pode levar à uma conclusão errada da função VerificaCRC() ou pior, a função ExecutaAcao() irá executar uma tarefa de maneira errada.

Existem algumas soluções para evitar esse problema: mutexes, semáforos, ou filas de mensagens para passar o evento para o programa principal. A solução mais simples é a utilização de uma flag indicando que a mensagem está em processamento e portanto a interrupção deve evitar alterar a mensagem.

Outra grande vantagem do uso da interrupção é a possibilidade de se utilizar o modo de baixo consumo de energia de modo simples. Como todas as ações no exemplo citado são executadas apenas quando há uma interrupção o sistema pode pausar o processamento enquanto não houver uma mensagem disponível no buffer. Sendo o sistema inteiro orientado à eventos que geram interrupção, ele automaticamente sairá do modo de baixo consumo sempre que algum evento importante acontecer.

O desenvolvimento do sistema pode ser, então, simplificado em 3 etapas:

  1. Atender as interrupções;
  2. Processar o resultado das interrupções no loop principal;
  3. Se não houver nada a ser processado entrar em modo de baixo consumo de energia.

Com a grande quantidade de microcontroladores voltados para baixo consumo existentes e sendo lançados (inclusive ARMs em DIP!) esta pode ser uma abordagem simples e eficiente.

Outros artigos da série

<< Arquitetura de desenvolvimento de software - parte IArquitetura de desenvolvimento de software – parte III >>
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Software » Arquitetura de desenvolvimento de software - parte II
Comentários:
Notificações
Notificar
guest
13 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Fabio Utzig
23/10/2013 21:40

Rodrigo, a versão com interrupção ficou sem incremento da variável "pos", logo vai ficar recebendo sempre na posição 0! Aliás, eu costumo escrever "buffer[pos++] = ..." justamente pra evitar este tipo de problema.

Rodrigo Almeida
Rodrigo Almeida
Reply to  Fabio Utzig
23/10/2013 22:18

Muito obrigado Fábio! Já está arrumado e boa a dica de usar o pos++ como indexador. Mas acho que com os alunos vou continuar incrementando fora pra evitar mais duvidas =)

Josias Inacio
Josias
23/10/2013 10:07

Excelente artigo! Uma dúvida me surgiu: Se implementarmos mutex para a variável "buffer", o que acontece então quando uma interrupção é gerada enquanto o código está executando o main, e o main já possui o lock da variável? Pois quando o programa entrar no Interrupt Service Routine, não vai poder alterar a o buffer ja que não tem o lock, mas também nao poderá retornar do ISR.

Resultaria num deadlock? Como resolver?

Rodrigo Almeida
Rodrigo Almeida
Reply to  Josias
23/10/2013 22:31

Essa é uma boa pergunta Josias, vou usar na minha próxima prova! Realmente teriamos um dead look e não vejo solução nessa arquitetura. No entanto se olharmos apenas do ponto de vista da aplicação o sistema está operando como o modelo de produtor-consumidor. Como o main não precisa alterar o buffer, a principio não seria muito critico não usar o mutex. Para aumentar a confiabilidade uma das alternativas que eu vejo, ao invez do mutex, é não deixar o main usar o buffer e utilizar um sistema de message parsing com uma queue. Inclusive o FreeRTOS indica essa solução pra… Leia mais »

Marcelo Rodrigo Dos Santos Andriolli
Marcelo Andriolli
19/10/2013 14:16

Muito bom!! Você poderia indicar-me algum material(livro, artigo etc...) sobre arquitetura de software para embarcados?

Rene Lima
rene
14/10/2013 10:04

Rodrigo muito bom seu artigo.

Porém fiquei em duvida em relação a um trecho do primeiro codigo:
while(data == 0){
data = RecebeSerial();
}

Depois que você receber o primeiro byte

qual será o comportamento? digo isso porque

não vi a variavel 'data' esta sendo zerada apos

transferir para o bufer o valor contido nela.

Você conseguirá receber uma string inteira?

att.

Rodrigo Almeida
Rodrigo Almeida
Reply to  rene
16/10/2013 06:50

Obrigado rene, já corrigi no código. É necessário realmente zerar a variável antes de retornar ao loop de espera. Para manter o código limpo troquei para um do-while.

Rene Lima
rene
Reply to  Rodrigo Almeida
16/10/2013 09:16

Boa saída Rodrigo .
Vlw.

André Castelan
12/10/2013 20:29

Existe outra forma de resolver este problema sem a utilização de variáveis globais?

Rodrigo Almeida
Rodrigo Almeida
Reply to  André Castelan
16/10/2013 06:54

Como as variáveis tem que ser acessadas em vários pontos do programa esta é a única solução. É possível no entanto colocar esta varíavel em um arquivo separado e dedicado ao uso da porta serial. Assim a variavel, apesar de global, estará disponível apenas para as funções deste arquivo. Nesta situação os acessos externos seriam feitos todos através de funções.

André Castelan
12/10/2013 16:06

Rodrigo utilizar variáveis globais é tido como uma péssima pratica de programação no geral, para interrupção está liberado? Existe algum outro jeito?
O que da pra fazer é utilizar static na variável global para ela se manter só no escopo do .c que ela está sendo utilizada, o que você me diz? Tem outro jeito de fazer isto sem usar variáveis globais?

abs

Matheus Quick
Matheus Quick
12/03/2017 17:47

excelente artigo.

trackback
23/05/2015 12:08

[…] Arquitetura de desenvolvimento de software - parte II […]

Talvez você goste:

Séries



Outros da Série

Menu

WEBINAR
 
Debugging
em Linux embarcado

 

Data: 30/09 às 19:30h - Apoio: Mouser Elecctronics
 
INSCREVA-SE AGORA »



 
close-link