AB32VG1超频,编译器优化设置

1.起因

之前修改了Helix解码库的底层强行使用C语言实现了底层,使得Helix解码库可以在任何处理器上运行。详情请见:Helix MP3解码库脱离汇编指令束缚,运行在任何处理器上的解决方案_Fairchild_1947的博客-CSDN博客

但是根据理论计算可知,解码44.1KHZ,320Kpbs的音频文件需要性能154.05DMIPS(具体计算请见上文),而AB32VG1的额定工作频率为120MHZ,实际使用时卡顿明显,因此欲通过编译器优化和超频使得该功能可以在AB32VG1单片机上正常使用。

2.编译器优化

编译器优化是非常稳妥的提高程序运行速度的方式,其设置如下图:

第1步:右键工程,并选择属性

第2步:选择C/C++构建

第3步:设置

第4步:点击Optimization

第5步:默认是不优化,从下拉单里面选择想要优化的等级即可,此处选择了最高3级优化

 3.编译器优化效果

不知道为什么RISC-V的编译器优化选择后编译后二进制文件大小不变,而且AB32VG1的下载软件带有一个检查两次下载镜像区别的功能,仅仅更新不同的地方,而调整了优化等级后,下载软件直接提示没有更新。

可能是我不了解RISC-V编译器,设置有误,请各位大佬指正。

 4.超频

原本以为这个想法很疯狂,没想到查看RT-Thread工程附赠的README.md时竟然看到了这样的一幕——

没想到官方已经抢先整活儿了,官方宣称可以超频到192MHZ,正好这也挑起了我的兴趣,我想试试AB32VG1的极限正常工作频率。 毕竟此前我把STM32F429一个额定180MHZ主频的单片机超频到了480MHZ与H7频率相同^*_^*

超频正式开始,RT-Thread会把时钟设置代码都放到board.c中,所以锁定这个文件找到对应的代码就可以改频率了。

4.1额定工作频率内的调整

额定工作频率内,即120MHZ以内调整可以用此方法,对了有什么意义呢?因为不知为何,新生成的工程都会默认48MHZ的工作频率,所以遇到AB32VG1性能不够的时候,有可能是因为工作的主频只有48MHZ,此函数代码如下:

void rt_hw_systick_init(void)
{
    CLKCON2 &= 0x00ffffff;
    CLKCON2 |= (25 << 24);                                  //配置x26m_div_clk = 1M (timer, ir, fmam ...用到)
    CLKCON0 &= ~(7 << 23);
    CLKCON0 |= BIT(24);                                     //tmr_inc select x26m_div_clk = 1M

    set_sysclk(SYSCLK_48M);    //该这一句就可在额定频率范围内调整频率

    /* Setting software interrupt */
    set_cpu_irq_comm(cpu_irq_comm);
    rt_hw_interrupt_install(IRQ_SW_VECTOR, rt_soft_isr, RT_NULL, "sw_irq");

    timer0_init();
    hal_set_tick_hook(timer0_cfg);
    hal_set_ticks(get_sysclk_nhz() / RT_TICK_PER_SECOND);

    PICCON |= 0x10002;
}

改变set_sysclk的形参即可在额定频率范围内调整,注意,此函数的参数请从枚举中选择

4.2额定工作频率以上超频

超出额定工作频率后,将没有现成简洁的方法来进行超频,必须阅读倍频器设置代码,并进行手动修改。由于AB32VG1的芯片资料太少,内部时钟树的结构并没有详细文档介绍,那么就跟着例程的代码瞎摸索吧。锁相环设置的函数在文件system_ab32vg1.c中,函数如下:

