Recursos sobre como escrever código C eficiente para microcontroladores? [fechadas]


15

Ajuda séria necessária aqui. Eu amo programação. Ultimamente, tenho lido muitos livros (como K&R) e artigos / fóruns on-line para a linguagem C. Até tentei pesquisar no código do Linux (embora eu estivesse perdido por onde começar, mas espreitar em pequenas bibliotecas ajudou?).

Comecei como programador Java e em Java é bastante simples; Se os programas ficarem muito grandes, divida-os em classes e depois em funções. Diretrizes como, mantenha o código legível e adicione comentários. Use técnicas de ocultação de informações e OOP. Alguns dos quais ainda se aplicam a C.

Eu tenho codificado em C agora e até agora eu recebo programas para trabalhar de uma maneira ou de outra. Muitas pessoas falam sobre desempenho / eficiência, algoritmo / design, otimização e manutenção. Algumas pessoas enfatizam uma mais que a outra, mas para engenheiros de software não profissionais você costuma ouvir algo como, por exemplo: Linux kernel dev não aceita apenas nenhum código.

Minha pergunta é a seguinte: planejo escrever um código para o microcontrolador de 8 bits sem desperdiçar recursos . Saiba que eu sou do fundo Java, para que as coisas não sejam mais as mesmas ... recursos / livros / links / dicas serão muito apreciados. Desempenho e tamanho agora são importantes. Recursos / Truques sobre código C eficiente (dentro das melhores práticas) para microcontroladores de 8 bits?

Além disso, inline assemblydesempenha um papel vital, mantendo-se próximo ao padrão dos microcontroladores. Mas existem regras gerais para a eficiência que se aplicam a todos?

Por exemplo: register unsigned int variable_name;é preferível a charqualquer momento. Ou use uint8_t se você não precisar de grandes números.

EDIT: Muito obrigado por todas as respostas e sugestões. Agradeço o esforço de todos para compartilhar conhecimento.


2
Isso geralmente não é o caso em um processador x86, mas em um microcontrolador, se você deseja obter toda a última gota de desempenho, provavelmente usará o assembly.
Rei Miyasaka 01/01

3
@Rei: O Assembly artesanal raramente, se é que alguma vez, usa menos memória e é mais rápido que os compiladores C modernos. É um desperdício de tempo para o código em assembly quando ele pode (como deveria) ser feito em C.
mattnz

1
@mattnz Por moderno, de que novidade você está falando? Com toda a honestidade, não escrevo código para um microcontrolador há quase uma década.
Rei Miyasaka

2
Uma dica simples: em microcontroladores, ainda é geralmente verdade que "se é mais simples, é mais rápido". Em chips complexos (ARM e superior), o hardware faz tantas otimizações que você nunca conhece até testar.
Javier

1
@ReiMiyasaka com um grande aumento no custo de manutenção. Um bom compilador C pode produzir quase o mesmo código que um programador altamente experiente.

Respostas:


33

Tenho mais de 20 anos de sistemas embarcados, principalmente 8 e 16 micros. A resposta curta para sua pergunta é a mesma que qualquer outro desenvolvimento de software - não otimize até que você saiba que precisa e, em seguida, não otimize até saber o que precisa otimizar. Escreva seu código para que seja confiável, legível e mantenível primeiro. A otimização prematura é tanto, se não mais, um problema em sistemas embarcados

Quando você programa "sem desperdiçar recursos", considera seu tempo um recurso? Se não, quem está pagando pelo seu tempo e, se ninguém, você tem algo melhor a ver com isso. Uma vez que qualquer projetista de sistema embarcado deve escolher é o custo do hardware versus o custo do tempo de engenharia. Se você enviará 100 unidades, use um micro maior, com 100.000 unidades, uma economia de US $ 1,00 por unidade é igual a 1 homem / ano de desenvolvimento de software (ignorando o tempo de lançamento no mercado, custo de oportunidade etc.), a 1 milhão de unidades. obtendo ROI por ser obsessivo com o uso de recursos, mas tenha cuidado porque muitos projetos incorporados nunca atingiram a marca de 1 milhão porque projetaram vender 1 milhão (alto investimento inicial com baixo custo de produção) e faliram antes de chegarem lá.

Dito isso, é necessário considerar e estar ciente de sistemas (pequenos) incorporados, porque eles o impedirão de funcionar, de maneiras inesperadas, e não apenas tornarão lento.

