Os plugues são inevitáveis ao desenvolver qualquer software. Em um embedd, seus generosos cinco centavos também podem causar problemas de hardware, mas essa é uma música separada. Mas emboscadas puramente programadas, quando você fica preso em um lugar aparentemente vazio ... Para mim, existem três tipos deles.
A maneira mais fácil é quando o manual, o padrão ou, por exemplo, o procedimento para configurar a biblioteca para ferro não é totalmente compreendido. Aqui está claro: nem todos os movimentos foram esgotados, paciência e trabalho, outros cinco ou dois experimentos, e ele ganhará vida. Osciloscópio e tyk científico para ajudar.
Escolhendo um divisor de frequência para configurar o barramento CANPior ainda, quando o problema é um erro de digitação ou um erro na lógica que você não consegue ver à queima-roupa até percorrer este lugar vinte vezes com seus olhos e com a depuração passo a passo. Então amanhece, um golpe sonoro na testa, um grito: "Bem, você é meio babai!", Editando. Isso funciona.
E uma terceira visão sombria: uma falha entrincheirada em uma biblioteca estrangeira e rastejando no cruzamento com ferro. As paixões shakespearianas dão origem à luz constante de um monitor. “Porque, não pode, o sistema não pode se comportar dessa maneira, porque nunca pode! Bem, sério! Ah ?! Não. Receba, assine.
Como resultado, a realidade é mais ampla, mais ampla e mais ampla do que o esperado. Alguns exemplos:
História nº 1. Unidade flash MicroSD e trabalho DMA
Anamnese
Você precisa despejar os dados em um arquivo no cartão SD. Obviamente, não tenho tempo nem desejo de escrever o sistema de arquivos e o driver SDIO, então escolho a biblioteca finalizada. Eu o configurei para ferro, e tudo funciona bem. No começo E então os dados foram gravados descontroladamente: os volumes são precisos, mas nos arquivos, pares de bytes separados são duplicados e depois desaparecem, sem qualquer regularidade. Não é bom!
As experiências começam. Estou escrevendo dados de teste - está tudo bem. Estou escrevendo combate - algum tipo de demônio. Eu mudo o tamanho dos buffers de dados, a frequência de sua descarga, os modelos de dados são inúteis. Nos próprios buffers, tudo é sempre excelente, os dados na memória estão em toda parte o que você precisa. E, no entanto, falhas em uma unidade flash - aqui estão elas.
Demorou alguns dias para cavar o cachorro.
O diagnóstico
O problema estava na interação da biblioteca com o equipamento
DMA .
Os cartões SD têm uma peculiaridade: eles são gravados apenas em blocos de 512 bytes. Para fazer isso, a biblioteca armazena em buffer os dados em uma matriz de 512 bytes e, após preenchê-los, libera a partir daí via DMA para piscar. Mas!
Se eu transferir para o registro um fragmento maior que <512xN + espaço vazio no buffer da biblioteca> bytes, a biblioteca (obviamente, para não empurrar a memória para frente e para trás) faz o seguinte: ela reabastece o buffer, grava-o para piscar e os próximos bytes 512xN são lançados diretamente no meu DMA do meu buffer! Bem, se algo for deixado inacabado - ele será copiado novamente até a próxima vez.
E tudo ficaria bem, mas o controlador DMA exige que os dados sejam colocados na memória alinhados em um limite de 4 bytes. O buffer da biblioteca está sempre tão alinhado que a linguagem garante isso. Mas com que endereço, depois de copiar uma parte dos dados, os restantes 512xN com um pequeno byte começam comigo - Deus sabe. E a biblioteca não verifica isso: o endereço, como é, é passado para o controlador DMA.
"Eles enviaram algo desajeitado ... Um cachorro com ele." O controlador silenciosamente redefine os 2 bits inferiores do endereço transmitido. E inicia a transferência.

