目录
DMA
1、画图Cortex-M3内部结构图,要体现相应的总线,并标注其作用。
Icode:程序存在Flash中,通过ICode(Instruction Code)总线与Cortex连接取指令。
Dcode:数据被存放在外设内部Flash(SRAM)中,通过DCode(Data Code)访问。
Systembus: 访问外设的寄存器,通常说的寄存器编程就是用这条总线的。
DMA: 数据变量拷贝时可以不占用CPU,通过DMA(Direct Memory Access)总线和DMA1、DMA2完成。
2、关于RCC时钟,完成如下任务
(1)画出RCC时钟树简图,要体现出相关的时钟源
上面这张的图的意思是:Cortex‐M3和core是不一样的,core就是cpu,Cortex‐M3还包含了NVIC(里面有所systick)等调试组件
而上面的时钟树图是在stm32芯片角度上的,可以看到HCLK 到了core,FCLK到了Cortex‐M(也就是里面的systick)而且还有一个HCLK /8的也到了Cortex‐M(这个指向明确加了个system timer)
1.SYSCLK:系统时钟,是STM32大部分器件的时钟来源,主要由AHB预分频器分配到各个部件。
2.HCLK:由AHB预分频器直接输出得到,它是高速总线AHB的时钟信号,提供给存储器,DMA及Cortex内核,是Cortex内核运行的时钟,CPU主频就是这个信号。
3.FCLK:由CM3core内核内部时钟源产生,是内核的“自由运行时钟”。“自由”表现在它不来自时钟HCLK,但值与HCLK相同。因此在HCLK停止时FCLK也可以继续运行。也就是说,即使CPU休眠了,也能够采样到外部中断和跟踪休眠事件。低功耗模式下使用。
【ARMJISHU注:FCLK 为处理器的自由振荡的处理器时钟,用来采样中断和为调试模块计时。在处理器休眠时,通过FCLK 保证可以采样到中断和跟踪休眠事件。 Cortex-M3内核的“自由运行时钟(free running clock)”FCLK。“自由”表现在它不来自系统时钟HCLK,因此在系统时钟停止时FCLK 也继续运行。FCLK和HCLK 互相同步。FCLK 是一个自由振荡的HCLK。FCLK 和HCLK 应该互相平衡,保证进入Cortex-M3 时的延迟相同。】
4.PCLK1:外设时钟,由APB1分频得到,最大可为72Mhz,提供给APB1总线上的外设使用。
5.PCLK2:外设时钟,由APB2预分频输出得到,最大为72Mhz,提供给APB2总线上的外设。
Systick 介绍
Systick定时器,是一个简单的定时器,对于CM3、CM4内核芯片,都有Systick定时器。Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。
Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。Systick中断的优先级也可以设置。
实际上,Systick就是一个定时器而已,只是它放在了NVIC中,主要的目的是为了给操作系统提供一个硬件上的中断,称之为滴答中断操作系统进行运转的时候,也会有时间节拍。它会根据节拍来工作,把整个时间段分成很多小小的时间片,而每个任务每次只能运行一个时间片的时间长度,超时就退出给别的任务运行,这样可以确保任何一个任务都不会霸占操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统的节拍。只要不把它在SysTick控制及状态寄存器中的使能位清除,就一直执行。
SysTick相关寄存器
SysTick有四个寄存器,分别为CTRL(控制与状态寄存器)、LOAD(自动重装载值寄存器)、VAL(当前值寄存器)、CALIB(校准值寄存器)。
上图中的外部时钟源指的是相对于core的内部,但还是在stm32芯片上
内核时钟数值上等于HCLK时钟,可以通过设置HCLK时钟来决定FCLK时钟的大小
参考:《Cortex-M3 权威指南》
systick定时器延时操作详情参考如下链接:
STM32入门:Systick(嘀嗒定时器)学习
【STM32】SysTick滴答定时器(delay延时函数讲解)
整个系统有四个部分需要时钟
- USB
- 各种外设
- RTC
- 看门狗
前两种主要是靠内部或外部高速时钟,默认复位后是内部高数时钟
RTC通过RTCsel来从HSE、LSE、LSI中选择一个
看门狗就是内部低速时钟
PLL准确的的来说并不是一个时钟,它是将时钟源传过来的时钟信号倍频再传给后续的外设
整个时钟树里有四个选择开关:
1.PLLXTPRE 倍频前分频选择开关,在传给PLLMUTL倍频前要不要分频
2.PLLSRC 倍频前输入信号来源选择开关,是选择内部的还是外部的
3.SW 各种外设时钟源选择开关,可以选择内部的还是外部的还是PLL倍频后 上面PLL倍频后的有内部倍频后的还是外部直接倍频 后的还是外部分频再倍频后的 所以SW切换后的有5种不同路径的时钟源个各种外设4.RTCsel RTC时钟源选择开关,可以切换三种来源,一高速外部时钟128分频后,二外部低速,三内部低速
复位后默认的都是内部高速和内部低速时钟源,倍频也没有打开
(2)简述由8M晶振到72M主频的过程,以及通过寄存器方式配置72M主频的过程
要达到72MHz ,那就必须要开倍频,且必须使用外部高速时钟,内部高速时钟在倍频前有一个2分频所以最高只能倍频到64MHz
以外部晶振为8MHz来说,路径是,
PLLXTPRE不分频-> PLLSRC选外部高速 -> PLLMULT 设为9倍频 -> SW切换为PLL输入
上述路径可以使系统主频达到72MHz
以下是整个的配置过程
RCC_CR 地址:0x40021000 + 0 = 0x40021000 复位值:0x00000083 16位HSEON置一,然后判断17位HSERDY,外部时钟就绪后进性下一步
RCC_CFGR 地址:0x40021000 + 0x04 = 0x40021004 复位值:0x00000000 就是我们上边说的哪个顺序,16位PLLSRC置1,17位PLLXTPRE置0 ,然后18-21位的PLLMUL
置0111,4-7位AHB预分频置0000不分频,8-10位APB1预分频置100,2分频(因为ahb是72mhz,apb1最高可以36mhz,所以必须分频)。11-13位APB2预分频置000,不分频。RCC_CR 24位PLLON置一,判断25位PLLRDY,为1说明就绪,继续下一步。
RCC_CFGR 0-1位SW置10,PLL输出作为系统时钟,然后判断2-3位,是否为10(PLL输出作为系统时钟)
(3)阅读如下代码,理解并解释每一步的作用
typedef unsigned short int uint16_t; //定义一个short型变量占两个字节长度
typedef unsigned int uint32_t; //定义一个int型变零占四个字节长度
#define _IO volatile
#define PERIPH_BASE ((uint32_t)0x40000000) //定义外设的基地址
#define APB2PPERIPH_BASE (PERIPH_BASE + 0x10000)//定义外设总线APB2的基地址
#define GPIOC_BASE (APB2PPERIPH_BASE + 0x1000)//定义外设总线APB2上挂载的GPIOC基地址
#define GPIOC (GPIOC_BASE)//定义GPIOC引脚的基地址
#define GPIOC_CRH (GPIOC + 0x04) //定义GPIOC端口配置高寄存器
#define GPIOC_ODH (GPIOC + 0x0c)//定义端口数据输出寄存器
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)//定义高速总线AHB的基地址
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)//定义RCC时钟基地址
#define RCC (RCC_BASE) //定义RCC时钟基地址
#define RCC_APB2ENR (RCC + 0x18) //定义使能挂载在APB2上的外设的寄存器
#define RCC_CR (RCC + 0x00)//定义时钟控制寄存器
#define RCC_CFGR (RCC + 0x04)//定义时钟配置寄存器
#define FLASH_R_BASE (AHBPERIPH_BASE + 0x2000) //定义AHB上挂载的flash接口地址
#define FLASH (FLASH_R_BASE )
#define FLASH_ACR (FLASH + 0x00)
void RCC_init(uint16_t PLL) //传入倍频数PLL
{
uint32_t temp = 0;
*((uint32_t *)RCC_CR) |= 0x00010000;//将HSEON位置1
while (!(*((uint32_t*)RCC_CR) >> 17)); //等待HSERDY位硬件置1后跳出循环
*((uint32_t *)RCC_CFGR) = 0X00000400; //将PPRE1位置100,,将HCLOCK二分频为PCLOCK1
PLL -= 2; //PLLMUL位是从0000开始二倍频,所以要将传入的倍频数减去一个2等于PLLMUL位实际应该写入的真实值
*((uint32_t *)RCC_CFGR) |= PLL << 18; //将倍频位置为需要的倍频
*((uint32_t *)RCC_CFGR) |= 1 << 16; //将PLLSRC位置1,选择HSE为倍频输入时钟源
*((uint32_t *)FLASH_ACR) |= 0x2; //将LATENCY位置为010,两个等待状态,闪存读取要和时钟频率一致不然程序会跑飞
*((uint32_t *)RCC_CR) |= 0x01000000; //将PLLON位置1,开启PLL倍频
while (!(*((uint32_t*)RCC_CR) >> 25)); //等待PLLRDY位硬件置1后跳出循环
*((uint32_t *)RCC_CFGR) |= 0x00000002; //将SW位置1,选择倍频后的时钟源作为系统时钟源
while (temp !=0x02) //等待SWS位硬件置为10时,PLL成功作为系统时钟源输出
{
temp = *((uint32_t *)RCC_CFGR) >> 2; //将SWS的两位移到最右边
temp &= 0x03; //与11取与
}
}
3.flash寄存器设置延时周期的作用
4.通过寄存器点亮led
typedef unsigned int u32;
#define GPIOC_BASE 0x40011000
#define PERIPH_BASE 0x40000000
#define APB2_BASE (PERIPH_BASE+0x10000)
#define AHB_BASE (PERIPH_BASE+0x20000)
#define RCC_BASE (AHB_BASE+0x1000)
#define RCC_APB2ENR (RCC_BASE+0x18)
#define GPIOx_CRH_OFF 0x0004
#define GPIOx_ODR_OFF 0x000C
#define GPIOx_CRH (GPIOC_BASE+GPIOx_CRH_OFF)//0x4001 1004
#define GPIOx_ODR (GPIOC_BASE+GPIOx_ODR_OFF)//0x4001 100C
void delay(u32 x)
{
u32 i = 0;
while(x--)
{
i = 10000000;
while(i--);
}
}
int main(void)
{
*((unsigned int *)RCC_APB2ENR) |= 0x00000010;
//*((unsigned int *)GPIOx_CRH) = 0x00300000;//error
//先清除寄存器相应位的值
*((unsigned int *)GPIOx_CRH) &= 0xff0fffff;
//然后再进行对相应位赋值
*((unsigned int *)GPIOx_CRH) |= 0x00300000;
//*((unsigned int *)GPIOx_CRH) = *((unsigned int *)GPIOx_CRH) + 0x00300000;
//*(GPIOx_CRH) = 0x00300000;
*((unsigned int *)GPIOx_ODR) &= ~(1<<13);
*((unsigned int *)GPIOx_ODR) |= 0x00000000;
//闪烁
while(1)
{
//亮
*((unsigned int *)GPIOx_ODR) &= ~(1<<13);
*((unsigned int *)GPIOx_ODR) |= 0x00000000;
//延时一些时间
//sleep(2);
delay(1);
//循环
//灭
*((unsigned int *)GPIOx_ODR) &= ~(1<<13);
*((unsigned int *)GPIOx_ODR) |= 0x00002000;//0000 0000 0000 0000 0010 0000 0000 0000
//延时一些时间
delay(1);
}
return 0;
}
上面一种是直接对ODR寄存器输出高低电平来控制亮灭
下面这种是通过BSRR寄存器来控制输出高低电平来控制亮灭
/*******************************************************************************
*
* 普中科技
--------------------------------------------------------------------------------
* 实 验 名 : 使用寄存器点亮一个LED
* 实验说明 : 操作寄存器控制LED1指示灯闪烁
* 连接方式 :
* 注 意 :
*******************************************************************************/
#include "stm32f10x.h"
typedef unsigned int u32; //类型重定义 unsigned int -- u32
void SystemInit()
{
}
/*******************************************************************************
* 函 数 名 : delay
* 函数功能 : 延时函数,通过while循环占用CPU,达到延时功能
* 输 入 : i
* 输 出 : 无
*******************************************************************************/
void delay(u32 i)
{
while(i--);
}
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
RCC_APB2ENR |= 1<<4;
GPIOC_CRH &= ~( 0x0F<< (4*5));//将MODE13和CONF13四位清零
GPIOC_CRH |= (1<<4*5); //将MODE13和CONF13四位置为0100
GPIOC_ODR &=~(1<<13);//配置输出低电平
GPIOC_BSRR=(1<<(16+13));
while(1)
{
GPIOC_BSRR=(1<<(16+13));
delay(0x1FFFFF);
GPIOC_BSRR=(1<<(13));
delay(0x1FFFFF);
}
}