Aplicação do Arm Mbed OS. Ajuste fino

LNDC1


Depois de usar o Arm Mbed OS, foi possível piscar o LED , era hora de testar e configurar outros serviços importantes. O seguinte é descrito:


  • Tecnologia de configuração Mbed
  • Por que é difícil mudar para C ++ em RTOS regulares
  • Como economizar memória no RTOS
  • Como as interrupções são organizadas no Mbed OS
  • Do que é conveniente depurar o Mbed OS
  • Como se livrar de uma camada extra de abstração do SDK

Continuamos familiarizados com a tecnologia de programação de microcontroladores da família MKE18F usando o ARM Mbed OS



Tecnologia de configuração Mbed


Um componente importante do projeto Mbed é um sistema automático de configuração e montagem, online e offline , ou seja, localmente no computador do usuário. Propõe-se configurar editando arquivos .json com nomes especiais. Em seguida, os scripts no projeto Python convertem esses arquivos em arquivos de cabeçalho, arquivos de espaço de trabalho do IDE selecionado pelo usuário, arquivos de comando do vinculador e outros arquivos de suporte.


Mas o problema do método descrito está na opacidade do ponto de vista dos textos de origem, pois é muito difícil rastrear onde e o que o sistema de configuração muda nas fontes. Por outro lado, no nosso caso, não há motivo para apoiar a capacidade do projeto de ser automaticamente transferido para diferentes IDEs.


Portanto, decidiu-se abandonar essa abordagem. Conforme declarado em um artigo anterior, ele foi simplesmente transformado em um projeto on-line para o IDE IAR , um monte não estruturado de arquivos foi obtido no espaço de trabalho do IDE, depois eles foram sistematizados e descartados. Como resultado, você não precisa mais fazer a configuração por meio de arquivos .json e existem apenas três locais específicos em que os parâmetros que afetam a configuração do Mbed estão localizados :


  • Opções do compilador IDE
  • Arquivo em lote do vinculador MKE18F512xxx16_flash.icf
  • Arquivo de cabeçalho mbed_config.h

No arquivo mbed_config.h , você pode contar cerca de 130 definições , o que é muito irritante no começo. Felizmente, porém, a maioria deles se refere a pilhas de protocolos sem fio que atualmente não são usados ​​no projeto. Por conveniência, as entradas foram classificadas para que as reais fossem colocadas no topo. Inicialmente, as definições para diferentes módulos no arquivo mbed_config.h são localizadas aleatoriamente, mas após a classificação, fica assim:


Aberto
#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 "Thread Network" // 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 "ABCDEFGH" // 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 

Recursos da transição para o C ++ aplicados ao RTOS


A API de nível superior no Mbed é escrita em C ++ , portanto, esse idioma deve ser usado no código do aplicativo. Mas há nuances que você precisa conhecer.
O uso do C ++ para RTOS em pequenos sistemas embarcados ainda é relativamente raro. O problema aqui é que os projetos RTOS de sucesso se esforçam para ser multiplataforma, enquanto o C ++ exige muito do gerenciamento de recursos da plataforma em comparação com o C. O motivo é o desejo de ocultar do usuário os detalhes do gerenciamento de recursos de baixo nível. É principalmente sobre recursos de memória. Construtores, destruidores, threads, exceções com destruição automática, modelos de objetos de estrutura de dados etc. usam operações implícitas com memória dinâmica. Mas o recurso de RAM em pequenos sistemas é muito limitado. A RAM é o recurso mais escasso nesses sistemas e especialmente no RTOS . No RTOS, cada tarefa recebe uma pilha, o desenvolvedor não pode prever seu tamanho exato com antecedência e, portanto, escolhe com uma margem. Assim, a presença de RTOS com uma dúzia de tarefas exige imediatamente um tamanho de RAM de 10 a 30 kB. É necessária muita memória para vários analisadores e protocolos (HTTP, HTML ...) e sistemas de arquivos. Se uma tela for usada, os requisitos para RAM livre aumentam ainda mais.


