12 votos

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

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

Ahora para PIC32 usamos MPLAB pero para ARM cortex-M0, usaremos Atmel Studio. Vamos a utilizar el lenguaje C en ambas plataformas. Lo que me preocupa es que vamos a utilizar dos microcontroladores de 32 bits (de la misma empresa) pero con arquitecturas diferentes. Esto nos obligará a aprender dos dispositivos diferentes y aumentará nuestra "curva de aprendizaje" + tiempo de entrega. Pero por otro lado, también creo que como vamos a utilizar 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, la gran diferencia que supone la arquitectura cuando programamos en lenguaje C, ya que proporciona una abstracción de las partes internas del microcontrolador. Y ¿Cuáles son las principales diferencias entre MPLAP y Atmel Studio? , considerando la programación en lenguaje C.

2 votos

Si las cosas funcionan con el PIC32, ¿qué sentido tiene cambiar? Aunque el código se adapte completamente (no lo hará), todavía hay que acostumbrarse a la nueva cadena de herramientas y al IDE. ¿Qué sentido tiene? 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.

0 votos

No he preguntado por el cambio. Hablé sobre la elección de una arquitectura diferente para otros proyectos, ya que estamos trabajando en múltiples proyectos + hay margen de mejora en nuestro diseño actual. El punto principal era sobre la curva de aprendizaje y los desafíos de trabajar con dos arquitecturas diferentes al mismo tiempo.

0 votos

Una cosa que encontré es que Atmel Studio proporciona una sincronización superior a la de MPLAB video de youtube

21voto

filo Puntos 1

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

La diferencia 1 (la más significativa) está en los periféricos. Cada uno de los MCU tiene similares UART, SPI, temporizadores, etc. - Sólo los nombres de los registros y los bits son diferentes. La mayoría de las veces fue el principal problema con el que tuve que lidiar al mover el código entre los chips. Solución: escriba sus controladores con una API común, para que tu aplicación sea portable.

La diferencia 2 es la arquitectura de la memoria. Si quieres colocar constantes en la flash en un AVR tienes que usar atributos especiales y funciones especiales para leerlas. En el mundo ARM sólo tienes que desreferenciar un puntero porque hay un único espacio de direcciones (no sé cómo lo manejan los PICs pequeños, pero asumiría que están más cerca del AVR).

La diferencia 3 es la declaración y el manejo de las interrupciones. avr-gcc tiene el ISR() macro. ARM sólo tiene un nombre de función (como someUART_Handler() - si usas cabeceras CMSIS y código de inicio). Los vectores de interrupción de ARM pueden ser colocados en cualquier lugar (incluyendo la RAM) y modificados en tiempo de ejecución (muy útil si tienes, por ejemplo, dos protocolos UART diferentes que pueden ser cambiados). El AVR sólo tiene la opción de usar vectores en la "flash principal" o en la "sección del cargador de arranque" (así que si quieres manejar las interrupciones de forma diferente tienes que usar un if declaración).

Diferencia 4: modos de suspensión y control de la energía. Si necesitas el menor consumo de energía, entonces tienes que aprovechar todas las características de la MCU. Esto puede diferir mucho entre MCU - algunos tienen modos de ahorro de energía más gruesos, algunos pueden activar/desactivar periféricos individuales. Algunos MCUs tienen reguladores ajustables para que puedas hacerlos funcionar con un voltaje más bajo a una velocidad más lenta, etc. No veo una manera fácil de lograr la misma eficiencia en una MCU (digamos) con 3 modos de energía global y otra con 7 modos de energía y control de reloj de periféricos individuales.

