#### 名词解释

##### PLL

锁相环(Phase Locked Loop)，使电路上的时钟和某一外部时钟的相位同步。PLL的主要用途是作为频率合成器，产生更宽范围的频率输出。也可以用作抖动滤波器，或补充时钟网络的延迟。锁相环通过由PD(Phase Detector)、LF(Loop Filter)和VCO(Voltage Controlled Oscillator)三部分组成。

##### APB

一个总线协议，几乎成为一种标准的片上总线结构。主要用于低带宽的外设连接。

#### 系统控制器的寄存器

系统控制器是个外设，它负责所有与系统相关的外设的设置。它的32个32位寄存器都映射在内存里。

| 编号  | 名称          | 描述                                                         |
| ----- | ------------- | ------------------------------------------------------------ |
| 0     | git_id        | Git短提交id。只读寄存器，git的commit id共160位，而它只能提供前32位，所以叫"short commit id"。 |
| 1     | clk_freq      | 系统时钟频率。只读寄存器。                                   |
| 2     | pll0          | PLL0控制器。可写，其中clkr0,clkf0,clkod0用于计算输出频率，其中clkr0,clkf0,clkod0,bwadj0用于控制输入频率，其中pll_bypass0设置是否bypass pll0(1:bypass, 0:do not bypass)，pll_out_en0位控制pll0的时钟使能，pll_pwrd0控制pll0的启动(0:power off, 1:power on)，pll_reset0用于重置pll0(0:不重置，1:重置) |
| 3     | pll1          | PLL1控制器。可写，其中clkr1,clkf1,clkod1用于计算输出频率，其中clkr1,clkf1,clkod1,bwadj1用于控制输入频率，其中其中pll_bypass1设置是否bypass pll1(1:bypass, 0:do not bypass)，pll_out_en1位控制pll1的时钟使能，pll_pwrd1控制pll1的启动(0:power off, 1:power on)，pll_reset1用于重置pll1(0:不重置，1:重置) |
| 4     | pll2          | PLL2控制器。可写，其中clkr2,clkf2,clkod2用于计算输出频率，其中clkr2,clkf2,clkod2,bwadj2用于控制输入频率，其中其中pll_bypass2设置是否bypass pll2(1:bypass, 0:do not bypass)，pll_out_en2位控制pll2的时钟使能，其中pll_ckin_sel2将pll2设为时钟源，pll_pwrd2控制pll2的启动(0:power off, 1:power on)，pll_reset2用于重置pll2(0:不重置，1:重置) |
| 5     | resv5         | 保留                                                         |
| 6     | pll_lock      | PLL锁测试器。其中pll_lock0~2均只读，pll_lock0=3则pll0已锁，pll_lock1=1则pll1已锁，pll_lock2=1则pll2已锁。其中pll_lip_clear0~2用于清空对应pll的slip，置1则清空对应pll的slip。 |
| 7     | rom_error     | AXI ROM测试器                                                |
| 8     | clk_sel0      | 时钟选择控制器0。可写，其中aclk_sel设置aclk为时钟源(0:IN0, 1:ACLK)；其中aclk_divider_sel占用2位，控制aclk的分频值；其中apb0~2_clk_sel分别占用3位，分别控制apb0~2的分频值；其中spi3_clk_sel设置spi3为时钟源；其中timer0~2_clk_sel分别设置timer0~2为时钟源；其它为保留位。 |
| 9     | clk_sel1      | 时钟选择控制器1。可写，仅第一位有效，spi3_sample_clk_sel将spi3_sample设为时钟源。 |
| 10    | clk_en_cent   | 中心时钟使能。可写，每一位控制一个时钟使能，共用6个位控制cpu, sram0~1, apb0~2，其余位保留。其中apb0~2分别控制着若干外设。置1使能，置0不使能。 |
| 11    | clk_en_peri   | 外部时钟使能。可写，每一位控制一个外设的时钟全能。置1使能，置0不使能。 |
| 12    | soft_reset    | 软重置。可写，仅第一位有效。置1重置，置0不重置。             |
| 13    | peri-reset    | 外重置。可写，每一位控制一个外设。置1重置，置0不重置。       |
| 14    | clk_th0       | 时钟分频值控制器0。可写，控制分频值宽度为4的外设的分频值，控制的外设有5个：sram0~1, ai, dvp, rom |
| 15    | clk_th1       | 时钟分频值控制器1。可写，控制分频值宽度为8的外设的分频值，控制的外设有4个：spi0~3 |
| 16    | clk_th2       | 时钟分频值控制器2。可写，控制分频值宽度为8的外设的分频值，控制的外设有3个：timer0~2，剩余8位为保留位 |
| 17    | clk_th3       | 时钟分频值控制器3。可写，控制分频值宽度为16的外设的分频值，控制的外设有2个：i2s0~1_clk_threshold |
| 18    | clk_th4       | 时钟分频值控制器4。可写，控制2个分频值宽度为8的外设的分频值：i2s0~1_mclk_threshold，还控制1个分频值宽度为16的外设的分频值：i2s2_clk_threshold |
| 19    | clk_th5       | 时钟分频值控制器5。可写，控制分频值宽度为8的外设的分频值，控制的外设有4个：i2s2_mclk_threshold, i2c0~2_clk_threshold |
| 20    | clk_th6       | 时钟分频值控制器6。可写，控制分频值宽度为8的外设的分频值，控制的外设有2个：wdt0~1_clk_threshold, 剩余16位为保留位 |
| 21    | misc          | 杂项控制器。有1个1位的sip_dvp_data_enable，1为使能，0为不使能 |
| 22    | peri          | 外设控制器                                                   |
| 23    | spi_sleep     | SPI睡眠控制器                                                |
| 24    | reset_status  | reset源的状态。wdt0~1_reset_sts和soft_reset_sts代表其各自的reset状态，1为reset，0为非reset，reset_sts_clr如写入1则清空reset状态。其余为保留位。 |
| 25    | dma_sel0      | DMA握手控制器0。可写，控制dma_sel0~4共5个6位宽的channel，其余2位保留 |
| 26    | dma_sel1      | DMA握手控制器1。可写，控制dma_sel5这个6位宽的channel，其余26位保留 |
| 27    | power_sel     | IO电源模式选择控制器。可写，power_mode_sel0~7共8个1位的项控制8种电源模式，1为使能0为不使能，其余24位保留 |
| 28-31 | resv28-resv31 | 保留                                                         |

