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.

[wpseo_breadcrumb]
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
Privacy Settings saved!
Configurações de Privacidade

Entenda quais dados e informações usamos para ter melhor entrega de conteúdo personalizado para você.

These cookies are necessary for the website to function and cannot be switched off in our systems.

Para usar este site, usamos os seguintes cookies tecnicamente exigidos

  • wordpress_test_cookie
  • wordpress_logged_in_
  • wordpress_sec

Decline all Services
Accept all Services