Algum tempo atrás, eu queria aprender montador e, depois de ler a literatura relevante, era hora de praticar. Na verdade, será discutido mais adiante. No começo, pratiquei no Arduino Uno (Atmega328p), agora decidi seguir em frente e assumi o STM32. O STM32F103C8 realmente caiu em minhas mãos e outras experiências serão realizadas.
As ferramentas
Eu usei as seguintes ferramentas:
- Notepad ++ - para escrever código
- Compilador GNU Assembler
- Utilitário STM32 ST-LINK + ST-LINK V2 - para exibir o código no microcontrolador e depurar
Iniciar
O principal objetivo da linguagem assembly para mim é aprender. Como você nunca sabe onde encontrar outro problema interessante, foi decidido escrever tudo do zero. A principal tarefa foi entender como o vetor de interrupção funciona. Diferentemente do Atmega no STM32, o vetor de interrupção não contém instruções de salto:
jmp main
Endereços específicos estão escritos nele e, durante a interrupção, o próprio processador substitui o endereço especificado no vetor no registro do PC. Aqui está um exemplo do meu vetor de interrupção:
.org 0x00000000 SP: .word STACKINIT RESET: .word main NMI_HANDLER: .word nmi_fault HARD_FAULT: .word hard_fault MEMORY_FAULT: .word memory_fault BUS_FAULT: .word bus_fault USAGE_FAULT: .word usage_fault .org 0x000000B0 TIMER2_INTERRUPT: .word timer2_interupt + 1
Quero chamar a atenção do leitor para o fato de que a primeira linha não é o vetor de redefinição, mas os valores pelos quais a pilha será inicializada. Imediatamente após, há um vetor de redefinição seguido por 5 vetores de interrupção obrigatórios (NMI_HANDLER - USAGE_FAULT).
Desenvolvimento
A primeira coisa com a qual fiquei presa foi a sintaxe do assembler do ARM. Mesmo durante o estudo do vetor de interrupção, me deparei com referências ao fato de o ARM ter 2 tipos de instruções Thumb e não Thumb. E que o Cortex-M3 (STM32F103C8, ou seja, o Cortex-M3) suporta apenas um conjunto de instruções Thumb. Escrevi as instruções estritamente de acordo com a documentação, mas por alguma razão o montador as xingou.
registro não deslocado necessário
Verificou-se que no início do programa
.syntax unificado
isso informa ao montador que você pode usar as instruções Thumb e não Thumb ao mesmo tempo.
A próxima coisa que me deparei foi com as portas GPOI padrão desativadas. Para fazê-los funcionar, entre outras coisas, você precisa definir os valores apropriados nos registros RCC (reset e controle de relógio). Eu usei o PORT C, ele pode ser ativado configurando o bit 4 (a numeração de bits começa do zero) em RCC_APB2ENR (registro de ativação do relógio periférico 2).
Mais LED piscando. Primeiro de tudo, como no Arduino, você precisa definir um pino para a gravação. Isso é feito via GPIOx_CRL (registro de controle baixo) ou GPIOx_CRH (registro de controle alto). Aqui é necessário cancelar que, para cada pino, 4 bits sejam responsáveis em um desses registros (registros de 32 bits). 2 bits (MODEy) determinam a taxa máxima de dados e a configuração de pinos de 2 bits (CNF). Usei o pino C da PORTA 14, para isso defino os bits [25:24] = 10 e os bits [27:26] = 00 no registro GPIOx_CRH.
Para o diodo queimar, é necessário definir o bit correspondente em GPIOx_ODR (registro de dados de saída). No meu caso, bit 14. Isso poderia encerrar este exemplo simples, criando uma função de atraso e colocando tudo em um loop, mas eu não poderia fazer isso. Decidi definir interrupções no temporizador ... Como se viu, foi em vão, principalmente porque os temporizadores são muito rápidos para esse tipo de tarefa.
Não descreverei em detalhes as configurações do timer, que estão interessadas no código no
Github . A idéia era simples: em um ciclo, envie o processador para Idle, saia do Idle pelo timer para ligar / desligar o LED e novamente para Idle. Mas o timer funcionou muito mais rápido do que eu consegui fazer todas as opções acima, por causa do qual tive que inserir um contador adicional.
O contador é uma variável de 32 bits que deveria estar na SRAM. E então outro ancinho estava me esperando. Quando programei no Atmega para colocar uma variável na SRAM, através do .org, defina o endereço do início da memória onde o bloco de dados foi realmente colocado. Agora, depois de ler um pouco sobre a inicialização da memória, não tenho certeza se isso estava correto, mas funcionou. E eu decidi pôr em marcha a mesma coisa com o STM32. O endereço inicial da memória no STM32F103C8 é 0x20000000. E quando eu fiz .org neste endereço, recebi um binário de 512mb. Isso me enviou algumas noites para fumar manuais. Ainda não entendo 100% como isso funciona, mas até onde eu entendo a seção .data coloca os valores pelos quais as variáveis devem ser inicializadas em um arquivo executável, mas, em tempo de execução, o programador deve inicializar os valores das variáveis na memória. Corrija-me, por favor, se eu estiver errado. Acabei criando uma variável como esta:
.section .bss .offset 0x20000000 flash_counter: .word
Inicializou no início da função principal e o LED piscou. Espero que este artigo ajude alguém. Se você tiver dúvidas, terei prazer em respondê-las.