以下只是个人学习的要点和个人观点,若整理的知识要点有问题,可以提出来,一起学习进步,嘻嘻嘻。
自毕业一年以后,我和我的一位学长住在了一起,最近和他探讨学习中,我被他喷的一无是处,作为一个嵌入式开发工程师,我的底子太薄了,所以我要重新学习单片机。
我们就从最简单的程序开始把,点亮一个LED灯。
看一下正点原子的程序:
#include "sys.h"
#include "delay.h"
#include "led.h"
int main(void)
{
delay_init(); //延时函数初始化
LED_Init(); //初始化与LED连接的硬件接口
while(1)
{
LED0=0;
LED1=1;
delay_ms(300); //延时300ms
LED0=1;
LED1=0;
delay_ms(300); //延时300ms
}
}
这个程序非常简单,但是我I们要重视,打好底子是最重要的,不要想着实现了就可以,要明白是怎么实现更重要,这会提高你解决复杂程序的效率,最重要的是,你要多敲代码,不懂多敲几遍,学习是循序渐进的过程,知识无穷尽,进一寸有一寸的欢喜。
#include “”这是加载一个库文件,是官方或者你个人编写好的库,这个库的大概内容就是将一些重复并复杂的操作集成后,直接做成库文件调用,极大的节省了开发的时间。
这里面sys.h文件,这个是系统头文件。delay.h----延时库文件。led.h----点亮LED灯的头文件。
单片机是怎么执行的我现在不大明白,但是我知道程序是直接从主函数main直接执行,顺序往下,为什么加while,如果不加单片机执行一遍就结束了么,还是说会执行完重新开始,再次从头开始。等我测试一下。
while应该就是要把你想要一直重复的工作放在里面,在main函数里面开头到while的区间中,就是你要进行的初始化配置,这就好比人的一生,父母孕育你的过程,只需要一遍,这叫初始化,等你乖乖落地,吃饭学习睡觉,不停地循环,这是while循环。总不能你父母孕育出来你就是让你吃一顿饭吧,然后就不要了,再养一个。是吧。
我们来看一下函数delay_init(); 点开之后
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us=SystemCoreClock/8000000; //为系统时钟的1/8
fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数
}
里面实际就进行了三个操作,选择一下时钟,然后进行微妙和毫秒的计数值的赋值。
打开SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
我们就可以看到(记得追溯一下参数源头,这里就不讲了。)
/**
* @brief Configures the SysTick clock source.
* @param SysTick_CLKSource: specifies the SysTick clock source.
* This parameter can be one of the following values:
* @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source.
* @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source.
* @retval None
*/
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
/* Check the parameters */
assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
{
SysTick->CTRL |= SysTick_CLKSource_HCLK;
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
}
}
可以看出这个就是官方的一个函数,它先是检查一下参数,然后是那种时钟模式,就将那种时钟模式赋予给了SysTick的CTRL参数,我们看一下CTRL到底是啥
/** @addtogroup CMSIS_CM3_SysTick CMSIS CM3 SysTick
memory mapped structure for SysTick
@{
*/
typedef struct
{
__IO uint32_t CTRL; /*!< Offset: 0x00 SysTick Control and Status Register */
__IO uint32_t LOAD; /*!< Offset: 0x04 SysTick Reload Value Register */
__IO uint32_t VAL; /*!< Offset: 0x08 SysTick Current Value Register */
__I uint32_t CALIB; /*!< Offset: 0x0C SysTick Calibration Register */
} SysTick_Type;
可以看到CTRL 就是系统时钟控制和状态寄存器的位置,给这个寄存器赋值,然后就决定了系统时钟是什么状态。
然后fac_us = SystemCoreClock/8000000; //为系统时钟的1/8 我们看一下这SystemCoreClock参数是啥。
#elif defined SYSCLK_FREQ_72MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz; /*!< System Clock Frequency (Core Clock) */
我们可以看出这是一个代表72MHZ的数字,这个就说明主频为72MHZ,如果主频不是,记得要改哦。fac_ms那就不用看了,然后,我看下一个函数LED_Init();
//初始化PB5和PE5为输出口.并使能这两个口的时钟
//LED IO初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能PB,PE端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 端口配置, 推挽输出
GPIO_Init(GPIOE, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz
GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高
}
这其实就是一个简单的IO口配置,第一句,定义了一个结构体变量,我们看一下里面有什么,
/**
* @brief GPIO Init structure definition
*/
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
只有三个参数,分别是GPIO引脚,速度和模式。
然后通过操作打开了系统时钟,因为时钟是所有引脚工作的动力源,你必须要跟他请求进行分配时钟进行干活,就好比小时候跟爸妈要钱买吃的。
让我看看这个函数里面
/**
* @brief Enables or disables the High Speed APB (APB2) peripheral clock.
* @param RCC_APB2Periph: specifies the APB2 peripheral to gates its clock.
* This parameter can be any combination of the following values:
* @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB,
* RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE,
* RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1,
* RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1,
* RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3,
* RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17,
* RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11
* @param NewState: new state of the specified peripheral clock.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
RCC->APB2ENR |= RCC_APB2Periph;
}
else
{
RCC->APB2ENR &= ~RCC_APB2Periph;
}
}
可一个看到APB2下面有这个么多小弟,每次进行操作都要检查一下参数对不对,就是看看是不是自己的小弟,然后使能和不使能,将小弟名字发过去。
哈哈哈哈,你发现没,这就是单片机,太神奇了。
紧接着下面我们把GPIO结构体里面定义赋完了,然后我们再次进行下一步操作,GPIO_Init();这是什么东东,我们再次进入他的身体里面看看。
/**
* @brief Initializes the GPIOx peripheral according to the specified
* parameters in the GPIO_InitStruct.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @param GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that
* contains the configuration information for the specified GPIO peripheral.
* @retval None
*/
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的赋的参数全部传给单片机的GPIO寄存器,那么这个GPIO算配置完成了。
那么后面的延时后定义都很简单啦,那么我们正式扒开了LED控制的所有面纱,它是不是你中意的哪一个呢。