一、学习CH04示例程序,包括gpio.c和4个工程中的main.c
GPIO-asm-20201110下的main.s:(汇编语言小灯测试工程主程序)
.include "include.inc" //头文件中主要定义了程序中需要使用到的一些常量
//(0)数据段与代码段的定义
//(0.1)定义数据存储data段开始,实际数据存储在RAM中
.section .data
//(0.1.1)定义需要输出的字符串,标号即为字符串首地址,\0为字符串结束标志
hello_information: //字符串标号
.ascii "-------------------------------------------------------\n"
.ascii "金葫芦提示: \n"
.ascii "LIGHT:ON--第一次用纯汇编点亮的蓝色发光二极管,太棒了! \n"
.ascii " 这只是万里长征第一步,但是,万事开头难, \n"
.ascii " 有了第一步,坚持下去,定有收获! \n"
.ascii "------------------------------------------------------\n\0"
data_format:
.ascii "%d\n\0" //printf使用的数据格式控制符
light_show1:
.ascii "LIGHT_BLUE:ON--\n\0" //灯亮状态提示
light_show2:
.ascii "LIGHT_BLUE:OFF--\n\0" //灯暗状态提示
light_show3:
.ascii "闪烁次数mLightCount=\0" //闪烁次数提示
//(0.1.2)定义变量
.align 4 //.word格式四字节对齐
mMainLoopCount: //定义主循环次数变量
.word 0
mFlag: //定义灯的状态标志,1为亮,0为暗
.byte 'A'
.align 4
mLightCount:
.word 0
//(0.2)定义代码存储text段开始,实际代码存储在Flash中
.section .text
.syntax unified //指示下方指令为ARM和thumb通用格式
.thumb //Thumb指令集
.type main function //声明main为函数类型
.global main //将main定义成全局函数,便于芯片初始化之后调用
.align 2 //指令和数据采用2字节对齐,兼容Thumb指令集
//--------------------------------------------------------------------
//main.c使用的内部函数声明处
//--------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
main:
//(1)======启动部分(开头)主循环前的初始化工作======================
//(1.1)声明main函数使用的局部变量
//(1.2)【不变】关总中断
cpsid i
//(1.3)给主函数使用的局部变量赋初值
//(1.4)给全局变量赋初值
//(1.5)用户外设模块初始化
// 初始化蓝灯, r0、r1、r2是gpio_init的入口参数
ldr r0,=LIGHT_BLUE //r0指明端口和引脚(用=,因常量>=256,需用ldr)
mov r1,#GPIO_OUTPUT //r1指明引脚方向为输出
mov r2,#LIGHT_OFF //r2指明引脚的初始状态为亮
bl gpio_init //调用gpio初始化函数
// 初始化串口UART_User1
mov r0,#UART_User //串口号
ldr r1,=UART_BAUD //波特率
bl uart_init //调用uart初始化函数
//(1.6)使能模块中断
mov r0,#UART_User //串口号
bl uart_enable_re_int //调用uart中断使能函数
//(1.7)【不变】开总中断
cpsie i
//显示hello_information定义的字符串
ldr r0,=hello_information //待显示字符串首地址
bl printf //调用printf显示字符串
//bl . //在此打桩(.表示当前地址),理解发光二极管为何亮起来了?
//(1)======启动部分(结尾)=======================================
//(2)======主循环部分(开头)=====================================
main_loop: //主循环标签(开头)
//(2.1)主循环次数变量mMainLoopCount+1
ldr r2,=mMainLoopCount //r2←mMainLoopCount的地址
ldr r1, [r2]
add r1,#(88-87)
str r1,[r2]
//(2.2)未达到主循环次数设定值,继续循环
ldr r2,=MainLoopNUM
cmp r1,r2
blO main_loop //未达到,继续循环
//(2.3)达到主循环次数设定值,执行下列语句,进行灯的亮暗处理
//(2.3.1)清除循环次数变量
ldr r2,=mMainLoopCount //r2←mMainLoopCount的地址
mov r1,#0
str r1,[r2]
//(2.3.2)如灯状态标志mFlag为'L',灯的闪烁次数+1并显示,改变灯状态及标志
//判断灯的状态标志
ldr r2,=mFlag
ldr r6,[r2]
cmp r6,#'L'
bne main_light_off //mFlag不等于'L'转
//mFlag等于'L'情况
ldr r3,=mLightCount //灯的闪烁次数mLightCount+1
ldr r1,[r3]
add r1,#1
str r1,[r3]
ldr r0,=light_show3 //显示“灯的闪烁次数mLightCount=”
bl printf
ldr r0,=data_format //显示灯的闪烁次数值
ldr r2,=mLightCount
ldr r1,[r2]
bl printf
ldr r2,=mFlag //灯的状态标志改为'A'
mov r7,#'A'
str r7,[r2]
ldr r0,=LIGHT_BLUE //亮灯
ldr r1,=LIGHT_ON
bl gpio_set
ldr r0, =light_show1 //显示灯亮提示
bl printf
//mFlag等于'L'情况处理完毕,转
b main_exit
//(2.3.3)如灯状态标志mFlag为'A',改变灯状态及标志
main_light_off:
ldr r2,=mFlag //灯的状态标志改为'L'
mov r7,#'L'
str r7,[r2]
ldr r0,=LIGHT_BLUE //暗灯
ldr r1,=LIGHT_OFF
bl gpio_set
ldr r0, =light_show2 //显示灯暗提示
bl printf
main_exit:
b main_loop //继续循环
//(2)======主循环部分(结尾)=====================================
.end //整个程序结束标志(结尾)
GPIO-BlueLight_20230328下的main.c:
#define GLOBLE_VAR
#include "includes.h" //包含总头文件
//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处
//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程)
int main(void)
{
gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF);
printf("程序写入运行后,观察蓝灯:应该亮\nB");
gpio_set(LIGHT_RED,LIGHT_ON); //灯“亮”
//理解蓝色发光二极管为何亮起来了?
for(;;)
{
}
}
GPIO-Output-Component_STM32L431_20200928下的main.c:
#define GLOBLE_VAR
#include "includes.h" //包含总头文件
#include <stdint.h>
// 简单的延时函数
void delay(uint32_t milliseconds) {
for (volatile uint32_t i = 0; i < (milliseconds * 1000); i++) {
// 空循环,用于延时
}
}
int main() {
gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF); //初始化红灯
gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF); //初始化绿灯
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF); //初始化蓝灯
while(1)
{
gpio_set(LIGHT_RED,LIGHT_ON); //红灯“亮”
printf("红灯:亮\n"); //串口输出灯的状态
delay(5000);
gpio_set(LIGHT_RED,LIGHT_OFF); //红灯“暗”
printf("红灯:暗\n"); //串口输出灯的状态
delay(5000);
gpio_set(LIGHT_GREEN,LIGHT_ON); //绿灯“亮”
printf("绿灯:亮\n"); //串口输出灯的状态
delay(5000);
gpio_set(LIGHT_GREEN,LIGHT_OFF); //绿灯“暗”
printf("绿灯:暗\n"); //串口输出灯的状态
delay(5000);
gpio_set(LIGHT_BLUE,LIGHT_ON); //蓝灯“亮”
printf("蓝灯:亮\n"); //串口输出灯的状态
delay(5000);
gpio_set(LIGHT_BLUE,LIGHT_OFF); //蓝灯“暗”
printf("蓝灯:暗\n"); //串口输出灯的状态
delay(5000);
//红+绿=黄灯亮
gpio_set(LIGHT_RED,LIGHT_ON);
gpio_set(LIGHT_GREEN,LIGHT_ON);
printf("黄灯:亮\n"); //串口输出灯的状态
delay(5000);
//黄灯暗
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_GREEN,LIGHT_OFF);
printf("黄灯:暗\n"); //串口输出灯的状态
delay(5000);
//红+蓝=紫灯亮
gpio_set(LIGHT_RED,LIGHT_ON);
gpio_set(LIGHT_BLUE,LIGHT_ON);
printf("紫灯:亮\n"); //串口输出灯的状态
delay(5000);
//紫灯暗
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
printf("紫灯:暗\n"); //串口输出灯的状态
delay(5000);
//绿+蓝=青灯亮
gpio_set(LIGHT_GREEN,LIGHT_ON);
gpio_set(LIGHT_BLUE,LIGHT_ON);
printf("青灯:亮\n"); //串口输出灯的状态
delay(5000);
//青灯暗
gpio_set(LIGHT_GREEN,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
printf("青灯:暗\n"); //串口输出灯的状态
delay(5000);
//红+绿+蓝=白灯亮
gpio_set(LIGHT_RED,LIGHT_ON);
gpio_set(LIGHT_GREEN,LIGHT_ON);
gpio_set(LIGHT_BLUE,LIGHT_ON);
printf("白灯:亮\n"); //串口输出灯的状态
delay(5000);
//全暗
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_GREEN,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
printf("全暗\n"); //串口输出灯的状态
delay(5000);
}
}
GPIO-Output-DirectAddress_STM32L431_20200928下的main.c:
#define GLOBLE_VAR
#include "includes.h" //包含总头文件
//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处
//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程见书稿)
int main(void)
{
//(1)======启动部分(开头)==========================================
//(1.1)声明main函数使用的局部变量
uint32_t mMainLoopCount; //主循环使用的记录主循环次数变量
uint8_t mFlag; //主循环使用的临时变量
//(1.2)【不变】关总中断
DISABLE_INTERRUPTS;
//(1.3)给主函数使用的局部变量赋初值
mMainLoopCount = 0; //主循环使用的记录主循环次数变量
//mFlag='A'; //主循环使用的临时变量:蓝灯状态标志
//(1.4)给全局变量赋初值
// 用户外设模块初始化
// B口9脚(蓝灯,低电平点亮)
// 声明变量
volatile uint32_t* RCC_AHB2; //GPIO的B口时钟使能寄存器地址
volatile uint32_t* gpio_ptr; //GPIO的B口基地址
volatile uint32_t* gpio_mode; //引脚模式寄存器地址=口基地址
volatile uint32_t* gpio_bsrr; //置位/复位寄存器地址
volatile uint32_t* gpio_brr; //GPIO位复位寄存器
// 变量赋值
RCC_AHB2 = (uint32_t*)0x4002104C; //GPIO的B口时钟使能寄存器地址
gpio_ptr = (uint32_t*)0x48000400; //GPIO的B口基地址
gpio_mode = gpio_ptr; //引脚模式寄存器地址=口基地址
gpio_bsrr = gpio_ptr + 6; //置位/复位寄存器地址
gpio_brr = gpio_ptr + 10; //GPIO位复位寄存器
// GPIO初始化
// 使能相应GPIOB的时钟
*RCC_AHB2 |= (1 << 1); //GPIOB的B口时钟使能
// 定义B口9脚为输出引脚
*gpio_mode &= ~(3 << 18); // 清除第 18 和第 19 位
*gpio_mode |= (1 << 18); // 设置第 18 位为输出模式
// 设置红灯引脚为输出引脚
*gpio_mode &= ~(3 << 14); // 清除第 14 和第 15 位
*gpio_mode |= (1 << 14); // 设置第 14 位为输出模式
// 设置绿灯引脚为输出引脚
*gpio_mode &= ~(3 << 16); // 清除第 16 和第 17 位
*gpio_mode |= (1 << 16); // 设置第 16 位为输出模式
// 开总中断
ENABLE_INTERRUPTS;
*gpio_bsrr |= (1 << 9); // 将蓝灯置位,即关闭
*gpio_bsrr |= (1 << 8); // 将绿灯置位,即关闭
*gpio_bsrr |= (1 << 7); // 将红灯置位,即关闭
mFlag='R'; //主循环使用的临时变量:初始状态设为红灯
printf("-----------------------------------------------------\r\n");
printf("金葫芦提示:直接地址方式进行GPIO输出\r\n");
printf(" 这个编程有点难以看懂,使用构件编程就简单多了,\r\n");
printf(" 但是构件制作要经过这一关,因此,我们把构件制作与\r\n");
printf(" 基于构件的编程分成不同过程。学习嵌入式系统,\r\n");
printf(" 以理解GPIO、UART、定时器、Flash、ADC、...\r\n");
printf(" 知识要素为出发点,学会正确运用构件进行应用编程,\r\n");
printf(" 理解和掌握2~3个简单构件的制作方法即可。\r\n");
printf("---------------------广州大学LYY------------------------\r\n");
//for(;;) { } //在此打桩,理解蓝色发光二极管为何亮起来了?
//(1)======启动部分(结尾)==========================================
//(2)======主循环部分(开头)=========================================
for(;;) //for(;;)(开头)
{
//(2.1)主循环次数+1,并判断是否小于特定常数
mMainLoopCount++; //+1
if (mMainLoopCount<=6556677) continue; //如果小于特定常数,继续循环
//(2.2)主循环次数超过特定常数,灯状态进行切换(这样灯会闪烁)
mMainLoopCount=0; //清主循环次数
//切换灯状态
if (mFlag=='R') //若灯状态标志为'R'
{
*gpio_bsrr|=(1<<9); //设置蓝灯“灭”
*gpio_brr|=(1<<7); //设置红灯“亮”
printf("蓝灯:灭\r\n"); //通过调试串口输出灯的状态
printf("红灯:亮\r\n");
mFlag='G'; //改变状态标志
}
else if (mFlag=='G') //若灯状态标志为'G'
{
*gpio_bsrr|=(1<<7); //设置红灯“暗”
*gpio_brr|=(1<<8); //设置绿灯“亮”
printf("红灯:暗\r\n"); //通过调试串口输出灯的状态
printf("绿灯:亮\r\n"); //通过调试串口输出灯的状态
mFlag='B'; //改变状态标志
}
else
{
*gpio_bsrr|=(1<<8); //设置绿灯“暗”
*gpio_brr|=(1<<9); //设置蓝灯“亮”
printf("绿灯:暗\r\n"); //通过调试串口输出灯的状态
printf("蓝灯:亮\r\n"); //通过调试串口输出灯的状态
mFlag='R'; //改变状态标志
}
} //for(;;)结尾
//(2)======主循环部分(结尾)========================================
}
gpio.c:
//===========================================================================
//文件名称:gpio.c
//功能概要:GPIO底层驱动构件源文件
//版权所有:SD-Arm(sumcu.suda.edu.cn)
//版本更新:20181201-20200221
//芯片类型:STM32
//===========================================================================
#include "gpio.h"
//GPIO口基地址放入常数数据组GPIO_ARR[0]~GPIO_ARR[5]中
GPIO_TypeDef * GPIO_ARR[] =
{(GPIO_TypeDef *)GPIOA_BASE,(GPIO_TypeDef *)GPIOB_BASE,
(GPIO_TypeDef *)GPIOC_BASE,(GPIO_TypeDef *)GPIOD_BASE,
(GPIO_TypeDef *)GPIOE_BASE,(GPIO_TypeDef *)GPIOH_BASE};
//====定义扩展中断IRQ号对应表====
IRQn_Type table_irq_exti[7] = {EXTI0_IRQn, EXTI1_IRQn, EXTI2_IRQn,
EXTI3_IRQn, EXTI4_IRQn, EXTI9_5_IRQn, EXTI15_10_IRQn};
//内部函数声明
void gpio_get_port_pin(uint16_t port_pin,uint8_t* port,uint8_t* pin);
//=====================================================================
//函数名称:gpio_init
//函数返回:无
//参数说明:port_pin:(端口号)|(引脚号)(如:(PTB_NUM)|(9) 表示为B口9号脚)
// dir:引脚方向(0=输入,1=输出,可用引脚方向宏定义)
// state:端口引脚初始状态(0=低电平,1=高电平)
//功能概要:初始化指定端口引脚作为GPIO引脚功能,并定义为输入或输出,若是输出,
// 还指定初始状态是低电平或高电平
//=====================================================================
void gpio_init(uint16_t port_pin, uint8_t dir, uint8_t state)
{
GPIO_TypeDef *gpio_ptr; //声明gpio_ptr为GPIO结构体类型指针
uint8_t port,pin; //声明端口port、引脚pin变量
uint32_t temp; //临时存放寄存器里的值
//根据带入参数port_pin,解析出端口与引脚分别赋给port,pin
gpio_get_port_pin(port_pin,&port,&pin);
//根据port,给局部变量gpio_ptr赋值(GPIO基地址)
if(7 == port) //GPIOH
gpio_ptr = GPIO_ARR[port-2];
else
gpio_ptr = GPIO_ARR[port];
//使能相应GPIO时钟
RCC->AHB2ENR |= (RCC_AHB2ENR_GPIOAEN<<(port * 1u));
//清GPIO模式寄存器对应引脚位
temp = gpio_ptr->MODER;
temp &= ~(GPIO_MODER_MODE0 << (pin * 2u));
if(dir == 1) //定义为输出引脚
{
temp |= (GPIO_OUTPUT << (pin * 2u));
gpio_ptr->MODER = temp;
gpio_set(port_pin,state); //调用gpio_set函数,设定引脚初始状态
}
else //定义为输入引脚
{
temp |= (GPIO_INPUT << (pin * 2u));
gpio_ptr->MODER = temp;
}
}
//=====================================================================
//函数名称:gpio_set
//函数返回:无
//参数说明:port_pin:(端口号)|(引脚号)(如:(PTB_NUM)|(9) 表示为B口9号脚)
// state:希望设置的端口引脚状态(0=低电平,1=高电平)
//功能概要:当指定端口引脚被定义为GPIO功能且为输出时,本函数设定引脚状态
//=====================================================================
void gpio_set(uint16_t port_pin, uint8_t state)
{
//局部变量声明
GPIO_TypeDef *gpio_ptr; //声明port_ptr为GPIO结构体类型指针(首地址)
uint8_t port,pin; //声明端口port、引脚pin变量
//根据带入参数port_pin,解析出端口与引脚分别赋给port,pin
gpio_get_port_pin(port_pin,&port,&pin);
//根据port,给局部变量gpio_ptr赋值(GPIO基地址)
if(7 == port) //GPIOH
gpio_ptr = GPIO_ARR[port-2];
else
gpio_ptr = GPIO_ARR[port];
//根据state,设置对应引脚状态
if(1 == state) //高电平(该引脚对应置位寄存器置1)
gpio_ptr->BSRR = (uint32_t)(1u<<pin);
else //低电平(该引脚对应重置寄存器置1)
gpio_ptr->BRR = (uint32_t)(1u<<pin);
}
//=====================================================================
//函数名称:gpio_get
//函数返回:指定端口引脚的状态(1或0)
//参数说明:port_pin:(端口号)|(引脚号)(如:(PTB_NUM)|(9) 表示为B口9号脚)
//功能概要:当指定端口引脚被定义为GPIO功能且为输入时,本函数获取指定引脚状态
//=====================================================================
uint8_t gpio_get(uint16_t port_pin)
{
//局部变量声明
GPIO_TypeDef *gpio_ptr; //声明port_ptr为GPIO结构体类型指针(首地址)
uint8_t port,pin; //声明端口port、引脚pin变量
uint32_t temp;
uint8_t value;
//根据带入参数port_pin,解析出端口与引脚分别赋给port,pin
gpio_get_port_pin(port_pin,&port,&pin);
//根据port,给局部变量gpio_ptr赋值(GPIO基地址)
if(7 == port) //GPIOH
gpio_ptr = GPIO_ARR[port-2];
else
gpio_ptr = GPIO_ARR[port];
//获得状态寄存器的值
temp = gpio_ptr->MODER;
if( (temp & (2u<<(pin*2)))>>(pin*2) == 1u )//GPIO输出
{
//读取Output data寄存器对应引脚的值
temp = gpio_ptr->ODR;
if((temp & (1u<<pin)) != 0x00u)
value = 1;
else
value = 0;
}
else //GPIO输入
{
//读取Input data寄存器对应引脚的值
temp = gpio_ptr->IDR;
if((temp & (1u<<pin)) != 0x00u)
value = 1;
else
value = 0;
}
return value;
}
//=====================================================================
//函数名称:gpio_reverse
//函数返回:无
//参数说明:port_pin:(端口号)|(引脚号)(如:(PTB_NUM)|(9) 表示为B口9号脚)
//功能概要:当指定端口引脚被定义为GPIO功能且为输出时,本函数反转引脚状态
//=====================================================================
void gpio_reverse(uint16_t port_pin)
{
//局部变量声明
GPIO_TypeDef *gpio_ptr; //声明port_ptr为GPIO结构体类型指针(首地址)
uint8_t port,pin; //声明端口port、引脚pin变量
//根据带入参数port_pin,解析出端口与引脚分别赋给port,pin
gpio_get_port_pin(port_pin,&port,&pin);
//根据port,给局部变量gpio_ptr赋值(GPIO基地址)
if(7 == port) //GPIOH
gpio_ptr = GPIO_ARR[port-2];
else
gpio_ptr = GPIO_ARR[port];
//判断引脚输出状态
if ((gpio_ptr->ODR & (1u<<pin)) != 0x00u)
//高电平,则反转为低电平
gpio_ptr->BRR = (uint32_t)(1u<<pin);
else
//低电平,则反转为高电平
gpio_ptr->BSRR = (uint32_t)(1u<<pin);
}
//=====================================================================
//函数名称:gpio_pull
//函数返回:无
//参数说明:port_pin:(端口号)|(引脚号)(如:(PTB_NUM)|(9) 表示为B口9号脚)
// pullselect:下拉/上拉(PULL_DOWN=下拉,PULL_UP=上拉)
//功能概要:当指定端口引脚被定义为GPIO功能且为输入时,本函数设置引脚下拉/上拉
//=====================================================================
void gpio_pull(uint16_t port_pin, uint8_t pullselect)
{
GPIO_TypeDef *gpio_ptr; //声明gpio_ptr为GPIO结构体类型指针
uint8_t port,pin; //声明端口port、引脚pin变量
//根据带入参数port_pin,解析出端口与引脚分别赋给port,pin
gpio_get_port_pin(port_pin,&port,&pin);
//根据port,给局部变量gpio_ptr赋值(GPIO基地址)
if(7 == port) //GPIOH
gpio_ptr = GPIO_ARR[port-2];
else
gpio_ptr = GPIO_ARR[port];
//设定为高速输出状态
gpio_ptr->OSPEEDR |= (uint32_t)(HIGH_SPEED << (pin * 2u));
if(1 == pullselect)
//引脚拉高电平
gpio_ptr->PUPDR |= (uint32_t)(PULL_UP << (pin * 2u));
else
//引脚拉低电平
gpio_ptr->PUPDR |= (uint32_t)(PULL_DOWN << (pin * 2u));
}
//=====================================================================
//函数名称:gpio_enable_int
//函数返回:无
//参数说明:port_pin:(端口号)|(引脚号)(如:(PTB_NUM)|(9) 表示为B口9号脚)
// irqtype:引脚中断类型,由宏定义给出,再次列举如下:
// RISING_EDGE 9 //上升沿触发
// FALLING_EDGE 10 //下降沿触发
// DOUBLE_EDGE 11 //双边沿触发
//功能概要:当指定端口引脚被定义为GPIO功能且为输入时,本函数开启引脚中断,并
// 设置中断触发条件。
//注 意: KL25芯片,只有PORTA、PORTD口具有GPIO类中断功能
// KW01芯片,只有PORTA、PORTC、PORTD口具有GPIO类中断功能
// KL36芯片,只有PORTA、PORTC、PORTD口具有GPIO类中断功能
// STM32L433芯片,PORTA、PORTB、PORTC、PORTH口具有GPIO类中断功能,在EXIT(扩展
// 中断)处配置,且只包含上升沿触发、下降沿触发、双边沿触发三种引脚中断类型
//=====================================================================
void gpio_enable_int(uint16_t port_pin,uint8_t irqtype)
{
uint8_t port,pin; //声明端口port、引脚pin变量
uint32_t temp; //临时存放寄存器里的值
//根据带入参数port_pin,解析出端口与引脚分别赋给port,pin
gpio_get_port_pin(port_pin,&port,&pin);
//使能SYSCFG时钟
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
//配置对应引脚的扩展中断
temp = SYSCFG->EXTICR[pin >> 2u];
temp &= ~(0x0FuL << (4u * (pin & 0x03u)));
temp |= (port << (4u * (pin & 0x03u)));
SYSCFG->EXTICR[pin >> 2u] = temp;
//允许对应扩展中断行的请求
EXTI->IMR1 |= (uint32_t)(1u<<pin);
//若上升沿或双边沿触发,使能输入线上升沿触发
if(RISING_EDGE == irqtype || DOUBLE_EDGE == irqtype)
EXTI->RTSR1 |= (uint32_t)(1u<<pin);
//若下降沿或双边沿触发,使能输入线下降沿触发
if(FALLING_EDGE == irqtype || DOUBLE_EDGE == irqtype)
EXTI->FTSR1 |= (uint32_t)(1u<<pin);
if(pin >= 0 && pin <= 4)
//开中断控制器IRQ中断(EXTIx,x=0-4)
NVIC_EnableIRQ(table_irq_exti[pin]);
else if(pin >= 5 && pin <= 9)
//开中断控制器IRQ中断(EXTI9_5)
NVIC_EnableIRQ(table_irq_exti[5]);
else
//开中断控制器IRQ中断(EXTI15_10)
NVIC_EnableIRQ(table_irq_exti[6]);
}
//=====================================================================
//函数名称:gpio_disable_int
//函数返回:无
//参数说明:port_pin:(端口号)|(引脚号)(如:(PTB_NUM)|(9) 表示为B口9号脚)
//功能概要:当指定端口引脚被定义为GPIO功能且为输入时,本函数关闭引脚中断
//注 意: KL25芯片,只有PORTA、PORTD口具有GPIO类中断功能
// KW01芯片,只有PORTA、PORTC、PORTD口具有GPIO类中断功能
// KL36芯片,只有PORTA、PORTC、PORTD口具有GPIO类中断功能
// STM32L433芯片,PORTA、PORTB、PORTC、PORTH口具有GPIO类中断功能,在EXIT(扩展
// 中断)处配置,且只包含上升沿触发、下降沿触发、双边沿触发三种引脚中断类型
//=====================================================================
void gpio_disable_int(uint16_t port_pin)
{
uint8_t port,pin; //声明端口port、引脚pin变量
//根据带入参数port_pin,解析出端口与引脚分别赋给port,pin
gpio_get_port_pin(port_pin,&port,&pin);
//禁止SYSCFG时钟
RCC->APB2ENR &= ~RCC_APB2ENR_SYSCFGEN;
//清对应引脚的扩展中断
SYSCFG->EXTICR[pin >> 2u] &= ~(0x0FuL << (4u * (pin & 0x03u)));
//屏蔽对应扩展中断行的请求
EXTI->IMR1 &= (uint32_t)~(1u<<pin);
//禁止上升沿下降沿触发
EXTI->RTSR1 &= (uint32_t)~(1u<<pin);
EXTI->FTSR1 &= (uint32_t)~(1u<<pin);
if(pin >= 0 && pin <= 4)
//开中断控制器IRQ中断(EXTIx,x=0-4)
NVIC_DisableIRQ(table_irq_exti[pin]);
else if(pin >= 5 && pin <= 9)
//开中断控制器IRQ中断(EXTI9_5)
NVIC_DisableIRQ(table_irq_exti[5]);
else
//开中断控制器IRQ中断(EXTI15_10)
NVIC_DisableIRQ(table_irq_exti[6]);
}
//=====================================================================
//函数名称:gpio_drive_strength
//函数返回:无
//参数说明:port_pin:(端口号)|(引脚号)(如:(PTB_NUM)|(9) 表示为B口9号脚)
// control:控制引脚的驱动能力,LOW_SPEED=低速,MSDIUM_SPEED=中速
// HIGH_SPEED=高速,VERY_HIGH_SPEED=超高速
//功能概要:(引脚驱动能力:指引脚输入或输出电流的承受力,一般用mA单位度量
// ,正常驱动能力5mA,高驱动能力18mA。)当引脚被配置为数字输出时,
// 对引脚的驱动能力进行设置
//=====================================================================
void gpio_drive_strength(uint16_t port_pin, uint8_t control)
{
GPIO_TypeDef *gpio_ptr; //声明gpio_ptr为GPIO结构体类型指针
uint8_t port,pin; //声明端口port、引脚pin变量
// uint32_t temp; //临时存放寄存器里的值
//根据带入参数port_pin,解析出端口与引脚分别赋给port,pin
gpio_get_port_pin(port_pin,&port,&pin);
//根据port,给局部变量gpio_ptr赋值(GPIO基地址)
if(7 == port) //GPIOH
gpio_ptr = GPIO_ARR[port-2];
else
gpio_ptr = GPIO_ARR[port];
//配置引脚的驱动能力
gpio_ptr->OSPEEDR |= (uint32_t)(control << (pin * 2u));
}
//=====================================================================
//函数名称:gpio_get_int
//函数返回:引脚GPIO中断标志(1或0)1表示引脚有GPIO中断,0表示引脚没有GPIO中断。
//参数说明:port_pin:(端口号)|(引脚号)(如:(PTB_NUM)|(9) 表示为B口9号脚)
//功能概要:当指定端口引脚被定义为GPIO功能且为输入时,获取中断标志。
//注 意: KL25芯片,只有PORTA、PORTD口具有GPIO类中断功能
// KW01芯片,只有PORTA、PORTC、PORTD口具有GPIO类中断功能
// KL36芯片,只有PORTA、PORTC、PORTD口具有GPIO类中断功能
// STM32L433芯片,PORTA、PORTB、PORTC、PORTH口具有GPIO类中断功能,在EXIT(扩展
// 中断)处配置,且只包含上升沿触发、下降沿触发、双边沿触发三种引脚中断类型
//=====================================================================
uint8_t gpio_get_int(uint16_t port_pin)
{
uint8_t port,pin; //声明端口port、引脚pin变量
//根据带入参数port_pin,解析出端口与引脚分别赋给port,pin
gpio_get_port_pin(port_pin,&port,&pin);
//获取GPIO中断标志
if((EXTI->PR1 & (1u << pin)) == (1u << pin))
return 1;
else
return 0;
}
//=====================================================================
//函数名称:gpio_clear_int
//函数返回:无
//参数说明:port_pin:(端口号)|(引脚号)(如:(PTB_NUM)|(9) 表示为B口9号脚)
//功能概要:当指定端口引脚被定义为GPIO功能且为输入时,清除中断标志。
//注 意: KL25芯片,只有PORTA、PORTD口具有GPIO类中断功能
// KW01芯片,只有PORTA、PORTC、PORTD口具有GPIO类中断功能
// KL36芯片,只有PORTA、PORTC、PORTD口具有GPIO类中断功能
// STM32L433芯片,PORTA、PORTB、PORTC、PORTH口具有GPIO类中断功能,在EXIT(扩展
// 中断)处配置,且只包含上升沿触发、下降沿触发、双边沿触发三种引脚中断类型
//=====================================================================
void gpio_clear_int(uint16_t port_pin)
{
uint8_t port,pin; //声明端口port、引脚pin变量
//根据带入参数port_pin,解析出端口与引脚分别赋给port,pin
gpio_get_port_pin(port_pin,&port,&pin);
//清GPIO中断标志
EXTI->PR1 |= (1u << pin);
}
//=====================================================================
//函数名称:gpio_clear_allint
//函数返回:无
//参数说明:无
//功能概要:清除所有端口的GPIO中断
//注 意: KL25芯片,只有PORTA、PORTD口具有GPIO类中断功能
// KW01芯片,只有PORTA、PORTC、PORTD口具有GPIO类中断功能
// KL36芯片,只有PORTA、PORTC、PORTD口具有GPIO类中断功能
// STM32L433芯片,PORTA、PORTB、PORTC、PORTH口具有GPIO类中断功能,在EXIT(扩展
// 中断)处配置,且只包含上升沿触发、下降沿触发、双边沿触发三种引脚中断类型
//=====================================================================
void gpio_clear_allint(void)
{
EXTI->PR1 |= 0xFFFF;//pin可取0-15
}
//----------------------以下为内部函数存放处-----------------
//=====================================================================
//函数名称:gpio_get_port_pin
//函数返回:无
//参数说明:port_pin:端口号|引脚号(如:(PTB_NUM)|(9) 表示为B口9号脚)
// port:端口号(传指带出参数)
// pin:引脚号(0~15,实际取值由芯片的物理引脚决定)(传指带出参数)
//功能概要:将传进参数port_pin进行解析,得出具体端口号与引脚号,分别赋值给port与pin,返回。
// (例:(PTB_NUM)|(9)解析为PORTB与9,并将其分别赋值给port与pin)。
//=====================================================================
void gpio_get_port_pin(uint16_t port_pin,uint8_t* port,uint8_t* pin)
{
*port = (port_pin>>8);
*pin = port_pin;
}
二、给出gpio_set(LIGHT_RED,LIGHT_OFF);语句中,LIGHT_RED和LIGHT_OFFF的值是多少?贴出每一步的查找截图。
main.c中:
main.c头文件includes.h:
找到头文件
找到user.h:
LIGHT_RED
的值是 PTB_NUM|7,LIGHT_OFF
的值是 1
。
三、
用GPIO端口的直接地址编程方式,实现红绿蓝三灯轮流闪烁。
通过CH04中的工程GPIO-BlueLight_20230328,找到LIGHT_RED,右键选择查看定义查看
红灯对应7号引脚,绿灯对应8号引脚,蓝灯对应9号引脚。
设计for循环:
部分代码:
//初始化GPIO:
volatile uint32_t* RCC_AHB2; //GPIO的B口时钟使能寄存器地址
volatile uint32_t* gpio_ptr; //GPIO的B口基地址
volatile uint32_t* gpio_mode; //引脚模式寄存器地址=口基地址
volatile uint32_t* gpio_bsrr; //置位/复位寄存器地址
volatile uint32_t* gpio_brr; //GPIO位复位寄存器
// 变量赋值
RCC_AHB2 = (uint32_t*)0x4002104C; //GPIO的B口时钟使能寄存器地址
gpio_ptr = (uint32_t*)0x48000400; //GPIO的B口基地址
gpio_mode = gpio_ptr; //引脚模式寄存器地址=口基地址
gpio_bsrr = gpio_ptr + 6; //置位/复位寄存器地址
gpio_brr = gpio_ptr + 10; //GPIO位复位寄存器
// GPIO初始化
// 使能相应GPIOB的时钟
*RCC_AHB2 |= (1 << 1); //GPIOB的B口时钟使能
// 定义B口9脚为输出引脚
*gpio_mode &= ~(3 << 18); // 清除第 18 和第 19 位
*gpio_mode |= (1 << 18); // 设置第 18 位为输出模式
// 设置红灯引脚为输出引脚
*gpio_mode &= ~(3 << 14); // 清除第 14 和第 15 位
*gpio_mode |= (1 << 14); // 设置第 14 位为输出模式
// 设置绿灯引脚为输出引脚
*gpio_mode &= ~(3 << 16); // 清除第 16 和第 17 位
*gpio_mode |= (1 << 16); // 设置第 16 位为输出模式
// 开总中断
ENABLE_INTERRUPTS;
*gpio_bsrr |= (1 << 9); // 将蓝灯置位,即关闭
*gpio_bsrr |= (1 << 8); // 将绿灯置位,即关闭
*gpio_bsrr |= (1 << 7); // 将红灯置位,即关闭
//循环代码:
for(;;) //for(;;)(开头)
{
//(2.1)主循环次数+1,并判断是否小于特定常数
mMainLoopCount++; //+1
if (mMainLoopCount<=6556677) continue; //如果小于特定常数,继续循环
//(2.2)主循环次数超过特定常数,灯状态进行切换(这样灯会闪烁)
mMainLoopCount=0; //清主循环次数
//切换灯状态
if (mFlag=='R') //若灯状态标志为'R'
{
*gpio_bsrr|=(1<<9); //设置蓝灯“灭”
*gpio_brr|=(1<<7); //设置红灯“亮”
printf("蓝灯:灭\r\n"); //通过调试串口输出灯的状态
printf("红灯:亮\r\n");
mFlag='G'; //改变状态标志
}
else if (mFlag=='G') //若灯状态标志为'G'
{
*gpio_bsrr|=(1<<7); //设置红灯“暗”
*gpio_brr|=(1<<8); //设置绿灯“亮”
printf("红灯:暗\r\n"); //通过调试串口输出灯的状态
printf("绿灯:亮\r\n"); //通过调试串口输出灯的状态
mFlag='B'; //改变状态标志
}
else
{
*gpio_bsrr|=(1<<8); //设置绿灯“暗”
*gpio_brr|=(1<<9); //设置蓝灯“亮”
printf("绿灯:暗\r\n"); //通过调试串口输出灯的状态
printf("蓝灯:亮\r\n"); //通过调试串口输出灯的状态
mFlag='R'; //改变状态标志
}
}
运行截图:
四、用调用GPIO构件方式,实现红绿蓝的八种组合轮流闪烁。
部分代码:
#define GLOBLE_VAR
#include "includes.h" //包含总头文件
#include <stdint.h>
// 简单的延时函数
void delay(uint32_t milliseconds) {
for (volatile uint32_t i = 0; i < (milliseconds * 1000); i++) {
// 空循环,用于延时
}
}
int main() {
gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF); //初始化红灯
gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF); //初始化绿灯
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF); //初始化蓝灯
while(1)
{
gpio_set(LIGHT_RED,LIGHT_ON); //红灯“亮”
printf("红灯:亮\n"); //串口输出灯的状态
delay(5000);
gpio_set(LIGHT_RED,LIGHT_OFF); //红灯“暗”
printf("红灯:暗\n"); //串口输出灯的状态
delay(5000);
gpio_set(LIGHT_GREEN,LIGHT_ON); //绿灯“亮”
printf("绿灯:亮\n"); //串口输出灯的状态
delay(5000);
gpio_set(LIGHT_GREEN,LIGHT_OFF); //绿灯“暗”
printf("绿灯:暗\n"); //串口输出灯的状态
delay(5000);
gpio_set(LIGHT_BLUE,LIGHT_ON); //蓝灯“亮”
printf("蓝灯:亮\n"); //串口输出灯的状态
delay(5000);
gpio_set(LIGHT_BLUE,LIGHT_OFF); //蓝灯“暗”
printf("蓝灯:暗\n"); //串口输出灯的状态
delay(5000);
//红+绿=黄灯亮
gpio_set(LIGHT_RED,LIGHT_ON);
gpio_set(LIGHT_GREEN,LIGHT_ON);
printf("黄灯:亮\n"); //串口输出灯的状态
delay(5000);
//黄灯暗
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_GREEN,LIGHT_OFF);
printf("黄灯:暗\n"); //串口输出灯的状态
delay(5000);
//红+蓝=紫灯亮
gpio_set(LIGHT_RED,LIGHT_ON);
gpio_set(LIGHT_BLUE,LIGHT_ON);
printf("紫灯:亮\n"); //串口输出灯的状态
delay(5000);
//紫灯暗
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
printf("紫灯:暗\n"); //串口输出灯的状态
delay(5000);
//绿+蓝=青灯亮
gpio_set(LIGHT_GREEN,LIGHT_ON);
gpio_set(LIGHT_BLUE,LIGHT_ON);
printf("青灯:亮\n"); //串口输出灯的状态
delay(5000);
//青灯暗
gpio_set(LIGHT_GREEN,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
printf("青灯:暗\n"); //串口输出灯的状态
delay(5000);
//红+绿+蓝=白灯亮
gpio_set(LIGHT_RED,LIGHT_ON);
gpio_set(LIGHT_GREEN,LIGHT_ON);
gpio_set(LIGHT_BLUE,LIGHT_ON);
printf("白灯:亮\n"); //串口输出灯的状态
delay(5000);
//全暗
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_GREEN,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
printf("全暗\n"); //串口输出灯的状态
delay(5000);
}
}
运行截图:
输出:
红灯->绿灯->蓝灯->红+绿+黄灯->红+蓝=紫灯->蓝+绿=青灯->红+绿+蓝=白灯->全暗
五、学习心得
通过这次实验我学到了实验开发环境及GPIO编程的基本知识,在前两个小题中,我学习了main.s源文件,认识到,main.s 源文件通常是汇编语言文件,用于编写启动代码和一些底层的初始化功能。这个文件通常包含了系统的启动代码,初始化内存、中断向量表、外设等,并且调用主函数 main()。在后面两个小题中,我学习了用GPIO端口的直接地址编程方式,以及用调用GPIO构件方式,实现红绿蓝轮流闪烁。在这个过程中需要初始化GPIO,要正确设置了红灯、绿灯、蓝灯的引脚模式。确保正确配置了引脚模式寄存器,以使红绿蓝灯能够正常工作。最后一小题通过让三个小灯不同时间亮暗达到组合成新的颜色灯效果,其中也设置一个延时函数使得有利于观察亮暗情况。