A programação está dividindo algo grande e impossível em algo pequeno e muito real.
Olá a todos. Para começar, gostaria de agradecer aos moderadores por terem perdido meu primeiro post (nojento) e dizer olá à mamãe! E também gostaria de agradecer a todos os leitores e pessoas que apontaram meus erros e ajudaram a corrigi-los. Faço imediatamente uma reserva de que em russo não escrevi a partir do 6º ano, em geral, não fique com raiva.STM32 Parte 1: Noções básicasEntão, vamos começar. Em um artigo anterior, passei rapidamente pelos primeiros pontos. Este foi o nosso arquivo startup.c, responsável por 2 vetores (pilha, Reset_Handler) e um pouco sobre o script do vinculador. Hoje vamos complementar nosso código de inicialização, desmontar o linker para peças de reposição e descobrir como tudo funciona.extern void *_estack;
void Reset_Handler();
void *vectors[] __attribute__((section(".isr_vector"), used)) = {
&_estack,
&Reset_Handler
};
void __attribute__((naked, noreturn)) Reset_Handler()
{
while(1);
}
Se esse código não estiver claro para ninguém, você poderá ler sobre ele aqui (o artigo não é uma fonte, mas tentou explicar).Agora vamos descobrir o que está faltando aqui e como adicioná-lo. Obviamente, mícrons têm outros vetores. Por exemplo, o vetor Hard_Fault é um vetor chamado ações impróprias com μ, por exemplo, se ele tentar executar uma instrução que o processador não entende, ele pulará para o endereço do vetor Hard_Fault. Existem muitos desses vetores e não é fato que em nosso programa usaremos tudo. Além disso, os vetores de interrupção estão na mesma tabela.(Para quem ainda não sabe o que é um vetor, isso é um ponteiro para um endereço, também conhecido como "ponteiro").Complementamos nosso código e depois explicarei o que fiz.extern void *_estack;
void Reset_Handler();
void Default_Handler();
void NMI_Handler() __attribute__((weak, alias("Default_Handler")));
void HardFault_Handler() __attribute__((weak, alias("Default_Handler")));
void *vectors[] __attribute__((section(".isr_vector"), used)) = {
&_estack,
&Reset_Handler
&NMI_Handler,
&HardFault_Handler
};
void __attribute__((naked, noreturn)) Reset_Handler()
{
while(1);
}
void __attribute__((naked, noreturn)) Default_Handler(){
while(1);
}
Aqui eu adicionei uma nova função Default_Handler (); que irá lidar com todas as interrupções, absolutamente. Mas também com a ajuda, __attribute__((weak, alias("Default_Handler")));
observei que se uma função com o mesmo nome for declarada, ela será a manipuladora dessa interrupção. Em outras palavras, agora é apenas um apelido para Default_Handler. Portanto, podemos adicionar todos os vetores na lista do seu manual de referência. Isso é feito para que, se você de repente decidiu usar uma interrupção, mas se esqueceu de criar um manipulador de funções, o Default_Handler usual seja chamado.Eu acho que neste momento com vetores você pode finalizar e ir para a inicialização de setores e para o vinculador. Para começar, vamos atualizar nosso startup.c novamente adicionando a inicialização dos setores de dados e bss ao corpo Reset_Handler, além de várias variáveis externas.extern void *_sidata, *_sdata, *_edata, *_sbss, *_ebss;
void __attribute__((naked, noreturn)) Reset_Handler()
{
void **pSource, **pDest;
for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++)
*pDest = *pSource;
for (pDest = &_sbss; pDest != &_ebss; pDest++)
*pDest = 0;
while(1);
}
Então, novas variáveis e novamente elas foram declaradas em nosso script vinculador. Lembre-se do script anterior . E também compare com um novo.MEMORY{
ROM(rx) : ORIGIN = 0x08000000, LENGTH = 1M
SRAM (rwx): ORIGIN = 0x20010000, LENGTH = 240K
}
_estack = LENGTH(SRAM) + ORIGIN(SRAM);
SECTIONS{
.isr_vector : {
KEEP(*(.isr_vector))
} >ROM
.text : {
. = ALIGN(4);
*(.text)
} >ROM
_sidata = LOADADDR(.data);
.data : {
. = ALIGN(4);
_sdata = .;
*(.data)
. = ALIGN(4);
_edata = .;
} >SRAM AT>ROM
.bss : {
. = ALIGN(4);
_sbss = .;
*(.bss)
. = ALIGN(4);
_ebss = .;
} >SRAM
}
Duas novas seções foram adicionadas, essas são .data e .bss. Por que nós precisamos deles? bem .data deixará variáveis globais e estática irá para bss. Ambas as seções estão na RAM e, se tudo é simples com a seção bss (são apenas zeros), existem algumas dificuldades com a seção de dados. Primeiro, os dados já são dados inicializados que são conhecidos em tempo de compilação. Assim, inicialmente a seção de dados estará em algum lugar da memória flash. Em segundo lugar, precisamos indicar de alguma forma ao vinculador o script que ele está na memória flash, mas será transferido para a RAM durante a execução do programa. Vamos dar uma olhada em uma parte do script onde descrevemos a seção de dados.
_sidata = LOADADDR(.data);
.data : {
. = ALIGN(4);
_sdata = .;
*(.data)
. = ALIGN(4);
_edata = .;
} >SRAM AT>ROM
Começa com a declaração de _sidata e a atribuição, no momento, de um significado que não está claro para nós. Em seguida, uma descrição da seção em si começa. Aconselho que você leia sobre a função ALIGN aqui , mas, resumidamente, atribuímos ao nosso ponto (endereço atual) um valor que é mais facilmente aceito pelo nosso processador quando ele tenta carregar dados ou instruções a partir daí.Também estão incluídos _sdata, _edata e o final da seção. Mas no final da seção, há exatamente o que precisamos.SRAM AT>ROM
. Esta linha informa ao nosso vinculador que a seção está na SRAM, mas esta seção é carregada na ROM ou na memória flash. Agora, de volta à nossa variável _sidata e à função LOADADDR. Esta função nos retornará um valor que será igual ao endereço da seção de carregamento. Ou seja, indicamos que a seção foi carregada na memória flash, mas a usamos na RAM e, para transferir essa seção, precisamos conhecer seu endereço na memória flash, que é o que a função LOADADDR retorna. Enquanto isso, as variáveis _sdata e _edata indicam a localização da seção na RAM, dando-nos a oportunidade de carregar a seção de dados na parte correta da memória. Deixe-me lembrá-lo de que é com isso que trata o código de inicialização que complementamos acima.Por que precisamos desse vinculador e de tais dificuldades com a distribuição de seções de nosso aplicativo? A seção Dados é apenas um pequeno exemplo de por que precisamos ser capazes de colocar e alterar seções na memória. Abaixo está um exemplo da seção de texto que eu uso..text (ORIGIN(ROM_ITCM) + SIZEOF(.isr_vector)): {
. = ALIGN(4);
*(.text)
} AT>ROM_AXIM
A essência desse bloco é que eu carrego a seção de texto também na memória flash, mas uso um barramento diferente para acessar as instruções e um acelerador ART (como um cache comum). E para que tudo funcione, eu indiquei o endereço de carga e o endereço durante a execução do código, mas não funcionaria se eu não indicasse que a seção deveria ser deslocada. Existem muitas outras invenções, não mostrarei todas elas.Nesse estágio, as seções são carregadas onde necessário e inicializadas. nós temos uma base de trabalho para escrever mais código. No próximo artigo, escreveremos nosso primeiro driver e piscaremos um LED.Obrigado a todos por sua atenção e sucesso em seus empreendimentos.