void set_sysclk(uint32_t sys_clk)
{
    uint32_t uart_baud, spll_div = 0, spi_baud = 0, spi1baud;
    uint8_t cnt_1us, clk_sel;

    clk_sel = get_clksel_val(sys_clk);
    if(sys.clk_sel == clk_sel) {
        return;
    }
//    if (sys_clk > SYSCLK_48M) {
//        PWRCON0 = (PWRCON0 & ~0xf) | (sys_trim.vddcore + 1);            //VDDCORE加一档
//    }
//    vddcore_other_offset();

//    printf("%s: %d, %d\n", __func__, sys_clk, clk_sel);
    switch (sys_clk) {
    case SYSCLK_12M:
        spll_div = 19;                   //pll0 240M
        cnt_1us = 1;
        spi_baud = 0;
        spi1baud = 0;
        break;

    case SYSCLK_24M:
        spll_div = 9;                   //pll0 240M
        cnt_1us = 2;
        spi_baud = 0;
        spi1baud = 1;
        break;

    case SYSCLK_30M:
        spll_div = 7;                   //pll0 240M
        cnt_1us = 3;
        spi_baud = 1;                   //Baud Rate =Fsys clock / (SPI_BAUD+1)
        spi1baud = 1;
        break;

    case SYSCLK_48M:
        spll_div = 4;                   //pll0 240M
        cnt_1us = 4;
        spi_baud = 1;                   //Baud Rate =Fsys clock / (SPI_BAUD+1)
        spi1baud = 3;
        break;

    case SYSCLK_60M:
        spll_div = 3;                   //pll0 240M
        cnt_1us = 5;
        spi_baud = 2;                   //Baud Rate =Fsys clock / (SPI_BAUD+1)
        spi1baud = 3;
        break;

    case SYSCLK_80M:
        spll_div = 2;                   //pll0 240M
        cnt_1us = 7;
        spi_baud = 3;                   //Baud Rate =Fsys clock / (SPI_BAUD+1)
        spi1baud = 4;
        break;

    case SYSCLK_120M:
        spll_div = 0;                   //pll0 240M
        cnt_1us = 10;
        spi_baud = 4;                   //Baud Rate =Fsys clock / (SPI_BAUD+1)     //spiclk 120/5 = 24M
        spi1baud = 9;
        break;

    case SYSCLK_26M:
        spll_div = 0;
        cnt_1us = 3;
        spi_baud = 1;
        spi1baud = 1;
        break;

    case SYSCLK_13M:
        spll_div = 1;
        cnt_1us = 1;
        spi_baud = 0;
        spi1baud = 0;
        break;

    case SYSCLK_2M:
        spll_div = 1;
        cnt_1us = 1;
        spi_baud = 0;
        spi1baud = 0;
        break;

    default:
        return;
    }

    //先判断PLL0是否打开
    if(clk_sel <= PLL0DIV_120M) {
        if (!(PLL0CON & BIT(12))) {
            PLL0CON &= ~(BIT(3) | BIT(4) | BIT(5));
            PLL0CON |= BIT(3);                     //Select PLL/VCO frequency band (PLL大于206M vcos = 0x01, 否则为0)
            PLL0CON |= BIT(12);                    //enable pll0 ldo
            delay_us(100);                         //delay 100us
            PLL0DIV = 240 * 65536 / 26;            //pll0: 240M, XOSC: 26M
            PLL0CON |= BIT(20);                    //update pll0div to pll0_clk
            PLL0CON |= BIT(6);                     //enable analog pll0
            PLL0CON |= BIT(18);                    //pll0 sdm enable
            delay_us(1000);                        //wait pll0 stable
        }
    }

    sys.cnt_1us = cnt_1us;
    sys.sys_clk = sys_clk;
    sys.clk_sel = clk_sel;
    uart_baud =  (((get_sysclk_nhz() + (sys.uart0baud / 2)) / sys.uart0baud) - 1);

    set_sysclk_do(sys_clk, clk_sel,spll_div, spi_baud, spi1baud);
    set_peripherals_clkdiv();
    update_sd0baud();       //更新下SD0BAUD
    update_uart0baud_in_sysclk(uart_baud);
}

可以看到,这个函数用switch将不同频率选择分开设置参数,最后设置锁相环。而在参数设置时,有两个SPI开头的,这两个没有作用是SPI相关的,只有spll_div和cnt_1us两个参数是需要调整的,其中cnt_1us是设置系统时基的,如果超频了,但是这个值没有修改,那么将导致系统的延时功能、计时功能等的时间长度变化,在对绝对时间无要求的场合这个参数也可以不改。spll_div是倍频器倍频后的分频器。

这些参数会在最后调用set_sysclk_do函数真正发挥作用,set_sysclk_do函数内如如下:

