PIC32 vs dsPIC vs ARM vs AVR, ¿importa la arquitectura cuando programamos en lenguaje C? [cerrado]

10

Actualmente estamos utilizando el microcontrolador PIC32 de 32 bits. Está funcionando bien para nuestras necesidades, pero también estamos explorando otros microcontollers que pueden adaptarse mejor a nosotros + tenemos otros proyectos para los que estamos seleccionando MCU. Para ese fin, hemos seleccionado el microcontoller SAM DA basado en ARM, que es el mismo de 32 bits pero está basado en ARM (más popular que PIC32, en la industria).

Ahora para PIC32 usamos MPLAB pero para ARM cortex-M0, usaremos Atmel Studio. Usaremos lenguaje C en ambas plataformas. Lo que me preocupa es que usaremos dos microcontollers de 32 bits (de la misma compañía) pero con arquitecturas diferentes. Esto requerirá que aprendamos dos dispositivos diferentes y aumentará nuestra "curva de aprendizaje" + tiempo de entrega. Pero, por otro lado, también creo que dado que usaremos el lenguaje C en ambos casos, la curva de aprendizaje para ARM no debería ser tan escuchada y vale la pena explorar ese procesador también.

Mi pregunta principal es, ¿qué tan grande es la diferencia que hace la arquitectura cuando programamos en lenguaje C, ya que proporciona una abstracción de los componentes internos del microcontrolador? Y cuáles son las principales diferencias en MPLAP y Atmel Studio , considerando la programación en lenguaje C.

ingeniero
fuente
2
Si las cosas funcionan con el PIC32, ¿cuál es el punto de cambiar? Incluso si el código se transfiere por completo (no lo hará), todavía queda la nueva cadena de herramientas y el IDE para acostumbrarse. ¿Cuál es el punto de? Cambiar por razones religiosas o para estar "basado en ARM" (o cualquier otra cosa) es una tontería. Necesitas tener una buena razón, pero no nos has mostrado ninguna.
Olin Lathrop
No pregunté sobre el cambio. Hablé sobre elegir una arquitectura diferente para otros proyectos, ya que estamos trabajando en proyectos múltiples + hay espacio para mejorar nuestro diseño existente. El punto principal era sobre la curva de aprendizaje y los desafíos al trabajar con dos arquitecturas diferentes al mismo tiempo.
ingeniero
Una cosa que encontré fue que Atmel Studio proporciona una sincronización superior al video de YouTube
ingeniero el

Respuestas:

20

Este es un tema bastante obstinado. Puedo hablar por mí mismo (AVR, ARM, MSP430).

La diferencia 1 (más significativa) está en los periféricos. Cada uno de los MCU tiene UART, SPI, temporizadores, etc. similares, solo que registrar nombres y bits son diferentes. La mayoría de las veces era el problema principal con el que tenía que lidiar al mover el código entre chips. Solución: escriba sus controladores con una API común, para que su aplicación pueda ser portátil.

La diferencia 2 es la arquitectura de la memoria. Si desea colocar constantes en flash en un AVR, debe usar atributos especiales y funciones especiales para leerlos. En el mundo ARM, simplemente desreferencia un puntero porque hay un solo espacio de direcciones (no sé qué tan pequeños lo manejan los PIC, pero supondría que están más cerca de AVR).

La diferencia 3 es la declaración de interrupción y el manejo. avr-gcctiene la ISR()macro ARM solo tiene un nombre de función (como someUART_Handler (), si usa encabezados CMSIS y código de inicio). Los vectores de interrupción ARM pueden colocarse en cualquier lugar (incluida la RAM) y modificarse en tiempo de ejecución (muy útil si tiene, por ejemplo, dos protocolos UART diferentes que se pueden cambiar). AVR solo tiene la opción de usar vectores en "flash principal" o en la "sección del gestor de arranque" (por lo tanto, si desea manejar las interrupciones de manera diferente, debe usar una ifdeclaración).

Diferencia 4: modos de suspensión y control de potencia. Si necesita el consumo de energía más bajo, debe levantar todas las funciones de la MCU. Esto puede diferir mucho entre MCU: algunos tienen modos de ahorro de energía más burdos, otros pueden habilitar / deshabilitar periféricos individuales. Algunas MCU tienen reguladores ajustables para que pueda ejecutarlas con un voltaje más bajo a una velocidad más baja, etc. No veo una manera fácil de lograr la misma eficiencia en una MCU (digamos) con 3 modos de potencia global y otra con 7 modos de potencia y Control de reloj periférico individual.