#### 系统控制器的功能

##### 外设时钟的使能或取消使能

`sysctl_clock_[enable | disable]`

共有三条总线(apb0, apb1, apb2)，所有外设都分配在这三条总线上。在**clk_en_cent**寄存器上有3位分别控制这三条总线时钟的使能或取消使能。

共有三种类型的外设。一种外设是PLL，它们的时钟使能由各自的寄存器控制(**pll0**, **pll1**, **pll2**)；一种连在内部总线上，它们是CPU、内存和外部总线，它们的时钟使能由寄存器**clk_en_cent**控制；其它外设连在外部总线上，它们的时钟使能由**clk_en_peri**控制。

对于PLL或连在内部总线上的设备，只要该外设对应的时钟使能位置1，即可使能该设备；对于连接在外部总线apb上的设备，则需要先使能该外设所在的apb总线的时钟，再使能该外设的时钟。

设备要取消使能，只要将该设备的使能位置0即可。

##### 对应时钟分频值的设置与获取

`sysctl_clock_[set | get]_threshold`

不同的外设其分频值位宽是不一样的，共有2、3、4、8、16五种情况。其中aclk_divider(2位)、apb0~2(各3位)由寄存器**clk_sel0**控制。其中sram0~1、ai、dvp、rom的分频值均为4位，由寄存器**clk_th0**控制。其中spi0~3的分频值为8位，由寄存器**clk_th1**控制。其中timer0~2的分频值为8位，由寄存器**clk_th2**控制。其中i2s0~1的分频值为16位，由寄存器**clk_th3**控制。其中i2s2的分频值为16位，i2s0~1_mclk的分频值为8位，由寄存器**clk_th4**控制。其中i2s2_mclk，i2c0~2的分频值为8位，由寄存器**clk_th5**控制。其中wdt0~1的分频值为8位，由寄存器**clk_th6**控制。

设置外设的时钟分频值就是向相应的位写入数据，获取外设的时钟分频值就是从相应的位读取数据。

##### 时钟对应时钟源的设置与获取

`sysctl_clock_[set | get]_clock_select`

