RISC-V 32架构实践专题五(从零开始写操作系统-板级时钟初始化)

        完成整个代码框架搭建后,后续将对此代码框架进行填充。

        首先需要初始化MCU的时钟源以及分频系数,同时再初始化串口,完成输出打印。此时将不采用中断来实现上述功能,在后续的专题中再进行中断的初始化等操作。

一、代码重定位

        首先需要修改链接脚本文件,获取.data在flash中的存储起始位置与.data重定位后在ram中的起始位置,以及.data在ram中的结束位置;同时也获取到.bss段在ram中的位置。

1.1 链接脚本修改

    /* Section for text (init最开始的代码段) */
    .init : 
    {
        /* Entry point and code */
        /* KEEP的作用是保留.init段中的所有内容,SORT_NONE的作用则是不对此段进行优化排序;使得.init段按照源代码中的顺序保持不变 */
        KEEP(*(SORT_NONE(.init)))
    } >FLASH AT>FLASH

    /* Section for text (code) */
    .text : 
    {
        /* code */
        *(.text)
    } >FLASH AT>FLASH

    /* 设置数据段在ram中存放的位置 */
    .valign :
    {
        . = ALIGN(4);
        PROVIDE(_vma_data_start = .);
    } >RAM AT>FLASH

    /* 设置数据段在flash中存放的位置 */
    .lalign :
    {
        . = ALIGN(4);
        PROVIDE(_lma_data_start = .);
    } >FLASH AT>FLASH

    /* Section for data */
    .data : 
    {
        /* Initialized data */
        *(.data)
        . = ALIGN(4);
        /* 获取数据段在ram中的结束位置 */
        PROVIDE(_vma_data_end = .);
    } >RAM AT>FLASH

    /* Section for uninitialized data */
    .bss : 
    {
        . = ALIGN(4);
        /* 获取bss段在ram中的开始位置 */
        PROVIDE(_sbss = .);
        /* Uninitialized data */
        *(.bss)
        . = ALIGN(4);
        /* 获取bss段在ram中的结束位置 */
        PROVIDE(_ebss = .);
    } >RAM AT>FLASH

        新增三个段描述,用于init代码段的描述、说明.data段分别存放在flash和ram的哪个位置处以及添加.bss段的位置说明,以便提供给代码使用。

        因为在链接脚本中,每个段所处的位置都使用 > 和 AT> 符号来分别描述其处于内存地址与加载地址的某处;所以每个段都具备两个不同区域的地址,并且每个段的地址默认都是以自动递增的方式排列在一起。AT>所指的区域就是flash的存储区域。

        其中.bss段存放的是未初始化的全局变量与静态变量,所以其值应该全为0;并且因为.bss段的数据全为0,因此在编译生成镜像时,此段数据将不会保存到生成的镜像中,仅使用位置符号来表示此段数据,这样做的好处在于可以缩小镜像本身的大小。(所以衍生处理的问题就是在运行镜像时,需要代码手动清零.bss段)

1.2 新增.init代码段

        随着代码的增加,在进行编译链接时会出现编译器将汇编代码链接到了靠后的位置处;而启动时,mcu需要读取首地址,此时将出现跑飞的现象。

        所以需要增加init代码段,专用于存放boot代码,使得mcu上电运行后,第一句代码应该为boot汇编代码。如下所示:

	# 用于声明接下的代码存储在.init段中,此段为自定义段
    .section .init

	.global	_start
	
_start:
	# 加载data段的ram区域与flash区域的位置
	la	t0, _vma_data_start
	la	t1, _lma_data_start
	la	t2, _vma_data_end
	# 如果data段的大小>0,则继续执行,将falsh中的data段拷贝到ram中
	bgeu t0, t2, 2f

1.3 数据段搬运以及bss段清零

        完成链接脚本的修改后,就可以根据链接脚本提供的段信息来进行代码重定位了。首先需要做的是搬运.data段从flash到ram,随即在ram中将.bss段区域清零。

        代码如下图所示:

_start:
	# 加载data段的ram区域与flash区域的位置
	la	t0, _vma_data_start
	la	t1, _lma_data_start
	la	t2, _vma_data_end
	# 如果data段的大小>0,则继续执行,将falsh中的data段拷贝到ram中
	bgeu t0, t2, 2f

