当前位置:网站首页>潘多拉 IOT 开发板学习(RT-Thread)—— 实验4 蜂鸣器+马达实验【按键外部中断】(学习笔记)
潘多拉 IOT 开发板学习(RT-Thread)—— 实验4 蜂鸣器+马达实验【按键外部中断】(学习笔记)
2022-07-05 00:41:00 【小辉_Super】
本文代码参考 RT-Thread 官方 BSP
文章目录
实验功能
例程源码:(main.c)
该实验实现的功能:4 个按键对应不同的功能(控制电机左转或右转,控制蜂鸣器响灭),其中 3 个按键是通过外部中断的方式检测的(另外的一个按键通过轮询检测),大部分操作代码在中断服务(回调)函数中,电机控制和蜂鸣器控制的代码很简单,全是 GPIO 写操作,就不单独分析了。
/* * Copyright (c) 2006-2018, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2018-08-23 balanceTWK first implementation */
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
enum
{
MOTOR_STOP,
MOTOR_LEFT,
MOTOR_RIGHT
};
/* 电机控制 */
void motor_ctrl(rt_uint8_t turn)
{
if (turn == MOTOR_STOP)
{
rt_pin_write(PIN_MOTOR_A, PIN_LOW);
rt_pin_write(PIN_MOTOR_B, PIN_LOW);
}
else if (turn == MOTOR_LEFT)
{
rt_pin_write(PIN_MOTOR_A, PIN_LOW);
rt_pin_write(PIN_MOTOR_B, PIN_HIGH);
}
else if (turn == MOTOR_RIGHT)
{
rt_pin_write(PIN_MOTOR_A, PIN_HIGH);
rt_pin_write(PIN_MOTOR_B, PIN_LOW);
}
else
{
LOG_D("err parameter ! Please enter 0-2.");
}
}
void beep_ctrl(rt_uint8_t on)
{
if (on)
{
rt_pin_write(PIN_BEEP, PIN_HIGH);
}
else
{
rt_pin_write(PIN_BEEP, PIN_LOW);
}
}
/* 中断回调 */
void irq_callback(void *args)
{
rt_uint32_t sign = (rt_uint32_t)args;
switch (sign)
{
case PIN_KEY0:
motor_ctrl(MOTOR_LEFT);
LOG_D("KEY0 interrupt. motor turn left.");
break;
case PIN_KEY1:
motor_ctrl(MOTOR_RIGHT);
LOG_D("KEY1 interrupt. motor turn right.");
break;
case PIN_KEY2:
motor_ctrl(MOTOR_STOP);
LOG_D("KEY2 interrupt. motor stop.");
break;
default:
LOG_E("error sign= %d !", sign);
break;
}
}
int main(void)
{
unsigned int count = 1;
/* 设置按键引脚为输入模式 */
rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP);
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLUP);
rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT_PULLUP);
rt_pin_mode(PIN_WK_UP, PIN_MODE_INPUT_PULLDOWN);
/* 设置电机控制引脚为输入模式 */
rt_pin_mode(PIN_MOTOR_A, PIN_MODE_OUTPUT);
rt_pin_mode(PIN_MOTOR_B, PIN_MODE_OUTPUT);
/* 设置蜂鸣器引脚为输出模式 */
rt_pin_mode(PIN_BEEP, PIN_MODE_OUTPUT);
/* 设置按键中断模式与中断回调函数 */
rt_pin_attach_irq(PIN_KEY0, PIN_IRQ_MODE_FALLING, irq_callback, (void *)PIN_KEY0);
rt_pin_attach_irq(PIN_KEY1, PIN_IRQ_MODE_FALLING, irq_callback, (void *)PIN_KEY1);
rt_pin_attach_irq(PIN_KEY2, PIN_IRQ_MODE_FALLING, irq_callback, (void *)PIN_KEY2);
/* 使能中断 */
rt_pin_irq_enable(PIN_KEY0, PIN_IRQ_ENABLE);
rt_pin_irq_enable(PIN_KEY1, PIN_IRQ_ENABLE);
rt_pin_irq_enable(PIN_KEY2, PIN_IRQ_ENABLE);
while (count > 0)
{
if (rt_pin_read(PIN_WK_UP) == PIN_HIGH)
{
rt_thread_mdelay(50);
if (rt_pin_read(PIN_WK_UP) == PIN_HIGH)
{
LOG_D("WK_UP pressed. beep on.");
beep_ctrl(1);
}
}
else
{
beep_ctrl(0);
}
rt_thread_mdelay(10);
count++;
}
return 0;
}
代码剖析
rt_pin_mode()
该函数的作用是 GPIO Pin 的初始化,定义为
/* RT-Thread Hardware PIN APIs */
void rt_pin_mode(rt_base_t pin, rt_base_t mode)
{
RT_ASSERT(_hw_pin.ops != RT_NULL);
_hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode);
}
参数 pin 是一个 rt_base_t 变量(long),下面的 GET_PIN()
是 STM32 的 pin 值宏定义,第一个参数填大写字母,第二个参数填数字。
#define GET_PIN(PORTx,PIN) (rt_base_t)((16 * ( ((rt_base_t)__STM32_PORT(PORTx) - (rt_base_t)GPIOA)/(0x0400UL) )) + PIN)
#define __STM32_PORT(port) GPIO##port // ## 是字符连接符,假如 port 为 A,则表示 GPIOA
例如实验中的
#define PIN_LED_R GET_PIN(E, 7)
,表示 GPIOE GPIO_Pin7
目前 RT-Thread 支持的引脚工作模式包括:
#define PIN_MODE_OUTPUT 0x00 /* 输出 */
#define PIN_MODE_INPUT 0x01 /* 输入 */
#define PIN_MODE_INPUT_PULLUP 0x02 /* 上拉输入 */
#define PIN_MODE_INPUT_PULLDOWN 0x03 /* 下拉输入 */
#define PIN_MODE_OUTPUT_OD 0x04 /* 开漏输出 */
在 bsp 的 drv_gpio.c
文件中,有底层 GPIO 驱动,下面是 STM32 的 GPIO 模式设置的驱动函数(大家应该很熟悉,就是用 HAL 库写的 GPIO 初始化代码)
static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode)
{
const struct pin_index *index;
GPIO_InitTypeDef GPIO_InitStruct;
index = get_pin(pin);
if (index == RT_NULL)
{
return;
}
/* Configure GPIO_InitStructure */
GPIO_InitStruct.Pin = index->pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
if (mode == PIN_MODE_OUTPUT)
{
/* output setting */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
}
else if (mode == PIN_MODE_INPUT)
{
/* input setting: not pull. */
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
}
else if (mode == PIN_MODE_INPUT_PULLUP)
{
/* input setting: pull up. */
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
}
else if (mode == PIN_MODE_INPUT_PULLDOWN)
{
/* input setting: pull down. */
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
}
else if (mode == PIN_MODE_OUTPUT_OD)
{
/* output setting: od. */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
}
HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);
}
rt_pin_attach_irq()
这是 RT-Thread 中断绑定(注册)函数,它会调用当前平台驱动中的相应函数。
rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode,
void (*hdr)(void *args), void *args)
{
RT_ASSERT(_hw_pin.ops != RT_NULL);
if(_hw_pin.ops->pin_attach_irq)
{
return _hw_pin.ops->pin_attach_irq(&_hw_pin.parent, pin, mode, hdr, args);
}
return RT_ENOSYS;
}
bsp 驱动(drv_gpio.c)中定义了 STM32 的中断注册函数 stm32_pin_attach_irq()
:
static rt_err_t stm32_pin_attach_irq(struct rt_device *device, rt_int32_t pin,
rt_uint32_t mode, void (*hdr)(void *args), void *args)
{
const struct pin_index *index;
rt_base_t level;
rt_int32_t irqindex = -1;
index = get_pin(pin);
if (index == RT_NULL)
{
return RT_ENOSYS;
}
irqindex = bit2bitno(index->pin);
if (irqindex < 0 || irqindex >= ITEM_NUM(pin_irq_map))
{
return RT_ENOSYS;
}
level = rt_hw_interrupt_disable();
if (pin_irq_hdr_tab[irqindex].pin == pin &&
pin_irq_hdr_tab[irqindex].hdr == hdr &&
pin_irq_hdr_tab[irqindex].mode == mode &&
pin_irq_hdr_tab[irqindex].args == args)
{
rt_hw_interrupt_enable(level);
return RT_EOK;
}
if (pin_irq_hdr_tab[irqindex].pin != -1)
{
rt_hw_interrupt_enable(level);
return RT_EBUSY;
}
pin_irq_hdr_tab[irqindex].pin = pin;
pin_irq_hdr_tab[irqindex].hdr = hdr;
pin_irq_hdr_tab[irqindex].mode = mode;
pin_irq_hdr_tab[irqindex].args = args;
rt_hw_interrupt_enable(level);
return RT_EOK;
}
attach 函数的原理很简单,就是将当前中断信息存放到驱动代码中的一个中断表中,中断表的结构体定义为:
struct rt_pin_irq_hdr
{
rt_int16_t pin;
rt_uint16_t mode;
void (*hdr)(void *args); // 中断回调函数
void *args;
};
rt_pin_irq_enable()
这是 RT-Thread 内核的 pin 中断使能函数,实际操作的是平台驱动对应函数,
rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled)
{
RT_ASSERT(_hw_pin.ops != RT_NULL);
if(_hw_pin.ops->pin_irq_enable)
{
return _hw_pin.ops->pin_irq_enable(&_hw_pin.parent, pin, enabled);
}
return RT_ENOSYS;
}
STM32 平台驱动中的中断使能函数如下,代码量还是很大的(HAL 库中外部中断的相关配置,由于要考虑不同参数选项,所以代码量大):
static rt_err_t stm32_pin_irq_enable(struct rt_device *device, rt_base_t pin,
rt_uint32_t enabled)
{
const struct pin_index *index;
const struct pin_irq_map *irqmap;
rt_base_t level;
rt_int32_t irqindex = -1;
GPIO_InitTypeDef GPIO_InitStruct;
index = get_pin(pin);
if (index == RT_NULL)
{
return RT_ENOSYS;
}
if (enabled == PIN_IRQ_ENABLE)
{
irqindex = bit2bitno(index->pin);
if (irqindex < 0 || irqindex >= ITEM_NUM(pin_irq_map))
{
return RT_ENOSYS;
}
level = rt_hw_interrupt_disable();
if (pin_irq_hdr_tab[irqindex].pin == -1)
{
rt_hw_interrupt_enable(level);
return RT_ENOSYS;
}
irqmap = &pin_irq_map[irqindex];
/* Configure GPIO_InitStructure */
GPIO_InitStruct.Pin = index->pin;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
switch (pin_irq_hdr_tab[irqindex].mode)
{
case PIN_IRQ_MODE_RISING:
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
break;
case PIN_IRQ_MODE_FALLING:
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
break;
case PIN_IRQ_MODE_RISING_FALLING:
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
break;
}
HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);
HAL_NVIC_SetPriority(irqmap->irqno, 5, 0);
HAL_NVIC_EnableIRQ(irqmap->irqno);
pin_irq_enable_mask |= irqmap->pinbit;
rt_hw_interrupt_enable(level);
}
else if (enabled == PIN_IRQ_DISABLE)
{
irqmap = get_pin_irq_map(index->pin);
if (irqmap == RT_NULL)
{
return RT_ENOSYS;
}
level = rt_hw_interrupt_disable();
HAL_GPIO_DeInit(index->gpio, index->pin);
pin_irq_enable_mask &= ~irqmap->pinbit;
#if defined(SOC_SERIES_STM32F0) || defined(SOC_SERIES_STM32G0)
if (( irqmap->pinbit>=GPIO_PIN_0 )&&( irqmap->pinbit<=GPIO_PIN_1 ))
{
if(!(pin_irq_enable_mask&(GPIO_PIN_0|GPIO_PIN_1)))
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
}
else if (( irqmap->pinbit>=GPIO_PIN_2 )&&( irqmap->pinbit<=GPIO_PIN_3 ))
{
if(!(pin_irq_enable_mask&(GPIO_PIN_2|GPIO_PIN_3)))
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
}
else if (( irqmap->pinbit>=GPIO_PIN_4 )&&( irqmap->pinbit<=GPIO_PIN_15 ))
{
if(!(pin_irq_enable_mask&(GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|
GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15)))
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
}
else
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
#else
if (( irqmap->pinbit>=GPIO_PIN_5 )&&( irqmap->pinbit<=GPIO_PIN_9 ))
{
if(!(pin_irq_enable_mask&(GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9)))
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
}
else if (( irqmap->pinbit>=GPIO_PIN_10 )&&( irqmap->pinbit<=GPIO_PIN_15 ))
{
if(!(pin_irq_enable_mask&(GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15)))
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
}
else
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
#endif
rt_hw_interrupt_enable(level);
}
else
{
return -RT_ENOSYS;
}
return RT_EOK;
}
中断处理函数
中断处理函数已经在 main.c 中定义,这里不展示了。
当外部中断触发时,会触发 HAL库的中断函数 HAL_GPIO_EXTI_Callback()
,而 STM32 bsp 中在该函数里运行了pin_irq_hdr(bit2bitno(GPIO_Pin));
,该函数会根据中断注册时分配的中断号来调用相应的回调函数。
HAL_GPIO_EXTI_Callback()
#if defined(SOC_SERIES_STM32G0)
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
pin_irq_hdr(bit2bitno(GPIO_Pin));
}
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
pin_irq_hdr(bit2bitno(GPIO_Pin));
}
#else
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
pin_irq_hdr(bit2bitno(GPIO_Pin));
}
#endif
pin_irq_hdr()
rt_inline void pin_irq_hdr(int irqno)
{
if (pin_irq_hdr_tab[irqno].hdr)
{
pin_irq_hdr_tab[irqno].hdr(pin_irq_hdr_tab[irqno].args);
}
}
rt_pin_read()
GPIO 读函数,下面是函数的定义:
int rt_pin_read(rt_base_t pin)
{
RT_ASSERT(_hw_pin.ops != RT_NULL);
return _hw_pin.ops->pin_read(&_hw_pin.parent, pin);
}
和 GPIO 模式配置函数类似,它会调用底层驱动里对应的函数,该底层函数是通过 HAL_GPIO_ReadPin()
来获取 GPIO 的电平。
static int stm32_pin_read(rt_device_t dev, rt_base_t pin)
{
int value;
const struct pin_index *index;
value = PIN_LOW;
index = get_pin(pin);
if (index == RT_NULL)
{
return value;
}
value = HAL_GPIO_ReadPin(index->gpio, index->pin);
return value;
}
rt_thread_mdelay()
这是 RT-Thread 的毫秒级延时函数,定义如下:
rt_err_t rt_thread_mdelay(rt_int32_t ms)
{
rt_tick_t tick;
// 获取需要的时钟节拍
tick = rt_tick_from_millisecond(ms);
// 阻塞相应的节拍时间
return rt_thread_sleep(tick);
}
rt_tick_from_millisecond()
/** * 算出 ms 对应的时钟节拍数 * * * @param ms the specified millisecond * - Negative Number wait forever * - Zero not wait * - Max 0x7fffffff * * @return the calculated tick */
rt_tick_t rt_tick_from_millisecond(rt_int32_t ms)
{
rt_tick_t tick;
if (ms < 0)
{
tick = (rt_tick_t)RT_WAITING_FOREVER; // -1
}
else
{
// 将“每秒节拍数” / 1000 * ms,算出对应的秒节拍数
tick = RT_TICK_PER_SECOND * (ms / 1000);
// 加上小于 1000ms 部分的节拍数
tick += (RT_TICK_PER_SECOND * (ms % 1000) + 999) / 1000;
}
/* return the calculated tick */
return tick;
}
rt_thread_sleep()
线程睡眠(挂起)函数,参数是系统节拍数:
/** * 该函数能让当前线程挂起一段时间(由 tick 决定) * * @param tick the sleep ticks * * @return RT_EOK */
rt_err_t rt_thread_sleep(rt_tick_t tick)
{
register rt_base_t temp;
struct rt_thread *thread;
/* set to current thread */
thread = rt_thread_self();
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* suspend thread */
rt_thread_suspend(thread);
/* reset the timeout of thread timer and start it */
rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &tick);
rt_timer_start(&(thread->thread_timer));
/* enable interrupt */
rt_hw_interrupt_enable(temp);
rt_schedule();
/* clear error number of this thread to RT_EOK */
if (thread->error == -RT_ETIMEOUT)
thread->error = RT_EOK;
return RT_EOK;
}
LOG_D()
本实验中,我们可以将 LOG_D()
视为 rt_kprintf()
,
#define dbg_log_line(lvl, color_n, fmt, ...) \ do \ {
\ _DBG_LOG_HDR(lvl, color_n); \ rt_kprintf(fmt, ##__VA_ARGS__); \ _DBG_LOG_X_END; \ } \ while (0)
LOG_D 是 RT-Thread 内核里的一个日志打印函数,详情可见:《RT-Thread 文档中心——ulog 日志》
RT-Thread 的日志 API 包括:
rt_pin_write()
GPIO 写函数,下面是函数的定义,
void rt_pin_write(rt_base_t pin, rt_base_t value)
{
RT_ASSERT(_hw_pin.ops != RT_NULL);
_hw_pin.ops->pin_write(&_hw_pin.parent, pin, value);
}
和 GPIO 模式配置函数类似,它会调用底层驱动里对应的函数,该底层函数是通过 HAL_GPIO_WritePin()
来完成 GPIO Pin 的修改。
static void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value)
{
const struct pin_index *index;
index = get_pin(pin);
if (index == RT_NULL)
{
return;
}
HAL_GPIO_WritePin(index->gpio, index->pin, (GPIO_PinState)value);
}
边栏推荐
- 揭露测试外包公司,关于外包,你或许听到过这样的声音
- Learning of basic amplification circuit
- IT转测试岗,从迷茫到坚定我究竟付出了什么?
- 巩固表达式C# 案例简单变量运算
- GDB常用命令
- GDB common commands
- Parameter passing mechanism of member methods
- Deux nombres se remplacent
- Liangzai's first program life and annual summary in 2022
- 107. SAP UI5 OverflowToolbar 容器控件以及 resize 事件处理的一些细节介绍
猜你喜欢
Specification for fs4061a boost 8.4v charging IC chip and fs4061b boost 12.6V charging IC chip datasheet
[selenium automation] common notes
《论文笔记》Multi-UAV Collaborative Monocular SLAM
Reasons and solutions of redis cache penetration and avalanche
Sorting selection sorting
Fs8b711s14 electric wine bottle opener MCU IC scheme development special integrated IC
Playwright之录制
大专学历,33岁宝妈又怎样?我照样销售转测试,月入13k+
Several simplified forms of lambda expression
1189. Maximum number of "balloons"
随机推荐
What if the programmer's SQL data script coding ability is weak and Bi can't do it?
Operator explanation
The waterfall flow layout demo2 (method 2) used by the uniapp wechat applet (copy and paste can be used without other processing)
[Yocto RM]10 - Images
Distributed base theory
Daily practice (18): stack containing min function
Fs8b711s14 electric wine bottle opener MCU IC scheme development special integrated IC
SAP UI5 应用开发教程之一百零六 - 如何提高 SAP UI5 应用路由 url 的可读性试读版
Reasons and solutions of redis cache penetration and avalanche
Parameter passing mechanism of member methods
巩固表达式C# 案例简单变量运算
Ap8022 switching power supply small household appliances ACDC chip offline switching power supply IC
《论文笔记》Multi-UAV Collaborative Monocular SLAM
Huawei employs millions of data governance experts! The 100 billion market behind it deserves attention
企业公司项目开发好一部分基础功能,重要的事保存到线上第一a
Ruby tutorial
Netcore3.1 JSON web token Middleware
It's too convenient. You can complete the code release and approval by nailing it!
“薪資倒掛”、“畢業生平替” 這些現象說明測試行業已經...
[path planning] RRT adds dynamic model for trajectory planning