Lo más importante al preocuparse por la portabilidad es dividir claramente su código en partes dependientes del hardware (controladores) y partes independientes del hardware (aplicación). Puede desarrollar y probar este último en una PC normal con un controlador simulado (por ejemplo, una consola en lugar de un UART). Esto me salvó muchas veces ya que el 90% del código de la aplicación se completó antes de que el hardware prototipo saliera del horno de reflujo :)

En mi opinión, lo bueno de ARM es la "monocultura": disponibilidad de muchos compiladores (gcc, Keil, IAR ... por nombrar algunos), muchos IDE gratuitos y con soporte oficial (al menos para NXP, STM32, Silicon Labs, Nórdico), muchas herramientas de depuración (SEGGER, especialmente Ozone, ULINK, OpenOCD ...) y muchos proveedores de chips (ni siquiera comenzaré a nombrarlos). El PIC32 se limita principalmente a Microchip (pero solo importa si no le gustan sus herramientas.

Cuando se trata de código C. Es el 99% igual, una ifdeclaración es la misma, un bucle funciona de la misma manera. Sin embargo, debe preocuparse por el tamaño de la palabra nativa. Por ejemplo, un forbucle en un AVR es más rápido si lo usa uint8_tpara el contador, mientras que en ARM uint32_tes el tipo más rápido (o int32_t). ARM tendría que verificar el desbordamiento de 8 bits cada vez que usara un tipo más pequeño.

La selección de un MCU y / o proveedor en general se trata principalmente de política y logística (a menos que tenga restricciones de ingeniería muy claras, por ejemplo: alta temperatura: use MSP430 o Vorago). Incluso si la aplicación puede ejecutarse en cualquier cosa y solo el 5% del código (controladores) tiene que ser desarrollado y soportado durante la vida útil del producto , sigue siendo un costo adicional para la empresa. Todos los lugares en los que he trabajado tenían un proveedor favorito y una línea de MCU (como "elija cualquier Kinetis que desee a menos que haya una muy buena razón para elegir algo diferente"). También ayuda si tiene otras personas para pedir ayuda, por lo que, como gerente, evitaría tener un departamento de desarrollo de 5 personas donde todos usaran un chip totalmente diferente.

filo
fuente
3
“AVR es más rápido si usa uint8_t para el contador, mientras que en ARM uint32_t es el tipo más rápido (o int32_t). ARM tendría que verificar el desbordamiento de 8 bits cada vez que utilizara un tipo más pequeño ". puede usar uint_fast8_t si solo necesita al menos 8 bits.
Michael
@Michael: seguro de que puede usar los tipos _fast, pero no puede contar con el comportamiento de desbordamiento. En mi stdint.h de gcc tengo "typedef unsigned int uint_fast8_t", que básicamente es un uint32_t :)
filo
Tratar de escribir una API que sea eficiente, universal y completa es difícil dado que las diferentes plataformas tienen diferentes capacidades. La CPU probablemente importa menos que los periféricos y las decisiones de diseño que se toman con ellos. Por ejemplo, algunos dispositivos han permitido la reconfiguración de varios periféricos en cualquier momento en un máximo de unos pocos microsegundos, mientras que otros pueden requerir múltiples pasos distribuidos en cientos de microsegundos o incluso milisegundos. Una función API que está destinada para el patrón anterior puede utilizarse dentro de una rutina de servicio de interrupción que se ejecuta a 10,000Hz, pero ...
supercat
... no podría soportar dicho uso en plataformas que requerirían extender las operaciones en cientos de microsegundos. No sé por qué los diseñadores de hardware no parecen esforzarse mucho para admitir la semántica API de "operación rápida en cualquier momento", pero muchos usan un modelo que sincroniza operaciones individuales en lugar de indicar que, por ejemplo, si se ha enviado una solicitud a enciende un dispositivo y el código se da cuenta de que no necesita estar encendido, el código debe esperar a que el dispositivo se encienda antes de que pueda emitir la solicitud de apagado. Manejarlo sin problemas en una API agrega complicaciones mayores.
supercat
11

He usado varias MCU de cuatro fabricantes diferentes. El trabajo principal cada vez más es familiarizarse con los periféricos.

Por ejemplo, un UART en sí no es demasiado complejo y encuentro el puerto de mis controladores fácilmente. Pero la última vez que me llevó casi un día ordenar los relojes, los pines de E / S interrumpen, habilitan, etc.

El GPIO puede ser muy complejo. Bit-set, bit-clear, bit-toggle, Funciones especiales habilitadas / deshabilitadas, tri-estado. Luego, obtienes interrupciones: cualquier borde, subida, caída, nivel bajo, nivel alto, autolimpieza o no.

Luego está I2C, SPI, PWM, Timers y dos docenas más de periféricos, cada uno con su propio reloj habilitado y cada vez que los registros son diferentes con nuevos bits. Para todos ellos, lleva muchas horas leer la hoja de datos sobre cómo establecer qué bit bajo qué circunstancias.

El último fabricante tenía muchos ejemplos de código que encontré inutilizables. Todo estaba abstraído. Pero cuando lo rastreé, ¡el código pasó por seis! niveles de llamadas a funciones para establecer un bit GPIO. Agradable si tiene un procesador de 3GHz pero no en una MCU de 48MHz. Mi código al final fue una sola línea:

GPIO->set_output = bit.

He intentado usar controladores más genéricos pero me he rendido. En una MCU siempre estás luchando con los ciclos espaciales y de reloj. Descubrí que la capa de abstracción es la primera en salir por la ventana si genera una forma de onda específica en una rutina de interrupción llamada a 10KHz.

Así que ahora tengo todo funcionando y planeo NO volver a cambiar a menos que sea por una muy, muy buena razón.

Todo lo anterior debe amortizarse sobre la cantidad de productos que vende y lo que ahorra. Vender un millón: ahorrar 0,10 para cambiar a un tipo diferente significa que puede gastar 100.000 en horas hombre de software. Vender 1000 solo tiene 100 para gastar.

Viejo pedo
fuente
1
Personalmente, esta es la razón por la que me quedo con el ensamblador. Precioso binario, sin abstracción.
Ian Bland
El preprocesador de C puede funcionar bastante bien con cosas, especialmente cuando se combina con intrínsecos __builtin_constant. Si uno define constantes para cada bit de E / S de la forma (número de puerto * 32 + número de bit), es posible escribir una macro para la OUTPUT_HI(n)cual se obtendrá un código equivalente a GPIOD->bssr |= 0x400;if nes una constante como 0x6A, pero llame a una subrutina simple si nes no constante Dicho esto, la mayoría de las API de proveedores que he visto varían entre mediocres y horribles.
supercat
8

Esto es más una opinión / comentario que una respuesta.

No desea y no debe programar en C. C ++, cuando se usa de la manera correcta , es muy superior. (OK, debo admitir que, cuando se usa de manera incorrecta, es mucho peor que C.) Eso lo limita a los chips que tienen un compilador C ++ (moderno), que es más o menos lo que es compatible con GCC, incluido AVR (con Algunas limitaciones, filo menciona los problemas de un espacio de direcciones no uniforme), pero excluye casi todos los PIC (PIC32 podría ser compatible, pero todavía no he visto ningún puerto decente).

Cuando está programando algoritmos en C / C ++, la diferencia entre las opciones que menciona es pequeña (excepto que un chip de 8 o 16 bits tendrá una grave desventaja cuando realice una gran cantidad de aritmética de 16, 32 o más bits). Cuando necesite la última onza de rendimiento, probablemente necesitará usar el ensamblador (ya sea el suyo o el código proporcionado por el proveedor o un tercero). En ese caso, es posible que desee volver a considerar el chip que seleccionó.

Cuando está codificando el hardware, puede usar alguna capa de abstracción (a menudo provista por el fabricante) o escribir la suya (según la hoja de datos y / o el código de ejemplo). Las abstracciones C existentes de IME (mbed, cmsis, ...) a menudo son funcionalmente (casi) correctas, pero fallan horriblemente en el rendimiento (verifique que los viejos pedos despotrican sobre 6 capas de indirección para una operación de configuración de pin), usabilidad y portabilidad. Quieren exponerle toda la funcionalidad del chip en particular, que en casi todos los casos no necesitará y no le importará, y bloquea su código a ese proveedor en particular (y probablemente ese chip en particular).

Esto es donde C ++ puede hacerlo mucho mejor: cuando se hace correctamente, un conjunto de pines puede atravesar 6 o más capas de abstracción (porque eso hace una mejor interfaz (¡portátil!) Y un código más corto posible), pero proporciona una interfaz que es independiente del objetivo para los casos simples , y aun así se obtiene el mismo código de máquina que escribiría en el ensamblador .

Un fragmento del estilo de codificación que uso, que puede hacerte entusiasta o alejarte con horror:

// 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 > >;

En realidad hay algunas capas más de abstracción. Sin embargo, el uso final del led, digamos que encenderlo, no muestra la complejidad o los detalles del objetivo (para una píldora azul Arduin uno o ST32, el código sería idéntico).

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

