第十二章C语言LED灯实验
为了加深理解汇编语言以及汇编初始化过程,第十一章我们使用汇编来控制LED0。本章节我们来学习使用C语言来控制LED0,实际的开发中我们接触最多的就是C语言了,在整个裸机开发中,基本上也都是使用C语言来完成。
本章将分为如下几个小节:
12.1、C语言控制LED灯简介;
12.2、硬件设计;
12.3、软件设计;
12.4、编译和测试;
12.1 C语言控制LED灯简介
第十一章的实验是用汇编语言来控制LED0闪烁的,学习该章节的目的也就是为了加深理解汇编的初始化过程,而实际的开发中我们很少使用汇编来开发,毕竟汇编比较难,可移植性也不高。不管怎样,学习和理解汇编还是很有必要的,汇编部分主要是在芯片上电后的初始化工作,当这些工作完成后才会进入C的世界,所以,不管是用汇编语言来开发,还是使用C语言来开发,前提都是离不开汇编的启动文件startup_stm32mp15xx.s。
本章节我们来学习使用C语言完成LED灯的控制,实际上ST官方提供的HAL库也是C语言,只是ST把具有通用性的函数封装成为接口,我们通过调用接口可以实现对应的功能,这大大提高了开发的效率以及移植性,不过这里我们先不用HAL库来开发,我们先自行编写C语言代码来实现。
12.2 硬件设计
硬件原理图和第十一章节一样。本章节控制的是开发板的LED0和LED1。
图12.2.1 LED与STM32MP157连接原理图
可以看出,LED0 接到了PI0引脚上,当PI0输出低电平(0)的时候发光二极管 LED0 就会导通点亮,当PI0输出高电平(1)的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。LED1接在了PF3引脚上,同理,LED1的亮灭取决于PF3的输出电平,输出 0 就亮,输出 1 就灭。
12.3 软件设计
本实验配置好的实验工程已经放到了开发板光盘中:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\ MP157-M4 HAL库V1.2\实验1 C语言LED灯实验。
12.3.1 创建工程
实验的第一步都是创建工程,创建工程步骤可参考前面第六章部分。这里我们新建三个文件:启动文件startup_stm32mp15xx.s、main.c文件、main.h文件,如下图:
图12.3.1.1新建工程
12.3.2 代码编写
1.修改startup_stm32mp15xx.s
我们直接拷贝STM32Cube固件包里的启动文件的代码到本工程的startup_stm32mp15xx.s文件中,文件在固件包为STM32Cube_FW_MP1_V1.2.0\Drivers\CMSIS\Device\ST\STM32MP1xx\Source\Templates\arm\startup_stm32mp15xx.s。拷贝完成后,我们修改一下地方:
IMPORT表示标号来自外部文件,本工程中没有SystemInit函数,所以我们注释掉第243行、246行、247行;本工程中只有main函数,所以224行和248行的标号__main改为main,最后修改的部分如下图所示:
图12.3.2.1修改启动文件
2. 添加main.h代码
main.h文件中主要是本工程中的GPIO相关的寄存器地址定义,这个部分其实也就和我们以前编写51单片机的代码类似,main.h的代码如下:
#ifndef __MAIN_H
#define __MAIN_H
/*
各个外设基地址
*/
#define PERIPH_BASE (0x40000000)
#define MCU_AHB4_PERIPH_BASE (PERIPH_BASE + 0x10000000)
#define RCC_BASE (MCU_AHB4_PERIPH_BASE + 0x0000)
#define GPIOI_BASE (MCU_AHB4_PERIPH_BASE + 0xA000)
#define GPIOF_BASE (MCU_AHB4_PERIPH_BASE + 0x7000)
/*
寄存器地址
*/
#define RCC_MC_AHB4ENSETR *((volatile unsigned int *)(RCC_BASE + 0XAA8))
#define GPIOI_MODER *((volatile unsigned int *)(GPIOI_BASE + 0x0000))
#define GPIOI_OTYPER *((volatile unsigned int *)(GPIOI_BASE + 0x0004))
#define GPIOI_OSPEEDR *((volatile unsigned int *)(GPIOI_BASE + 0x0008))
#define GPIOI_PUPDR *((volatile unsigned int *)(GPIOI_BASE + 0x000C))
#define GPIOI_BSRR *((volatile unsigned int *)(GPIOI_BASE + 0x0018))
#define GPIOF_MODER *((volatile unsigned int *)(GPIOF_BASE + 0x0000))
#define GPIOF_OTYPER *((volatile unsigned int *)(GPIOF_BASE + 0x0004))
#define GPIOF_OSPEEDR *((volatile unsigned int *)(GPIOF_BASE + 0x0008))
#define GPIOF_PUPDR *((volatile unsigned int *)(GPIOF_BASE + 0x000C))
#define GPIOF_BSRR *((volatile unsigned int *)(GPIOF_BASE + 0x0018))
#define OFF 0
#define ON 1
#endif
main.h中,最后定义以下地址:
AHB4基地址0x50000000 | GPIOI_MODER地址0x5000A000 | GPIOF_MODER地址0x50007000 |
RCC基地址0x50000000 | GPIOI_OTYPER地址0x5000A004 | GPIOF_OTYPER地址0x50007004 |
GPIOI基地址0x5000A000 | GPIOI_OSPEEDR 地址0x5000A008 | GPIOF_OSPEEDR地址0x50007008 |
GPIOF基地址0x50007000 | GPIOI_PUPDR地址0x5000A00C | GPIOF_PUPDR 地址0x5000700C |
RCC_MC_AHB4ENSETR地址0x50000AA8 | GPIOI_BSRR地址 0x5000A018 | GPIOF_BSRR 地址0x50007018 |
表12.3.2.1本工程涉及的寄存器地址
3. 添加main.c文件代码
main.c文件代码如下:
#include "main.h"
/**
* @brief使能相关时钟
* @param无
* @retval无
*/
void clk_enable(void)
{
RCC_MC_AHB4ENSETR |= ((unsigned int)1 << 8); /* 使能GPIOI时钟 */
RCC_MC_AHB4ENSETR |= ((unsigned int)1 << 5); /* 使能GPIOF时钟 */
}
/**
* @brief初始化GPIO
* @param无
* @retval无
*/
void gpio_init(void)
{
/* GPIOI_0设置为输出 */
GPIOI_MODER &= ~((unsigned int)3 << (2 * 0));
GPIOI_MODER |= ((unsigned int)1 << (2 * 0));
/* GPIOI_0设置为推挽模式 */
GPIOI_OTYPER &= ~((unsigned int)1 << 0);
/* GPIOI_0设置为高速模式 */
GPIOI_OSPEEDR &= ~((unsigned int)3 << (2 * 0));
GPIOI_OSPEEDR |= ((unsigned int)2 << (2 * 0));
/* GPIOI_0设置为上拉 */
GPIOI_PUPDR &= ~((unsigned int)3 << (2 * 0));
GPIOI_PUPDR |= ((unsigned int)1 << (2 * 0));
/* GPIOF_3设置为输出 */
GPIOF_MODER &= ~((unsigned int)3 << (2 * 3));
GPIOF_MODER |= ((unsigned int)1 << (2 * 3));
/* GPIOF_3设置为推挽模式 */
GPIOF_OTYPER &= ~((unsigned int)1 << 3);
/* GPIOF_3设置为高速模式 */
GPIOF_OSPEEDR &= ~((unsigned int)3 << (2 * 3));
GPIOF_OSPEEDR |= ((unsigned int)2 << (2 * 3));
/* GPIOF_3设置为上拉 */
GPIOF_PUPDR &= ~((unsigned int)3 << (2 * 3));
GPIOF_PUPDR |= ((unsigned int)1 << (2 * 3));
}
/**
* @brief开关函数
* @param无
* @retval无
*/
void led0_switch(unsigned char state)
{
if(state == OFF)
{
GPIOI_BSRR |= ((unsigned int)1 << 0); /* GPIOI_0输出高电平 */
} else if(state == ON)
{
GPIOI_BSRR |= ((unsigned int)1 << 16); /* GPIOI_0输出低电平 */
}
}
/**
* @brief开关函数
* @param无
* @retval无
*/
void led1_switch(unsigned char state)
{
if(state == OFF)
{
GPIOF_BSRR |= ((unsigned int)1 << 3); /* GPIOF_3输出高电平 */
} else if(state == ON)
{
GPIOF_BSRR |= ((unsigned int)1 << 19); /* GPIOF_3输出低电平 */
}
}
/**
* @brief短时间延时函数
* @param要延时循环次数(空操作循环次数,模式延时)
* @retval无
*/
void delay_short(volatile unsigned int n)
{
while(n--){}
}
/**
* @brief长延时函数
* @param要延时的时间循环数
* @retval无
*/
void delay(volatile unsigned int n)
{
while(n--)
{
delay_short(0x7fff);
}
}
/**
* @brief函数
* @param无
* @retval无
*/
int main(void)
{
clk_enable(); /* 使能时钟 */
gpio_init(); /* 初始化GPIO */
while(1)
{
led0_switch(ON); /* LED0开 */
led1_switch(OFF); /* LED1关 */
delay(100); /* 延时一定时间 */
led0_switch(OFF); /* LED0关 */
led1_switch(ON); /* LED1开 */
delay(100); /* 延时一定时间 */
}
}
main.c文件的代码比较简单:
clk_enable函数主要完成PI0和PF3的时钟开启,这里注意的是,我们没有去配置任何时钟,所以复位以后,系统默认使用内部高速时钟HSI(64MHz)来工作,此时AHB4总线的时钟为64MHz,而GIOI和GPIOF都挂在AHB4总线上,所以GPIOI和GPIOF的时钟频率也为64MHz;
gpio_init函数PI0和PF3的初始化:设置引脚为推挽输出、上拉、高速模式;
led0_switch函数设置PI0引脚为高或低电平;
led1_switch函数设置PF3引脚为高或低电平;
delay函数调用delay_short函数完成时钟延时功能,这里通过让CPU做空循环达到延时的效果;
main函数通过调用以上函数,由while循环实现LED0和LED1交替闪烁。
12.3.3 编译和测试
编译工程无报错,进入仿真模式运行,最后看到LED0和LED1交替闪所,实验完成。