本节课我们以stm32为基础,keil5为开发平台,进行ISP(在线系统编程).
//注:我们通常用stm32做一些开发应用,所以用的基本上是顶层函数,对于那些用来设置核内寄存器和外设的地址的底层函数,只需要了解即可。除非你真的需要对ARM的架构精通,但首先你必须有很好的汇编和c/c++基础。
注:库是架设(位于)在寄存器和用户驱动程序之间的代码,向上给用户提供配置底层寄存器的接口,向下得到用户的配置信息后,对底层寄存器进行配置。因为库的存在,用户不需要直接按位配置底层寄存器了,只需配置库提供的函数接口,对寄存器的操作,交给库函数来完成。
一.基本C程序
1.编程步骤
对于keil的基本配置,创建工程。
添加4个组,分别是startup+CMIS+FWib+output+listing+user然后向这四个组里面添加相应的.c文件和.h文件.
对各个源文件和头文件的解释:
A.startup_stm32f10x_hd.s—->startup
相对于ARM上一代的主流ARM7/ARM9内核架构,新一代Cortex内核架构的启动方式有了比较大的变化。ARM7/ARM9内核的控制器在复位后,CPU会从存储空间的绝对地址0x000000取出第一条指令执行复位中断服务程序的方式启动,即固定了复位后的起始地址为0x000000(PC = 0x000000)同时中断向量表的位置并不是固定的。而Cortex-M3内核则正好相反,有3种情况:
1、 SRAM启动模式:通过boot引脚设置可以将中断向量表定位于SRAM区,即起始地址为0x2000000,同时复位后PC指针位于0x2000000处;
2、 用户闪存启动模式:通过boot引脚设置可以将中断向量表定位于FLASH区,即起始地址为0x8000000,同时复位后PC指针位于0x8000000处;
3、 系统存储器启动模式:即系统从芯片内部一块特定的区域启动,通过boot引脚设置可以将中断向量表定位于内置Bootloader区(就这个区域),本文不对这种情况做论述;
而Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。(这句话啥意思呢?向量表从 FLASH 的 0 地址开始放置,以 4 个字节为一个单位,地址 0 存放的是栈顶地址,0X04 存放的是复位程序的地址)
对比ARM7/ARM9内核,Cortex-M3内核则是固定了中断向量表的位置而起始地址是可变化的。
上文引自http://www.amobbs.com/thread-5462931-1-1.html
见http://blog.csdn.net/wqx521/article/details/50925553
或http://www.openedv.com/posts/list/5903.htm
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel
//DCD给相应的中断向量分配内存
外部中断向量表存放的是中断向量(存放中断函数的地址),我们要根据外设的这个中断向量写入我们的中断函数。
B. core_cm3.c—->CMIS.system_stm32f10x.c—->CMIS
core_cm3.c:
CMSIS有三个层:核内外设访问层Core Peripheral Access Layer(CPAL),中间件访问层Middleware Access Layer(MWAL),设备访问层(Device Peripheral Access Layer)。
CPAL用于访问内核的寄存器和组件,如NVIC,调试系统等。该层是由ARM实现的。MWAL用于对中间件的访问,现在该层还未实现。(也不知道所谓的中间件是什么东西)。DPAL用于定义一些硬件寄存器的地址和一些外设访问函数,由芯片制造商实现。
CPAL层的实现就是core_cm3.c文件.
DPAL层的实现就是system_stm32f10x.c文件(似乎还应该加上外设的函数库)。
上引自http://www.cnblogs.com/king-77024128/articles/2512997.html
下面介绍system_stm32f10x.c:
由st公司提供,该文件的功能是设置系统的时钟和总线时钟。
C.stm32f10x_conf.h—->user.stm32f10x_lib.h—->user.
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32F10x_CONF_H
#define __STM32F10x_CONF_H
/* Includes ------------------------------------------------------------------*/
/* Uncomment/Comment the line below to enable/disable peripheral header file inclusion */
//#include "stm32f10x_adc.h"
//#include "stm32f10x_bkp.h"
//#include "stm32f10x_can.h"
//#include "stm32f10x_cec.h"
//#include "stm32f10x_crc.h"
//#include "stm32f10x_dac.h"
//#include "stm32f10x_dbgmcu.h"
//#include "stm32f10x_dma.h"
//#include "stm32f10x_exti.h"
//#include "stm32f10x_flash.h"
//#include "stm32f10x_fsmc.h"
#include "stm32f10x_gpio.h"
//#include "stm32f10x_i2c.h"
//#include "stm32f10x_iwdg.h"
//#include "stm32f10x_pwr.h"
#include "stm32f10x_rcc.h"
//#include "stm32f10x_rtc.h"
//#include "stm32f10x_sdio.h"
//#include "stm32f10x_spi.h"
//#include "stm32f10x_tim.h"
//#include "stm32f10x_usart.h"
//#include "stm32f10x_wwdg.h"
#include "misc.h" /* High level functions for NVIC and SysTick (add-on to CMSIS functions) */
stm32f10x_conf.h中包含了所有外设的头文件,供用户根据自己的需要选择使用,比如点亮LED,用到stm32f10x_gpio.h,stm32f10x_rcc.h.
只需要将其前面的注释//去掉即可。(见尾部添加USE_STDPERIPH_DRIVER的说明)
stm32f10x_lib.h:2.0版本的ST库函数头文件,在我们开发板老版本的寄存器版本例程上面有用到,新版本例程一律没用了,可忽略。
D.stm32f10x_it.h—->user.stm32f10x.h—->user.
stm32f10x_it.h: 相比较老版,系统只给出了内部中断函数(含内部异常中断默认函数,一般是while(1)无限循环的),所以说如果用到外设中断的话,要自己加上去。中断入口函数都是固件库已经定义好的,你可以到启动文件里看那个中断向量表,并找到对应的函数.如串口1的中断函数就是void USART1_IRQHandler().(注:中断函数不需要事先声明)
stm32f10x_it.h:这个就不多说了,给出了寄存器映射地址。
第一种:查询方式
下面开始进入正题:
开始:
A.系统上电初始化
keil5默认系统外部晶振为8MHz,这里可以在options里面改动。
若不是,比如选择12MHz的XTAL,则都要及时修改。
打开stm32f10x.h
#if !defined HSE_VALUE //如果HSE_VALUE未定义
#ifdef STM32F10X_CL //若是通信型产品
#define HSE_VALUE ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */
#else
#define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
#endif /* STM32F10X_CL */
#endif /* HSE_VALUE */
再打开system_stm32f10x.h,这里有一个系统时钟的宏定义。
#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
#define SYSCLK_FREQ_24MHz 24000000
#else
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz 24000000 */
/* #define SYSCLK_FREQ_36MHz 36000000 */
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
#define SYSCLK_FREQ_72MHz 72000000 //也就是103系列能跑到的最大值72M(系统时钟对应的最大晶振频率)
#endif
当系统复位后,首先执行Systeminit函数,再进入Reset Handler()函数,这个函数调用了main(),所以顺理成章的进入main()函数。
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
其中void Systeminit()的作用是先将配置时钟相关的寄存器都复位为默认值,再调用SetSysClock()设置时钟。
如下:
* @brief CMSIS Cortex-M3 Device Peripheral Access Layer System Source File.
*
* 1. This file provides two functions and one global variable to be called from
* user application:
* - SystemInit(): Setups the system clock (System clock source, PLL Multiplier
* factors, AHB/APBx prescalers and Flash settings).
* This function is called at startup just after reset and
* before branch to main program. This call is made inside
* the "startup_stm32f10x_xx.s" file.
*
B.时钟参数配置
说实话这个内容真特么多,调用各种时钟配置函数,一定要看图看表看库才能理解。
/***********************rcc.c************************/
#include "rcc.h"
void RCC_Configuration()
{
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus=RCC_WaitForHSEStartUp();
if(HSEStartUpStatus==SUCCESS)
{
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_ADCCLKConfig(RCC_PCLK2_Div4);
FLASH_SetLatency(FLASH_Latency_2);
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET)
{}
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource()!=0x08)
{}
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//使能GPIOA的时钟
}
}
/***********************rcc.h************************/
#ifndef _RCC_H
#define _RCC_H
#include "stm32f10x_rcc.h"
#include "stm32f10x_flash.h"
void RCC_Configuration(void);
#endif
内部时钟由RC振荡器产生,其中HSI为8MHz,LSI为40kHz。
外部时钟以外部晶振作为时钟源,HSE可取4~16MHz,LSE为32.768kHz.
其中在芯片刚上电的时候,由于内部时钟起振较快,所以默认采用内部高速时钟HSI(复位时依然是这个)。而外部时钟是由外部晶振输入,在精度和稳定性上面都有很大优势,所以我们上电之后再通过RCC_Configuetion(),采用外部时钟信号。
分频是因为stm32有高速外设和低速外设,各种外设的工作频率不一样,所以要把高速外设和低速外设分开管理,每个外设都有相应的时钟使能,所以用到哪个开哪个。
AHB常和DMA连接,
APB1总线连接高速外设,APB2总线连接系统外设和中断控制。
C.引脚参数配置
/********************gpio.c*************************/
#include "gpio.h"
void GPIOConfiguration()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
/********************gpio.h*************************/
#ifndef _GPIO_H //如果宏定义未定义,则...
#define _GPIO_H
#include "stm32f10x_gpio.h"
void GPIO_Configuration(void);
#endif
看库文件能明白
D.查询点亮
#include "stm32f10x.h"
#include "gpio.h"
#include "rcc.h"
extern void Delay(vu32 nCount)
{
for(;nCount>0;nCount--)
;
}
void main()
{
#ifdef DEBUG
debug();
#endif
RCC_Configuration();
GPIO_Configuration();
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_2)==0x00)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction) (1));
GPIO_WriteBit(GPIOA,GPIO_Pin_1,(BitAction) (1));
Delay(1000);
}
else
{
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction) (0));
GPIO_WriteBit(GPIOA,GPIO_Pin_1,(BitAction) (0));
Delay(1000);
}
}
}
对于开头的条件编译指令:
#ifdef DEBUG
debug();
#endif
在工程设置里有一些设置会对该工程自动产生一系列的宏,用以控制程序的编译和运行。如果你把代码夹在#ifdef DEBUG 和对应的 #endif 中间,那么这段代码只有在调试(DEBUG)下才会被编译。也就是说,如果你在RELEASE模式下,这些代码根本就不会存在于你的最终代码里头。
注:在keil配置中应该
1)在c/c++栏的处理器标识定义里面添加USE_STDPERIPH_DRIVER和STM32F10X_MD.其中:
A.USE_STDPERIPH_DRIVER:使用标准外设文件.在stm32f10x.h中查找可得
#ifdef USE_STDPERIPH_DRIVER
#include "stm32f10x_conf.h"
#endif
若添加USE这样的操作,可以使用外设头文件,否则就要自己手动添加了.
B.STM32F10X_MD:根据所用芯片FLASH大小选择宏定义,在stm32f10x.h中查找可得
#if !defined (STM32F10X_LD) && !defined (STM32F10X_LD_VL)\&& !defined (STM32F10X_MD) && !defined (STM32F10X_MD_VL)\
&& !defined (STM32F10X_HD) && !defined (STM32F10X_HD_VL)&& !defined (STM32F10X_XL) && !defined (STM32F10X_CL)
/* #define STM32F10X_LD */ /*!< STM32F10X_LD: STM32 Low density devices */
/* #define STM32F10X_LD_VL */ /*!< STM32F10X_LD_VL: STM32 Low density Value Line devices */
/* #define STM32F10X_MD */ /*!< STM32F10X_MD: STM32 Medium density devices */
/* #define STM32F10X_MD_VL */ /*!< STM32F10X_MD_VL: STM32 Medium density Value Line devices */
/* #define STM32F10X_HD */ /*!< STM32F10X_HD: STM32 High density devices */
/* #define STM32F10X_HD_VL */ /*!< STM32F10X_HD_VL: STM32 High density value line devices */
/* #define STM32F10X_XL */ /*!< STM32F10X_XL: STM32 XL-density devices */
/* #define STM32F10X_CL */ /*!< STM32F10X_CL: STM32 Connectivity line devices */
#endif
2)include paths:凡是所用到的头文件一律添加到相应路径。