a) Pilha - normalmente você tem apenas um tamanho pequeno e, geralmente, limitado, tamanhos de quadros de pilha. Você deve estar ciente de qual é a utilização da sua pilha o tempo todo. Esteja avisado, problemas na pilha causam alguns dos defeitos mais insidiosos.

b) Heap - novamente, tamanhos pequenos de heap, portanto, tenha cuidado com a alocação de memória injustificada. Fragmentação se torna um problema. Com esses dois, você precisa saber o que fazer quando acabar - isso não acontece em um sistema grande devido à paginação fornecida pelo SO. Quando malloc retorna NULL, você verifica e o que faz. Toda malva precisa de um cheque e manuseio, código inchaço ?. Como um guia - não use se houver uma alternativa. A maioria dos sistemas pequenos não usa memória dinmática por esses motivos.

c) Interrupções de hardware - Você precisa saber como lidar com elas de maneira segura e oportuna. Você também precisa saber como criar código de reentrante seguro. Por exemplo, as bibliotecas padrão C geralmente não são reentrantes, portanto, não devem ser usadas dentro de manipuladores de interrupção.

d) Montagem - quase sempre otimização prematura. No máximo, é necessária uma pequena quantidade (inline) para obter algo que C simplesmente não pode fazer. Como exercício, escreva um método pequeno na montagem artesanal (do zero). Faça o mesmo em C. Meça o desempenho. Aposto que o C será mais rápido, sei que será mais legível, sustentável e extensível. Agora, para a parte 2 do exercício - escreva um programa útil em assembly e C.
Como outro exercício, observe quanto do kernal do Linux é assembler, e depois leia o parágrafo abaixo sobre o kernel do linux.

Vale a pena saber como fazê-lo; pode até valer a pena ser proficiente nas línguas para um ou dois micros comuns.

e) "register unsigned int variable_name", "register" é, e sempre foi, uma dica para o compilador, não uma instrução, no início dos anos 70 (40 anos atrás), fazia sentido. Em 2012, é um desperdício de pressionamentos de tecla, pois os compiladores são muito inteligentes e as instruções do micros são tão complexas.

Voltando ao seu comentário sobre o Linux - o problema que você tem aqui é que não estamos falando de apenas 1 milhão de unidades, estamos falando de centenas de milhões, com um tempo de vida para sempre. O tempo e o custo de engenharia para obtê-lo da melhor forma possível humanamente vale a pena. Embora seja um bom exemplo das melhores práticas de engenharia, seria suicídio comercial para a maioria dos desenvolvedores de sistemas embarcados ser tão pedante quanto o linux kernal exige.


4
mattnz: esta é uma das respostas mais bonitas em sites de .stackexchange.
Ahmed Masud

1
Não posso melhorar esta resposta. Eu só poderia acrescentar que a inserção de código de montagem raramente faz sentido para o desempenho, mas poderia fazer sentido para coisas como cutucando chips de I / O ou outros truques de hardware que pode não ser fácil de fazer em C.
Mike Dunlavey

@mattnz Obrigado pela resposta bem colocada. +1
AceofSpades

1
Às vezes, o assembler @MikeDunlavey é necessário para o tempo exato. Acabado de uma sobreposição de vídeo que utiliza bit batendo para gerar o sinal NTSC de um pino de I / O, o tempo é em termos de - alta tensão para ciclos 3clock, em seguida, para baixo, em seguida, 6 ....
Martin Beckett

@ Martin: Isso faz todo o sentido. Faz muito tempo desde que eu codifiquei nesse nível.
precisa saber é o seguinte

3

Sua pergunta ("sem desperdiçar recursos") é muito geral, por isso é difícil dar muitos conselhos. Tomado literalmente, se você não deseja desperdiçar recursos, talvez deva dar um passo atrás e avaliar se você precisa fazer alguma coisa, ou seja, se pode resolver o problema de outras maneiras.

Além disso, conselhos úteis dependem muito de suas restrições - que tipo de sistema você está construindo e que tipo de CPU está usando? É um sistema difícil em tempo real? Quanta memória você tem para código e dados? Suporta nativamente todas as operações C (principalmente, multiplicação e divisão) e para quais tipos? De maneira mais geral: leia a folha de dados inteira e entenda .

O conselho mais importante: mantenha as coisas simples.

Por exemplo: esqueça estruturas de dados complexas (hashes, árvores, possivelmente até listas vinculadas) e use matrizes de tamanho fixo. O uso de estruturas de dados mais complicadas é garantido somente depois que você provar por medição que as matrizes são muito lentas.

