Você não pode confiar no código que você não escreveu completamente. - Ken Thompson
Talvez minha citação favorita. Foi ela quem se tornou a razão pela qual eu decidi mergulhar nas profundezas da toca do coelho. Comecei minha jornada no mundo da programação recentemente, apenas cerca de um mês e decidi escrever artigos para consolidar o material. Tudo começou com uma tarefa simples: sincronizar as lâmpadas no seu estúdio de fotografia usando o Arduina. O problema foi resolvido, mas eu não entrei mais no estúdio de fotografia, não há tempo. A partir desse momento, eu decidi me engajar na programação do microcontrolador. Arduin, apesar de atraente em sua simplicidade, não gostei da plataforma. A escolha recaiu sobre a empresa ST e seus produtos populares. Naquele momento, eu ainda não tinha ideia da diferença, mas como consumidor típico comparei a velocidade do “processador” e a quantidade de memória, comprei uma placa impressionante com uma tela STM32F746NG - Discovery.Sentirei falta dos momentos de desespero e vou direto ao ponto.Imerso na imagem de um programador, li muito, estudei, experimentei. E como eu já descrevi acima, eu queria estudar bem, só isso. E para isso, estabeleci uma meta, não soluções prontas, apenas a minha. E se tudo der certo para mim, você terá sucesso.Lista de tudo que você precisa:- Máquina virtual Ubuntu 16+ ou qualquer outra coisa
- compilador de arm - faça o download em developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
- depurador e programador openocd - você não poderá fazer o download a partir do link, coletamos das fontes, as instruções estão anexadas
git clone https://git.code.sf.net/p/openocd/code openocd
- Editor de texto ao seu gosto
Depois que tudo estiver instalado e montado, podemos prosseguir para o primeiro projeto! E não, nem é uma lâmpada piscando. Para começar, precisamos nos aprofundar no processo de inicialização do próprio micropoço.O que precisamos:- Makefile
- Linker.ld
- Init.c
Vamos começar com o último parágrafo Init.c. Antes de tudo, nosso mk deve carregar o endereço "ponteiro da pilha" é um ponteiro para o endereço de memória usado nas instruções PUSH e POP. Eu recomendo fortemente que você estude cuidadosamente essas duas instruções, pois não explicarei em detalhes todas as instruções. Como implementar isso, veja abaixo.extern void *_estack;
void *vectors[] __attribute__((section(".isr_vector"), used)) = {
&_estack
}
Agora vamos ver este exemplo. Extern significa que o símbolo é externo e, como declaramos esse símbolo no arquivo Linker.ld, retornaremos a ele um pouco mais tarde. __attribute__((section(".isr_vector"), used))
Aqui, usamos um atributo que diz ao compilador para colocar o array na seção isr_vector e que, mesmo que não o utilizemos no código, ele ainda deve ser incluído no programa. E seu primeiro elemento será o mesmo ponteiro.Agora vamos descobrir por que esse array é e com o que ele será consumido. Ao contrário de um processador convencional, o micron na arquitetura do braço inicia a execução quando não é do endereço zero, mas do endereço apontado pelo ponteiro nessa matriz, tudo é complicado. Além disso, o primeiro ponteiro nessa matriz sempre aponta para o início da pilha, mas o segundo já aponta para o início do nosso código.Vou dar um exemplo. é dado que a pilha começa com 0x20010000 e o código do programa é 0x0800008. então a matriz pode ser escrita comovoid *vectors[] __attribute__((section(".isr_vector"), used)) = {
0x20010000,
0x08000008
}
Ou seja, o controlador inicializa a pilha e depois considera o endereço da primeira instrução e o carrega no registrador do contador de programa. Agora, o mais importante, dependendo do modelo, esses números podem ser diferentes, mas com o exemplo de stm32f7, posso dizer com segurança que esse array deve estar na memória no endereço 0x08000000. É a partir desse endereço que o mk inicia seu trabalho após ligar ou redefinir.Agora vamos parar e prestar atenção em como colocar esse array na seção que precisamos. Isso é feito por "ld" ou vinculador. Este programa coleta todo o nosso programa e o script Linker.ld é usado para isso. Dou um exemplo e o analiso ainda mais.MEMORY{
ROM_AXIM (rx) : ORIGIN = 0x08000000, LENGTH = 1M
RAM_DTCM (rwx): ORIGIN = 0x20000000, LENGTH = 64K
}
_estack = LENGTH(RAM_DTCM) + ORIGIN(RAM_DTCM);
SECTIONS{
.isr_vector : {
KEEP(*(.isr_vector))
} >ROM_AXIM</code>
}
Vamos ver o que acontece aqui. MEMORY define seções da memória e SECTIONS define seções. aqui vemos nossa pilha e o fato de ser igual à soma do começo da memória e seu comprimento, isto é, o fim da nossa memória. A seção .isr_vector na qual colocamos nossa matriz também é definida. >ROM_AXIM
no final de nossa seção significa que esta seção deve ser colocada na seção de memória que começa com 0x08000000 conforme exigido por nossos mícrons.Colocamos a matriz onde é necessário agora precisamos de algum tipo de instrução para que nosso micron funcione. Aqui está o init.c aumentado:extern void *_estack;
void Reset_Handler();
void *vectors[] __attribute__((section(".isr_vector"), used)) = {
&_estack,
&Reset_Handler
};
void __attribute__((naked, noreturn)) Reset_Handler()
{
while(1);
}
Como mencionei anteriormente, o segundo endereço deve ser um ponteiro para a primeira função ou instrução. E, novamente, os atributos, mas tudo é simples, a função não retorna nenhum valor e segue qualquer ABI na entrada, ou seja, nu "nu". Em tais funções, o montador usual é empurrado.Agora é a hora de compilar nosso código e ver o que está por trás. Por enquanto, não tocaremos no Makefile.arm-none-eabi-gcc -c init.c -o init.o -mthumb
arm-none-eabi-gcc -TLinker.ld -o prog.elf init.o -Wl,--gc-sections -nostartfiles -nodefaultlibs -nostdlib
E aqui vemos muito incompreensível. em ordem:- -mthumb compile para armv7, que usa apenas instruções de polegar
- -TLinker.ld especifica o script do vinculador. por padrão, ele compila para execução no ambiente Linux
- -Wl, - gc-section -nostartfiles -nodefaultlibs -nostdlib remove todas as bibliotecas padrão, arquivos de inicialização do ambiente C e todas as outras bibliotecas auxiliares, como matemática.
Obviamente, não faz sentido carregar isso em mícrons. Mas há um sentido em visualizar e estudar o binário.objcopy -O ihex prog.elf prog.bin
hexdump prog.bin
E então veremos a conclusão. "00000000: 00 01 00 02 09 00 00 08", que simbolizarão nosso sucesso. Este é o meu primeiro artigo e não pude revelar completamente todo o material e a essência; portanto, no próximo, descreverei os mecanismos com mais detalhes e nós, amigos, poderemos passar para um programa que não pisca, mas configura o relógio do processador e dos barramentos.