Lo más importante cuando se trata de la portabilidad es dividir claramente el código en partes dependientes del hardware (controladores) e independientes del hardware (aplicación). Puedes desarrollar y probar esto último en un PC normal con un controlador simulado (por ejemplo, una consola en lugar de una UART). Esto me salvó muchas veces, ya que el 90% del código de la aplicación estaba completo antes de que el prototipo de hardware 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 IDEs gratuitos y con soporte oficial (al menos para NXP, STM32, Silicon Labs, Nordic) , muchas herramientas de depuración (SEGGER - especialmente el Ozone, ULINK, OpenOCD...) y muchos vendedores de chips (ni siquiera voy a empezar a nombrarlos). El PIC32 se limita principalmente a Microchip (pero sólo importa si no te gustan sus herramientas.

Cuando se trata de código C. Es el mismo en un 99%, un if es lo mismo, un bucle funciona de la misma manera. Sin embargo, hay que tener en cuenta el tamaño nativo de la palabra. Por ejemplo, un for en un AVR es más rápido si se utiliza 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 comprobar el desbordamiento de 8 bits cada vez si se utiliza un tipo más pequeño.

Selección de una MCU y/o un proveedor en general se trata sobre todo de política y logística (a menos que se tengan restricciones de ingeniería muy claras, por ejemplo: alta temperatura - usar MSP430 o Vorago). Incluso si la aplicación puede ejecutarse en cualquier cosa y sólo hay que desarrollar el 5% del código (controladores) y con apoyo a lo largo de la vida del producto - sigue siendo un coste adicional para la empresa. Todos los lugares en los que he trabajado tenían un proveedor favorito y una línea de MCU (como "elige cualquier Kinetis que quieras a menos que haya una muy buena razón para elegir algo diferente"). También ayuda si tienes otras personas a las que pedir ayuda, así que como gerente evitaría tener un departamento de desarrollo de 5 personas en el que cada uno usara un chip totalmente diferente.

3 votos

"AVR es más rápido si usas 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 comprobar el desbordamiento de 8 bits cada vez si se utiliza un tipo más pequeño". puedes utilizar uint_fast8_t si sólo necesitas al menos 8 bits.

0 votos

@Michael - seguro que puedes usar los tipos _fast, pero no puedes 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 :)

0 votos

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 tomadas con ellos. Por ejemplo, algunos dispositivos permiten reconfigurar varios periféricos en cualquier momento en un máximo de unos pocos microsegundos, mientras que otros pueden requerir múltiples pasos repartidos en cientos de microsegundos o incluso milisegundos. Una función de la API destinada al primer patrón puede ser utilizable dentro de una rutina de servicio de interrupción que se ejecuta a 10.000 Hz, pero...

12voto

yeyo123321 Puntos 8

He utilizado varios MCU de cuatro fabricantes diferentes. El trabajo principal cada vez es familiarizarse con los periféricos.

Por ejemplo una UART en sí no es demasiado compleja y encuentro mi puerto de controladores fácilmente. Pero la última vez que me tomó casi un día para conseguir los relojes, los pines de E / S interrumpir, permitir, etc. ordenados.

El GPIO puede ser muy complejo. Fijación de bits, borrado de bits, conmutación de bits, habilitación/deshabilitación de funciones especiales, triestado. Luego tienes interrupciones: cualquier borde, subida, bajada, nivel-bajo, nivel-alto, auto-despeje o no.

Luego hay I2C, SPI, PWM, temporizadores y dos docenas más de tipos de periféricos, cada uno con sus propias habilitaciones de reloj y cada vez los registros son diferentes con nuevos bits. Para todos ellos se necesitan muchas horas de lectura de la hoja de datos para saber cómo configurar cada bit en cada circunstancia.

El último fabricante tenía un montón de código de ejemplo que me pareció inservible. Todo estaba abstraído. Pero cuando lo rastreé, el código pasaba por ¡seis! niveles de llamadas de función para establecer un bit GPIO. Es bueno si tienes un procesador de 3GHz pero no en una MCU de 48MHz. Mi código al final era una sola línea:

GPIO->set_output = bit.

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

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

Todo lo anterior debe amortizarse en función del número de productos que vendas y de lo que te ahorres. Vendiendo un millón: ahorrar 0,10 para cambiar de tipo significa que puedes gastar 100.000 en horas de trabajo de software. Vendiendo 1.000 sólo tienes 100 para gastar.

1 votos

Personalmente, esta es la razón por la que me quedo con el ensamblador. Un binario encantador, ninguna abstracción.

0 votos

El preprocesador de C puede hacer bastante bien las cosas, especialmente cuando se combina con los 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 OUTPUT_HI(n) que dará lugar a un código equivalente a GPIOD->bssr |= 0x400; si n es una constante como 0x6A, pero llama a una subrutina simple si n no es constante. Dicho esto, la mayoría de las API de los proveedores que he visto oscilan entre lo mediocre y lo horrible.

8voto

GetFree Puntos 495

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