1:
	# 将flash中的data段数据拷贝到ram中
	lw t3, 0(t1)
	sw t3, 0(t0)
	addi t0, t0, 4
	addi t1, t1, 4
	# 当t0>=t2时,说明data段已经拷贝完成,可以跳出循环继续执行
	bltu t0, t2, 1b

2:
	# 对bss段进行清零
	la	t0, _sbss
	la	t1, _ebss
	# 如果bss段的大小>0,则继续执行,将ram中bss段区域内存进行清零
	bgeu t0, t1, 2f

1:
	# 将bss段内存清零
	sw x0, 0(t0)
	addi t0, t0, 4
	# 当t0>=t1时,说明bss段内存已经清零完毕,可以跳出循环继续执行
	bltu t0, t1, 1b

2:
	# 设置栈指针地址
	la	sp, _stack_end

	# 跳转到c代码执行
	j	start_kernel

        通过对ram与flash中的数据段与bss段定位,然后将其进行拷贝与清零操作。上述代码中的注释已经说明了汇编代码中是如何进行搬运与清零的,这里将不再进行赘述。

二、时钟初始化

        首先通过查看原理图,获取MCU的外部输入时钟是8MHz;然后在以此为基础进行倍频与分屏的设置。

        当系统上电复位时,系统采用的时钟源是MCU内部的8MHz时钟源,此时钟精度不够,所以在初始化时,一般会将系统时钟源设置为外部高速时钟源。时钟树框图如下所示:

2.1 默认SYSCLK配置

        其中SYSCLK为最后得到的系统时钟,在默认的情况下,它的产生路径由下图所示:

         时钟配置寄存器中,默认的时钟源为HSI,所以最后得到输出的SYSCLK频率为8MHz。

2.2 修改SYSCLK配置

        接下来将时钟源修改为精度更高并且更稳定的外部HSE时钟,并且将SYSCLK时钟频率配置为96MHz。其时钟配置框图如下所示:

        在PREDIV1SRC选择处选择原始HSE作为时钟输入,同时在PLLSRC处也选择HSE作为时钟输入,接着在PLLMULL处进行12倍频,得到PLLCLK的输出频率为8 * 12 = 96MHz,最后选择PLLCLK作为SYSCLK的时钟源,系统得到96MHz的时钟频率。

       实现代码如下所示:

#include "platform.h"
#include "types.h"
#include "clock.h"


/*********************************************************************
 * @fn      clock_hse_on
 *
 * @brief   使能HSE时钟,使得HSE有输出时钟进入MCU.
 *
 * @param   on - 0,关闭HSE时钟;1,打开HSE时钟.
 *
 * @return  none
 */
void clock_hse_on(int on)
{
    if(on)
    {
        // 开启HSE振荡器
        //RCC->CTLR |= RCC_HSE_Bypass;
        //RCC->CTLR &= ~RCC_HSE_Bypass;
        RCC->CTLR |= RCC_HSE_ON;
    }
    else
    {
        // 关闭HSE振荡器
        RCC->CTLR &= ~RCC_HSE_ON;
        RCC->CTLR &= ~RCC_HSE_Bypass;
    }

}

/*********************************************************************
 * @fn      clock_pll_on
 *
 * @brief   pll使能开关函数.
 *
 * @param   pll - 用于选择PLL、PLL2或PLL3.
 *          on - 0,关闭PLL;1,打开PLL.
 *
 * @return  none
 */
void clock_pll_on(int pll, int on)
{
    switch(pll)
    {
        case 0:
            if(on)
                RCC->CTLR |= 1 << PLL_ON_CTL;
            else
                RCC->CTLR &= ~(1 << PLL_ON_CTL);
            break;

        case 1:
            if(on)
                RCC->CTLR |= 1 << PLL2_ON_CTL;
            else
                RCC->CTLR &= ~(1 << PLL2_ON_CTL);
            break;

        case 2:
            if(on)
                RCC->CTLR |= 1 << PLL3_ON_CTL;
            else
                RCC->CTLR &= ~(1 << PLL3_ON_CTL);
            break;

        default :
            if(on)
                RCC->CTLR |= 1 << PLL_ON_CTL;
            else
                RCC->CTLR &= ~(1 << PLL_ON_CTL);
            return;
    }
}

/*********************************************************************
 * @fn      clock_pll_cfg
 *
 * @brief   设置PLLSRC的时钟源以及pll的倍频系数.
 *
 * @param   src - 时钟源选择:0,HSI;1,HSE.
 *          mul - 倍频系数,倍频的范围为3~18(且17除外).
 *
 * @return  none
 */
