开发环境搭建完成后,我们就开始了在Linux环境下的STM32应用程序开发之旅了。
为了验证我们的开发环境配置是否正确,下面就通过创建一个点亮开发板上的LED灯珠的实例来进行检验。
在这个验证实例中,笔者选用的硬件是自己设计的基于STM32F103ZET6的核心板。
首先我们任意选择一个磁盘分区,新建一个目录名为led_test的文件夹,用来作为测试实例的根目录。
在根目录下新建app、doc、drv、hal四个目录,用来存放我们的测试代码和文档。其中:
app目录下新建src和inc两个目录,分别用来存在app层的源码文件和头文件。
doc目录用来存放项目开发日志和相关的文档文件。
drv下新建core、user两个目录,每个目录下再新建src和inc目录,其中core用来存放mcu内核相关的源码文件和头文件,user目录用 来存放驱动源码文件和头文件。
hal目录下新建src和inc两个目录,分别存放中间层源码文件和头文件。在源码中增加中间件的目的是为了在以后的项目中,便于工程代码在不同mcu中进行移植。
创建好目录结构后,将ST官方库STM32F10x_StdPeriph_Lib_V3.5.0文件中的汇编文件Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\TrueSTUDIO\startup_stm32f10x_hd.s复制到项目中的drv\core\src目录下。同时将system_stm32f10x.c文件也复制到该文件夹下。再将core_cm3.h、stm32f10x.h、system_stm32f10x.h、stm32f103_conf.h复制到drv\core\inc目录下。
至此,测试实例的基本框架结构创建完成。 下一步,打开Visual Studio Code,将led_test文件夹通过添加工作区文件夹的方式导入到Visual Studio Code工作区中。
在stm32f103zet6_flash.ld文件中添加如下内容:
/******************************************************************************
*
* @FileName : stm32f103zet6_flash.ld
*
* @Author : WeiShuangbo
*
* @Version : 1.0
*
* @Date : 2020-04-12
*
* @Description : The LD file with led_test
*
* @Target : STMicroelectronics STM32F103
*
*
******************************************************************************/
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = 0x20010000; /* end of 64K RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0; /* required amount of heap */
_Min_Stack_Size = 0x200; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K
}
/* Define output sections */
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} >FLASH
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.ARM.attributes : { *(.ARM.attributes) } > FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(.fini_array*))
KEEP (*(SORT(.fini_array.*)))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
/* used by the startup to initialize data */
_sidata = .;
/* Initialized data sections goes into RAM, load LMA copy after code */
.data : AT ( _sidata )
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM
/* Uninitialized data section */
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss secion */
_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
PROVIDE ( end = _ebss );
PROVIDE ( _end = _ebss );
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(4);
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(4);
} >RAM
/* MEMORY_bank1 section, code must be located here explicitly */
/* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */
.memory_b1_text :
{
*(.mb1text) /* .mb1text sections (code) */
*(.mb1text*) /* .mb1text* sections (code) */
*(.mb1rodata) /* read-only data (constants) */
*(.mb1rodata*)
} >MEMORY_B1
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
}
在makefile文件中添加如下内容:
#project name is led_test
TARGET = led_test
export CC = arm-none-eabi-gcc
export AS = arm-none-eabi-as
export LD = arm-none-eabi-ld
export OBJCOPY = arm-none-eabi-objcopy
top = $(shell pwd)
inc = -I $(top)/drv/core/inc \
-I $(top)/drv/user/inc \
-I $(top)/hal/inc \
-I $(top)/app/inc
obj_flag = -W -Wall -g -mcpu=cortex-m3 -mthumb -D STM32F10X_HD -D USE_STDPERIPH_DRIVER -D BOARD_V2 $(inc) -O0 -std=gnu11
target_flag = -mthumb -mcpu=cortex-m3 -Wl,--start-group -lc -lm -Wl,--end-group \
-specs=nano.specs -specs=nosys.specs -static -Wl,-cref,-u,Reset_Handler \
-Wl,-Map=./project.map -Wl,--gc-sections -Wl,--defsym=malloc_getpagesize_P=0x80
as_flag = -c -mthumb -mcpu=cortex-m3 -g -Wa,--warn -o
src = $(shell find ./ -name '*.c')
obj = $(src:%.c=%.o)
all:$(obj)
$(CC) $(as_flag) ./drv/core/src/startup_stm32f10x_hd.o ./drv/core/src/startup_stm32f10x_hd.s
$(CC) $(obj) ./drv/core/src/startup_stm32f10x_hd.o -T ./stm32f103zet6_flash.ld -o ./output/$(TARGET).elf $(target_flag)
$(OBJCOPY) ./output/$(TARGET).elf ./output/$(TARGET).bin -Obinary
$(OBJCOPY) ./output/$(TARGET).elf ./output/$(TARGET).hex -Oihex
$(OBJCOPY) ./output/$(TARGET).elf ./output/$(TARGET).srec -Osrec
$(obj):%.o:%.c
$(CC) -c $(obj_flag) -o $@ $<
project:
mkdir output
clean:
rm -f $(shell find ./ -name '*.o')
rm -f $(shell find ./ -name '*.d')
rm -f $(shell find ./ -name '*.map')
rm -f $(shell find ./ -name '*.elf')
rm -f $(shell find ./ -name '*.bin')
rm -f $(shell find ./ -name '*.hex')
完成stm32f103zet6_flash.ld和makefile文件的编写后,在app/src下添加test_main.c文件,并在文件中添加如下内容:
/******************************************************************************
*
* @FileName : test_main.c
*
* @Author : WeiShuangbo
*
* @Version : 1.0
*
* @Date : 2020-04-12
*
* @Description : The main file with led_test
*
******************************************************************************/
int main(void)
{
return 0;
}
完成以上工作后,打开Linux子系统终端,进入led_test目录。Linux子系统访问Windnows下的分区是通过/mnt/分区盘符/….来实现的。
在led_test根目录下输入make指令,出现如下问题:
再一次执行make指令。
下一步,我们添加让led灯珠交替闪烁的代码。
在drv/user./src文件夹下增加gpio的驱动文件drv_gpio.c,在drv/user/inc文件夹下增加drv_gpio.h头文件。
drv_gpio.c文件的内容如下:
/******************************************************************************
*
* @FileName : drv_gpio.c
*
* @Author : WeiShuangbo
*
* @Version : 1.0
*
* @Date : 2020-04-12
*
* @Description : The Drvice File of the GPIO with led_test
*
*
******************************************************************************/
#include "drv_gpio.h"
#include "stm32f10x.h"
/*******************************************************
*
* Function name : drv_gpio_config_led
* Description : config the gpio to led
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_gpio_config_led(void)
{
RCC->APB2ENR |=RCC_APB2ENR_IOPEEN;
GPIOE->CRL |= GPIO_CRL_MODE2 | GPIO_CRL_MODE3 ;
}
/*******************************************************
*
* Function name : drv_gpio_output_led1_high
* Description : set the gpio is high for led1
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_gpio_output_led1_high(void)
{
GPIOE->ODR|= GPIO_ODR_ODR2;
}
/*******************************************************
*
* Function name : drv_gpio_output_led1_low
* Description : set the gpio is low for led1
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_gpio_output_led1_low(void)
{
GPIOE->ODR&= ~(GPIO_ODR_ODR2);
}
/*******************************************************
*
* Function name : drv_gpio_get_led1
* Description : get the gpio level for led1
* Parameter : NULL
* Return : NULL
**********************************************************/
uint8_t drv_gpio_get_led1(void)
{
return (GPIOE->ODR & GPIO_ODR_ODR2);
}
/*******************************************************
*
* Function name : drv_gpio_output_led2_high
* Description : set the gpio is high for led2
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_gpio_output_led2_high(void)
{
GPIOE->ODR|= GPIO_ODR_ODR3;
}
/*******************************************************
*
* Function name : drv_gpio_output_led2_low
* Description : set the gpio is low for led2
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_gpio_output_led2_low(void)
{
GPIOE->ODR&= ~(GPIO_ODR_ODR3);
}
/*******************************************************
*
* Function name : drv_gpio_get_led2
* Description : get the gpio level for led2
* Parameter : NULL
* Return : NULL
**********************************************************/
uint8_t drv_gpio_get_led2(void)
{
return (GPIOE->ODR & GPIO_ODR_ODR3);
}
drv_gpio.h文件的内容如下:
/******************************************************************************
*
* @FileName : drv_gpio.h
*
* @Author : WeiShuangbo
*
* @Version : 1.0
*
* @Date : 2020-04-12
*
* @Description : The Drvice Header File of the GPIO with led_test
*
*
******************************************************************************/
#ifndef __DRV_GPIO_H__
#define __DRV_GPIO_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
// led config
void drv_gpio_config_led(void);
void drv_gpio_output_led1_high(void);
void drv_gpio_output_led1_low(void);
uint8_t drv_gpio_get_led1(void);
void drv_gpio_output_led2_high(void);
void drv_gpio_output_led2_low(void);
uint8_t drv_gpio_get_led1(void);
#ifdef __cplusplus
}
#endif
#endif
在drv/user./src文件夹下增加led的驱动文件drv_led.c,在drv/user/inc文件夹下增加drv_led.h头文件。
drv_led.c文件的内容如下:
/******************************************************************************
*
* @FileName : drv_led.c
*
* @Author : WeiShuangbo
*
* @Version : 1.0
*
* @Date : 2020-04-13
*
* @Description : The Drvice File of the LED with led_test
*
*
******************************************************************************/
#include "drv_led.h"
#include "drv_gpio.h"
/*******************************************************
*
* Function name : drv_led_init
* Description : Init the led
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_led_init(void)
{
drv_gpio_config_led();
}
/*******************************************************
*
* Function name : drv_led1_on
* Description : turn on the led1
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_led1_on(void)
{
drv_gpio_output_led1_low();
}
/*******************************************************
*
* Function name : drv_led1_off
* Description : turn off the led1
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_led1_off(void)
{
drv_gpio_output_led1_high();
}
/*******************************************************
*
* Function name : drv_led1_statu_change
* Description : change the statu for led1
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_led1_statu_change(void)
{
if(drv_gpio_get_led1())
{
drv_gpio_output_led1_low();
}
else
{
drv_gpio_output_led1_high();
}
}
/*******************************************************
*
* Function name : drv_led2_on
* Description : turn on the led2
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_led2_on(void)
{
drv_gpio_output_led2_low();
}
/*******************************************************
*
* Function name : drv_led2_off
* Description : turn off the led2
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_led2_off(void)
{
drv_gpio_output_led2_high();
}
/*******************************************************
*
* Function name : drv_led2_statu_change
* Description : change the statu for led2
* Parameter : NULL
* Return : NULL
**********************************************************/
void drv_led2_statu_change(void)
{
if(drv_gpio_get_led2())
{
drv_gpio_output_led2_low();
}
else
{
drv_gpio_output_led2_high();
}
}
drv_led.h头文件的内容如下:
/******************************************************************************
*
* @FileName : drv_led.h
*
* @Author : WeiShuangbo
*
* @Version : 1.0
*
* @Date : 2020-04-13
*
* @Description : The Drvice Header File of the LED with led_test
*
*
******************************************************************************/
#ifndef __DRV_LED_H__
#define __DRV_LED_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
// led config
void drv_led_init(void);
void drv_led1_on(void);
void drv_led1_off(void);
void drv_led1_statu_change(void);
void drv_led2_on(void);
void drv_led2_off(void);
void drv_led2_statu_change(void);
#ifdef __cplusplus
}
#endif
#endif
led_test实例仅是为了验证搭建的环境是否可以正常进行STM32开发,所以为了简便,就直接在app层上调用驱动层中的函数了。在实际项目中,最好的做法是app层通过hal层间接调用驱动层的函数和方法。
修改test_main.c文件中的内容:
/******************************************************************************
*
* @FileName : test_main.c
*
* @Author : WeiShuangbo
*
* @Version : 1.0
*
* @Date : 2020-04-12
*
* @Description : The main file with led_test
*
******************************************************************************/
#include <stdint.h>
#include "drv_led.h"
void delay(uint32_t ms)
{
uint32_t i = ms;
while(i--)
{
;
}
}
int main(void)
{
drv_led_init();
drv_led1_on();
drv_led2_off();
while(1)
{
delay(1000000);
drv_led1_statu_change();
drv_led2_statu_change();
}
return 0;
}
在Linux子系统中运行make指令,生成可烧录的镜像文件。
将镜像文件通过J-FLASH烧录到芯片上,运行结果如下: