【STM32开发】win平台开源工具链开发STM32(四)工程结构和新工程建立

摘要

前面的教程介绍了建立工程需要的一些基本工具,本节将介绍一个STM32标准库工程的结构和各个部分的作用,为建立我们自己的工程打基础。然后将以keil MDK工程结构作为基础,建立新的使用arm-gcc的工程。最后将移植xprintf组件,以便在stm32上使用printf功能。


一、stm32标准库工程结构

下面是正点原子标准库串口例程的工程结构,本节将以此工程为例子介绍stm32工程的组织结构和各个文件的功能。

├─CORE
├─HARDWARE
│  ├─KEY
│  └─LED
├─OBJ
├─STM32F10x_FWLib
│  ├─inc
│  └─src
├─SYSTEM
│  ├─delay
│  ├─sys
│  └─usart
└─USER
    └─DebugConfig

CORE文件夹

core_cm3.c
core_cm3.h
startup_stm32f10x_hd.s
  • core_cm3.c,core_cm3.h
    这两个文件提供了cotex-M3内核的寄存器定义和一些内核指令和函数。也包含一些数据类型的定义。

  • startup_stm32f10x_hd.s
    这个是stm32的启动文件。学过c语言的同学都知道c语言一定是从main函数开始运行的,并且函数的调用是通过堆栈来完成的。我们知道stm32启动时读取第一条指令的地址是固定的0x08000000,cortex-M3内核此时依靠的是默认的硬件配置,PC(指令寄存器)和SP(堆栈指针寄存器)都是默认值。那么内核是如何找到main函数并跳转的,片上的基本外设如中断向量和系统时钟树是如何配置的,这些工作就由这个启动文件来完成。
    概括来说启动文件主要完成下面几个方面的工作

    1)、初始化堆栈指针SP
    
    2)、初始化PC指针
    
    3)、初始化中断向量表
    
    4)、配置系统时钟
    
    5)、跳转到main函数
    

简单来说就是为用户配置硬件运行环境,使c程序能够在stm32上正常运行。

HARDWARE文件夹

这个大家应该很熟悉,里面是用户编写的外设驱动

STM32F10x_FWLib文件夹

这里面是标准外设库的寄存器定义和操作函数

SYSTEM文件夹

这个是正点原子自己编写的一些库,便于开发的

USER文件夹

main.c
stm32f10x.h
stm32f10x_conf.h
stm32f10x_it.c
stm32f10x_it.h
system_stm32f10x.c
system_stm32f10x.h
  • main.c
  • stm32f10x.h
    定义了外设寄存器结构体和宏
  • stm32f10x_conf.h
    用于配置外设库,以便兼容不同型号的芯片
  • stm32f10x_it.c,stm32f10x_it.h
    这两个文件是用来实现中断服务函数的,但是将中断服务函数直接写在对应的外设驱动文件里更加利于阅读和移植,因此在下面新建的工程里不使用这两个文件。
  • system_stm32f10x.c,system_stm32f10x.h
    这两个文件里实现了系统时钟的初始化,前面提到startup_stm32f10x_hd.s汇编程序里有调用这个文件实现的函数来做时钟初始化。

二、建立新工程

了解了stm32工程的基本组成后,本节我们将自己构建一个新工程。

1、工程准备

  • 新建一个文件夹,复制一个keil的stm32工程到文件夹下,这里选用的是串口例程。
  • 删除keil的工程文件,简单来说就是除了.c、.h、.s文件,其他的全删了,只留下源码。stm32f10x_it.c,stm32f10x_it.h看个人编程习惯,这里选择删除。
  • 新建一个BUILD文件夹,用于存放编译过程中生成的中间文件
  • 替换启动文件,因为keil的汇编程序和arm-gcc的汇编程序语法不同,不能直接使用,ST官方的固件库会提供这个文件。下载stm32f10x的标准库(一般正点原子或者野火的资料里会有),在下面的路径里找到对应的启动文件,替换自己工程里的启动文件
STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\gcc_ride7
  • 复制链接文件。从标准库以下目录中复制链接文件到工程根目录下,.ld文件用于指导链接器生成hex文件。
STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template\TrueSTUDIO\STM3210C-EVAL
  • 新建一个Makefile文件,不用加任何后缀名。放在工程根目录下。
  • 整理好的工程结构如下
│  Makefile
│  README.TXT
│  stm32_flash.ld
│
├─BUILD
├─CORE
│      core_cm3.c
│      core_cm3.h
│      startup_stm32f10x_hd.s
│
├─HARDWARE
│  ├─KEY
│  │      key.c
│  │      key.h
│  │
│  └─LED
│          led.c
│          led.h
│
├─STM32F10x_FWLib
│  ├─inc
│  │      misc.h
│  │      stm32f10x_adc.h
│  │      stm32f10x_bkp.h
│  │      stm32f10x_can.h
│  │      stm32f10x_cec.h
│  │      stm32f10x_crc.h
│  │      stm32f10x_dac.h
│  │      stm32f10x_dbgmcu.h
│  │      stm32f10x_dma.h
│  │      stm32f10x_exti.h
│  │      stm32f10x_flash.h
│  │      stm32f10x_fsmc.h
│  │      stm32f10x_gpio.h
│  │      stm32f10x_i2c.h
│  │      stm32f10x_iwdg.h
│  │      stm32f10x_pwr.h
│  │      stm32f10x_rcc.h
│  │      stm32f10x_rtc.h
│  │      stm32f10x_sdio.h
│  │      stm32f10x_spi.h
│  │      stm32f10x_tim.h
│  │      stm32f10x_usart.h
│  │      stm32f10x_wwdg.h
│  │
│  └─src
│          misc.c
│          stm32f10x_adc.c
│          stm32f10x_bkp.c
│          stm32f10x_can.c
│          stm32f10x_cec.c
│          stm32f10x_crc.c
│          stm32f10x_dac.c
│          stm32f10x_dbgmcu.c
│          stm32f10x_dma.c
│          stm32f10x_exti.c
│          stm32f10x_flash.c
│          stm32f10x_fsmc.c
│          stm32f10x_gpio.c
│          stm32f10x_i2c.c
│          stm32f10x_iwdg.c
│          stm32f10x_pwr.c
│          stm32f10x_rcc.c
│          stm32f10x_rtc.c
│          stm32f10x_sdio.c
│          stm32f10x_spi.c
│          stm32f10x_tim.c
│          stm32f10x_usart.c
│          stm32f10x_wwdg.c
│
├─SYSTEM
│  ├─delay
│  │      delay.c
│  │      delay.h
│  │
│  ├─sys
│  │      sys.h
│  │
│  └─usart
│          usart.c
│          usart.h
│
└─USER
        main.c
        stm32f10x.h
        stm32f10x_conf.h
        system_stm32f10x.c
        system_stm32f10x.h

  • 修改main.c,此时还有一些兼容问题,所以main函数里只留一个while循环。
int main(void)
 {		
 	while(1)
	{
	}	 
	return 0;
 }

2、Makefile模板

这里直接提供一个Makefile模板并做简要介绍,有兴趣和能力的读者可以自行编写,或者使用cmake生成。

# 假目标,用于清除编译生成的中间文件
.PHONY: clean
#######################################
# binaries
#######################################
#指定使用的编译器
PREFIX = arm-none-eabi-
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S

#指定openocd的配置文件,当更换下载器时需要修改这里的内容
#openocd
OCD_INTERFACE =interface/stlink-v2.cfg 
OCD_TARGET =target/stm32f1x_stlink.cfg
######################################
# target
######################################
# 生成的hex文件名字
TARGET = stm32makefileTemplate

# macros for gcc
# AS defines
AS_DEFS = 

# 宏定义,在keil中也有这两个宏,作用同keil
# C defines
C_DEFS =  \
-DSTM32F10X_HD \
-DUSE_STDPERIPH_DRIVER \

######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og

#######################################
# paths
#######################################
#指定输出路径
# Build path
BUILD_DIR = BUILD
# AS includes
AS_INCLUDES = 


#指定头文件包含路径,作用同keil
# C includes
C_INCLUDES =  \
-ICORE \
-IUSER \
-IHARDWARE/LED \
-ISYSTEM/delay \
-ISYSTEM/sys \
-ISYSTEM/usart \
-ISTM32F10x_FWLib/inc \

######################################
# source
######################################
#指定启动文件
# ASM sources
ASM_SOURCES =  \
CORE/startup_stm32f10x_hd.s \

#指定c文件,需要增减时修改
# C sources
#CORE
C_SOURCES =  \
CORE/core_cm3.c \
#ST
C_SOURCES +=  \
USER/system_stm32f10x.c \
STM32F10x_FWLib/src/stm32f10x_rcc.c \
STM32F10x_FWLib/src/misc.c \
STM32F10x_FWLib/src/stm32f10x_gpio.c \
STM32F10x_FWLib/src/stm32f10x_usart.c \
#USER
C_SOURCES +=  \
USER/main.c \
SYSTEM/delay/delay.c \
SYSTEM/usart/usart.c \
HARDWARE/LED/led.c \

#######################################
# LDFLAGS
#######################################
#指定链接文件
# link script
LDSCRIPT = stm32_flash.ld

#指定使用的链接库和库目录
# libraries
LIBS = -lc -lm -lnosys 
LIBDIR = 

#######################################
# CFLAGS
#######################################
#指定内核架构
# cpu
CPU = -mcpu=cortex-m3

# fpu
# NONE for Cortex-M0/M0+/M3

# float-abi


# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)

# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif

#生成依赖信息
# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"


LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections

# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin

#开始编译
#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))

$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) 
	$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@

$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
	$(AS) -c $(CFLAGS) $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CC) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@

$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(HEX) $< $@
	
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(BIN) $< $@	
	
$(BUILD_DIR):
	mkdir $@		

#######################################
# clean up
#######################################
#清除中间文件
clean:
	del  BUILD\*.* /q
##########################
# write to flash
##########################
#烧录命令,这里集成到了makefile中
write:
	openocd -f $(OCD_INTERFACE)  -f $(OCD_TARGET) -c init -c halt -c "flash write_image erase $(BUILD_DIR)/$(TARGET).hex" -c reset -c shutdown
 

#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)

# *** EOF ***

注意这里没有添加usart.c,因为正点原子实现串口printf用到了keil的microlib,arm-gcc没有这个库,所以要实现串口printf需要使用其他方法。

3、修改core_m3.c

找到core_cm3.c

736行改为:
 __ASMvolatile(“strexb%0,%2,[%1]:"=&r"(result):“r”(addr),“r”(value));
753行改为:
__ASMvolatile(“strexh%0,%2,[%1]:"=&r"(result):“r”(addr),“r”(value));

4、编译烧录

使用vscode打开新建的工程文件夹,在vscode中打开终端,输入make进行编译,如果提示路径错误,请检查终端的工作路径是不是在stm32工程路径下。
最终输出以下结果表明编译成功

arm-none-eabi-size BUILD/stm32makefileTemplate.elf
   text    data     bss     dec     hex filename
   3024      32     504    3560     de8 BUILD/stm32makefileTemplate.elf
arm-none-eabi-objcopy -O ihex BUILD/stm32makefileTemplate.elf BUILD/stm32makefileTemplate.hex
arm-none-eabi-objcopy -O binary -S BUILD/stm32makefileTemplate.elf BUILD/stm32makefileTemplate.bin

三、移植xprintf

上面提到arm-gcc不能像keil那样实现printf,所以这里借用了第三方组件xprintf,详情见官网【嵌入式字符串函数】

  • 下载好xprintf后将xprintf.c和xprintf.h两个文件放在usart文件夹下,并在makefile中添加xprintf.c
  • 修改usart.c去除重定向部分的代码,并实现一个字符输出函数,函数形式为void xxxx(char ch)
  • 在usart.c中包含xprintf.h,并为xprintf绑定字符输出函数,其实就是将xprintf提供的函数指针指向前年实现的字符输出函数。
  • 最终完成的usart.c如下其中xdev_out(uart_putc);将xprintf组件和串口输出绑定。
#include "sys.h"
#include "usart.h"	  
#include "xprintf.h"

//	 
/*
串口1初始化
*/
// 	  
 

//

#if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误   	
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记	  
  
void uart_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
  //USART1_RX	  GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

  USART_Init(USART1, &USART_InitStructure); //初始化串口1
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
  USART_Cmd(USART1, ENABLE);                    //使能串口1 

  //绑定xprintf
		xdev_out(uart_putc);
}

void uart_putc(char c)
{
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) c;
}



void USART1_IRQHandler(void)                	//串口1中断服务程序
	{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 

} 
#endif	


完整工程

这里提供作者移植完成的一个工程

链接:https://pan.baidu.com/s/1MT-l-TtKhtNz358AIyjg2A 
提取码:qamr 
  • stm32f103ze,(HD型号)
  • 移植systick实现的delay函数
  • 移植xprintf,使用方法同printf
  • make指令用于生成hex文件
  • make write指令用于烧录程序,默认使用stlink-v2,swd下载方式
  • make clean用于清除生成的中间文件,强烈建议在改动.h文件而没改动.c文件的情况下,编译之前先执行一遍make clean

【题外】

打开 gcc版的startup_stm32f10x_hd.s,keil版的没有这一段,可以找到这样一段指令

/* Call the clock system intitialization function.*/
  bl  SystemInit   
/* Call the application's entry point.*/
  bl  main
  • bl SystemInit跳转到SystemInit函数,在这个函数中完成了系统时钟的初始化工作。
  • bl main跳转到main函数,从这里开始才真正执行用户编写的代码,这也是c程序从main函数开始执行的原因,在这一句指令之前都在做系统初始化和为c语言运行准备环境,这一句之后才真正进入c语言的世界。
  • 有兴趣的读者可以试着将这里的main换成其他名字,然后记得把main.c里的main函数修改成对应的名字,编译烧录看是否可以正常运行。:)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值