Bibliotecas de ambientes de desenvolvimento como o IAR estão equipadas com bons gerenciadores de memória dinâmica, mas são projetadas para um ambiente de tempo de execução de thread único. Que eles começaram a trabalhar no RTOS , é necessário escrever um código adicional. Esse processo é chamado de redirecionamento .
No RTOS escrito em C, o redirecionamento geralmente não é realizado. Como não há operações implícitas com memória dinâmica no nível do idioma, todas as operações são executadas explicitamente chamando suas próprias versões de thread-safe das funções malloc e free . O programador tem controle total sobre as operações com memória dinâmica e pode aplicar facilmente todas as medidas possíveis para salvá-lo.


No caso do C ++, se quisermos usar todos os recursos dessa linguagem, teremos que fazer um redirecionamento . Mas o redirecionamento em cada ambiente de desenvolvimento é um processo puramente individual. Isso dificulta a vida dos desenvolvedores de RTOS .


A figura abaixo é um exemplo de uma estrutura de chamada com redirecionamento. As funções __write , __lseek , __read podem não ser implementadas pelo usuário, mas a funcionalidade delas permanece a critério do IDE . E certamente printf e scanf não serão multithread.



O Mbed é um dos poucos, senão o único RTOS, que fornece aos códigos-fonte redirecionamentos já feitos para uma tríade de ferramentas de desenvolvimento conhecidas: GCC , IAR , Keil
Apesar de tudo isso, você pode encontrar artigos sobre como transportar RTOS para C ++ sem executar redirecionamento, por exemplo, resolvendo o problema simplesmente substituindo algumas funções padrão comuns pelas suas. Isso pode funcionar, mas o programador precisa estar ciente de várias restrições implícitas e não documentadas ao usar construções C ++ no IAR (apenas construtores estáticos, verifique todos os modelos quanto a novas , descartar exceções etc.). Já será difícil chamar C ++ . O Mbed, como um sistema fácil de usar, remove muitas dessas limitações, abordando o Arduino com simplicidade.


Além de todas as versões recentes do IAR, há dificuldades em mudar para C11 e C ++ 14, descritas aqui - https://www.iar.com/support/resources/articles/exploring-c11-and-c14/


Como as interrupções são organizadas no Mbed


Curiosamente, não será possível encontrar uma classe ou função ou algo adequado para organizar o serviço de interrupção na API Mbed. Você pode encontrar apenas a classe InterruptIn , destinada apenas a portas externas.


As respostas para essas perguntas devem ser buscadas no CMSIS-RTOS , nomeadamente na camada de acesso periférico principal do CMSIS Cortex-M4 . As macros são definidas lá:


  • NVIC_SetVector - define a rotina de serviço de interrupção (ISR) para um determinado vetor
  • NVIC_SetPriority - Define a prioridade para o vetor de interrupção especificado.
  • NVIC_EnableIRQ - permite interromper chamadas em um determinado vetor

Veja como a organização de interrupções do timer SysTick é inicializada:


  NVIC_SetVector(mbed_get_m0_tick_irqn(), (uint32_t)SysTick_Handler); //   mbed_get_m0_tick_irqn       SysTick. //        SysTick_IRQn    MKE18F16.h //  SysTick_Handler   . NVIC_SetPriority(mbed_get_m0_tick_irqn(), 0xFF); // 0xFF    .      .   NVIC    MKE18F16   16  . ..   4-  . NVIC_EnableIRQ(mbed_get_m0_tick_irqn()); //    . 

As prioridades de tarefas do Mbed e as prioridades de interrupção não devem ser confundidas.


Se a prioridade não for atribuída, por padrão, ela será configurada para o máximo.
Dos manipuladores de interrupção designados dessa maneira, você pode chamar qualquer serviço RTOS que não cause expectativas. I.e. transmitir sinalizadores, semáforos, mensagens, caixas de correio, etc. Os serviços são chamados, no entanto, não do próprio ISR , mas chamando interrupções de software, definindo o bit PENDSVSET no ICSR ( Interrupt Control and State Register ) do bloco de controle do sistema ( SCB ) do kernel Cortex-M . I.e. após a conclusão do manipulador de interrupção atual, se não houver outras interrupções prioritárias, o manipulador do sistema será chamado pelo vetor PendSV onde o serviço será executado.


