随着汽车行业进入智能化和新能源时代,过去由瑞萨、英飞凌、恩智浦、ST等芯片巨头所垄断的汽车级芯片市场竞争格局被打破,国产MCU中科芯、先楫、中微爱芯,GD、复旦微电子、航顺、中微、芯海科技等相继加入。从市场的汽车智能化的速度来判断市场对于汽车芯片将会出现爆发式增长的态势,对汽车级芯片的需求将会出现井喷式增长。众所周知的原因,高端芯片和汽车级芯片,国外品牌自供不暇,这也给了国产品牌MCU突围的机会。我最近在选型,调研了五十八所旗下的央企中科芯MCU,他们的产品CKS32103和407系列顺利通过AEC-Q100汽车级认证,据实验结果获得了TÜV莱茵的可靠性认证。申明一点,我这里没有任何打广告的成分,纯粹介绍这款IC,让大家多一个选择而已。我之前用过他们的103RBT7,也是车规级的,感觉不错,软硬件都兼容ST的,这次也不是介绍这个型号。这次开发一个带4G通信功能的20KW直流充电桩,103的资源不够用,我着重调研了407的资源,CKS32F407VE和ZE系列的资源是可以的,而且软件直接下载,能够运行就可以,硬件是PTP,更多应用场景和技术参数这里就不多说了,大家可以自己去官网查看。我这里主要分享下CKS32F4xx系列的产品时钟配置、GPIO口配置、中断优先级管理。
国产替代STM32F芯片CKS32F407(417、405、415)系列产品配备了1MB eFlash和192KB SRAM,采用2.0-3.3V电压供电,工作频率144MHz,工作温度范围-40~+105℃,工作寿命15年以上。凭借优异特性,CKS32F407系列产品可以广泛用于多种车用场景,如车窗、雨刷、空调、智能车锁、电动座椅、电动后备箱、氛围灯、动态尾灯等车身控制系统、车用照明系统和电机电源系统,以及仪表盘、车载影音、娱乐音响、中控导航、车载无线充等智能座舱系统。
作为MCU运行的基础,时钟是单片机各个模块工作时序的最小时间单位,推动单片机的各指令执行,是MCU选型的一个重要指标。CKS32F4xx系列产品具有众多的外设,但并非所有的外设均需要系统时钟的高频率,并且高时钟频率将导致功耗增加、抗电磁干扰能力变弱,因此,CKS32F4xx系列产品内部具备多个时钟源。本文将对CKS32F4xx系列产品时钟组成进行分析,并讲解该系统单片机的时钟的配置方法,以能够让用户更加简单的对系统时钟进行配置。
在CKS32F4xx系列产品中,有HSI、HSE、LSI、LSE、PLL五个重要的时钟源,其中PLL分为主PLL和专用PLL两部分。从时钟频率来分可以分为高速时钟源(HIS、HIS、PLL)和低速时钟源(LSI、LSE);从来源可分为外部时钟源(HSE、LSE)和内部时钟源(HIS、LSI、PLL)。
①:LSI是低速内部RC振荡器,频率为32kHz。供独立看门狗和RTC单元使用。
②:LSE是低速外部时钟,接频率为32.768kHz晶振。主要供RTC单元使用。
③:HSE是高速外部时钟晶振输入,频率范围为4MHz~26MHz。。
④:HSI是高速内部RC振荡器,频率为16MHz。可以直接作为系统时钟或者用作PLL输入。
⑤:PLL为锁相环倍频输出,有两个PLL:
1)主 PLL(PLL)由 HSE 或者 HSI 提供时钟信号,并具有两个不同的输出时钟。
其一PLLP用于生成高速的系统时钟(最高 168MHz)
其二PLLQ用于生成 USB_OTG_FS(48MHz)、随机数发生器SDIO时钟。
2)专用 PLL(PLLI2S)用于生成精确时钟,用于实现I2S高品质音频性能。
在CKS32F4xx系列固件库system_cks32f4xx.c文件中定义了函数SystemInit(void),并在其中调用了SetSysClock()函数来配置系统关键时钟寄存器,其处理流程如下:
先使能外部时钟HSE,等待HSE稳定之后,配置AHB、APB1、APB2时钟相关的分频因子;等待这些都配置完成之后,打开主PLL时钟并设置主PLL作为系统SYSCLK时钟源。如果HSE不能达到就绪状态则依然以HSI作为系统时钟源头。
在设置主PLL时钟时,需要设置一系列的分频系数和倍频参数,代码如下:
RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);
PLL_M、PLL_N、PLL_P宏定义均在在System_cks32f4xx.c文件中定义,当采用8MHz外部晶振时,主PLL时钟计算方法如下:
PLL = 8MHz * PLL_N / (PLL_M * PLL_P) = 8MHz * 336 /(8 * 2) = 168MHz
用户可根据实际需求,根据SetSysClock函数内的注释进行实际修改,可用的时钟源配置宏定义位于cks32f4xx.h中,如RCC_CR_HSION、RCC_CR_HSEON等。
在系统初始化之后,在使用部分外设时,我们还需要根据外设需求修改某些时钟源配置。在CKS32F4xx系列固件库中,时钟源的选择以及时钟使能等函数均在RCC相关固件库文件 cks32f4xx_rcc.h 和 cks32f
voidRCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
voidRCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
voidRCC_AHB3PeriphClockCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
voidRCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
以上5个系统时钟使能函数分别控制AHB1、AHB2、AHB3、APB1、APB2总线。要使能某个外设,调用对应的总线外设时钟使能函数即可。
例如,如果我们要使能GPIOA,那么我们可以在头文件 cks32f4xx_rcc.h 里面查看到宏定义标识符RCC_AHB1Periph_GPIOA挂载在AHB1总线之下,因此我们调用方式入如下:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
同理,如果我们要使能USART1的时钟,那么我们调用的函数为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
还有一类时钟使能函数是时钟源使能函数,前面我们已经讲解过CKS32F4xx系列有5类时钟源。这里我们列出来几种重要的时钟源使能函数:
voidRCC_HSICmd(FunctionalState NewState);
voidRCC_LSICmd(FunctionalState NewState);
voidRCC_PLLCmd(FunctionalState NewState);
voidRCC_PLLI2SCmd(FunctionalState NewState);
voidRCC_PLLSAICmd(FunctionalStateNewState);
void RCC_RTCCLKCmd(FunctionalState NewState);
具体调用方法如下:
RCC_PLLCmd(ENABLE);
第二类时钟功能函数:时钟源选择和分频因子配置函数用来选择相应的时钟源以及配置相应的时钟分频系数,比如配置HSI、HSE、PLL三个中的一个时钟源为系统时钟。以下为几种时钟源配置函数:
void RCC_LSEConfig(uint8_t RCC_LSE);
voidRCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource);
voidRCC_HCLKConfig(uint32_t RCC_SYSCLK);
voidRCC_PCLK1Config(uint32_t RCC_HCLK);
voidRCC_PCLK2Config(uint32_t RCC_HCLK);
voidRCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);
void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t PLLM,uint32_t PLLN, int32_t PLLP, uint32_t PLLQ);
比如我们要设置系统时钟源为 HSI,则可以调用系统时钟源配置函数:
RCC_HCLKConfig(RCC_SYSCLKSource_HSI);
第三类外设复位函数如下:
voidRCC_AHB1PeriphResetCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
voidRCC_AHB2PeriphResetCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
voidRCC_AHB3PeriphResetCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
voidRCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
这类函数和前面讲解的外设时钟函数使用方法基本一致,不同的是一个是用来使能外设时钟,一个是用来复位对应的外设。对于这些时钟操作函数,我们就不一一列举出来,大家可以打开 RCC 对应的文件仔细了解。
CKS32F407VE VG ZE ZG T6/T7系列产品GPIO是通用输入输出端口的简称,CKS32F4xx系列产品通过GPIO引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。最基本的输出功能是由CKS32F4xx系列产品控制引脚输出高、低电平,实现开关控制,如把GPIO引脚接入到LED灯,那就可以控制LED灯的亮灭,引脚接入到继电器或三极管,那就可以通过继电器或三极管控制外部大功率电路的通断。最基本的输入功能是检测外部输入电平,如把 GPIO引脚连接到按键,通过电平高低区分按键是否被按下。
该图从最右端看起,最右端标注着“I/O”的就是代表CKS32F4xx系列产品引出的GPIO引脚,其余部件都位于芯片内部。引脚处的两个保护二级管可以防止引脚外部过高或过低的电压输入,当引脚电压高于VDD_FT时,上方的二极管导通,当引脚电压低于VSS时,下方的二极管导通,由此可以防止不正常电压引入芯片导致芯片的烧毁。这里要特别注意VDD_FT 代表IO口兼容3.3V和5V,如果没有标注“FT”,就代表着不兼容5V。在芯片数据手册的引脚定义中,会看到有“电平I/O”一列,有FT标注的即为支持5V,如下图所示:
标号1处是上拉、下拉电阻,从它的结构我们可以看出,通过上、下拉对应的开关配置,我们可以控制引脚默认状态下的电压,开启上拉的时候引脚电压为高电平,开启下拉的时候引脚电压为低电平。同时也可以设置“既不上拉也不下拉模式”,我们也把这种状态称为浮空模式。
标号2处是一个由P-MOS和N-MOS管组成的单元电路。这个结构使GPIO具有了“推挽输出”和“开漏输出”两种模式。上方的P-MOS管高电平导通,低电平关闭,下方的N-MOS低电平导通,高电平关闭。
标号3处是输出数据寄存器,它为标号2处的双MOS管结构电路提供输入控制信号,因此通过修改输出数据寄存器的值就可以修改GPIO引脚的输出电平。而图中“置位/复位寄存器 GPIOx_BSRR”可以通过修改输出数据寄存器的值从而影响电路的输出。
标号4处是复用功能输出,“复用”是指CKS32F4xx系列产品的其它片上外设可以对GPIO引脚进行控制,此时GPIO引脚用作该外设功能的一部分,算是第二用途。例如我们使用USART串口通讯时,需要用到某个GPIO引脚作为通讯发送引脚,这个时候就可以把该GPIO引脚配置成USART串口复用功能,由串口外设控制该引脚,发送数据。
标号5处是输入数据寄存器,它连接到图中的TTL施密特触发器,触发器的基本原理是当输入电压高于正向阈值电压时,输出为高;当输入电压低于负向阈值电压时,输出为低;IO口信号经过触发器后,模拟信号转化为0和1的数字信号,也就是高低电平,并且是TTL电平协议, 然后存储在“输入数据寄存器。因此,通过读取该寄存器就可以了解GPIO引脚的电平状态。
标号6处是复用功能输入,与“复用功能输出”模式类似,同样,如果我们使用USART串口通讯时,需要用到某个GPIO引脚作为通讯接收引脚,这个时候就可以把该GPIO引脚配置成USART串口复用功能,使USART可以通过该通讯引脚接收远端数据。
标号7处是模拟输入输出,当 GPIO引脚用于ADC采集电压的输入通道时,用作“模拟输入”功能,此时信号是不经过施密特触发器的,因为经过施密特触发器后信号只有0、1 两种状态,所以ADC外设要采集到原始的模拟信号,信号源输入必须在施密特触发器之前。同样的,当 GPIO引脚用于DAC作为模拟电压输出通道时,此时作为“模拟输出”功能,DAC 的模拟信号输出就不经过双MOS管结构了,在GPIO结构框图的右下角处,模拟信号直接输出到引脚。
浮空输入状态下,IO的电平状态是不确定的,完全由外部输入决定。如果在该引脚悬空的情况下,读取该端口的电平是不确定的。
在该模式下,如果IO口外部没有信号输入或者引脚悬空,IO口默认为高电平。如果I/O口输入低电平,那么引脚就为低电平,MCU读取到的就是低电平。
在该模式下如果IO口外部没有信号输入或者引脚悬空,IO口默认为低电平,如果I/O口输入高电平,那么引脚就为高电平,MCU读取到的就是高电平。
当GPIO用于模拟功能时,引脚的上、下拉电阻是不起作用的,这个时候即使配置了上拉或下拉模式,也不会影响到模拟信号的输入输出。除了ADC和DAC要将IO配置为模拟功能模式之外,其他外设功能一律要配置为复用功能模式。
在该模式下,若MCU控制输出为高电平1时,输出指令是不会起到作用的。此时I/O端口的电平就不会由输出的高电平决定,而是由I/O端口外部的上拉或者下拉决定,如果没有上拉或者下拉,IO口就处于高阻态。虽然通过软件设置内部上拉,也可以输出高电平,但是CKS32F4xx系列产品内部上拉是"弱上拉",即通过此上拉输出的电流是很弱的,驱动能力很弱。但是在该模式下,当MCU控制输出为低电平0时,即使没有上拉或者下拉,I/O端口也会输出低电平。另一方面,在开漏模式下,施密特触发器是打开的,即输入可用,可以通过输入数据寄存器GPIOx_IDR读取I/O的实际状态。开漏输出主要有以下两点作用:
a. I/O端口设置成开漏输出模式时,可以用来连接不同电平的器件,用来匹配电平,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻。因此我们通过改变上拉电源的电压,便可以改变传输电平。比如通过加上上拉电阻就可以提供TTL电平-CMOS电平的输出。
b.当多个设置为开漏输出的引脚连接到一条总线上时。通过外加一个上拉电阻,在不增加任何器件的情况下,这些引脚形成了“与逻辑”关系,即“线与”。如果有一个引脚输出为逻辑0,相当于接地,那么与之并联的回路“相当于被一根导线短路”,所以总线上的逻辑电平便为0。只有都为高电平时,总线上的电平才为1。在IIC通信中,引脚通常设置为开漏输出模式。
此时GPIO复用为其他外设,输出数据寄存器GPIOx_ODR无效;即输出的高低电平来源于其它外设,除了输出信号的来源改变之外,其他的与开漏输出功能相同。
在该模式下,如果我们控制输出为0,低电平,则I/O端口的电平就是低电平。若控制输出为1,高电平,则I/O端口的电平就是高电平。此时,外部上拉和下拉的作用是控制在没有输出IO口的默认电平。在该模式下,施密特触发器也是打开的,即输入可用,通过输入数据寄存器GPIOx_IDR可读取I/O的实际状态。
此时GPIO复用为其他外设,输出数据寄存器GPIOx_ODR无效;即输出的高低电平来源于其它外设,除了输出信号的来源改变之外,其他的与推挽输出功能相同。
GPIO的引脚速度又称输出驱动电路的响应速度,即一个驱动电路可以不失真地通过信号的最大频率。比如信号频率为10MHz,而我们把GPIO速度配置成了2MHz,则10MHz的方波很可能就变成了正弦波,发生了失真。芯片内部在I/O口的输出部分安排了多个响应速度不同的输出驱动电路,我们可以根据自己的需要选择合适的驱动电路,通过选择速度来选择不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。因为GPIO口的速度配置越高,噪声越大,功耗也越大。比如在USART串口通信中,若最大波特率只需115.2k,那用2M的速度就够了,既省电,噪声也小;而在SPI接口中,若使用18M或9M的波特率,则需要选用50M的GPIO的引脚速度。
注意:CKS32F407用于配置PA13输出速度的GPIOA_OSPEEDR寄存器的端口 13配置位(GPIOA_OSPEEDR_OSPEEDR13[1:0])初始值为00,即PA13的I/O输出速度默认为低速。因此客户在实际使用CKS32F407时,要按照参考手册中的介绍来配置GPIOA_OSPEED寄存器中 OSPEEDR13[1:0]位,从而来选择PA13的I/O输出指定的速度。
接下来我们讲解如何利用CKS32F4xx系列固件库对GPIO口的工作模式进行配置。首先,固件库中定义了一个如下的结构体:
typedef struct
{
uint32_t GPIO_Pin;
GPIOMode_TypeDef GPIO_Mode;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOOType_TypeDef GPIO_OType;
GPIOPuPd_TypeDef GPIO_PuPd;
}GPIO_InitTypeDef;
通过对该结构体成员中各个变量的初始化,就可以完成对2.2小节中所讲的GPIO口的工作模式配置。结构体中各个成员变量的介绍及初始化时可被赋的值如下:
1)GPIO_Pin:用来选择要控制的GPIO引脚,在标准库函数中可选择的值及其定义如下:
#define GPIO_Pin_0 ((uint16_t)0x0001)
#define GPIO_Pin_1 ((uint16_t)0x0002)
#define GPIO_Pin_2 ((uint16_t)0x0004)
#define GPIO_Pin_3 ((uint16_t)0x0008)
#define GPIO_Pin_4 ((uint16_t)0x0010)
#define GPIO_Pin_5 ((uint16_t)0x0020)
#define GPIO_Pin_6 ((uint16_t)0x0040)
#define GPIO_Pin_7 ((uint16_t)0x0080)
#define GPIO_Pin_8 ((uint16_t)0x0100)
#define GPIO_Pin_9 ((uint16_t)0x0200)
#define GPIO_Pin_10 ((uint16_t)0x0400)
#define GPIO_Pin_11 ((uint16_t)0x0800)
#define GPIO_Pin_12 ((uint16_t)0x1000)
#define GPIO_Pin_13 ((uint16_t)0x2000)
#define GPIO_Pin_14 ((uint16_t)0x4000)
#define GPIO_Pin_15 ((uint16_t)0x8000)
#define GPIO_Pin_All ((uint16_t)0xFFFF)
2) GPIO_Mode:用来设置已经选择的GPIO引脚的模式,在标准库函数中可选择的值及其定义如下:
typedef enum
{
GPIO_Mode_IN = 0x00, /*!设置为输入模式 */
GPIO_Mode_OUT = 0x01, /*!设置为输出模式*/
GPIO_Mode_AF = 0x02, /*!设置为复用模式 */
GPIO_Mode_AN = 0x03 /*!设置为模拟模式*/
}GPIOMode_TypeDef;
3) GPIO_Speed:用来设置已经选择的GPIO引脚的速度,在标准库函数中可选择的值及其定义如下:
#define GPIO_Speed_2MHz GPIO_Low_Speed
#define GPIO_Speed_25MHz GPIO_Medium_Speed
#define GPIO_Speed_50MHz GPIO_Fast_Speed
#define GPIO_Speed_100MHz GPIO_High_Speed
4) GPIO_OType:用来设置已经选择的GPIO引脚的输出模式,只有输出模式才需要该配置,输入模式下不需要该配置。在标准库函数中可选择的值及其定义如下:
typedef enum
{
GPIO_OType_PP = 0x00, /*!设置为推挽输出模式 */
GPIO_OType_OD = 0x01 /*!设置为开漏输出模式 */
}GPIOOType_TypeDef;
5) GPIO_PuPd:用来设置已经选择的GPIO引脚的上下拉,在标准库函数中可选择的值及其定义如下:
typedef enum
{
GPIO_PuPd_NOPULL = 0x00, /*!设置为既不上拉也不下拉/浮空模式 */
GPIO_PuPd_UP = 0x01, /*!设置为上拉模式*/
GPIO_PuPd_DOWN = 0x02 /*!设置为下拉模式*/
}GPIOPuPd_TypeDef;
根据上面所讲解的配置方法,我们讲解标准库下的3个实际配置实例。
1) 作为普通的GPIO口输出,控制LED灯的亮灭,其GPIO口初始化函数如下:
void LED_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);//使能GPIOF时钟
GPIO_Pin = GPIO_Pin_2;//LED1对应的IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//速度100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIO
}
2) 复用为CAN外设的输出。
void CAN1_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能PORTA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11| GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA11,PA12
GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1); //GPIOA11复用为CAN1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1); //GPIOA12复用为CAN1
3) 当ADC采集的输入通道,作为普通模拟输入。
void ADC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PA5 通道5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;//模拟输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//不带上下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化
}
好了,关于中科芯的这个407芯片的介绍就到这里了,我用这个407做带4G通信功能的20KW的直流充电桩主控,函数的名字都是一样的,比如时钟配置,GPIO口的配置,中断服务函数等等,跟stm32都是一样的,可以直接调用软件。烧录之后,上电调试一下,看下功能测试情况,我的经验是烧录成功,调试灯亮,基本没有问题。如有问题,可以看下时钟配置的参数,具体见上面详细图解。
之前的原理图已经画好,目前PCBA已经做好,芯片也焊接上去了,程序也烧录进去了,具体调试看后面整机通电之后的情况,这里就不详细赘述了。用下来的心得体会是直接替换,价格便宜,国内品牌的交货期也可以保证。最后申明一点,研发真的废脑力,我也没有时间和精力去和其他国产品牌MCU比较,只是分享下我用CKS32F407的心得体会,给大家一个参考,具体方案的选择大家自己决定。大家若觉得有点参考,请关注收藏点赞,给我动力,更多的去分享,谢谢!