开源解读寄存器sys.c内部源码

#include "./SYSTEM/sys/sys.h"

void sys_nvic_set_vector_table(uint32_t baseaddr, uint32_t offset) // (设置中断向量表)

{

    // Cortex-M处理器的VTOR寄存器只关心地址的0~8位,而9~31位(高23位)的值都被忽略

    // 按位与运算保留偏移地址的0~8位二进制位,

    // 这样就能够将中断向量表起始位置的地址偏移设置到 VTOR 寄存器中,从而完成中断向量表的偏移操作。

    SCB->VTOR = baseaddr | (offset & (uint32_t)0xFFFFFE00);

    // 实现方式:将指定的中断向量表的偏移地址 offset 和最低的9位(即偏移地址的0~8位)按位与运算,

    // 再同变量 baseaddr 按位或操作,然后将结果赋值给VTOR寄存器。

}

static void sys_nvic_priority_group_config(uint8_t group) // (--局部函数-- 配置NVIC中断优先级分组,)

{

    /*--补充知识--

    NVIC_PRIORITYGROUP_4是一种基于中断控制器(NVIC)实现优先级分组的方案。

    它是基于Cortex-M处理器中的硬件优先级的分组方法。

    在NVIC_PRIORITYGROUP_4中,有4个优先级组,从0到3(优先级从高到低)。

    在每个优先级组中,有16个优先级级别。其中,优先级0为最高级别,15为最低级别。

    在同一个优先级组内,可以为每个中断设置不同的子优先级

    抢占优先级和响应优先级都被赋予16个级别。抢占优先级用于中断之间的抢占,响应优先级用于中断服务函数内部的优先级控制。

    中断向量表---------->参考手册p130页表54

    */

    uint32_t temp, temp1;

    temp1 = (~group) & 0x07; // 对变量 group 进行按位求反(~)再和二进制数 0x07 的按位与操作

    temp1 <<= 8;             // 将 temp1 左移 8 位。目的是将优先级分组的值放到 AIRCR 的第 15~13 位中

    temp = SCB->AIRCR;       // 读取 System Control Block(SCB)寄存器中的AIRCR(应用程序中断和复位控制寄存器)的值给temp

    temp &= 0X0000F8FF;      // 将 temp 与 0x0000F8FF 按位与操作,目的是将 AIRCR 的(中断优先级组)和(子优先级)置 0,以便重新配置

    temp |= 0X05FA0000;      // 将 temp 的值与 0x05FA0000 按位或操作,该数的意思是使用 NVIC_PRIORITYGROUP_4 方案进行优先级编码。在该方案中,优先级分组是 0b10

    temp |= temp1;           // 将 temp1 与 temp 按位或操作,将分组值放置到对应的位中。最终将前三位设置成为优先级分组值。

    SCB->AIRCR = temp;       // 将 temp 写入 (SCB)寄存器中的(AIRCR),从而完成配置 NVCI 中断优先级分组的全过程。

}

void sys_nvic_init(uint8_t pprio, uint8_t sprio, uint8_t ch, uint8_t group) // (NVIC中断控制器的初始化)

{

    sys_nvic_priority_group_config(group); // 调用sys_nvic_priority_group_config函数,参数为group,从而完成配置 NVCI 中断优先级分组

    uint32_t temp;

    temp = pprio << (4 - group);           // 将pprio的值左移(4-group)位,将结果存入temp中。目的是计算出主优先级

    temp |= sprio & (0x0f >> group);       // 将sprio的低4位存入temp中,使用0x0f右移(group)位再按位与操作,保留temp中的低4位。目的是计算出从优先级

    temp &= 0xf;                           // 将temp与0xf按位与(&)操作,以保证temp中的值不大于15。

    NVIC->ISER[ch / 32] |= 1 << (ch % 32); // 将NVIC->ISER[ch/32]寄存器中的针对相应中断线的位置1,使能相应的中断线;

    NVIC->IP[ch] |= temp << 4;             // 将计算出的优先级temp左移4位后写入NVIC->IP[ch]寄存器,以指定相应中断的优先级。

}

void sys_nvic_ex_config(GPIO_TypeDef *p_gpiox, uint16_t pinx, uint8_t tmode) //(NVIC外部中断配置)