| 时钟                                 | 时钟源  | 意义                                                     |
| ------------------------------------ | ------- | -------------------------------------------------------- |
| pll0~2->pll_bypass0~2                | 0, 1    | 1，绕过对应pll；0，不绕过对应pll                         |
| pll2->pll_ckin_sel2                  | 0, 1, 2 | 0，IN0；1，PLL0；2，PLL1                                 |
| clk_sel0寄存器的aclk, spi3, timer0~2 | 0, 1    | 1，设置对应设备为时钟源；0，不设置对应设备为时钟源       |
| clk_sel1->spi3_sample_clk_sel        | 0, 1    | 1，设置spi3_sample为时钟源；0，不设置spi3_sample为时钟源 |

  时钟源的设置就是为如上寄存器的相关位指定时钟源。

  时钟源的获取就是从如上寄存器中的相应位获取对应的值。

##### 获取PLL的频率

`sysctl_pll_get_freq`

  ```
  FOUT = FIN / NR * NF /OD	# 输出频率 = 输入频率 / R分频器 * N计数器 / od
  	# nr = pll.clkr + 1
  	# nf = pll.clkf + 1
  	# od = pll.clkod + 1
  	# FIN : 如PLL0~1则为26000000UL；如PLL2则从pll2.pll_ckin_sel2获取对应时钟源(可能是SOURCE_IN0, SOURCE_PLL0, SOURCE_PLL1)，如SOURCE_IN0则为26000000UL,如SOURCE_PLL0~1则按上述公式再算一遍。
  ```

##### 获取各种设备的基本时钟频率

`sysctl_clock_get_freq`

| clock                                                    | divider | 频率                                                         |
| -------------------------------------------------------- | ------- | ------------------------------------------------------------ |
| **IN0**                                                  |         | 源频率是26000000UL，结果频率与源频率相等                     |
| directly under **PLL**时钟域(PLL0~2)                     | gated   | 源频率是对应PLL的频率，结果频率与源频率相等                  |
| directly under **ACLK**时钟域(CPU, DMA, FFT, ACLK, HCLK) |         | 从寄存器clk_sel0的aclk位取对应的值，如为0表示没有选择aclk，则源频率为IN0的频率；如为1表示选择了aclk，则源频率的算法为(PLL0的频率)/(2ULL << ACLK的时钟分频值)。结果频率与源频率相等 |
| under **ACLK**时钟域(SRAM0~1, ROM, DVP)                  | gated   | 源频率是ACLK的时钟频率，结果频率的算法为(源频率)/(设备对应的分频值 + 1) |
| under **ACLK**时钟域(ABP0~2)                             | even    | 源频率是ACLK的时钟频率，结果频率的算法为(源频率)/(设备对应的分频值 + 1) |
| under **AI**时钟域                                       | gated   | 源频率是PLL1的时钟频率，结果频率的算法为(源频率)/(AI的分频值 + 1) |
| under **I2S**时钟域(I2S0~2)                              | even    | 源频率是PLL2的时钟频率，结果频率的算法为(源频率)/(对应设备的分频值 + 1)/2 |
| under **WDT**时钟域(WDT0~1)                              | even    | 源频率是IN0的时钟频率，结果频率的算法为(源频率)/(对应设备的分频值 + 1)/2 |
| under **PLL0**时钟域(SPI0~2, I2C0~2)                     | even    | 源频率是PLL0的时钟频率，结果频率的算法为(源频率)/(对应设备的分频值 + 1)/2 |
| under **PLL0_SEL**时钟域(SPI3, TIMER0~2)                 | even    | 从寄存器clk_sel0相应的位获取对应的值，如为0表示没有选择对应设备的时钟，则源频率为IN0的频率；如为1表示选择了对应设备的时钟，其源频率为PLL0的频率。对于SPI3，结果频率的算法是(源频率)/(SPI3的分频值 + 1)/2；对于TIMER0~2，结果频率的算法是(源频率)/(TIMER2的分频值 + 1)/2 |
| under **MISC**时钟域                                     | even    | 无代码，仅注释，目前无任何意义                               |
| under **APB0**时钟域(GPIO, UART1~3, FPIOA, SHA)          | even    | 源频率为APB0的时钟频率，结果频率与源频率相等                 |
| under **APB1**时钟域(AES, OTP, RTC)                      | even    | 如为AES或OTP，则源频率是APB1的时钟频率；如为RTC，则源频率是IN0的时钟频率。结果频率与源频率相等 |
| under **APB2**时钟域                                     | even    | Do nothing，即源频率和结果频率均为0                          |

