由于项目的要求,之前一段时间感觉学起来很乱,软件上要看GPRS部分代码并参与跟上位机服务器端交互的调试,但我本人对STM32事实上还有很多原理上的东西不明白,硬件方面要学电路原理还有DXP制作电路板的流程,后面会写一篇关于DXP的使用心得。今天上午考完科学*来到实验室终于稍微缓一下好好看看STM32,打开项目来到main(),又一次定位到GPIO的配置代码,上次日志中已经有初步的理解,但是还遗留很多问题。
GPIO_InitTypeDef GPIO_InitStructure;
/* LED1 LED2 LED3 GPIO Config */
/* GPIOF Periph clock enable */
RCC_AHB1PeriphClockCmd(CSTXJT_GJY_LED_GPIO_CLK, ENABLE);
/* Configure PC3 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = CSTXJT_GJY_GPIO_Pin_LED1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(CSTXJT_GJY_LED1_GPIO, &GPIO_InitStructure);
之前一直不明白RCC_AHB1PeriphClockCmd()的作用是什么,为什么一定要使能外设时钟?这就需要对Cortex-M3的总线结构有一定了解:
这是在百度上找的简略图,更加容易理解,可以看到所有的外设都连接到APB和DMA总线上,只有使能了外设时钟这个端口才是打开状态,就如要进一个房间先要打开门,至于用那个函数(有AHX,APBX)这要看具体的外设时钟频率和根据其总线地址来配置,总线地址可以在数据手册里查到。现在来看看这个外设时钟使能函数究竟做了什么:
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_RCC_AHB1_CLOCK_PERIPH(RCC_AHB1Periph));//(1)
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
RCC->AHB1ENR |= RCC_AHB1Periph;//(2)
}
else
{
RCC->AHB1ENR &= ~RCC_AHB1Periph;
}
}
(1)语句可以定位到一个判断函数
#define IS_RCC_AHB1_CLOCK_PERIPH(PERIPH) ((((PERIPH) & 0x819BEE00) == 0x00) && ((PERIPH) != 0x00))
为什么是和和0x819BEE00相与呢,项目中传的的参数是0x00000001,这里又要留一个疑问了,不过在固件库的RCC头文件中,GPIOX的外设时钟都是AHB1
(2)语句中AHB1ENR是 RCC模块的一个寄存器
有了这个就很好理解外设时钟使能的根本原理,就是根据参数置位GPIOX(X=A…E);
明白了外设时钟使能原理,接着是GPIOX
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
------------------------------------------------------------------------------
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)
-----------------------------------------------------------------------------
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
-----------------------------------------------------------------------------
#define PERIPH_BASE ((uint32_t)0x40000000)
看完这几行环环相扣的代码最关键是要弄懂这写地址到底是依据什么来写的,想到了万能的数据手册,查之:
最下面一行正是GPIOA在内存中的地址,先来算算到底对不对吧
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
------------------------------------------------------------------------------
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000) //=0x4002 0000
-----------------------------------------------------------------------------
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)//=0x4002 0000
-----------------------------------------------------------------------------
#define PERIPH_BASE ((uint32_t)0x40000000)
有一种恍然大悟的感觉,再算GPIOB也是对的。那么这几句就很好解释了:
GPIOA基址,它的所有寄存器地址都是在这个地址上的偏移
AHB1总线基址
外设基址
再来看看GPIO_PIN,这个问题看似不是个问题,但一直困扰我很久了,因为固件库将PIN作为GPIO的一个结构属性,让我一直误以为PIN是在GPIO的某个寄存器中赋值的,于是各种找GPIO的寄存器一直未果,今天算是大概搞懂了这个PIN其实很简单,就是芯片上的一个管脚,他是通过GPIO_BSRR来配置某个外设连接到此引脚,如下代码是一个点亮LED的关键部分
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->BSRRL = GPIO_Pin;
}
在数据手册中是找不到BSRRL这个寄存器的,固件库将它分为高16位(BSRRH)和低16位(BSRRH)。
关于这个PIN还可以在电路上得到印证,这也得益于这些天学习DXP初步掌握了电路设计的方法,电路基本也能看懂(但看不懂原理)。
首先看代码,是一个系统启动就点亮LED灯
LED_Switch(LED_OPEN,LED_OPEN,LED_OPEN);
里面顺序点亮LED1,LED2.LED3.再看电路原理图
最后来看看电路板PCB吧,这个是跟实物最接近的了
DS4,5,6分别是LED1,2,3的元件号,他们接了一个电阻后都直接连接到STM32的引脚上,而这引脚正是代码中所指的GPIOX_PIN
那么他们又是怎么映射的呢?这个。。。
看看GPIO_Init(),又是上次的老问题了,虽然目前还是不懂它里面的各种逻辑运算,但是总算有了一个整体的概念:初始化时指定一个引脚给外设,在后面的代码对程序访问都无需再重新指定了。