发新帖本帖赏金 80.00元(功能说明)我要提问
返回列表

[MM32生态] MM32F0020 使用上的一些经验之谈

[复制链接]
1075|3
手机看帖
扫描二维码
随时随地手机跟帖
yang377156216|  楼主 | 2022-6-19 23:07 | 显示全部楼层 |阅读模式
本帖最后由 yang377156216 于 2022-6-19 23:09 编辑
#申请原创#  @21小跑堂

由于项目需要,用了灵动微的 MM32F0020 20脚的 MCU ,使用期间边学习边总结,于是就有了今天的这篇使用心得,无私分享给大家吧。
灵动微官网关于 MM32F0020 的资料链接:https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_value_line/mm32f0020/

关于最小系统的设计
在硬件设计之初,最先会有疑惑的地方在于该芯片的最小系统该如何处理?不同厂商的不同 MCU 平台都存在它们独特的一些特性,所以在切换平台时确实应该注意芯片的最小系统设计注意事项,其中包括电源、时钟、复位以及boot pin 启动选择等几大方面,幸好大部分内容还是可以通过芯片的 DS 手册获取到的。
通过查看 DS 手册得知,MM32F0020 工作电源电压范围是 2.0V ~ 5.5V,相对而言属于宽压范围了。其中 VDD VDDA VSS VSSA 内部分别并接在一起,即数字电源与模拟电源是非隔离的,在封装上一并叫做 VDD VSS,作为电源引脚为 I/O 和内部的调节器及其它电源域供电。常规的电源原理设计上都会加上旁路去耦滤波电容,一般用一颗1uF 和 一颗 103 电容对地并联,滤去高频和低频的杂波信号。为增强电源的抗干扰能力,还会在电源上先串个磁珠或者0~50R 电阻再进 MCU 。此外,电源的供电电流特性和上下电时序都需要参考 DS 手册对应章节,它们都需要根据多种参数和因素综合考量。“那能给 5.6V 电源电压吗?”这是一个好的疑问,但我们还是最好在人家规定的设计范围内的参数去做设计吧,不建议超出 spec 使用。另外,由于我们硬件上需要做最低功耗的设计,且是由电池供电的,所以必需考虑多级电源拓扑结构以及注意上下电的时序是否符合规格,这个参数在 spec 手册里面也是有的,之前用 NXP 的LPC 系列芯片遇到过慢上电死机以及未按照规格掉电到0v后就立马上电的死机问题,所以这个时序图还是必须得关注的:
0020上下电时序图.jpg
下面是我们用过的能够控制切断电源的一个电池供电拓扑结构设计图,在系统的低功耗设计时必须得考虑应用上的
电池供电拓扑结构.jpg
再来看时钟系统,该芯片上电启动后是使用内部的 8 MHz 高速振荡器作为默认的系统时钟源头,随后可根程序配置选择使用内部的振荡器进行 PLL 倍频得到最终想要的主频(最高为48 MHz,且默认为系统时钟 1 分频而来),作为入门级的单片机这个时钟够高了,且其精度在全温全压下也都还算不错,完全可以满足串口正常通讯,这就不需要外接晶振了。不过也可选择外部的有源晶振或者无源振荡器匹配电路作为外部时钟源,根据 DS 手册的外部时钟源特性章节得知,有源晶振频率范围是 2MHz ~ 32MHz ,无源晶振频率范围是 4MHz ~ 24MHz ,且参数的选择需要以供电电压为依据。另外,根据芯片 UM 手册的时钟章节可以了解到,内部有时钟反馈机制可以启用,这个 CRS  功能就厉害了,启用后当监测到外部时钟无效时,系统会自动将外部时钟源屏蔽,关闭 PLL ,转而使用内部的振荡器。由于我的应用用不到外部晶振,而且晶振口也需要被用作于普通 IO ,开始不知道如何配置,由于例程中也未有参考,一顿操作后才知道,PB0-OSC_IN 和 PB1-OSC_OUT 脚可作为 GPIO 时的初始化使用配置与其它 IO 无异,只要时钟宏定义那里将系统时钟源选择内部时钟即可。
时钟特性.jpg
再来看硬件复位引脚,这颗芯片的复位 Pin 与 PA10 共用,也不知道它的设计原理是如何实现的,于是向代理商的工程师咨询到“上电到内核稳定工作的这段时间该脚是默认为 NRST 复位脚功能,正常工作后该脚根据用户选项字节 FLASH_OBR.Bit7 的配置来决定是否切换为 PA10 功能。”于是理解了,我的应用上没有必要将它用作 IO ,还是老老实实地用作复位脚吧。另外,他们说该引脚内部默认接了一个不能断开的 MOS 型弱上拉电阻,阻值可能随环境温度变化较大,所以让我外接复位电路是必须的,还给我推荐了一个 RC 参数:一般在该脚上增加 100K 上拉电阻和并联 0.1uF~4.7uF 电容确保不会发生板级寄生复位,并且为了设计成更可靠的烧录接口,还需要将复位脚引出到烧录口,这样会让烧录更稳定,不至于使用软件复位失效而无法复位芯片而导致烧录失败了。又学到了!但貌似手册上关于这点的信息不是很多哟,希望后面能够改善吧。
复位电路推荐.jpg
做过 ST 芯片应用设计的都知道,一般 M0 内核的单片机都会有一个或者两个 boot 脚,通过它们来做硬件上的启动选择。灵动的 MM32F0020A1T 这颗芯片自然也一样,它是通过 BOOT0 引脚和 内部的选项字节的 nBOOT1 选择位来决定上电启动后是从片内 Flash 启动,还是从系统 ISP Bootloader 启动,又或者是从片内 SRAM 启动。从引脚定义图可以看出,BOOT0 引脚与 PA14 / SWDCLK 引脚是共用的,该脚内部有加弱下拉电阻,在启动到完全稳定工作这段时间默认为 boot 选择功能(所以要想从片内 Flash 启动的话该脚外部是不可以加上拉电阻的),尔后,该脚默认为 SWD 调试口的 SWDCLK 功能脚,接着该脚可以在程序中被切换为 GPIO 。由于一般 SWDIO 引脚可以预留外部上拉电阻,而 SWDCLK 可以预留下拉电阻位置,所以当此引脚不用于 GPIO 时外部可增加下拉电阻。需要注意,由于不同板子在走线和 layout 上会有不同导致 IO 口的外部阻抗会有不同,从而会造成悬空该脚时会是上拉的情况,我的一版硬件就遇到了该问题,最后导致上电后不能跑到主程序里面而是从芯片内部 boot 启动了,所以也强烈建议大家在做设计时,不要把该脚应用到需要默认外部上拉的场合,而尽量外部加个下拉电阻吧。说到这个 ISP ,该功能还是不错的,至少不需要额外再做个自己的 BootLoader 就能做在线升级了(前提是需要向厂家申请到通讯协议),并且了解到该芯片的 ISP 是可以通过所有支持 UART1 的 IO 去进行编程的。我的应用就用到了该 ISP 功能,并且我是从应用 APP 中直接跳转到芯片 boot 的,跳转代码可以参见以下代码:
#define SYSTEM_BOOT_ADD   0x1FFFF400 
u32 gJumpToSystemBoot_Cnt = 0 ;

void Jump_To_APP(uint32_t app_address)
{
    void (*pUserApp)(void);
    uint32_t JumpAddress;      
    __asm("CPSID  I");     //关中断

    JumpAddress = *(volatile uint32_t*) (app_address+4);
    pUserApp = (void (*)(void)) JumpAddress;
    /* Initialize user application's Stack Pointer */
    __set_MSP(*(volatile uint32_t*) app_address);
    pUserApp();
}

////////////////////////////////////////////////////////////////////////////////
/// [url=home.php?mod=space&uid=247401]@brief[/url]  This function is main entrance.
/// @param  None.
/// @retval  0.
////////////////////////////////////////////////////////////////////////////////
s32 main(void)
{
    LED_Init();
    DELAY_Init();
    while(1) {
        LED1_TOGGLE();
        LED2_TOGGLE();
        LED3_TOGGLE();
        LED4_TOGGLE();
        DELAY_Ms(1000);
        gJumpToSystemBoot_Cnt ++;
        if(gJumpToSystemBoot_Cnt > 5)
        {
            gJumpToSystemBoot_Cnt = 0;
            /*
            在跳转到Bootloader 之前,有几个事情必须要做好:
            1) 关闭所有外设的时钟
            2) 关闭使用的PLL
            3) 禁用所有中断
            4) 清除所有挂起的中断标志位
            5) 建议先将 RCC 切为默认的 8M HSI
            */
            Jump_To_APP(SYSTEM_BOOT_ADD);
        }
}


关于 GPIO 的使用和设计
在通讯 IO 、 DIO 以及模拟输入引脚 这些引脚的硬件设计上总结的一些经验如下:

1. 由于该芯片所有 IO 都不能支持 5V 容忍,所以任意 IO 口引入的电压值都不能超过 VDD 值(可能有一点点余量吧)。言下之意,当 VDD 为 3.3V 时,所有 IO 不能引入超过 3.3V 的电压;当 VDD 为 5V 时,所有 IO 不能引入超过 5V 的电压。长时间工作在超出 IO 输入电压承受量程的情况下,容易引发内部逻辑紊乱;如果瞬间超出电压很多且电流还很大时,还容易引发芯片的 latch-up 。在我的一些按键上拉、NTC 分压 IO 口设计时我有针对性地做了控制。
2. 所有 IO 口为 COMS 兼容的,COMS 电路是电压控制器件,它的输入阻抗很大,对干扰信号的捕捉能力很强。所以,不用的引脚建议不要悬空,要接上拉电阻或者下拉电阻,给它一个固定的电平,或者直接连接到覆铜地上。如果未注意到此点,恰巧被悬空的引脚为 ADC 输入脚,此时需要注意不能在程序中配置该脚为模拟输入,否则比较容易干扰其它 ADC 通道采样转换结果。
3. 所有对外通讯的连接口,除了要注意不同器件之间引脚的电平兼容性,最好都预留一个 50R~1K 电阻位置,而不直接引入到 MCU 引脚,这样做既能增强板级的抗干扰能力,还便于分析查找电路的问题。
4. 所有通用 IO 口的输出驱动力都能够承受的最大灌电流和拉电流为 20mA per IO,且在程序中配置速度为 50MHz 时驱动能力最强,根据实测得知,该速度参数的差异也只体现在驱动力上而非真正的翻转速率上。在某些应用场景需要稍微大些的驱动能力,此时可以将多个 IO 连接在一起作为一个驱动引脚使用,在程序中同时同反方向地操作这几个 IO。
5. 该芯片的 ADC 输入口内阻最大为 1.5K ,通常信号源不能直接引入到 ADC 通道 IO 中,而会在输入端前面加一级电压跟随器或者加一个简单的阻抗匹配 RC 电路,匹配的参数需要满足 DS 手册中的计算公式,根据该公式决定输入匹配电阻值以及采样速率和采样保持周期值,采样的精确度很大程度上由板级采样通道设计和程序中 ADC 转换速率及保持时间的配置决定,不同配置可能会有极大的差别哟。

芯片的复位源查看
在调试软件的时候,会发现板子会因为某种不明原因而触发了复位,导致一些功能体验不友好,于是需要找到复位源。
MM32F0020 共有两大类复位:电源复位、系统复位。 其中电源复位有:上电复位 POR 、掉电复位 PDR 、待机唤醒复位,不包括 BOR 复位哟 (ST 是有的);系统复位有:NRST 外部硬复位、窗口看门狗和独立看门狗溢出复位、软件复位、CPU 死锁复位、内部电源电压监测器复位 PVD 、低功耗误触发复位。当未复用 NRST 引脚为普通 IO 时,所有复位动作都会触发 NRST 引脚拉低。所以,复位源分为硬件源和软件源头,可分步查验。硬件上,可以使用示波器同时抓取 VDD NRST 引脚波形,调整示波器采样率和采样深度到合适值并且保持下降沿触发模式,看是否存在瞬间掉电情况或者电源跌落幅度较大情况(一般高频运行时所需要的电源电压稳定度越高)。另外查验一些人机交互接口,看是否会有 ESD 引发复位的可能,这个在系统 EMC 做的不好时可能会大概率发生手摸复位的情况哟。软件上,主要可以通过打印出 RCC_CSR 状态寄存器查看上一次复位源具体是哪类,然后有针对性地查看程序中是否有做软件复位动作,是否开启看门狗但忘了及时喂狗,选项字节配置中是否使能了看门狗的硬件模式,等等。另外根据经验,有时候可以适当降低主频运行从而不再引发内核的复位。

IO 功能模式在软件中如何切换
常规的 GPIO 输入输出以及复用功能都会配置,但是一些特殊的使用场景就有些疑惑了,比如说 I2C 的时钟输入该配置复用还是上拉输入?复用功能引脚怎么切换成通用输出引脚?该怎么使用串口的引脚 Swap 功能?这就需要深入理解 UM 手册中的 GPIO 功能结构框图,以及 DS 手册的引脚功能复用表了。总结如下:
1. 由于I2C 的时钟引脚同样也是双向 IO ,所以需要配成复用开漏模式,而不是通用 / 复用上拉输入。
2. 引脚复用功能表中标注 “-”的表示无此复用功能,另外一个意思就是说表示通用输出功能号。所以当 UART / I2C / SPI / TIM CHx 等复用输出功能引脚需要用作通用输出引脚时,只需要将 AF 号配成表中无复用功能的号数 ;同理在应用上也可以来回切换 SWD 口的复用和通用输出功能,但需要注意切换配置后需延时等待稳定。
3. 为了将 UART_TX 与 UART_RX 引脚功能在 PAD 上置换,需要在不改变原先配置基础上,先将 UART 模块禁止,然后置位 UART_GCR.SWAP 位,再将之前的 UART_TX 引脚配置为输入模式,UART_RX 脚配置为输出模式,两者之前的复用功能号不需要改变,最后再重新使能 UART 模块,此时串口的收发功能引脚已经置换。
IO 结构.jpg
IO 复用表.jpg
由于我还需要将 SWD 复用为 GPIO ,对照上面这个表即可知道,其中 AF4 可以复用为 GPIO 功能,参考我的配置代码如下:
#define JTAG_LED1_ON()  GPIO_ResetBits(GPIOA,GPIO_Pin_14)        // PA14
#define JTAG_LED1_OFF()  GPIO_SetBits(GPIOA,GPIO_Pin_14)        // PA14
#define JTAG_LED1_TOGGLE()  (GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_14))?(GPIO_ResetBits(GPIOA,GPIO_Pin_14)):(GPIO_SetBits(GPIOA,GPIO_Pin_14))        // PA14

#define JTAG_LED2_ON()  GPIO_ResetBits(GPIOA,GPIO_Pin_13)        // PA13
#define JTAG_LED2_OFF()  GPIO_SetBits(GPIOA,GPIO_Pin_13)        // PA13
#define JTAG_LED2_TOGGLE()  (GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_13))?(GPIO_ResetBits(GPIOA,GPIO_Pin_13)):(GPIO_SetBits(GPIOA,GPIO_Pin_13))        // PA13

/********************************************************************************************************
**函数信息 :LED_Init(void)                        
**功能描述 :LED初始化
**输入参数 :无
**输出参数 :无
********************************************************************************************************/
void JTAG_LED_Init(void)
{        
        do  {
                GPIO_InitTypeDef  GPIO_InitStructure;
                RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);  //开启GPIOA时钟
                GPIO_PinAFConfig(GPIOA,GPIO_PinSource14,GPIO_AF_4);                //任意没有特殊功能的复用
                GPIO_PinAFConfig(GPIOA,GPIO_PinSource13,GPIO_AF_4);                //任意没有特殊功能的复用
                GPIO_InitStructure.GPIO_Pin  =  GPIO_Pin_14|GPIO_Pin_13;
                GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
                GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
                GPIO_Init(GPIOA, &GPIO_InitStructure);        
        }while(0);        
        JTAG_LED1_ON();
        JTAG_LED2_ON();
}

////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//                         功能测试部分程序                                           //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////
void Config_Init(void)
{        
        Delay_Init();
        Delay_Ms(2000);//调试时候用,以免swd口复用为普通io后不能烧录调试        
        JTAG_LED_Init()        ;
}

void Function_Test(void)
{
        Delay_Ms(1000);
        JTAG_LED1_TOGGLE();
        JTAG_LED2_TOGGLE();        
}
无法进入 Stop 低功耗的问题
在移植官方 Lib Samples 的低功耗样例时一直无法正常进入 Stop 模式,电流表检测的数据一直达到mA 级别,根据 debug 调试定位到,是由于自己在移植程序时未将 Delay_Init(); 初始化函数去除,而恰巧该函数使用到 Systick 并且开启了中断,而进 Stop 低功耗模式是通过调用 _WFI 或者 _WFE 指令的,任何中断标志都会将这两条指令触发的低功耗模式唤醒,如果未注意到这点那么就会导致进低功耗失败,教训啊!

IWDG 的使用
IWDG 通常在量产程序中都会开启,它是为了增强系统的健壮性而增加的,但在 Debug 模式下不便于开启独立看门狗,要么将其功能调试好后先暂时屏蔽,要么需要使用 DBG_CR.DBG_IWDG_STOP 位来使其在调试模式下停止工作。另外在进入 Stop 低功耗前如果也配置并开启了 IWDG 看门狗,进入低功耗后由于 LSI 时钟不会被关闭,而主程序由于 AHB 时钟已经被关闭了导致不能喂狗,从而会引发独立看门狗复位。要想同时启用这 2 个功能,那可以在进入 Stop 低功耗模式前先将 LSI 时钟关闭,被正常唤醒后再将 LSI 时钟使能即可,这个功能我觉得还是挺好的,不至于像 ST 的开启后就无法关闭了。
另外,由于 IWDG 的时钟源是 LSI ,这个时钟一般都是 40K ,但实际测试不会那么准确的,不像内部的高速时钟一样精度那么高,那么想了一个办法,用高速时钟去计算和侧面地校准这个低速时钟,从而得出较为准确的延时时间。大体思路是如下框图:
LSI 校准.jpg
当然,也可以参照我附件中的测试程序,来测试反推后的计算结果与实际抓取到的波形时间间隔一致性如何:
实际延时时间.png

MM32F0020A1T 使用心得总结完毕,也总算完成了自己在 21IC 社区中的第十篇原创贴。继续努力,再接再厉!

MM32_HSI_Calc_LSI_Wakeup_Stop.zip (1.83 MB)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 80.00 元 2022-06-20
理由:恭喜通过原创文章审核!请多多加油哦!

评论
21小跑堂 2022-6-20 16:41 回复TA
软硬件结合,详细描述了一个最小系统的设计过程,重点叙述到位,层次分明,思路清晰,对其他平台MCU最小系统设计有借鉴意义。 
gouguoccc| | 2022-6-20 19:52 | 显示全部楼层
收藏了,感谢楼主分享经验。

使用特权

评论回复
onlycook| | 2022-6-27 11:52 | 显示全部楼层
收藏了,感谢楼主分享的宝贵经验

使用特权

评论回复
发新帖 本帖赏金 80.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则