一、创建工程模板
在创建工程模板之前,先认识一个工程中必不可少的组件——固件库,固件库是由C语言编写的一系列程序文件,这些文件是由ST公司官方编写的,其目的是帮助用户很方便的开发STM32单片机。
工程文件夹
1. STARTUP ------- 启动文件
2. CMSIS -------- CMSIS标准文件
3. FWLIB --------- 库文件
4. HARDWARE --------- 外设文件
5. SYSTEM --------- 系统文件
6. USER --------- 用户文件
宏定义
#define 的新名字在左边,并且可以给任何变量换名字,而typedef只能给变量换名字,新名字在右边
二、STM32库文件说明
1. stm32f10x.h : 定义系统寄存器的地址以及使用结构体封装。定义声明以及包装内存操作。新版的固件库 V3.0 以上main等源文件中不再直接包含 stmf10x_conf.h,而是 stm32f10x.h ;
stm32f10x.h 中定义了启动设置,以及所有寄存器宏定义,此文件中需要注意的有:
(1)device的选择;(2)外部时钟频率选择;(3)外设宏定义USE_STDPERIPH_DRIVER;
stm32f10x_conf.h 中包含了所有外设的头文件,因此任意源文件只要包含了 stm32f10x.h ,就可以在源文件调用任意外设的函数,而不需要的外设部分,可以在 stm32f10x_conf.h 中注释掉,项目编译时就不会再编译去掉的外设。
2. stm32f10x_it.c和stm32f10x_it.h : 所有中断服务函数
中断是嵌入式系统中重要的组成部分,但是在标准C中不包含中断。许多编译开发商在标准C上增加了对中断的支持,提供新的关键字用于标示中断服务程序 (ISR),类似于__interrupt、#program interrupt等。当一个函数被定义为ISR的时候,编译器会自动为该函数增加中断服务程序所需要的中断现场入栈和出栈代码。
中断服务程序需要满足如下要求:
(1)不能返回值;
(2)不能向ISR传递参数;
(3) ISR应该尽可能的短小精悍;
(4) printf(char * lpFormatString,…)函数会带来重入和性能问题,不能在ISR中采用。
参阅网上资料和个人的一些理解
a.为什么不能有返回值?
中断服务函数的调用是硬件级别的,当中断产生,pc指针强制跳转到对应的中断服务函数入口,进入中断具有随机性,并不是某段代码对其进行调用,那么如果有返回值它的返回值返回给谁?显然这个返回值毫无意义,如果有返回值,它必定需要进行压栈操作,这样一来何时出栈怎么出栈将变得无法解决。
b.不能向ISR传递参数?
同理,也是由于这样会破坏栈的原因,因为函数传递参数必定会要求压栈出栈操作,由于进入中断服务函数的随机行,谁给它传递参数都成问题
c.ISR应尽可能的短小精悍?
如果某个中断频繁产生,而它对应的ISR相当的耗时,那么对中断的响应就会无限的延迟,会丢掉很多的中断请求
d.printf(char * lpFormatString,…)函数会带来重入和性能问题,不能在ISR中采用。
这就涉及到一个中断嵌套问题,由于printf之类的glibc函数采用的是缓冲机制,这个缓冲区是共享的,相当于一个全局变量,第一层中断来时,它向缓冲里面写入一些部分内容,恰好这时来了个优先级更高的中断,它同样调用了printf,也向缓冲里面写入一些内容,这样缓冲区的内容就错乱了。
3. stm32f10x_conf.h:所有外设头文件
4. system_stm32f10x.c:设备外设访问层,主要配置时钟频率(设置系统以及总线时钟)
system_stm32f10x.h:配置时钟频率相应的头文件
5. 下面是一些启动文件
6. stm32f10x_flash.icf:编译器链接库文件
三、STM32各种头文件No such file or directory的解决方法
初学STM32在编译的时候遇到找不到某个头文件的编译错误,大部分是工程设置的问题,路径设置不正确,无法找到文件等等,
首先,必须清晰的了解工程设置路径的含义,为什么要设置这个路径,工程设置的路径是在搜素 xx.h 文件。
四、GPIO
GPIO(General purpose input output)是通用输入输出端口的简称,简单来说就是软件可控制的引脚,STM32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。
1. STM32引脚全部是GPIO吗?
并不是所有的引脚都是GPIO
以STM32F103VET6为例,其引脚图如图所示
STM32芯片的GPIO被分成很多组,每组有16个引脚,如型号为 STM32F103VET6 型号的芯片有 GPIOA、GPIOB、GPIOC至GPIOE共5组GPIO,同时,GPIO还具备复用功能。
2. GPIO的输入输出模式
GPIO可配置8种模式
- 输出模式下可控制端口的输出高低电平,可用于驱动LED、控制蜂鸣器、模拟通信协议输出输出时序(I2C、SPI等)
- 输入模式下可读取端口的高低电平或电压,用于读取按键输入,外接模块电平信号输入,ADC电压采集、模拟通信协议接收数据(I2C、SPI等)
3. GPIO基本结构
每个GPIO端口对应16个引脚,例GPIOA(PA0~PA15)
内核CPU就可以通过APB2总线对寄存器读写,完成输出电平和读取电平的功能。
4. GPIO的功能框图
- 保护二极管
芯片的引脚电平0~3.3V,部分引脚可以5V,引脚的两个保护二极管可以防止引脚外部过高或过低的电压输入
(1) 当引脚电压高于VDD时,上方的二极管导通,防止过高电压进入芯片内部烧坏芯片
(2) 当引脚电压低于VSS时,下方的二极管导通,防止电压过低,从芯片内部汲取电流
- 施密特触发器
具有滤波作用,就是让通过的电平输出稳定的高低电平
5. GPIO的8种工作模式
6. GPIO的寄存器
通过对GPIO寄存器写入不同的参数,就可以改变 GPIO 的工作模式,要了解具体寄存器时一定要查阅《STM32F10x-参考手册》中对应外设的寄存器说明。
端口配置低寄存器
在GPIO外设中,控制端口高低控制寄存器CRH和CRL可以配置每个GPIO的工作模式和工作的速度,每4个位控制一个IO,CRH控制端口的高8位,CRL控制端口的低8位。
端口配置高寄存器
端口输出寄存器
端口位设置/清除寄存器
端口位清除寄存器
端口输入数据寄存器
7. GPIO的固件库函数
8. GPIO各种输入输出模式
(1)输入模式:
模拟输入 GPIO_AIN:应用ADC模拟输入,或者低功耗下省电
浮空输入 GPIO_IN_FLOATING:引脚处于浮空模式,电平状态是不确定的。外部信号输入什么,IO口就是什么状态。
上拉输入 GPIO_IPU:防止IO口出现不确定的状态,比如,当IO口悬空时,就会通过内部的上拉电阻将该点钳位在高电平
下拉输入 GPIO_IPD:功能与上拉电阻类似,防止IO口出现不确定的状态,比如,当IO口悬空时,就会通过内部的下拉电阻将该点钳位在低电平
(2)输出模式:
开漏输出 GPIO_OUT_OD:IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。
推挽输出 GPIO_OUT_PP: IO输出0接GND,IO输出1,接VCC
复用功能的推挽输出 GPIO_AF_PP:一般设置片内外设功能(I2C的SCL、SDA)
复用功能的开漏输出 GPIO_AF_OD:片内外设功能(TX1,MOSI,MISO,SCK,SS)
9. 输出频率设置
STM32的GPIO有3种输出速度(2MHz、10MHz和50MHz),这个速度是指I/O口驱动电路的响应速度。通过选择速度来选择不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。高频的驱动电路,噪声也高,当不需要高的输出频率时,请选用低频驱动电路,这样非常有利于提高系统的EMI性能。当然如果要输出较高频率的信号,但却选用了较低频率的驱动模块,很可能会得到失真的输出信号。
如何选择输出频率
输出频率一般是应用频率的10倍为宜,例如:
对于串口,假如波特率115200,只需115.2k,那么用2M的GPIO的引脚速度就够了,既低功耗也噪声小;
对于I2C接口,假如使用400k波特率,若想把余量留大些,那么用2M的GPIO的引脚速度或许不够,这时可以选用10M的GPIO引脚速度;
对于SPI接口,假如使用18M或9M波特率,用10M的GPIO的引脚速度显然不够了,需要选用50M的GPIO的引脚速度;
10. GPIO配置步骤
第一步:使用RCC开启GPIO的时钟
第二步:使用GPIO_Init() 函数初始化 GPIO
第三步:使用输出或者输入的函数控制 GPIO
常用的RCC开启时钟函数
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph,FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph,FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph,FunctionalState NewState);
参数1:选择外设
参数2:使能或者失能
常用的GPIO函数
复位GPIO外设函数:
void GPIO_DeInit (GPIO_TypeDef* GPIOx);
复位AFIO外设函数:
void GPIO_AFIODeInit (void);
初始化GPIO口函数
用结构体的参数来初始化GPIO口,先定义一个结构体变量,然后再给结构体赋值,最后调用此函数 ,函数内部会自动读取结构体的值,然后自动把外设的各个参数配置好
void GPIO_Init (GPIO_TypeDef* GPIOx,GPIO_InitTypedef* GPIO_InitStruct );
五、点亮一个LED灯
众所周知,点亮一个LED灯,是学习单片机的第一步,点亮一个LED灯就成功了一半。