1-GPIO
GPIO的配置:
GPIO库函数编程:
void LED_init(void)//LED初始化
{
GPIO_InitTypeDef GPIO_InitStructure;//定义一个结构体变量
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //开相应的时钟--APB2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //GPIOC13
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //上拉方式
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化
}
GPIO寄存器配置:
void LED_init(void)
{
RCC->APB2ENR|=<<;//开启时钟
GPIOA->CRH &=0xff3fffff;//推挽输出,50MHz //端口位设置/清除寄存器(GPIOx_BSRR) (x=A..E)
//向相应的BRR写1,则清零
//向相应的BSRR写1,则置位
GPIO->BRR |=<<; //点亮led
GPIO->BSRR |=<<;//关闭led //端口输出数据寄存器(GPIOx_ODR) (x=A..E)
//向相应的ODR写1,则置位
//GPIO->ODR &=~(1<<13); //点亮led
//GPIO->ODR |= (1<<13); //关闭led
//GPIOB->ODR ^= GPIO_Pin_13; //LED_Toggle
//库函数接口
//GPIO_ResetBits(GPIOC,GPIO_Pin_13);//点亮led
//GPIO_SetBits(GPIOC,GPIO_Pin_13);//关闭led
}
BRR:
BSRR:
ODR:
STM32的PA15、PB3、 PB4管脚作普通管脚的解决办法
使用的是SWD方式进行下载程序,仅仅使用到SWDIO(PA13) 和SWCLK(PA14)两个管脚。我将PA15(JTDI)和PB3(JTDO)管脚用于他用(用于点LED使用),发现无法将这两个管脚拉低:
我在网上搜到PA15,PB3,PB4这几个管脚默认功能不是普通管脚,需要进行重映射后,才能普通使用
解决办法:
GPIO_InitTypeDef GPIO_InitStructus; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);//RCC_APB2Periph_AFIO必须开启
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//改变映射(失能JTAG) GPIO_InitStructus.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructus.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructus.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructus); GPIO_InitStructus.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructus.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructus.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructus);
GPIO_SetBits(GPIOB,LED2);
其中:GPIO_Remap_SWJ_JTAGDisable 改变管脚映射(失能JTAG功能)
GPIO_Remap_SWJ_NoJTRST : Full SWJ Enabled (JTAG-DP + SW-DP) but without JTRST
GPIO_Remap_SWJ_Disable : Full SWJ Disabled (JTAG-DP + SW-DP)
什么时候需要开复用时钟,RCC_APB2Periph_AFIO:
(1)使用EXTI
(2)重映射(用到外设的重映射功能时才需要使能AFIO的时钟)
举例:重映射USART2
USART2的TX/RX在PA.2/3.但是,PA.2已经被Timer2的channel3使用.这时,如果还想使用USART2,但又不想影响Timer2的使用,这就需要把USART2的TX/RX重映射到PD.5/6。
映射库函数的调用过程
(1)使能被重新映射到的I/O端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
(2)使能被重新映射的外设时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
(3)使能AFIO功能的时钟(勿忘!)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
(4)进行重映射
GPIO_PinRemapConfig(GPIO_Remap_USART2, ENABLE);
GPIO位带的操作:
首先我们先简单回顾一下,51单片机怎样点亮一盏LED灯呢?
假设51单片机的P3^1口外挂一个带上拉电阻的LED灯,那么我们直接通过一个关键字sbit进行位定义,如:
#include<reg52.h> sbit P31 =P3^; //位定义
int main(void)
{
P31 =; //点亮led
P31 =; //关闭led
}
就可实现led的点亮,但是我们的stm32并没有这样的关键字哦。也就是它不能单独的对一个bit进行读写,那么我们怎样做呐?
可以通过对位带别名区进行访问进而实现对某一个位进行读写。
什么是位带?
将一个单元的某一位,通过简单的地址变换算法,映射到一个地址的空间(简称位带别名区),然后对地址进行操作,进而达到对位操作的效果。
为辅助大家理解,特此制作一张图:
PS: 要想对左边某位进行读写,只需对右边映射后的地址进行操作即可(将RAM和外设中的每一个bit映射到一个独立的地址,对这个地址的32位读写操作实现对一个bit的操作,就像51单片机中的位寻址区域一样)。
在stm32中能实现位带功能的也仅仅有两个地方(简称位带区):
sram区最低1MB空间-0x20000000--0x200fffff
外设区最低1MB空间 -0x40000000--0x400fffff
图:来源Cortex-M3权威指南Cn P84
这里两个位带区的每一位都可以分别通过映射算法映射到某一地址空间(这里的映射关系可以通过一个数学公式表达,即:映射算法=公式):
仅有限范围可以实现位绑定:
sram区: 0x2000 0000 ~0x200f ffff---1M—2^20
公式:AliasAddr =0x22000000 + ((A-0x20000000)*8+n)*4
=0x22000000 + (A-0x20000000)*32 + n*4
片上外设:0x4000 0000 ~0x400f ffff---1M
公式:AliasAddr =0x42000000 + ((A-0x40000000)*8+n)*4
=0x42000000 + (A-0x40000000)*32 + n*4
其中:
A --位带区某个bit所在的地址
0x20000000 --sram区的位带区的起始地址
0x40000000 --外设区的位带区的起始地址
AliasAddr --映射后的位带别名区
0x22000000 --sram区的位带别名区的起始地址
0x42000000 --外设位带别名区的起始地址
n --代表在A地址的第几位(范围:0--7,一个地址单元只有8位)
公式解读:
①这里的A-0x20000000或A-0x40000000代表的意思是,该地址之前有多少个地址单位,又因为STM32的一个地址单元对应8个bit位(一个字节),所以然后乘以8转化为bit;
②因为位带区的1bit映射到位带别名区后就是4字节(32bit),同时位带别名区的一个地址单元也是1Byte,也就是需要4个Byte, 所以后面需要将第一步转换的bit乘以4
③位带别名区起始地址在①②两步偏移后,然后将我们所需要操作的位在此基础上进行偏移
举例:利用位带对GPIOA的PA3进行位设置或位清除 GPIOA->ODR |= (1<<3);或者 GPIOA->ODR &= ~ (1<<3);
①计算(A - 0x40000000) *8 将偏移地址转换为bit
GPIOA_BASE =APB2PERIPH_BASE + 0x0800 = PERIPH_BASE + 0x10000 + 0x0800 = 0x40000000 + 0x10000 + 0x0800 =0x40010800
A= GPIOA_BASE (基地址) + ODR偏移地址 = 0x40010800 + 0x0C = 0x4001080C
(A - 0x40000000) *8 = (0x4001080C - 0x40000000)*8
②然后将第一步bit膨胀到位带别名区,乘以4字节(位带区的1bit 变成了位带别名区的4Byte),并在位带别名区进行偏移
0x42000000 + 0x4001080C - 0x40000000)*8 *4
③那么由①②两步已经将要操作的 ODR寄存器的那个位所在的地址0x4001080C 映射到位带别名区了,这时候我们只需要把位带区该地址(0x4001080C)要操作的位ODR2(bit3)膨胀到位带别名区所在的地址:
(volitile unsigned long *)(0x42000000 +(A-0x40000000)*32 + 3*4)
④然后我们对其定义,就可以对该地址操作,不就是对ODR的bit3位进行操作了吗?从而实现了位操作
u32 *PA_out_3= (volitile unsigned long *)(0x42000000 +(A-0x40000000)*32 + 3*4);
*PA_out_3 = 0;//复位该位
*PA_out_3 =1;//置位该位
The range of value for n, we should pay special attention to this situation:
According to the header file stm32f10x.h, then we know that the selection of ODR offset address directly affects the value of n.
When we only operate on the lower six bits of the port output data register, its offset address is 0x0C, and then n ranges from 0 to 7.
When we only operate on the highest six bits of the port output data register, its offset address can be 0x0C or 0x0D:
If its offset address is 0x0C, and then n ranges from 0 to 15.
If its offset address if 0x0D, and then n ranges from 0 to 7.
图:来源Cortex-M3权威指南Cn P87
位带区的每一个位 1 bit 都与别名区的一个字 32 bit 相对应(由 4 个字节组成)。 位带区的一个bit位需4个字节0x2000 0000的第0位对应别名区地址0x2200 0000~0x2200 0003,第2位对0x22000004~0x2200 0007。这样対别名区进行的字操作, 都会映射到位带区的相应的位上, 从而实现对位带区的一 个位进行快速操作。
我们可以对输出数据寄存器进行操作,将ODR位置1,则该位对应的管脚就会输出高电平。
接下来就可以实现点灯操作:
<1>确定所要操作的寄存器的第几位,确定偏移地址和管脚基地址,最终明确 A 的值和所位于的位带区(sram/外设)
<2>确定 n 的范围
<3>由映射关系公式进行计算得到所操作的地址
<4>对要操作的地址进行访问即可
/***********************************************************************************************************************************/
#define GPIOA_ODR (GPIOC_BASE + 0x0C) //A的地址表示--GPIO的基地址 + 偏移地址
//点PA13的灯
u32 *PC_out_3 =(u32*)(0x42000000 +(GPIOA_ODR-0x40000000)* + *);//定义一个指针来指向该地址
//这里是操作的ODR的高8位,且偏移地址是0x0C --n为13
int main(void)
{
LED_init();//初始化led灯 *PC_out_3=;//关闭led灯
*PC_out_3=;//打开led灯 //这样也可以: 是因为最终经映射所得到的地址,对其操作时,仅关心最低位(也就是最低位有效)
*PC_out_3=;//关闭led灯
*PC_out_3=;//打开led灯
}
通过以上方法,虽然可以实现像51一样进行对某一位进行操作,但是当操作的位不仅在sram的位带区,还可能在外设的位带区,这时候此方法显得捉襟见肘了。那么我们有没有好的办法呢?
简洁的位带宏定义
通过简单的宏定义即可实现两个位带区通用的公式,也就不需要进行多个指针变量进行定义了。
/*********************************************************************************************************************************/
//宏定义
#define GPIOC_ODR (GPIOC_BASE + 0x0C) //A=GPIOC的基地址 + 偏移地址
#define GPIOC_IDR (GPIOC_BASE + 0x08) #define BitBand(Addr,Bitnum) *((volitile unsigned long *)((Addr & 0xF0000000)+0x02000000+((Addr&0xfffff)<<5)+(Bitnum<<2)))//通用公式
#define PC_out(n) BitBand(GPIOC_ODR,n)//对映射后的地址赋值
#define PC_in(n) BitBand(GPIOC_IDR,n) int main(void)
{
LED_init();//初始化led灯
if(PC_in()==)//IDR的第11位进行操作 如果PC11为高电平时,将PC3的电平置高,否则置低
PC_out()=; //ODR的第3位进行操作
else
PC_out()=;
}
这篇文章有没有帮助到你哈,可以关注一下我的微信公众号: 电子小彭友, 了解更为有趣的我!
1-GPIO的更多相关文章
- [转]: stm328种GPIO模式
[原创]:这段时间开始研究stm32,今天撸着一段代码一直追,追到了GPIO口模式的枚举类型这里,遂去网上查看这8种模式到底是什么,网上一查,看到了一个答案被很多博主转载或者原创,那我也就不重复废话了 ...
- 基於tiny4412的Linux內核移植--- 中斷和GPIO學習(3)
作者 彭東林 pengdonglin137@163.com 平臺 tiny4412 ADK Linux-4.4.4 u-boot使用的U-Boot 2010.12,是友善自帶的,爲支持設備樹和uIma ...
- 基於tiny4412的Linux內核移植--- 中斷和GPIO學習(2)
作者 彭東林 pengdonglin137@163.com 平臺 tiny4412 ADK Linux-4.4.4 u-boot使用的U-Boot 2010.12,是友善自帶的,爲支持設備樹和uIma ...
- 基於tiny4412的Linux內核移植--- 中斷和GPIO學習(1)
作者 彭東林 pengdonglin137@163.com 平臺 tiny4412 ADK Linux-4.4.4 u-boot使用的U-Boot 2010.12,是友善自帶的,爲支持設備樹和uIma ...
- STM32f10xxx 之 GPIO口配置
背景 配置stm32f103使其完成PWM输出的过程中,在配置GPIO口的时候,按照习惯配置GPIO口的speed为50MHZ,突然就意识到,为什么大部分例程习惯配置为50MHZ,而不是其它值,即有了 ...
- android gpio口控制
android gpio口控制 GPIO口控制方式是在jni层控制的方式实现高低电平输出,类似linux的控制句柄方式,在linux系统下将每个设备看作一个文件,android系统是基于linux内 ...
- STM32F412应用开发笔记之二:基本GPIO控制
NUCLEO-F412ZG板子上的元器件并没有完全焊接,除去ST-LINK部分和电源部分后,还有用一个USB主机接口,三个LED灯和两个按钮,不过很多功能引脚都已经引到了插针.查看原理图可发现,由原理 ...
- 通过数组和枚举简化GPIO操作编码
在工作中,经常遇到大量使用GPIO作为数字量输入输出来控制设备或采集状态,每次定义操作不同的GPIO针脚既麻烦又容易出错,于是就想要简化操作过程.对于数字量输入来说就是采集对应针脚的状态:而输出则是根 ...
- Zybo GPIO Demo Run Embedded Linux
1.Environment Ubuntu 12.04 x86_64 Vivado 2013.4 SDK 2013.4 2.Pre-requisites 2.1 CodeSourcery arm-g ...
- Android(Java)控制GPIO的方法及耗时分析
前面两篇分别介绍了通过脚本和C代码读写/sys/class/gpio以控制GPIO.实际项目调试时经常还需要在Java代码里控制GPIO,其实现与C代码类似,唯一不同是Android权限.本文重点介绍 ...
随机推荐
- ios支付签名认证
一.解析json中参数的含义 private Long id; @ApiModelProperty(value = "创建时间") private Date gmtCreate; ...
- 定时器+echarts运行时间太长导致内存溢出页面崩溃
最近做的项目需要在页面上展示echarts图表,且数据每隔10s刷新一次,然后发现时间长了以后chorme浏览器会显示页面崩溃.一开始以为是定时器的原因,试了网上的很多方法,比如把setInterva ...
- Python变量和注释
1.变量与变量的作用: (1)什么是变量:变量源于数学,是计算机语言中能存储计算结果或能表示值抽象概念.变量可以通过变量名访问.在指令式语言中,变量通常是可变的:在Python中变量名必须是大小写英文 ...
- Python绘图之Turtle库详解(1)
Turtle库是Python语言中一个很流行的绘制图像的函数库,想象一个小乌龟,在一个横轴为x.纵轴为y的坐标系原点,(0,0)位置开始,它根据一组函数指令的控制,在这个平面坐标系中移动,从而在它爬行 ...
- c常用函数-strcpy和strncpy
strcpy和strncpy strcpy在脚本开发中经常用到,例如处理参数等,它的作用是把一个字符串复制到另一个字符串中. strncpy的作用是把一个字符串中的指定长度复制到另一个字符串中,如果指 ...
- Linux系统使用Nmon监控及分析系统性能
一.下载nmon(1)查看sever的内核版本: 命令:lsb_release -a执行结果:LSB Version: :base-4.0-amd64:base-4.0-noarch:c ...
- 【JMeter_19】JMeter逻辑控制器__简单控制器<Simple Controller>
简单控制器<Simple Controller> 业务逻辑: 就像他的名字一样,简单,可以理解为一个文件夹,就是分组用的,没有其他特殊功能,但相比不添加简单控制器,区别在于简单控制器可以被 ...
- Area.js下载
因为vant AddressEdit 地址编辑的必要组件area.js网站经常进不去,所以存在这里,area.js 代码如下: export default { province_list: { 11 ...
- DDoS压力测试工具t50
site: https://sourceforge.net/projects/t50/ 例子:t50 192.168.1.1 --flood--protocol T50|TCP|UDP|ICMP--t ...
- 入门大数据---Hive是什么?
这篇文章主要介绍Hive的概念. 简介: Hive中文名叫数据仓库管理系统,之前我们操作MapReduce必须通过编写代码或者通过特殊命令来实现,有了Hive我们通过常用的SQL语句就能操作MapRe ...