Como o Mbed funciona com memória dinâmica?


A memória dinâmica, ou então heap ou heap, é um componente necessário ao programar em C ++ . Em nosso projeto Mbed no IAR , o tamanho dessa área de memória é determinado no arquivo de configurações do vinculador MKE18F512xxx16_flash.icf , gravando na variável __size_heap__ . Definimos o tamanho para que ocupe toda a memória livre restante. Sabemos quanta memória livre resta do arquivo .map após a compilação, ou seja, heap de dimensionamento é um processo iterativo.


Chamando construtores estáticos de objetos C ++


Uma questão importante ao usar C ++ é onde e como os construtores de objetos globais são chamados. Mesmo no RTOS alegando seriedade, por exemplo, MAX , isso é ignorado, ou seja, deixado ao acaso. Lá, os designers estão envolvidos em uma biblioteca de ambiente de desenvolvimento padrão de thread único com o mecanismo de alocação de memória comum de thread único. Porém, após o início, o RTOS comum cria seu próprio mecanismo para gerenciar a memória dinâmica, e a memória ocupada por objetos globais permanece esquecida. Este é um buraco nos nossos esforços para economizar memória e controlar tudo.


Mbed abordou esse assunto com muito mais seriedade. Para cada ambiente de desenvolvimento, existe uma abordagem própria. No IAR, isso é feito assim:


  • o fluxo de inicialização é interceptado pelo código do usuário antes mesmo que os designers chamam
  • métodos de bloqueio de fluxos da API do RTOS são substituídos na biblioteca padrão
  • funções padrão new , delete , malloc , free ... são redirecionadas para chamadas para a API de memória dinâmica RTOS .

Mbed usa adaptadores de biblioteca IAR para operação multithread
Você pode ler sobre a adaptação do IAR para multithreading aqui - http://supp.iar.com/FilesPublic/UPDINFO/005691/arm/doc/infocenter/DLIBThreadSupport.html
O Mbed adaptou os bloqueios do sistema e os fluxos de arquivos da biblioteca IAR . Eles são implementados no arquivo mbed_boot.c e usam mutexes do SO .


A função __iar_program_start executável nas primeiras linhas do arquivo startup_MKE18F16.s inicializa a pilha do SO e a memória dinâmica chamando mbed_set_stack_heap


Dimensionando pilhas de tarefas


Aparar pilhas de tarefas no mínimo é a opção mais atraente para economizar RAM .
Para tarefas que exigem menos pilha, diferentes técnicas são usadas. Por exemplo, as funções da biblioteca afetam fortemente a pilha para saída e formatação de linhas printf , sprintf , scanf etc. Eles têm a capacidade de alocar grandes áreas temporárias para armazenamento de dados na pilha. Se nos recusarmos a usar essas funções na tarefa, podemos reduzir a pilha de tarefas em algumas centenas de bytes.


O SO Mbed na inicialização cria imediatamente três tarefas com nomes: "main_thread" , "timer_thread" , "idle_thread" . O tamanho da pilha padrão para eles foi determinado por macros no arquivo de cabeçalho mbed_rtx_conf.h . Movemos as declarações dessas pilhas para o arquivo de configuração mbed_config.h e reduzimos o tamanho das pilhas. Agora as definições são assim:


  • A pilha de tarefas "main_thread" é definida pela macro MBED_CONF_APP_MAIN_STACK_SIZE = 1024 bytes
  • A pilha de tarefas timer_thread é definida pela macro MBED_CONF_APP_TIMER_THREAD_STACK_SIZE = 512 bytes
  • A pilha de tarefas idle_thread é definida pela macro MBED_CONF_APP_IDLE_THREAD_STACK_SIZE = 512 bytes

Controles de uso de memória Mbed