##### 设备重置与获取重置状态

`sysctl_reset`, `sysctl_get_reset_status`

  重置的过程就是将寄存器**soft_rest**或**peri_rest**中对应的位置1再置0。

  获取重置状态就是从寄存器**reset_status**中依次读取相应的位，如某一位为1，则表明该位对应的外设被重置了。可以获取重置状态的外设有wdt1, wdt2, soft。最后，应该是通过给reset_sts_clr位置1来清空外设的重置状态。

##### PLL的使能与禁用

`sysctl_pll_[enable | disable]`

  寄存器**pll0~2**分别控制相应PLL的使能与禁用。

  使能的过程：

  ```
  sysctl->pll.pll_bypass = 0		// 不绕过pll
  sysctl->pll.pll_pwd = 1			// 开启pll
  
  // 重置pll
  sysctl->pll.pll_reset = 0
  sysctl->pll.pll_reset = 1
  asm volatile("nop; nop");
  sysctl->pll.pll_reset = 0
  ```

  禁用的过程：

  ```
  sysctl->pll.pll_bypass = 1		// 绕过pll
  sysctl->pll.pll_pwrd = 0		// 关闭pll
  ```

##### 选择外设DMA通道握手信号

`sysctl_dma_select`

  DMA一共有6个通道，每个通道占6位。前5个通道在寄存器**dma_sel0**，第6个通道在寄存器**dma_sel1**。每个通道可选择的外设有SSI, I2C, UART, AES, SHA, AI, FFT, I2S。

##### 设置spi0和dvp数据

`sysctl_set_spi0_dvp_data`

  寄存器**misc**的spi_dvp_data_enable位置1则使能，置0则不使能。

##### 设置FPIOA电源模式

`sysctl_set_power_mode`

  电源模式共有两种：3.3V和1.8V。寄存器**power_sel**共可选择8种power bank(即IO电源域编号)，如电源模式选择1.8V则相应的power bank位置1，如电源模式选择3.3V则相应的power bank位置0。

##### CPU频率的获取与设置

`sysctl_cpu_[get | set]_freq`

  获取CPU频率固定为390000000.

  设置CPU的频率实际上就是设置PLL0的频率，以IN0为源频率，以`(clk_sel0->aclk_divider_sel + 1) * 2 * freq`为结果频率，计算出对应的pll寄存器相关位的值并进行设置。

##### 设置PLL的频率

`sysctl_pll_set_freq`

  1. 如为PLL0，则设置寄存器clk_sel0的aclk_sel位为0，目的是为了让CPU时钟变为XTAL；
  2. 设置对应pll寄存器的pll_out_en位为0，目的是使对应pll输出失效；
  3. 设置对应pll寄存器的pll_pwrd位为0，目的是关闭对应的pll；
  4. 设置pll的新值，如为pll2则以pll2->pll_ckin_sel为源计算结果频率，如为其它pll则以IN0为源计算结果频率；
  5. 设置对应pll寄存器的pll_pwrd位为1，目的是开启对应的pll；
  6. 设置对应pll寄存器的pll_reset位为1，目的是重置对应的pll；
  7. 设置寄存器pll_lock里与pll对应的pll_slip_clear位为1，清空对应pll的slip；
  8. 设置对应pll寄存器的pll_out_en位为1，目的是开启对应pll的输出；
  9. 如为PLL0，则设置寄存器clk_sel0的aclk_sel位为1，目的是为了让CPU时钟变为PLL；
  10. 最终return第4步算出的结果频率。

##### 中断的使能与取消使能

`sysctl_[enable | disable]_irq`

  通过设置**mie**和**mstatus**相应的位实现。

##### 获取从启动到现在所用的时间

`sysctl_get_time_us`

  从**mcycle**获取CPU周期数，从`sysctl_clock_get_freq()`获取CPU频率，两者相除即为启动到现在所过的时间。

#### 待解决的问题

- 不知道PLL的具体作用。

- 不知道时钟的threshold的具体作用。

  threshold在kendryte的编程指南里翻译为分频值，似乎不是词典里翻译的阈值的意思。

- 外设重置可以理解为重置外设的寄存器，那么软重置是什么意思呢？

- 为什么重置状态寄存器只记录wdt0~1和soft的重置呢？其它外设为什么不记录呢？