第16集 蜂鸣器实验
这个实验和流水灯是一样的,只是将相对应的IO口拉高拉低即可控制蜂鸣器。
值得注意的是电路设计方面,根据视频描述,STM32上电是其IO口为浮空状态,
电平不确定,加入R38 10K 电阻,当上电时有小电流时会直接被R38流入GND,
当电流达到一定时才会进入S8050三极管。
第17集 按键实验
主要是将IO口设置为输入,然后轮训获取该IO口的状态,根据状态点亮相应的 LED 灯。
这一集还未看之前就自己先敲代码,对比视频上的代码,自己写的移植性较好,视频上的代码思路比较好,
而且代码更加精简一些。总体来说,视频的代码甚于我的代码。
按键有两种扫描方式:
1、长按时,只看做是一次按下
实现思路:如果此次是按下状态则检查上一次是否弹起状态,只有上一次是弹起状态时才当成按下。
2、长按时,被看作连续按下
实现思路:读取当前按键所处于的状态直接返回该状态
总结:这两种方式唯一不一样且关键的地方在于是否需要判断上一次是否为弹起状态,所以要切换模式时
只需要让这关键的地方失效或者生效即可,这种思路可以用在其他地方。
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,KEY0按下
//2,KEY1按下
//3,KEY2按下
//4,KEY3按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY2>KEY3!!
#define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)//读取按键0
#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)//读取按键1
#define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)//读取按键2
#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//读取按键3(WK_UP)
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1; //默认为松开状态
if(mode) // mode = 1 则让此条件失效
key_up=1; //支持连按
if(key_up && (KEY0==0 || KEY1==0 || KEY2==0 || WK_UP==1))
{
delay_ms(10); //去抖动
key_up=0; // 更新标志位 为按下状态
if(KEY0==0)
return KEY0_PRES;
else if(KEY1==0)
return KEY1_PRES;
else if(KEY2==0)
return KEY2_PRES;
else if(WK_UP==1)
return WKUP_PRES;
}
else if(KEY0==1 && KEY1==1 && KEY2==1 && WK_UP==0)
{
key_up=1; // 当所有都是弹起时才将标志位更新为松开状态
}
return 0;// 无按键按下
}
这个函数缺陷是同时只能检测一个按键并且有优先级顺序所限制,一种改进的方式是修改按键状态标志,
每个按键状态对于整数的一个位,将这些按键状态合并起来,成为一个整数,判断时再拆分起来即可。
贴出自己写的源码:
-------------------------------------------------------------------------------
key.h
-------------------------------------------------------------------------------
#ifndef __KEY_H
#define __KEY_H
enum en_KEY_NUMBER
{
eKEY_NUMBER_1,
eKEY_NUMBER_2,
};
enum en_KEY_STATUS
{
eKEY_STATUS_DOWN,
eKEY_STATUS_UP,
};
void KEY_Init(void);
enum en_KEY_STATUS KEY_Read(enum en_KEY_NUMBER number);
enum en_KEY_STATUS KEY_ReadKey1_One(void);
enum en_KEY_STATUS KEY_ReadKey2_One(void);
#endif
-------------------------------------------------------------------------------
key.c
-------------------------------------------------------------------------------
#include "public.h"
#include "key.h"
// 移植部分 ------------------------------------------------------------
#define KEY1_GPIO A
#define KEY1_GPIO_Pin 0
#define KEY2_GPIO C
#define KEY2_GPIO_Pin 13
// 上拉或下拉
#define KYE1_MODE GPIO_Mode_IPD
#define KYE2_MODE GPIO_Mode_IPU
// 按键按下时的电平状态,这点做的比较好,可以统一起来
#define KEY1_DOWN 1
#define KEY2_DOWN 0
// 移植部分 ------------------------------------------------------------
/* 初始化按键 */
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(TORCC(KEY1_GPIO) | TORCC(KEY2_GPIO), ENABLE);
GPIO_InitStructure.GPIO_Mode = KYE1_MODE;
GPIO_InitStructure.GPIO_Pin = TOPIN(KEY1_GPIO_Pin);
GPIO_Init(TOGPIO(KEY1_GPIO), &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = KYE2_MODE;
GPIO_InitStructure.GPIO_Pin = TOPIN(KEY2_GPIO_Pin);
GPIO_Init(TOGPIO(KEY2_GPIO), &GPIO_InitStructure);
}
/* 读取指定按键的状态 长按时连续返回状态*/
enum en_KEY_STATUS KEY_Read(enum en_KEY_NUMBER number)
{
enum en_KEY_STATUS status;
uint8_t tmp;
switch((int)number)
{
case eKEY_NUMBER_1:
tmp = GPIO_ReadInputDataBit(TOGPIO(KEY1_GPIO), TOPIN(KEY1_GPIO_Pin));
status = (tmp == KEY1_DOWN) ? eKEY_STATUS_DOWN : eKEY_STATUS_UP;
break;
case eKEY_NUMBER_2:
tmp = GPIO_ReadInputDataBit(TOGPIO(KEY2_GPIO), TOPIN(KEY2_GPIO_Pin));
status = (tmp == KEY2_DOWN) ? eKEY_STATUS_DOWN : eKEY_STATUS_UP;
break;
}
return status ;
}
/* 读取指定按键的状态 长按时只会返回一次按下状态*/
enum en_KEY_STATUS KEY_ReadKey1_One(void)
{
static enum en_KEY_STATUS oldstatus = eKEY_STATUS_UP;
enum en_KEY_STATUS status;
status = KEY_Read(eKEY_NUMBER_1);
if(eKEY_STATUS_UP == oldstatus && eKEY_STATUS_DOWN == status)
{
oldstatus = eKEY_STATUS_DOWN;
return eKEY_STATUS_DOWN;
}
if(eKEY_STATUS_UP == status)
{
oldstatus = eKEY_STATUS_UP;
}
return eKEY_STATUS_UP ;
}
/* 读取指定按键的状态 长按时只会返回一次按下状态*/
enum en_KEY_STATUS KEY_ReadKey2_One(void)
{
static enum en_KEY_STATUS oldstatus = eKEY_STATUS_UP;
enum en_KEY_STATUS status;
status = KEY_Read(eKEY_NUMBER_2);
if(eKEY_STATUS_UP == oldstatus && eKEY_STATUS_DOWN == status)
{
oldstatus = eKEY_STATUS_DOWN;
return eKEY_STATUS_DOWN;
}
if(eKEY_STATUS_UP == status)
{
oldstatus = eKEY_STATUS_UP;
}
return eKEY_STATUS_UP ;
}
第18集 C语言复习-寄存器地址名称映射
一、C语言复习
这里将一些C语音的位操作、宏定义这些基础知识,此处略
二、寄存器地址名称映射
这里讲解利用C语言的一些特性,实现结构体来访问对应的寄存器地址。是一个非常有意思的技巧。
按照自己的理解,尝试描述一遍,从设置GPIOA组的第一个pin为高电平的方式为切入点进行分析。
// 设置 GPIOA组的第1引脚为高电平
GPIO_SetBits(GPIOA, GPIO_Pin_1);
// 函数的实现
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BSRR = GPIO_Pin;
}
从上面看,最终设置的是GPIOA的BSRR寄存器,那这个寄存器的地址是多少呢?GPIOx是从 GPIOA 传进来的。
看GPIOA的定义:(STM32F10x.h)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) // GPIOA的基址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) // APB2总线的基址
#define PERIPH_BASE ((uint32_t)0x40000000) // 片上外设基址
1、外设基址
#define PERIPH_BASE ((uint32_t)0x40000000)
STM32使用的ARM M3的内核,该内核的外设基址是从0x40000000开始。(M3权威指南 86页)
从STM32官方的参考手册来看也是从 0x40000000开始(STM32中文参考手册 29页)
所以STM32的外设基址为 0x40000000 ,即所有外设都是从 0x40000000 ~ 0x5003FFFF
2、APB2基址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) // APB2总线的基址
外设基址为 0x40000000 + APB2偏移0x10000 = APB2的基址。
从STM32官方的参考手册来看也是从 0x40010000开始 (STM32中文参考手册V10 28页)
3、GPIOA基址
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) // GPIOA的基址
从上图看,GPIOA的基址是0x40010800,而APB2基址为 0x40010000 + GPIOA偏移0x0800 = 0x40010800
该宏完全展开后是这样的 ( 0x40000000 + 0x10000 )+ 0x0800 = 0x40010800
4、强制转换为 GPIO_TypeDef 类型的指针(重点,精华)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
强制转为GPIO_TypeDef 类型的指针,表示从 0x40010800 开始连续占用sizeof(GPIO_TypeDef) 字节的长度。
注意,它把 0x40010800 转为 GPIO_TypeDef 类型的指针,即GPIOA的地址0x40010800。
该结构体定义如下:
typedef struct
{
__IO uint32_t CRL; // 占用 4 个字节 即 0x40010800 ~ 0x40010804 为CRL寄存器
__IO uint32_t CRH; // 占用 4 个字节 即 0x40010804 ~ 0x40010808 为CRH寄存器
__IO uint32_t IDR; // 占用 4 个字节 即 0x40010808 ~ 0x4001080C 为IDR寄存器
__IO uint32_t ODR; // 占用 4 个字节 即 0x4001080C ~ 0x40010810 为ODR寄存器
__IO uint32_t BSRR; // 占用 4 个字节 即 0x40010810 ~ 0x40010814 为BSRR寄存器
__IO uint32_t BRR; // 占用 4 个字节 即 0x40010814 ~ 0x40010818 为BSRR寄存器
__IO uint32_t LCKR; // 占用 4 个字节 即 0x40010818 ~ 0x4001081C 为BSRR寄存器
} GPIO_TypeDef;
对应STM32的GPIO寄存器映射表,刚好完全对齐。( STM32中文参考手册V10 129页 )
5、看看GPIO_Pin的定义
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
仔细观察,发现定义的宏都很有规律,展开二进制来看刚刚好是对应16位数的每一个位。
6、回头看看
// 函数的实现
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BSRR = GPIO_Pin;
}
我们传给GPIO_SetBits()是GPIOA和GPIO_Pin_1,GPIOx->BSRR = GPIO_Pin;
是将GPIO_Pin_1((uint16_t)0x0002) 写入到 GPIOA(0x40010800)->BSRR(10)中。
简单的来说,是将0x0002写入到 0x40010810为首的寄存器中,再通俗的来说,就是将0x40010810 中的第1bin置1。置1会出现神马情况呢?
YES,就会将GPIOA的第1 pin 置为高电平。
7、BSRR寄存器
该寄存器对应位写1,就会让对应的IO口置为高电平。从而完成目的。
关于结构体,还有一个技巧,是我从A20的源码中看到的,蛮有意思的,很巧妙的将联合体、位域、结构体融合起来,实现非常灵活的操作寄存器方式。
/* 联合体 只占用4个字节*/
typedef union
{
__u32 dwval; // 这里是对寄存器整体赋值修改
struct
{
__u32 io_map_sel : 1 ; // default: 0;
__u32 res0 : 29 ; // default: ;
__u32 tcon_gamma_en : 1 ; // default: 0;
__u32 tcon_en : 1 ; // default: 0;
} bits; // 这里个单独对某些寄存器进行操作
} tcon_gctl_reg_t;
因联合体的特性,dwval与 bits 共用同一个内存空间,因结构体的特性,将每个寄存器分开,因位域的特性,得以单独修改某一位或几个位。
如果我要修改 tcon_gctl_reg_t 寄存器中的其中 bin31 位。那么可以这样:
#define SUNXI_LCD0_BASE 0X01C0C000
static volatile tcon_gctl_reg_t *lcd_dev;
lcd_dev=(__de_lcd_dev_t *)SUNXI_LCD0_BASE;
lcd_dev-> tcon_en = 1;
如果想要对这个寄存器所有位都赋值则可以这样:
lcd_dev-> dwval = 0x01;
|