当前 ST 公司正在力推STM32Cube(HAL & LL)固件库,但是出于使用习惯,这里依然选择了STM32 Standard Peripheral Libraries标准固件库来编写实验代码,新建一个stm 工程文件夹,并添加CMSIS、Lib、Startup、User四个子文件夹。
A、将STM32F10x_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x目录下的stm32f10x.h、system_stm32f10x.c、system_stm32f10x.h三个文件,以及STM32F10x_V3.5.0\Libraries\CMSIS\CM3\CoreSupport目录下的所有文件复制到刚才新建的CMSIS目录下;
B、然后进入STM32F10x_V3.5.0\ Libraries\ CMSIS\CM3 \DeviceSupport \ST\ ST M32 F10x\startup\arm目录将所有文件复制到刚才新建的Startup目录;
C、接下来再将STM32F10x_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver目录下的src和inc两个子文件夹拷贝到刚才新建的Lib目录;
D、最后将STM32F10x_V3.5.0\Project\STM32F10x_StdPeriph_Template目录下的main.c、stm32f10x_conf.h、stm32f10x_it.c、stm32f10x_it.h四个文件拷贝至刚才新建的User目录;
E、完成 STM32 官方标准库文件的拷贝与复制之后,还需要对 Keil uVision 进行相应设置。首先新建一个【New uVision Project】,将CMSIS目录下的 2 个.c文件全部添加至 Keil uVision 项目的CMSIS组,然后将Lib\src目录下的所有文件添加至Lib组,将Startup目录下的startup_stm32f10x_md.s汇编文件添加至Startup组,最后将User下的 2 个.c文件添加至User组,最后保存退出,然后在【C/C++】选项卡下的【 Prepro cessor Symbols】 内的【Define】输入框内填入USE_STDPERIPH_ DRIVER, STM32F10X_ MD。最后点击【Include Paths】左侧的添加按钮,把刚才新建的那些文件夹逐一选入。
官方库文件添加完毕之后,还需要新建Basic和Hardware两个目录来放置开发人员编写的代码,同样按照以上步骤将其添加至 Keil uVision 开发环境当中,至此项目中所有目录的功能说明如下:
CMSIS:内核驱动程序;
Lib:内部功能基本函数库;
Startup:汇编编写的启动程序;
User:用户主函数以及其它用户程序;
Basic:内部功能驱动函数;
Hardware:外部硬件驱动函数;
也可加入.txt作为增加程序的说明使用。
接下来进一步对 Keil uVision 进行一些初始化的设置,这里首先设置【Target】选项卡下面的外部晶振频率为8.0 MHz,然后勾选【Output】选项卡下的Create HEX File。
作为一个无51经验的菜鸟,直接上手STM32确实感到很吃力,各种教程都是把点灯作为实操单片机的第一步也是最简单的一步。实现这一功能也是初步学习配置GPIO,下面就库函数详细说明如何实现这一功能。以洋桃电子开发板为例,所控的两个LED灯与GPIOB_Pin0与Pin1引脚连接,LED1灭,LED2亮。
/*GPIO初始化*/
void GPIO_Configuration(void)
{
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(GPIOB, &GPIO_InitStructure);
}
/* 配置系统时钟,使能各外设时钟*/
#include "stm32f10x.h"
void RCC_Configuration(void)
{
SystemInit();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB
| RCC_APB2Periph_GPIOC, ENABLE );
}
/* main函数*/
int main(void)
{
Init_All_Periph();
while(1)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_0);
GPIO_SetBits(GPIOB, GPIO_Pin_1);
}
}
以上为对LED亮灭的基础控制。
下面就实现多种LED功能示例介绍:(.h文件为方便程序编写的声明,在添加文件中加入.h文件,编译一下即可编写)
main.c
/** User/main.c */
#include "delay.h"
#include "led.h"
#include "stm32f10x.h"
#include "sys.h"
int main(void) {
RCC_Configuration(); // 时钟设置
LED_Init();
while (1) {
/** 方式 1
typedef enum {
Bit_RESET = 0,
Bit_SET
} BitAction; // 位于 stm32f10x_gpio.h
*/
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1)); // LED1 输出高电平 1
delay_us(50000); // 延时 1 秒
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0)); // LED1 输出低电平 0
delay_us(50000); // 延时 1 秒
/** 方式 2 */
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1 - GPIO_ReadOutputDataBit(LEDPORT, LED1))); // 取反 LED1
delay_ms(500); // 延时 1 秒
/** 方式 3 */
GPIO_SetBits(LEDPORT, LED1); // LED 置为高电平 1
delay_s(1); // 延时 1 秒
GPIO_ResetBits(LEDPORT, LED1); // LED 重置为低电平 0
delay_s(1); // 延时 1 秒
/** 方式 4 */
GPIO_Write(LEDPORT, 0x0001); // 将 32 位状态值写入整组 GPIO
delay_s(2); // 延时 1 秒
GPIO_Write(LEDPORT, 0x0000); // 将 32 位状态值写入整组 GPIO
delay_s(2); // 延时 1 秒
}
}
LED.h 声明头文件
/** Hardware/LED/led.h */
#ifndef __LED_H
#define __LED_H
#include "sys.h"
/*
// 通过 PXout() 函数直接操作 GPIO 底层寄存器
#define LED1 PBout(0) // 直接操作 PB0,使用方式:LED1 = 0
#define LED2 PBout(1) // 直接操作 PB1,使用方式:LED2 = 1
*/
#define LEDPORT GPIOB // 定义 GPIOB
#define LED1 GPIO_Pin_0 // 定义 GPIO_Pin_0
#define LED2 GPIO_Pin_1 // 定义 GPIO_Pin_1
void LED_Init(void); // 初始化
#endif
delay.h
/** Basic/delay/delay.h */
#ifndef __DELAY_H
#define __DELAY_H
#include "sys.h"
void delay_s(u16 s);
void delay_ms(u16 ms);
void delay_us(u32 us);
#endif
LED.c 四种控制程序
/** Hardware/LED/led.c */
#include "led.h"
/* LED 初始化函数 */
void LED_Init(void) {
/*
typedef struct {
uint16_t GPIO_Pin; // GPIO 编号
GPIOSpeed_TypeDef GPIO_Speed; // GPIO 速度
GPIOMode_TypeDef GPIO_Mode; // GPIO 模式
} GPIO_InitTypeDef;
*/
GPIO_InitTypeDef GPIO_InitStructure; // GPIO 初始化参数结构体的声明
/* AHB 外设时钟使能 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
/* GPIO 初始化参数结构体 */
GPIO_InitStructure.GPIO_Pin = LED1 | LED2; // 选择 GPIO 编号(0~15 / all)
/*
GPIO_Mode_AIN 模拟输入 GPIO_Mode_IN_FLOATING 浮空输入 GPIO_Mode_IPD 下拉输入
GPIO_Mode_IPU 上拉输入 GPIO_Mode_Out_PP 推挽输出 GPIO_Mode_Out_OD 开漏输出
GPIO_Mode_AF_PP 复用推挽输出 GPIO_Mode_AF_OD 复用开漏输出
*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 选择 GPIO 工作模式为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置 GPIO 接口速度(2MHz / 10MHz / 50MHz)
GPIO_Init(LEDPORT, &GPIO_InitStructure); // 运行 GPIO 初始化库函数,将设置写入寄存器
}
delay.c 延时函数模板,s/ms/μs
/** Basic/delay/delay.c
* 基于系统时基定时器SysTick的延时函数,出于性能考量,采用了寄存器操作实现,并未使用库函数。
* */
#include "delay.h"
#define AHB_INPUT 72 // RCC 里设置的 AHB 时钟频率,单位为 MHz
/* 微秒 us 级延时,参数最大值为 233015 */
void delay_us(u32 uS) {
SysTick->LOAD = AHB_INPUT * uS; // 计数器重装初值,当前频率为 72MHz,即 72 次为 1us
SysTick->VAL = 0x00; // 定时器计数器清零
SysTick->CTRL = 0x00000005; // 时钟源 HCLK,打开定时器
while (!(SysTick->CTRL & 0x00010000)); // 等待计数值归 0
SysTick->CTRL = 0x00000004; // 关闭定时器
}
/* 毫秒 ms 级延时,参数最大值为 65535 */
void delay_ms(u16 ms) {
while (ms-- != 0) {
delay_us(1000); // 调用 1000us 延时函数
}
}
/* 秒 s 级延时,参数最大值为 65535 */
void delay_s(u16 s) {
while (s-- != 0) {
delay_ms(1000); // 调用 1000ms 延时函数
}
}
这里涉及视觉中帧的概念,通过调整LED灯中一周期高低电平的占空比(即一周期内高低电平所占的时间)来实现对LED灯亮度的控制。同时通过for循环来控制亮度变化的速率。其他与上雷同,仅改变主程序:
mian.c
/** User/main.c */
#include "delay.h"
#include "led.h"
#include "stm32f10x.h"
#include "sys.h"
int main(void) {
/* 变量定义 */
u8 MENU;
u16 t, i;
/* 初始化程序 */
RCC_Configuration(); // 时钟设置
LED_Init();
MENU = 0; // 设置初始值
t = 1;
while (1) {
/* 变亮循环 */
if (MENU == 0) {
for (i = 0; i < 10; i++) {
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1)); // LED1 输出高电平 1
delay_us(t); // 延时
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0)); // LED1 输出低电平 0
delay_us(501 - t); // 延时
}
t++;
if (t == 500) {
MENU = 1; // 切换至变暗循环
}
}
/* 变暗循环 */
if (MENU == 1) {
for (i = 0; i < 10; i++) {
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(1)); // LED1 输出高电平 1
delay_us(t); // 延时
GPIO_WriteBit(LEDPORT, LED1, (BitAction)(0)); // LED1 输出低电平 0
delay_us(501 - t); // 延时
}
t--;
if (t == 1) {
MENU = 0; // 切换至变亮循环
}
}
}
}