void clock_pll_cfg(int src, int mul)
{
    RCC->CFGR0 &= ~0x3f0000;

    if(src)
    {
        RCC->CFGR0 |= mul | (1 << PLL_SRC_CTL);
    }
    else
    {
        RCC->CFGR0 |= mul;
        RCC->CFGR0 &= ~(1 << PLL_SRC_CTL);
    }
}

/*********************************************************************
 * @fn      clock_prediv1_cfg
 *
 * @brief   设置prediv1的时钟源以及prediv1的分频系数.
 *
 * @param   src - 时钟源选择:0,HSE;1,PLL2.
 *          div - 分频系数,分频的范围为1~16.
 *
 * @return  none
 */
void clock_prediv1_cfg(int src, int div)
{
    RCC->CFGR2 &= ~((1 << PREDIV1_SRC_CTL) | 0x0f);

    if(src)
    {
        RCC->CFGR2 |= div | (1 << PREDIV1_SRC_CTL);
    }
    else
    {
        RCC->CFGR2 |= div;
        RCC->CFGR2 &= ~(1 << PREDIV1_SRC_CTL);
    }
}

/*********************************************************************
 * @fn      clock_src_cfg
 *
 * @brief   设置最后sysclk的时钟源选择.
 *
 * @param   src - 时钟源选择:0,HSI;1,HSI;2,PLL.
 *
 * @return  none
 */
void clock_src_cfg(int src)
{
    RCC->CFGR0 |= (src & 0x03);
}

/*********************************************************************
 * @fn      clock_hse_96Mhz
 *
 * @brief   设置sysclk时钟源为PLL,且PLL由HSE通过倍频后得到.
 *
 * @param   none.
 *
 * @return  none
 */
