
Después de usar el sistema operativo Arm Mbed, fue posible parpadear el LED , llegó el momento de probar y configurar otros servicios importantes. Se describe lo siguiente:
- Tecnología de configuración de Mbed
- ¿Por qué es difícil cambiar a C ++ en RTOS regular?
- Cómo ahorrar memoria en RTOS
- ¿Cómo se organizan las interrupciones en Mbed OS?
- De lo que es conveniente depurar Mbed OS
- Cómo deshacerse de una capa adicional de abstracción SDK
Continuamos conociendo la tecnología de programación de microcontroladores de la familia MKE18F utilizando ARM Mbed OS
Tecnología de configuración de Mbed
Un componente importante del proyecto Mbed es un sistema de configuración y ensamblaje automático, tanto en línea como fuera de línea , es decir. localmente en la computadora del usuario. Se propone configurar editando archivos .json con nombres especiales. Luego, los scripts en el proyecto Python convierten estos archivos en archivos de encabezado, archivos de espacio de trabajo del IDE seleccionado por el usuario, archivos de comando de enlazador y otros archivos de soporte.
Pero el problema del método descrito está en la opacidad desde el punto de vista de los textos fuente, ya que es muy difícil para nosotros rastrear dónde y qué cambia el sistema de configuración en las fuentes. Por otro lado, en nuestro caso no hay motivo para respaldar la capacidad del proyecto de ser transferido automáticamente a diferentes IDEs.
Por lo tanto, se decidió abandonar este enfoque. Como se indicó en un artículo anterior, simplemente se formó en un proyecto en línea para IDE IAR , se obtuvo un montón de archivos no estructurados en el espacio de trabajo de IDE, luego se sistematizaron y se descartaron innecesarios. Como resultado, ya no necesita hacer la configuración a través de archivos .json y solo hay tres lugares específicos donde se encuentran los parámetros que afectan la configuración de Mbed :
- Opciones del compilador IDE
- Archivo por lotes Linker MKE18F512xxx16_flash.icf
- Archivo de encabezado mbed_config.h
En el archivo mbed_config.h , puede contar alrededor de 130 definiciones, lo cual es muy molesto al principio. Pero afortunadamente, la mayoría de ellos se relacionan con pilas de protocolos inalámbricos que no se utilizan actualmente en el proyecto. Por conveniencia, las entradas se ordenaron de modo que las reales se ubicaran en la parte superior. Inicialmente, las definiciones para diferentes módulos en el archivo mbed_config.h se ubican de forma aleatoria, pero después de ordenarlas se ve así:
Abierto#ifndef __MBED_CONFIG_DATA__ #define __MBED_CONFIG_DATA__ // Configuration parameters #define MBED_CONF_RTOS_PRESENT 1 // set by library:rtos #define MBED_ALL_STATS_ENABLED 1 //#define DEVICE_SLEEP=1 SLEEP #define MBED_CONF_APP_MAIN_STACK_SIZE 1024 #define MBED_CONF_APP_TIMER_THREAD_STACK_SIZE 512 #define MBED_CONF_APP_IDLE_THREAD_STACK_SIZE 512 #define MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE 3000000 // set by library:platform #define MBED_CONF_PLATFORM_ERROR_ALL_THREADS_INFO 0 // set by library:platform #define MBED_CONF_PLATFORM_ERROR_FILENAME_CAPTURE_ENABLED 0 // set by library:platform #define MBED_CONF_PLATFORM_ERROR_HIST_ENABLED 0 // set by library:platform #define MBED_CONF_PLATFORM_ERROR_HIST_SIZE 4 // set by library:platform #define MBED_CONF_PLATFORM_FORCE_NON_COPYABLE_ERROR 0 // set by library:platform #define MBED_CONF_PLATFORM_MAX_ERROR_FILENAME_LEN 16 // set by library:platform #define MBED_CONF_PLATFORM_POLL_USE_LOWPOWER_TIMER 0 // set by library:platform #define MBED_CONF_PLATFORM_STDIO_BAUD_RATE 3000000 // set by library:platform #define MBED_CONF_PLATFORM_STDIO_BUFFERED_SERIAL 0 // set by library:platform #define MBED_CONF_PLATFORM_STDIO_CONVERT_NEWLINES 1 // set by library:platform #define MBED_CONF_PLATFORM_STDIO_CONVERT_TTY_NEWLINES 0 // set by library:platform #define MBED_CONF_PLATFORM_STDIO_FLUSH_AT_EXIT 1 // set by library:platform #define MBED_CONF_DRIVERS_UART_SERIAL_RXBUF_SIZE 256 // set by library:drivers #define MBED_CONF_DRIVERS_UART_SERIAL_TXBUF_SIZE 256 // set by library:drivers #define MBED_CONF_EVENTS_PRESENT 1 // set by library:events #define MBED_CONF_EVENTS_SHARED_DISPATCH_FROM_APPLICATION 0 // set by library:events #define MBED_CONF_EVENTS_SHARED_EVENTSIZE 256 // set by library:events #define MBED_CONF_EVENTS_SHARED_HIGHPRIO_EVENTSIZE 256 // set by library:events #define MBED_CONF_EVENTS_SHARED_HIGHPRIO_STACKSIZE 1024 // set by library:events #define MBED_CONF_EVENTS_SHARED_STACKSIZE 1024 // set by library:events #define MBED_CONF_EVENTS_USE_LOWPOWER_TIMER_TICKER 0 // set by library:events #define MBED_CONF_CELLULAR_DEBUG_AT 0 // set by library:cellular #define MBED_CONF_CELLULAR_RANDOM_MAX_START_DELAY 0 // set by library:cellular #define MBED_CONF_CELLULAR_USE_APN_LOOKUP 1 // set by library:cellular #define MBED_CONF_FILESYSTEM_PRESENT 1 // set by library:filesystem #define MBED_CONF_KINETIS_EMAC_RX_RING_LEN 16 // set by library:kinetis-emac #define MBED_CONF_KINETIS_EMAC_TX_RING_LEN 8 // set by library:kinetis-emac #define MBED_CONF_LORA_ADR_ON 1 // set by library:lora #define MBED_CONF_LORA_APP_PORT 15 // set by library:lora #define MBED_CONF_LORA_APPLICATION_EUI {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set by library:lora #define MBED_CONF_LORA_APPLICATION_KEY {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set by library:lora #define MBED_CONF_LORA_APPSKEY {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set by library:lora #define MBED_CONF_LORA_AUTOMATIC_UPLINK_MESSAGE 1 // set by library:lora #define MBED_CONF_LORA_DEVICE_ADDRESS 0x00000000 // set by library:lora #define MBED_CONF_LORA_DEVICE_EUI {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set by library:lora #define MBED_CONF_LORA_DUTY_CYCLE_ON 1 // set by library:lora #define MBED_CONF_LORA_LBT_ON 0 // set by library:lora #define MBED_CONF_LORA_NB_TRIALS 12 // set by library:lora #define MBED_CONF_LORA_NWKSKEY {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set by library:lora #define MBED_CONF_LORA_OVER_THE_AIR_ACTIVATION 1 // set by library:lora #define MBED_CONF_LORA_PHY EU868 // set by library:lora #define MBED_CONF_LORA_PUBLIC_NETWORK 1 // set by library:lora #define MBED_CONF_LORA_TX_MAX_SIZE 64 // set by library:lora #define MBED_CONF_LWIP_ADDR_TIMEOUT 5 // set by library:lwip #define MBED_CONF_LWIP_ADDR_TIMEOUT_MODE 1 // set by library:lwip #define MBED_CONF_LWIP_DEBUG_ENABLED 0 // set by library:lwip #define MBED_CONF_LWIP_DEFAULT_THREAD_STACKSIZE 512 // set by library:lwip #define MBED_CONF_LWIP_ENABLE_PPP_TRACE 0 // set by library:lwip #define MBED_CONF_LWIP_ETHERNET_ENABLED 1 // set by library:lwip #define MBED_CONF_LWIP_IP_VER_PREF 4 // set by library:lwip #define MBED_CONF_LWIP_IPV4_ENABLED 1 // set by library:lwip #define MBED_CONF_LWIP_IPV6_ENABLED 0 // set by library:lwip #define MBED_CONF_LWIP_MEM_SIZE 36560 // set by library:lwip[Freescale] #define MBED_CONF_LWIP_PPP_THREAD_STACKSIZE 768 // set by library:lwip #define MBED_CONF_LWIP_SOCKET_MAX 4 // set by library:lwip #define MBED_CONF_LWIP_TCP_ENABLED 1 // set by library:lwip #define MBED_CONF_LWIP_TCP_SERVER_MAX 4 // set by library:lwip #define MBED_CONF_LWIP_TCP_SOCKET_MAX 4 // set by library:lwip #define MBED_CONF_LWIP_TCPIP_THREAD_STACKSIZE 1200 // set by library:lwip #define MBED_CONF_LWIP_UDP_SOCKET_MAX 4 // set by library:lwip #define MBED_CONF_LWIP_USE_MBED_TRACE 0 // set by library:lwip #define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_CHANNEL 0 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_CHANNEL_MASK 0x7fff800 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_CHANNEL_PAGE 0 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_DEVICE_TYPE NET_6LOWPAN_ROUTER // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_PANID_FILTER 0xffff // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_PSK_KEY {0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf} // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_PSK_KEY_ID 1 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_SEC_LEVEL 5 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_SECURITY_MODE NONE // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_HEAP_SIZE 32500 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_CHANNEL 22 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_CHANNEL_MASK 0x7fff800 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_CHANNEL_PAGE 0 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_COMMISSIONING_DATASET_TIMESTAMP 0x10000 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_EXTENDED_PANID {0xf1, 0xb5, 0xa1, 0xb2,0xc4, 0xd5, 0xa1, 0xbd } // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_ML_PREFIX {0xfd, 0x0, 0x0d, 0xb8, 0x0, 0x0, 0x0, 0x0} // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_NETWORK_NAME // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_PANID 0x0700 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_PSKC {0xc8, 0xa6, 0x2e, 0xae, 0xf3, 0x68, 0xf3, 0x46, 0xa9, 0x9e, 0x57, 0x85, 0x98, 0x9d, 0x1c, 0xd0} // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_DEVICE_TYPE MESH_DEVICE_TYPE_THREAD_ROUTER // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_MASTER_KEY {0x10, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff} // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_PSKD // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_SECURITY_POLICY 255 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_THREAD_USE_STATIC_LINK_CONFIG 1 // set by library:mbed-mesh-api #define MBED_CONF_MBED_MESH_API_USE_MALLOC_FOR_HEAP 0 // set by library:mbed-mesh-api #define MBED_CONF_NANOSTACK_CONFIGURATION nanostack_full // set by library:nanostack #define MBED_CONF_NANOSTACK_HAL_CRITICAL_SECTION_USABLE_FROM_INTERRUPT 0 // set by library:nanostack-hal #define MBED_CONF_NANOSTACK_HAL_EVENT_LOOP_DISPATCH_FROM_APPLICATION 0 // set by library:nanostack-hal #define MBED_CONF_NANOSTACK_HAL_EVENT_LOOP_THREAD_STACK_SIZE 6144 // set by library:nanostack-hal #define MBED_CONF_NANOSTACK_HAL_EVENT_LOOP_USE_MBED_EVENTS 0 // set by library:nanostack-hal #define MBED_CONF_NANOSTACK_HAL_NVM_CFSTORE 0 // set by library:nanostack-hal #define MBED_CONF_NSAPI_DEFAULT_MESH_TYPE THREAD // set by library:nsapi #define MBED_CONF_NSAPI_DEFAULT_STACK LWIP // set by library:nsapi #define MBED_CONF_NSAPI_DEFAULT_WIFI_SECURITY NONE // set by library:nsapi #define MBED_CONF_NSAPI_DNS_CACHE_SIZE 3 // set by library:nsapi #define MBED_CONF_NSAPI_DNS_RESPONSE_WAIT_TIME 5000 // set by library:nsapi #define MBED_CONF_NSAPI_DNS_RETRIES 0 // set by library:nsapi #define MBED_CONF_NSAPI_DNS_TOTAL_ATTEMPTS 3 // set by library:nsapi #define MBED_CONF_NSAPI_PRESENT 1 // set by library:nsapi #define MBED_CONF_PPP_CELL_IFACE_APN_LOOKUP 1 // set by library:ppp-cell-iface #define MBED_CONF_PPP_CELL_IFACE_AT_PARSER_BUFFER_SIZE 256 // set by library:ppp-cell-iface #define MBED_CONF_PPP_CELL_IFACE_AT_PARSER_TIMEOUT 8000 // set by library:ppp-cell-iface #define MBED_CONF_PPP_CELL_IFACE_BAUD_RATE 115200 // set by library:ppp-cell-iface #define MBED_CONF_TARGET_NETWORK_DEFAULT_INTERFACE_TYPE ETHERNET // set by target:K66F #define MBED_LFS_BLOCK_SIZE 512 // set by library:littlefs #define MBED_LFS_ENABLE_INFO 0 // set by library:littlefs #define MBED_LFS_INTRINSICS 1 // set by library:littlefs #define MBED_LFS_LOOKAHEAD 512 // set by library:littlefs #define MBED_LFS_PROG_SIZE 64 // set by library:littlefs #define MBED_LFS_READ_SIZE 64 // set by library:littlefs #define NSAPI_PPP_AVAILABLE 0 // set by library:lwip #define NSAPI_PPP_IPV4_AVAILABLE 1 // set by library:lwip #define NSAPI_PPP_IPV6_AVAILABLE 0 // set by library:lwip #define NVSTORE_ENABLED 1 // set by library:nvstore #define NVSTORE_MAX_KEYS 16 // set by library:nvstore // Macros #define _RTE_ // defined by library:rtos #define NS_USE_EXTERNAL_MBED_TLS // defined by library:nanostack #define UNITY_INCLUDE_CONFIG_H // defined by library:utest #endif
Características de la transición a C ++ aplicada a RTOS
La API de nivel superior en Mbed está escrita en C ++ , por lo que este lenguaje debe usarse en el código de la aplicación. Pero hay matices que debes conocer.
El uso de C ++ para RTOS en pequeños sistemas integrados sigue siendo relativamente raro. El problema aquí es que los proyectos RTOS exitosos se esfuerzan por ser multiplataforma, mientras que C ++ impone altas demandas en la gestión de recursos de la plataforma en comparación con C. La razón es el deseo de ocultar al usuario los detalles de la gestión de recursos de bajo nivel. Se trata principalmente de recursos de memoria. Los constructores, destructores, hilos, excepciones con destrucción automática, plantillas de objetos de estructura de datos, etc. utilizan operaciones implícitas con memoria dinámica. Pero el recurso de RAM en sistemas pequeños es muy limitado. RAM es el recurso más escaso en tales sistemas y especialmente en RTOS . En RTOS, a cada tarea se le asigna una pila, el desarrollador no puede predecir su tamaño exacto por adelantado y, por lo tanto, elige con un margen. Por lo tanto, la presencia de RTOS con una docena de tareas requiere de inmediato un tamaño de RAM de 10 a 30 kB. Se necesita mucha memoria para varios analizadores y protocolos (HTTP, HTML ...) y sistemas de archivos. Si se utiliza una pantalla, los requisitos de RAM libre se incrementan aún más.
Las bibliotecas de entornos de desarrollo como IAR están equipadas con buenos administradores de memoria dinámica, pero están diseñadas para un entorno de tiempo de ejecución de subproceso único. Para que puedan comenzar a trabajar en RTOS, debe escribir código adicional. Este proceso se llama retargeting .
En RTOS escrito en C, el retargeting generalmente no se realiza. Como no hay operaciones implícitas con memoria dinámica a nivel de lenguaje, todas las operaciones se realizan explícitamente llamando a sus propias versiones seguras de subprocesos de malloc y funciones libres . El programador tiene control total sobre las operaciones con memoria dinámica y puede aplicar fácilmente todas las medidas posibles para guardarlo.
En el caso de C ++, si queremos usar todas las características de este lenguaje, tendremos que reorientar . Pero la reorientación en cada entorno de desarrollo es un proceso puramente individual. Esto dificulta la vida de los desarrolladores de RTOS .
La siguiente figura es un ejemplo de una estructura de llamada con retargeting. Las funciones __write , __lseek , __read pueden no ser implementadas por el usuario, pero su funcionalidad queda a discreción del IDE . Y ciertamente printf y scanf no serán multiproceso.

