极海单片机替换华大(极海作为备用),一路上遇到了很多问题,所以写一篇文章记录一下。
原本单片机使用的是 STM32F030C8T6,后因 STM32 芯片涨价+缺货,换成了华大,现又考虑选极海作为备选方案。当得知极海的 APM32F030C8T6 和 STM32 对应型号是软件+硬件兼容的时候,我以为极海单片机的验证会非常简单,万万没想到,整个调试过程中遇到了各种问题。
出师不利:串口不通
第一步先更换了单片机芯片,原芯片是华大 HC32F030J8TA,和 APM32F030C8T6 (或STM32F030C8T6)只有一个脚不同,稍微改动电路后(拆了一个电阻),将单片机换上,烧录之前的 STM32 版本程序。
因为极海 APM32F030C8T6 和 STM32 对应型号软硬件兼容,所以可以直接使用 STM32 的程序和 ST-Link,烧录程序后,发现功能基本能跑通,除了串口。
雪上加霜:官方Demo运行没反应
虽然串口暂时没有工作,但程序能跑起来,还是挺让人欣慰的,这样的话后面都不需要怎么改软件了。
想着试试跑一下官方的例程,但是结果让人怀疑人生,GPIO 例程竟然连个灯都没点起来,之前我拿 STM32 的代码都能实现 GPIO 功能,这种问题属实难到了我这个新手。
悬而未决:更换外部晶振
仔细对比 STM32 版本(旧版硬件)和 当前版本的原理图,发现原来的外部主晶振时钟频率为 8MHz,而现在板子上贴的是 32MHz 的晶振。很多国产单片机都是和 STM32 软硬件兼容,而华大却做了一些小改动(也不是说这样不好,只是没其他品牌用的舒服)。
看了一下华大单片机数据手册(我所用的型号),其外部高速晶振可选频率范围为 4~32MHz,这个和其他品牌性能相仿的单片机是一样的,
但是 STM32 和其他与 STM32 软硬件兼容的单片机品牌都是推荐 8MHz 外部高速晶振,而华大默认 32MHz,这里的默认指的是官方例程里设置的外部高速晶振频率。
所以到这里,极海官方例程无法运行就是因为系统时钟不正常,有两个解决办法,一是将板子上的 32MHz 晶振换成 8MHz 晶振,二是改代码,通过适当的分频与倍频实现 48MHz 的系统时钟(该系列最大的时钟频率)。
很明显,第二个办法更加简单,但奈何我当时没想到,傻傻地去把 32MHz 晶振吹了下来,从旧板子上拆了一个 8MHz 晶振焊到测试板上(第二个办法在本文最后一章节讲到)。
换完晶振后,极海的官方例程可以跑起来了,但是串口的问题依然没解决。
渐入佳境:测试真实时钟频率
虽然换了晶振,官方 GPIO 例程也能运行,但我还是怀疑系统时钟有问题。
目前我有两种获取系统时钟的方案,一种是通过库函数,直接读取程序运行时的系统时钟值,然后通过串口(当然现在我串口还没通,就不用想了)打印,或在 Debug 模式下查看。另一种是通过 MCO(时钟输出,microcontroller clock output),将系统的时钟输出到指定的 IO。
库函数获取时钟频率
时钟控制是一个很重要的功能模块,所以单片机官方例程一般都会有一个单独的 Demo 来介绍相关库函数的使用,比如下面我所用的例程,例程名为 RCM_ClockSwitch,在调试模式下,运行相关函数,得到的数据如下,时钟源为 2 (指的是将 PLL 作为系统时钟),系统时钟频率为 48MHz。
这样看来,更换晶振后,系统时钟好像是正确的。但是串口为什么还是没用呢?难道这个打印的时钟不可信?
的确,这个结果没说服我,后来我也了解到,这里打印的时钟只是代码里进行时钟配置后的结果,并不能真正反映系统的时钟(除非你能保证程序里的时钟配置是完全按照外部晶振频率来设置的)。
这样一来,哪怕我还是用 32MHz 的晶振,这里获取到的时钟值依然会是 48MHz,但实际设置的系统时钟到了 48M 的 4 倍(32 / 8 = 4),不过该型号单片机最高支持 48MHz。官方 GPIO 例程不能运行已经说明了胡乱设置系统的时钟会影响程序正常运行。
正所谓实践出真知,上面得出的时钟频率相当于理论上的系统时钟频率,而实际的时钟频率,还需要通过实打实的测试来求出,也就是下面 MCO 时钟输出。
MCO 输出时钟频率
下图是时钟树里对 MCO 的描述:
我当前所用单片机只有一个 IO 有此功能,PA8(我发现好多不同品牌,不同型号的 MCO 都在 PA8 上):
时钟输出的配置很简单,就和普通的 GPIO 配置差类似,下面是极海官方例程中的 MCO 配置函数:
/*!
* @brief Clock output init
*
* @param None
*
* @retval None
*
* @note
*/
void ClockOutputInit()
{
GPIO_Config_T gpioconfig;
/** Connect RCM Clock output */
GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_8, GPIO_AF_PIN0);
gpioconfig.mode = GPIO_MODE_AF;
gpioconfig.outtype = GPIO_OUT_TYPE_PP;
gpioconfig.pin = GPIO_PIN_8;
gpioconfig.pupd = GPIO_PUPD_NO;
gpioconfig.speed = GPIO_SPEED_50MHz;
GPIO_Config(GPIOA, &gpioconfig);
/** set SYSCLK as COC source*/
#if defined (APM32F030) || defined (APM32F051)
RCM_ConfigCOC(RCM_COC_SYSCLK);
#else
RCM_ConfigCOC(RCM_COC_SYSCLK, RCM_COC_DIV_128);
#endif
}
在 main() 函数调用上面的 MCO 初始化函数,然后用示波器或逻辑分析仪测试 PA8 的输出频率:
测到这步,已经可以确定系统时钟就是 48MHz。
对比了之前贴 32MHz 外部高速晶振的时钟输出(用 STM32 代码实现的,极海的代码无法正常运行),发现在使用 32MHz 晶振时输出的系统时钟是不稳定的,一直在一个范围内变动,所以极海官方例程无法运行的问题已经找到了,就是时钟设置出错,导致系统时钟不稳定。
就在使用极海时钟管理例程时,我发现串口竟然打印了数据!!该例程的代码如下:
重新解压了一份新的官方串口例程,发现串口已经可以使用,所以之前换完 8MHz 晶振后串口依然没用是因为代码的问题(可能改太多,改出了一些隐藏的 Bug),无论如何,串口能用起来就完事了,其他的都不重要了。
举一反三:不换晶振,灵活配置系统时钟
前面提到极海官方例程默认外部高速晶振的时钟频率为 8 MHz,而我板子上贴的是 32 MHz,如果不想换晶振,也可以通过配置系统时钟来实现 48MHz (该芯片最高频率)的系统时钟。
32M 转为 48M,只需要先对 HSE (外部高速时钟频率)进行 2 分频,然后将其作为 PLL 的时钟源,PLL 3 倍频后作为系统时钟,32 / 2 * 3 = 48。
这里发现一个问题:
CFG1_B(时钟控制寄存器 1) 结构体里 PLLSRCSEL 占两个位,
而用户手册里这个值明明只占一位数据,这代码和文档有点对不上呀。
HSE 2 分频
回到时钟配置的问题,先将 HSE 进行 2 分频,这个操作可以通过将 PLLHSEPSC 置 1 来实现,这个值只占 1 位数据,所以最多实现 2 分频。
PLL 3 倍频
PLL 倍频可以通过修改 PLLMULCFG 实现,3 倍频,将其设置为 1 即可。
修改配置函数
上面这些都可以在时钟函数中进行配置,比如我所用的工程代码会通过 SystemClock48M()
来配置系统时钟,那么我就对其做了以下修改:
使用库函数获取时钟频率,只有 12MHz,但 MCO 时钟输出测得的时钟频率为 48MHz。说明 48MHz 系统时钟已经有了,只是程序代码还有没完善的地方。
修改 HSE_VALUE 就能解决上面这个问题,将原来的 8000000
改为 32000000
,
修改 HSE 频率值后,库函数能获取正确的系统时钟频率了。