void clock_hse_96Mhz(void)
{
    uint32_t tmp = 0;

    // 关闭hse振荡器
    clock_hse_on(0);
    // 关闭PLL,为了设置PLL的分频系数
    clock_pll_on(0, 0);

    // 开启hse振荡器
    clock_hse_on(1);
    // 等待hse时钟稳定
    do
    {
        tmp = (RCC->CTLR >> RCC_HSE_RDY) & 0x01;
    } while (!tmp);

    /* HCLK = SYSCLK */
    RCC->CFGR0 |= (uint32_t)0x00;
    /* PCLK2 = HCLK */
    RCC->CFGR0 |= (uint32_t)0x00;
    /* PCLK1 = HCLK / 2 */
    RCC->CFGR0 |= (uint32_t)0x400;

    // 选择HSE作为PREDIV1的时钟源,并且将PREDIV1进行1分频
    clock_prediv1_cfg(0, RCC_PREDIV1_Div1);

    // 选择PREDIV1作为PLLSRC的时钟源,并且将PLL进行12倍频
    clock_pll_cfg(1, RCC_PLLMul_12_EXTEN);

    // 开启PLL
    clock_pll_on(0, 1);
    // 等待PLL稳定
    do
    {
        tmp = (RCC->CTLR >> PLL_ON_RDY) & 0x01;
    } while (!tmp);

    // PLL锁定成功后,切换sysclk时钟源为PLL
    clock_src_cfg(RCC_PLL_CFG);
    // 等待切换时钟源成功
    do
    {
        tmp = (RCC->CFGR0 >> 2) & 0x03;
    } while (tmp != 2);
}

        上述代码的作用就是根据框图中的红色路线进行了时钟设置,特别需要注意的是每当使能一个时钟源或者PLL时,最好等待其稳定状态ready后,再进行下一步操作。

        当系统时钟设置完毕后,后续就可以根据此时钟来初始化各个总线的时钟频率了;并且各总线下的外设也可以进行初始化设置了,所以下一章我们将进行串口设备的初始化。

  • 39
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: RISC-V架构手册是一本介绍RISC-V指令集体系结构的重要参考资料。RISC-V是一种开放的指令集架构,具有可扩展性、灵活性和高效性的特点。 首先,RISC-V架构手册详细介绍了RISC-V指令集中的所有指令,包括常用的算术指令、逻辑指令、存取指令、分支指令等。它不仅列出了每个指令的编码格式和操作码,还详细说明了每个指令的功能和使用方法。这样的信息对于开发RISC-V处理器的工程师来说至关重要。 其次,RISC-V架构手册还介绍了RISC-V处理器的基本结构和中断处理机制。它详细解释了数据通路、控制单元和存储器等组件的连接方式和工作原理。同时,手册还讨论了RISC-V处理器的中断处理流程,包括中断的触发条件、中断处理程序的执行和中断返回等。这对于编操作系统或者驱动程序的开发者来说非常有帮助。 此外,RISC-V架构手册还包含了关于扩展特性和可选模块的说明。它介绍了如何在RISC-V架构上扩展自定义指令或者功能,以及如何添加可选的模块,如浮点单元、虚拟内存管理等。这样的灵活性使得RISC-V架构非常适合于各种应用领域,从嵌入式系统到超级计算机。 总的来说,RISC-V架构手册是开发人员和研究人员掌握RISC-V指令集和架构设计的重要参考资料。它全面介绍了RISC-V指令集的各个方面,并提供了实现RISC-V处理器的指导。通过学习和理解这本手册,人们可以更好地应用和推广RISC-V架构,推动开源指令集的发展。 ### 回答2: RISC-V(精简指令集计算机-)架构手册是一份完整的指南,用于描述和解释RISC-V计算机架构的细节和规范。这个手册为开发者和研究人员提供了一个详细的参考,以了解和标准化他们在RISC-V处理器设计和实现方面的工作。 RISC-V架构手册包含多个章节和附录,涵盖了RISC-V指令集的不同方面。它首先介绍了RISC-V的设计原理和目标,比如简洁性、可扩展性和定制化能力。然后,手册详细说明了RISC-V指令集的不同指令格式和编码规则,包括指令解码过程和操作码的定义。 这个手册还涵盖了RISC-V的寄存器和寄存器文件,描述了它们的使用方法和特殊规则。此外,手册还提供了关于异常处理机制和中断处理机制的解释,以及RISC-V中的特权级别和特权模式的详细信息。 RISC-V架构手册还包含了有关内存管理单元(MMU)和虚拟内存系统的信息,说明了RISC-V支持的不同内存访问方式和存储体系结构的细节。此外,手册还提供了有关浮点运算和向量指令集的详细说明以及其使用方法。 除了这些主要内容外,RISC-V架构手册还提供了一些附录,包括指令集的变种和扩展,以及示例代码和编程实例。这些附录为开发者提供了实际应用和开发RISC-V处理器的指导支持。 总之,RISC-V架构手册是一个重要的参考资料,用于理解和使用RISC-V计算机架构。它的详细说明和规范为开发者提供了标准化和统一化的参考,以便设计、实现和优化RISC-V处理器。 ### 回答3: RISC-V架构手册是一本详细介绍RISC-V指令集架构的重要参考资料。RISC-V是一种新兴的开源指令集架构,其优势在于简洁、可扩展和高度灵活。RISC-V架构手册系统地介绍了RISC-V指令集的各种特性和用法。 首先,RISC-V架构手册提供了RISC-V指令集的全面介绍。它详细解释了RISC-V的指令编码方式、寄存器组织、内存管理机制等基本概念。通过学习手册,人们可以了解到RISC-V指令的格式和操作方式,从而能够编符合RISC-V架构的程序。 其次,RISC-V架构手册系统介绍了RISC-V的扩展指令集。RISC-V提供了一种模块化的设计理念,允许用户根据不同的应用需求选择使用不同的指令集扩展。手册详细介绍了RISC-V的各种扩展,如乘法/除法扩展、向量扩展等,并提供了使用这些扩展的示例和指导。 另外,RISC-V架构手册还介绍了RISC-V的异常处理和中断机制。这些机制对于系统安全和稳定运行非常重要。手册详细解释了异常和中断的分类、处理流程以及相关的指令和寄存器。通过学习手册,人们可以了解到如何在RISC-V架构中设计有效的异常处理和中断控制机制。 最后,RISC-V架构手册还介绍了RISC-V的特殊指令和特殊寄存器。这些特殊指令和寄存器常用于系统级编程和性能优化。手册提供了这些指令和寄存器的详细说明和使用方法,帮助人们充分发挥RISC-V架构的优势。 综上所述,RISC-V架构手册是学习和理解RISC-V指令集架构的重要工具。通过研读手册,人们可以掌握RISC-V的基本概念、指令格式和操作方式,进而能够灵活应用RISC-V的各种扩展和特殊功能,为不同的应用场景设计高效的RISC-V架构系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值