中科芯
嵌入式开发软件和服务的全球领导者IAR今日宣布,与中科芯集成电路有限公司(以下简称中科芯)达成生态合作,IAR已全面支持CKS32系列MCU的应用开发。这一合作将进一步推动嵌入式系统的发展,并为开发者提供更完整、高效的开发解决方案。
IAR Embedded Workbench集成开发环境一直是全球众多开发者首选的嵌入式软件开发解决方案之一。这一强大的工具套件已全面支持中科芯CKS32系列MCU,为开发者提供了无与伦比的支持。通过该解决方案,开发者可以充分利用代码优化功能,同时还享有一系列强大的调试功能,包括代码和数据断点、运行时堆栈分析、调用堆栈可视化等。此外,IAR Embedded Workbench还整合了静态代码分析工具C-STAT以及动态代码分析工具C-RUN,可帮助开发者及早发现潜在问题,从而提高代码质量。不仅如此,IAR还提供经过TÜV SÜD认证的功能安全版本,满足ISO 26262等功能安全认证标准,为开发功能安全产品的开发者提供了强有力的支持。
中科芯是国内一流的科研单位和集成电路骨干企业,中科芯MCU事业部专注于32位MCU芯片研发、生产和销售,已批量供货多个系列MCU产品。目前中科芯的CKS32系列MCU已发展成18个产品序列,涵盖了超过100款量产型号,以其卓越的运算性能、兼容性、使用体验以及全产业链的品质和生命周期管控能力而闻名于业内。这些MCU广泛应用于高端消费电子、能源管理、汽车电子、工业控制、电机驱动、智能电表、医疗电子、安防、无线通信等对安全性和可靠性要求极高的领域。
中科芯MCU事业部总经理胡凯先生表示:“我们非常高兴与IAR建立合作伙伴关系。这一合作将有助于中科芯MCU更好地满足国内客户的需求,同时为符合ISO26262功能安全要求的车规级MCU芯片客户提供更高效、便捷和安全的MCU代码调试和优化支持。中科芯MCU将继续与行业生态伙伴紧密合作,构建完整的生态环境,为客户提供从芯片硬件到软件算法、从参考方案到系统设计的全方位支持。”
IAR亚太区副总裁Kiyo Uemura表示:“我们对与中科芯的合作感到非常高兴。这次合作为中国开发者提供了高质量的开发工具,以及原厂技术支持。我们将中国视为全球最重要的市场,深知这里充满潜力和机遇。我们将继续与中国本地厂商紧密合作,共同构建嵌入式领域的繁荣生态系统,为本地客户提供世界一流的技术支持和解决方案,以推动中国嵌入式产业的蓬勃发展。”
中科芯和IAR共同期待为嵌入式开发者和最终用户提供更丰富的资源和支持,为中国嵌入式领域带来更多创新和突破。
关于中科芯
中科芯集成电路有限公司位于集成电路发祥地、国家微电子工业南方基地、风景秀丽的无锡太湖之滨。公司现有职工5000余人,现有南京、武汉、西安分公司,北京、上海、深圳、成都和厦门研发中心,已形成无锡一总部、四基地、外地N研发中心的区位发展布局。中科芯MCU事业部专注于32位MCU芯片研发、生产和销售工作。目前MCU事业部已成功完成数百余MCU产品的设计及推广,已批量供货多个系列MCU产品,部分型号已通过AEC-Q100车规认证,以满足客户及市场多领域、多层次的丰富应用场景需求。如需了解详情,请访问:www.cks.cetc.com.cn
关于IAR
IAR为嵌入式开发提供世界领先的软件和服务,帮助世界各地的公司创造满足当前需求和未来趋势的创新产品。自1983年以来,IAR的嵌入式开发和嵌入式安全解决方案已被用在100多万个嵌入式应用的开发中,保证了其质量、可靠性和效率。IAR总部位于瑞典,并在世界各地设有销售分公司和支持办事处。在中国,IAR设立了经验丰富的应用工程师支持团队,向客户提供快速、专业、本地化的技术支持服务,持续为客户创造最大价值。更多信息,请访问:www.iar.com
在本章节中,首先向大家介绍电源系统结构,接着简述三种低功耗模式,最后重点介绍CKS32F4xx系列如何进入STOP模式以及使用按键实现中断唤醒。
CKS32F4xx电源系统介绍
CKS32F4xx系列的电源系统框图
CKS32F4xx系列的器件工作电压(VDD)为1.8~3.6V。
①备份域电路:包含LSE振荡器、RTC、备份寄存器及备份SRAM。电源开关自动切换VDD和VBAT供电。
②调压器电路(1.2V域):为备份域及待机电路外的所有数字电路供电,其中包括内核、数字外设及RAM。
③ADC电路和参考电压:ADC工作电源使用VDDA引脚输入,使用VSSA作为独立的地连接,VREF引脚则为ADC提供测量使用的参考电压。
低功耗模式简介
CKS32F4xx系列按功耗由高到低排列,分别是运行、睡眠、停止(STOP)和待机(STANDBY)四种工作模式。上电复位后,CKS32F4xx系列会处于正常运行状态,当不需要继续运行内核时,就可以选择进入后面的三种低功耗模式降低功耗。这三种模式中,电源消耗不同、唤醒时间不同、唤醒源不同,用户需要根据应用需求,选择最佳的低功耗模式。其中,最低功耗的是待机模式,停机模式(STOP)是次低功耗的,最后是睡眠模式。低功耗相关寄存器主要有SCB_SCR、PWR_CR、PWR_CSR,进入各种低功耗需要调用的指令有WFI或WFE,以下做简要介绍。
1.SCB_SCR(系统控制寄存器)
若进入停止模式or待机模式,需将SLEEPDEEP置1。
2.PWR_CR(电源控制寄存器)
若进入停止模式,需将PDDS清0,LPDS选调节器模式;若进入待机模式,需将PDDS置1,清除唤醒位CWUF。
3.PWR_CSR(电源控制/状态寄存器)
若芯片处于待机模式下,需使用使用WKUP引脚唤醒并需要清除WUF标记位。
注:WKUP上升沿才能唤醒待机状态,清除WUF标记位实则需操作CWUF位。
4.WFI与WFE指令
实质上都是内核指令,在库文件core_cmInstr.h中把这些指令封装成了函数,调用它们都能进入低功耗模式。调用时,需要使用函数的格式“__WFI();”和“__WFE();”,这是因为__wfi及__wfe是编译器内置的函数,函数内部使用调用了相应的汇编指令。其中WFI指令决定了它需要用中断唤醒,而WFE则决定了它可用事件来唤醒。
以下表格是对三种低功耗模式的简要概述。
本章中,我们主要对CKS32F4xx系列的低功耗模式—停止模式(STOP)做详细介绍。该模式中,由于其1.2V区域的部分电源没有关闭,会保留内核的寄存器、内存的信息,所以从STOP模式唤醒,并重新开启时钟后,还可以从上次停止处继续执行代码。停止模式可以由任意一个外部中断(EXTI)唤醒。在停止模式中可以选择电压调节器为开模式或低功耗模式,可选择内部FLASH工作在正常模式或掉电模式。那我们如何进入STOP模式,可以按照下述表格中的步骤执行即可:
CKS32F4xx系列标准库把进入STOP模式这部分的操作封装到PWR_EnterSTOPMode函数中了,并且需要注意的是进入STOP模式后,CKS32F4xx系列的所有IO都保持在停止前的状态,且当它被唤醒时,CKS32F4xx系列使用HSI作为系统时钟运行,由于系统时钟会影响很多外设的工作状态,所以一般我们在唤醒后会重新开启HSE,把系统时钟设置为原来的状态。上面表格也提到在停止模式中,还可以控制FLASH的供电,使用库函数PWR_FlashPowerDownCmd配置进入掉电状态还是正常供电状态,本质上是封装了一个对FPDS寄存器位操作的语句,使用时需要再进入停止模式前被调用,可以进一步降低功耗。
采用EXTI唤醒STOP模式实验
程序设计主要要点如下:
① 初始化用于唤醒的中断按键;
② 配置不用的I/O端口;
③ 设置停滞状态是FLASH掉电以及选择电压调节器的工作模式并进入停止状态;
④ 使用按键中断唤醒芯片;
⑤ 重启HSE时钟,使系统完全恢复停止之前的状态。
1)初始化用于唤醒的中断按键
此处选择连接PB1引脚的按键用于将芯片从STOP模式唤醒,代码如下:
void CKS_EXTI_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); /* Configure PB1 as EXTI */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStructure); /* Configure PB1 to EXTI line 1 */ SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB,EXTI_PinSource1); /* EXTI configuration */ EXTI_InitStructure.EXTI_Line = EXTI_Line1; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); /* NVIC configuration */ NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
在CKS_EXTI_Init函数中,首先使能GPIOB时钟,又因为用到外部中断,所以必须先使能SYSCFG时钟,接着对GPIOB初始化为上拉输入,并调用函数 SYSCFG_EXTILineConfig配置GPIOB.1连接到中断线1,最后初始化EXTI中断线以及NVIC中断优先级。
void EXTI1_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line1) != RESET) { EXTI_ClearITPendingBit(EXTI_Line1); } }
在中断服务函数EXTI1_IRQHandler内,主要是清除LINE1上的中断标志位。
2)配置未使用的I/O口
进入STOP模式之前,需要对I/O进行处理,若不处理很多配置为输入浮空的I/O口在受到外界干扰的时候,状态不定,消耗大量的电流,代码如下:
void CKS_Stop_Mode_IO_Set(void) { /* set pins not used to AIN */ GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOI, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_Init(GPIOD, &GPIO_InitStructure); GPIO_Init(GPIOF, &GPIO_InitStructure); GPIO_Init(GPIOG, &GPIO_InitStructure); GPIO_Init(GPIOH, &GPIO_InitStructure); GPIO_Init(GPIOI, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = (~GPIO_Pin_1); GPIO_Init(GPIOB, &GPIO_InitStructure); }
由上述代码可知,我们将没用到的I/O全部设置为模拟输入,保留PB1作为按键,产生外部中断来将芯片从STOP模式唤醒。
3)芯片进入STOP模式
该部分代码一般是程序执行一段时间再调用,我们先使用PWR_FlashPowerDownCmd配置停止模式下FLASH使用掉电模式,随后调用PWR_EnterSTOPMode把调压器设置在低功耗模式,最后使用WFI指令进入停止状态。由上文可知,WFI进入停止模式可由任意的EXTI的中断唤醒,所以此处使用按键中断唤醒是可行的,代码如下:
void CKS_Set_Stop_Mode(void) { PWR_FlashPowerDownCmd (ENABLE); PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFI); }
4)主函数配置
本例程中主函数主要对上文所述函数调用,程序编译下载至开发板,先进行相关外设初始化后,直接进入STOP模式,此时按下按键,芯片可立即被唤醒,随即又进入STOP模式,循环往复,主函数代码如下:
int main(void) { GPIO_Configuration(); CKS_EXTI_Init(); CKS_Stop_Mode_IO_Set(); while (1) { CKS_Stop_Mode_IO_Set(); CKS_Set_Stop_Mode(); SystemInit(); Delay(0xffffff); } }
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
比较输出简介
比较输出可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形,简称脉宽调制,这也是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制,PWM原理如下图所示:
图中,我们假定定时器工作在向上计数PWM模式,且当CNT<ccrx <="" font="">时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的 PWM,示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变ARR的值,就可以改变PWM输出的频率,这就是 PWM输出的原理。
CKS32F4的定时器除了TIM6和7。其他的定时器都可以用来产生PWM输出。其中高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出。这里仅使用TIM14的CH1产生一路PWM输出。要使CKS32F4的通用定时器TIMx 产生PWM输出,除了上一章介绍的寄存器外,我们还会用到3个寄存器,来控制PWM的。这三个寄存器分别是:
捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。对于捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx _CCMR1和TIMx _CCMR2,不过TIM14只有一个。TIMx_CCMR1控制CH1和2,而TIMx_CCMR2控制CH3和4。以下我们将以TIM14为例进行介绍。TIM14_CCMR1寄存器各位描述如下图所示:
该寄存器的有些位在不同模式下,功能不一样,所以在图中,我们把寄存器分了2层,上面一层对应输出而下面的则对应输入。关于该寄存器的详细说明,请参考《CKS32F4xx
中文参考手册》这里我们需要说明的是模式设置位 OC1M,此部分由3位组成。总共可以配置成7种模式,我们使用的是PWM模式,所以这3位必须设置为110/111。
这两种PWM模式的区别就是输出电平的极性相反。另外CC1S用于设置通道的方向(输入/输 出)默认设置为0,就是设置通道作为输出使用。注意:这里是因为我们的TIM14只有1个通道,所以才只有第八位有效,高八位无效,其他有多个通道的定时器,高八位也是有效的,具体请参考《CKS32F4xvx 中文参考手册》对应定时器的寄存器描述。
接下来,我们介绍TIM14的捕获/比较使能寄存器(TIM14_CCER),该寄存器控制着各个输入输出通道的开关。该寄存器的各位描述如下图所示:
该寄存器比较简单,我们这里只用到了CC1E位,该位是输入/捕获1输出使能位,要想PWM从IO口输出,这个位必须设置为1,所以我们需要设置该位为1。因为TIM14只有1个通道,所以才只有低四位有效,如果是其他定时器,该寄存器的其他位也可能有效。
最后,我们介绍一下捕获/比较寄存器(TIMx_CCR1~4),该寄存器总共有4个,对应4 个通道CH1~4。不过TIM14只有一个,即:TIM14_CCR1,该寄存器的各位描述如下图所示:
在输出模式下,该寄存器的值与CNT的值比较,根据比较结果产生相应动作。利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。
如果是通用定时器,则配置以上三个寄存器就够了,但是如果是高级定时器,则还需要配置:刹车和死区寄存器(TIMx_BDTR),该寄存器各位描述如下图所示:
该寄存器,我们只需要关注最高位:MOE位,要想高级定时器的PWM正常输出,则必须设置MOE位为1,否则不会有输出。注意:通用定时器不需要配置这个。其他位我们这里就不详细介绍了。
PWM实际跟上一章节一样使用的是定时器的功能,所以相关的函数设置同样在库函数文件 CKS32f4xx_tim.h和CKS32f4xx_tim.c文件中。
1)开启TIM14和GPIO时钟,配置PF9选择复用功能AF9(TIM14)输出
要使用TIM14,我们必须先开启TIM14的时钟,这点相信大家看了这么多代码,应该明白了。这里我们还要配置PF9为复用(AF9)输出,才可以实现TIM14_CH1的PWM经过PF9输出。库函数使能 TIM14时钟的方法是:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE); //>>TIM14 时钟使能
这在前面章节已经提到过。当然,这里我们还要使能GPIOF的时钟。然后我们要配置PF9引脚映射至AF9,复用为定时器14,调用的函数为:
GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14); //>>GPIOF9 复用为定时器 14
这个方法跟我们串口实验讲解一样,调用的同一个函数,最后设置PF9为复用功能输出这里我们只列出GPIO初始化为复用功能的一行代码:
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //>>复用功能
这里还需要说明一下,对于定时器通道的引脚关系,大家可以查看CKS32F4对应的数据手册,比如我们PWM实验,我们使用的是定时器14的通道1,对应的引脚PF9可以从数据手册表中查看:
2)初始化TIM14,设置TIM14的ARR和PSC等参数
在开启了TIM14的时钟之后,我们要设置ARR和 PSC两个寄存器的值来控制输出PWM的周期。这在库函数是通过TIM_TimeBaseInit函数实现的,在上一节定时器中断章节我们已经有讲解,这里就不详细讲解,调用的格式为:
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化 TIMx
3)设置TIM14_CH1的PWM模式,使能TIM14的 CH1输出
设置TIM14_CH1为PWM模式(默认是冻结的)通过配置TIM14_CCMR1的相关位来控制TIM14_CH1的模式。在库函数中,PWM通道设置是通过函数 TIM_OC1Init()~TIM_OC4Init()来设置的,不同的通道的设置函数不一样,这里我们使用的是通道1,所以使用的函数是TIM_OC1Init()。
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
这种初始化格式大家学到这里应该也熟悉了,所以我们直接来看看结构体TIM_OCInitTypeDef的定义:
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值 typedef struct { uint16_t TIM_OCMode; uint16_t TIM_OutputState; uint16_t TIM_OutputNState; */ uint16_t TIM_Pulse; uint16_t TIM_OCPolarity; uint16_t TIM_OCNPolarity; uint16_t TIM_OCIdleState; uint16_t TIM_OCNIdleState; } TIM_OCInitTypeDef;
这里我们讲解一下与我们要求相关的几个成员变量:参数TIM_OCMode设置模式是PWM还是输出比较,这里我们是PWM模式。
参数TIM_OutputState用来设置比较输出使能,也就是使能PWM输出到端口。
参数TIM_OCPolarity用来设置极性是高还是低。其他的参数TIM_OutputNState,TIM_OCNPolarity,TIM_OCIdleState和 TIM_OCNIdleState是高级定时器才用到的。要实现我们上面提到的场景,方法是:
TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //>>选择模式 PWM TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //>>比较输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //>>输出极性低 TIM_OC1Init(TIM14, &TIM_OCInitStructure); //>>根据指定的参数初始化外设TIM1 4OC1
4)使能TIM14
在完成以上设置了之后,我们需要使能TIM14。使能TIM14的方法前面已经讲解过:
TIM_Cmd(TIM14, ENABLE); //>>使能 TIM14
5)修改TIM14_CCR1来控制占空比
最后,在经过以上设置之后,PWM其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改TIM14_CCR1则可以控制 CH1的输出占空比。在库函数中,修改TIM14_CCR1占空比的函数是:
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare2);
理所当然,对于其他通道,分别有一个函数名字,函数格式为TIM_SetComparex(x=1,2,3,4)。
通过以上5个步骤,我们就可以控制TIM14的CH1输出PWM波了。这里特别提醒一下大家,高级定时器虽然和通用定时器类似,但是高级定时器要想输出PWM,必须还要设置一个MOE位(TIMx_BDTR 的第15位),以使能主输出,否则不会输出PWM。库函数设置的函数为:
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState)
代码示例
TIM3时钟来自于APB1域,我们通过APB1总线下的时钟使能函数来使能TIM3的时钟。调用的函数是:
//>>TIM14 PWM 部分初始化 //>>PWM 输出初始化 //>>arr:自动重装值 psc:时钟预分频数 void TIM14_PWM_Init(u32 arr,u32 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE);//TIM14 时钟使能 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); //使能 PORTF 时钟 GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14); //GF9 复用为 TIM14 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //GPIOF9 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度 50MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOF,&GPIO_InitStructure); //初始化 PF9 TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInit(TIM14,&TIM_TimeBaseStructure); //初始化定时器 14 //初始化 TIM14 Channel1 PWM 模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM 调制模式 1 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性低 TIM_OC1Init(TIM14, &TIM_OCInitStructure); //初始化外设 TIM1 4OC1 TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable); //使能预装载寄存器 TIM_ARRPreloadConfig(TIM14,ENABLE);//ARPE 使能 TIM_Cmd(TIM14, ENABLE); //使能 TIM14 TIM_SetCompare1(TIM14,300); //>>设置pwm的占空比为300/500 = 60% }
此部分代码包含了上面介绍的PWM输出设置的前5个步骤。这里我们关于TIM14的设置就不再说了。接下来,我们看看主程序里面的main函数如下:
int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//>>设置系统中断优先级分组 2 delay_init(168); //>>初始化延时函数 TIM14_PWM_Init(500-1,84-1); //>>定时器时钟为 84M,分频系数为 84,所以计数频率 //>>为 84M/84=1Mhz,重装载值 500,所以 PWM 频率为 1M/500=2Khz. while(1) { } }
这里,我们先设置好了NVIC终端优先级,然后初始化延时函数和timer,在timer的初始化参数中我们把PWM的频率设置成2K,将占空比设置成60%,完成PWM输出设置。
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
这一课我们将介绍CKS32F4XX系列产品的定时器使用,CKS32F4XX的定时器功能十分强大,包含2个高级控制定时器,8个普通定时器,2个基础定时器,以及两个看门狗定时器和一个系统定时器,总共15个定时器之多。关于定时器部分内容的讲解我们将分3个部分展开,本节将介绍定时器的基本特征和定时操作。
1、计数器分辨率:指定时器一个计数周期,例如同样是84Mhz的工作时钟,
对于TIM2,其分辨率的范围为:1*(1000ns/ 84)~(2^32)*(1000ns/84);
对于TIM3,其分辨率的范围为:1*(1000ns/84)~(2^16)*(1000ns/84)。
2、计数器类型:这个参数按照计数的方向来划分:
向上计数指的是从0开始到1,2...直到自己设置的计数上限值N,达到后再次从0开始计数,周而复始;
向下计数指的是从设置的计数上限值N开始到N-1,N-2,...直到0,达到后再次从N开始计数,周而复始;向上向下计数指的是从0,1,2...N,然后再从N,N-1,N-2...0,周而复始。
3、预分频系数:可以通过设置该系数来配置时基,如定时器工作在84Mhz下,配置不分频则一个计数时基为11.9ns,配置成2分频则一个计数时基为23.8ns。
4、产生DMA请求:定时器的更新会发出DMA请求,这是因为在DMA通道中为Timer预留了一个通道。
5、捕获比较通道:捕获就是定时器可以捕捉到通道的上升沿或者下降沿信号,比较就是定时器可以将计数器的值和装载值做比较,关于这部分将会在下后续章节展开。
6、互补输出:互补输出指的是输出的两个通道两个波形完全相反,通常运用在桥式电路中的互补PWM输出,这一部分将在后续章节展开。
7、最大接口时钟和最大工作时钟:定时器的时钟来源是APB,通过APB预分频器的配置,最大工作时钟可以是PCLKx的两倍。
CKS32F4XX定时器的定时操作
定时器的定时操作原理其实很简单,就像家里用的微波炉一样,需要加热食物时,先设定一个加热时间,然后按下开关,开始计时,当达到我们设置的定时时间以后,微波炉就会停止工作,并会有一个声音提示我们,定时时间到了。当然,在完成定时操作之前,必须要对Timer进行一些配置,下面我们以timer3为例,为大家演示。
1、Timer3时钟使能
TIM3时钟来自于APB1域,我们通过APB1总线下的时钟使能函数来使能TIM3的时钟。调用的函数是:
//>>使能 TIM3 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);typedefstruct
2、定时器参数初始化
在库函数中定时器的初始化通过TIM_TimeBaseInit实现的:
void TIM_TimeBaseInit(TIM_TypeDef *TIMx, TIM_TimeBaseInitTypeDef * TIM_TimeBaseInitStruct);
参数结构体指针,结构体类型为TIM_TimeBaseInitTypeDef,下面是结构体的定义:
typedef struct { uint16_t TIM_Prescaler; uint16_t TIM_CounterMode; uint16_t TIM_Period; uint16_t TIM_ClockDivision; uint8_t TIM_RepetitionCounter; } TIM_TimeBaseInitTypeDef;typedefstruct
这个结构体一共有5个成员变量,要说明的是,对于通用定时器只有前面四个参数有用,最后参数TIM_RepetitionCounter是高级定时器才有用的,后续章节会详解,在此不赘述。
第一个参数TIM_Prescaler是用来设置分频系数的,对应上表中的预分频系数。
第二个参数TIM_CounterMode是用来设置计数方式,如上表所述,可以设置为向上计数,向下计数方式还有向上\向下计数(中央对齐计数)方式,比较常用的是向上计数TIM_CounterMode_Up和向下计数 TIM_CounterMode_Down。
第三个参数是设置自动重载计数周期值,可以通俗的理解成要定时的次数,这个是根据定时时间和时基做除法换算得到的,比如定时器现在计数1次,时间经过了250ns,要定时100us,那自动重载计数周期值为400。
第四个参数是用来设置时钟分频因子,这个参数与定时器的其他功能有密切,本节操作先按照TIM_CKD_DIV1来配置(不分频)。
针对TIM3初始化范例代码格式:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period=5000; TIM_TimeBaseStructure.TIM_Prescaler=7199; TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
3、设置TIM3_DIER允许更新中断
因为我们要达到定时时间到后有一个到时提醒的效果,这就需要用到TIM3的更新中断,在库函数里面定时器中断使能是通过TIM_ITConfig函数来实现的:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
第一个参数是选择定时器号,取值为 TIM1~TIM17。
第二个参数非常关键,是用来指明我们使能的定时器中断的类型,定时器中断的类型有很多种,包括更新中断TIM_IT_Update,触发中断TIM_IT_Trigger,以及输入捕获中断等等。
第三个参数就很简单了,就是失能还是使能。
例如我们要使能TIM3的更新中断,格式为:
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
4、TIM3中断优先级设置
在定时器中断使能之后,因为要产生中断,必不可少的要设置NVIC(向量中断控制器)来设置中断优先级。关于NVIC_Init函数实现中断优先级的设置请到NVIC章节参考,这里就不重复讲解。
5、使能TIM3
配置好定时器后,不要忘记开启定时器,就像按下微波炉的开关一样,定时器才会进入工作状态,在固件库里面使能定时器的函数是通过TIM_Cmd函数来实现的:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
这个函数非常简单,比如我们要使能定时器3,方法为:
//>>使能 TIMx 外设 TIM_Cmd(TIM3, ENABLE);
6、编写中断服务函数
最后,要编写定时器中断服务函数,类似于听到微波炉结束工作的声音后,我们需要进行把加热的食物取出或者继续加热等操作,通过该函数来处理定时器产生的相关中断。在中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。然后执行相关的操作,我们这里使用的是更新(溢出)中断,在处理完中断之后应,该向TIM3_SR的最低位写0,来清除该中断标志,在固件库函数里面,用来读取中断状态寄存器的值判断中断类型的函数是:ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t)
该函数的作用是,判断定时器TIMx的中断类型 TIM_IT是否发生中断。比如,我们要判断定时器3 是否发生更新(溢出)中断,方法为:
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){} IT_Update) != RESET){}
固件库中清除中断标志位的函数是:
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)
该函数的作用是,清除定时器TIMx的中断TIM_IT 标志位。使用起来非常简单,比如我们在TIM3的溢出中断发生后,我们要清除中断标志位,方法是:
TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
这里需要说明一下,固件库还提供了两个函数用来判断定时器状态以及清除定时器状态标
志位的函数TIM_GetFlagStatus 和TIM_ClearFlag,他们的作用和前面两个函数的作用类似。只 是在TIM_GetITStatus函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而TIM_GetFlagStatus 直接用来判断状态标志位。通过以上几个步骤,我们就可以达到我们的目的了,使用通用定时器的更新中断,来实现定时并产生定时中断信号。
代码实例
/**通用定时器3中断初始化
>>arr:自动重装值。psc:时钟预分频数
>>定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
>>Ft=定时器工作频率,单位:Mhz
这里使用的是定时器3**/
void TIM3_Int_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; NVIC_InitTypeDef NVIC_InitStructure; //>> ①使能 TIM3 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //>>自动重装载值 TIM_TimeBaseInitStructure.TIM_Period = arr; //>>定时器分频 TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //>>向上计数模式 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; // ②初始化定时器 TIM3 TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); //③允许定时器 3 更新中断 TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //定时器 3 中断 NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //抢占优先级 1 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //响应优先级 3 NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; // ④初始化 NVIC NVIC_Init(&NVIC_InitStructure); //⑤使能定时器 3 TIM_Cmd(TIM3,ENABLE); } //⑥定时器 3 中断服务函数 void TIM3_IRQHandler(void) { //>>溢出中断 if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) { } //>>清除中断标志位 TIM_ClearITPendingBit(TIM3,TIM_IT_Update); }
这里列出了一个中断服务函数和一个定时器3中断初始化函数,中断服务函数中,在每次中断后,判断 TIM3的中断类型,如果中断类型正确,则执行自己需要执行的操作并清除中断标志,TIM3_Int_Init 函数就是执行我们上面介绍的那5个步骤,使得 TIM3开始工作,并开启中断。这里我们分别用标号①~⑤来标注定时器初始化的五个步骤。该函数的2 个参数用来设置TIM3的溢出时间。假设系统初始化 SystemInit函数里面已经初始化APB1的时钟为 4分频,所以APB1的时钟为42M,这也是timer3的最大接口时钟,而从CKS32F4的内部时钟树图得知:当APB1的时钟分频数为1的时候,TIM2~7以及TIM12~14的时钟为APB1的时钟,而如果APB1的时钟分频数不为1,那么TIM2~7以及TIM12~14的时钟频率将为APB1时钟的两倍。因此,TIM3的时钟为84M,再根据我们设计的arr和psc的值,就可以计算中断时间了。计算公式如下:
Tout=((arr+1)*(psc+1))/Tclk;
其中:
Tclk:TIM3的输入时钟频率(单位为Mhz)。
Tout:TIM3溢出时间(单位为us)。
本节我们介绍了CKS32F4XX各定时器概况,以及如何设置最基础的定时功能,包括开启定时器的时钟,配置定时器的时基,定时次数以及计数的方向等,此外定时器其他的功能如输入捕获,比较输出和PWM输出等功能将会在后续章节展开。
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
EXTI 简介
EXTI(External interrupt/event controller)—外部中断/事件控制器,管理了控制器的 23个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
EXTI 功能框图
EXTI的功能框图包含了 EXTI最核心内容,EXTI可分为两大部分功能,一个是产生中断,另一个是产生事件。EXTI功能框图如下:
中断/事件线
EXTI有23个中断/事件线,每个GPIO都可以被设置为输入线,占用EXTI0至EXTI15,还有另外七根用于特定的外设事件。
EXTI0至EXTI15用于GPIO,通过编程控制可以实现任意一个GPIO作为 EXTI的输入源。由上图可知,EXTI0可以通过AFIO的外部中断配置寄存器 1(AFIO_EXTICR1)的EXTI0[3:0]位选择配置为PA0、PB0、PC0、PD0、PE0、PF0、PG0、PH0 或者PI0,其他EXTI线(EXTI中断/事件线)使用配置都是类似的。
外设事件连接模式:
EXTI line 16 连接PVD输出
EXTI line 17 连接RTC闹钟事件
EXTI line 18 连接USB OTG FS唤醒事件
EXTI line 19 连接以太网唤醒事件
EXTI line 20 连接USB OTG HS唤醒事件
EXTI line 21 连接RTC 篡改和时标事件
EXTI line 22 RTC唤醒事件
EXTI 初始化结构体
标准库函数对每个外设都建立了一个初始化结构体,比如 EXTI_InitTypeDef,结构体成员用于设置外设工作参数,并由外设初始化配置函数,比如 EXTI_Init()调用,这些设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。
初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在 cks32f4xx_exti.h 文件中,初始化库函数定义在 cks32f4xx_exti.c 文件中,编程时我们可以结合这两个文件内注释使用。
EXTI操作流程
a)首先配置GPIO引脚模式:
RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); /* 配置GPIOA.0 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; /* 配置为输入模式 */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; /* 配置速率为高速模式 */ GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; /* 使能下拉模式 */ GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; /* 调用初始化函数 */ GPIO_Init(GPIOA, &GPIO_InitStructure);typedefstruct
b)配置EXTI line0中断线连接GPIOA.0:
typedefstrucSYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, GPIO_PinSource0);
c)配置中断线为中断模式,上升沿触发模式并使能:
EXTI_InitTypeDef EXTI_Structure; EXTI_StructInit(&EXTI_Structure); /* 配置中断线0 */ EXTI_Structure.EXTI_Line = EXTI_Line0; /* 使用中断模式 */ EXTI_Structure.EXTI_Mode = EXTI_Mode_Interrupt; /* 电压低于阀值时产生中断 */ EXTI_Structure.EXTI_Trigger = EXTI_Trigger_Raising; /* 使能中断线 */ EXTI_Structure.EXTI_LineCmd = ENABLE; /* 初始化中断 */ EXTI_Init(&EXTI_Structure);
d)配置中断管理器NVIC,设置EXTI0_IRQn中断源优先级并初始化:
Svoid NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; /* 嵌套向量中断控制器组选择 */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); /* 配置EXTI0_IRQn中断源 */ NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; /* 抢断优先级为1 */ NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; /* 子优先级为1 */ NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; /* 使能中断 */ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; /* 初始化配置NVIC */ NVIC_Init(&NVIC_InitStructure); } NVIC_PriorityGroupConfig(NVYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, GPIO_PinSource0);
e)当产生 EXTI0中断,在中断程序中进行相应的处理:
typvoid EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0) != RESET) { EXTI_ClearITPendingBit(EXTI_Line0); /* 添加用户处理代码 */ } } edefstruct
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
在上一讲,我们讲过CKS32F4xx系列的6个串口都支持DMA传输。因此本节我们对CKS32F4xx系列的DMA进行介绍,同时利用DMA对串口数据进行传输。
DMA介绍
DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
CKS32F4xx系列最多有2个DMA控制器(DMA1和DMA2),共16个数据流(每个控制器8个),每个数据流总共可以有多达8个通道(或称请求)。CKS32F4xx系列的DMA支持外设到存储器传输、存储器到外设传输和存储器到存储器传输三种传输模式。,存储器一般是指片内SRAM、外部存储器、片内Flash等等。
外设到存储器:把外设数据寄存器内容转移到指定的内存空间。比如进行ADC采集时我们可以利用DMA传输把AD转换的数据转移到我们定义的存储区中。
存储器到外设:把特定存储区内容转移至外设的数据寄存器中,这种多用于外设的发送通信,比如SPI、I2C和串口等。
存储器到存储器:就是把一个指定的存储区内容拷贝到另一个存储区空间。功能类似于C语言内存拷贝函数memcpy,不过利用DMA传输可以达到更高的传输效率。
注意:只有DMA2控制器支持存储器到存储器的传输,DMA1不支持。
DMA功能框图
标号1处是外设通道,每一个数据流(标号2)对应着8个外设通道,外设通道选择要解决的主要问题是决定哪一个外设作为该数据流的源地址或者目标地址。DMA1各个通道的请求映像如下表所示:
DMA2各个通道的请求映像如下表所示:
比如我们利用DMA将存储器数据传输到串口3,然后发送出去,那么根据上表可知,我们可以选择DMA1的数据流3通道4,或者DMA1的数据流4通道7。而将串口3的数据传输到存储器则可以选择DMA1的数据流1通道4。
标号2处是数据流和仲裁器,一个DMA控制器对应8个数据流,数据流包含要传输数据的源地址、目标地址、数据等等信息。如果我们需要同时使用同一个DMA控制器 (DMA1 或DMA2) 多个外设请求时,那必然需要同时使用多个数据流,这时就需要仲裁器对每一个数据流的优先级进行划分。数据流的优先级可以通过配置DMA_SxCR寄存器 PL[1:0]位,可以设置为非常高、高、中和 低四个级别。如果两个或以上数据流软件设置优先级一样,则他们优先级取决于数据流编号,编号越低越具有优先权,比如数据流2优先级高于数据流 3。
标号3处是FIFO,用于在源数据传输到目标地址之前临时存放数据用的。可以通过DMA数据流xFIFO控制寄存器DMA_SxFCR的FTH[1:0]位来控制FIFO的阈值,分别为1/4、1/2、3/4和满。如果数据存储量达到阈值级别时,FIFO内容将传输到目标中。
标号4和5处是存储器端口、外设端口。DMA控制器通过存储器端口和外设端口与存储器和外设进行数据传输。DMA2(DMA控制器2)的存储器端口和外设端口都是连接到AHB总线矩阵,可以使用AHB总线矩阵功能。而DMA1的存储区端口相比DMA2的要减少AHB2外设的访问权,同时DMA1外设端口是没有连接至总线矩阵的,只有连接到APB1外设,所以 DMA1不能实现存储器到存储器传输。
标号6处是编程端口,AHB从器件编程端口是连接至AHB2外设的,AHB2外设在使用 DMA传输时需要相关控制信号。
DMA软件配置
在标准库函数中对DMA进行初始化主要是对结构体DMA_InitTypeDef内的各类参数进行初始化。DMA_InitTypeDef结构体参数及介绍如下:
typedef struct { uint32_t DMA_Channel; uint32_t DMA_PeripheralBaseAddr; uint32_t DMA_Memory0BaseAddr; uint32_t DMA_DIR; uint32_t DMA_BufferSize; uint32_t DMA_PeripheralInc; uint32_t DMA_MemoryInc; uint32_t DMA_PeripheralDataSize; uint32_t DMA_MemoryDataSize; uint32_t DMA_Mode; uint32_t DMA_Priority; uint32_t DMA_FIFOMode; uint32_t DMA_FIFOThreshold; uint32_t DMA_MemoryBurst; uint32_t DMA_PeripheralBurst; }DMA_InitTypeDef;
1)DMA_Channel:DMA请求通道的选择,每个外设对应固定的通道,具体设置值需要查25.2节中的DMA1和DMA2各个通道的请求映像表。该值在标准库中可供选择的参数值如下:
#define DMA_Channel_0 ((uint32_t)0x00000000) #define DMA_Channel_1 ((uint32_t)0x02000000) #define DMA_Channel_2 ((uint32_t)0x04000000) #define DMA_Channel_3 ((uint32_t)0x06000000) #define DMA_Channel_4 ((uint32_t)0x08000000) #define DMA_Channel_5 ((uint32_t)0x0A000000) #define DMA_Channel_6 ((uint32_t)0x0C000000) #define DMA_Channel_7 ((uint32_t)0x0E000000)
2)DMA_PeripheralBaseAddr:外设地址,设定DMA_SxPAR寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储区地址。比如要进行串口DMA传输,那么外设基地址为串口接收发送数据存储器USART1->DR的地址,表示方法为&USART1->DR。
3)DMA_Memory0BaseAddr:存储器0地址,设定DMA_SxM0AR寄存器值;一般设置为我们自定义存储区的首地址。比如我们程序中自己定义的用来存放数据的数组,此时这个值设置为数组名即可。
4) DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设以及存储器到存储器。该值在标准库中可供选择的参数值如下:
DMA_DIR_PeripheralToMemory //外设到存储器 DMA_DIR_MemoryToPeripheral //存储器到外设 DMA_DIR_MemoryToMemory //存储器到存储器
5)DMA_BufferSize:设定待传输数据数目,初始化设定DMA_SxNDTR寄存器的值。
6)DMA_PeripheralInc:是否使能外设地址自动递增功能,它设定DMA_SxCR寄存器的PINC位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。该值在标准库中可供选择的参数值如下:
DMA_PeripheralInc_Enable //使能 DMA_PeripheralInc_Disable //不使能
7)DMA_MemoryInc:是否使能存储器地址自动递增功能,它设定DMA_SxCR寄存器的MINC位的值;我们自定义的存储区一般都是存放多个数据的,所以一般是使能存储器地址自动递增功能。该值在标准库中可供选择的参数值如下:
DMA_MemoryInc_Enable //使能 DMA_MemoryInc_Disable //不使能
8)DMA_PeripheralDataSize:外设数据宽度,可选字节(8位)、半字(16位)和字(32位),根据外设数据长度进行选择。该值在标准库中可供选择的参数值如下:
DMA_PeripheralDataSize_Byte //字节 DMA_PeripheralDataSize_HalfWord //半字 DMA_PeripheralDataSize_Word //字
9) DMA_MemoryDataSize:存储器数据宽度,可选字节(8位)、半字(16位)和字(32位),和外设数据宽度相对应。该值在标准库中可供选择的参数值如下:
DMA_MemoryDataSize_Byte //字节 DMA_MemoryDataSize_HalfWord //半字 DMA_MemoryDataSize_Word //字
10) DMA_Mode:DMA传输模式选择,可选一次传输或者循环传输。该值在标准库中可供选择的参数值如下:
DMA_Mode_Normal //一次传输 DMA_Mode_Circular //循环传输
11) DMA_Priority:软件设置数据流的优先级,有4个可选优先级分别为非常高、高、中和低。DMA优先级只有在多个DMA数据流同时使用时才有意义,如果只有一个数据流的话,设置成非常高优先级就可以了。该值在标准库中可供选择的参数值如下:
DMA_Priority_Low //低 DMA_Priority_Medium //中 DMA_Priority_High //高 DMA_Priority_VeryHigh //非常高
12) DMA_FIFOMode:FIFO模式使能,如果设置为DMA_FIFOMode_Enable 表示使能FIFO模式功能;如果采用直接传输模式,则不需要使用FIFO模式。直接模式下,DMA直接进行数据从源地址到目的地址的传输。而FIFO模式下,可以将要传输的多个数据(或字节)累计存储在FIFO缓冲器中,然后在FIFO缓冲器中设置存储阈值,当到达阈值时,FIFO会自动把所有存储的数据一次性的发送到目标地址。该值在标准库中可供选择的参数值如下:
DMA_FIFOMode_Disable //不使能 DMA_FIFOMode_Enable //使能
13) DMA_FIFOThreshold:FIFO阈值选择,可选4种状态分别为FIFO容量的1/4、1/2、3/4和满;不使用FIFO模式时,该设置改值无效。该值在标准库中可供选择的参数值如下:
DMA_FIFOThreshold_1QuarterFull //1/4 DMA_FIFOThreshold_HalfFull //1/2 DMA_FIFOThreshold_3QuartersFull //3/4 DMA_FIFOThreshold_Full //满
14) DMA_MemoryBurst:存储器突发模式选择,可选单次模式、4节拍的增量突发模式、8节拍的增量突发模式或16节拍的增量突发模式。单次传输模式下,一次操作(软件)只能传输一次,突发传输模式下,一次操作可以传输多次,如4次,8次,16次。该值在标准库中可供选择的参数值如下:
DMA_MemoryBurst_Single //单次 DMA_MemoryBurst_INC4 //4节拍 DMA_MemoryBurst_INC8 //8节拍 DMA_MemoryBurst_INC16 //16节拍
15) DMA_PeripheralBurst:外设突发模式选择,可选单次模式、4节拍的增量突发模式、8节拍的增量突发模式或16节拍的增量突发模式。该值在标准库中可供选择的参数值如下:
DMA_PeripheralBurst_Single //单次 DMA_PeripheralBurst_INC4 //4节拍 DMA_PeripheralBurst_INC8 //8节拍 DMA_PeripheralBurst_INC16 //16节拍
串口DMA接发通信实验
串口的DMA接发通信实验是存储器到外设和外设到存储器的数据传输。在第24课串口通信的基础上编写而成。
1.编程要点
1)配置USART通信功能;
2)配置DMA通信功能,存储器到外设和外设到存储器两种模式的配置。
3)使能指定的DMA数据流中断;
4)使能USART3的DMA发送和接收请求;
5)开始一次DMA传输。
2.代码分析
代码中有关串口的配置的程序在第24课已经详细讲过了,这里就不再讲述。主要是对DMA相关的代码进行分析,相关程序在dma.c文件里。
代码清单1:DMA初始化配置
void MYDMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); while (DMA_GetCmdStatus(DMA1_Stream3) != DISABLE){} //存储器到外设的DMA配置 DMA_DeInit(DMA1_Stream3); DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR);//DMA外设地址 DMA_InitStructure.DMA_Memory0BaseAddr =(uint32_t)USART3_TX_BUF;//DMA 存储器0地址 DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式 DMA_InitStructure.DMA_BufferSize = USART3_MAX_TX_LEN;//数据传输量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输 DMA_Init(DMA1_Stream3, &DMA_InitStructure);//初始化DMA Stream DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3); //清除标志位 DMA_Cmd(DMA1_Stream3, DISABLE); //关闭DMA DMA_ITConfig(DMA1_Stream3,DMA_IT_TC,ENABLE); NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE); //外设到存储器的DMA配置 DMA_DeInit(DMA1_Stream1); while (DMA_GetCmdStatus(DMA1_Stream1) != DISABLE){}//等待DMA可配置 DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR);;//DMA外设地址 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)USART3_RX_BUF;//DMA 存储器0地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//存储器到外设模式 DMA_InitStructure.DMA_BufferSize = USART3_MAX_RX_LEN;//数据传输量 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式 DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;//最高优先级 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输 DMA_Init(DMA1_Stream1, &DMA_InitStructure);//初始化DMA Stream DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1); //清除标志位 DMA_Cmd(DMA1_Stream1, ENABLE); //关闭DMA DMA_ITConfig(DMA1_Stream1,DMA_IT_TC,ENABLE); //使能DMA1数据流1的传输完成中断 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_DMACmd(USART3,USART_DMAReq_Rx,ENABLE); }
这段代码主要是根据第25.3节中讲解的内容对DMA进行配置的。串口发送数据我们利用的是DMA数据流3通道4,存储器是我们定义的一个数组USART3_TX_BUF,外设是串口的数据寄存器USART_DR,表示方法为&USART3->DR。串口接收数据我们利用的是DMA数据流1通道4,存储器是我们定义的一个数组USART3_RX_BUF,外设是串口的数据寄存器USART_DR,表示方法为&USART3->DR。
代码清单2:DMA1_Stream3_IRQHandler函数
void DMA1_Stream3_IRQHandler(void) //DMA发送数据流中断服务函数 { if(DMA_GetITStatus(DMA1_Stream3,DMA_IT_TCIF3) != RESET) { DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3); DMA_Cmd(DMA1_Stream3,DISABLE); //关闭DMA USART3_DMA_Tx_flag = 0; //发送完成 } }
该函数是DMA1数据流3中断服务函数,在函数里我们通过对DMA_IT_TCIF3标志位的判断,可以知道数据流3的数据是否传输完成,然后清除相应的标志位和关闭DMA传输。
代码清单3:DMA1_Stream1_IRQHandler函数
void DMA1_Stream1_IRQHandler(void) //DMA接收数据流中断服务函数 { if(DMA_GetITStatus(DMA1_Stream1,DMA_IT_TCIF1) != RESET) { DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1); //清除标志位 DMA_Cmd(DMA1_Stream1,DISABLE); //关闭DMA } }
该函数的功能和DMA1数据流3中断服务函数的功能是一样的。
代码清单4:USART3_IRQHandler函数
void USART3_IRQHandler(void) { if(USART_GetITStatus(USART3 , USART_IT_IDLE) != RESET) { USART_ReceiveData(USART3);//清除空闲中断标志 DMA_Cmd(DMA1_Stream1,DISABLE);//关闭DMA ,防止干扰 USART3_RX_STA = USART3_MAX_RX_LEN - DMA_GetCurrDataCounter(DMA1_Stream1);//获得接收到的字节数 DMA_SetCurrDataCounter(DMA1_Stream1,USART3_MAX_RX_LEN); DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1);//比f103多的一句 DMA_Cmd(DMA1_Stream1,ENABLE);// DMA 开启,等待数据。 USART3_RX_BUF[USART3_RX_STA&0X7FFF]='\0'; USART3_RX_STA|=0x8000; //最高位置1,标记接收完成了 } if(USART_GetFlagStatus(USART3,USART_FLAG_ORE) == SET) // 检查 ORE 标志,防止开关总中断死机,放在接收中断前面 { USART_ClearFlag(USART3,USART_FLAG_ORE); USART_ReceiveData(USART3); } }
该函数是串口3的中断服务函数。在函数里判断接收完成是通过串口空闲中断的方式实现,即当串口数据流停止后,就会产生IDLE中断。然后在中断里依次做了以下事情:处理接收buff的数据;关闭串口接收DMA通道,防止后面接收到数据产生串扰;计算接收到的字节数存储在USART3_RX_STA变量中;重新设置DMA下次要接收的数据字节数;清除DMA1数据流1接收完成标志位;开启DMA通道,等待下一次的数据接收;置位标志位USART3_RX_STA,表示接收完成了。同时为了防止串口溢出错误,程序卡死在串口中断里,在中断服务函数里增加了溢出错误的处理。
代码清单5:主函数
int main(void) { GPIO_Configuration(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2 USART_Configuration(); printf("start\r\n"); while (1) { if(USART3_RX_STA&0x8000) { //printf("USART3_Read:%s",USART3_RX_BUF); printf("USART3_Read:%s",USART3_RX_BUF); USART3_RX_STA=0; memset(USART3_RX_BUF,0,sizeof(USART3_RX_BUF)); } } }
主函数的编写逻辑比较简单,首先是各类外设的初始化,包括GPIO初始化、NVIC中断初始化、串口初始化。然后在while循环里等待接收完成标志置位,将接收到的数据再发送到串口调试助手,并利用memset函数将存储数据的数组USART3_RX_BUF清零。
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
GPIO(General-purpose input/output)是通用输入输出端口的简称,CKS32F4xx系列产品通过GPIO引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。最基本的输出功能是由CKS32F4xx系列产品控制引脚输出高、低电平,实现开关控制,如把GPIO引脚接入到LED灯,那就可以控制LED灯的亮灭,引脚接入到继电器或三极管,那就可以通过继电器或三极管控制外部大功率电路的通断。最基本的输入功能是检测外部输入电平,如把 GPIO引脚连接到按键,通过电平高低区分按键是否被按下。
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结构框图的右下角处,模拟信号直接输出到引脚。
GPIO的工作模式
- 4种输入模式 -
1.浮空输入
浮空输入状态下,IO的电平状态是不确定的,完全由外部输入决定。如果在该引脚悬空的情况下,读取该端口的电平是不确定的。
2.上拉输入
在该模式下,如果IO口外部没有信号输入或者引脚悬空,IO口默认为高电平。如果I/O口输入低电平,那么引脚就为低电平,MCU读取到的就是低电平。
3.下拉输入
在该模式下如果IO口外部没有信号输入或者引脚悬空,IO口默认为低电平,如果I/O口输入高电平,那么引脚就为高电平,MCU读取到的就是高电平。
4.模拟功能
当GPIO用于模拟功能时,引脚的上、下拉电阻是不起作用的,这个时候即使配置了上拉或下拉模式,也不会影响到模拟信号的输入输出。除了ADC和DAC要将IO配置为模拟功能模式之外,其他外设功能一律要配置为复用功能模式。
- 4种输出模式 -
1.开漏输出(带上拉或者下拉)
在该模式下,若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通信中,引脚通常设置为开漏输出模式。
2.复用开漏输出(带上拉或者下拉)
此时GPIO复用为其他外设,输出数据寄存器GPIOx_ODR无效;即输出的高低电平来源于其它外设,除了输出信号的来源改变之外,其他的与开漏输出功能相同。
3.推挽输出(带上拉或者下拉)
在该模式下,如果我们控制输出为0,低电平,则I/O端口的电平就是低电平。若控制输出为1,高电平,则I/O端口的电平就是高电平。此时,外部上拉和下拉的作用是控制在没有输出IO口的默认电平。在该模式下,施密特触发器也是打开的,即输入可用,通过输入数据寄存器GPIOx_IDR可读取I/O的实际状态。
4.复用推挽输出(带上拉或者下拉)
此时GPIO复用为其他外设,输出数据寄存器GPIOx_ODR无效;即输出的高低电平来源于其它外设,除了输出信号的来源改变之外,其他的与推挽输出功能相同。
- 4种输出速度 -
1. 2MHZ(低速)
2. 25MHZ(中速)
3. 50MHZ(快速)
4. 100MHZ(高速)
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输出指定的速度。
GPIO的配置
接下来我们讲解如何利用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_InitStructure.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);//初始化 }
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
作为MCU运行的基础,时钟是单片机各个模块工作时序的最小时间单位,推动单片机的各指令执行,是MCU选型的一个重要指标。CKS32F4xx系列产品具有众多的外设,但并非所有的外设均需要系统时钟的高频率,并且高时钟频率将导致功耗增加、抗电磁干扰能力变弱,因此,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系列系统时钟配置
在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系列外设时钟配置
在系统初始化之后,在使用部分外设时,我们还需要根据外设需求修改某些时钟源配置。在CKS32F4xx系列固件库中,时钟源的选择以及时钟使能等函数均在RCC相关固件库文件 cks32f4xx_rcc.h 和 cks32f
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState); void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState); void RCC_AHB3PeriphClockCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState); void RCC_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类时钟源。这里我们列出来几种重要的时钟源使能函数:
void RCC_HSICmd(FunctionalState NewState); void RCC_LSICmd(FunctionalState NewState); void RCC_PLLCmd(FunctionalState NewState); void RCC_PLLI2SCmd(FunctionalState NewState); void RCC_PLLSAICmd(FunctionalStateNewState); void RCC_RTCCLKCmd(FunctionalState NewState);
具体调用方法如下:
RCC_PLLCmd(ENABLE);
第二类时钟功能函数:时钟源选择和分频因子配置函数用来选择相应的时钟源以及配置相应的时钟分频系数,比如配置HSI、HSE、PLL三个中的一个时钟源为系统时钟。以下为几种时钟源配置函数:
void RCC_LSEConfig(uint8_t RCC_LSE); void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource); void RCC_HCLKConfig(uint32_t RCC_SYSCLK); void RCC_PCLK1Config(uint32_t RCC_HCLK); void RCC_PCLK2Config(uint32_t RCC_HCLK); void RCC_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);
第三类外设复位函数如下:
void RCC_AHB1PeriphResetCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);void RCC_AHB2PeriphResetCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);void RCC_AHB3PeriphResetCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
这类函数和前面讲解的外设时钟函数使用方法基本一致,不同的是一个是用来使能外设时钟,一个是用来复位对应的外设。对于这些时钟操作函数,我们就不一一列举出来,大家可以打开 RCC 对应的文件仔细了解。有问题欢迎邮件咨询:wangb@cksmcu.com.cn
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
随着中科芯MCU在市场上越来越多的被应用,为了更好的支持客户在使用中科芯32位MCU芯片时的仿真调试,中科芯MCU事业部推出了一款自主设计的USB编程调试工具—CKS-Debugger,稳定可靠、小巧便携、操作简单,适用于CKS32全系列MCU产品。支持CKS32 MCU在线仿真、调试和烧录,并提供虚拟串口。CKS-Debugger的推出进一步完善了CKS32 MCU开发生态体系,强化了CKS32系列微控制器高性能与易用性的产品优势,也为开发人员提供了便利性,提高开发效率。
CKS-Debugger是一个全功能的仿真调试器和编程器,集成了IDE在线仿真、在线编程和USB转串口三种主要功能。可通过USB 2.0全速接口连接到电脑主机,标准即插即用免安装驱动,并由SWD或JTAG接口连接到目标芯片进行调试编程。配备三色LED状态指示灯,可以显示上电、空闲、调试、在线编程等不同模式下的工作状态。
CKS-Debugger可以提供完整的CKS32全系列MCU产品调试和编程功能,包括芯片设置、单步调试、全速运行、FLASH断点设置、寄存器访问、Flash编程等操作,并兼容如Keil MDK、IAR等主流开发工具IDE。增强USB CDC虚拟串口功能使得开发人员不用再额外连接一个USB转串口调试工具,就可以在仿真的同时进行串口调试或参数读写。
CKS-Debugger支持型号表
随着CKS-Debugger编程调试工具的推出,中科芯MCU事业部将根据市场需求为CKS-Debugger增添更多的扩展功能,支持更多的MCU型号,并通过固件升级提供持续的支持服务,为用户带来更好的开发体验。
注:详细使用说明见附件CKS-Debugger仿真器使用说明书,扫码后可下载。
来源:中科芯MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。