跑马灯
跑马灯是STM32编程中最经典的第一个项目,目的是让操作者去熟悉GPIO的用法,包括一些最基本的操作和概念。
文中涉及到一些基本的C语言知识和库函数概念等,我会尽可能一一详解。
打好基础是学习一门庞杂课程的重中之重,所以前期我会事无巨细地详加解释。
硬件部分
首先我们看电路图
不难发现,当左侧(PF端)为低电平时,发光二极管就会被点亮。得到对应关系如下:
软件部分
这里我们采用我所使用的“神舟三号”给出的实例代码作为学习的材料,没有的同学可以去搜索并下载,获取起来很容易。
这里官方的写法是使用库函数直接进行操作,这里我需要强调一点,如果只熟悉库函数而不了解其寄存器对应的写法,是无法在工作中真正学会灵活应用的。所以在我们熟悉库函数的用法以后,我们会使用寄存器再来进行一次改写。
头文件及定义部分
#include "stm32f10x.h"
stm32f10x.h 这个头文件是STM32开发最为重要的一个头文件,涉及到寄存器地址到寄存器结构体变量的映射。
#define RCC_GPIO_LED RCC_APB2Periph_GPIOF /*RCC是ARM里面复位与时钟控制器(Reset Clock Controller)缩写,这里使用的是给GPIOF组所配置的RCC*/
#define LEDn 4 /*定义LED的数量*/
#define GPIO_LED GPIOF /*LED灯使用的GPIO组,从A到G一共有8组,这里使用的是F组*/
#define DS1_PIN GPIO_Pin_6 /**/
#define DS2_PIN GPIO_Pin_7 /**/
#define DS3_PIN GPIO_Pin_8 /**/
#define DS4_PIN GPIO_Pin_9 /**/
这里先定义好了LED灯相关的一系列参数。define的用法如下:
#define 标识符 常量 //注意, 最后没有分号
#define又称宏定义,标识符为所定义的宏名,简称宏。标识符的命名规则与前面讲的变量的命名规则是一样的。#define 的功能是将标识符定义为其后的常量。一经定义,程序中就可以直接用标识符来表示这个常量。
GPIO_InitTypeDef GPIO_InitStructure;//创造类型为GPIO_InitTypeDef的结构体
ErrorStatus HSEStartUpStatus;//创造类型为ErrorStatus的枚举结构体
u8 count=0;//创造count以用来计数
跟踪GPIO_InitTypeDef 的代码:
typedef struct
{
uint16_t GPIO_Pin; /*GPIO的针脚号 */
GPIOSpeed_TypeDef GPIO_Speed; /*输出口的频率*/
GPIOMode_TypeDef GPIO_Mode; /*输出的模式*/
}GPIO_InitTypeDef;
跟踪 ErrorStatus的代码:
typedef enum {ERROR = 0, SUCCESS = !ERROR} ErrorStatus; /*用于判断是否发生错误*/
除了完成这些基本组件的搭建,我们还要继续进行后续要用到的函数的声明,如下:
void Delay(vu32 nCount);/*控制灯与灯之间的时间间隔*/
void Turn_On_LED(u8 LED_NUM);/*打开LED灯触发的函数*/
功能函数模块
按输入序号打开LED灯
void Turn_On_LED(u8 LED_NUM)
{
switch(LED_NUM)
{
case 0:
GPIO_ResetBits(GPIO_LED,DS1_PIN); /*打开DS1*/
break;
case 1:
GPIO_ResetBits(GPIO_LED,DS2_PIN); /*打开DS2*/
break;
case 2:
GPIO_ResetBits(GPIO_LED,DS3_PIN); /*打开DS3*/
break;
case 3:
GPIO_ResetBits(GPIO_LED,DS4_PIN); /*打开DS4*/
break;
default:
GPIO_ResetBits(GPIO_LED,DS1_PIN|DS2_PIN|DS3_PIN|DS4_PIN); /*都不是的情况下打开四个灯,正常情况下不可能发生,也可视作为报错*/
break;
}
}
控制灯与灯之间的时间间隔
void Delay(vu32 nCount)
{
for(; nCount != 0; nCount--);//设定一个延迟,递减为0以后退出函数
}
主函数
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_GPIO_LED, ENABLE); /*开启时钟*/
GPIO_InitStructure.GPIO_Pin = DS1_PIN|DS2_PIN|DS3_PIN|DS4_PIN; /*设定结构体内的四个针脚*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /*输出模式设定为推挽模式*/
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; /*输出频率定位50MHz*/
GPIO_Init(GPIO_LED, &GPIO_InitStructure); /*将GPIO组与创建的GPIO结构体进行绑定初始化*/
GPIO_SetBits(GPIO_LED,DS1_PIN|DS2_PIN|DS3_PIN|DS4_PIN); /*将4个LED灯设置为 高电平,即设置为熄灭状态*/
while(1)
{
GPIO_SetBits(GPIO_LED,DS1_PIN|DS2_PIN|DS3_PIN|DS4_PIN); /*先使得4个灯全部熄灭*/
Turn_On_LED(count%4); /*使计数器模除4,可以保证结果在0~3之间*/
count++; /*使得计数器不断累增,让计数器不断在0~3之内循环*/
Delay(0x2FFFFF); /*启动延时函数,让 灯与灯之间有间隔*/
}
}
改造为呼吸灯程序
呼吸灯原理分析
PulseWidthModulation脉冲宽度调制,简称PWM。
PWM(脉冲宽度调制)对模拟信号电平进行数字编码的方法,计算机只能输出0或5V的数字电压值而不能输出模拟电压,而我们如果想获得一个模拟电压值,则需通过使用高分辨率计数器,改变方波的占空比来对一个模拟信号的电平进行编码。
设备仍输出数字信号,因为满幅值的直流供电只有高电平(1)和低电平(0)两种。电压是以一种连接(1)或断开(0)的重复脉冲序列被夹到模拟负载上去的,连接即是直流供电输出,断开即是直流供电断开。通过对连接和断开时间的控制,只要带宽足够,可以输出任意不大于最大电压值的模拟电压。
所以我们只要控制高低电平的占空比,就可以用PWM模拟出呼吸灯的效果。
主函数
int main(void)
{
int i,b=4000;/*定义两个参数作为呼吸灯的高低电平比例控制,b的值可以自己设定*/
RCC_APB2PeriphClockCmd(RCC_GPIO_LED, ENABLE); /*开启时钟*/
GPIO_InitStructure.GPIO_Pin = DS1_PIN|DS2_PIN|DS3_PIN|DS4_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO_LED, &GPIO_InitStructure); /*绑定GPIO组和结构体并初始化*/
GPIO_SetBits(GPIO_LED,DS1_PIN|DS2_PIN|DS3_PIN|DS4_PIN);/*使所有灯先熄灭*/
while(1)/*开启无限循环*/
{
for(i=0;i<b;i++){ /*正向模拟PWM*/
GPIO_ResetBits(GPIO_LED,DS1_PIN); /*将灯打开*/
Delay(b-i); /*控制本次灯亮的时间*/
GPIO_SetBits(GPIO_LED,DS1_PIN); /*将灯熄灭*/
Delay(i); /*控制本次灯灭的时间*/
}
for(i=0;i<b;i++){ /*反向模拟PWM*/
GPIO_ResetBits(GPIO_LED,DS1_PIN); /*将灯打开*/
Delay(i); /*控制本次灯亮的时间*/
GPIO_SetBits(GPIO_LED,DS1_PIN); /*将灯熄灭*/
Delay(b-i); /*控制本次灯灭的时间*/
}
}
}