Em um artigo anterior , falamos sobre como, de acordo com o autor, é possível programar as ações usuais de um microcontrolador em tempo real, dividindo-as em várias tarefas independentes (ou quase independentes) uma da outra.
Foi escolhido um microcontrolador, com um núcleo de uma família muito difundida de ARM Cortex M. Do familiar a muitos, e não apenas o autor, opções com os números 0,3,4 e 7, M4 foi escolhido, porque estava à mão.
As duas considerações que nos levaram a embarcar no caminho escorregadio e instável de "inventar uma bicicleta", como observou alguns leitores com perspicácia, eram realmente simples. A primeira foi que ainda temos que viver e conviver com esses "córtex". E a segunda é tentar não fazer algo universal (ganhando fama e fortuna), mas fazer algo mais estreitamente focado, na esperança de alcançar eficiência e simplicidade. Aqueles que às vezes fazem algo com as mãos se lembram facilmente de que, como regra, uma chave de fenda especialmente selecionada é melhor do que a retirada de um conjunto universal brilhante.
Um exemplo de assembler foi dado para mostrar que não são gastos mais que 80 ciclos de clock na comutação. E na velocidade de clock de 72 megahertz, acontece um pouco mais de 1 microssegundo. Portanto, um carrapato com 50 microssegundos de tamanho não será tão caro. Apenas 2% da sobrecarga. Portanto, como um dos personagens favoritos do autor disse, "é aconselhável sofrer".
Portanto, temos N tarefas, cada uma com garantia de execução de um pedaço (marca T) de tempo e repetição desse segmento o mais tardar após (N-1) T, além de um atraso que não excede D. Esse atraso irritante, felizmente , é limitado pelo tamanho máximo possível no tempo, que é igual à soma da duração da operação de todas as interrupções permitidas. Em outras palavras, a tarefa que tem as maiores interrupções em potencial por um determinado período antes do próximo tick é mais azarada. Por um longo tempo, a tarefa não pode ser adiada. Ela inevitavelmente receberá seu intervalo de tempo o mais tardar em microssegundos (N-1) T + D. Na minha opinião, isso é chamado de difícil em tempo real.
As tarefas devem concluir suas tarefas e relatar a implementação. Para quem devo me reportar? Aparentemente, alguém está no comando e, em regra, eles são muito menores que os subordinados (na verdade, o autor também encontrou exceções quando havia quatro chefes para três trabalhadores com as duas mãos esquerdas, dos quais apenas um conhecia e respeitava a palavra " adequação ”).
E se "vocês são muitos, mas eu sou um", isso significa uma fila. Muitos vão começar a empurrar e tentar escapar. E alguém terá que esperar e depois se atrasar e explicar. Apesar de tudo isso parecer terrível, isso é chamado de belo: a luta pelo recurso. Filas são uma solução bem conhecida para todos. Eu conhecia muitos que não alimentam pão - deixe-me ficar na fila.
Mas a nossa não pode esperar! No sentido, tarefas. Eles são de difícil tempo real. Suponha que duas tarefas leiam leituras uma vez a cada segundo, e a terceira deve medir algo a cada 10 milissegundos, colocá-lo em uma pilha e reportar ao topo. E eles lhe dizem: "Diga-me, ainda não terminamos com os chefs".
Aparentemente, é preciso recorrer, para dizer o mínimo, a um tempo não muito real (tempo real suave).
Vamos ter uma tarefa especial que sabe esperar e adora fazê-lo. O recurso que ele servirá será o canal de comunicação. Como você sabe, você não vai enfiar tudo de uma vez.
Mas, você pode descobrir imediatamente qual velocidade o canal deve ter para que nada se perca. Para fazer isso, você precisa conhecer o desempenho com o qual todas as nossas tarefas graphomaniacs, pah, funcionam. Obviamente, você também deve calcular o tamanho do buffer ou buffers dos quais todos os pacotes serão enviados (ou à direita).
Se o canal não for um, a essência não muda. Uma tarefa separada é simplesmente adicionada para cada canal, que é projetado para aguardar (e, é claro, esperar e acreditar).
Algumas palavras sobre o canal de comunicação com o operador, ou mais simplesmente, com a pessoa. Aqui o canal é bidirecional, mas a direção externa é mais interessante. Faça uma reserva imediatamente, há uma circunstância que, mesmo com um forte desejo, é impossível excluir. Isso é sobrecarga de canal. Durante o teste, precisamos alcançá-lo e ter um mecanismo para ver seu início. Não discuto, não é bom enganar, mas um pouco pode ser deixado de fora. Vaughn, Gerasim, abusou completamente.
Portanto, assumimos imediatamente que a mensagem para o operador da tarefa pode ser perdida. E para que uma pessoa saiba disso, nós os contaremos. Isso determinará onde e quantas vezes nosso operador ficou sem nada. Por fim, você sempre pode fazer algo no código, adicionar cálculos ou até mesmo anexá-lo ao circuito elétrico para corrigir a situação. Parece que, por enquanto, será mais fácil. Mas, é claro, isso não é necessário para aplicações militares. Para ser honesto, perder uma mensagem não parece uma desgraça apenas ao depurar.
Por exemplo, vamos ter uma interface serial duplex sem reconhecimento em 115200 baud. Por exemplo, RS422 na configuração "economia" dois fios - lá, dois - de volta. Sua capacidade é de aproximadamente 10.000 bytes por segundo. Vamos considerar o tamanho médio da mensagem para uma pessoa igual a 50 bytes. Recebemos 200 mensagens por segundo ou uma mensagem em 5 milissegundos. Se tivermos três tarefas que desejam comunicar alguma coisa, deixe-as fazer isso a cada 15 milissegundos cada. Em média, é claro. E, se não for em média, serão necessários cálculos estatísticos sérios ou um experimento em larga escala. Escolha o último. Afinal, aprendemos a detectar mensagens ausentes e veremos tudo na tela do emulador de terminal.
Portanto, deixe as três tarefas criarem mensagens individuais. Deixe as mensagens diferirem em importância ou urgência do conteúdo e nossas tarefas as colocarão no buffer apropriado. Aloque esses três buffers de anel para três níveis de urgência, como mostra a Figura 1.

A quarta tarefa seleciona desses buffers uma mensagem de acordo com nosso plano aprovado e tenta entregá-la. Se o envio ainda não for possível, a quarta tarefa estima o quanto ele pode dormir e faz isso. Depois de dormir, ela já tem o espaço necessário no buffer do anel para enviar.
Os buffers de várias urgências, é claro, não armazenam as mensagens em si, mas seus endereços (links). Ao mesmo tempo, as próprias tarefas não precisam esperar nada. Ok Não, não mesmo. Isso não funciona, e aqui está o porquê. Cada um desses três buffers de anel é um recurso compartilhado. Imagine que a tarefa 1 estava prestes a colocar um endereço em um buffer intermediário. Ela lê a palavra, verifica se o local está vazio, ou seja, o valor é zero e (nesse momento ela é substituída por outra tarefa 2, que deseja fazer exatamente o mesmo e consegue), a primeira tarefa, retornando, coloca a palavra ali, sobrescrevendo tudo o que conseguiu em segundo. Aqui está um colega pedindo palavras. Parece que sei o que ele dirá.
Sim, tudo é muito simples, você pode proibir interrupções durante o teste e nada de ruim vai acontecer, não é por muito tempo.
- Verdade, não por muito tempo, mas quantas vezes? Quanto tempo tiraremos da tarefa? E qual deles? Esqueci de avisar, nunca proibimos interrupções, nossa seita dura de tempo real nos proíbe de fazer isso.
-E se você não proibir interrupções, poderá solicitar à nossa chave de tarefa que coloque o endereço da mensagem lá. Ele pode fazer isso atomicamente.
- Sim, talvez, mas depois quero perguntar-lhe outra coisa, depois outra. E por que alcançamos 72 graus para diluir tudo com água? Desculpe, eu quis dizer 72 ciclos para mudar de contexto.
Vamos tentar facilitar, como na Figura 2.

Nesse caso, cada tarefa possui seu próprio buffer ou seu próprio conjunto de buffers, se você desejar uma urgência, pompa e importância diferentes. Pessoalmente, eu, como operador simples, ainda tenho a mesma importância para todos.
Esse esquema não faz você lutar pelo recurso. Agora temos uma opção muito funcional. Só não gosto disso. Mas e se as tarefas à esquerda na imagem não tiverem nada para enviar? Seria mais sensato pedir que a tarefa da direita fosse acordada quando o motivo aparecesse e não acordar apenas para definir o alarme novamente. As tarefas à esquerda são mais fáceis de executar. Além disso, uma função que ajuda a acordar um amigo foi mencionada em uma postagem anterior.
Prevejo uma proposta de racionalização: “Deixe a interrupção da porta serial (UART) envolvida na tarefa 4 que está sendo executada agora, haverá economia.” Você pode fazer isso, mas não acho que seja bom. Vou tentar esclarecer. As tarefas à esquerda, de fato, podem ativar o procedimento de interrupção UART, e ele começará a funcionar e não será interrompido até que faça tudo. O procedimento de interrupção agora deve fazer tudo o que a tarefa 4 costumava fazer.O tempo gasto para processar a interrupção aumentará, nem uma única tarefa poderá ser ativada até que o próximo "spool" termine. E o que dizemos aos nossos camaradas do persistente círculo em tempo real? Mas nos disseram que a resposta a qualquer interrupção externa deveria ser a mais curta possível. Este é apenas um bom tom. Ou, em outras palavras: é necessário fazer o bem, o mal e sem você funcionará.
A Figura 3 explica qual é o processo e quais desafios estão localizados.

Agora nos voltamos para a situação, pode-se dizer, um espelho. É quando as informações vêm de fora. Seja um canal SPI com vários gondoleiros com gôndolas e uma pequena orquestra de cordas amadora. Não, é muito cedo para pensar em descanso. Deixe apenas a interface SPI e alguns chips. Por exemplo, sensor de pressão atmosférica, acelerômetro e memória armazenada.
Devo dizer imediatamente - um exemplo estúpido. Não por causa do gondoliero com seu eterno “devo acrescentar, cavalheiro”. Não, é estúpido, de fato, misturar em uma interface esses dados de entrada de importância diferente. De fato, se você precisa conhecer a aceleração, então, com certeza, para descobrir rapidamente quando remover o pedal do acelerador, virar as abas ou fechar os olhos, finalmente. Esta informação é frequentemente necessária. Mas a pressão muda lentamente e terá que descer cerca de três metros, de modo que nas camadas inferiores a vida se torne mais quente.
Quanto à memória armazenada, e quem geralmente a coloca neste SPI? Existe um segundo SPI? E não é esperado? Nenhum lugar para ir, algo precisa ser feito. Redirecione as setas na direção oposta na Figura 2 e comece a pensar.
A tarefa 4 agora atende ao SPI e acorda apenas por seus sinais. Sua conexão com a tarefa 1, que deseja colocar algo na memória armazenada, é direcionada para o exterior e é realizada através da fila. Também é necessário fornecer um mecanismo para monitorar o estouro do buffer do anel. A produção dos valores de aceleração e pressão da tarefa 4 deve fornecer sem a participação de duas tarefas consumidoras. Você só precisa girar e acompanhar. Agora podemos esboçar uma figura explicativa e escrever uma nota explicativa. Na Figura 4, esses
as ações são mostradas esquematicamente (ou diagrama de blocos).

Verificação de fluxo insuficiente - essas ações ajudam a descobrir se o valor da aceleração tem tempo para mudar antes de ser lido novamente pela tarefa consumidora. Essa verificação é mostrada por uma ação separada na Figura 4 apenas para chamar a atenção. De fato, essa etapa ocorre junto com a leitura do valor do acelerômetro de acordo com o esquema, conforme mostrado na Figura 5.

Cabe ressaltar que existe um recurso compartilhado, pois o local de armazenamento do resultado também é um indicador de ações (semáforo). As corridas são possíveis aqui, falando a linguagem dos circuitos, mas para nós isso não é uma omissão. Afinal, entrar na porta de um veículo apenas na vida pode ser considerado uma fortuna. Aqui consideraremos com segurança um atraso.
O acesso à memória ocorre em partes para limitar o tempo de cada etapa. Assim, garantiremos uma leitura uniforme dos valores de aceleração que mudam rapidamente e, nesse meio tempo, cuidaremos do resto.
Bem, agora resta encontrar algo adequado com ferro e experimentar como deveria. Acho que essa será a próxima história.