Mbed es uno de los pocos, si no el único RTOS que proporciona códigos fuente con retargeting ya realizado para una tríada de herramientas de desarrollo conocidas: GCC , IAR , Keil
A pesar de todo lo anterior, puede encontrar artículos sobre cómo portar RTOS a C ++ sin realizar el retargeting, por ejemplo, resolviendo el problema simplemente reemplazando algunas funciones estándar comunes por las suyas. Esto puede funcionar, pero luego el programador debe ser consciente de varias restricciones implícitas y no documentadas cuando utiliza construcciones C ++ en IAR (solo constructores estáticos, verifique todas las plantillas para ver si hay nuevas excepciones de descarte, etc.) Ya será difícil llamar a C ++ . Mbed, como un sistema fácil de usar, elimina muchas de estas limitaciones, acercándose a Arduino con simplicidad.
Además de todas las versiones recientes de IAR, existen dificultades para cambiar a C11 y C ++ 14, que se describe aquí: https://www.iar.com/support/resources/articles/exploring-c11-and-c14/
¿Cómo se organizan las interrupciones en Mbed?
Por extraño que parezca, no será posible encontrar una clase o función o algo adecuado para organizar el servicio de interrupción en la API de Mbed. Solo puede encontrar la clase InterruptIn , que está destinada solo a puertos externos.
Las respuestas a tales preguntas deben buscarse en CMSIS-RTOS , es decir, en la capa de acceso periférico central CMSIS Cortex-M4 . Las macros se definen allí:
- NVIC_SetVector : establece la rutina de servicio de interrupción (ISR) para un vector dado
- NVIC_SetPriority : establece la prioridad para el vector de interrupción especificado.
- NVIC_EnableIRQ : permite interrumpir llamadas en un vector determinado
Así se inicializa la organización de interrupción del temporizador SysTick :
NVIC_SetVector(mbed_get_m0_tick_irqn(), (uint32_t)SysTick_Handler)
Las prioridades de tareas de Mbed y las prioridades de interrupción no deben confundirse.
Si no se asigna la prioridad, de manera predeterminada se establece en el máximo.
Desde los manejadores de interrupciones asignados de esta manera, puede llamar a cualquier servicio RTOS que no genere expectativas. Es decir transmitir banderas, semáforos, mensajes, buzones, etc. Sin embargo, los servicios se llaman no desde el ISR en sí, sino llamando a las interrupciones de software configurando el bit PENDSVSET en el Control de interrupción y el Registro de estado ( ICSR ) del bloque de control del sistema ( SCB ) del núcleo Cortex-M . Es decir después de completar el controlador de interrupción actual, si no hay otras interrupciones de prioridad, el vector PendSV llamará al controlador del sistema donde se realizará el servicio.
¿Cómo funciona Mbed con memoria dinámica?
La memoria dinámica, o bien montón o montón, es un componente requerido cuando se programa en C ++ . En nuestro proyecto Mbed bajo IAR , el tamaño de esta área de memoria se determina en el archivo de configuración del vinculador MKE18F512xxx16_flash.icf escribiendo en la variable __size_heap__ . Establecemos el tamaño para que ocupe toda la memoria libre restante. Sabemos cuánta memoria libre queda del archivo .map después de la compilación, es decir. dimensionar el montón es un proceso iterativo.
Llamar a constructores estáticos de objetos C ++
Un problema importante al usar C ++ es dónde y cómo se llaman los constructores de objetos globales. Incluso en RTOS que afirman seriedad, por ejemplo, MAX , esto se ignora, es decir, dejado al azar. Allí, los diseñadores participan en una biblioteca de entorno de desarrollo de subproceso único estándar con el mecanismo habitual de asignación de memoria de subproceso único. Pero después del comienzo, los RTOS comunes crean su propio mecanismo para administrar la memoria dinámica, y la memoria ocupada por objetos globales permanece olvidada. Este es un agujero en nuestros esfuerzos por ahorrar memoria y controlar todo.
Mbed abordó este tema mucho más en serio. Hay para cada entorno de desarrollo tiene su propio enfoque. En el IAR, esto se hace así:
- la secuencia de inicialización es interceptada por el código de usuario incluso antes de que los diseñadores llamen
- los métodos de bloqueo de flujos de la API RTOS se sustituyen en la biblioteca estándar
- Las funciones estándar new , delete , malloc , free ... se redirigen a llamadas a la API de memoria dinámica RTOS .
Mbed utiliza adaptadores de biblioteca IAR para operación multihilo
Puede leer sobre la adaptación de IAR para subprocesos múltiples aquí: http://supp.iar.com/FilesPublic/UPDINFO/005691/arm/doc/infocenter/DLIBThreadSupport.html
Mbed ha adaptado tanto los bloqueos del sistema como los bloqueos de flujo de archivos de la biblioteca IAR . Se implementan en el archivo mbed_boot.c y usan mutexes del sistema operativo .
La función __iar_program_start ejecutable en las primeras líneas del archivo startup_MKE18F16.s inicializa la pila del sistema operativo y la memoria dinámica llamando a mbed_set_stack_heap
Dimensionar pilas de tareas
Recortar pilas de tareas al mínimo es la opción más atractiva para ahorrar RAM .
Para las tareas que requieren menos apilamiento, se utilizan diferentes técnicas. Por ejemplo, las funciones de la biblioteca afectan fuertemente a la pila para generar y formatear líneas printf , sprintf , scanf , etc. Tienen la capacidad de asignar grandes áreas temporales para el almacenamiento de datos en la pila. Si nos negamos a usar estas funciones en la tarea, podemos reducir la pila de tareas en unos pocos cientos de bytes.
Mbed OS al inicio crea inmediatamente tres tareas con nombres: "main_thread" , "timer_thread" , "idle_thread" . El tamaño de pila predeterminado para ellos fue determinado por macros en el archivo de encabezado mbed_rtx_conf.h . Movimos las declaraciones de estas pilas al archivo de configuración mbed_config.h y redujimos el tamaño de las pilas. Ahora las definiciones se ven así:
- La pila de tareas "main_thread" está definida por la macro MBED_CONF_APP_MAIN_STACK_SIZE = 1024 bytes
- La pila de tareas timer_thread está definida por la macro MBED_CONF_APP_TIMER_THREAD_STACK_SIZE = 512 bytes
- La pila de tareas idle_thread está definida por la macro MBED_CONF_APP_IDLE_THREAD_STACK_SIZE = 512 bytes
Controles de uso de memoria Mbed
La memoria dinámica y la pila son recursos que requieren atención constante. Para ver cuánta memoria dinámica se usa y cuál es la intensidad de las solicitudes, cuánta pila queda para cada tarea y cuál fue la carga máxima de la pila en Mbed, hay contadores especiales. De manera predeterminada, están deshabilitados por la directiva de compilación condicional; para habilitarlos, debe declarar MBED_ALL_STATS_ENABLED . Cuando se declara la definición, debe escribir su propio procedimiento para mostrar información al usuario. Escribimos un procedimiento especial para enviar estadísticas al emulador de terminal VT100, que se discutirá más adelante.
Además de las herramientas proporcionadas por el sistema operativo, el entorno de desarrollo IAR en las versiones recientes agrega una nueva característica: los canarios apilados . Lee sobre ellos aquí . Los problemas generales de protección de la pila del desbordamiento se analizan aquí y aquí .
Herramientas de análisis y depuración de código Mbed
Un estudio realmente profundo del trabajo de Mbed en la nueva plataforma solo es posible utilizando el depurador JTAG / SWD .

