PlatformIO Community

Struggling with splitting a piece of code into multiple files

I hope I’m putting this in the right place, please feel free to move it elsewhere if i’m mistaken.

Hello! First post in the forum.
I’m having problems with splitting my code in multiple files, all while following the standard C/C++ folder structure.

The error:

Processing bluepill_f103c8 (platform: ststm32; board: bluepill_f103c8; framework: libopencm3)
-----------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/ststm32/bluepill_f103c8.html
PLATFORM: ST STM32 (11.0.0) > BluePill F103C8
HARDWARE: STM32F103C8T6 72MHz, 20KB RAM, 64KB Flash
DEBUG: Current (stlink) External (blackmagic, cmsis-dap, jlink, stlink)
PACKAGES: 
 - framework-libopencm3 1.10000.200730 (1.0.0) 
 - toolchain-gccarmnoneeabi 1.70201.0 (7.2.1)
LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 0 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Linking .pio/build/bluepill_f103c8/firmware.elf
.pio/build/bluepill_f103c8/src/main.o: In function `main':
main.c:(.text.startup.main+0x2): undefined reference to `clock_setup'
main.c:(.text.startup.main+0x6): undefined reference to `usart_setup'
main.c:(.text.startup.main+0xa): undefined reference to `i2c_setup'
main.c:(.text.startup.main+0xe): undefined reference to `gpio_setup'
collect2: error: ld returned 1 exit status
*** [.pio/build/bluepill_f103c8/firmware.elf] Error 1
============================== [FAILED] Took 0.84 seconds ==============================
The terminal process "pio 'run'" terminated with exit code: 1.

Project structure(standard libopencm3):
Screenshot_20210207_194433
Here the files:

  • main.c
#include <string.h>
#include "init.h"

void usart_send_str(uint32_t usart, char *str);

void usart_send_str(uint32_t usart, char *str) {
    while(*str) {
        usart_send_blocking(usart, *str);
        str++;
    }
}

int main(void) {
    clock_setup();
    usart_setup();
    i2c_setup();
    gpio_setup();

    usart_send_str(USART1, "Program started!\r\n");
    
    while(1) {
        __asm__("nop");
    }
    return 0;
}

void usart1_isr(void) {

    static uint8_t data = 'A';

	/* Check if we were called because of RXNE. */
	if (((USART_CR1(USART1) & USART_CR1_RXNEIE) != 0) &&
	    ((USART_SR(USART1) & USART_SR_RXNE) != 0)) {

		/* Indicate that we got data. */
		gpio_toggle(GPIOC, GPIO13);

		/* Retrieve the data from the peripheral. */
		data = usart_recv(USART1);

		/* Enable transmit interrupt so it sends back the data. */
		USART_CR1(USART1) |= USART_CR1_TXEIE;
	}

	/* Check if we were called because of TXE. */
	if (((USART_CR1(USART1) & USART_CR1_TXEIE) != 0) &&
	    ((USART_SR(USART1) & USART_SR_TXE) != 0)) {

		/* Indicate that we are sending out data. */
		gpio_toggle(GPIOC, GPIO13);

		/* Put data into the transmit register. */
		usart_send(USART1, data);

		/* Disable the TXE interrupt as we don't need it anymore. */
		USART_CR1(USART1) &= ~USART_CR1_TXEIE;
	}
}
  • init.c
#include "init.h"

static void clock_setup(void) {
    rcc_clock_setup_in_hse_8mhz_out_72mhz();

    rcc_periph_clock_enable(RCC_GPIOA);
    rcc_periph_clock_enable(RCC_GPIOC);
    rcc_periph_clock_enable(RCC_GPIOB);

    rcc_periph_clock_enable(RCC_USART1);

    rcc_periph_clock_enable(RCC_I2C1);
}

static void usart_setup(void) {

    nvic_enable_irq(NVIC_USART1_IRQ);

    gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_USART1_TX);
    gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_USART1_RX);

    usart_set_baudrate(USART1, 115200);
    usart_set_databits(USART1, 8);
    usart_set_stopbits(USART1, USART_STOPBITS_1);
    usart_set_mode(USART1, USART_MODE_TX_RX);
    usart_set_parity(USART1, USART_PARITY_NONE);
	usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
    usart_enable_rx_interrupt(USART1);

    usart_enable(USART1);
}

static void i2c_setup(void) {
    gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_I2C1_SCL | GPIO_I2C1_SDA);

    i2c_peripheral_disable(I2C1);

    i2c_set_clock_frequency(I2C1, I2C_CR2_FREQ_36MHZ);
    i2c_set_fast_mode(I2C1);
    i2c_set_ccr(I2C1, 0x1e);
    i2c_set_trise(I2C1, 0x0b);
    i2c_set_own_7bit_slave_address(I2C1, 0x32);
    i2c_peripheral_enable(I2C1);

}

static void gpio_setup(void) {
    gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
}
  • init.h
#ifndef INIT_H
#define INIT_H
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/cm3/nvic.h>

static void clock_setup(void);
static void usart_setup(void);
static void i2c_setup(void);
static void gpio_setup(void);

#endif

It appears to be a linker problem, although I’m unsure what I’m doing wrong, since the header files are in the include folder and the source files are in src.

By declaring the function static you’ve limited the visibility scope of the function to be only inside the .c file it has been written in. Thus you cannot reference it from main.c.

Remove all static keywords in functions that you want to share across files. That goes for the .c file as well as the .h file.

Oh my god, thank you so much.
Can’t believe I made such a stupid mistake, it works now!