El compilador no se siente intimidado por todas esas capas, y debido a que no hay funciones virtuales involucradas, el optimizador ve a través de todo (algunos detalles, omitidos, como habilitar el reloj periférico):

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

Así es como lo habría escrito en ensamblador: SI me hubiera dado cuenta de que los registros PIO se pueden usar con compensaciones desde una base común. En este caso probablemente lo haría, pero el compilador es mucho mejor para optimizar tales cosas que yo.

Entonces, hasta donde tengo una respuesta, es: escriba una capa de abstracción para su hardware, pero hágalo en C ++ moderno (conceptos, plantillas) para que no perjudique su rendimiento. Con eso en su lugar, puede cambiar fácilmente a otro chip. Incluso puede comenzar a desarrollar en algún chip aleatorio que tenga, con el que esté familiarizado, tenga buenas herramientas de depuración, etc. y posponga la elección final hasta más tarde (cuando tenga más información sobre la memoria requerida, la velocidad de la CPU, etc.).

En mi opinión, una de las falacias del desarrollo integrado es elegir el chip primero (es una pregunta que a menudo se hace en este foro: qué chip debería elegir ... La mejor respuesta es generalmente: no importa).

(editar - respuesta a "Entonces, en cuanto al rendimiento, ¿C o C ++ estarían al mismo nivel?")

Para las mismas construcciones, C y C ++ son iguales. C ++ tiene muchas más construcciones para la abstracción (solo unas pocas: clases, plantillas, constexpr) que pueden, como cualquier herramienta, usarse para bien o para mal. Para hacer las discusiones más interesantes: no todos están de acuerdo en lo que es bueno o malo ...

Wouter van Ooijen
fuente
Entonces, en cuanto al rendimiento, ¿C o C ++ estarían al mismo nivel? Creo que C ++ tendrá más sobrecarga. Definitivamente me apuntaste en la dirección correcta, C ++ es el camino a seguir, no C.
ingeniero
Las plantillas de C ++ fuerzan el polimorfismo en tiempo de compilación que puede ser cero (o incluso negativo) en términos de rendimiento, ya que el código se compila para cada caso de uso específico. Sin embargo, esto tiende a prestarse mejor a la velocidad de orientación (O3 para GCC). El polimorfismo en tiempo de ejecución, como las funciones virtuales, puede sufrir una penalización mucho mayor, aunque es discutiblemente más fácil de mantener y, en algunos casos, lo suficientemente bueno.
Hans
1
Afirma que C ++ es mejor, pero luego va y usa moldes de estilo C. Para vergüenza.
JAB
@JAB Nunca sentí mucho por los elencos de nuevo estilo, pero los intentaré. Pero mi prioridad actual está en otras partes de esta biblioteca. El problema real es, por supuesto, que no pude pasar los punteros como parámetros de plantilla.
Wouter van Ooijen
El estilo @Hans my cto (Objetos de tiempo de compilación) tiene un caso de uso bastante estrecho (cercano al hardware, situación conocida en tiempo de compilación), es más un asesino en C que un reemplazo para los usos tradicionales de OO basado en virtual. Una captura secundaria útil es que la ausencia de indirección permite calcular el tamaño de la pila.
Wouter van Ooijen
4

Si entiendo correctamente, desea saber qué características específicas de la arquitectura de la plataforma "aparecen" en su entorno de lenguaje C, lo que hace más difícil escribir código portátil y sostenible en ambas plataformas.

C ya es bastante flexible ya que es un "ensamblador portátil". Todas las plataformas que ha seleccionado tienen compiladores GCC / comerciales disponibles que son compatibles con los estándares de lenguaje C89 y C99, lo que significa que puede ejecutar un código similar en todas las plataformas.

Hay algunas consideraciones:

  • Algunas arquitecturas son Von Neumann (ARM, MIPS), otras son Harvard. Las principales limitaciones surgen cuando su programa C necesita leer datos de ROM, por ejemplo, para imprimir cadenas, tener datos definidos como "const" o similar.

Algunas plataformas / compiladores pueden ocultar esta "limitación" mejor que otras. Por ejemplo, en AVR necesita usar macros específicas para leer datos de ROM. En PIC24 / dsPIC también hay disponibles instrucciones de tblrd dedicadas. Sin embargo, algunas partes también tienen disponible la función de "visibilidad del espacio del programa" (PSVPAG) que permite mapear una página del FLASH en la RAM, haciendo que el direccionamiento de datos inmediato esté disponible sin tblrd. El compilador puede hacer esto de manera bastante efectiva.

