STM32学习了有一阵子了,现做一个简单的总结。
先上一个跑马灯的小程序,本人学习过程中先学习了一些STM32F103的一些基本知识,但是直到接触到真实的程序后开始思索看代码后一些模糊的概念才发现原先学习的基础感念都提到过,不过当时在学习的时候仅仅过了遍脑子没有理解了,故在此我先贴出跑马灯的程序,然后通过程序拓展出STM32编程必知的基础。
什么是跑马灯,本篇的程序实现的功能是板子上的两个小灯来回的闪烁。
下面将贴出寄存器版和库函数版两版源码。
main.c
#include "sys.h"
#include "delay.h"
#include "led.h"
int main(void)
{
delay_init(); //延时函数初始化
LED_Init(); //初始化与LED连接的硬件接口
while(1)
{
LED0=0;
LED1=1;
delay_ms(300); //延时300ms
LED0=1;
LED1=0;
delay_ms(300); //延时300ms
}
}
/**下面注释掉的代码是通过 直接操作寄存器 方式实现IO口控制**
int main(void)
{
delay_init(); //初始化延时函数
LED_Init(); //初始化LED端口
while(1)
{
GPIOB->BRR=GPIO_Pin_5;//LED0亮
GPIOE->BSRR=GPIO_Pin_5;//LED1灭
delay_ms(300);
GPIOB->BSRR=GPIO_Pin_5;//LED0灭
GPIOE->BRR=GPIO_Pin_5;//LED1亮
delay_ms(300);
}
}
***************************************************/
sys.h
#ifndef __SYS_H
#define __SYS_H
#include "stm32f10x.h"
//0,不支持ucos
//1,支持ucos
#define SYSTEM_SUPPORT_OS 0 //定义系统文件夹是否支持UCOS
//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
#endif
led.h
#ifndef __LED_H
#define __LED_H
#include "sys.h"
#define LED0 PBout(5)// PB5
#define LED1 PEout(5)// PE5
void LED_Init(void);//初始化
#endif
led.c
#include "led.h"
//初始化PB5和PE5为输出口.并使能这两个口的时钟
//LED IO初始化
void LED_Init(void)
{
RCC->APB2ENR|=1<<3; //使能PORTB时钟
RCC->APB2ENR|=1<<6; //使能PORTE时钟
GPIOB->CRL&=0XFF0FFFFF;
GPIOB->CRL|=0X00300000;//PB.5 推挽输出
GPIOB->ODR|=1<<5; //PB.5 输出高
GPIOE->CRL&=0XFF0FFFFF;
GPIOE->CRL|=0X00300000;//PE.5推挽输出
GPIOE->ODR|=1<<5; //PE.5输出高
}
delay.h
#ifndef __DELAY_H
#define __DELAY_H
#include "sys.h"
void delay_init(void);
void delay_ms(u16 nms);
void delay_us(u32 nus);
#endif
delay.c
#include "delay.h"
static u8 fac_us=0;//us延时倍乘数
static u16 fac_ms=0;//ms延时倍乘数,在ucos下,代表每个节拍的ms数
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us=SystemCoreClock/8000000; //为系统时钟的1/8
fac_ms=(u16)fac_us*1000;
}
//延时nus
//nus为要延时的us数.
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us;//时间加载
SysTick->VAL=0x00;//清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;//开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;//关闭计数器
SysTick->VAL =0X00;//清空计数器
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00;//清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;//开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16)));//等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;//关闭计数器
SysTick->VAL =0X00;//清空计数器
}
#endif
main.c
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
int main(void)
{
delay_init(); //初始化延时函数
LED_Init(); //初始化LED端口
while(1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_5); //LED0对应引脚GPIOB.5拉低,亮 等同LED0=0;
GPIO_SetBits(GPIOE,GPIO_Pin_5); //LED1对应引脚GPIOE.5拉高,灭 等同LED1=1;
delay_ms(300); //延时300ms
GPIO_SetBits(GPIOB,GPIO_Pin_5); //LED0对应引脚GPIOB.5拉高,灭 等同LED0=1;
GPIO_ResetBits(GPIOE,GPIO_Pin_5); //LED1对应引脚GPIOE.5拉低,亮 等同LED1=0;
delay_ms(300); //延时300ms
}
}
led.h
#ifndef __LED_H
#define __LED_H
void LED_Init(void);//初始化
#endif
led.c
#include "led.h"
//初始化PB5和PE5为输出口.并使能这两个口的时钟
//LED IO初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);//使能PB,PE端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);//根据设定参数初始化GPIOB.5
GPIO_SetBits(GPIOB,GPIO_Pin_5);//PB.5 输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//LED1-->PE.5 端口配置, 推挽输出
GPIO_Init(GPIOE, &GPIO_InitStructure);//推挽输出 ,IO口速度为50MHz
GPIO_SetBits(GPIOE,GPIO_Pin_5);//PE.5 输出高
}
上面的代码即是一个跑马灯程序的源码了,其中sys.h为正点原子为我们可以实现51类似的GPIO控制功能,定义的一些宏定义。
看了上述的单片机入门学习代码(类似语言学习入门代码helloword),想必会有很多疑问提出来,下面会罗列一些学习方面的问题,然后在一个一个的给予解释。
1-1)使用MDK5新建工程,工程建好后,通过右键工程项目菜单“Manage project items…”将相应的基本开发库函数源码填入进来,下图是本程序用到的基础库的介绍,请看图
要使程序编译成功,还需在 “Options for target ‘marquee’”弹出框中设置 “C/C++”相应配置项
①设置“Preprocessor Symbols”,如下图
②设置 “Include Paths”,即设置相应库函数的目录,如下图
USE_STDPERIPH_DRIVER :表示是否在应用中启用外设驱动。我们使用标准外设库是为了方便控制外设,添加这个定义,以启用外设驱动。( to use or not the peripheral’s drivers in application code (i.e. code will be based on direct access to peripheral’s registers rather than drivers API) , this option is controlled by the #define USE_STDPERIPH_DRIVER)
STM32F10X_HD:STM32F10X系列每种系列都有所区别,例如sram或者flash或者外设数量不一样,所以stm32标准外设库必须根据你使用的处理器来做相应的预处理。
在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
#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)
#error "Please select first the target STM32F10x device used in your application (in stm32f10x.h file)"
#endif
1-2)采用ST-Link下载程序
使用ST-Link方式下载程序我们可以使用debug功能在线调试代码,建议开发阶段使用这种方式。
ST-Link方式下载程序设置方式如下图:
在debug页签中选中“ST-Link debugger”,点击“Setting”修改 Debug Adpater为“ST-Link/V2”,
安照图设置”CPU DLL”为SARMCM3.DLL、“Parameter”为 -REMAP;设置“Debug DLL”为DARMSTM.DLL、“Parameter”为-pSTM32F103ZE;设置“Driver DLL”为SARMCM3.DLL;设置“Dialog DLL”为TARMSTM.DLL、Parameter”为-pSTM32F103ZE;
除了上图设置还需要设置“Cortex-M Target Driver Setup”中的“Flash Download”选中 Reset and Run,如下图
ST-Link实物连接如下图:
1-3)修改程序编译结果为hex文件
mdk5默认输出是不选中Create Hex File选项的,我们需要选中,如下图:
按照上面步骤设置好环境后,编译下载到板子上,跑马灯程序即可运行,运行效果如下图:
碍于篇幅长度,问题中的2~5后续文章会一一解释记录。