Atividade #373
AbertaMeta #318: Firmware: Implementação do RTOS
Estudo e aplicação mais aprofundada de um firmware com FreeRTOS
Descrição
A ideia dessa tarefa é para que eu possa tentar desenvolver algo usando FreeRTOS para criar algum hábito antes de partir pro código enorme do firmware.
Arquivos
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
O foco da atividade esta em fazer o firmware atual rodar em um projeto com os arquivos do freertos, com o firmware do robo seja uma atividade única do rtos. A partir disso, será dividido em mais atividades.
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Foi feito o primeiro exemplo do livro Mastering FreeRTOS. Tivemos diversos problemas que aprendemos a resolver, como as bibliotecas de freertos nao sendo compiladas e a main não tinha funções importantes para o funcionamento do freertos.
O exemplo do livro, ele imprime no terminal que esta alternando as tarefas. Isso nao foi feito ainda. Será a próxima parte que farei desta tarefa: imprimir no terminal do atollic que as tarefas estão sendo alternadas.
Código usado: https://github.com/OniasC/FreeRTOS_study
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Juntamos ao projeto o freertos. Porém, apareceram vários problemas, como múltiplas definições de variaveis (até a ultima compilação, estava sendo acusada a PendSV_Handler). E comentamos toda a main para ver se os erros em relação ao freertos sumiam (por ex, multiplas referências). Por enquanto nao.
https://github.com/roboime/roboime-firmware/tree/FreeRTOS-test
Atualizado por Luiz Renault Leite Rodrigues há aproximadamente 7 anos
A PendSV deve estar duplicada, pois é usada no FreeRTOS e está implementada vazia no firmware padrão. Basta comentá-la.
Atualizado por Luiz Renault Leite Rodrigues há aproximadamente 7 anos
Código compilando comitado.
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Eu e o gustavo começamos fazendo um firmware com o freertos que piscava um led com uma tarefa unica. Após isso começamos a inserir as funções do firmware da ssl com o objetivo de fazer funcionar a comunicação serial dele. Conseguimos fazer o mesmo led piscar e se comunicar com o tera term em uma única tarefa. Em seguida, separamos as tarefas para estudar como reagiria o código.
Problemas que tivemos no desenvolvimento:
- o firmware que estamos usando está ocupando mta memória - o que ocupa o espaço das filas das tasks, fora fazer o firmware demorar a passar pra placa depois de compilado.
- o erro que acusava não era de compilação, mas ora o firmware entrava no loop do vApplicationMallocFailedHook (quando colocavamos stacks de memória muito altos pras 2 tasks), ora dava hardfault, ora dava Stackoverflow (quando botavamos memória de menos). No fim conseguimos fazer funcionar a comunicação com o tera term escolhendo tamanho de stacks carteados de 150 pro led piscar e 800 pra COM. Eu li na documentação que era difícil estimar esse número com precisão. Mas algo parece errado.
Para futuras referencias:
https://mcuoneclipse.com/2016/08/28/arm-cortex-m-interrupts-and-freertos-part-3/
link para o código que fizemos ontem: https://github.com/OniasC/TXFreeRTOS_SSL
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Falamos pro atollic ignorar alguns arquivos na hr de compilar e passar pra placa, aos moldes do firmware atual, mas mesmo assim estamos com esse problema no rendimento. O sr tem ideia de onde podemos ter errado?
Atualizado por Luiz Renault Leite Rodrigues há aproximadamente 7 anos
Qual o tamanho do código compilado?
Quanto tempo está levando para gravar?
É possível estimar o tamanho da stack fazendo a análise do código. O tamanho necessário será o das variáveis alocadas em tempo de execução mais o número de registradores vezes o número de chamadas aninhadas a funções.
Entretanto, como é uma tarefa chada, pode usar em torno de 120 + tamanho em palavras das variáveis alocadas.
O Hardfault deve ser devido à stack overflow em situações onde o FreeRTOS não consegue detectar (ele só detecta na transição entre as tarefas).
Durante o desenvolvimento, use sempre um breakpoint em vApplicationMallocFailedHook e vApplicationStackOverflowHook.
A memória total disponível está como 7KB: #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 7 * 1024 ) )
Uma pilha de tamanho 800 ocupa 800*4 bytes
Qual esquema de memória estão usando?
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Documentação do site do freeRTOS sobre as diferenças entre os heaps (heaps_1, heaps_2, heap_3, heap_4 e heap_5): http://www.freertos.org/a00111.html
Resumindo:
Following below:
heap_1 - the very simplest, does not permit memory to be freed
heap_2 - permits memory to be freed, but not does coalescence adjacent free blocks.
heap_3 - simply wraps the standard malloc() and free() for thread safety
heap_4 - coalescences adjacent free blocks to avoid fragmentation. Includes absolute address placement option
heap_5 - as per heap_4, with the ability to span the heap across multiple non-adjacent memory areas
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Tentei implementar uma tarefa com nrf porém houveram problemas:
primeiro que quando coloquei as funções na bsp.cpp o atollic deu um erro "arm-none-eabi-gdb.exe has stopped working". Comentei todas uma a uma para achar o erro quando descomentei todas o programa rodou e foi passado pra placa sem problemas.
Criei uma task a mais para inicializar o nrf24, porém ficou chaveando entre MallocFailedHook e HardFault dependendo do quanto de stacks eu colocava para cada função.
A modificação mais recente foi commitada no https://github.com/OniasC/TXFreeRTOS_SSL
Atualizado por Luiz Renault Leite Rodrigues há aproximadamente 7 anos
Este problema do gdbserver ocorre normalmente por ter um número excessivo de breakpoints definidos, inclusive em múltiplos projetos abertos. Não é relativo ao código em si.
Deve fazer com que as áreas de stack e heap comportem as aplicações das tarefas.
Qual o tamanho das áreas de stack e heap estão usando?
Atualizado por Luiz Renault Leite Rodrigues há aproximadamente 7 anos
Outra questão é quando você está utilizando o gerenciamento de memória do RTOS, você precisa usar suas funções de alocação e liberação para todos os objetos.
Então não pode-se utilizar malloc, calloc, e free diretamente da linguagem C.
Também deverá sobrecarregar os operadores New, Delete, New[] e Delete[] de C++.
Segue o código que faz isso.
Ao analisar rapidamente o código vejo alocações de memória do tipo:
NRF24L01P::NRF24L01P:
MODEM,
_spi(&spi),
_SS_PIN(&SS_PIN),
_NIRQ_PIN(&NIRQ_PIN),
_CE_PIN(&CE_PIN),
_rxbuffer(0,2048),
_txbuffer(0,2048),
_channel(0),
_retransmit_irq_count(0),
_receive_irq_count(0),
_transmit_irq_count(0),
_busy(1),
REG
{
}
Onde são criados dois objetos _rxbuffer(0,2048), _txbuffer(0,2048), que na verdade usam o operador new[] para a alocação de memória.
Assim, sua Heap deverá comportar todos esses objetos criados.
Basicamente, do ponto de vista do usuário, na stack são alocadas as variáveis locais quando declaradas como "uint8_t test2048;" e na heap como "uint8_t *test=malloc(2048);".
No lugar de malloc deve-se usar pvPortMalloc eno lugar de free, vPortFree.
Faça a análise da quantidade de memória heap necessária e configure o FreeRTOS de acordo.
Atualizado por Gustavo Claudio Karl Couto há aproximadamente 7 anos
The stack is the memory set aside as scratch space for a thread of execution. When a function is called, a block is reserved on the top of the stack for local variables and some bookkeeping data. When that function returns, the block becomes unused and can be used the next time a function is called. The stack is always reserved in a LIFO (last in first out) order; the most recently reserved block is always the next block to be freed. This makes it really simple to keep track of the stack; freeing a block from the stack is nothing more than adjusting one pointer.
The heap is memory set aside for dynamic allocation. Unlike the stack, there's no enforced pattern to the allocation and deallocation of blocks from the heap; you can allocate a block at any time and free it at any time. This makes it much more complex to keep track of which parts of the heap are allocated or free at any given time; there are many custom heap allocators available to tune heap performance for different usage patterns.
Stack:
Stored in computer RAM just like the heap.
Variables created on the stack will go out of scope and are automatically deallocated.
Much faster to allocate in comparison to variables on the heap.
Implemented with an actual stack data structure.
Stores local data, return addresses, used for parameter passing.
Can have a stack overflow when too much of the stack is used (mostly from infinite or too deep recursion, very large allocations).
Data created on the stack can be used without pointers.
You would use the stack if you know exactly how much data you need to allocate before compile time and it is not too big.
Usually has a maximum size already determined when your program starts.
Heap:
Stored in computer RAM just like the stack.
In C++, variables on the heap must be destroyed manually and never fall out of scope. The data is freed with delete, delete[], or free.
Slower to allocate in comparison to variables on the stack.
Used on demand to allocate a block of data for use by the program.
Can have fragmentation when there are a lot of allocations and deallocations.
In C++ or C, data created on the heap will be pointed to by pointers and allocated with new or malloc respectively.
Can have allocation failures if too big of a buffer is requested to be allocated.
You would use the heap if you don't know exactly how much data you will need at run time or if you need to allocate a lot of data.
Responsible for memory leaks.
Example:
int foo()
{
char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
bool b = true; // Allocated on the stack.
if(b)
{
//Create 500 bytes on the stack
char buffer500;
//Create 500 bytes on the heap
pBuffer = new char[500];
}//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;
Atualizado por Gustavo Claudio Karl Couto há aproximadamente 7 anos
A stack é mais rápida e quando você declara normalmente as variáveis elas vão para a stack, que é uma pilha toda vez que sai do processo as variáveis ligadas aquele processo são guardadas na pilha. Ela também é de tamanho mais limitado, então seria melhor que esses vetores grandes fossem guardados na heap, para usar a heap no cpp declare as variáveis através do método new, como no exemplo de código acima,
Pelo que o capitão observou talvez precise sobrecarregar o método new e delete das classes para usar o malloc do freertos,
É importante fazer isso soh para as classes que tem muito consumo de memória, no caso a que vc usa é soh a do buffer circular, que é importante alocar na heap, pq essas estruturas são grandes e serão compartilhadas por vários processos.
Atualizado por Gustavo Claudio Karl Couto há aproximadamente 7 anos
Aki tem o bizú de como sobrecarregar o new para usar o pVmalloc do freeRtos ao invés de usar o malloc padrão que é do linker,
http://jacobjennin.gs/post/141861963680/freertos-and-the-c-new-operator
Atualizado por Luiz Renault Leite Rodrigues há aproximadamente 7 anos
Não confundam as coisas. A Stack possui exatamente a mesma velocidade de acesso que a memória Heap pois eles compartilham as mesmas memórias físicas.
Os acessos às variáveis na pilha e heap são feitos exatamente da mesma forma. Se você checar o código em assembly gerado pelo compilador, verá que são idênticos.
A única diferença é que na alocação, há um overhead de processamento de alocação dinâmica de memória para manter, por exemplo, as tabelas de memórias alocadas e livres. Só isso. Na execução do código e utilização da memória, as velocidades são as mesmas. Podem até fazer um código demo para mostrar esta questão, se quiserem.
Os tamanhos de memória são completamente customizáveis. Assim, a pilha pode ocupar muitas vezes o tamanho da heap disponível, vai depender da sua programação. De fato, imagine um espaço físico de memória. A stack começa do final para o início, e a heap do início para o final. Se as duas se encontrarem, significa que seu sistema está sem memória disponível. Mas o crescimento delas, só depende da sua programação.
Na verdade, é mandatório sobrecarregar os operadores new e delete, pois ele são utilizados quando instanciados objetos. Para fazer isso, basta incluir no seu projeto o arquivo new.cpp que enviei. Este já está testado. Não vai achar muita coisa sobre isso na net, pois não está tão comum o uso de FreeRTOS com C++.
Como sugestão, sempre que copiar um texto, coloque a referência, para caso os leitores queiram se aprofundar no assunto. https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap
Nota: A heap não é responsável pelo vazamento de memória. Esta responsabilidade é do usuário, quando a utiliza de forma errada, esquecendo de liberar a memória.
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Luiz Renault Leite Rodrigues escreveu:
Este problema do gdbserver ocorre normalmente por ter um número excessivo de breakpoints definidos, inclusive em múltiplos projetos abertos. Não é relativo ao código em si.
Deve fazer com que as áreas de stack e heap comportem as aplicações das tarefas.
Qual o tamanho das áreas de stack e heap estão usando?
Para a task de acender o led: 150 de stack depth. Acender o segundo led e inicializar o nrf: 700 de stack. Inicializar a com via serial: 700 de stack.
Quanto a memoria heap, eu n sei como checar isso. No livro q tem no site deles fala que a partir do 9.0.0 pode usar alocação estatica de memoria, sem a necessidade de gerenciador de memoria heap.
Atualizado por Luiz Renault Leite Rodrigues há aproximadamente 7 anos
Quando me refiro ao tamanho, quero dizer o disponível para o OS.
É interessante tentarem entender o funcionamento, pois o firmware, ou o OS, seja ele real time ou não, não é um ente mágico, que funciona sem explicação.
Esse entendimento pode ser obtido acompanhando a execução do código do próprio FreeRTOS.
Quando criam uma tarefa, por exemplo, ele aloca na heap a pilha da tarefa. Assim, a heap tem que ser grande o suficiente para caber todas as pilhas.
E onde está essa heap?
O OS não adivinha qual uC você está usando e escolhe automaticamente o local da memória e tamanho da heap. A declaração está no próprio código.
Tente identificar este parâmetro no header de configuração do FreeRTOS e onde ela é alocada.
Entendendo estas questões, e sabendo onde cada variável ou objeto é alocado, vocês não terão problema com organização de memória no FreeRTOS ou qualquer outro sistema operacional de tempo real.
Tentem responder agora onde é qual o tamanho alocado para a heap, e qual o tamanho mínimo necessário.
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Fizemos a comunicação entre dois robos usando o freeRTOS. O que escrevi no meu pc apareceu no do gustavo através de dois robos, o RX e o TX.
Atualizado por Gustavo Claudio Karl Couto há aproximadamente 7 anos
texto recebido.
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Alterações no firmware disponíveis em:
https://github.com/OniasC/TXFreeRTOS_SSL/commit/ebf1f7a4064fffbfb02bb8c3401a0eaa937a0e2a
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Começamos a fazer a task para controlar os motores. Conseguimos mandar um degrau predefinido pra ela. Porém tivemos problemas para setar o controle de velocidade, mais especificamente na hora de implementar a interrupção por timer. Amanhã analisaremos isso com mais cuidado. As mudanças mais recentes ja foram commitadas.
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Consertamos a interrupção para que fosse possível controle de velocidade. O problema era que havia um conflito nas prioridades das interrupções, este link - http://www.freertos.org/RTOS-Cortex-M3-M4.html - mostra como corrigir o problema.
Após isso, implementamos uma fila de referência (ponteiro de velocidade dos motores) para estabelecer contato entre a task da linha de comando e da controle de velocidade.
No último commit, foi subida uma versão que permite o controle de velocidade de uma roda através da linha de comando. Sendo uma task responsavel pela cmd line, outra pra controle da roda e uma queue por referecia para mandar dados de uma pra outra.
Atualizado por Luiz Renault Leite Rodrigues há aproximadamente 7 anos
O que é uma queue por referência? O FreeRTOS sempre passa os itens da queue por cópia.
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Segundo a documentação do freeRTOS, a queue por copia pode sr usada como uma queue por referencia.
There are two ways in which queue behavior could have been implemented:
1. Queue by copy
Queuing by copy means the data sent to the queue is copied byte for byte into the
queue.
2. Queue by reference
Queuing by reference means the queue only holds pointers to the data sent to the
queue, not the data itself.
fonte: Mastering the FreeRTOS Real Time Kernel pag 131
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
- Arquivo 161204_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf 161204_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf adicionado
Segue em anexo o livro
Atualizado por Luiz Renault Leite Rodrigues há aproximadamente 7 anos
A forma de fazer está certa, mas não pode chamar de by reference. Só para não misturar os termos. O correto é por cópia. Entretanto, estão enviando cópias de ponteiros.
Há outras implementações de RTOS que implementam fila por referência, o que não é o que o livro descreve.
Utilizando as cópias das estruturas (que contém os ponteiros) é uma forma eficiente de fazer. Se os dados forem de tamanhos definidos e fixos, podem utilizar a estrutura completa, já com o espaço para os dados. Vai ser ainda mais eficiente, ao custo de pré-alocar a memória necessária para a fila. Com isso não terão que se preocupar com alocação e liberação de memória.
Atualizado por Onias Castelo Branco há aproximadamente 7 anos
Subimos agr a versao TX que transmite os comandos com protobuf. Resta implementar COM com os ids.