Franzininho C0 Sem o STM32Cube: Estruturando Seu Primeiro Projeto na unha (sem IDE)

Este post faz parte da série Franzininho C0 Sem o STM32Cube

Este é o segundo artigo da série onde aprenderemos como desenvolver projetos com STM32 sem depender de IDEs gráficas ou ecossistemas como o STM32Cube. Neste artigo, você aprenderá a montar a estrutura de diretórios manualmente e a escrever o código para o seu primeiro exemplo: um programa blink.

Montando estrutura de diretórios

  1. Em seu sistema, busque pelo diretório do linux: “\\wsl.localhost\Ubuntu\home\user”.
  1. Crie uma pasta para o projeto, aqui vou chamá-la de hello-world. 
  1. Dentro desta pasta crie as seguintes subpastas: build, inc, src, stm
  1. Entre em https://github.com/STMicroelectronics/STM32CubeC0/tree/main, faça o clone do repositório e inicie os submodulos.
git clone git@github.com:STMicroelectronics/STM32CubeC0.gitgit submodule update --init --recursive 
  1. Acesse o diretório \STM32CubeC0\Drivers\CMSIS\Device\ST\STM32C0xx\Source\Templates e copie o arquivo system_stm32c0xx.c (responsável pela inicialização e configuração do clock) para a subpasta src criada dentro da pasta do seu projeto.
  1. Em seguida, abra \STM32CubeC0\Drivers\CMSIS\Device\ST\STM32C0xx\Source\Templates\gcc e copie o arquivo startup_stm32c011xx.s (responsável por definir a vector table) para a subpasta src riada dentro da pasta do seu projeto.
  1. Depois, localize algum projeto criado no STM32CubeIDE para a Franzininho C0 e copie o arquivo STM32C011F6PX_FLASH.ld (script de ligação responsável por gerar o binário, especificar a localização da vector table e configurar os endereços relativos para chamadas de função e acesso a dados) para a pasta raiz do seu projeto. Caso você não possua esse arquivo, crie um novo arquivo com o nome STM32C011F6PX_FLASH.ld e insira o seguinte código:
/*
******************************************************************************
**
** @file        : LinkerScript.ld
**
** @author      : Auto-generated by STM32CubeIDE
**
** @brief       : Linker script for STM32C011F6Px Device from STM32C0 series
**                      32KBytes FLASH
**                      6KBytes RAM
**
**                Set heap size, stack size and stack location according
**                to application requirements.
**
**                Set memory bank area and size if external memory is used
**
**  Target      : STMicroelectronics STM32
**
**  Distribution: The file is distributed as is, without any warranty
**                of any kind.
**
******************************************************************************
** @attention
**
** Copyright (c) 2024 STMicroelectronics.
** All rights reserved.
**
** This software is licensed under terms that can be found in the LICENSE file
** in the root directory of this software component.
** If no LICENSE file comes with this software, it is provided AS-IS.
**
******************************************************************************
*/

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */

_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Memories definition */
MEMORY
{
  RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 6K
  FLASH    (rx)    : ORIGIN = 0x8000000,   LENGTH = 32K
}

