单片机
返回首页

STC8A,STC8G,STC8H系列的IRC内部振荡源频率调节

2022-06-09 来源:eefocus

从STC10/STC11系列开始出现内置RC震荡源(内置时脉, 宏晶称之为IRC). 最初的三代STC10/STC11/STC12, 在手册上的介绍都是'由于使用内部时钟源误差较大, … 有串行通信的情况下不建议使用内部R/C时钟源'. 到STC15这一代之后, 精度提升明显, 开始出现了默认不使用外置晶振的MCU型号. 从STC8开始对IRC进行了更多的改进, 从STC8A/8F的一个频段, 到STC8G/STC8H的两个频段, 到STC8A8K64D4的4个频段, 从CODE预置, 到XDATA只读预置, 可用性也在不断提升.


这里说一下STC8系列的IRC设置.


STC8A/STC8F的内部时钟机制

只有一个IRC频段, 频率范围在16-27MHz, 通过两个寄存器调节内部时脉RTRIM, LIRTIM, 通过 CLKDIV 分频后作为系统时钟SYSCLK


细节

STC-ISP写入时对时钟校准, 除了会改IRTRIM的预设值, 还可能会修改CLKDIV(时钟分频系数)的预设值

STC-ISP中预设的频率, 18MHz-27MHz是不进行分频的(CLKDIV为0), 再往下更小的频率就会开始改CLKDIV了

代码中使用预设值对IRTRIM赋值, 加上设置CLKDIV=0, 可以将时钟切换到这两个频率

因为环境和老化等因素, 出厂时标定的值产生的频率会产生漂移, 如果重新标定可能会在这个值的±2附近, 预设值用作于UART之类的通信是足够了

芯片上内置出厂时标定的22.1184MHz和24MHz频率对应的IRTRIM值, 可以从FLASH和RAM中读取, 但是这仅限于使用STC-ISP烧录并且勾选了对应选项的情况

FLASH部分的预设值: 22.1184MHz和24MHz地址分别是0xFDF4和0xFDF3, 这些地址位于code, 通过*(char code *)0xfdf3这样的形式访问

在STC-ISP每次写入时(注意, 是每次), 必须勾选'在程序区结束处添加重要测试参数'才会往这部分地址写入, 否则这部分地址的值全为0xFF

RAM部分的预设值: 22.1184MHz和24MHz地址分别是0xFA和0xFB, 这些地址位于idata, 通过*(unsigned char idata *)0xFB这样的形式访问

这两个值与其它因素无关, 上电即可读取, 相对于FLASH这个预设值更加可靠, 但是仅限于通过STC-ISP刷写的程序, 通过stcgal写入的程序, 这两个地址的数值为0

Linux环境下对IRTRIM, LIRTIM的标定

如果在Linux下开发, 不能使用STC-ISP, 则上述的方式都无效, 要通过辅助手段标定


方式一: 通过程序给ITRIM和L遍历整个[0,255]区间确定中心点得到粗略的IRTRIM值

方式二: 上设备, 示波器或者逻辑分析仪都行

实现思路


预设系统时钟为22.1184MHz, UART波特率为115200

代码中根据上面的条件, 初始化串口和定时器

按IRTRIM从[0, 255], LIRTIM从[0,3]循环, UART输出一个固定的字符串

当系统实际时钟接近 22.1184MHz时, 接收端才能看到正确的字符, 其它时候看到的都是乱码

取接收正常的部分的中心点, 用作22.1184MHz的IRTRIM和LIRTRIM值

如果用示波器或者逻辑分析仪, 可以做时钟10分频或20分频的输出, 在过程中观察波形宽度, 记下输出最接近的宽度时的IRTRIM和LIRTRIM.

STC8G 时钟

STC8G的内置振荡源频段有两个: 20MHz和33MHz, 可以从20MHz覆盖到36.864MHz,


因为存在多个频段, 多了两个寄存器用于频段的切换


IRCBAND: 用于选择频段

VRTRIM: 用于设置对应频段的电压

在设置频率时, 会涉及到这4个寄存器 IRCBAND, VRTRIM, IRTRIM, LIRTRIM, 都会对频率有影响, 调节程度从左到右递减


一个例子


ADDR: 0x1FE9

       VER1     VER2

VRTRIM 35M      40M:      20

VRTRIM 20M      24M:      1F

ITRIM  ---      48M:      FF

ITRIM  ---      44.2368M: D0

ITRIM 36.864M   40M:      A3

ITRIM 35M       36.864M:  88

ITRIM 33.1776M  35M:      6F

ITRIM 30M       33.1776M: 43

ITRIM 27M       30M:      1A

ITRIM 20M       27M:      63

ITRIM 24M       24M:      BA

ITRIM 22.1184M  22.1184M: 90

32kHz PD FreQ:            8DCC

1.19Vref:                 04A9


MCUID: F7 A4 C4 0D 11 E0 EE 

Current VRTRIM:20, IRTRIM:A3, LIRTRIM:03


STC8H 时钟

STC8H要区分两个不同的系列


STC8H1K

内置振荡源频段有两个, 20MHz和35MHz, STC-ISP预设的数值, 右边一列是芯片实测数据


ADDR: 0x1FE9

       VER1     VER2

VRTRIM 35M      40M:      1F

VRTRIM 20M      24M:      1E

ITRIM  ---      48M:      FF

ITRIM  ---      44.2368M: E4

ITRIM 36.864M   40M:      B5

ITRIM 35M       36.864M:  9A

ITRIM 33.1776M  35M:      7E

ITRIM 30M       33.1776M: 51

ITRIM 27M       30M:      26

ITRIM 20M       27M:      73

ITRIM 24M       24M:      D0

ITRIM 22.1184M  22.1184M: A4

32kHz PD FreQ:            8A48

1.19Vref:                 04AA


MCUID: F7 34 C5 68 00 11 22 

Current VRTRIM:1F, IRTRIM:B7, LIRTRIM:03


STC8H3K

这里是和手册不一致的地方, 对于STC8H3K32S2, F/W version: 7.4.1U, 实际上有4个频段, 也就是IRCBAND取值从 0x00 - 0x03, 24MHz和40MHz这两个属于0x02和0x03, 预设的寄存器要提前两个字节, 从0x7FE7开始读, 这样才是正确的, 研究这个问题耽误了半天时间. 手册不一致, 真是坑爹.


ADDR: 0x7FE7

       VER1     VER2

VRTRIM          40M:      19

VRTRIM          24M:      1C

VRTRIM 35M      ??M:      20

VRTRIM 20M      ??M:      1E

ITRIM  ---      45M:      7C

ITRIM  ---      40M:      47

ITRIM 36.864M:            2D

ITRIM 35M:                12

ITRIM 33.1776M:           FF

ITRIM 30M:                D2

ITRIM 27M:                98

ITRIM 20M:                1A

ITRIM 24M:                64

ITRIM 22.1184M:           41

32kHz PD FreQ:            8D04

1.19Vref:                 04A3


MCUID: F7 4A C5 26 03 11 22 

Current IRCBAND:03, VRTRIM:19, IRTRIM:2D, LIRTRIM:00


在设置时, 如果选择了高频段(35MHz或40MHz), ITRIM不能设置得太高, 如果设置得太高(超过0xE0), 会导致芯片无法启动


STC8A8K64D4 时钟

内置RC振荡的频段增加到了4个: 6M, 10M, 27M, 44M,


STC8A8K64D4是个比较特殊的型号, 你可以看成和STC8H8K功能一样, 只是引脚布局做成了和STC8A8K兼容的布局. 可以看成是为了让之前使用STC8A8K的客户能不改PCB升级的一个STC8H版本.


使用方式和4个IRC频段的STC8H一致, 也是通过IRCBAND, VRTRIM, IRTRIM, LIRTRIM这4个寄存器调节频率.


总结

记录一下避免其他人踩坑


同系列之间可能是跨系列产品

STC8H1K和STC8H3K

STC8A8K64S4和STC8A8K64D4

获取各频段预设值

对于在Linux下开发的, 建议还是要在Windows下通过STC-ISP烧录一次, 这样是最方便快捷的方式得到芯片的预设标定值.