A memória dinâmica e a pilha são recursos que requerem atenção constante. Para ver quanta memória dinâmica é usada e qual a intensidade de solicitações, quanta pilha resta para cada tarefa e qual foi o pico de carregamento da pilha no Mbed, existem contadores especiais. Por padrão, eles são desabilitados pela diretiva de compilação condicional; para habilitá-los, você deve declarar MBED_ALL_STATS_ENABLED . Quando a definição é declarada, você precisa escrever seu próprio procedimento para exibir informações ao usuário. Escrevemos um procedimento especial para gerar estatísticas no emulador de terminal VT100, que será discutido mais adiante.


Além das ferramentas fornecidas pelo sistema operacional, o ambiente de desenvolvimento do IAR nas versões recentes adiciona um novo canário empilhado por recursos . Leia sobre eles aqui . Os problemas gerais de proteção de pilha contra estouros são discutidos aqui e aqui .


Ferramentas de depuração e análise de código Mbed


Um estudo verdadeiramente aprofundado do trabalho do Mbed na nova plataforma é possível apenas usando o depurador JTAG / SWD .



As fontes Mbed estão cheias de macros multiníveis e instruções de compilação condicional. Apenas olhando a fonte, você não pode dizer quais funções funcionam e quais não, onde o programa é executado e onde não. A única saída é conectar o depurador e passo a passo para analisar o caminho de execução do programa. Já na fase de portabilidade, é quase impossível fazer isso sem a depuração passo a passo.


Eu recomendaria as versões dos depuradores Segger do J-Link Pro e J-Link Ultra . Eles se distinguem pela alta largura de banda, várias vezes maior que a dos modelos baratos comuns. Para sistemas de depuração com tempo real difícil, isso é importante. Ao rastrear eventos rápidos, esses depuradores são menos propensos a estouros e exigem menos iterações de depuração. Além da depuração passo a passo, eles podem registrar interrupções, manter estatísticas de interrupções, incluindo suas durações de execução, suportar tecnologias de depuração RTT e ITM , capturar exceções de hardware e outras funções.


Abaixo está uma vista da janela do depurador IAR ao trabalhar com o adaptador J-Link.



Não vale a pena economizar, 90% do tempo de desenvolvimento é gasto na depuração. Embora os rastreadores Segger J-Trace mais caros não ofereçam mais uma grande vantagem, uma vez que a série MKE18F não possui uma interface de rastreamento especial.


A segunda ferramenta de depuração, é claro, é a E / S via UART . O Mbed possui pelo menos quatro tecnologias diferentes para acessar um canal de dados serial via UART ,


Isto é:


  • redirecionamento através da classe DirectSerial das funções de E / S C / C ++ padrão
  • classe RawSerial
  • série serial
  • classe UARTSerial

Bastante uma variedade incomum. Todos os quatro podem ser usados ​​simultaneamente para a saída na mesma porta. Alguns deles são mais funcionais, outros são menos, alguns são documentados e outros não. No futuro, em nosso projeto, usaremos RawSerial , como a classe que menos consome recursos.


Quantos temporizadores perdemos portando o Mbed


Para piscar o LED, foi usada a função de espera da API do Mbed OS. O último artigo falou sobre os problemas de portá-lo. Mas isso não é tudo, exceto o Mbed mantém um contador de seu trabalho (ele pode ser lido pela função mbed_stats_cpu_get ) e existe uma API de timer . Esses serviços usam as funções de baixo nível do arquivo lp_ticker.c . Neste arquivo, o timer LPTMR do kit periférico Kinetis é usado para organizar a contagem de tempo. Durante o processo de portabilidade, esse arquivo foi editado para corresponder aos métodos de relógio usados ​​no microcontrolador MKE18F512VLL16 .


Assim, a porta Mbed captura completamente dois módulos de contador - PIT e LPTMR e o temporizador de núcleo SysTick . Isso deve ser lembrado ao planejar recursos para uma tarefa aplicada.


Recursos de inicialização do MKE18F