Las fuentes de Mbed están llenas de macros multinivel e instrucciones de compilación condicional. Simplemente mirando la fuente, no puede decir qué funciones funcionan y cuáles no, dónde se ejecuta el programa y dónde no. La única salida es conectar el depurador y, paso a paso, analizar la ruta de ejecución del programa. Ya en la etapa de portabilidad, es casi imposible hacerlo sin una depuración paso a paso.
Recomendaría las versiones de depuradores Segger de J-Link Pro y J-Link Ultra . Se distinguen por un alto ancho de banda, varias veces mayor que el de los modelos baratos comunes. Para los sistemas de depuración con tiempo real difícil, esto es importante. Al rastrear eventos rápidos, tales depuradores son menos propensos a desbordamientos y requieren menos iteraciones de depuración. Además de la depuración paso a paso, pueden registrar interrupciones, mantener estadísticas de las interrupciones, incluida su duración, admitir tecnologías de depuración RTT e ITM , detectar excepciones de hardware y otras funciones.
A continuación se muestra una vista de la ventana del depurador IAR cuando se trabaja a través del adaptador J-Link.

No vale la pena ahorrar, el 90% del tiempo de desarrollo se dedica a la depuración. Aunque los trazadores Segger J-Trace más caros ya no ofrecen una gran ventaja, ya que la serie MKE18F no tiene una interfaz de trazado especial.
La segunda herramienta de depuración, por supuesto, es E / S a través de UART . Mbed tiene al menos cuatro tecnologías diferentes para acceder a un canal de datos en serie a través de UART ,
Esto es:
- redirección a través de la clase DirectSerial de funciones estándar de E / S C / C ++
- clase RawSerial
- clase serial
- clase UARTSerial
Toda una variedad inusual. Los cuatro se pueden usar simultáneamente para la salida al mismo puerto. Algunos de ellos son más funcionales, otros son menos, otros están documentados y otros no. En el futuro, en nuestro proyecto usaremos RawSerial , como la clase que requiere menos recursos.
¿Cuántos temporizadores perdemos portando Mbed?
Para parpadear el LED, se utilizó la función de espera de la API del sistema operativo Mbed. El último artículo habló sobre los problemas de portarlo. Pero esto no es todo, excepto que Mbed mantiene un contador de su trabajo (la función mbed_stats_cpu_get puede leerlo) y hay una API de temporizador . Estos servicios utilizan las funciones de bajo nivel del archivo lp_ticker.c . En este archivo, el temporizador LPTMR del kit periférico Kinetis se usa para organizar el conteo de tiempo. Durante el proceso de portabilidad, este archivo se editó para que coincida con los métodos de sincronización utilizados en el microcontrolador MKE18F512VLL16 .
Por lo tanto, el puerto Mbed captura completamente dos módulos de contador: PIT y LPTMR y el temporizador central SysTick . Esto debe recordarse cuando se planifican recursos para una tarea aplicada.
Características de Bootstrap MKE18F
La familia de chips MKE18F tiene una ROM incorporada con un gestor de arranque a través de interfaces seriales: UART, CAN, SPI, I2C . Pero está planeado usar nuestro gestor de arranque seguro en nuestra placa, por lo que el trabajo de un gestor de arranque normal no es deseable.
En este caso, los chips Kinetis deben prestar atención al contenido del área Flash del programa en 0x040D . Se almacena una constante que determina el orden de arranque. En nuestro caso, la constante 0x7B debe escribirse allí , indicando el inicio desde la memoria Flash , y no desde la ROM, y desactivando la función NMI en la salida externa. Con un valor diferente para esta constante, el programa puede congelarse en el gestor de arranque incorporado llamado accidentalmente desde la ROM.
También es importante recordar que escribir en la memoria Flash del controlador solo es posible a una frecuencia central de 120 MHz y no más alta, es decir En el modo HRUN , la grabación con flash no es posible.
Activar perro guardián
La placa de nuestro controlador está diseñada para uso industrial, lo que significa que no puede prescindir de Watchdog .
Inicialmente, la macro DISABLE_WDOG en el archivo system_MKE18F16.h se configuró para deshabilitar el perro guardián . Para corregir la situación, se borró esta macro y se implementó su propia inicialización de WDOG .
La inicialización de Watchdog se realiza en la función SystemInit . El contador de vigilancia se actualiza en la tarea IDLE .
Tal enfoque requiere que las tareas de mayor prioridad no capturen exclusivamente el procesador durante más de 100 ms . Pero esto puede suceder fácilmente, por ejemplo, cuando se envían grandes volcados de datos al terminal. Por lo tanto, en todas partes dividimos procedimientos largos en tareas con prioridad mayor que IDLE en fragmentos cortos intercalados con pausas usando la función de espera .
Problema de controladores de la entrega del SDK
Los controladores de SDK tienen el prefijo fsl ubicado en el directorio NXP_MKE18F_drivers y son una especie de capa de abstracción periférica. Se supone que facilitan la programación de periféricos difíciles de aprender, pero desafortunadamente están mínimamente documentados. Más bien, su documentación se limita a comentarios en encabezados de función. Hay desconcierto para quién están escritos, y cómo pueden liberarnos de estudiar manuales en la periferia del microcontrolador. Mi respuesta es de ninguna manera. Los controladores simplemente facilitan la transferencia de programas a diferentes microcontroladores dentro de la misma familia. Pero para aplicarlos de manera efectiva, debe tratar muy bien con la documentación periférica. Por lo tanto, los controladores SDK resuelven un problema bastante privado de los desarrolladores de los propios controladores, lejos de las necesidades de los usuarios que están comenzando a aprender Kinetis .
Los controladores también están diseñados para funcionar en todos los chips de la familia, es decir sea universal y, por lo tanto, saturado de macros condicionales y verifica que para cada chip en particular no tengan ninguna función útil.
Los controladores aún pueden ayudar de alguna manera a comprender mejor cómo manejar los periféricos, pero después de comprender el controlador, puede ignorarlo con seguridad.
En este sentido, no debería sorprendernos que en este proyecto, el acceso a los periféricos no afectados por el puerto Mbed pasa directamente por alto al controlador.
Sin embargo, puede haber preocupaciones sobre las dependencias de un periférico particular en la disponibilidad de controladores SDK. Aquí debe examinar el código fuente de los controladores. El principal peligro de las dependencias proviene de la separación de funciones de los controladores DMA . Para evitar esto, debe rastrear y prohibir que los servicios regulares de Mbed usen DMA cuando trabajen con periféricos. Si los controladores de SDK no tocan DMA , entonces casi todo lo que no se aplica a los 2 módulos de temporizador mencionados ( PIT y LPTMR ) y el UART de depuración se pueden eliminar o ignorar del directorio NXP_MKE18F_drivers .
Un peligro menor, pero también significativo, puede provenir de priorizar los periféricos involucrados a través de los controladores SDK. Por ejemplo, debe saber qué prioridad se asigna a las interrupciones SysTick, depurar UART y temporizadores. En una situación donde su prioridad es igual o mayor que la prioridad de la periferia utilizada en la gestión en tiempo real, esto puede conducir a la degradación de la calidad de la gestión.
Recuerde que el puerto Mbed para el MKE18F inicia interrupciones UART y temporizador sin priorización, es decir. obtienen el nivel máximo por defecto.