ARM y MIPS son Von Neumann, por lo que tienen regiones de memoria para ROM, RAM y periféricos empaquetados en 1 bus. No notará ninguna diferencia entre leer datos de RAM o "ROM".

  • Si bucea por debajo de C y mira las instrucciones generadas para ciertas operaciones, encontrará algunas grandes diferencias en torno a las E / S. ARM y MIPS son una arquitectura de registro de almacenamiento de carga RISC . Esto significa que el acceso a datos en el bus de memoria debe pasar por instrucciones MOV. Esto también significa que cualquier modificación de un valor periférico conducirá a una operación de lectura-modificación-escritura (RMW). Hay algunas partes ARM que admiten Bit-Banding, ese conjunto de mapas / registros de clr-bit en el espacio periférico de E / S. Sin embargo, debe codificar este acceso usted mismo.

Por otro lado, un PIC24 permite que las operaciones de ALU lean y escriban datos directamente a través del direccionamiento indirecto (incluso con modificaciones de puntero ...). Esto tiene algunas características de una arquitectura similar a CISC, por lo que 1 instrucción puede hacer más trabajo. Este diseño puede conducir a núcleos de CPU más complejos, relojes más bajos, mayor consumo de energía, etc. Afortunadamente para usted, la pieza ya está diseñada. ;-)

Estas diferencias pueden significar que un PIC24 puede ser operaciones de E / S wrt "más contundentes" que un chip ARM o MIPS con reloj similar. Sin embargo, puede obtener una parte ARM / MIPS de reloj mucho más alta por las mismas restricciones de precio / paquete / diseño. Supongo que, en términos prácticos, creo que mucho de "aprender la plataforma" se está dando cuenta de lo que la arquitectura puede y no puede hacer, qué tan rápido será un conjunto de operaciones, etc.

  • Los periféricos, la gestión del reloj, etc. difieren según la familia de piezas. Estrictamente hablando, esto también cambiará dentro del ecosistema ARM entre los proveedores, a excepción de algunos periféricos unidos a Cortex m como NVIC y SysTick.

Estas diferencias pueden ser encapsuladas de alguna manera por los controladores de dispositivo, pero al final el firmware incorporado tiene un alto nivel de acoplamiento con el hardware, por lo que a veces no se puede evitar el trabajo personalizado.

Además, si abandona los ecosistemas de Microchip / ex Atmel, es posible que las piezas ARM requieran más configuración para que funcionen. Me refiero en términos de; habilitar relojes a periféricos, luego configurar periféricos y "habilitarlos", configurar NVIC por separado, etc. Esto es solo parte de la curva de aprendizaje. Una vez que recuerde hacer todas estas cosas, en el orden correcto, escribir controladores de dispositivos para todos estos microcontroladores se sentirá bastante similar en algún momento.

  • Además, intente usar bibliotecas como stdint.h, stdbool.h, etc. si aún no lo ha hecho. Estos tipos enteros hacen explícitos los anchos, lo que hace que el comportamiento del código sea más predecible entre las plataformas. Esto puede significar el uso de enteros de 32 bits en un AVR de 8 bits; pero si tu código lo necesita, que así sea.
Hans
fuente
3

Si y no. Desde la perspectiva de los programadores, lo ideal es ocultar los detalles del conjunto de instrucciones. Pero eso, en cierta medida, ya no es relevante, los periféricos, que es el objetivo de escribir el programa, no forman parte del conjunto de instrucciones. Ahora, al mismo tiempo, no puede comparar las partes flash 4096Byte en esos conjuntos de instrucciones, particularmente si se usa C, la cantidad de consumo de la memoria flash / memoria está fuertemente determinada por el conjunto de instrucciones y el compilador, algunos nunca deberían ver un compilador (tos PIC tos) debido a la cantidad de desperdicio de esos recursos que se consume al compilar. Otros consumo flash es una sobrecarga menor. El rendimiento también es un problema cuando se usa un lenguaje de alto nivel y el rendimiento es importante en las aplicaciones MCU, por lo que puede marcar la diferencia entre gastar $ 3 por placa para el mcu o $ 1.

Si se trata de facilitar la programación (al costo total del producto), debería poder descargar un paquete de desarrolladores para el mcu de modo que la arquitectura del conjunto de instrucciones sea algo que nunca verá, por lo que si esa es su principal preocupación, No es una preocupación. Todavía le cuesta dinero en cuanto al costo del producto usar estas bibliotecas, pero, el tiempo de comercialización puede ser más corto, creo que las bibliotecas tardan más tiempo / trabajo en usar que hablar directamente con los periféricos.

En pocas palabras, los conjuntos de instrucciones son la menor de sus preocupaciones, pase a problemas reales.

viejo contador de tiempo
fuente