PIC32 vs dsPIC vs ARM vs AVR, a arquitetura importa quando estamos programando em linguagem C de qualquer maneira? [fechadas]


10

Atualmente, estamos usando o microcontrolador PIC32 de 32 bits. Está funcionando bem para as nossas necessidades, mas também estamos explorando outros microcontoladores que podem nos acomodar melhor + temos outros projetos para os quais estamos selecionando o MCU. Para esse fim, selecionamos o microcontolador SAM DA baseado em ARM, que é o mesmo de 32 bits, mas baseado em ARM (mais popular que o PIC32 - na indústria).

Agora, para o PIC32, usamos o MPLAB, mas para o ARM córtex-M0, usaremos o Atmel Studio. Usaremos a linguagem C nas duas plataformas. O que me preocupa é que estaremos usando dois microcontoladores de 32 bits (da mesma empresa), mas com arquiteturas diferentes. Isso exigirá que aprendamos dois dispositivos diferentes e aumentará nossa "curva de aprendizado" + tempo de entrega. Mas, por outro lado, também acho que, como usaremos a linguagem C nos dois casos, a curva de aprendizado do ARM não deve ser tão ouvida e vale a pena explorar esse processador também.

Minha principal pergunta é: qual a diferença da arquitetura quando estamos programando em linguagem C, uma vez que fornece uma abstração das partes internas do microcontrolador. E quais são as principais diferenças no MPLAP e no Atmel Studio , considerando a programação em linguagem C?


2
Se as coisas estão funcionando com o PIC32, qual é o sentido de mudar? Mesmo que o código seja completamente portado (não será), ainda há a nova cadeia de ferramentas e o IDE a se acostumar. Qual é o objetivo? Mudar por motivos religiosos ou para ser "baseado em ARM" (ou qualquer outra coisa) é tolo. Você precisa ter um bom motivo, mas não nos mostrou nenhum.
precisa

Eu não perguntei sobre a mudança. Falei sobre a escolha de uma arquitetura diferente para outros projetos, pois estamos trabalhando em vários projetos + há espaço para melhorias em nosso design existente. O ponto principal era sobre a curva de aprendizado e os desafios ao trabalhar com duas arquiteturas diferentes ao mesmo tempo.
engineer

Uma coisa que eu achei que Atmel Studio oferece sincronismo superior do que MPLAB youtube vídeo
engenheiro de

Respostas:


20

Este é um tópico bastante opinativo. Eu posso falar por mim mesmo (AVR, ARM, MSP430).

A diferença 1 (mais significativa) está nos periféricos. Cada um dos MCU possui UART, SPI, temporizadores etc. semelhantes - apenas registre nomes e bits são diferentes. Na maioria das vezes, era o principal problema que eu tinha que lidar ao mover o código entre os chips. Solução: escreva seus drivers com uma API comum, para que seu aplicativo possa ser portátil.

A diferença 2 é a arquitetura da memória. Para colocar constantes em flash em um AVR, é necessário usar atributos e funções especiais para lê-las. No mundo do ARM, você simplesmente desrefere um ponteiro porque existe um único espaço de endereço (não sei como os PICs pequenos lidam com ele, mas presumiríamos que eles estão mais próximos do AVR).

A diferença 3 é declaração e manuseio de interrupção. avr-gcctem a ISR()macro O ARM possui apenas um nome de função (como someUART_Handler () - se você usar cabeçalhos CMSIS e código de inicialização). Os vetores de interrupção ARM podem ser colocados em qualquer lugar (incluindo RAM) e modificados em tempo de execução (muito útil se você tiver, por exemplo, dois protocolos UART diferentes que podem ser alternados). O AVR tem apenas a opção de usar vetores no "flash principal" ou na "seção do carregador de inicialização" (portanto, se você quiser lidar com interrupções de maneira diferente, precisará usar uma ifinstrução).