/* Sections */
SECTIONS
{
  /* The startup code into "FLASH" Rom type memory */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data into "FLASH" Rom type memory */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH

  /* Constant data into "FLASH" Rom type memory */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH

  .ARM.extab (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
  {
    . = ALIGN(4);
    *(.ARM.extab* .gnu.linkonce.armextab.*)
    . = ALIGN(4);
  } >FLASH

  .ARM (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
  {
    . = ALIGN(4);
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
    . = ALIGN(4);
  } >FLASH

  .preinit_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
    . = ALIGN(4);
  } >FLASH

  .init_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
    . = ALIGN(4);
  } >FLASH

  .fini_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
    . = ALIGN(4);
  } >FLASH

  /* Used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections into "RAM" Ram type memory */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */

  } >RAM AT> FLASH

  /* Uninitialized data section into "RAM" Ram type memory */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss section */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM

  /* User_heap_stack section, used to check that there is enough "RAM" Ram  type memory left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM

  /* Remove information from the compiler libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}
  1. Por último, acesse \STM32CubeC0\Drivers\ e copie as pastas CMIS e da HAL_C0 para a sua subpasta stm. Também copie o arquivo “stm32c0xx_hal_conf_template.h” para sua pasta include

É importante destacar que o diretório e o template referentes à HAL não serão utilizados neste projeto, portanto, sua inclusão é opcional. No entanto, estou apresentando essa etapa para que você tenha a estrutura preparada caso decida programar utilizando os drivers HAL no futuro, sem a necessidade de abrir o STM32CubeIDE. 

Primeiro Programa: Blink

Entre na pasta do seu projeto pelo terminal Ubuntu e digite code . para abrir no VSCode. 

Em src crie um arquivo main.c

A Franzininho C0 possui um LED conectado ao pino PB6, e nosso objetivo é fazê-lo piscar.

Os passos que devemos seguir para implementação do código são: 

  1. Habilitar clock no GPIO
  2. Configurar o GPIO como saída na porta desejada
  3. Configurar o GPIO no modo pushpull 
  4. Configurar a velocidade no GPIO
  5. Escrever 1 no registrador de saída para ligar o LED  
  6. Escrever 0 no registrador de saída para ligar o LED 

No entanto, antes de começar a codificação, é preciso compreender alguns conceitos sobre registradores. Para isso, consulte o manual de referência do STM32C0:: https://www.st.com/resource/en/reference_manual/rm0490-stm32c0-series-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

Habilitar o Clock no GPIO

No manual de referência encontre o RCC_IOPENR. Esse registrador é responsável por habilitar o clock do GPIO. 

Configurações GPIO

Buscando por General-purpose I/Os (GPIO) no manual de referência teremos a descrição sobre os GPIOs.Cada porta de E/S de uso geral possui quatro registradores de configuração de 32 bits (GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR e GPIOx_PUPDR), dois registradores de dados de 32 bits (GPIOx_IDR e GPIOx_ODR) e um registrador de configuração de bits de set/reset de 32 bits (GPIOx_BSRR). Além disso, todas as GPIOs possuem um registrador de bloqueio de 32 bits (GPIOx_LCKR) e dois registradores de seleção de função alternativa de 32 bits (GPIOx_AFRH e GPIOx_AFRL). 

Estrutura básica GPIOs

Tabela de configuração de bits de porta

Detalhamento Registradores 

  1. GPIOx_MODER define o modo de operação do GPIO. Ele tem 2 bits dedicados para cada pino.
  • 00: Entrada digital
  • 01: Saída digital
  • 10: Função alternativa (para periféricos como SPI, UART, etc.)
  • 11: Modo analógico (para ADC ou DAC).

No nosso exemplo, vamos usar o pino PB6, como cada pino possui 2 bits dedicados, o 6 estará na posição 12 e 13 e para o pino atuar como saída será necessário que neles fique 0 na posição 13 e 1 na posição 12.

  1. GPIOx_OTYPER configura o GPIO em output push-pull (reset state) quando usado o bit 0 e quando usado bit 1 configura como Output open-drain.

No nosso exemplo, na posição 6 deveremos colocar o bit 0. 

  1. GPIOx_OSPEEDR define a velocidade no registrador. É reservado 2 bits para essa configuração. 
  • 00: Velocidade muito baixa
  • 01: Baixa velocidade
  • 10: Alta velocidade
  • 11: Velocidade muito alta

Novamente, em nosso exemplo vamos utilizar o PB6, portanto nas posições 12 e 13 ajustaremos com os bits 00 para configurar em velocidade muito baixa. 

  1. GPIOx_ODR é utilizado para manipular saídas. Esse registrador é dividido em bits individuais, com cada bit correspondendo a um pino da porta GPIO, em que se bit definido como:
  • 0 – pino é colocado em nível lógico baixo
  • 1 – pino é colocado em nível lógico alto

Em nosso exemplo, basicamente vamos deslocar para o pino na posição 6 e setar 1 para nível lógico alto.

Código

Por fim, agora que entendemos os conceitos básicos sobre registradores, podemos avançar para a implementação do código com auxílio das macros da CMSIS. No arquivo main.c adicione o seguinte código:

#include "stm32c011xx.h"

void delay(unsigned long delay) {
    for (unsigned long i = 0; i < delay * 1000; i++) {
        __asm("nop"); 
    }
}

void gpio_init(void) {
    // Habilitar o clock para o GPIOB
    RCC->IOPENR |= RCC_IOPENR_GPIOBEN;

    // Configurar PB6 como saída
    GPIOB->MODER &= ~(3U << (6 * 2)); // Limpa os bits correspondentes ao pino 6
    GPIOB->MODER |= (1U << (6 * 2));  // Configura PB6 como saída (01)

    // Configurar PB6 como push-pull
    GPIOB->OTYPER &= ~(1U << 6); // 0 = Saída push-pull

    // Configurar PB6 para baixa velocidade
    GPIOB->OSPEEDR &= ~(3U << (6 * 2)); // 00 = Baixa velocidade
}

void gpio_set_high(void) {
    // Configurar PB6 em nível lógico alto usando ODR
    GPIOB->ODR |= (1U << 6);
}

void gpio_set_low(void) {
    // Configurar PB6 em nível lógico baixo usando ODR
    GPIOB->ODR &= ~(1U << 6);
}

int main(void) {
    gpio_init();

    while (1) {
        gpio_set_high(); // PB6 em nível alto
        delay(1000);     // Aguarda 
        gpio_set_low();  // PB6 em nível baixo
        delay(1000);     // Aguarda 
    }

    return 0;
}

Caso você queira visualizar as macros declaradas, acesse o diretório stm/CMSIS/Device/ST/STM32C0xx/Include/stm32c011xx.h.

Conclusão

Neste segundo artigo, você aprendeu a montar a estrutura de diretórios e a escrever seu primeiro código utilizando as macros da CMSIS e manipulando registradores. No próximo artigo, vamos gerar o arquivo .elf e gravá-lo na placa. Fique atento aqui no Portal!

Franzininho C0 Sem o STM32Cube

Franzininho C0 Sem o STM32Cube: Programando STM32 com GNU ARM, Make e OpenOCD Criando um Makefile e Gravando com OpenOCD 
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Home » Software » Firmware » Franzininho C0 Sem o STM32Cube: Estruturando Seu Primeiro Projeto na unha (sem IDE)

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: