STM32库函数编程、KeliMDK、stm32f103zet6
catalogue
0. Cortex-M3地址空间
1. 基于标准外设库的软件开发
2. 基于固件库实现串⼝输出(发送)程序
3. 红外接收实验
4. 深⼊分析流⽔灯例程
5. GPIO再举例之按键实验
6. 串⼝通信(USART)
7.  库函数开发通⽤流程⼩结
8. DMA传输⽅式
9. STM32 ADC
10. SysTick(系统滴答定时器)
11. STM32定时器
0. Cortex-M3地址空间
0x1: MDK中三种linker之间的区别
1. 采⽤Target对话框中的RAM和ROM地址
采⽤此⽅式,需在Linker选项卡中勾选Use Memory Layout from Target Dialog选项(选中这⼀项实际上是默认在Target中对Flash和RAM的地址配置,编译链接时会产⽣⼀个默认的脚本⽂件),并且在Target中设置好RAM、ROM地址。MDK会根据Target选项中设定的RAM和ROM地址⾃动加载⽣成⼀个加载⽂件。最后链接器会根据此⽂件中的信息对⽬标⽂件进⾏链接,⽣成axf镜像⽂件
STM32是通过同⼀个连续的地址空间来寻址⽚上ROM和⽚外RAM
2. 直接通过Linker选项卡中的R/O  Base和R/W Base来设定链接信息
接器最后可根据此处指定的地址信息进⾏链接,链接的⽂件应该是顺序存放了,最多RO和RW分开。此时需要注意的是应将Use  Memory  Layout  from Target  Diaglog前的勾去掉,且保证Scatter File⼀栏中未包含分散加载⽂件,并且要在Misc controls中设定镜像⽂件的⼊⼝点
3. 直接采⽤分散加载⽂件
Relevant Link:
blog.csdn/mybelief321/article/details/8947424
www.openedv/thread-17087-1-1.html
1. 基于标准外设库的软件开发
0x1: STM32标准外设库概述
STM32标准外设库之前的版本也称固件函数库或简称固件库(即操作⽚外固件的代码集合),是⼀个固件函数包,它由程序、数据结构和宏组成,包括了微控制器所有外设的性能特征。该函数库还包括每⼀个外设的驱动描述和应⽤实例,为开发者访问底层硬件提供了⼀个中间API,通过使⽤固件函数库,⽆需深⼊掌握底层硬件细节,开发者就可以轻松应⽤每⼀个外设。因此,使⽤固态函数库可以⼤⼤减少⽤户
的程序编写时间,进⽽降低开发成本。每个外设驱动都由⼀组函数组成,这组函数覆盖了该外设所有功能。每个器件的开发都由⼀个通⽤API (application programming interface 应⽤编程界⾯)驱动,API对该驱动程序的结构,函数和参数名称都进⾏了标准化,顺便提⼀句,arduino之所以⼊门容易开发简单就是因为我们很多时候是要"⾯向固件库编程",很多复杂的外设操作都通过简单的API调⽤就完成了
ST公司2007年10⽉发布了V1.0版本的固件库,MDK ARM3.22之前的版本均⽀持该库。2008年6⽉发布了V2.0版的固件库,从2008年9⽉推出的MDK ARM3.23版本⾄今均使⽤V2.0版本的固件库。V3.0以后的版本相对之前的版本改动较⼤
0x2: 使⽤标准外设库开发的优势
使⽤标准外设库进⾏开发最⼤的优势就在于可以使开发者不⽤深⼊了解底层硬件细节就可以灵活规范的使⽤每⼀个外设。标准外设库覆盖了从GPIO到定时器,再到CAN、I2C、SPI、UART和ADC等等的所有标准外设。对应的C源代码只是⽤了最基本的C编程的知识,所有代码经过严格测试,易于理解和使⽤,并且配有完整的⽂档,⾮常⽅便进⾏⼆次开发和应⽤
0x3: STM32F10XXX标准外设库结构与⽂件描述
表 5‑4 STM32F10XXX V3.4标准外设库⽂件夹描述
STM32F10x_StdPeriph_Lib_V3.4.0_htmresc本⽂件夹包含了所有的html页⾯资源
Libraries CMSIS微控制器软件接⼝标准
(CMSIS:Cortex
Microcontroller Software
Interface Standard)
是 Cortex-M 处理器系列的与供应商⽆关的软件抽象层。使⽤
CMSIS,可以为处理器和外设实现⼀致且简单的软件接⼝,从⽽
简化软件的重⽤
STM32F10x_StdPeriph_Driver inc标准外设库驱动头⽂件
src标准外设库驱动源⽂件
Project Examples标准外设库驱动的完整例程Template MDK-ARM KEIL RVMDK的项⽬模板⽰例
RIDE Raisonance RIDE的项⽬模板⽰例
EWARM IAR EWARM的项⽬模板⽰例
Utilities STM3210-EVAL本⽂件夹包含了⽤于STM3210B-EVAL和STM3210E-EVAL评估板
的专⽤驱动
标准外设库的第⼀部分是CMSIS 和STM32F10x_StdPeriph_Driver,CMSIS 是独⽴于供应商的Cortex-M 处理器系列硬件抽象层,为芯⽚⼚商和中间件供应商提供了简单的处理器软件接⼝,简化了软件复⽤⼯作,降低了Cortex-M 上操作系统的移植难度,并减少了新⼊门的微控制器开发者的学习曲线和新产品的上市时间。
STM32F10x_StdPeriph_Driver则包括了分别对应包括了所有外设对应驱动函数,这些驱动函数均使⽤C语⾔编写,并提供了统⼀的易于调⽤的函数接⼝,供开发者使⽤。Project⽂件夹中则包括了ST官⽅的所有例程和基于不同编译器的项⽬模板,这些例程是学习和使⽤STM32的重要参考。Utilities包含了相关评估板的⽰例程序和驱动函数,供使⽤官⽅评估板的开发者使⽤,很多驱动函数同样可以作为学习的重要参考
0x4: STM32F10xxx标准外设库体系结构
⽂件名功能描述具体功能说明
core_cm3.h core_cm3.c Cortex-M3内核及
其设备⽂件
访问Cortex-M3内核及其设备:
NVIC,SysTick等
访问Cortex-M3的CPU寄存器和内核外设
的函数
stm32f10x.h微控制器专⽤头⽂
件这个⽂件包含了STM32F10x全系列所有外设寄存器的定义(寄存器的基地址和布局)、位定义、中断向量表、存储空间的地址映射等
函数SystemInit,⽤来初始化微控制器
system_stm32f10x.h system_stm32f10x.c 微控制器专⽤系统
⽂件
函数Sysem_ExtMemCtl,⽤来配置外部
存储器控制器。它位于⽂件
startup_stm32f10x_xx.s /.c,在跳转到
main前调⽤
SystemFrequncy,该值代表系统时钟频
startup_stm32f10x_Xd.s编译器启动代码微控制器专⽤的中断处理程序列表(与头⽂件⼀致)
弱定义(Weak)的中断处理程序默认函数(可以被⽤户代码覆盖) 该⽂件是与编译器相关的
stm32f10x_conf.h固件库配置⽂件通过更改包含的外设头⽂件来选择固件库所使⽤的外设,在新建程序和进⾏功能变更之前应当⾸先修改对应的配置。
stm32f10x_it.h
stm32f10x_it.c 外设中断函数⽂件
⽤户可以相应的加⼊⾃⼰的中断程序的代
码,对于指向同⼀个中断向量的多个不同
中断请求,⽤户可以通过判断外设的中断
stm32f10x_ppp.h
stm32f10x_ppp.c
外设驱动函数⽂件
Application.c⽤户⽂件
0x5: 基于CMSIS标准的软件架构
对于ARM公司来说,⼀个ARM内核往往会授权给多个⼚家,⽣产种类繁多的产品,如果没有⼀个通⽤的软件接⼝标准,那么当开发者在使⽤不同⼚家的芯⽚时将极⼤的增加了软件开发成本,因此,ARM与Atmel、IAR、Keil、hami-nary Micro、Micrium、NXP、SEGGER和ST等诸多芯⽚和软件⼚商合作,将
所有Cortex芯⽚⼚商产品的软件接⼝标准化,制定了CMSIS标准。此举意在降低软件开发成本,尤其针对新设备项⽬开发,或者将已有软件移植到其他芯⽚⼚商提供的基于Cortex处理器的微控制器的情况。有了该标准,芯⽚⼚商就能够将他们的资源专注于产品外设特性的差异化,并且消除对微控制器进⾏编程时需要维持的不同的、互相不兼容的标准的需求,从⽽达到降低开发成本的⽬的
基于CMSIS标准的软件架构(或者叫固件库架构)主要分为以下4层
1. ⽤户应⽤层
2. 操作系统及中间件接⼝层
3. CMSIS层: CMSIS层起着承上启下的作⽤
1) ⼀⽅⾯该层对硬件寄存器层进⾏统⼀实现,屏蔽了不同⼚商对Cortex-M系列微处理器核内外设寄存器的不同定义
2) 另⼀⽅⾯⼜向上层的操作系统及中间件接⼝层和应⽤层提供接⼝,简化了应⽤程序开发难度,使开发⼈员能够在完全透明的情况下进⾏应⽤程序开发。也正是如此,CMSIS层的实现相对复杂
4. 硬件寄存器层
0x6: 使⽤⽅式
在实际开发过程中,根据应⽤程序的需要,可以采取2种⽅法使⽤标准外设库(StdPeriph_Lib)
1. 使⽤外设驱动: 这时应⽤程序开发基于外设驱动的API(应⽤编程接⼝)。⽤户只需要配置⽂件"stm32f10x_conf.h",并使⽤相应的⽂件例如"stm32f10x_ppp.h/.c"即可
2. 不使⽤外设驱动: 这时应⽤程序开发基于外设的寄存器结构和位定义⽂件(需要了解单⽚机的⼤量硬件、引脚细节)
标准外设库(StdPeriph_Lib)⽀持STM32F10xxx系列全部成员:⼤容量,中容量和⼩容量产品,实际开发中根据使⽤的STM32产品具体型号,⽤户可以通过⽂件"stm32f10x.h"中的预处理define或者通过开发环境中的全局设置来配置标准外设库(StdPeriph_Lib),⼀个define对应⼀个产品系列
STM32F10x_LD:STM32⼩容量产品
STM32F10x_MD:STM32中容量产品
STM32F10x_HD:STM32⼤容量产品
在库⽂件中这些define的具体作⽤范围是
1. ⽂件"stm3210f.h"中的中断IRQ定义
2. 启动⽂件中的向量表,⼩容量,中容量,⼤容量产品各有⼀个启动⽂件
3. 外设存储器映像和寄存器物理地址
4. 产品设置: 外部晶振(HSE)的值等
5. 系统配置函数
因此通过宏定义这种⽅式,可以使标准外设库适⽤于不同系列的产品,同时也⽅便与不同产品之间的软件移植,极⼤的⽅便了软件的开发
0x7: 命名规范
标准外设库中的主要外设均采⽤了缩写的形式,通过这些缩写可以很容易的辨认对应的外设。
缩写外设/单元
ADC模数转换器
BKP备份寄存器
CAN控制器局域⽹模块
CEC
CRC CRC计算单元
DAC数模转换器
DBGMCU调试⽀持
DMA直接内存存取控制器
EXTI外部中断事件控制器
FLASH闪存存储器
FSMC灵活的静态存储器控制器
GPIO通⽤输⼊输出
I2C I2C接⼝
IWDG独⽴看门狗
PWR电源/功耗控制
RCC复位与时钟控制器
RTC实时时钟
SDIO SDIO接⼝
SPI串⾏外设接⼝
TIM定时器
USART通⽤同步/异步收发器
WWDG窗⼝看门狗
标准外设库遵从以下命名规则 PPP表⽰任⼀外设缩写,例如:ADC。源程序⽂件和头⽂件命名都以“stm32f10x_”作为开头,例如:stm32f10x_conf.h
外设函数的命名以该外设的缩写加下划线为开头。每个单词的第⼀个字母都由英⽂字母⼤写书写,例如:SPI_SendData。在函数名中,只允许存在⼀个下划线,⽤以分隔外设缩写和函数名的其它部分。对于函数命名
Relevant Link:
baike.baidu/link?url=X3kL65ER2yug2m-_XhgXTggAd7uH7VCnyhdaJ2ddxYt-Nj8oqB46tWDhNngqyrPnuAzzs8wJe56NzIJi-6zWWa
wwwblogs/emouse/archive/2011/11/29/2268441.html
2. 基于固件库实现串⼝输出(发送)程序
0x1: 关键库函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
发送函数,它告诉我们很重要的⼀点,那就是串⼝是以"位"来传输的
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
⽤它来得知串⼝的状态
0x2: 参考printf函数编写串⼝发送函数(⾃定义⼀个完全脱库标准C库的新函数)
#include <stdio.h>
#include <stdarg.h>
/*************************************************
* Function Name  : USART1_printf
* Description    :
* Input          :
* Output        : NONE
* Return        : NONE
*************************************************/
void USART1_printf (char *fmt, ...)
{
char buffer[CMD_BUFFER_LEN+1];
u8 i = 0;
va_list arg_ptr;
va_start(arg_ptr, fmt);
vsnprintf(buffer, CMD_BUFFER_LEN+1, fmt, arg_ptr);
while ((i < CMD_BUFFER_LEN) && buffer[i])
{
USART_SendData(USART1, (u8) buffer[i++]);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
va_end(arg_ptr);
}
/*************************************************
* Function Name  : USART1_SendData
* Description    : 串⼝1发送
* Input          : char *Buffer
* Output        : NONE
* Return        : NONE
*************************************************/
void USART1_SendData(char *Buffer)
{
u8 Counter = 0;
while( (Counter == 0) || (Buffer[Counter] != 0) ) //条件...
{
USART_SendData(USART1, Buffer[Counter++]);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
}
0x3: Code
/*----------------------------------------------------------------------------
* Name:    Hello.c
* Purpose: Hello World Example
* Note(s):
*----------------------------------------------------------------------------
* This file is part of the uVision/ARM development tools.
* This software may only be used under the terms of a valid, current,
* end user licence from KEIL for a compatible version of KEIL software
* development tools. Nothing else gives you the right to use this software.
*
* This software is supplied "AS IS" without warranties of any kind.
*
* Copyright (c) 2012 Keil - An ARM Company. All rights reserved.
*----------------------------------------------------------------------------*/
#include <stdio.h>              /* prototype declarations for I/O functions  */
#include <stm32f10x.h>          /* STM32F10x definitions                    */
#include <stm32f10x_usart.h>
#include <stdarg.h>
#include <string.h>
#define CMD_BUFFER_LEN 64u
static void delayms(int cnt){
int i;
while(cnt--)
for (i=0; i<7333; i++);
}
void USART2_printf (char *fmt, ...)
{
char buffer[CMD_BUFFER_LEN+1] = {0};
u8 i = 0;
int len = 0;
va_list arg_ptr;
memset(buffer, '\x00', CMD_BUFFER_LEN+1);
len = strlen(fmt) + 1;
va_start(arg_ptr, fmt);
vsnprintf(buffer, len, fmt, arg_ptr);
while ((i < len) && buffer[i] != '\x00') {
USART_SendData(USART1, (u8) buffer[i++]);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
;
}
va_end(arg_ptr);
}
extern void SER_Init(void);                                  /* see Serial.c */
/*----------------------------------------------------------------------------
main program
*----------------------------------------------------------------------------*/
int main (void)  {              /* execution starts here                    */
SER_Init ();                  /* initialize the serial interface          */
USART2_printf ("Hello World\n");      /* the 'printf' function call                */
while (1) {                    /* An embedded program does not stop and    */
USART2_printf ("Hello World\n");  /* ... *//* never returns. We use an endless loop.    */        delayms(3000);
}                              /* Replace the dots (...) with your own code.*/
}
使⽤64字节的缓冲数组保存需要发送的数据,然后通过while循环逐byte发送往Terminal终端
为了显⽰⽅便,还可以加上sleep函数
static void delayms(int cnt){
int i;
while(cnt--)
for (i=0; i<7333; i++);
}
需要明⽩的,我们从指令层⾯看while循环,由于指令周期、机器周期都是可时间量化的,因此可以⽤while来实现spin CPU空转,即sleep
Relevant Link:
wwwblogs/mocet/p/stm32f10x_usart_InputOutout_Function_Design_1.html
3. 红外接收实验
⼆进制脉冲码的形式有多种,其中最为常见的是PWM码(脉冲宽度调制码)和PPM码(脉冲位置调制码,脉冲串之间的时间间隔来实现信号调制),如果要开发红外接收设备,⼀定要知道红外发射器(例如遥控器)的编码⽅式和载波频率,我们才可以选取⼀体化红外接收头和制定解码⽅案
/**********************************************************************************
*
***********************************************************************************/
#include "stm32f10x.h"
#include "stm32f10x_exti.h"
//#include "stm8s_beep.h"
#include "stm32f10x_systick.h"
#define    LED1_0      GPIOD->BRR  = 0x00000100
#define    LED2_0      GPIOD->BRR  = 0x00000200
#define    LED3_0      GPIOD->BRR  = 0x00000400
#define    LED4_0      GPIOD->BRR  = 0x00000800
#define    LED1_1      GPIOD->BSRR = 0x00000100
#define    LED2_1      GPIOD->BSRR = 0x00000200
#define    LED3_1      GPIOD->BSRR = 0x00000400
#define    LED4_1      GPIOD->BSRR = 0x00000800
#define    IR_Hongwai_0        GPIOE->BRR  = 0x00000004  //
#define    IR_Hongwai_1        GPIOE->BSRR = 0x00000004  //
#define    IR_Hongwai_x  GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_2)  //
unsigned int TimeByte;
volatile unsigned int IR_Tireafg[4] = {0, 0, 0, 0};
//unsigned int IR_xidwrit[8] = {0, 0, 0, 0, 0, 0 ,0, 0};
/*
*  GPIO
*/
void GPIO_InitStructReadtempCmd(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;    //??GPIO??
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;    //
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;  //50MHZ
GPIO_Init(GPIOE, &GPIO_InitStruct);      //
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;  //
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStruct);
}
/*
*
*/
void RCC_APB2PeriphReadtempyCmd(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  //??GPIOB
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);  //??GPIOE
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);  //??GPIOD
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE);  //??AFIO
}
/*
* Count1 * 10us
*/
unsigned int IR_HongwaiRead_LSB_Cmd()
{
unsigned int Count1 = 0;    //
IR_Hongwai_0;        //
do//
{
Count1++;      //1
Delay_10us(1);      //??10us
}  while(IR_Hongwai_x == 0);  //
return(Count1);        //
}
/*
* Count2 * 10us
*/
unsigned int IR_HongwaiRead_MSB_Cmd()
{
unsigned int Count2 = 0;    //
IR_Hongwai_1;        //
do//
{
Count2++;      //1
Delay_10us(1);      //??10us
}  while(IR_Hongwai_x == 1);  //
return(Count2);
}
/
*
*
*/
int main(void)
{
SystemInit();        //72M??
SYSTICK_InitStructReadTCmd();    //SysTick
RCC_APB2PeriphReadtempyCmd();    //
GPIO_InitStructReadtempCmd();    //GPIO?
EXTI_InitStructReadtempCmd();    //EXTI?
NVIC_InitStructReadtempCmd();    //NVIC?
while(1)
{
}
}
/*
*  EXTI
*/
void EXTI2_IRQHandler(void)
{
unsigned char i = 0;
unsigned int Countline2 = 0;
IR_Hongwai_1;
Countline2 = IR_HongwaiRead_LSB_Cmd();  // 9ms??
if((Countline2 < 286) || (Countline2 > 305)) //??8694us ??9272us {
return;
}
Countline2 = IR_HongwaiRead_MSB_Cmd();  // 4.5ms??
if((Countline2 < 138) || (Countline2 > 155)) //??4195us ??4712us {
return;
}
TimeByte = 0;
for(i = 1; i < 14; i++)
{
TimeByte = TimeByte >> 1;
Countline2 = IR_HongwaiRead_LSB_Cmd();    //0.56 ??
if((Countline2 < 14) || (Countline2 > 22)) //??425us ??851us
{
return;
}
Countline2 = IR_HongwaiRead_MSB_Cmd();    //0.56??
if((Countline2 < 14) || (Countline2 > 65)) //??425us ??1793us  {
return;
}
if( Countline2 > 50)    //1300us?1???0
{
TimeByte |= 0x80;    //?1
}
}
IR_Tireafg[0] = TimeByte;
TimeByte = 0;
for(i = 14; i < 27; i++)
{
TimeByte = TimeByte >> 1;
Countline2 = IR_HongwaiRead_LSB_Cmd();
if((Countline2 < 14) || (Countline2 > 22))
{
return;
}
Countline2 = IR_HongwaiRead_MSB_Cmd();
if((Countline2 < 14) || (Countline2 > 65))
{
return;
}
if( Countline2 > 50)
{
TimeByte |= 0x80;
}
}
IR_Tireafg[1] = TimeByte;
TimeByte = 0;
for(i = 27; i < 35; i++)
{
TimeByte = TimeByte >> 1;
Countline2 = IR_HongwaiRead_LSB_Cmd();
if((Countline2 < 14) || (Countline2 > 22))
{
return;
}
Countline2 = IR_HongwaiRead_MSB_Cmd();
if((Countline2 < 14) && (Countline2 > 65))
库函数printf详解
{
return;
}
if( Countline2 > 50)
{
TimeByte |= 0x80;
}
}
IR_Tireafg[2] = TimeByte;
TimeByte = 0;
for(i = 35; i < 43; i++)
{
TimeByte = TimeByte >> 1;
Countline2 = IR_HongwaiRead_LSB_Cmd();
if((Countline2 < 14) || (Countline2 > 22))
{
return;
}
Countline2 = IR_HongwaiRead_MSB_Cmd();
if((Countline2 < 14) || (Countline2 > 65))
{
return;
}
if( Countline2 > 52)
{
TimeByte |= 0x80;
}
}
IR_Tireafg[3] = TimeByte;
/
/***************************************************************//
/* if(IR_Tireafg[0]!= 0x11A)
{
return;
}  */
//***************************************************************//
/*  do
{
if(IR_Tireafg[2] == ~IR_Tireafg[3])
{
flag = 0;