Diferença 4 - modos de suspensão e controle de potência. Se você tiver a necessidade de menor consumo de energia, precisará executar todos os recursos do MCU. Isso pode diferir muito entre o MCU - alguns têm modos de economia de energia mais grosseiros, outros podem ativar / desativar periféricos individuais. Alguns MCUs possuem reguladores ajustáveis ​​para que você possa executá-los com tensão mais baixa em velocidade mais baixa etc. Não vejo uma maneira fácil de obter a mesma eficiência em um MCU (digamos) com 3 modos de potência global e outro com 7 modos de potência e controle de relógio periférico individual.

A coisa mais importante ao se preocupar com a portabilidade é dividir claramente seu código em partes dependentes de hardware (drivers) e independentes de hardware (aplicativo). Você pode desenvolver e testar o último em um PC comum com um driver simulado (por exemplo, console em vez de um UART). Isso me salvou muitas vezes, pois 90% do código do aplicativo estava completo antes que o hardware do protótipo saísse do forno de refluxo :)

Na minha opinião, o bom do ARM é a "monocultura" - disponibilidade de muitos compiladores (gcc, Keil, IAR ... para citar alguns), muitos IDEs gratuitos e com suporte oficial (pelo menos para NXP, STM32, Silicon Labs, Nordic), muitas ferramentas de depuração (SEGGER - especialmente Ozone, ULINK, OpenOCD ...) e muitos fornecedores de chips (eu nem vou começar a nomeá-los). O PIC32 é principalmente limitado ao Microchip (mas só importa se você não gostar das ferramentas deles.

Quando se trata de código C. É 99% o mesmo, uma ifdeclaração é a mesma, um loop funciona da mesma maneira. No entanto, você deve se preocupar com o tamanho da palavra nativa. Por exemplo, um forloop em um AVR é ​​mais rápido se você usar uint8_tpara o contador, enquanto no ARM uint32_té o tipo mais rápido (ou int32_t). O ARM precisaria verificar o estouro de 8 bits toda vez que você usasse um tipo menor.

A seleção de um MCU e / ou fornecedor em geral é principalmente sobre política e logística (a menos que você tenha restrições muito claras de engenharia, por exemplo: alta temperatura - use MSP430 ou Vorago). Mesmo que o aplicativo possa ser executado em qualquer coisa e apenas 5% do código (drivers) precise ser desenvolvido e suportado durante a vida útil do produto - ainda é um custo extra para a empresa. Todos os lugares em que trabalhei tinham um fornecedor e uma linha MCU favoritos (como "escolha qualquer Kinetis que você queira, a menos que haja uma boa razão para escolher algo diferente"). Também ajuda se você tiver outras pessoas para pedir ajuda; assim, como gerente, eu evitaria ter um departamento de desenvolvimento para 5 pessoas, onde todos usassem um chip totalmente diferente.


3
“O AVR é ​​mais rápido se você usar o uint8_t no balcão, enquanto no ARM o uint32_t é o tipo mais rápido (ou int32_t). O ARM precisaria verificar o estouro de 8 bits toda vez que você usasse um tipo menor. ” você pode usar uint_fast8_t se precisar de apenas pelo menos 8 bits.
Michael

@ Michael - com certeza você pode usar os tipos _fast, mas não pode contar com o comportamento de estouro. Em stdint.h do meu gcc Eu tenho "typedef uint_fast8_t int sem sinal", que basicamente é um uint32_t :)
filo

Tentar escrever uma API que seja eficiente, universal e completa é difícil, uma vez que plataformas diferentes têm habilidades diferentes. A CPU provavelmente importa menos que os periféricos e as decisões de design feitas com eles. Por exemplo, alguns dispositivos permitem reconfigurar vários periféricos a qualquer momento em no máximo alguns microssegundos, enquanto outros podem exigir várias etapas espalhadas por centenas de microssegundos ou mesmo milissegundos. Uma função de API destinada ao padrão anterior pode ser utilizável em uma rotina de serviço de interrupção que é executada em 10.000Hz, mas ...
supercat

... não podia suportar esse uso em plataformas que exigiriam a dispersão de operações por centenas de microssegundos. Não sei por que os designers de hardware não parecem se esforçar muito para oferecer suporte à semântica da API "operação rápida a qualquer momento", mas muitos usam um modelo que sincroniza operações individuais em vez de declarar, de modo que, por exemplo, se uma solicitação foi dada a ligar um dispositivo e o código perceber que não precisa estar ligado, o código deve aguardar a ativação do dispositivo antes que ele possa emitir a solicitação de desativação. Lidar com isso sem problemas em uma API adiciona grandes complicações.
supercat

11

Eu usei vários MCUs de quatro fabricantes diferentes. O trabalho principal é sempre familiarizar-se com os periféricos.

Por exemplo, um UART em si não é muito complexo e acho a porta dos meus drivers facilmente. Mas a última vez que levei quase um dia para que os relógios, pinos de E / S interrompessem, ativassem etc., foram resolvidos.

O GPIO pode ser muito complexo. Bit-set, bit-clear, bit-toggle, Funções especiais ativar / desativar, tri-state. Em seguida, você obtém interrupções: qualquer extremidade, ascensão, queda, nível baixo, nível alto, autolimpeza ou não.

Além disso, existem I2C, SPI, PWM, Timers e mais duas dúzias de periféricos, cada um com seu próprio relógio e cada vez que os registros são diferentes com novos bits. Para todos, leva muitas horas lendo a folha de dados como definir qual bit em que circunstâncias.

O último fabricante teve muitos exemplos de código que eu achei inutilizáveis. Tudo foi abstraído. Mas quando eu o localizei, o código passou por seis! níveis de chamadas de função para definir um bit GPIO. Bom se você tiver um processador de 3GHz, mas não em um MCU de 48MHz. Meu código no final era uma única linha:

GPIO->set_output = bit.

Eu tentei usar drivers mais genéricos, mas desisti. Em um MCU, você está sempre lutando com os ciclos de espaço e relógio. Descobri que a camada de abstração é a primeira a sair pela janela se você gerar uma forma de onda específica em uma rotina de interrupção chamada 10KHz.

Portanto, agora tenho tudo funcionando e pretendo NÃO mudar novamente, a menos que por um motivo muito, muito bom.

Todos os itens acima devem ser amortizados sobre quantos produtos você vende e o que economiza. Venda de um milhão: economizar 0,10 para mudar para um tipo diferente significa que você pode gastar 100.000 em horas de trabalho com software. Vendendo 1000, você tem apenas 100 para gastar.


11
Pessoalmente, é por isso que fico com o assembler. Binário adorável, sem abstração.
Ian Bland

O pré-processador de C pode se sair muito bem com as coisas, especialmente quando combinado com os intrínsecos __builtin_constant. Se alguém definir constantes para cada bit de E / S do formulário (número da porta * número de 32 + bits), é possível escrever uma macro para a OUTPUT_HI(n)qual produzirá um código equivalente a GPIOD->bssr |= 0x400;se nfor uma constante como 0x6A, mas chamar uma sub-rotina simples se nfor não constante. Dito isto, a maioria das APIs de fornecedores que eu vi variam entre medíocres e horríveis.
precisa

8

Isso é mais uma opinião / comentário do que uma resposta.

Você não quer e não deve programar em C. O C ++, quando usado da maneira correta , é muito superior. (OK, devo admitir que, quando usado de maneira errada, é muito pior que C.) Isso limita você a chips que possuem um compilador C ++ (moderno), que é praticamente tudo suportado pelo GCC, incluindo o AVR (com algumas limitações, filo menciona os problemas de um espaço de endereço não uniforme), mas excluindo quase todos os PICs (o PIC32 poderia ser suportado, mas ainda não vi nenhuma porta decente).