A família de chips MKE18F possui uma ROM incorporada com um gerenciador de inicialização através de interfaces seriais: UART, CAN, SPI, I2C . Mas está planejado usar nosso gerenciador de inicialização seguro em nossa placa, portanto, o trabalho de um gerenciador de inicialização comum é indesejável.
Nesse caso, os chips Kinetis precisam prestar atenção ao conteúdo da área do Program Flash em 0x040D . É armazenada uma constante que determina a ordem de inicialização. No nosso caso, a constante 0x7B deve ser escrita , indicando o início da memória Flash e não da ROM e desativando a função NMI na saída externa. Com um valor diferente para essa constante, o programa pode congelar no carregador de inicialização interno chamado acidentalmente da ROM.


Também é importante lembrar que gravar na memória Flash do controlador é possível apenas a uma frequência central de 120 MHz e não superior, ou seja, No modo HRUN , a gravação em Flash não é possível.


Ativar Watchdog


A placa do nosso controlador é projetada para uso industrial, o que significa que você não pode ficar sem o Watchdog .


Inicialmente, a macro DISABLE_WDOG no arquivo system_MKE18F16.h foi definida para desativar o watchdog . Para corrigir a situação, essa macro foi apagada e sua própria inicialização do WDOG foi implementada.


A inicialização do Watchdog é feita na função SystemInit . O contador de watchdog é atualizado na tarefa IDLE .


Essa abordagem exige que tarefas de prioridade mais alta não capturem exclusivamente o processador por mais de 100 ms . Mas isso pode acontecer facilmente, por exemplo, ao enviar grandes despejos de dados para o terminal. Portanto, em todos os lugares, quebramos procedimentos demorados em tarefas com prioridade maior que IDLE em pequenos fragmentos intercalados com pausas usando a função de espera .


Problema de drivers na entrega do SDK


Os drivers SDK são prefixados com fsl localizado no diretório NXP_MKE18F_drivers e são um tipo de camada de abstração periférica. Eles devem facilitar a programação de periféricos difíceis de aprender, mas infelizmente são minimamente documentados. Em vez disso, sua documentação é limitada a comentários nos cabeçalhos das funções. Há perplexidade para quem eles são escritos e como eles podem nos libertar do estudo de manuais na periferia do microcontrolador. Minha resposta não é possível. Os drivers facilitam a transferência de programas para diferentes microcontroladores dentro da mesma família. Mas, para aplicá-los efetivamente, você precisa lidar muito bem com a documentação periférica. Assim, os drivers do SDK resolvem um problema bastante particular dos desenvolvedores dos próprios drivers, longe das necessidades dos usuários que estão começando a aprender o Kinetis .


Os drivers também são projetados para funcionar com todos os chips da família, ou seja, seja universal e, portanto, saturado com macros condicionais e verifique se, para cada chip em particular, não possui nenhuma função útil.


Os drivers ainda podem ajudar de alguma maneira a entender melhor como lidar com periféricos, mas depois de entender o driver, você pode ignorá-lo com segurança.
Nesse sentido, não deve surpreender que, neste projeto, o acesso aos periféricos não afetados pela porta Mbed passe diretamente pelo driver.


No entanto, pode haver preocupações sobre as dependências de um periférico específico na disponibilidade de drivers SDK. Aqui você tem que examinar o código fonte dos drivers. O principal perigo das dependências vem da separação de funções dos drivers DMA . Para evitar isso, você precisa rastrear e proibir os serviços Mbed regulares de usar o DMA ao trabalhar com periféricos. Se o DMA permanecer intocado pelos drivers do SDK , quase tudo o que não se aplica aos 2 módulos de timer mencionados ( PIT e LPTMR ) e o UART de depuração podem ser descartados ou ignorados no diretório NXP_MKE18F_drivers .


Um perigo menor, mas também significativo, pode advir da priorização dos periféricos envolvidos pelos drivers do SDK. Por exemplo, você precisa saber qual prioridade é atribuída às interrupções do SysTick, depurar UARTs e temporizadores. Em uma situação em que sua prioridade é igual ou superior à prioridade da periferia usada no gerenciamento em tempo real, isso pode levar à degradação da qualidade do gerenciamento.


Lembre-se de que a porta Mbed do MKE18F inicia o UART e o timer é interrompido sem priorização, ou seja, eles obtêm o nível máximo por padrão.

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


All Articles