Além disso, não exagere no design (algo que os desenvolvedores Java / C # têm tendência a fazer): escreva um código processual direto, sem muitas camadas. Abstração tem um custo!

Sinta-se à vontade com a idéia de usar variáveis ​​globais e vá [muito útil para limpezas na ausência de exceções];)

Se você tiver que lidar com interrupções, leia sobre reentrada. Escrever código reentrante é muito pouco trivial.


3

Eu concordo com a resposta de mattnz - na maior parte. Comecei a programar no 8085 há mais de 30 anos, depois no Z80, depois migrando rapidamente para o 8031. Depois disso, fui para os microcontroladores da série 68300, depois para 80x86, XScale, MXL e PICS de 8 bits (mais tarde). palpite significa que eu tenho um círculo completo. Para constar, posso afirmar que os FAE de vários fabricantes importantes de microprocessadores ainda usam assembler, embora de maneira orientada a objetos para reutilização de código intencional.

O que não vejo na resposta aprovada é uma discussão sobre o tipo de processador de destino e / ou a arquitetura proposta. É um 0,50 $ 8,00 amargo com memória limitada? É um núcleo ARM9 com pipelining e 8Meg de flash? Coprocessador de gerenciamento de memória? Será que ele tem um sistema operacional? Um loop while (1)? Um dispositivo consumidor com uma produção inicial de 100000 unidades? Uma empresa iniciante com grandes idéias e sonhos?

Embora eu concorde que os compiladores modernos fazem um ótimo trabalho de otimização, nunca trabalhei em um projeto em 30 anos em que não parei o depurador e visualizei o código de montagem gerado para ver exatamente o que estava acontecendo sob o capô ( reconhecidamente um pesadelo quando pipelining e otimização entram em cena); portanto, o conhecimento da montagem é importante.

E nunca tive CEO, vice-presidente de engenharia, cliente que não me pressionou a tentar enfiar um galão em um contêiner de um litro ou economizar 0,05 $ usando uma solução de software para corrigir um problema de hardware (ei, é apenas software certo? o que é tão difícil?). A otimização da memória (código ou dados) sempre contará.

O que quero dizer é que, se você visualizar o projeto de um ponto de vista de programação puro, obterá uma solução de escopo mais restrito. A Mattnz está certa - faça funcionar e, em seguida, faça-o mais rápido, menor e melhor, mas você ainda precisa gastar muito tempo com os requisitos e as entregas antes mesmo de pensar em codificação.


Olá Gio, evite HTML desnecessário nas suas postagens e use a sintaxe do Markdown . Para <br />você, basta pressionar enter e, para parágrafos, deixar uma linha em branco entre eles. Além disso, quando você referenciar outra resposta, adicione um link a ela. Nesse ponto, pode haver apenas algumas respostas, mas pode haver mais, distribuídas por muitas páginas, e não ficará muito claro qual resposta você quer dizer. Confira o histórico de revisões para ver minhas edições.
precisa saber é

@Gio Obrigado por mencionar outros fatores importantes. +1 :)
AceofSpades

+1 - Boa expansão da minha resposta.
mattnz

1

A resposta de Manttz coloca muito bem os pontos mais importantes sobre como fazer a programação "próxima ao hardware". Afinal, é para isso que C se destina.

No entanto, gostaria de acrescentar que, embora a palavra-chave estrita de "Class" não exista em C - é bastante simples pensar em termos de programação orientada a objetos em C, mesmo quando você estiver próximo ao hardware.

Você pode considerar esta resposta: Melhores práticas de OO para programas C, o que explica esse ponto.

Aqui estão alguns recursos que ajudarão você a escrever um bom código orientado a objetos em C.

uma. Programação orientada a objetos em C
b. este é um bom lugar onde as pessoas trocam idéias
c. e aqui está o livro completo

Outro bom recurso que gostaria de sugerir é:

A série Great Write Code . Este é um livro de dois volumes. O primeiro livro aborda aspectos muito essenciais das máquinas em obras de nível inferior. O segundo livro aborda é "Pensando em nível baixo - escrevendo em nível alto"


1

Você tem alguns problemas. primeiro você deseja que este projeto / código seja portátil? A portabilidade custa desempenho e tamanho. Sua plataforma de escolha e tarefa que você está implementando podem tolerar o tamanho excessivo e o desempenho inferior?