Quando você está programando algoritmos em C / C ++, a diferença entre as opções mencionadas é pequena (exceto que um chip de 8 ou 16 bits estará em grande desvantagem quando você faz muita aritmética de 16, 32 ou mais bits). Quando você precisar do último pingo de desempenho, provavelmente precisará usar o assembler (o seu ou o código fornecido pelo fornecedor ou por terceiros). Nesse caso, convém considerar novamente o chip selecionado.

Ao codificar para o hardware, você pode usar alguma camada de abstração (geralmente fornecida pelo fabricante) ou escrever sua própria (com base na folha de dados e / ou código de exemplo). As abstrações C existentes no IME (mbed, cmsis, ...) geralmente são funcionais (quase) corretas, mas falham horrivelmente no desempenho (verifique os antiquários discursam cerca de 6 camadas de indireção para uma operação de conjunto de pinos), usabilidade e portabilidade. Eles desejam expor toda a funcionalidade do chip em particular, o que em quase todos os casos você não precisará e nem se importa, e bloqueia seu código para esse fornecedor em particular (e provavelmente para esse chip).

É aqui que o C ++ pode fazer muito melhor: quando feito corretamente, um conjunto de pinos pode passar por 6 ou mais camadas de abstração (porque isso possibilita uma interface melhor (portátil!) E um código mais curto), além de fornecer uma interface independente do alvo para os casos simples , e ainda resultar no mesmo código de máquina que você escreveria no assembler .

Um trecho do estilo de codificação que eu uso, que pode deixar você entusiasmado ou se afastar horrorizado:

// GPIO part of a HAL for atsam3xa
enum class _port { a = 0x400E0E00U, . . . };

template< _port P, uint32_t pin >
struct _pin_in_out_base : _pin_in_out_root {

   static void direction_set_direct( pin_direction d ){
      ( ( d == pin_direction::input )
         ? ((Pio*)P)->PIO_ODR : ((Pio*)P)->PIO_OER )  = ( 0x1U << pin );
   }

   static void set_direct( bool v ){
      ( v ? ((Pio*)P)->PIO_SODR : ((Pio*)P)->PIO_CODR )  = ( 0x1U << pin );    
   }
};

// a general GPIO needs some boilerplate functionality
template< _port P, uint32_t pin >
using _pin_in_out = _box_creator< _pin_in_out_base< P, pin > >;

// an Arduino Due has an on-board led, and (suppose) it is active low
using _led = _pin_in_out< _port::b, 27 >;
using led  = invert< pin_out< _led > >;

Na realidade, existem mais algumas camadas de abstração. No entanto, o uso final do led, digamos que ativá-lo, não mostra a complexidade ou os detalhes do alvo (para um arduin uno ou um comprimido azul ST32, o código seria idêntico).

target::led::init();
target::led::set( 1 );