{

    uint8_t offset;

    uint32_t gpio_num = 0;

    uint32_t pinpos = 0, curpin = 0, pos = 0;

    /*参数说明

    offset用于存储引脚号在EXTICR寄存器中的位偏移量。

    gpio_num存储对应的GPIO编号,

    pinpos和curpin分别用于遍历和检查引脚号中的每一位,

    pos用于生成位掩码,

    */

    gpio_num = ((uint32_t)p_gpiox - (uint32_t)GPIOA) / 0X400; // 计算得到GPIO端口GPIOA在AHB总线中的地址与输入参数p_gpiox的地址之差,再除以0x400,即可得到对应的GPIO编号。

    RCC->APB2ENR |= 1 << 0;                                   // 使能AFIO时钟,在RCC->APB2ENR寄存器中将AFIO时钟位置1,以便访问AFIO寄存器

    for (pinpos = 0; pinpos < 16; pinpos++)                   // 遍历引脚号中的每一位

    {

        pos = 1 << pinpos;   // 使用pos生成位掩码,

        curpin = pinx & pos; // 使用按位与(&)操作取出引脚号的当前位curpin

        if (curpin == pos)   // 如果当前位为1,则说明该引脚需要配置EXTI功能。

        {

            offset = (pinpos % 4) * 4;                       // 计算它在EXTICR寄存器中对应的位偏移量offset,

            AFIO->EXTICR[pinpos / 4] &= ~(0x000F << offset); // 然后将AFIO->EXTICR[pinpos / 4]寄存器中指定位置的值清零

            AFIO->EXTICR[pinpos / 4] |= gpio_num << offset;  // 再将gpio_num按照偏移量offset的位偏移值存入该位置

            EXTI->IMR |= 1 << pinpos;                        // 将EXTI->IMR寄存器中对应引脚的位置1,使能该引脚对应的EXTI中断(即使能line BITx的中断)。

            /*另外,根据传入的触发方式tmode来配置EXTI对应线的触发方式*/

            if (tmode & 0x01)

                EXTI->FTSR |= 1 << pinpos; // 如果tmode的最低位为1,则使能下降沿触发;

            if (tmode & 0x02)

                EXTI->RTSR |= 1 << pinpos; // 如果tmode的次低位为1,则使能上升沿触发。

        }

    }

}

void sys_gpio_remap_set(uint8_t pos, uint8_t bit, uint8_t val) //(GPIO引脚重映射)

{

    uint32_t temp = 0; // temp用于保存生成的位掩码,i用于遍历需要配置的位。

    uint8_t i = 0;

    RCC->APB2ENR |= 1 << 0;   // 使能AFIO时钟,temp用于保存生成的位掩码,i用于遍历需要配置的位。

    for (i = 0; i < bit; i++) // 根据传入的bit参数计算出需要配置的位掩码temp

    {

        temp <<= 1; // 参数bit表示有多少个位需要配置,因此需要生成一个低bit位都为1的掩码,

        temp += 1;  // 通过使用temp按位左移及按位或运算进行生成。

    }

    AFIO->MAPR &= ~(temp << pos);       // 在AFIO->MAPR寄存器中将需要配置的位置清零,这可以通过temp左移pos位后取反(~)再与原值取按位与(&)操作实现

    AFIO->MAPR |= (uint32_t)val << pos; // 最后,在AFIO->MAPR寄存器中将需要配置的位设置为新值。可以将新值左移pos位后再按位或(|)到原值中完成设置。

}

//(GPIO引脚设置)

// 结构体定义:

// typedef struct

// {

//     volatile uint32_t MODER;   // GPIO端口模式寄存器

//     volatile uint16_t OTYPER;  // GPIO端口输出类型寄存器

//     uint16_t RESERVED0;        // 保留,值为0x02

//     volatile uint32_t OSPEEDR; // GPIO端口输出速度寄存器

//     volatile uint32_t PUPDR;   // GPIO端口上拉/下拉寄存器

//     volatile uint16_t IDR;     // GPIO端口输入数据寄存器

//     uint16_t RESERVED1;        // 保留,值为0x0A

//     volatile uint16_t ODR;     // GPIO端口输出数据寄存器

//     uint16_t RESERVED2;        // 保留,值为0x0E

//     volatile uint16_t BSRR;    // GPIO端口位设置/重置寄存器

//     uint16_t RESERVED3;        // 保留,值为0x12

//     volatile uint16_t LCKR;    // GPIO端口配置锁寄存器

//     uint16_t RESERVED4;        // 保留,值为0x16

//     volatile uint16_t AFR[2];  // GPIO端口替代功能寄存器

//     uint16_t RESERVED5[10];    // 保留,值为0x1C-0x28

//     volatile uint32_t BRR;     // GPIO位重置寄存器

// } GPIO_TypeDef;

void sys_gpio_set(GPIO_TypeDef *p_gpiox, uint16_t pinx, uint32_t mode, uint32_t otype, uint32_t ospeed, uint32_t pupd)

{

    /*参数介绍

    p_gpiox指向GPIOx(x为A、B、C等)的寄存器地址,

    pinx代表要配置的引脚(可能是多个,用二进制位表示)

    ,mode表示引脚工作模式(输入、输出、复用等),

    otype表示输出类型(开漏或推挽),

    ospeed表示输出速率,

    pupd表示上下拉模式。*/

    uint32_t pinpos = 0, pos = 0, curpin = 0;

    // pinpos用于循环检查GPIO口的每个引脚,pos用于检查引脚是否需要配置,curpin是检查后的结果。

    uint32_t config = 0;

    // config变量,初始值为0,用于保存某一个IO的设置

    for (pinpos = 0; pinpos < 16; pinpos++) // 使用for循环,依次处理pinpos为0~15的引脚。

    {

        pos = 1 << pinpos;   // 将1左移pinpos位,赋值给pos,即pos用于检查对应引脚是否要配置

        curpin = pinx & pos; // 引脚配置信息存储在pinx中,使用位运算&得到curpin,即该引脚是否需要配置。

        if (curpin == pos) // 如果curpin等于pos,则表示该引脚需要配置。

        {

            config = 0;                           // 如果需要配置,首先将config清零,表示该引脚的配置为默认模拟输入模式。

            if ((mode == 0X01) || (mode == 0X02)) // 如果mode为普通输出模式或复用功能模式

            {

                config = ospeed & 0X03;        // 将速度参数ospeed低2位和config的低2位进行或操作,得到bit0/1,表示MODE[1:0]的设置。

                config |= (otype & 0X01) << 2; // 将otype低位和config的第2位进行或操作,得到CNF[0]的设置。

                config |= (mode - 1) << 3;     // 将mode减去1的值左移3位,再和config的第3位进行或操作,得到CNF[1]的设置。

            }

            else if (mode == 0) // 如果mode等于0则为普通输入模式。

            {

                if (pupd == 0) // 如果不带上下拉,即浮空输入模式。

                {

                    config = 1 << 2; // 将config第2 3位设置为0 1,表示浮空输入模式。

                }

                else // 否则(即带上下拉输入模式)。

                {

                    config = 1 << 3; // 将config的第2 3位设置为1 0,表示为上下拉输入模式

                    /*下面为根据pupd参数的低位,对GPIO口的引脚进行上拉或者下拉。*/

                    p_gpiox->ODR &= ~(1 << pinpos);          // 将ODR的对应引脚位清零,

                    p_gpiox->ODR |= (pupd & 0X01) << pinpos; // 再根据pupd的低位,左移对应引脚位,进行或操作,设置为上拉或者下拉。

                }

            }

            /*下面根据pinpos来判断引脚所在的CRH或者CRL寄存器。*/

            if (pinpos <= 7) // 如果当前处理的引脚pinpos的位置在低8位,则对应寄存器是CRL寄存器。

            {

                p_gpiox->CRL &= ~(0X0F << (pinpos * 4)); // 首先把pinpos左移2位(相当于乘以4)得到(低8位)偏移量,然后将0X0F左移偏移量位,

                                                         // 得到要写入寄存器的掩码,再使用位反码来清除掩码指定的位

                p_gpiox->CRL |= config << (pinpos * 4);  // 将config左移偏移量位,并将结果写入CRL寄存器。

            }

            else // 如果当前处理的引脚pinpos的位置在高8位,则对应寄存器是CRH寄存器。

            {

                p_gpiox->CRH &= ~(0X0F << ((pinpos - 8) * 4)); // 同上,只不过偏移量应减去8(高8位)再进行计算

                p_gpiox->CRH |= config << ((pinpos - 8) * 4);  // 将config左移偏移量位,并将结果写入CRH寄存器。

            }

        }

    }

}

void sys_gpio_pin_set(GPIO_TypeDef *p_gpiox, uint16_t pinx, uint8_t status) //(GPIO单个引脚输出状态)

{

    /*参数介绍

    p_gpiox指向GPIOx(x为A、B、C等)的寄存器地址,pinx代表要控制的引脚,status则表示引脚状态(0或1)。

    */

    if (status & 0X01) // 判断status的最低比特位(即0或1),确定要执行的引脚操作

    {

        // 此时将pinx的值与掩码0xFFFF(即所有比特位均为1)进行按位或运算,将BSRR寄存器的低16位设置为1,即将对应引脚的电平设置为高电平。

        p_gpiox->BSRR |= pinx;

    }

    else

    {

        // 此时将pinx的值与掩码0xFFFF(即所有比特位均为1)进行按位或运算,将BSRR寄存器的低16位设置为1,即将对应引脚的电平设置为高电平。

        p_gpiox->BSRR |= (uint32_t)pinx << 16;

    }

}

uint8_t sys_gpio_pin_get(GPIO_TypeDef *p_gpiox, uint16_t pinx) // (读取GPIO单个引脚状态)

{

    /*参数介绍

    p_gpiox指向GPIOx(x为A、B、C等)的寄存器地址

    pinx代表要获取状态的引脚

    */

    if (p_gpiox->IDR & pinx) // if判断语句判断IDR的某一比特位(即引脚电平数据)是否为1,确定该引脚的实际电平状态

    {

        return 1; // 意味着该引脚电平为高电平,返回1

    }

    else

    {

        return 0; // 如果该比特位为0,意味着该引脚电平为低电平,返回0。

    }

}

void sys_wfi_set(void) // (进入低功耗状态)

{

    __ASM volatile("wfi"); // 代码使用了汇编指令 wfi,该指令会将处理器置于 WFI 模式,并等待中断信号的触发。

}

void sys_intx_disable(void) // (关闭所有中断)

{

    __ASM volatile("cpsid i"); // 代码使用了汇编指令 cpsid i,该指令会将处理器的中断使能位清零,禁止所有中断的触发。

}

void sys_intx_enable(void) // (开启所有中断)

{

    __ASM volatile("cpsie i"); // 代码使用了汇编指令 cpsie i,该指令会将处理器的中断使能位置位,允许中断请求的触发。

}

void sys_msr_msp(uint32_t addr) // (设置栈顶地址)

{

    // (Cortex Microcontroller Software Interface Standard)

    __set_MSP(addr); // 代码使用了CMSIS库函数 __set_MSP(),该函数用于设置MSP寄存器的值为传入的addr参数。

}

void sys_standby(void) // (进入待机模式)

{

    RCC->APB1ENR |= 1 << 28; // 使能RCC模块中的APB1总线上的PWR(Power Control)模块时钟,(将1 << 28左移28位,得到0x10000000表示PWR)

    PWR->CSR |= 1 << 8;      // 将PWR模块的CSR寄存器的第8位(PDDS)置为1,表示进入待机模式。

    PWR->CR |= 1 << 2;       // 将PWR模块的CR寄存器的第2位(ULP)置为1,表示选择ULP(Ultra Low Power)模式,并开启进入待机模式的操作。

    PWR->CR |= 1 << 1;       // 将PWR模块的CR寄存器的第1位(PDDS)置为1,PDDS(Power Down Deep Sleep)模式,并开启进入待机模式的操作。

    SCB->SCR |= 1 << 2;      // 将SCB(System Control Block)模块的SCR寄存器的第2位(SLEEPDEEP)置为1,表示进入深度睡眠状态。

    sys_wfi_set();           // (进入低功耗状态)

}

void sys_soft_reset(void) // (系统软件复位)

{

    // 0X05FA0000和(uint32_t)0x04的"或"运算,可以得到一个特定值0x05FA0004,表示执行系统软件复位。

    // 将这个值赋给SCB的AIRCR寄存器,就会让处理器执行软件复位操作。

    SCB->AIRCR = 0X05FA0000 | (uint32_t)0x04;

}

uint8_t sys_clock_set(uint32_t plln) // (时钟设置函数)

{

    uint32_t retry = 0;

    uint8_t retval = 0;

    // plln参数,表示需要设置的时钟倍频系数。retry和retval分别用于记录重试次数和返回值。

    // RCC是一个寄存器控制器模块

    RCC->CR |= 0x00010000; // 使用"|="运算,将该寄存器的位16(HSEON)置为1,表示要开启外部高速时钟(HSE)。

    while (retry < 0XFFF0)

    {

        __nop(); // 空指令语句__nop(),以等待外部高速时钟就绪。

        if (RCC->CR & (1 << 17) && retry > 0X8000)

        {

            break; // 如果外部高速时钟稳定就绪,则会将时钟控制寄存器的位17(HSERDY)置为1,此时可以退出循环。

        }

        retry++;

    }

    if (retry >= 0XFFF0) // 外部高速时钟需要一定的时间才能稳定,因此设置了一个最大重试次数0XFFF0,如果超过该次数仍然无法就绪,

    {

        retval = 1; // retval为1表示时钟设置失败。

    }

    else // 如果外部高速时钟稳定就绪,就进入到时钟配置的核心部分。

    {

        RCC->CFGR = 0X00000400;

        // 将时钟分频寄存器CFGR的值设置为0X00000400,表示AHB(Advanced High-performance Bus)和APB

        // (Advanced Peripheral Bus)分别不分频,即AHB和APB时钟频率相同。

        plln -= 2;               // 传入的时钟倍频系数plln减去2,

        RCC->CFGR |= plln << 18; // 并将其左移18位,然后使用"|="运算,将其设置为CFGR寄存器对应的位[29:24],表示设置PLL时钟的倍频系数。

        RCC->CFGR |= 1 << 16;    // 将其设置为CFGR寄存器对应的位[29:24],表示设置PLL时钟的倍频系数。

        /*配置Flash存储器的延时周期*/

        FLASH->ACR = 1 << 4;  // 使用"或"运算将ACR寄存器的位4(LATENCY)置为1,表示设置Flash存储器的延时周期为1个时钟周期。

        FLASH->ACR |= 2 << 0; // 使用"或"运算将ACR寄存器的位[2:0]设置为2,表示使用HCLK(AHB时钟)延时。

        /*上述配置目的为:使得Flash存储器能够支持系统时钟在48MHz时的工作。*/

        RCC->CR |= 1 << 24;      // 使用"或"运算将CR寄存器的位24(PLLON)置为1,使能PLL时钟。

        while (!(RCC->CR >> 25)) // 使用while循环等待时钟稳定,即等待CR寄存器的PLL锁定位(PLLRDY)变为1,表示PLL时钟稳定。

            ;

        RCC->CFGR |= 2 << 0;                   // 使用while循环等待时钟稳定,即等待CR寄存器的PLL锁定位(PLLRDY)变为1,表示PLL时钟稳定。

        while (((RCC->CFGR >> 2) & 0X03) != 2) // 使用while循环等待CFGR寄存器的位[3:2]变为2,表示PLL时钟作为系统时钟已经稳定。

            ;

    }

    return retval; // 最后,返回时钟设置的状态,如果返回值为0表示时钟设置成功,如果返回值为1表示设置失败。

}

void sys_stm32_clock_init(uint32_t plln) // (时钟初始化函数)

{

    RCC->APB1RSTR = 0x00000000; // 将APB1的复位寄存器清零,即将其所有位设置为0,这样可以确保时钟初始化时这个总线上的所有设备都处于复位状态。

    RCC->APB2RSTR = 0x00000000; // 将APB2的复位寄存器清零,即将其所有位设置为0,这样可以确保时钟初始化时这个总线上的所有设备都处于复位状态。

    RCC->AHBENR = 0x00000014;   // 设置AHB总线上的时钟使能寄存器,使能DMA、GPIOA和GPIOB外设。

    RCC->APB2ENR = 0x00000000;  // 将APB2的时钟使能寄存器清零,即将其所有位设置为0,这样可以确保时钟初始化时这个总线上的所有设备都未启用。

    RCC->APB1ENR = 0x00000000;  // 将APB1的时钟使能寄存器清零,即将其所有位设置为0,这样可以确保时钟初始化时这个总线上的所有设备都未启用。

    RCC->CR |= 0x00000001;      // 设置RCC时钟控制寄存器的开启位,使能外部高速时钟(HSE)。

    RCC->CFGR &= 0xF8FF0000;    // 清除RCC时钟配置寄存器的PLL、分频器和时钟选项位。

    RCC->CR &= 0xFEF6FFFF;      // 清除RCC时钟配置寄存器的PLL、分频器和时钟选项位。

    RCC->CR &= 0xFFFBFFFF;      // 清除RCC时钟控制寄存器的HSE禁止位

    RCC->CFGR &= 0xFF80FFFF;    // 清除RCC时钟配置寄存器的PLL倍频位

    RCC->CIR = 0x009F0000;      // 清空RCC时钟中断寄存器。

    sys_clock_set(plln);        // 用sys_clock_set函数设置PLL倍频系数和选择是否将PLL作为系统时钟。

#ifdef VECT_TAB_RAM

    sys_nvic_set_vector_table(SRAM_BASE, 0x0); // 选择向量表在RAM中的地址,用于用户程序的跳转和中断处理。

#else

    sys_nvic_set_vector_table(FLASH_BASE, 0x0); // 选择向量表在FLASH中的地址,用于用户程序的跳转和中断处理。

#endif

}

猜你喜欢

转载自blog.csdn.net/JohnJill/article/details/130546178