No quieres ni debes programar en C. C++, cuando se utiliza de forma correcta es muy superior. (Vale, tengo que admitir que cuando se usa de forma incorrecta es mucho peor que C.) Eso te limita a los chips que tienen un compilador (moderno) de C++, que es más o menos todo lo que soporta GCC, incluyendo AVR (con algunas limitaciones, filo menciona los problemas de un espacio de direcciones no uniforme), pero excluyendo casi todos los PICs (PIC32 podría ser soportado, pero no he visto ningún puerto decente todavía).

Cuando estás programando algoritmos en C/C++ la diferencia entre las opciones que mencionas es pequeña (excepto que un chip de 8 o 16 bits estará en grave desventaja cuando hagas mucha aritmética de 16, 32 o más bits). Cuando necesites la última onza de rendimiento, probablemente tendrás que usar ensamblador (ya sea el tuyo propio o el código proporcionado por el vendedor o un tercero). En ese caso, es posible que quieras reconsiderar el chip que has elegido.

Cuando estás codificando para el hardware puedes usar alguna capa de abstracción (a menudo proporcionada por el fabricante) o escribir la tuya propia (basada en la hoja de datos y/o el código de ejemplo). IME las abstracciones existentes en C (mbed, cmsis, ...) son a menudo funcionalmente (casi) correctas, pero fallan horriblemente en el rendimiento (comprueba el despotricar de oldfarts sobre 6 capas de indirección para una operación de ajuste de pin), usabilidad y portabilidad. Quieren exponer todo funcionalidad del chip en particular, que en casi todos los casos no necesitarás ni te importará, y bloquea tu código a ese proveedor en particular (y probablemente a ese chip en particular).

Esto es lo que C++ puede hacer mucho mejor: cuando se hace correctamente, un conjunto de pines puede pasar por 6 o más capas de abstracción (porque eso hace posible una mejor interfaz (¡portátil!) y un código más corto), pero proporcionar una interfaz independiente del objetivo para los casos sencillos y sigue dando como resultado el mismo código máquina que se escribiría en ensamblador .