O compilador não se deixa intimidar por todas essas camadas e, como não há funções virtuais envolvidas, o otimizador enxerga tudo (alguns detalhes, omitidos, como ativar o relógio periférico):

 mov.w  r2, #134217728  ; 0x8000000
 ldr    r3, [pc, #24]   
 str    r2, [r3, #16]
 str    r2, [r3, #48]   

Foi assim que eu o teria escrito no assembler - se eu tivesse percebido que os registros do PIO podem ser usados ​​com deslocamentos de uma base comum. Nesse caso, provavelmente o faria, mas o compilador é muito melhor em otimizar essas coisas do que eu.

Então, até onde eu tenho uma resposta, é: escreva uma camada de abstração para o seu hardware, mas faça isso em C ++ moderno (conceitos, modelos) para não prejudicar seu desempenho. Com isso, você pode mudar facilmente para outro chip. Você pode até começar a desenvolver algum chip aleatório que tenha, que esteja familiarizado, que tenha boas ferramentas de depuração, etc. e adie a escolha final até mais tarde (quando você tiver mais informações sobre a memória necessária, a velocidade da CPU, etc.).

Uma das falhas do desenvolvimento incorporado da OMI é escolher o chip primeiro (é uma pergunta freqüente neste fórum: qual chip devo escolher ... A melhor resposta é geralmente: não importa.)

(editar - resposta a "Portanto, em termos de desempenho, C ou C ++ estaria no mesmo nível?")

Para as mesmas construções, C e C ++ são os mesmos. O C ++ possui muito mais construções para abstração (apenas algumas: classes, modelos, constexpr) que podem, como qualquer ferramenta, ser usada para o bem ou para o mal. Para tornar as discussões mais interessantes: nem todo mundo concorda com o que é bom ou ruim ...


Então, em termos de desempenho, C ou C ++ estariam no mesmo nível? Eu acho que C ++ terá mais sobrecarga. Definitivamente você me apontou na direção certa, C ++ é o caminho a percorrer não C.
engenheiro de

Os modelos C ++ impõem um polimorfismo em tempo de compilação que pode ter custo zero (ou até negativo) em termos de desempenho, pois o código é compilado para cada caso de uso específico. Porém, isso tende a se prestar melhor à velocidade de segmentação (O3 para o GCC). O polimorfismo em tempo de execução, como funções virtuais, pode sofrer uma penalidade muito maior, embora discutível seja mais fácil de manter e, em alguns casos, suficientemente bom.
Hans

11
Você alega que o C ++ é melhor, mas depois usa o estilo C. Por vergonha.
JAB

@JAB Eu nunca senti muito pelos elencos de novo estilo, mas vou experimentá-los. Mas minha prioridade atual é em outras partes desta biblioteca. O problema real é claro que não consegui passar os ponteiros como parâmetros do modelo.
Wouter van Ooijen

@Hans meu estilo cto (Objetos de Tempo de Compilação) possui um caso de uso bastante restrito (próximo ao hardware, situação conhecida em tempo de compilação), é mais um C-killer do que um substituto para usos tradicionais de OO baseado em virtual. Uma captura acessória útil é que a ausência de indireção torna possível calcular o tamanho da pilha.
Wouter van Ooijen

4

Se bem entendi, você deseja saber quais recursos específicos da arquitetura da plataforma "surgem" no seu ambiente de linguagem C, tornando mais desafiador escrever código portátil e de manutenção em ambas as plataformas.

C já é bastante flexível por ser um "montador portátil". Todas as plataformas que você selecionou têm compiladores comerciais / GCC disponíveis que oferecem suporte aos padrões de linguagem C89 e C99, o que significa que você pode executar código semelhante em todas as plataformas.

Existem algumas considerações:

  • Algumas arquiteturas são Von Neumann (ARM, MIPS), outras são Harvard. As principais limitações surgem quando o seu programa C precisa ler dados da ROM, por exemplo, para imprimir strings, ter dados definidos como "const" ou similar.

Algumas plataformas / compiladores podem ocultar essa "limitação" melhor que outras. Por exemplo, no AVR, você precisa usar macros específicas para ler os dados da ROM. No PIC24 / dsPIC também existem instâncias tblrd dedicadas disponíveis. No entanto, algumas partes também têm o recurso "visibilidade do espaço do programa" (PSVPAG) disponível, que permite mapear uma página do FLASH para a RAM, disponibilizando o endereçamento imediato de dados sem tblrd. O compilador pode fazer isso com bastante eficiência.

ARM e MIPS são Von Neumann, portanto possuem regiões de memória para ROM, RAM e periféricos compactados em 1 barramento. Você não notará nenhuma diferença entre ler dados da RAM ou "ROM".

  • Se você mergulhar abaixo de C e observar as instruções geradas para determinadas operações, encontrará grandes diferenças em relação à E / S. ARM e MIPS são arquitetura de registro de armazenamento de carga RISC . Isso significa que o acesso aos dados no barramento de memória deve seguir as instruções do MOV. Isso também significa que qualquer modificação de um valor periférico levará a uma operação de leitura-modificação-gravação (RMW). Existem algumas partes do ARM que suportam Bit-Banding, que mapeiam registros set / clr-bit no espaço periférico de E / S. No entanto, você precisa codificar esse acesso por conta própria.

Por outro lado, um PIC24 permite que as operações da ALU leiam e gravem dados diretamente via endereçamento indireto (mesmo com modificações no ponteiro ..). Isso tem algumas características de uma arquitetura como o CISC, portanto, uma instrução pode fazer mais trabalho. Esse design pode levar a núcleos de CPU mais complexos, relógios mais baixos, maior consumo de energia etc. Felizmente, para você, a peça já foi projetada. ;-)

Essas diferenças podem significar que um PIC24 pode ser operações de E / S wrt "mais exigentes" do que um chip ARM ou MIPS com freqüência similar. No entanto, você pode obter uma peça ARM / MIPS do clocker muito mais alta pelas mesmas restrições de preço / pacote / design. Acho que, em termos práticos, acho que muitos "aprendendo a plataforma" estão aprendendo o que a arquitetura pode ou não fazer, a rapidez com que um conjunto de operações será, etc.

  • Periféricos, gerenciamento de relógio, etc. diferem por família de peças. A rigor, isso também mudará dentro do ecossistema ARM entre os fornecedores, exceto alguns periféricos ligados ao Cortex m, como NVIC e SysTick.

Essas diferenças podem ser um pouco encapsuladas pelos drivers de dispositivo, mas no final o firmware incorporado possui um alto nível de acoplamento com o hardware, portanto, às vezes, o trabalho personalizado não pode ser evitado.

Além disso, se você estiver deixando os ecossistemas do Microchip / antigo Atmel, poderá descobrir que as peças do ARM exigem mais configuração para que possam funcionar. Quero dizer em termos de; habilitar relógios para periféricos, depois configurá-los e "habilitá-los", configure o NVIC separadamente etc. Isso é apenas parte da curva de aprendizado. Depois que você se lembrar de fazer todas essas coisas, na ordem certa, escrever drivers de dispositivo para todos esses microcontroladores parecerá bastante semelhante em algum momento.

  • Além disso, tente usar bibliotecas como stdint.h, stdbool.h, etc., se você ainda não estiver. Esses tipos de números inteiros explicitam as larguras, o que torna o comportamento do código mais previsível entre as plataformas. Isso pode significar o uso de números inteiros de 32 bits em um AVR de 8 bits; mas se o seu código precisar, que assim seja.

3

Sim e não. Do ponto de vista dos programadores, você está ocultando idealmente os detalhes do conjunto de instruções. Mas, até certo ponto, isso já não é relevante, pois os periféricos, que são o objetivo principal de escrever o programa, não fazem parte do conjunto de instruções. Agora, ao mesmo tempo, você não pode simplesmente comparar as partes do flash 4096Byte nesses conjuntos de instruções, principalmente se estiver usando C, a quantidade de consumo do flash / memória é fortemente determinada pelo conjunto de instruções e pelo compilador, alguns nunca devem ver um compilador (tosse PIC tosse) devido à quantidade de desperdício desses recursos consumida pela compilação. Outros o consumo de flash é uma sobrecarga menor. O desempenho também é um problema ao usar um idioma de alto nível e questões de desempenho nos aplicativos MCU, para que possa fazer a diferença entre gastar US $ 3 por placa para o mcu ou US $ 1.

Se o objetivo é facilitar a programação (com o custo geral do produto), você deve poder baixar um pacote de desenvolvedores para o mcu, de modo que a arquitetura do conjunto de instruções seja algo que você nunca vê; portanto, se essa é sua principal preocupação, não é uma preocupação. Ainda lhe custa dinheiro, tanto quanto o custo do produto para usar essas bibliotecas, mas, o tempo de lançamento no mercado pode ser mais curto, acho que as bibliotecas levam mais tempo / trabalho para usar do que diretamente falando com os periféricos.

Conclusão: os conjuntos de instruções são a menor das suas preocupações, passe para problemas reais.

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.