调节CLKDIV需要一个较长的稳定期

如果在程序开始时调节了 CLKDIV, 至少需要等待 0x2FFFF 个时钟周期后再进行其它操作, 否则极容易导致后续的UART和Timer设置出现莫名其妙的错误, 这是今天排查了一整天才填的坑.


设置的代码

/**

 * STC8H Clock: 

 *  MCKSEL                     ||===> MCLKODIV ==> MCLKO_S => P1.6/P5.4

 *  00 Internal IRC    |       ||

 *  01 External OSC    |==> CLKDIV ==> SYSCLK

 *  10 External 32KHz  |

 *  11 Internal 32KHz  |

*/


#define SYS_SetFOSC(__IRCBAND__, __VRTRIM__, __IRTRIM__, __LIRTRIM__)  do {     

                                     IRCBAND = ((__IRCBAND__) & 0x03);           

                                     VRTRIM = (__VRTRIM__);                     

                                     IRTRIM = (__IRTRIM__);                     

                                     LIRTRIM = ((__LIRTRIM__) & 0x03);           

                                 } while(0)


/**

 * Change system clock

 * - invoke this in the beginning of code

 * - don't invoke this if the target frequency is already set by STC-ISP

*/

void SYS_SetClock(void)

{

    uint16_t i = 0; uint8_t j = 5;

    P_SW2 = 0x80;

    if (CLKDIV != (__CONF_CLKDIV))

    {

        CLKDIV = (__CONF_CLKDIV);

        do { // Wait a while after clock changed, or it may block the main process

            while (--i);

        } while (--j);

    }

    P_SW2 = 0x00;

    clkdiv = (__CONF_CLKDIV == 0)? 1 : __CONF_CLKDIV;

    SYS_SetFOSC(__CONF_IRCBAND, __CONF_VRTRIM, __CONF_IRTRIM, __CONF_LIRTRIM);

    while (--i); // Wait

}


读取各预设值的代码

INTERRUPT(tm0isr, 1)

{

    uint8_t i, j;

    counter++;

    if (counter == 1000)

    {

        i = 0;

        counter = 0;

        UTIL_PrintString('ADDR: 0x');

        UTIL_PrintHex(__CID_ADDR >> 8);

        UTIL_PrintHex(__CID_ADDR & 0xFF);

        UTIL_PrintString('rn');

        UTIL_PrintString('       VER1     VER2rn');

        UTIL_PrintString('VRTRIM          40M:      ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('VRTRIM          24M:      ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('VRTRIM 35M      ??M:      ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('VRTRIM 20M      ??M:      ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('ITRIM  ---      45M:      ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('ITRIM  ---      40M:      ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('ITRIM 36.864M:            ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('ITRIM 35M:                ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('ITRIM 33.1776M:           ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('ITRIM 30M:                ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('ITRIM 27M:                ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('ITRIM 20M:                ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('ITRIM 24M:                ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('ITRIM 22.1184M:           ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('32kHz PD FreQ:            ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('1.19Vref:                 ');

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintHex(readCode(i++));

        UTIL_PrintString('rn');

        UTIL_PrintString('rn');

        UTIL_PrintString('MCUID: ');

        for (j = 0; j < 7; j++)

        {

            UTIL_PrintHex(readCode(i+j));

            UTIL_PrintChar(' ');

        }

        UTIL_PrintString('rn');

        UTIL_PrintString('Current IRCBAND:');

        UTIL_PrintHex(IRCBAND);

        UTIL_PrintString(', VRTRIM:');

        UTIL_PrintHex(VRTRIM);

        UTIL_PrintString(', IRTRIM:');

        UTIL_PrintHex(IRTRIM);

        UTIL_PrintString(', LIRTRIM:');

        UTIL_PrintHex(LIRTRIM);

        UTIL_PrintString('rnrn');

    }

}

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • PIC单片机控制的遥控防盗报警器电路

  • RS-485基础知识:处理空闲总线条件的两种常见方法

  • 带有短路保护系统的5V直流稳压电源电路图

  • 如何构建一个触摸传感器电路

  • 如何调制IC555振荡器

  • 基于ICL296的大电流开关稳压器电源电路

    相关电子头条文章