Un fragmento del estilo de codificación que utilizo, que puede entusiasmarte o hacerte retroceder 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 para encenderlo, no muestra la complejidad ni los detalles del objetivo (para un arduin uno o un ST32 blue pill 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 como no hay funciones virtuales implicadas, el optimizador lo ve todo (algunos detalles, omitidos, como la activación del reloj del periférico):

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

Que es como lo habría escrito en ensamblador - si me hubiera dado cuenta de que los registros PIO pueden ser utilizados con desplazamientos desde una base común. En este caso probablemente lo haría, pero el compilador es mucho mejor que yo para optimizar estas cosas.

Así que la respuesta que tengo es: escribe una capa de abstracción para tu hardware, pero hazlo en C++ moderno (conceptos, plantillas) para que no perjudique tu rendimiento. Con eso en su lugar, usted puede cambiar fácilmente a otro chip. Incluso puedes empezar a desarrollar en algún chip al azar que tengas por ahí, con el que estés familiarizado, para el que tengas buenas herramientas de depuración, etc. y posponer la elección final hasta más adelante (cuando tengas más información sobre la memoria necesaria, la velocidad de la CPU, etc.).

OMI una de las falacias del desarrollo embebido es elegir primero el chip (es una pregunta que se hace a menudo en este foro: qué chip debo elegir para .... La mejor respuesta es generalmente: no importa).

(editar - respuesta a "Entonces, en cuanto a 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 (sólo algunas: clases, plantillas, constexpr) que pueden, como cualquier herramienta, usarse para lo bueno o para lo malo. Para hacer las discusiones más interesantes: no todos están de acuerdo en lo que es bueno o malo...

0 votos

Entonces, en cuanto al rendimiento, ¿C o C++ estarían al mismo nivel? Creo que C++ tendrá más sobrecarga. Definitivamente me has indicado la dirección correcta, C++ es el camino a seguir, no C.

0 votos

Las plantillas de C++ obligan a un polimorfismo en tiempo de compilación que puede tener un coste 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 más a la velocidad (O3 para GCC). El polimorfismo en tiempo de ejecución, como las funciones virtuales, puede sufrir una penalización mucho mayor, aunque es discutible que sea más fácil de mantener y, en algunos casos, suficientemente bueno.

1 votos

Afirmas que C++ es mejor, pero luego vas y usas los moldes de C. Es una vergüenza.

4voto

Avner Puntos 2065

Si lo he entendido bien, quieres saber qué características específicas de la arquitectura de la plataforma "aparecen" en tu entorno de lenguaje C, lo que hace más difícil escribir código mantenible y portable en ambas plataformas.

C ya es bastante flexible al ser un "ensamblador portátil". Todas las plataformas que has seleccionado tienen compiladores GCC/comerciales disponibles que tienen soporte para los estándares de lenguaje C89 y C99, lo que significa que puedes ejecutar 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 tu programa en C necesita leer datos de la ROM, por ejemplo, para imprimir cadenas, tener datos definidos como "const" o similares.

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

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ás ninguna diferencia entre leer datos de la RAM o de la "ROM".

  • Si buceas por debajo de C, y miras las instrucciones generadas para ciertas operaciones, encontrarás algunas grandes diferencias en torno a la E/S. ARM y MIPS son RISC arquitectura de registro de carga-almacenamiento . Esto significa que el acceso a los datos en el bus de memoria debe pasar por las instrucciones MOV. Esto también significa que cualquier modificación de un valor periférico llevará a una operación de lectura-modificación-escritura (RMW). Hay algunas partes de ARM que soportan Bit-Banding, que mapean registros set/clr-bit en el espacio periférico de E/S. Sin embargo, tienes que codificar este acceso tú mismo.

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

Estas diferencias pueden significar que un PIC24 puede ser "más potente" en cuanto a las operaciones de E/S que un chip ARM o MIPS con un reloj similar. Sin embargo, puedes conseguir una pieza ARM/MIPS con un reloj mucho más alto por las mismas limitaciones de precio/paquete/diseño. Creo que, en términos prácticos, gran parte del "aprendizaje de la plataforma" consiste en comprender lo que la arquitectura puede y no puede hacer, la velocidad de 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, excepto para algunos periféricos vinculados a Cortex m como NVIC y SysTick.

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

Además, si sales de los ecosistemas de Microchip/antigua Atmel, puedes encontrar que las partes ARM requieren más configuración para ponerlas en marcha. Me refiero a: habilitar los relojes a los periféricos, luego configurar los periféricos y "habilitarlos", configurar la NVIC por separado, etc. Esto es sólo parte de la curva de aprendizaje. Una vez que recuerde hacer todas estas cosas, en el orden correcto, escribir los controladores de dispositivos para todos estos microcontroladores se sentirá bastante similar en algún momento.

  • Además, intente utilizar bibliotecas como stdint.h, stdbool.h, etc. si no lo está haciendo ya. Estos tipos de 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.

3voto

Sí y no. Desde la perspectiva del programador, lo ideal es ocultar los detalles del conjunto de instrucciones. Pero hasta cierto punto eso no es relevante, ya que los periféricos, que es el objetivo de escribir el programa, no forman parte del conjunto de instrucciones. Ahora, al mismo tiempo, no se puede comparar las partes de flash de 4096Byte a través de esos conjuntos de instrucciones, sobre todo si se utiliza C, la cantidad de consumo de la flash/memoria está fuertemente determinada por el conjunto de instrucciones y el compilador, algunos nunca deben ver un compilador (tos PIC tos) debido a la cantidad de residuos de esos recursos se consumen por la compilación. El consumo de flash de otros es una sobrecarga menor. El rendimiento también es un problema cuando se utiliza un lenguaje de alto nivel y el rendimiento es importante en las aplicaciones MCU, por lo que puede marcar la diferencia entre gastar 3perboardforthemcuor 1. Construir un millón de unidades supone regalar 2 millones de dólares en relación con unas decenas o cientos de miles de dólares en tiempo de desarrollo de software.

Si se trata de facilitar la programación (al coste total del producto) deberías poder descargar un paquete de desarrolladores para la mcu de tal manera que la arquitectura del conjunto de instrucciones es algo que nunca ves, así que si esa es tu principal preocupación, no es una preocupación. El uso de estas librerías sigue costando dinero en cuanto al coste del producto, pero el tiempo de comercialización es menor. puede ser más corto, me parece que las bibliotecas toman más tiempo/trabajo para usar frente a hablar directamente con los periféricos.

En resumen, los conjuntos de instrucciones son la menor de tus preocupaciones, pasa a los problemas reales.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X