static void set_sysclk_do(uint32_t sys_clk, uint32_t clk_sel, uint32_t spll_div, uint32_t spi_baud, uint32_t spi1baud)
{
    uint32_t cpu_ie;
    cpu_ie = PICCON & BIT(0);
    PICCONCLR = BIT(0);                             //关中断,切换系统时钟
    set_peripherals_clkdiv_safety();

    CLKCON0 &= ~(BIT(2) | BIT(3));                  //sysclk sel rc2m
    CLKCON2 &= ~(0x1f << 8);                        //reset spll div

    if(clk_sel <= PLL0DIV_120M) {
        //sys_clk来源PLL0的分频配置
        CLKCON0 &= ~(BIT(4) | BIT(5) | BIT(6));     //sys_pll select pll0out
        if (PLL0DIV != (240 * 65536 / 26)) {
            PLL0DIV = 230 * 65536 / 26;             //pll: 240M, XOSC: 26M
            PLL0CON &= ~(BIT(3) | BIT(4) | BIT(5));
            PLL0CON |= BIT(3);                      //Select PLL/VCO frequency band (PLL大于206M vcos = 0x01, 否则为0)
            PLL0CON |= BIT(20);                     //update pll0div to pll0_clk
            CLKCON3 &= ~(7 << 16);
            CLKCON3 |= (4 << 16);                   //USB CLK 48M
        }
    } else if (clk_sel <= OSCDIV_26M) {
        //sys_clk来源于XOSC26M时钟分频, 无USB时关闭PLL0
//        if (!is_usb_support()) {
//            PLL0CON &= ~BIT(18);
//            PLL0CON &= ~(BIT(12) | BIT(6));         //close pll0
//        }

        CLKCON0 &= ~(BIT(4) | BIT(5) | BIT(6));
        CLKCON0 |= BIT(6);                          //spll select xosc26m_clk
    }

    CLKCON2 |= (spll_div << 8);
    CLKCON0 |= BIT(3);                          //sysclk sel spll
    SPI0BAUD = spi_baud;
    if (CLKGAT1 & BIT(12)) {
        SPI1BAUD = spi1baud;
    }
//    if (spiflash_speed_up_en()) {
//        set_flash_safety(sys_clk);
//    }
    PICCON |= cpu_ie;
}

其中真真发挥作用的是给PLL0CON、PLL0DIV以及CLKCON0、CLKCON1等寄存器写入的语句。由于AB32VG1配套的文档没有对时钟系统的介绍,故这里用STM23的倍频器图代替理解。

倍频器可以选择从外部晶振和内部RC振荡器输入,输入后先预分频即“/M”再倍频即“*N”最后再分频即“/P”输入到后续的内核及外设。输入后,内核和不同的外设又拥有自己的分频器,再次分配,比如APB总线上的分频器,AHB总线上的分频器等。

接下来类比set_sysclk_do函数中的寄存器进行理解,PLL0CON就是Main PLL的“*N”,PLL0DIV就是Main PLL的“/M”,而CLKCON0、CLKCON1等好比APB、AHB总线上的分频器,其中通过观察发现CLKCON2是处理器的分频器,因为CLKCON2的设置参数来源于spll_div而此参数正是处理器的分频系数。

观察代码可发现,PLL0CON虽然是倍频设置,但是修改难度比较大不直观,而“PLL0DIV = 230 * 65536 / 26;             //pll: 240M, XOSC: 26M”这句不仅意思一目了然而且给了注释,那么就从这句下手改。

这句默认情况打开后参数是 240 * 65536 / 26,意思是,倍频器输出240MHZ前提是使用26MHZ的晶振,此时设置处理器分频为1(即2分频)即可得到120MHZ的额定主频。

超频时,笔者选择了将处理器分频改为0(不分频)然后不断修改PLL0DIV参数来进行超频。因为这种做法可以保证处理器之外的外设工作在额定频率以内,减少超频失败的因素。

实测发现,AB32VG1可以超频到比说明手册192MHZ更高的230MHZ稳定运行,当然再往上就不行了。

至此成功超频到230MHZ。

3.后续工作

超频后,由于AB32VG1默认是配有RT-Thread操作系统的,并不是裸机,所以需要重新设置系统定时器,以避免系统时间片过短。AB32VG1的系统定时器是timer0,设置的代码在board.c的void rt_hw_systick_init(void)函数,前文提到过,在这个函数中hal_set_ticks(get_sysclk_nhz()/RT_TICK_PER_SECOND);用来设置系统定时器的计数寄存器,这个将决定它的中断频率继而决定系统时间片长度。一路追溯下去发现获取参数的函数如下:

uint32_t get_sysclk_nhz(void)
{
    return sysclk_index[sys.sys_clk] * 1000000;
}

该函数直接从数组sysclk_index中的对应位置取值并乘以1M返回。而对应位置的参数sys.sys_clk正是在board.c的void rt_hw_systick_init(void)函数中调用set_sysclk(SYSCLK_120M);时写入的枚举变量SYSCLK_120M,因此我们可以在以此枚举变量为偏移量对应的sysclk_index数组那里修改为当前的实际工作频率。该数组定义如下:

const uint8_t sysclk_index[] = {
    2,
    12,
    13,
    24,
    26,
    30,
    48,
    60,
    80,
    120,
};

这里把数组最后的那个120改成230即可。

4.测试结果

超频230MHZ已经是很高的主频了。STM32F7系列也不过是216MHZ的主频。本以为可以秒天地的时候,不料再次尝试MP3功能时依旧有些许卡顿。而同样的算法,在STM32F429上180MHZ就可以非常流畅的运行。当然RISC-V编译器的优化也是拖后腿的一个很大原因,期待RISC-V编译器优化,相信优化后是可以性能是改天换地的!

猜你喜欢

转载自blog.csdn.net/Fairchild_1947/article/details/123159343