sys层
这一层,主要剖析一下底层核心驱动函数, 基本上要访问一个系统的最小驱动就是这三个。
delay
原理
首先,stm32里面的delay 延时的编程思想:
CM3 内核处理器,内部包含了一个 SysTick 定时器
SysTick 是一个 24 位的向下递减的计数定时器,当计数值减到 0 时,将从 RELOAD 寄存器中自动重装载定时初值,开始新一轮计数。只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息
所以,这里的delay延时,依赖我们的第一个外设:CM3内部的一个systick的定时器。
利用 STM32的内部 SysTick 来实现延时的,这样既不占用中断,也不占用系统定时器。
这里说的不占用中断的意思是不开定时器中断:
SysTick 是内核外设,它确实有一个专属的异常向量(Exception),即 SysTick_Handler。但在实现 delay 函数时,市面上常见的做法(尤其是正点原子、野火等教程)确实是不开启中断的。
这里不占用中断的意思是:不进入中断服务函数(ISR)
DCD SysTick_Handler ; SysTick Handler
|
实现
ucos/freertos 运行需要一个系统时钟节拍(类似“心跳”),而这个节拍是固定的(由 OS_TICKS_PER_SEC 宏定义设置),比如要求 5ms 一次。
一般是由 SysTick 来提供这个节拍,也就是 SysTick要设置为 5ms 中断一次,为 ucos 提供时钟节拍,而且这个时钟一般是不能被打断的(否则就不准了)。
systick定时器,是一个外设,给定主频sysclk时钟,设置技术周期,就可以实现节拍中断
当你实现了系统节拍定时中断后,systick 不能再被随意更改,如果我们还想利用 systick 来做 delay_us 或者 delay_ms 的延时,就必须想点办法了,这里我们利用的是时钟摘取法。
delay_us 为例,
sysclk = 72Mhz, 输入systick定时器,计数分频(1/8)得到 9Mhz的systick信号。那么一个systick脉冲周期 = (1/9)us.
此时如果要delay_us(50), 一直统计 systick 的计数变化,直到数到(50 * 9), 那么就是50us
优点:只是抓取 SysTick 计数器的变化,并不需要修改 SysTick 的任何状态,完全不影响 SysTick 作为 UCOS时钟节拍的功能
缺点:占用cpu
delay_init()
delay_init(72Mhz), 意味着,systick的输入是72Mhz,systick计数值减少72,经过1us
delay_init(72);
static uint32_t g_fac_us = 0;
void delay_init(uint16_t sysclk) { #if SYS_SUPPORT_OS uint32_t reload; #endif
g_fac_us = sysclk;
#if SYS_SUPPORT_OS reload = sysclk; reload *= 1000000 / delay_ostickspersec;
g_fac_ms = 1000 / delay_ostickspersec; SysTick->CTRL |= 1 << 1; SysTick->LOAD = reload; SysTick->CTRL |= 1 << 0; #endif }
|
delay_us()
可见,是占用cpu资源的等待,不停的去读systick->val
void delay_us(uint32_t nus) { uint32_t ticks; uint32_t told, tnow, tcnt = 0; uint32_t reload = SysTick->LOAD;
ticks = nus * g_fac_us; #if SYS_SUPPORT_OS delay_osschedlock(); #endif
told = SysTick->VAL; while (1) { tnow = SysTick->VAL; if (tnow != told) { if (tnow < told) { tcnt += told - tnow; } else { tcnt += reload - tnow + told; } told = tnow; if (tcnt >= ticks) { break; } } }
#if SYS_SUPPORT_OS delay_osschedunlock(); #endif
}
|
可见,这种最基础的延时,就是不停的读systick的计数累计变化,来实现延时的。所以如果支持OS,他需要锁住调度。
当 OS 还未运行的时候,我们的 delay_ms 就是直接由 delay_us 实现的,OS 下的 delay_us
可以实现很长的延时(达到 53 秒)而不溢出!,所以放心的使用 delay_us 来实现 delay_ms,不
过由于 delay_us 的时候,任务调度被上锁了,所以还是建议不要用 delay_us 来延时很长的时间,否则影响整个系统的性能
总结

在没有os的情况下,是不需要systick中断的,
但是如果有rtos,则必须要systick中断来提供系统节拍进行调度
sys
sys_nvic_set_vector_table()
设置中断向量表的偏移(VTOR)
void sys_nvic_set_vector_table(uint32_t baseaddr, uint32_t offset) { SCB->VTOR = baseaddr | (offset & (uint32_t)0xFFFFFE00); }
|
sys_stm32_clock_init()
设置从时钟源到系统时钟sysclk,到所有外设总线时钟
void sys_stm32_clock_init(uint32_t plln) { HAL_StatusTypeDef ret = HAL_ERROR; RCC_OscInitTypeDef rcc_osc_init = {0}; RCC_ClkInitTypeDef rcc_clk_init = {0};
rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE; rcc_osc_init.HSEState = RCC_HSE_ON; rcc_osc_init.HSEPredivValue = RCC_HSE_PREDIV_DIV1; rcc_osc_init.PLL.PLLState = RCC_PLL_ON; rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE; rcc_osc_init.PLL.PLLMUL = plln; ret = HAL_RCC_OscConfig(&rcc_osc_init);
if (ret != HAL_OK) { while (1); }
rcc_clk_init.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1; rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV2; rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV1; ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_2);
if (ret != HAL_OK) { while (1); } }
|
uart
这个单独一章节讲