Sim, absolutamente em uma máquina de 8 bits retornando caracteres não assinados em vez de entradas ou shorts não assinados, é uma maneira de melhorar o desempenho e o tamanho. Da mesma forma, em uma máquina de 16 bits, use shorts não assinados e uma máquina de 32 bits sem assinatura. Você pode ver facilmente que, se, por exemplo, você usou ints não assinados em todos os lugares para portabilidade no sistema que está assumindo o controle (o ARM, por exemplo, abrindo caminho para os mercados de menor consumo de energia e menor consumo de energia), esse código é um enorme problema. um micro de 8 bits. Obviamente, você pode simplesmente usar sem assinatura sem int ou short ou char e deixar o compilador escolher o tamanho ideal.

Não apenas assembly embutido, mas linguagem assembly em geral. A montagem em linha é muito não portátil e mais difícil de escrever do que apenas chamar uma função asm. sim, você queima a configuração da chamada e retorna, mas a um custo de desenvolvimento mais fácil, melhor manutenção e controle. A regra ainda se aplica, apenas escreva-a como necessário, se você realmente concluiu o trabalho para concluir que a saída do compilador é seu problema nessa área e quanto ganho de desempenho você pode obter ao fazê-lo manualmente. Em seguida, retorne à portabilidade e manutenção, assim que você começar a misturar C e asm, e toda vez que misturar C e asm, poderá prejudicar sua portabilidade e tornar o projeto menos sustentável, dependendo de quem mais estiver trabalhando nele ou é um produto que você está desenvolvendo agora e alguém tem que manter o caminho. Depois de fazer essa análise, você saberá automaticamente se deve ou não alinhar ou montar com montagem direta. Tenho mais de 25 anos no campo, escrevo misturas C e asm todos os dias, vivo na / na camada de hardware / software e, bem, nunca use asm inline. Raramente vale a pena o esforço, específico demais para o compilador, eu escrevo código não específico do compilador sempre que possível (quase todos os lugares).

A chave para toda a sua pergunta é desmontar o seu código C. Aprenda o que o compilador faz com o seu código e, com o tempo, se desejar, você pode aprender a manipular o compilador para gerar o código desejado sem precisar recorrer ao asm. Com mais tempo, você pode aprender a manipular o compilador para produzir código eficiente em vários destinos, tornando o código mais portátil sem precisar recorrer ao asm. Você não deve ter problemas para entender por que o caracter não assinado funciona melhor como retorno de status de uma função do que o não assinado em um micro de 8 bits. Da mesma forma, o caracter não assinado fica mais caro em sistemas de 16 e 32 bits (algumas arquiteturas ajudam você fora, alguns não).

Alguns microcontroladores de 8 bits, todos ?, são muito hostis ao compilador e nenhum compilador produz um bom código. Não há demanda suficiente para criar um mercado de compiladores para esses dispositivos, para criar um ótimo compilador para esses destinos, de modo que os compiladores existentes estejam lá para atrair mais negócios, programadores não asm, e não porque o compilador seja melhor do que o escrito à mão . Os arm and mips que entram neste mundo estão mudando esse modelo, pois você tem alvos que possuem compiladores que tiveram muito trabalho neles, compiladores que produzem códigos muito bons etc. Para micros com processadores como esse, é claro que você ainda tem No caso em que você precisa ir para asm, mas não é tão frequente, é muito mais fácil dizer ao compilador o que você quer que ele faça do que não usá-lo. Observe que manipular um compilador não é um código feio e ilegível, na verdade, é o código oposto, limpo e direto, talvez reorganizando alguns itens. Controlando o tamanho de suas funções e o número de parâmetros, esse tipo de coisa faz uma enorme diferença na saída do compilador. Evite os recursos ghee-whiz do compilador ou idioma, KISS, mantenha-o estúpido, geralmente produz um código muito melhor e mais rápido.


Você não indica o tipo de produto que produz, eu diria que é um volume muito alto, margem baixa ou nicho de mercado específico com margens insanas. Isso é fundamental para decidir em nível de negócios se você deve usar um pequeno montador de 8 bits, criado à mão, ou um micro e C. maior. Em resposta ao seu comentário (excluído?) - eu trabalho com micros de 8 bits. comece com um micro maior que o necessário e faça a revisão somente quando e se o custo da BOM se tornar um problema.) O tempo de comercialização, o custo de oportunidade e o custo de desenvolvimento amortizado nos permitem o luxo de adicionar 10 ou 20 centavos à BOM.
mattnz
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.