通过LED流水灯,由浅入深,理解寄存器和与GPIO相关的函数,比如GPIO_Init()。
流水灯,设计思路很简单,只需要将多个连续的GPIO口依次设置电平,根据你LED连接线路,选择是高电平触发还是低电平触发。每个灯亮几毫秒然后熄灭,再延时,你当然可以通过GPIO_ResetBits(GPIOA,GPIO_Pin_0)这种方式一个一个写,但是,注意,观察GPIO_Pin_对应寄存器的值,不难发现。只需要将GPIO_Pin_0的寄存器值左移一位,就是GPIO_Pin_1的值,所以思路就有了,我的代码如下
#include "stm32f10x.h"
#include "Delay.h" // Device header
int main(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef g;
g.GPIO_Mode=GPIO_Mode_Out_PP;
g.GPIO_Pin=GPIO_Pin_All;
g.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&g);
while (1)
{
int a=0x0001;
for (int i = 0; i < 7; i++)
{
int b=a<<i;
GPIO_Write(GPIOA,~b);
Delay_ms(200);
}
}
}
至于为啥,右键gpio_pin_跳转到定义。如下图
可以看到,对应pin口的寄存器值,我们知道,Stm32中,GPIOA这一组IO口由7个寄存器控制,单片机编程的本质就是操作寄存器。为什么这里没有指定电平,却能正确输出低电平的,这就要从寄存器说起。
IDR,ODR,BRR,LCKR四个寄存器的高16位都是保留的。对于GPIO_ResetBits,内部是对BRR寄存器写入了值。对于GPIO_SetBits,是对BSRR寄存器写入了值,写入的什么值?就是上图对应引脚的值。比如GPIO_ResetBits(GPIOA,GPIO_Pin_0)。GPIO_Pin_0对应0000 0000 0000 0001,即BRR寄存器的BR0为1,表示清除ODR0为1。清除这个步骤应该是硬件电路决定的。总结就是。BRR寄存器哪一位为1就清除ODR对应位。BSRR寄存器低16位哪一位为1就设置ODR对应位为1。BSRR寄存器高16位哪一位为1就清除ODR对应位为0。
那GPIO_Write是怎么回事呢。跳转查看,内部是GPIOx->ODR = PortVal。什么是ODR,就是端口输出数据寄存器,比如PortVal为0x0001,就表示将ODR寄存器低16位设为了0000 0000 0000 0001。根据下图,就是ODR0=1,也就是使GPIO_Pin_0输出高电平,其余为0的位对应端口输出模式输出低电平。根据我的理解这是硬件电路的连接规则。如果设为0000 0000 0100 0000。就是ODR6=1,也就是使GPIO_Pin_6输出高电平,其余为0的位端口输出低电平。
如果想使两个端口输出高电平呢,直接用或,比如,PA0和PA2,0000 0000 0000 0001 | 0000 0000 0000 0100。按位或,得0000 0000 0000 0101。即可实现PA0与PA2输出高电平。
说到这里,不如再说说GPIO_Init函数的本质。如下,是八种输入输出模式。还有速度。
.
枚举,GPIO_Speed_2MHz=2。GPIO_Speed_50MHz=3
注意,根据CRL寄存器发现 ,这32位共控制8个IO口的配置,每个IO口对应四位。mode0和cnf0对应GPIO_Pin_0,mode1和cnf对应GPIO_Pin_1,以此类推。mode控制是输入还是输出(也包含了速度),cnf控制配置,同样为00,如果是输入则为模拟输入,如果是输出则为通用推挽输出。
GPIO_Init分析如下
currentmode = ((uint32_t)GPIO_InitStruct-> & ((uint32_t)0x0F) //这是将GPIO_Mode的低四位取出存入currentmode,比如OUT_PP为0x10,计算currentmode=0x10&0x0F=0001 0000 & 0000 1111=0000 0000=0x00。因为A&1=A,0&B=0。
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00) //这是判断是否为输出模式。比如通用推挽输出0x10。0x10&0x10=0x10。上拉输入为0x48。0x48&0x10=0100 10000&0001 0000=0000 0000=0x00。
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed; //这是取出结构体的speed放入crrentmode低二位。比如速度为GPIO_Speed_50MHz。currentmode=currentmode | GPIO_Speed_50MHz=0x00 | 0x03=0000 0000 | 0000 0011=0000 0011=0x03。currentmode虽然保存了引脚的模式速度信息,但是信息保存在current最低4位,所以要想配置具体某个引脚,还要移动currentmode到对应位。
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) //这是这是判断引脚定义没有,并判断是否是Pin0-7。因为Pin0-7的格式都为0x00mm,而Pin8-15为0xnn00,通过&0x00FF就可以判断。如果是Pin8-15结果应为0x0000。结果为0x00mm就是Pin1-7。
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
这部分是获取io口位置,我假定选的是GPIO_Pin_5,即0x0020运算过程如图。可以看到,当pinpos加到0x05时,pos=crrentpin=0x20。这时候pinpos就是引脚号
找到对应pin口时,进入if判断语句
if (currentpin == pos)
{
pos = pinpos << 2;//因为CRL寄存器每四位配置一个io口,所以这里左移两位,也就是*4。
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos;//将0000 1111左移引脚号*4,如果是pin5,就是左移20位
tmpreg &= ~pinmask。//将引脚对应的CRL的4位清零。~表示按位取反。式子为tmpreg=tmpreg&~pinmask。取反的好处,&之后不影响其他引脚配置位,还可以把要配置的引脚4位清0.
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);//配置对应引脚的4位,为什么用或,因为0|A=A,也就是不影响其他的引脚对应的配置位。
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);//如果配置为下拉输入,将对应引脚对应的ODR位清0。
}
else
{
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);//如果是上拉输入,将BSRR低16位的对应引脚位置1,然后硬件会自动将对应ODR位置1
}
}
}
void GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
/*---------------------------- GPIO Mode Configuration -----------------------*/
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* Check the parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Output mode */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*---------------------------- GPIO CRL Configuration ------------------------*/
/* Configure the eight low port pins */
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
tmpreg = GPIOx->CRL;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
GPIOx->CRL = tmpreg;
}
/*---------------------------- GPIO CRH Configuration高寄存器差不多。省略 ------------------------*/
}