虽然现在工作中主要使用的是51系列单片,看到STM32发展如此快,就买了一个板子开始学习,没有自己想的那么简单,只能下班回来学习,有时一个问题自己得琢磨好几晚上才解决,今晚猛然想,何不在一个论坛把自己遇到的问题写出来,看了一下几个论坛,最终绝地写在这个论坛上,学习STM32已经快半个月了,期间遇到好多问题,今晚就先写一下今晚碰到的问题,想到什么就写什么。
1 对NVIC的理解
CM3支持硬件中断嵌套,分为抢占式优先级和亚优先级,使用规则主要有,抢占优先级高级别的可以打断低级别的,同一级别的抢占优先级同时发生时,亚当优先级高的先发生中断,若是相同,则按硬件排列顺序发生。若是有一个亚优先级正在执行中断,同一级别的其它亚优先级发生时,则先挂起,等此中断执行完再执行!
从库函数中找到优先级分组模式:
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /* 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /* 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /* 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /* 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /* 4 bits for pre-emption priority
0 bits for subpriority */
从中可以看出第一组只有一个级别,16个亚优先级,我可以这样理解,若是分配成这个组里,不能发生嵌套中断,同时发生中断时,亚优先级高的先发生,若有中断执行时,必须等中断执行完才能执行下一个中断。最后一组正好相反,有15个级别,若是执行一个中断,可以最多嵌套15个中断执行一个中断。看下面的例子:
NVIC_InitTypeDef NVIC_InitStructure; //定义中断初始化类型结构体变量
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //配置优先级分组1 2个两个抢占优先级 8个亚优先级
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //开口外部中断0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//配置0号抢占式优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//亚优先级配置为0号
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能通道
NVIC_Init(&NVIC_InitStructure); //对外部中断0进行初始化配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; //开口外部中断5到9
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//配置1号抢占式优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//亚优先级配置为1号
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能通道
NVIC_Init(&NVIC_InitStructure); //对外部中断0进行初始化配置
NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn; //ADC1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//配置1号抢占式优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//亚优先级配置为1号
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能通道
NVIC_Init(&NVIC_InitStructure); //对外部中断0进行初始化配置
从上面的配置来看,外部中断0的优先级最高,可以打断ADC和外部5到9的中断,也就说可以嵌套发生,当ADC中断和外部5到9中断同时发生时,它们的抢占优先级别相同,亚优先级别也相同,因为ADC1硬件排在更靠前,则先发生ADC中断,若是两者任何一个中断正在执行,则等此中断执行完,再去执行另一个中断。
今天晚上就先理解到这里,明天晚上继续。
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
上班闲着时,看了一下,事件与中断的关系,通过仔细分析和查资料,是不能混为一谈的,把找到的知识点分析如下:
2 事件与中断
事件:是表示检测到某一动作(电平边沿)触发事件发生了。
中断:有某个事件发生并产生中断,并跳转到对应的中断处理程序中。
事件可以触发中断,也可以不触发
中断有可能被更优先的中断屏蔽,事件不会
事件本质上就是一个触发信号,是用来触发特定的外设模块或核心本身(唤醒).
事件只是一个触发信号(脉冲),而中断则是一个固定的电平信号
通过上图的表示,可以明显看出,蓝 {MOD}是中断发生,红 {MOD}是事件发生,从一到三两者线路是相同的,然后两者分开,上升沿和下降沿用来选择电平方式,中断和事件屏蔽寄存器起到相应的开关作用,软件中断/事件寄存器只要为1对后面的线路都有作用,挂起请求寄存器主要记录电平变化!先理解到这里!
3 SysTick仔细探究
首先看STM官方芯片资料,上面真正涉及到得很少,其中在系统时钟树有个简单的说明如下:
手册如下说明: RCC通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。通过对SysTick控制与状态寄存器的设置,可选择上述时钟或Cortex(HCLK)时钟作为SysTick时钟。通过这句话,可以看出SysTick时钟的来源,从图上也可以详细的看出SysTick是经过8分频得来的,这样好理解了,我们在手册上再仔细找,很能找到一句话:系统嘀嗒校准值固定为9000,当系统嘀嗒时钟设定为9MHz(HCLK/8的最大值),产生1ms时间基准。简单的说,我们把SysTick设置为9000时,就能产生1ms时间基准,说的明白点就是一个中断信号。计算一下:
系统的晶振是8MHZ,PLL9倍频以后为8MHZ*9=72MHz,然后经过8分频为9MHZ,即给systick分配的时钟9MHz,接下来我们再计算,一个计数周期的时间为1/9000000S, 这样写更容易明白点,我要计时1ms的话,即1/1000s的时间,(1/1000)/(1/9000000)=9000,可以看出我们要计时1ms的话设置为9000是这样得来的。其实上面的公式自己可以推导一下,设系统的时钟频率为SystemFrequency,我们要给滴答定时器设置的值为SystemFrequency / (1/时间基准)。例如1ms的设置72000000 / (1/0.001S)=9000.通过这里我们可以很容易的分配时间。注意设置的时候不要超过24位的最大值0xffffff.
其中在库中有专门的配置函数如下:
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SYSTICK_MAXCOUNT) return (1); /* Reload value impossible */
SysTick->LOAD = (ticks & SYSTICK_MAXCOUNT) - 1; /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */
SysTick->VAL = (0x00); /* Load the SysTick Counter Value */
SysTick->CTRL = (1 << SYSTICK_CLKSOURCE) | (1<<SYSTICK_ENABLE) | (1<<SYSTICK_TICKINT); /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
} 看这个函数就很好理解,配置好要基准的时间,就等中断发生吧。
void SysTick_Handler(void)
{
}在中断函数,我们可以划分时间片,不要使CPU空等待.也就是到达你想要的那个时间就执行你想做的动作,主要不超出时间片得时间就行。今晚就先总结到这里,简单的知识写出来容易理解一些,反复来回进度有点慢了。
我们在学STM32的时候函数assert_param出现的几率非常大,上网搜索一下,网上一般解释断言机制,做为程序开发调试阶段时使用。下面我就谈一下我对这些应用的看法,学习东西抱着知其然也要知其所以然。
4 断言机制函数assert_param
我们在分析库函数的时候,几乎每一个函数的原型有这个函数assert_param();下面以assert_param(IS_GPIO_ALL_PERIPH(GPIOx));为例说一下我的理解,函数的参数IS_GPIO_ALL_PERIPH(GPIOx),我们可以寻找到原型
#define IS_GPIO_ALL_PERIPH(PERIPH) (((*(uint32_t*)&(PERIPH)) == GPIOA_BASE) ||
((*(uint32_t*)&(PERIPH)) == GPIOB_BASE) ||
((*(uint32_t*)&(PERIPH)) == GPIOC_BASE) ||
((*(uint32_t*)&(PERIPH)) == GPIOD_BASE) ||
((*(uint32_t*)&(PERIPH)) == GPIOE_BASE) ||
((*(uint32_t*)&(PERIPH)) == GPIOF_BASE) ||
((*(uint32_t*)&(PERIPH)) == GPIOG_BASE))
这个宏定义的作用就是检查参数PERIPH,判断参数PERIPH是否为GPIOX(A...G)基址中的一个,只要有一个为真则其值为真,否则为假,不用多说,这是C语言中基本的逻辑运算。当然这个库函数也用的很有意思,看:首先对PERIPH进行取址,也就是求地址,&ERIPH,然后对这个地址强制转化为32位的指针,即前面加(uint32_t *),然后通过*进行访问这个地址(指针)中的内容。不多说了,看几遍就能明白。
下面我们再回到assert_param这个函数,这个函数是哪里的呢?在stm32f10x_conf.h寻找到原型如下:
#ifdef USE_FULL_ASSERT
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr) ((void)0)
#endif
这是一个预编译文件,若是定义了USE_FULL_ASSERT这个文件,则执行后面的文件,我们在程序中一般都没什么定义,即执行后面这个语句((void)0),这个语句不用多想,没有定义USE_FULL_ASSERT就是什么也不执行。说的明白点,对上面的那个语句IS_GPIO_ALL_PERIPH(GPIOx)不执行任何操作。
若是定义了USE_FULL_ASSERT它,我们调用这个函数assert_param时,及对参数IS_GPIO_ALL_PERIPH(GPIOx)的正确性进行检查,通过一个C语言中的双目运算符来判断,若是返回1,执行语句(void)0,跟上面一样,若是返回0,则执行后面的函数assert_failed((uint8_t *)__FILE__, __LINE__),函数的作用在库函数中有解释,用来指示出错的行数和文件。注意:__FILE__, __LINE__是标准库函数中的宏定义!切记
void assert_failed(uint8_t* file, uint32_t line);刚开始没看明白为什么加在这里,仔细一想是在头文件的函数声明。至于函数实体呢?我们从官方文件的模板中main.c中可以找到。如下:
void assert_failed(u8* file, u32 line)
{ /* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d ", file, line) */
/* Infinite loop */
while (1) { }
} 英文注释也说明了怎么应用,通过输入参数来确定位置,最简单的方法就是串口打印了,这个函数的主要思想是在输入参数有问题的时候,但是有编译不出来,它可以帮你检查参数的有效性,好处不必多言,自己领悟就行。
继续说明如下: assert_param是怎样包含进去的呢?我们在stm32f10x_conf.h这个头文件中定义的函数声明还是宏定义,怎么在其它文件中都能应用呢?也很多网上朋友在刚开始学习的时候都遇到编译不过去的问题出现,最后通过在文件中添加USE_STDPERIPH_DRIVER来解决的:
我们可以在整个工程中进行搜索USE_STDPERIPH_DRIVER,通过头文件可以看出,是使用标准外设文件。在stm32f10x.h文件中我们可以搜索到如下情况:
#if !defined USE_STDPERIPH_DRIVER
/**
* @brief Comment the line below if you will not use the peripherals drivers.
In this case, these drivers will not be included and the application code will
be based on direct access to peripherals registers
*/
#define USE_STDPERIPH_DRIVER
#endif
#ifdef USE_STDPERIPH_DRIVER
#include "stm32f10x_conf.h"
#endif
可以很容易看出来,我们不在那里添加,这个头文件中也给我们设置了开关,只要把第一个的注释去掉,就不用在配置中添加USE_STDPERIPH_DRIVER了,在第二个文件中我们可以知道怎样包含这个控制开关文件了,呵呵。我们也明白为什么我们在写程序的时候只要包含stm32f10x.h就能很容易的包含所有的文件文件了吧,我们只要在stm32f10x_conf.h配置一下就能包含所需要的库文件了。
通过以上可以看出,通过头文件的相互包含,来控制外设以及调试文件的调用,这样我们理清思路,理解起来就好多了。当然在学习中可能有些C语言问题还没有理解透彻,多上网搜一下,或者多看书,很快就搞明白的。
一周热门 更多>