O endereço, inicialmente não múltiplo de 4, é substituído por um múltiplo - voila, até os últimos três bytes do buffer da biblioteca são reescritos no arquivo do meu, e o mesmo número de bytes do buffer é perdido sem deixar rastro. Como resultado, a quantidade total de dados está correta, as operações ocorrem sem problemas, mas o disco não faz sentido.
Tratamento
Eu tive que adicionar outro buffer imediatamente antes de chamar a função de gravação de hardware. Se o endereço de gravação não for múltiplo de 4, os dados serão copiados primeiro para ele. Ao mesmo tempo, a velocidade média aumentou devido a uma escolha razoável do tamanho do buffer. Obviamente, foi preciso memória, mas o que significa 4 kilobytes por uma boa causa, quando você tem à sua disposição - 192 ilimitados!
História No. 2. Rantime e um monte
Prólogo
Após a próxima alteração, o programa começou a cair e, de alguma forma, caiu muito, jogando o processador no manipulador de
Hard Fault . E ele jogou lá logo após o início, mesmo antes da execução chegar a main (), ou seja, nenhuma linha do meu código teve tempo de executar.
A primeira impressão é "o castor está morto, o chip é para substituição". E então o programador deu o carvalho. Mas não, a versão antiga do firmware funciona de forma estável, mas a nova versão cai constantemente em algumas profundidades obscuras de montagem entre o lançamento e o meu código. Eu não tinha suposições de que tipo de heresia isso era.
Capítulo 1
Ajudou a Internet a observar como obter pelo menos algumas informações adicionais. O procedimento para analisar as consequências de um padrão rígido foi pesquisado: estado dos registros, pilha de despejo. Dopilil. Usou.
Acontece que ele trava devido a um erro de operação no barramento. Decidi que isso era novamente um acesso desequilibrado - um problema do mesmo tipo que na primeira história, mas de uma perspectiva diferente. Mas o mais oposto é onde ocorreu o erro. E surgiu dentro da biblioteca de tempo de execução, ou seja, no código, que, em teoria, era lambido como os machucados do gato em um dia ensolarado.
A continuação da análise mostrou que a falha é uma consequência de uma tentativa de inicializar variáveis estáticas locais.
Digressão líricaA propósito, considerando o código desmontado, eu encontrei simultaneamente a resposta para uma pergunta que às vezes me perguntava, mas estava com preguiça de pesquisar imediatamente: como a situação é resolvida quando 2 ou mais threads podem tentar inicializar essa variável ao mesmo tempo. Verificou-se que, nesse caso, o compilador organiza a inicialização com semáforos, garantindo que apenas um thread de cada vez passará por todo o procedimento, e o restante aguardará até que o primeiro termine.
Esse comportamento foi padronizado desde o C ++ 11. Você sabia Eu não
Capítulo 2
Uma vez que o tempo de execução está envolvido na construção de variáveis, é também para ele chamar destruidores após a conclusão do programa (mesmo que o programa nunca realmente conclua o trabalho, que é a norma absoluta para os microcontroladores). Para fazer isso, ele precisa de um local para armazenar informações sobre todas as variáveis que ele conseguiu inicializar.
É exatamente no local em que essas informações são armazenadas em algum tipo de lista interna, o tempo de execução também caiu. Como a função malloc (), através da qual a memória foi alocada para os elementos desta lista e que, de acordo com o padrão, produz blocos garantidos para serem alinhados
pelo menos no limite de 8 bytes , após um n-ésimo número de chamadas bem-sucedidas, produz uma peça que não está alinhada nesse limite.

Alterações no novo código de firmware quebraram malloc ?! Mas como isso é possível? Não redefini exatamente o malloc; eu mesmo não preciso dele em nenhum outro lugar!
Útil nas opções do compilador, para procurar por algumas palavras-chave, ajuda, mas foi dito claramente em todos os lugares:
malloc () garante a saída da memória alinhada ao longo do limite fundamental. Ou ponteiro nulo, caso não haja memória suficiente .
Capítulo 3
Por um longo tempo, fiquei sem sentido no código, estabeleci pontos de interrupção, sofri e não entendi nada, até que, em algum momento, não deu certo e observei os endereços retornados pelo malloc com cuidado. Antes disso, toda a análise era para verificar se o último dígito do endereço é 0x4. E agora ele começou a comparar inteiramente entre si endereços emitidos por chamadas sucessivas para malloc.
E oh, um milagre!
Todas as chamadas bem-sucedidas emitiram endereços do espaço RAM (0x20000000 e mais antigo para esta pedra), aumentando seqüencialmente de uma chamada para outra. E o primeiro malsucedido retornou 0x00000036. Ou seja, o endereço não apenas não estava alinhado, mas também não estava no espaço de endereço da RAM! O processador tentou escrever algo lá e naturalmente caiu.
E, surpreendentemente, mesmo que malloc () agisse de acordo com o padrão e retornasse 0 se não houvesse espaço suficiente, isso não teria mudado nada no sentido de uma falha no programa (a menos que a causa do bug tivesse sido esclarecida anteriormente). O valor retornado pelo malloc ainda não é verificado, mas entra em ação imediatamente. Isso está em tempo de execução.
Epílogo
Aumentou o tamanho da pilha no arquivo de configuração e tudo foi corrigido.
Mas antes desse momento, eu nem pensava no volume. Se o inferno se rendeu a mim, pensei. Enfim, tenho todas as variáveis e objetos estáticos ou na pilha. Portanto, apenas por inércia, deixei 0x300 bytes sob ele, pois algum volume no heap é alocado em todos os projetos de modelo. Mas não, o tempo de execução C ++ ainda precisa de memória alocada dinamicamente, e em quantidades bastante visíveis, pelos padrões dos controladores.
Viva e aprenda.