Cinco anos de uso do C ++ em projetos de microcontroladores em produção

Neste artigo, mostrarei como, durante cinco anos, traduzi as empresas com as quais trabalhei desde o gerenciamento de projetos de microcontroladores em C para C ++ e o que resultou disso (spoiler: tudo está ruim).

Um pouco sobre você


Comecei a escrever com microcontroladores C, tendo apenas experiência na escola com Pascal, depois estudei montador e passei cerca de 3 anos estudando várias arquiteturas de microcontroladores e seus periféricos. Depois, houve a experiência de um trabalho real em C # e C ++ com seu estudo paralelo, que levou vários anos. Após esse período, voltei novamente e por muito tempo a programar microcontroladores, já tendo a base teórica necessária para trabalhar em projetos reais.

Primeiro ano


Eu não tinha nada contra o estilo processual de C, mas a empresa que iniciou minha prática real em projetos reais usou "programação C em um estilo orientado a objetos". Parecia algo assim.

typedef const struct _uart_init { USART_TypeDef *USARTx; uint32_t baudrate; ... } uart_cfg_t; int uart_init (uart_cfg_t *cfg); int uart_start_tx (int fd, void *d, uint16_t l); int uart_tx (int fd, void *d, uint16_t l, uint32_t timeout); 

Essa abordagem teve as seguintes vantagens:

  1. o código continuou sendo o código C. As seguintes vantagens seguem:
    • é mais fácil controlar "objetos", porque é fácil rastrear quem e onde causa o quê e em que sequência (com exceção das interrupções, mas não neste artigo);
    • para armazenar o "ponteiro para o objeto" basta lembrar o fd retornado;
    • se o "objeto" foi excluído, quando você tentar usá-lo, receberá um erro correspondente no valor de retorno da função;
  2. a abstração de tais objetos sobre o HAL usado ali tornava possível escrever objetos personalizáveis ​​para a tarefa a partir de sua própria estrutura de inicialização (e, no caso de falta de funcionalidade do HAL, era possível ocultar o acesso aos registros dentro dos "objetos").

Contras:

  1. se alguém deletou o "objeto" e criou um novo de um tipo diferente, pode acontecer que o novo receba o fd do antigo e não seja determinado o comportamento adicional. Esse comportamento pode ser facilmente alterado ao custo de um pequeno consumo de memória para uma lista vinculada, em vez de usar uma matriz com um valor-chave (a matriz para cada índice fd armazenava um ponteiro na estrutura do objeto).
  2. era impossível marcar estaticamente a memória em "objetos globais". Como na maioria dos aplicativos, os "objetos" foram criados uma vez e não foram mais excluídos, parecia uma "muleta". Aqui, ao criar um objeto, seria possível passar um ponteiro para sua estrutura interna, que foi alocada estaticamente durante o layout, mas isso confundiria ainda mais o código de inicialização e quebraria o encapsulamento.

Quando perguntado por que o C ++ não foi selecionado ao criar toda a infraestrutura, fui respondido aproximadamente ao seguinte: "Bem, o C ++ gera altos custos adicionais, custos de memória não controlada e um arquivo de firmware executável volumoso". Talvez eles estivessem certos. De fato, no momento do início do design, havia apenas o GCC 3.0.5, que não brilhava com especial simpatia pelo C ++ (ainda precisamos trabalhar com ele para escrever programas no QNX6). Não havia constexpr e C ++ 11/14, permitindo criar objetos globais, que em essência eram dados na área .data, calculados no estágio de compilação e métodos para eles.

Para a pergunta, por que não escrever nos registros - recebi uma resposta clara de que o uso de "objetos" permite configurar o mesmo tipo de aplicativo "em um dia".

Percebendo tudo isso e percebendo que agora o C ++ não é o mesmo que era com o GCC 3.0.5, comecei a reescrever a parte principal da funcionalidade no C ++. Para começar, trabalhe com os periféricos de hardware do microcontrolador e depois com os periféricos de dispositivos externos. Na verdade, esse era apenas um shell mais conveniente do que estava disponível naquele momento.

Ano dois e três


Reescrevi tudo o que precisava para meus projetos em C ++ e continuei escrevendo novos módulos imediatamente em C ++. No entanto, essas eram apenas conchas sobre C. Depois de perceber que eu não estava usando C ++ o suficiente, comecei a usar seus pontos fortes: modelos, classes somente de cabeçalho, constexpr e muito mais. Tudo estava indo bem.

Quarto e Quinto Ano


  • todos os objetos são globais e incluem links entre si no estágio de compilação (de acordo com a arquitetura do projeto);
  • todos os objetos têm memória alocada no estágio de layout;
  • por objeto de classe para cada pino;
  • um objeto que encapsula todos os pinos para inicializá-los com um método;
  • um objeto de controle RCC que encapsula todos os objetos que estão nos barramentos de hardware;
  • O projeto do conversor CAN <-> RS485 de acordo com o protocolo do cliente contém 60 objetos;
  • caso algo esteja nesse nível no HAL ou no nível de classe de algum objeto, é necessário não apenas "corrigir o problema", mas também pensar em como corrigi-lo para que essa correção funcione em todas as configurações possíveis deste módulo ;
  • os modelos e constexpr usados ​​não podem ser calculados antes de visualizar os arquivos de mapa, asm e bin do firmware final (ou iniciar a depuração no microcontrolador);
  • em caso de erro no modelo, é emitida uma mensagem com um comprimento de um terço da configuração do projeto do GCC. Ler e entender algo é uma conquista separada.

Sumário


Agora eu entendo o seguinte:
  1. o uso de "construtores de módulos universais" apenas complica desnecessariamente o programa. É muito mais fácil ajustar os registros de configuração para um novo projeto do que mergulhar nos relacionamentos entre os objetos e também na biblioteca HAL;
  2. não tenha medo de usar C ++ com medo de "devorar muita memória" ou "ser menos otimizado que C". Não é não. Você precisa ter medo de que o uso de objetos e muitas camadas de abstração tornem o código ilegível e a depuração será um feito heróico;
  3. se você não usa nada de "complicado", como modelos, herança e outros encantos atraentes de C ++, por que usar C ++? Apenas por uma questão de objetos? Vale a pena? E pelo bem de objetos globais estáticos sem usar novo / excluir proibido em alguns projetos?

Resumindo, podemos dizer que a aparente simplicidade do uso de C ++ acabou sendo apenas uma desculpa para aumentar repetidamente a complexidade do projeto sem nenhum ganho de velocidade ou memória.

Source: https://habr.com/ru/post/pt461113/


All Articles