CKS32F4xx

CKS32F4xx系列芯片内部嵌入一个FLASH,若FLASH存储了用户的应用程序后仍留有空间,剩余空间可被当作EEPROM使用,这比访问外部FLASH速度优势更为明显。在本章节中,将会向大家简要介绍内部FLASH,并通过一个例程来演示FLASH模拟EEPROM的操作。

内部FLASH简介

FLASH结合了ROM和RAM的优点,不仅具备了EEPROM的可擦写性能,还类似于失性随机存储器(NVRAM),不会因断电而丢失数据,且能以较快的速度读取数据。对于CKS32F4xx系列的FLASH接口,它负责管理CPU通过AHB I-Code和D-Code总线对FLASH进行访问,这个接口可以执行擦除和编程操作,同时实施了一些读写保护机制,以确保数据的安全性。此外,FLASH接口还通过指令预取和缓存机制来提高代码执行的速度。下图为系统架构内的FLASH接口连接图:

1.png

针对不同型号的CKS32F4xx系列,其FLASH容量在128K至1024K字节之间。本章中以开发板上搭载的CKS32F407VGT6为例,它的FLASH容量为1024K字节,下图是CKS32F40xx/41xx的闪存模块组织图:

2.png

由上图可知,CKS32F4的存储区主要是由主存储器、系统存储器、OTP区域和选项字节构成,各存储区简述如下:

①主存储器:该部分用来存放代码和数据常数,分为12个大小不同的扇区,主存储器的起始地址是0x08000000;

②系统存储器:主要用来存放CKS32F4的bootloader代码,此代码是出厂的时候就固化在芯片内部了,例如用串口下载程序时的bootloader(ISP下载),它专门用来给主存储器下载代码;

③OTP区域:即一次性可编程区域,一次性的,写完一次,永远不能擦除;

④选项字节:用于配置读保护、BOR级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位,相当于一些寄存器位。

内部FLASH读写操作

FLASH是以字节为最小单位进行操作,每个存储单元存放一个字节。以下是对内部FLASH读写操作的概述:

---内部FLASH指定地址读出数据

例如,从地址addr读取一个字节,可通过语句data = *(vu8*)addr来实现,若是读取一个半字或一个字,只将上面的vu8改vu16或vu32即可。

---内部FLASH的写入过程

(1)解锁:CKS32F4xx系列复位后,FLASH编程操作被写保护,只有向FLASH_KEYR寄存器写入特定序列0x45670123和0xCDEF89AB,方可解除写保护,进而操作其它相关寄存器;

(2)设置闪存操作位数:CKS32F4xx系列电源电压会影响数据的最大操作位数,由于本例中使用的开发板电压为3.3V,所以根据下述配置表,必须将FLASH_CR的PSIZE字段配置为10b,即32位并行位数,这也决定后续的擦除或编程操作必须以32位为基础进行。

3.png

(3)擦除扇区:在写入数据前,必须先擦除存储区域,CKS32标准库提供了扇区擦除和批量擦除的指令,批量擦除指令仅针对主存储区。

(4)写入数据:擦除完毕后才可写入数据,CKS32F4xx系列标准库提供了字节、半字、字和双字写入函数供用户调用,封装了对寄存器FLASH_CR和FLASH_SR的操作,具体步骤可查阅CKS32F4xx的嵌入式FLASH章节。

---其它注意事项:

①CPU时钟频率(HCLK)不能低于1MHz,不能在FLASH操作期间进行器件复位;

②FLASH执行写入或擦除操作期间,不能进行读操作,否则会导致总线阻塞。因此,FLASH写操作时,有必要写FLASH_DataCacheCmd(DISABLE),来禁止数据缓存,写完后再打开;

③写入地址必须是用户代码区以外的地址,不能覆盖用户代码,否则程序会出错,可以查看map文件,选择合适的存储空间;

④由于数据是以32bit写入的,占用4个地址位,所以写入地址必须是是4的倍数。

采用内部FLASH模拟EEPROM实验

CKS32F4xx系列有关FLASH的函数分布在文件cks32f4xx_flash.c以及cks32F4xx_flash.h中,本例中通过cks_flash_test函数演示内部FLASH的读写,该函数的执行过程如下:

① 调用FLASH_Unlock 解锁;

② 调用FLASH_DataCacheCmd禁止数据缓存;

③ 调用FLASH_EraseSector擦除待写入地址所在扇区,可由上述闪存模块组织图得知,擦除时是按字为单位进行操作,并等待FLASH操作结束进入下一步;

④ 本例中是调用FLASH_ProgramWord函数向指定地址写入指定数据,并等待FLASH操作结束进入下一步,CKS32F4xx系列官方库提供FLASH_ProgramHalfWord、FLASH_ProgramByte函数,用户可根据需求选用;

⑤ 调用FLASH_DataCacheCmd开启数据缓存;

⑥ 调用FLASH_Lock 上锁。

代码如下:

int main(void)
{
    cks_flash_test();
    while (1)
    {
    }
}
void cks_flash_test(void)
{
    FLASH_Unlock();
    FLASH_DataCacheCmd(DISABLE);
    if(FLASH_EraseSector(FLASH_Sector_1, VoltageRange_3) != FLASH_COMPLETE)
    {
        return;
    }
    if(FLASH_ProgramWord(0x08004000, 0x44332211) != FLASH_COMPLETE)
    {
        return;
    }
    FLASH_DataCacheCmd(ENABLE);
    FLASH_Lock();
}

4.png

图1

5.png

图2

主函数对cks_flash_test函数调用,cks_flash_test函数实现对指定地址0x08004000进行擦除,写入操作。通过断点调试,如图1当程序运行至FLASH_EraseSector后,0x08004000起始处的四个字节先被擦除成0xFF;如图2,当程序运行运行至FLASH_ProgramWord之后,执行向0x08004000写入0x44332211后,0x08004000起始处数据变成0x11、0x22、0x33、0x44。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 7

互补输出简介

互补输出只能在高级控制定时器(TIM1和TIM8)上使用,可以输出两路互补信号,包括主输出OCx和互补输出OCxN。基于比较输出一节的内容,OCx和OCxN都可以输出一定频率和占空比的PWM波形,且他们的极性是相反的,如图1所示,OCxREF是参考信号,OCx输出信号与参考信号相同,只是它的上升沿相对于参考信号的上升沿有一个延迟,OCxN输出信号与参考信号相反,只是它的上升沿相对于参考信号的下降沿有一个延迟,这个延迟时间,我们是可以通过死区寄存器配置的,本节中我们可以设置为0。

1.png

图1 带死区插入的互补输出

2.png

图2捕获比较通道的输出阶段

如图3所示,因为互补输出多用于PWM控制电机的项目中,所以紧急情况下的刹车控制是必不可少的,OSSR的含义是运行模式下的关闭状态选择,OSSI的含义是空闲模式下的关闭状态选择,MOE的含义是主输出使能,一般的应用模式为,当短路输入为有效状态,MOE又硬件异步清零,OSSI设置为0,禁止OC\OCN输出,OSI1\OSI1N设置为0,OC1\OC1N输出为0,达到紧急刹车的目的。

3.png

图3 控制位和输出状态

配置步骤

互补输出实际跟比较输出章节一样使用的是定时器的功能,所以相关的函数设置同样在库函数文件CKS32f4xx_tim.h和CKS32f4xx_tim.c文件中。

1)开启TIM1和GPIO时钟,配置PA7、PA8选择复用功能GPIO_AF_TIM1输出。

要使用TIM1,我们必须先开启TIM1的时钟,这点相信大家看了这么多代码,应该明白了。这里我们还要配置PA7、PA8为复用(GPIO_AF_TIM1)输出,才可以实现TIM1_CH1的互补PWM经过PA7、PA8输出。库函数使能TIM1时钟的方法是:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE); //>>TIM1 时钟使能

这在前面章节已经提到过。当然,这里我们还要使能GPIOA的时钟。然后我们要配置PA7引脚映射至GPIO_AF_TIM1,复用为定时器1,调用的函数为:

GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM1); //>>GPIOA7 复用为定时器1

配置PA8引脚映射至GPIO_AF_TIM1,复用为定时器1,调用的函数为:

GPIO_PinAFConfig(GPIOA,GPIO_PinSource8,GPIO_AF_TIM1); //>>GPIOA8 复用为定时器1

这个方法跟我们串口实验讲解一样,调用的同一个函数,最后设置PA7为复用功能输出这里我们只列出GPIO初始化为复用功能的一行代码:

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //>>复用功能

这里还需要说明一下,对于定时器通道的引脚关系,大家可以查看CKS32F4对应的数据手册,比如我们PWM实验,我们使用的是定时器1的通道1,对应的引脚PA7可以从数据手册表中查看:

3.png

2)初始化TIM1,设置TIM1的ARR和PSC等参数。

在开启了TIM1的时钟之后,我们要设置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(TIM1, &TIM_TimeBaseStructure); //根据指定的参数初始化 TIMx

3)设置TIM1_CH1的PWM模式,使能TIM1的CH1输出。

设置TIM1_CH1为PWM模式(默认是冻结的)通过配置TIM1_CCMR1的相关位来控制TIM1_CH1的模式。在库函数中,PWM通道设置是通过函数TIM_OC1Init()~TIM_OC4Init()来设置的,不同的通道的设置函数不一样,这里我们使用的是通道1,所以使用的函数是TIM_OC1Init()。

Void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

这种初始化格式大家学到这里应该也熟悉了,所以我们直接来看看结构体TIM_OCInitTypeDef的定义:

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\OutputNState用来设置比较输出使能,也就是使能PWM输出到端口。参数TIM_OCPolarity\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_OutputNState = TIM_OutputState_Enable; //>>比较输出使能
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High; //>>输出极性高
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //>>输出极性低
TIM_OCInitStructure. TIM_OCIdleState = TIM_OCNIdleState_Reset; //>>当MOE=0重置输出空闲状态
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset; //>>当MOE=0重置输出空闲状态
TIM_OCInitStruct.TIM_Pulse=100;//待装入捕获比较寄存器的脉冲值
TIM_OC1Init(TIM1, &TIM_OCInitStructure); //>>根据指定的参数初始化外设
TIM1 OC1 TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);  //使能TIM1在CCR上的预装载寄存器

4)使能 TIM1。

在完成以上设置了之后,我们不开启刹车功能,并使能TIM1,使能TIM1的方法前面已经讲解过:

TIM_BDTRStructure.TIM_AutomaticOutput=TIM_AutomaticOutput_Enable;//自动输出功能使能
TIM_BDTRStructure.TIM_Break=TIM_Break_Disable;//失能刹车输入
TIM_BDTRStructure.TIM_BreakPolarity=TIM_BreakPolarity_High; //刹车输入管脚极性高
TIM_BDTRStructure.TIM_DeadTime=0; //输出打开和关闭状态之间的延时
TIM_BDTRStructure.TIM_LOCKLevel=TIM_LOCKLevel_OFF;// 锁电平参数: 不锁任何位
TIM_BDTRStructure.TIM_OSSIState=TIM_OSSIState_Disable; //设置在运行模式下非工作状态选项
TIM_BDTRStructure.TIM_OSSRState=TIM_OSSRState_Disable; //设置在运行模式下非工作状态选项
TIM_BDTRConfig(TIM1,&TIM_BDTRStructure);
TIM_ARRPreloadConfig(TIM1,ENABLE); //使能TIM1在ARR上的预装载寄存器
TIM_CtrlPWMOutputs(TIM1,ENABLE);   //PWM使能主输出MOE=1
TIM_Cmd(TIM1,ENABLE);   //打开TIM1

5)修改TIM1_CCR1来控制占空比。

最后,在经过以上设置之后,PWM其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改TIM1_CCR1则可以控制CH1的输出占空比。在库函数中,修改TIM1_CCR1占空比的函数是:

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare2);

理所当然,对于其他通道,分别有一个函数名字,函数格式为:

TIM_SetComparex(x=1,2,3,4)

通过以上5个步骤,我们就可以控制TIM1的CH1输出互补PWM波了。

3代码示例

添加PWM配置文件pwm.c和pwm.h。

pwm.c源文件代码如下:

//>>TIM1 PWM 部分初始化
//>>PWM 输出初始化
//>>arr:自动重装值 psc:时钟预分频数
void TIM1_PWM_Init(u32 arr,u32 psc)
{
    PIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);//TIM1 时钟使能
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能 PORTA 时钟
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM1); //PA7 复用为 TIM1
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource8,GPIO_AF_TIM1); //PA8 复用为 TIM1
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8; //GPIOF9
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度 100MHz
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化 PA7\PA8
    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(TIM1,&TIM_TimeBaseStructure);//初始化定时器 1
    //初始化  TIM1 Channel1 互补PWM 模式
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //>>选择模式 PWM
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //>>比较输出使能
    TIM_OCInitStructure.TIM_OutputNState = TIM_OutputState_Enable; //>>比较输出使能
    TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High; //>>输出极性低
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //>>输出极性低
    TIM_OCInitStructure. TIM_OCIdleState = TIM_OCNIdleState_Reset; //>>当MOE=0重置输出空闲状态
    TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset; //>>当MOE=0重置输出空闲状态
    TIM_OC1Init(TIM1, &TIM_OCInitStructure); //>>根据指定的参数初始化外设TIM1 OC1 
    TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);  //使能TIM1在CCR上的预装载寄存器
    TIM_BDTRStructure.TIM_AutomaticOutput=TIM_AutomaticOutput_Enable;//自动输出功能使能  
    TIM_BDTRStructure.TIM_Break=TIM_Break_Disable;//失能刹车输入
    TIM_BDTRStructure.TIM_BreakPolarity=TIM_BreakPolarity_High; //刹车输入管脚极性高
    TIM_BDTRStructure.TIM_DeadTime=0; //输出打开和关闭状态之间的延时
    TIM_BDTRStructure.TIM_LOCKLevel=TIM_LOCKLevel_OFF;// 锁电平参数: 不锁任何位
    TIM_BDTRStructure.TIM_OSSIState=TIM_OSSIState_Disable; //设置在运行模式下非工作状态选项
    TIM_BDTRStructure.TIM_OSSRState=TIM_OSSRState_Disable; //设置在运行模式下非工作状态选项
    TIM_BDTRConfig(TIM1,&TIM_BDTRStructure);
    TIM_ARRPreloadConfig(TIM1,ENABLE); //使能TIM1在ARR上的预装载寄存器
    TIM_CtrlPWMOutputs(TIM1,ENABLE);   //PWM使能主输出MOE=1
    TIM_Cmd(TIM1,ENABLE);              //打开TIM1
    TIM_SetCompare1(TIM1,300); //>>设置pwm的占空比为300/500 = 60%
}

此部分代码包含了上面介绍的PWM输出设置的前5个步骤。这里我们关于TIM1的设置就不再说了。接下来,我们看看主程序里面的main函数如下:

int main(void)
{ 
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//>>设置系统中断优先级分组 2
    delay_init(168); //>>初始化延时函数
    TIM1_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)。

围观 8

CKS32F4xx系列芯片自带以太网模块,该模块包括带专用DMA控制器的MAC 802.3(介质访问控制)控制器,支持介质独立接口(MII)和简化介质独立接口(RMII),并自带了一个用于外部PHY通信的SMI接口,通过一组配置寄存器,用户可以为MAC控制器和DMA控制器选择所需模式和功能。自带以太网模块特点包括:

·支持外部PHY接口,实现10M/100Mbit/s的数据传输速率;

·通过符合IEEE802.3的MII/RMII接口与外部以太网PHY进行通信;

·支持全双工和半双工操作;

·可编程帧长度,支持高达16KB巨型帧;

·可编程帧间隔(40~96位时间,以8为步长);

·支持多种灵活的地址过滤模式;

·通过SMI(MDIO)接口配置和管理PHY设备;

·支持以太网时间戳(参见IEEE1588-2008),提供64位时间戳;

·提供接收和发送两组FIFO;

·支持DMA。

以太网主要功能及框图

1.png

图1 ETH框图

从上图可以看出,CKS32F4xx系列必须外接PHY芯片,才可以完成以太网通信的,外部PHY芯片可以通过MII/RMII接口与CKS32F4xx系列内部MAC连接,并且支持SMI(MDIO&MDC)接口配置外部以太网PHY芯片。SMI接口,即站管理接口,该接口允许应用程序通过2条线:时钟(MDC)和数据线(MDIO)访问任意PHY寄存器。该接口支持访问多达32个PHY,应用程序可以从32个PHY中选择一个PHY,然后从任意PHY包含的32个寄存器中选择一个寄存器,发送控制数据或接收状态信息。任意给定时间内只能对一个PHY中的一个寄存器进行寻址。MII接口,即介质独立接口,用于MAC层与PHY层进行数据传输。CKS32F4xx系列通过MII与PHY层芯片的连接如图2所示:

2.jpg

图2 介质独立接口信号

MII_TX_CLK:连续时钟信号。该信号提供进行TX数据传输时的参考时序。标称频率为:速率为10Mbit/s时为2.5MHz;速率为100Mbit/s时为25MHz。

·MII_RX_CLK:连续时钟信号。该信号提供进行RX数据传输时的参考时序。标称频率为:速率为10Mbit/s时为2.5MHz;速率为100Mbit/s时为25MHz。

·MII_TX_EN:发送使能信号。

·MII_TXD[3:0]:数据发送信号。该信号是4个一组的数据信号,

·MII_CRS:载波侦听信号。·MII_COL:冲突检测信号。

·MII_RXD[3:0]:数据接收信号。该信号是4个一组的数据信号。

·MII_RX_DV:接收数据有效信号。

·MII_RX_ER:接收错误信号。该信号必须保持一个或多个周期(MII_RX_CLK),从而向MAC子层指示在帧的某处检测到错误。

RMII接口,即精简介质独立接口,该接口降低了在10/100Mbit/s下微控制器以太网外设与外部PHY间的引脚数。根据IEEE 802.3u标准,MII包括16个数据和控制信号的引脚。RMII规范将引脚数减少为7个。

RMII接口是MAC和PHY之间的实例化对象。这有助于将MAC的MII转换为RMII。RMII具有以下特性:·支持10Mbit/s和100Mbit/s的运行速率;

·参考时钟必须是50MHz;

·相同的参考时钟必须从外部提供给MAC和外部以太网PHY;

·它提供了独立的2位宽(双位)的发送和接收数据路径;

CKS32F4xx系列通过RMII接口与PHY层芯片的连接如图3所示:

3.jpg

图3 精简介质独立接口信号

从上图可以看出RMII相比MII,引脚数量精简了不少。注意,图中的REF_CLK信号,是RMII和外部PHY共用的50Mhz参考时钟,必须由外部提供,比如有源晶振,或者CKS32F4xx系列的MCO输出。不过有些PHY芯片可以自己产生50Mhz参考时钟,同时提供给CKS32F4xx系列,这样也是可以的。

LWIP简介

LWIP是瑞典计算机科学院(SICS)的Adam Dunkels等开发的一个小型开源的TCP/IP协议栈,是TCP/IP的一种实现方式。LWIP是轻量级IP协议,有无操作系统的支持都可以运行,LWIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM的占用,它只需十几KB的RAM和40K左右的ROM就可以运行,这使LWIP协议栈适合在低端的嵌入式系统中使用。该部分功能移植由于内容较多,请自行参考相关移植材料,本文不做叙述。

LAN8720A通讯

本章我们采用RMII接口和外部PHY芯片连接,实现网络通信功能。LAN8720A是低功耗的10/100M以太网PHY层芯片,I/O引脚电压符合IEEE802.3-2005标准,支持通过RMII接口与以太网MAC层通信,内置10-BASE-T/100BASE-TX全双工传输模块,支持10Mbps和100Mbps,可以通过自协商的方式与目的主机最佳的连接方式(速度和双工模式),支持HP Auto-MDIX自动翻转功能,无需更换网线即可将连接更改为直连或交叉连接。

具体驱动方式请参考相关demo例程。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 13

很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有U盘,FLASH芯片,SD卡等。他们各有优点,综合比较,最适合单片机系统的莫过于SD卡了,它不仅容量可以做到很大(32GB以上),支持SPI/SDIO驱动,而且有多种体积的尺寸可供选择(标准的SD卡尺寸,以及TF卡尺寸等),能满足不同应用的要求。

CKS32F4xx系列的SDIO控制器支持多媒体卡(MMC卡)、SD存储卡、SD I/O卡和CE-ATA设备等。SDIO的主要功能如下:

1. 与多媒体卡系统规格书版本4.2全兼容。支持三种不同的数据总线模式:1位(默认)、4位和8位。

2. 与较早的多媒体卡系统规格版本全兼容(向前兼容)。

3. 与SD存储卡规格版本2.0全兼容。

4. 与SD I/O卡规格版本2.0全兼容:支持良种不同的数据总线模式:1位(默认)和4位。

5. 完全支持CE-ATA功能(与CE-ATA数字协议版本1.1全兼容)。8位总线模式下数据传输速率可达48MHz(分频器旁路时)。

数据和命令输出使能信号,用于控制外部双向驱动器。

SDIO主要功能及框图

CKS32F4xx系列的SDIO控制器包含2个部分:SDIO适配器模块和APB2总线接口,其功能框图如下:

1.jpg

图1 CKS32F4xx系列SDIO控制器功能框图

复位后默认情况下SDIO_D0用于数据传输。初始化后主机可以改变数据总线的宽度(通过ACMD6命令设置)。

如果一个多媒体卡接到了总线上,则SDIO_D0、SDIO_D[3:0]或SDIO_D[7:0]可以用于数据传输。MMC版本V3.31和之前版本的协议只支持1位数据线,所以只能用SDIO_D0(为了通用性考虑,在程序里面我们只要检测到是MMC卡就设置为1位总线数据)。

如果一个SD或SD I/O卡接到了总线上,可以通过主机配置数据传输使用SDIO_D0或SDIO_D[3:0]。所有的数据线都工作在推挽模式。

SDIO_CMD有两种操作模式:

①用于初始化时的开路模式(仅用于MMC版本V3.31或之前版本)

②用于命令传输的推挽模式(SD/SD I/O卡和MMC V4.2在初始化时也使用推挽驱动)

SDIO时钟

SDIO总共有3个时钟:

1. 卡时钟(SDIO_CK):每个时钟周期在命令和数据线上传输1位命令或数据。对于多媒体卡V3.31协议,时钟频率可以在0MHz至20MHz间变化;对于多媒体卡V4.0/4.2协议,时钟频率可以在0MHz至48MHz间变化;对于SD或SD I/O卡,时钟频率可以在0MHz至25MHz间变化。

2. SDIO适配器时钟(SDIOCLK):该时钟用于驱动SDIO适配器,来自PLL48CK,一般为48Mhz,并用于产生SDIO_CK时钟。

3. APB2总线接口时钟(PCLK2):该时钟用于驱动SDIO的APB2总线接口,其频率为HCLK/2,一般为84Mhz。

我们的SD卡时钟(SDIO_CK),根据卡的不同,可能有好几个区间,这就涉及到时钟频率的设置,SDIO_CK与SDIOCLK的关系(时钟分频器不旁路时)为:

SDIO_CK=SDIOCLK/(2+CLKDIV)

其中,SDIOCLK为PLL48CK,一般是48Mhz,而CLKDIV则是分配系数,可以通过SDIO的SDIO_CLKCR寄存器进行设置(确保SDIO_CK不超过卡的最大操作频率)。注意,以上公式,是时钟分频器不旁路时的计算公式,当时钟分频器旁路时,SDIO_CK直接等于 SDIOCLK。

这里要提醒大家,在SD卡刚刚初始化的时候,其时钟频率(SDIO_CK)是不能超过400Khz的,否则可能无法完成初始化。在初始化以后,就可以设置时钟频率到最大了(但不可超过SD卡的最大操作时钟频率)。

SDIO命令与响应

SDIO的命令分为应用相关命令(ACMD)和通用命令(CMD)两部分,应用相关命令(ACMD)的发送,必须先发送通用命令(CMD55),然后才能发送应用相关命令(ACMD)。SDIO的所有命令和响应都是通过SDIO_CMD引脚传输的,任何命令的长度都是固定为48位,SDIO的命令格式如表1所示:

2.jpg

表1 SDIO命令格式

所有的命令都是由MCU发出,其中开始位、传输位、CRC7和结束位由SDIO硬件控制,我们需要设置的就只有命令索引和参数部分。其中命令索引(如CMD0,CMD1)在SDIO_CMD寄存器里面设置,命令参数则由寄存器SDIO_ARG设置。

一般情况下,选中的SD卡在接收到命令之后,都会回复一个应答(CMD0是无应答),这个应答我们称之为响应,响应也是在CMD线上串行传输的。CKS32F4xx系列的SDIO控制器支持2种响应类型,即:短响应(48位)和长响应(136位),这两种响应类型都带CRC错误检测(不带CRC的响应应忽略CRC错误标志,如CMD1响应)。

3.jpg

表2 短响应格式

4.jpg

表3 长响应的格式

同样,硬件为我们滤除了开始位、传输位、CRC7以及结束位等信息,对于短响应,命令索引存放在SDIO_RESPCMD寄存器,参数则存放在SDIO_RESP1寄存器里面。对于长响应,则仅留CID/CSD位域,存放在SDIO_RESP1~SDIO_RESP4等4个寄存器。SD存储卡总共有5类响应(R1、R2、R3、R6、R7),我们这里以R1为例简单介绍一下。R1(普通响应命令)响应输入短响应,其长度为48位,R1响应的格式如表4所示:

5.jpg

表4 R1响应格式

在收到R1响应后,我们可以从SDIO_RESPCMD寄存器和SDIO_RESP1寄存器分别读出命令索引和卡状态信息。关于其他响应的介绍,请大家参考SD卡2.0协议。

最后,我们看看数据在SDIO控制器与SD卡之间的传输。对于SDI/SDIO存储器,数据是以数据块的形式传输的,而对于MMC卡,数据是以数据块或者数据流的形式传输。本节我们只考虑数据块形式的数据传输。

6.jpg

图2 SDIO(多个)块读取操作

从上图,我们可以看出,从机在收到主机相关命令后,开始发送数据块给主机,所有数据块都带有CRC校验值(CRC由SDIO硬件自动处理),单个数据块读的时候,在收到1个数据块以后即可以停止了,不需要发送停止命令(CMD12)。但是多块数据读的时候,SD卡将一直发送数据给主机,直到接到主机发送的STOP命令(CMD12)。

7.jpg

图3 SDIO(多个)块写入操作

数据块写操作同数据块读操作基本类似,只是数据块写的时候,多了一个繁忙判断,新的数据块必须在SD卡非繁忙的时候发送。这里的繁忙信号由SD卡拉低SDIO_D0,以表示繁忙,SDIO硬件自动控制,不需要我们软件处理。

SD卡初始化流程

要实现SDIO驱动SD卡,最重要的步骤就是SD卡的初始化,只要SD卡初始化完成,那么剩下的读写操作就简单了,所以我们这里重点介绍SD卡的初始化。从SD卡2.0协议中,我们得到SD卡初始化流程图如图4所示:

8.jpg

图4 SD卡初始化流程

从图中,我们看到,不管什么卡(这里我们将卡分为4类:SD2.0高容量卡(SDHC,最大32G),SD2.0标准容量卡(SDSC,最大2G),SD1.x卡和MMC卡),首先我们要执行的是卡上电(需要设置SDIO_POWER[1:0]=11),上电后发送CMD0,对卡进行软复位,之后发送CMD8命令,用于区分SD卡2.0,只有2.0及以后的卡才支持CMD8命令,MMC卡和V1.x的卡,是不支持该命令的。

在发送CMD8后,发送ACMD41(注意发送ACMD41之前要先发送CMD55),来进一步确认卡的操作电压范围,并通过HCS位来告诉SD卡,主机是不是支持高容量卡(SDHC)。

对于支持CMD8指令的卡,主机通过ACMD41的参数设置HCS位为1,来告诉SD卡主机支SDHC卡,如果设置为0,则表示主机不支持SDHC卡,SDHC卡如果接收到HCS为0,则永远不会反回卡就绪状态。对于不支持CMD8的卡,HCS位设置为0即可。

SD卡在接收到ACMD41后,返回OCR寄存器内容,如果是2.0的卡,主机可以通过判断OCR的CCS位来判断是SDHC还是SDSC;如果是1.x的卡,则忽略该位。OCR寄存器的最后一个位用于告诉主机SD卡是否上电完成,如果上电完成,该位将会被置 1。

对于MMC卡,则不支持ACMD41,不响应CMD55,对MMC卡,我们只需要在发送CMD0后,再发送CMD1(作用同ACMD41),检查MMC卡的OCR寄存器,实现MMC卡的初始化。至此,我们便实现了对SD卡的类型区分,最后发送CMD2和CMD3命令,用于获得卡CID寄存器数据和卡相对地址(RCA)。

SD卡在收到CMD2后,将返回R2长响应(136位),其中包含128位有效数据(CID寄存器内容),存放在SDIO_RESP1~4等4个寄存器里面。通过读取这四个寄存器,就可以获得SD卡的CID信息。

CMD3用于设置卡相对地址(RCA,必须为非0),对于SD卡(非MMC卡),在收到CMD3后,将返回一个新的RCA给主机,方便主机寻址。RCA的存在允许一个SDIO接口挂多个SD卡,通过RCA来区分主机要操作的是哪个卡。而对于MMC卡,则不是由SD卡自动返回RCA,而是主机主动设置MMC卡的RCA,即通过CMD3带参数(高16位用于RCA设置),实现RCA设置。同样MMC卡也支持一个SDIO接口挂多个MMC卡,不同于SD卡的是所有的RCA都是由主机主动设置的,而SD卡的RCA则是SD卡发给主机的。

在获得卡RCA之后,我们便可以发送CMD9(带RCA参数),获得SD卡的CSD寄存器内容,从CSD寄存器,我们可以得到SD卡的容量和扇区大小等十分重要的信息。CSD寄存器我们在这里就不详细介绍了,关于CSD寄存器的详细介绍,请大家参考SD卡2.0协议。

至此,我们的SD卡初始化基本就结束了,最后通过CMD7命令,选中我们要操作的SD卡,即可开始对SD卡的读写操作了。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 6

随机数发生器简介

CKS32F4xx系列自带了硬件随机数发生器(RNG),RNG处理器是一个以连续模拟噪声为基础的随机数发生器,在主机读数时提供一个32位的随机数。

CKS32F4xx系列的随机数发生器框图如图1所示:

1.jpg

图1.RNG框图

CKS32F4xx系列的随机数发生器(RNG)采用模拟电路实现。此电路产生馈入线性反馈移位寄存器(RNG_LFSR)的种子,用于生成32位随机数。

该模拟电路由几个环形振荡器组成,振荡器的输出进行异或运算以产生种子。RNG_LFSR由专用时钟(PLL48CLK)按恒定频率提供时钟信息,因此随机数质量与HCLK频率无关。当将大量种子引入RNG_LFSR后,RNG_LFSR的内容会传入数据寄存器(RNG_DR)。

同时,系统会监视模拟种子和专用时钟PLL48CLK,当种子上出现异常序列,或PLL48CLK时钟频率过低时,可以由RNG_SR寄存器的对应位读取到,如果设置了中断,则在检测到错误时,还可以产生中断。

RNG寄存器

我们介绍下CKS32F4xx系列随机数发生器(RNG)的几个寄存器。

2.png

图2.RNG控制寄存器

图2为RNG的CR寄存器,其只有bit2和bit3有效,用于使能随机数发生器和中断。我们一般不用中断,所以只需要设置bit2为1,使能随机数发生器即可。

3.jpg

图3.RNG状态寄存器

图3为RNG状态寄存器:RNG_SR。该寄存器我们仅关心最低位(DRDY位),该位用于表示RNG_DR寄存器包含的随机数数据是否有效,如果该位为1,则说明RNG_DR的数据是有效的,可以读取出来了。读RNG_DR后,该位自动清零。

4.jpg

图4.RNG数据寄存器

图4为RNG数据寄存器:RNG_DR。在RNG_SR的DRDY位置位后,我们就可以读取该寄存器获得32位随机数值。此寄存器在最多40个PLL48CK时钟周期后,又可以提供新的随机数值。

至此,随机数发生器的寄存器,我们就介绍完了。接下来,我们看看要使用库函数操作随机数发生器,应该如何设置。

配置随机数发生器

1.使能随机数发生器时钟

要使用随机数发生器,必须先使能其时钟。随机数发生器时钟来自PLL48CK,通过AHB2ENR寄存器使能。所以我们调用使能AHB2总线外设时钟的函数使能RNG时钟即可:

RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_RNG, ENABLE);

2.使能随机数发生器

这个就是通过RNG_CR寄存器的最低位设置为1,使能随机数发生器。当然,如果需要用到中断,你快还可以使能RNG中断。本章我们不用中断。库函数中使能随机数发生器的方法为:

RNG_Cmd(ENABLE);

3.判断DRDY位,读取随机数值

经过前面两个步骤,我们就可以读取随机数值了,不过每次读取之前,必须先判断 RNG_SR寄存器的DRDY位,如果该位为1,则可以读取RNG_DR得到随机数值,如果不为1,则需要等待。

获取随机数发生器状态的函数为:

FlagStatus RNG_GetFlagStatus(uint8_t RNG_FLAG);

判断数据是否有效的入口参数为 RNG_FLAG_DRDY,所以等待就绪的方法为:

while(RNG_GetFlagStatus(RNG_FLAG_DRDY)==RESET);

判断数据有效后,然后我们读取随机数发生器产生的随机数即可,调用函数为:

uint32_t RNG_GetRandomNumber(void);

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 10

12位ADC是逐次趋近型模数转换器,它具有多达19个复用通道,可测量来自16个外部源、2个内部源(温度传感器、VREF)和VBAT通道的信号。这些通道的A/D转换可在单次、连续、扫描或不连续采样模式下进行,并将ADC的结果存储在一个左对齐或右对齐的16位数据寄存器中。

除此之外,ADC还具有模拟看门狗特性,允许应用检测输入电压是否超过了用户自定义的阈值上限/下限。

ADC主要特性

·可配置12位、10位、8位或6位分辨率

·转换结束、注入转换结束以及发生模拟看门狗或溢出事件时产生中断

    ·单次和连续转换模式

·用于自动将通道0转换为通道“n”的扫描模式

·数据对齐以保持内置数据一致性

·可独立设置各通道采样时间

·外部触发器选项,可为规则转换和注入转换配置极性

·不连续采样模式

·双重/三重模式(具有2个或更多ADC的器件提供)

·双重/三重ADC模式下可配置的DMA数据存储

·双重/三重交替模式下可配置的转换间延迟

·ADC转换类型(参见数据手册)

·ADC电源要求:全速运行时为2.4V到3.6V,慢速运行时为1.8V

·ADC输入范围:VREF≤VIN≤VREF+

·规则通道转换期间可产生DMA请求

1.png

图1.ADC框图

注意:VREF-如果可用(取决于封装),则必须将其连接到VSSA。

ADC功能说明

1.ADC开关控制    

ADC的开关控制可通过将ADC_CR2寄存器中的ADON位置1来为ADC供电。首次将ADON位置1时,会将ADC从掉电模式中唤醒,在SWSTART或JSWSTART位置1时,启动AD转换,然后可通过将ADON位清零,用于停止转换并使ADC进入掉电模式。在此模式下,ADC几乎不耗电(只有几μA)。

2.ADC时钟    

ADC具有两个时钟方案:    

①用于模拟电路的时钟:ADCCLK,所有ADC共用此时钟,时钟来源于经可编程预分频器分频的APB2时钟,该预分频器允许ADC在fPCLK2/2、/4、/6或/8下工作,有关ADCCLK的最大值,请参见数据手册。

②用于数字接口的时钟(用于寄存器读/写访问)
此时钟等效于APB2时钟,即可通过RCC APB2外设时钟使能寄存器(RCC_APB2ENR)分别为每个ADC使能/禁止数字接口时钟。

3.通道选择

对于CKS32F4XX器件,温度传感器内部连接到通道ADC1_IN16,内部参考电压VREFINT连接到ADC1_IN17。

注意:温度传感器、VREFINT和VBAT通道只在主ADC1外设上可用

4.单次转换模式

在单次转换模式下,ADC执行一次转换,且CONT位为0时,可通过以下方式启动此模式:

·将ADC_CR2寄存器中的SWSTART位置1(仅适用于规则通道);

·将JSWSTART位置1(适用于注入通道);

·外部触发(适用于规则通道或注入通道);

完成所选通道的转换之后:

·如果转换了规则通道:

-转换数据存储在16位ADC_DR寄存器中;

-EOC(转换结束)标志置1;

-EOCIE位置1时将产生中断;

·如果转换了注入通道:

-转换数据存储在16位ADC_JDR1寄存器中;

-JEOC(注入转换结束)标志置1;

-JEOCIE位置1时将产生中断;

完成以上步骤之后,停止ADC。

5.时序图

ADC在开始精确转换之前需要一段稳定时间tSTAB,并且在ADC开始转换经过15个时钟周期后,EOC标志将置1,最后将转换结果存放在16位ADC数据寄存器中,时序如图2所示。

2.png

图2.时序图

数据对齐

    DC_CR2寄存器中的ALIGN位用于选择转换后存储的数据的对齐方式,在ADC配置时可根据需求选择左对齐和右对齐两种方式,如图3和图4所示。

3.png

图3.12位数据的右对齐

4.png

图4.12位数据的左对齐

需注意的是,注入通道组的转换数据将减去 ADC_JOFRx寄存器中写入的用户自定义偏移量,因此结果可以是一个负值,图中的SEXT位表示扩展符号值。

对于规则组中的通道,不会减去任何偏移量,因此只有十二个位有效。

可独立设置各通道采样时间

ADC会在数个ADCCLK周期内对输入电压进行采样,在进行配置时,可通过修改ADC_SMPR1和ADC_SMPR2寄存器中的SMP[2:0]位来更改相应的周期数,对于ADC的多个通道而言,每个通道均可以使用不同的采样时间进行采样。
总转换时间的计算公式如下:

Tconv=采样时间+12个周期

示例:

ADCCLK=30MHz且采样时间=3个周期时:
Tconv=3+12=15个周期=0.5μs(APB2为60MHz时)

温度传感器

CKS32有一个内部的温度传感器,温度传感器可用于测量器件的环境温度(TA)。该温度传感器内部连接到ADC1_IN16通道,ADC1用于将传感器输出电压转换为数字值,图5显示了温度传感器框图,当不使用时,可将传感器置于掉电模式。
注意:必须将TSVREFE位置1才能同时对两个通道进行转换。ADC1_IN16(温度传感器)和ADC1_IN17(VREFINT)。
主要特性:

·支持的温度范围:-40°C到125°C

·精度:±1.5°C

5.png

图5.温度传感器与Vrefint通道框图

VSENSE是连接到ADC1_IN16的输入:

CKS32内部温度传感器的使用较为简单,只要设置一下内部ADC,并激活其内部通道即可。关于ADC的设置,在之前的章节已经进行了详细的介绍,本章节仅做简要讲解,接下来介绍一下如何读取内部温度传感器采集到的温度。

读取温度,要使用传感器,请执行以下操作:

1) 选择ADC1_IN16输入通道;

2) 选择一个采样时间,该采样时间要大于数据手册中所指定的最低采样时间;

3) 在ADC_CCR寄存器中将TSVREFE位置1,以便将温度传感器从掉电模式中唤醒;

4) 通过将SWSTART位置1(或通过外部触发)开始ADC转换;

5) 读取ADC数据寄存器中生成的VSENSE数据;

6) 使用以下公式计算温度:温度(单位为°C)={(VSENSE-V25)/Avg_Slope}+25

其中:

-V25=25°C时的VSENSE

-Avg_Slope=温度与VSENSE曲线的平均斜率(以mV/°C或μV/°C表示)

有关V25和Avg_Slope实际值的相关信息,请参见数据手册中的电气特性一节。

注意:传感器从掉电模式中唤醒需要一个启动时间,启动时间过后其才能正确输出VSENSE。ADC在上电后同样需要一个启动时间,因此,为尽可能减少延迟间,应同时将ADON和TSVREFE位置1。

温度传感器的输出电压随温度线性变化。由于工艺不同,该线性函数的偏移量取决于各个芯片(芯片之间的温度变化可达45°C)。    

内部温度传感器更适用于对温度变量而非绝对温度进行测量的应用情况。如果需要读取精确温度,则应使用外部温度传感器。    

通过以上讲解,即可总结一下CKS32内部温度传感器使用的步骤了,如下:

1)设置ADC,开启内部温度传感器    

关于如何设置ADC,之前已经介绍了,我们采用与常规ADC配置相似的设置。不同的是之前温度传感器读取外部通道的值,而内部温度传感器相当于把通道端口连接在内部温度传感器上,所以这里,我们要开启内部温度传感器功能:

ADC_TempSensorVrefintCmd(ENABLE);

2)读取通道16的AD值,计算结果    

在设置完之后,即可读取温度传感器的电压值了,得到该值后,利用上面的公式计算温度值。如文中所述,可知ADC通道与GPIO的对应关系,内部温度传感器是通过对应ADC通道16,其它的跟上一节的讲解是一样的。

软件设计

打开内部温度传感器实验工程,即可看到adc.c与adc.h文件。函数的作用跟之前ADC实验基本是一样的。不同的是在adc_Init函数中设置为开启内部温度传感器模式,代码如下:

voidadc_Init(void)
{
    ADC_CommonInitTypeDefADC_CommonInitStructure;
    ADC_InitTypeDef       ADC_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE);
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE); 
    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_CommonInitStructure.ADC_TwoSamplingDelay=ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; 
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
    ADC_CommonInit(&ADC_CommonInitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConvEdge=ADC_ExternalTrigConvEdge_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfConversion = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    ADC_TempSensorVrefintCmd(ENABLE);
    ADC_Cmd(ADC1, ENABLE);
}

这部分代码与之前的ADC测试代码类似,仅去掉外部IO初始化。

我们还在adc.c里面添加了Get_Temprate函数,用来获取温度值,也就是把采集到的电压根据计算公式转换为温度值,Get_Temprate函数代码如下:

short Get_Temprate(void)
{
    u32 adcx;  
    double temperate;
    ADC_SoftwareStartConv(ADC1);
    while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//µÈ´ýת»»½áÊø
    ADC_RegularChannelConfig(ADC1,ADC_Channel_16,1,ADC_SampleTime_480Cycles );
    adcx = ADC_GetConversionValue(ADC1);
    temperate=(float)adcx*(3.3/4096);
    temperate=(temperate-0.76)/0.0025 + 25;
    printf("温度:%f ℃\r\n",temperate);
}

接下来我们就可以开始读取温度传感器的电压了。在main.c文件里面我们的main函数代码如下:

int main(void)
{
    u8 len,t;
    u16 parse_num;
    float temp,temperate;
    delay_init(168);      
    uart_init(115200);
    adc_Init();
    while(1)
    {
        Get_Temprate();
        printf("\r\n\r\n");
        delay_ms(500);
    }
}

这里同上一章的主函数也大同小异,上面的代码将温度传感器得到的电压值,换算成温度值,下载后通过串口打印结果进行验证。

6.png

以上即为ADC内部温度传感器的实验内容。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 12

DAC模块作为CKS32F4xx系列的一个常用外设,可以将数字信号转换成模拟信号,最高分辨率可达12位,且两个独立DAC输出通道转换互不影响,各个通道均能使用DMA功能,可由软硬件触发。因此,为了实现DAC输出正弦波,拟采用一定的时间向DAC的数据寄存器写入数据,随后进行数模转换输出不同的电压,最后在时间轴上显示出波形。同时为了不占用CPU资源,配置DMA建立传输通道,以便数据快速的从内存搬移到外设。且在DAC初始化时,可以设置成定时器触发,待定时器溢出就会触发DAC工作,所以只要修改定时器的定时时间,就可改变正弦波周期。

DAC简述

1.png

①:DAC将VREF+引脚作为参考电压,在实际使用时将VSSA接地,同时把VREF+和VDDA接3.3V,DAC即可获得0~3.3V的输出电压。

②:数模转换器以VREF+作为参考电源,将DAC的数据寄存器“DORx”的数字编码转换成模拟信号并由右侧的“DAC_OUTx”通道输出。在CKS32有2个这样的DAC部件,其中PA4对应通道1,PA5对应通道2。

③:控制逻辑可以控制数据寄存器“DORx”加入一些伪噪声信号或配置产生三角波信号。

④:使用DAC时,数据会被先写入到DHRx寄存器,随后DAC会根据触发配置进行处理,最后将数据传输至DORx。DAC的触发源有三种,分别为:外部中断源触发、定时器触发和软件控制触发。

对于单DAC通道x的三种数据格式

8位数据右对齐:

用户须将数据写入寄存器DAC_DHR8Rx[7:0]位(实际是存入寄存器DHRx[11:4]位)。

12位数据左对齐:

用户须将数据写入寄存器DAC_DHR12Lx[15:4]位(实际是存入寄存器DHRx[11:0]位)。

12位数据右对齐:

用户须将数据写入寄存器DAC_DHR12Rx[11:0]位(实际是存入寄存器DHRx[11:0]位)。

数字输入经过DAC被线性地转换为模拟电压输出,任一DAC通道引脚上的输出电压满足下面的关系:

本案例中选择DAC的通道1,并采用12位的右对齐方式,通过查阅《CKS32F4xx参考手册》DAC和DMA章节可知,DAC1对应DMA1控制器通道7数据流5。

总的来说,DAC的输出是由DORx寄存器直接控制的,而用户写的数据是要写入DHRx寄存器,然后通过DHRx间接操作DORx,最终实现DAC的输出。

DAC输出正弦波配置

本文采用DAC1+TIM2+DMA1的方式,通过TIM2触发DAC1转换,转换完成后通过DMA1输出,主要步骤如下:

①由Matlab计算一个周期的正弦波数组;

②根据一个正弦波周期内点数和所需正弦波频率确定定时器触发间隔;

③初始化DAC1输出管脚和工作模式;

④配置触发DAC1用的定时器2;

⑤配置DMA1自主搬运正弦波数组。

待上述配置完成后,将PA4引脚接到示波器上,即可显示正弦波。以下是DAC的详细配置。

(1)正弦波数组生成

以下代码用于生成正弦波波形表:

for(i=0;i<100;i++)
{
    Sine12bit[i]=2048*sin(1.0*i/(100- 1)*2*PI)+2048;
}

从上述函数可以看出,正弦波的幅度被控制在0~4096之间,一个周期被平均分成100份,即100个点代表一个周期的波形,数组Sine12bit里面是100个采样点。

const uint16_t Sine12bit[100] = {

0x0800,0x0881,0x0901,0x0980,0x09FD,0x0A79,0x0AF2,0x0B68,0x0BDA,0x0C49,0x0CB3,0x0D19,0x0D79,0x0DD4,0x0E29,0x0E78,0x0EC0,0x0F02,0x0F3C,0x0F6F,0x0F9B,0x0FBF,0x0FDB,0x0FEF,0x0FFB,0x0FFF,0x0FFB,0x0FEF,0x0FDB,0x0FBF,0x0F9B,0x0F6F,0x0F3C,0x0F02,0x0EC0,0x0E78,0x0E29,0x0DD4,0x0D79,0x0D19,0x0CB3,0x0C49,0x0BDA,0x0B68,0x0AF2,0x0A79,0x09FD,0x0980,0x0901,0x0881,0x0800,0x077F,0x06FF,0x0680,0x0603,0x0587,0x050E,0x0498,0x0426,0x03B7,0x034D,0x02E7,0x0287,0x022C,0x01D7,0x0188,0x0140,0x00FE,0x00C4,0x0091,0x0065,0x0041,0x0025,0x0011,0x0005,0x0001,0x0005,0x0011,0x0025,0x0041,0x0065,0x0091,0x00C4,0x00FE,0x0140,0x0188,0x01D7,0x022C,0x0287,0x02E7,0x034D,0x03B7,0x0426,0x0498,0x050E,0x0587,0x0603,0x0680,0x06FF,0x077F};

(2)GPIO和DAC模式配置

该部分为输出引脚配置和DAC通道1配置,代码如下:

void DAC1_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    DAC_InitTypeDef DAC_InitStructure;
    /*  Enable GPIOA clock  */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);/*  Enable DAC clock  */RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);/*  Configure the DAC Pin to Analog mode: DAC_OUT1 -- PA4  */GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;  
    GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    /*  Configure DAC Channel_1  */
    DAC_InitStructure.DAC_Trigger= DAC_Trigger_T2_TRGO;
    DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bit0;DAC_Init(DAC_Channel_1, &DAC_InitStructure);
    DAC_Cmd(DAC_Channel_1, ENABLE);//Enable DAC Channel_1.
    DAC_DMACmd(DAC_Channel_1, ENABLE);//Enable DAC channel_1 DMA request.
}

在DAC1_GPIO_Init函数中,实现了相应GPIO引脚(PA4)的初始化和DAC工作模式配置。其中为了避免寄生的干扰和额外的功耗,应将PA4引脚设置成模拟输入模式(AIN),如此方可正常工作。

而对DAC工作模式进行配置时,可查看CKS官方提供的DAC_InitTypeDef结构体,该结构体中主要包含了DAC_CR寄存器的各寄存器配置。如下是DAC_InitTypeDef结构体成员简述:

(a)DAC_Trigger

该成员用于DAC的触发模式配置,由上文DAC通道框图可知,共有三种触发模式,分别是定时器触发(DAC_Trigger_T2/4/5/6/7/8_TRGO)、软件触发(DAC_Trigger_Software)和EXTI_9触发方式(DAC_Trigger_Ext_IT9)。

(b)DAC_WaveGeneration

该成员可配置输出伪噪声和三角波输出(DAC_WaveGeneration_Noise/Triangle),若使用自定义输出,应配置为DAC_WaveGeneration_None。

(c)DAC_OutputBuffer

该成员用于控制是否使能DAC的输出缓冲(DAC_OutputBuffer_Enable/Disable)。若需要直接驱动外部负载,可以使能该成员以减小输出阻抗。

(d)DAC_LFSRUnmask_TriangleAmplitude

该成员通过控制DAC_CR的MAMP2位设置LFSR寄存器位的数据,即当使用伪噪声或三角波输出时要叠加到DHRx的值。若使用伪噪声输出时LFSR=0xAAA,这时该结构体成员可赋值为DAC_LFSRUnmask_Bit0~DAC_LFSRUnmask_Bit11_0;若使用三角波输出时,这时该结构体成员可赋值为DAC_TriangleAmplitude_1~DAC_TriangleAmplitude_4096,可用于设置三角波的最大幅值。

本例中,将DAC通道1配置成定时器TIM2触发,不使用波形发生器和不使用输出缓存,不使用输出缓存是因为CKS32的DAC无需外部运放就可以直接驱动负载,三角波振幅一项虽然本案例没有用到,可以配置成任意,但此项不可缺,最后调用DAC_Cmd、DAC_DMACmd函数使能DAC通道1和DMA的请求。

(3)定时器配置

该部分是配置触发DAC的定时器TIM2,通过设定触发的间隔,从而间接控制正弦波周期,TIM2的工作决定DMA与DAC的工作频率,代码如下:

void TIM2_Init(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    /*  Enable Timer2 clock.  */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);/*  Configure Timer2 --Clock Frequency is 84MHz  */
    TIM_TimeBaseStructure.TIM_Period=83; TIM_TimeBaseStructure.TIM_Prescaler= 0x0;       
    TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;    
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);  /*  Configure the trigger source for Timer2.  */TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
    TIM_Cmd(TIM2, ENABLE);//Enable Timer2.
}

前文的DAC已选用TIM2当触发源,此处TIM2的定时周期被配置为83,向上计数,不分频。CKS32F4xx系列的主频是168MHz,TIM2的时钟是84MHz,所以TIM2的更新频率是84M/(TIM_Period+1)/(TIM_Prescaler+1),即TIM2每隔1us触发一次DAC事件,不需要设置中断,当定时器向上计数至指定值时,产生Update事件,同时触发DAC把DHRx寄存器的数据转移到DORx,开始进行转换。由于正弦波数组是100个采样点,可得正弦波的输出频率为:

(4)DMA配置

该部分主要完成数据的传输,代码如下:

void DMA_InitForDAC(void)
{
    DMA_InitTypeDef  DMA_InitStructure;
    /*  Enable DMA1 clock.  */RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);/*  Configure DMA1 Stream5 Channel_7 For DAC1  */
    DMA_InitStructure.DMA_Channel = DMA_Channel_7;  DMA_InitStructure.DMA_PeripheralBaseAdDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)Sine12bit ;DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_BufferSize = 100;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    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_Stream5, &DMA_InitStructure);DMA_Cmd(DMA1_Stream5, ENABLE); //Enable DMA1 Stream5.
}

需要注意的是,DAC->DHR12R1对应数据寄存器的地址,正弦波数组Sine12bit对应数据输入地址,DMA缓存的个数是单个正弦波周期对应的点数,DMA需工作在循环模式,由于正弦波数组Sine12bit定义为16位,那么涉及数据传输的变量都要配置成半字16位。经过上述的配置后,定时器TIM2每隔1us就会触发DMA搬运正弦波数组的一个数据到DAC通道1寄存器进行转换,每搬运100个数据即一个完整周期后,DMA开始循环,最终循环输出正弦波。

(5)主函数配置

本例程主函数主要对前文所述函数依次调用,程序编译下载至开发板,使用示波器测量PA4引脚即可查看输出10kHz的正弦波形,代码如下:

int main(void)   
{
    DAC1_GPIO_Init();
    TIM2_Init();
    DMA_InitForDAC();
    while (1);
}

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 26

在上一讲单通道ADC电压采集的基础上,本节主要介绍CKS32F4xx系列产品基于DMA传输的ADC多通道电压采集转换实现。

DMA传输在ADC中的应用

DMA是直接存储器存取,通常在使用ADC时,需要通过MCU内核不停的读取数据,如果使用DMA,那么读取的过程会绕过MCU,减轻MCU内核的处理压力,这样有利于资源的充分利用,提高ADC数据的处理效率。由于ADC规则通道组只有一个数据寄存器中,当转换多个通道时,使用DMA还可以避免丢失已经存储在ADC_DR寄存器中的数据。在使能DMA模式的情况下,每完成规则通道组中的一个通道转换后,都会生成一个DMA请求,便可将转换的数据从ADC_DR寄存器传输到用指定的目标内存位置。这样取代单通道实验使用中断服务的读取方法,可以实现多通道ADC应用中高速高效的采集。

软件设计要点

跟单通道例程一样,编写两个ADC驱动文件,bsp_adc.h和bsp_adc.c,用来存放ADC所用IO引脚的初始化函数以及ADC和DMA相关配置函数,主要流程为:

(1)初始化配置ADC目标引脚为模拟输入模式;

(2)使能ADC时钟和DMA时钟;

(3)配置DMA从ADC数据寄存器传输数据到指定的存储区;

(4)配置通用ADC为独立模式;

(5)设置ADC为12位分辨率,启动扫描,连续转换,不需要外部触发;

(6)设置ADC转换通道顺序及采样时间;

(7)使能DMA请求,DMA在AD转换完自动传输数据到指定的存储区;

(8)启动ADC模块;

(9)软件使能触发ADC转换。

这里需要注意的是,在使用ADC+DMA功能时,如果在启动ADC转换之后使能DMA,ADC采样数据可能会出现异常。因此建议先配置ADC及DMA相关参数,最后启动ADC转换。

代码实现

受篇幅限制,这里只介绍核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本课程配套的例程。相关核心代码实现如下:

(1)ADC宏定义

#define TEMP_NOFCHANEL      3
/*=====================通道1 IO======================*/
// PB0 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT1    GPIOB
#define TEMP_ADC_GPIO_PIN1     GPIO_Pin_0
#define TEMP_ADC_GPIO_CLK1     RCC_AHB1Periph_GPIOB
#define TEMP_ADC_CHANNEL1      ADC_Channel_8
/*=====================通道2 IO ======================*/
// PB1 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT2    GPIOB
#define TEMP_ADC_GPIO_PIN2     GPIO_Pin_1
#define TEMP_ADC_GPIO_CLK2     RCC_AHB1Periph_GPIOB
#define TEMP_ADC_CHANNEL2      ADC_Channel_9
/*=====================通道3 IO ======================*/
// PA6 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT3    GPIOA
#define TEMP_ADC_GPIO_PIN3     GPIO_Pin_6
#define TEMP_ADC_GPIO_CLK3     RCC_AHB1Periph_GPIOA
#define TEMP_ADC_CHANNEL3     ADC_Channel_6
// ADC 序号宏定义
#define TEMP_ADC              ADC1
#define TEMP_ADC_CLK          RCC_APB2Periph_ADC1
// ADC DR寄存器宏定义,ADC转换后的数字值则存放在这里
#define TEMP_ADC_DR_ADDR    ((u32)ADC1+0x4c)
// ADC DMA 通道宏定义,使用DMA传输
#define TEMP_ADC_DMA_CLK      RCC_AHB1Periph_DMA2
#define TEMP_ADC_DMA_CHANNEL  DMA_Channel_0
#define TEMP_ADC_DMA_STREAM   DMA2_Stream0

定义多个通道进行多通道ADC实验,并且定义DMA相关配置。

(2)ADC GPIO初始化

static void Temp_ADC_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    /*=====================通道1======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK1,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN1; // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;     //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT1, &GPIO_InitStructure);
    /*=====================通道2======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK2,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN2; // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;     //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT2, &GPIO_InitStructure);
    /*=====================通道3=======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK3,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN3;  // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;   //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT3, &GPIO_InitStructure);
}

使用到GPIO时候都必须开启对应的GPIO时钟,GPIO用于AD转换功能必须配置为模拟输入模式。

(3)配置ADC工作模式

static void Temp_ADC_Mode_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;     
    // --------------DMA Init 结构体参数初始化-------------     
    // ADC1使用DMA2,数据流0,通道0,
    RCC_AHB1PeriphClockCmd(TEMP_ADC_DMA_CLK, ENABLE);   // 开启DMA时钟
    DMA_InitStructure.DMA_PeripheralBaseAddr = TEMP_ADC_DR_ADDR; // 外设基址为:ADC 数据寄存器地址
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)ADC_ConvertedValue;  // AD值存储地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;   // 数据传输方向为外设到存储器
    DMA_InitStructure.DMA_BufferSize = TEMP_NOFCHANEL; // 缓冲区大小,指一次传输的数据量
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;// 外设寄存器只有一个,地址不递增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   // 存储器地址固定
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  // 外设数据大小为半字
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;   // 存储器数据大小也为半字
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环传输模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;// DMA传输通道优先级为高
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;    // 禁止DMA FIFO ,使用直连模式
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; // FIFO大小,FIFO禁止时不用配置
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
    DMA_InitStructure.DMA_Channel = TEMP_ADC_DMA_CHANNEL; // 选择 DMA 通道,通道存在于流中
    DMA_Init(TEMP_ADC_DMA_STREAM, &DMA_InitStructure);//初始化DMA流,
    DMA_Cmd(TEMP_ADC_DMA_STREAM, ENABLE); // 使能DMA流

    RCC_APB2PeriphClockCmd(TEMP_ADC_CLK , ENABLE);// 开启ADC时钟     
    // -------------ADC Common 结构体 参数初始化----------------
    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;// 独立ADC模式
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;// 时钟为fpclk x分频
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // 禁止DMA直接访问模式     
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;  // 采样时间间隔
    ADC_CommonInit(&ADC_CommonInitStructure);     
    // -------------------ADC Init 结构体 参数初始化--------------------------
    ADC_StructInit(&ADC_InitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;// ADC 分辨率
    ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式,多通道采集需要
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // 连续转换
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止外部边沿触发
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;  //外部触发通道,使用软件触发时此值随便赋值即可
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐ADC_InitStructure.ADC_NbrOfConversion = TEMP_NOFCHANEL;      //转换通道3个                             
    ADC_Init(TEMP_ADC, &ADC_InitStructure);

    
  // 配置 ADC 通道转换顺序和采样时间周期
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL1, 1, ADC_SampleTime_3Cycles);
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL2, 2, ADC_SampleTime_3Cycles);
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL3, 3, ADC_SampleTime_3Cycles);

    ADC_DMARequestAfterLastTransferCmd(TEMP_ADC, ENABLE); // 使能DMA请求      
    ADC_DMACmd(TEMP_ADC, ENABLE);// 使能ADC DMA
    ADC_Cmd(TEMP_ADC, ENABLE);  // 使能ADC
    ADC_SoftwareStartConv(TEMP_ADC);//开始ADC转换,软件触发
}

首先,使用DMA_InitTypeDef定义了DMA初始化类型变量,另外使用ADC_InitTypeDef和ADC_CommonInitTypeDef结构体分别定义一个ADC初始化和ADC通用类型变量。

调用RCC_APB2PeriphClockCmd()开启ADC时钟以及RCC_AHB1PeriphClockCmd()开启DMA时钟。

对DMA进行必要的配置。首先设置外设基地址就是ADC的规则数据寄存器地址;存储器的地址就是指定的数据存储区空间,ADC_ConvertedValue是我们定义的一个全局数组名,它是一个无符号16位含有3个元素的整数数组;ADC规则转换对应只有一个数据寄存器,所以地址不能递增,而定义的存储区是专门用来存放不同通道数据的,所以需要自动地址递增。ADC的规则数据寄存器只有低16位有效,实际存放的数据只有12位而已,所以设置数据大小为半字大小。ADC配置为连续转换模式,DMA也设置为循环传输模式。设置好DMA相关参数后就使能DMA的ADC通道。

接下来使用ADC_CommonInitTypeDef结构体变量ADC_CommonInitStructure来配置ADC为独立模式、分频系数4、20个周期的采样延迟,并调用ADC_CommonInit函数完成ADC通用工作环境配置。

使用ADC_InitTypeDef结构体变量ADC_InitStructure来配置ADC1为12位分辨率、使能扫描模式、启动连续转换、使用内部软件触发无需外部触发事件、使用右对齐数据格式、转换通道为3,并调用ADC_Init函数完成ADC1工作环境配置。

ADC_RegularChannelConfifig函数用来绑定ADC通道转换顺序和采样时间。分别绑定3个ADC通道引脚并设置相应的转换顺序。

ADC_DMARequestAfterLastTransferCmd函数控制是否使能ADC的DMA请求,如果使能请求,并调用ADC_DMACmd函数使能DMA,则在ADC转换完成后就请求DMA实现数据传输。ADC_Cmd函数控制ADC转换启动和停止。

最后使用软件触发调用ADC_SoftwareStartConvCmd函数进行使能配置。

(4)Main程序

/**  主函数  */
int main(void)
{
    Debug_USART_Config();
    Temp_Init();
    while (1)
    {
        ADC_ConvertedValueLocal[0] =(float) ADC_ConvertedValue[0]/4096*(float)3.3;
        ADC_ConvertedValueLocal[1] =(float) ADC_ConvertedValue[1]/4096*(float)3.3;
        ADC_ConvertedValueLocal[2] =(float) ADC_ConvertedValue[2]/4096*(float)3.3;
        printf("\r\n PB0 value = %f V \r\n",ADC_ConvertedValueLocal[0]);
        printf("\r\n PB1 value = %f V \r\n",ADC_ConvertedValueLocal[1]);
        printf("\r\n PA6 value = %f V \r\n",ADC_ConvertedValueLocal[2]);
        Delay(0xffffff);  
    }
}

主函数先调用Debug_USART_Config函数配置调试串口相关参数,接下来调用Temp_Init函数进行ADC初始化配置并启动ADC。配置了DMA数据传输,它会自动把ADC转换完成后数据保存到数组ADC_ConvertedValue内,我们只要使用数组就可以了。经过简单地计算就可以得到每个通道对应的实际电压。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 11

ADC可以将现实世界中连续变化的模拟量,如温度、压力、流量、速度、光强等,转换成离散的数字量,输入到计算机中进行处理。按照原理不同,ADC可以分为积分型、逐次逼近型(SAR)、并行比较型、Σ-∆调制型、电容阵列逐次比较型及压频变换型。逐次逼近型(SAR)ADC由比较器和DA转换器构成,它从最高有效MSB位开始通过逐次比较,按顺序以位位单位对输入电压与内置DA转换器输出电压进行比较,经过多次比较输出相应的数字值。逐次逼近型(SAR)ADC的电路规模属于中等,它的优点是速度较快、功耗低、成本适中。

ADC简介

CKS32F4xx系列产品内嵌3个12位SAR型ADC,每个ADC多达19个复用通道,可测量来自16个外部、2个内部和VBAT通道的信号,具有独立模式、双重模式和三种模式,并支持单次、连续、扫描或间断采样模式下进行A/D转换,对于不同AD转换要求几乎都有合适的模式可选,转换的结果可以按照左对齐或右对齐的方式存储在16位数据寄存器中。下图是CKS32F4xx系列产品ADC的结构框图,每个模块的具体描述和技术参数可以参阅《CKS32F4xx参考手册》。

1.png

ADC主要特性

▲ 分辨率支持12位,10位,8位,6位,可软件配置

▲ 支持转换结束产生中断、包括规则通道和注入通道

▲ 支持单次、连续、间隔转换模式

▲ 可独立设置各通道采样时间

▲ 规则通道转换和注入通道转换均有外部触发选项,支持软件或者硬件触发转换

▲ 支持模拟看门狗

▲ 支持双重或者三重模式(具有2个或以上ADC的器件)

▲ 支持双重或者三重模式下可配置的DMA数据存储

▲ 可配置双重或者三重交替采样模式下的转换延迟时间间隔

▲ ADC供电要求:全速模式下为2.4V到3.6V,低速模式下为1.8V

▲ ADC输入电压范围:Vref-≤Vin≤Vref+

▲ 规则通道转换期间可产生DMA请求

固件库中与ADC相关的主要API

CKS32F4xx系列固件库中与ADC相关的API定义在cks32f4xx_adc.h和cks32f4xx_adc.c两个文件中,前者为头文件,包含寄存器地址、常量定义、API函数声明,后者为API的具体实现。CKS32F4xx系列产品最多有3个ADC,编号分别为ADC1、ADC2、ADC3,大多数API的第一个参数为ADC_TypeDef*ADCx,表示操作的ADC实体编号。下表列出了常用的API函数:

API函数原型

函数功能

void ADC_DeInit(void)

复位ADC外设寄存器

void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct)

根据ADC_InitStruct中参数初始化ADC

void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct)

复位结构体成员

void ADC_CommonInit(ADC_CommonInitTypeDef* ADC_CommonInitStruct)

根据结构体参数初始化ADC外设

void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState)

使能或者禁止ADC外设

void ADC_TempSensorVrefintCmd(FunctionalState NewState)

使能或者禁止温度传感器通道

void ADC_VBATCmd(FunctionalState NewState)

使能或者禁止VBAT通道

void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime)

设置ADC规则通道的参数和采样时间

void ADC_SoftwareStartConv(ADC_TypeDef* ADCx)

软件启动ADC转换

FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx)

得到ADC转换状态

void ADC_EOCOnEachRegularChannelCmd(ADC_TypeDef* ADCx, FunctionalState NewState)

使能或者禁止每个规则转换通道的EOC

void ADC_ContinuousModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState)

使能或者禁止ADC的连续转换模式

void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number)

配置ADC规则组通道的间断模式

void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState)

使能或者禁止ADC的规则组通道

uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx)

得到规则通道最后一次转换的数值

void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState)

使能或者禁止ADC的DMA请求

void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime)

设置ADC注入通道的参数和采样时间

void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length)

配置注入通道序列长度

void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset)

设置注入通道转换值偏移

void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv)

配置注入通道的外部触发

void ADC_SoftwareStartInjectedConv(ADC_TypeDef* ADCx)

配置注入通道转换

void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState)

使能注入通道组的间断模式

void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState)

使能或者禁止ADC中断

FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG)

检测ADC特定标志是否设置

void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG)

清除ADC标志

ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT)

通过相应标志检测是否发生相应中断

void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT)

清除中断挂起位

实现ADC单通道电压采集

CKS32F4xx系列的ADC功能繁多,我们设计几个实验尽量完整的展示ADC的功能。本次课堂主要介绍比较基础实用的单通道电压采集,实现开发板上引脚电压的采集并通过串口打印至PC端串口调试助手。单通道电压采集适用AD转换完成中断,在中断服务函数中读取数据,不使用DMA传输,在后期多通道采集实验中再使用DMA传输。相关核心代码实现如下:

(1)ADC宏定义

// ADC GPIO 宏定义
#define TEMP_ADC_GPIO_PORT GPIOB
#define TEMP_ADC_GPIO_PIN GPIO_Pin_0
#define TEMP_ADC_GPIO_CLK RCC_AHB1Periph_GPIOB
// ADC 序号宏定义
#define TEMP_ADC ADC1
#define TEMP_ADC_CLK RCC_APB2Periph_ADC1
#define TEMP_ADC_CHANNEL ADC_Channel_8
// ADC 中断宏定义
#define Temp_ADC_IRQ ADC_IRQn
#define Temp_ADC_INT_FUNCTION ADC_IRQHandler

(2)ADC GPIO初始化

static void Temp_ADC_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK, ENABLE);// 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN;  // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;    // 配置为模拟输入
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;  //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT, &GPIO_InitStructure);
}

使用到GPIO时候都必须开启对应的GPIO时钟,GPIO用于AD转换功能必须配置为模拟输入模式。

(3)配置ADC工作模式

static void Temp_ADC_Mode_Config(void)
{
    ADC_InitTypeDef ADC_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;
    RCC_APB2PeriphClockCmd(TEMP_ADC_CLK , ENABLE);     // 开启ADC时钟 
    
    // -------------------ADC Common 结构体 参数 初始化------------------------
    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; // 独立ADC模式
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;// 时钟为fpclk x分频
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;// 禁止DMA直接访问模式
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;   // 采样时间间隔 ADC_CommonInit(&ADC_CommonInitStructure);

    // -------------------ADC Init 结构体 参数 初始化--------------------------
    ADC_StructInit(&ADC_InitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; // ADC 分辨率
    ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 禁止扫描模式,多通道采集才需要
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // 连续转换
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止外部边沿触发
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; //外部触发通道,本例子使用软件触发,此值随便赋值即可
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
    ADC_InitStructure.ADC_NbrOfConversion = 1;         //转换通道 1个                      
    ADC_Init(TEMP_ADC, &ADC_InitStructure);

    // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
    ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL, 1, ADC_SampleTime_56Cycles);
    // ADC 转换结束产生中断,在中断服务程序中读取转换值
    ADC_ITConfig(TEMP_ADC, ADC_IT_EOC, ENABLE);
    ADC_Cmd(TEMP_ADC, ENABLE);  // 使能ADC
    ADC_SoftwareStartConv(TEMP_ADC);//开始ADC转换,软件触发
}

首先,使用ADC_InitTypeDef和ADC_CommonInitTypeDef结构体分别定义一个ADC初始化和ADC通用类型变量。调用RCC_APB2PeriphClockCmd()开启ADC时钟。接下来使用ADC_CommonInitTypeDef结构体变量ADC_CommonInitStructure来配置ADC为独立模式、分频系数为2、不需要设置DMA模式、20个周期的采样延迟,并调用ADC_CommonInit函数完成ADC通用工作环境配置。我们使用ADC_InitTypeDef结构体变量ADC_InitStructure来配置ADC1为12位分辨率、单通道采集不需要扫描、启动连续转换、使用内部软件触发无需外部触发事件、使用右对齐数据格式转换通道为1,并调用ADC_Init函数完成ADC1工作环境配置。

ADC_RegularChannelConfifig函数用来绑定ADC通道转换顺序和时间。它接收4个形参,第一个形参选择ADC外设,可为ADC1、ADC2或ADC3;第二个形参通道选择,总共可选18个通道;第三个形参为转换顺序,可选为1到16;第四个形参为采样周期选择,采样周期越短,ADC转换数据输出周期就越短但数据精度也越低,采样周期越长,ADC转换数据输出周期就越长同时数据精度越高。PC3对应ADC通道ADC_Channel_13,这里我们选择ADC_SampleTime_56Cycles即56周期的采样时间。

利用ADC转换完成中断可以非常方便的保证我们读取到的数据是转换完成后的数据而不用担心该数据可能是ADC正在转换时“不稳定”数据。我们使用ADC_ITConfifig函数使能ADC转换完成中断,并在中断服务函数中读取转换结果数据。ADC_Cmd函数控制ADC转换启动和停止。

最后使用软件触发调用ADC_SoftwareStartConvCmd函数进行使能配置。

4)在Temp_ADC_NVIC_Config函数中配置ADC转换完成中断的优先级分组和优先级。

static void Temp_ADC_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 配置优先级分组
    NVIC_InitStructure.NVIC_IRQChannel = Temp_ADC_IRQ;  // 配置中断优先级
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

(5)ADC中断服务程序

void ADC_IRQHandler(void)
{
    if(ADC_GetITStatus(TEMP_ADC,ADC_IT_EOC)==SET)
    {
        ADC_ConvertedValue = ADC_GetConversionValue(TEMP_ADC);  // 读取ADC的转换值
    }
    ADC_ClearITPendingBit(TEMP_ADC,ADC_IT_EOC);
}

中断服务函数一般定义在cks32f4xx_it.c文件内,我们使能了ADC转换完成中断,因此在ADC转换完成后就会进入中断服务函数,在中断服务函数内直接读取ADC转换结果保存在变量ADC_ConvertedValue(在main.c中定义)中。ADC_GetConversionValue函数是获取ADC转换结果值的库函数,只有一个形参为ADC外设,可选为ADC1、ADC2或ADC3,该函数返回一个16位的ADC转换结果值。

(6)Main程序

int main(void)
{
    Debug_USART_Config(); //初始化USART1
    Temp_Init();while(1)
    {   
        ADC_Vol =(float) ADC_ConvertedValue/4096*(float)3.3; // 读取转换的AD值
        printf("\r\n The current AD value = %f V \r\n",ADC_Vol);     
        Delay(0xffffee);  
    }
}

主函数先配置调试串口相关参数,接下来调用Temp_Init函数进行ADC初始化配置并启动ADC。在ADC中断服务函数中把AD转换结果保存在变量ADC_ConvertedValue中,可以计算出对应的IO口电压值,最后把相关数据打印至串口调试助手。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 19

本章中,我们主要对CKS32F4xx系列的待机模式(STANDBY)做详细介绍。在该模式下,芯片功耗最低,1.2V供电区域、PLL、HSI和HSE振荡器也完全被关闭。除备份域(RTC寄存器、RTC备份寄存器和备份SRAM)和待机电路中的寄存器外,SRAM和寄存器内容丢失。因此,从待机模式唤醒后,只能从头开始执行程序,那我们如何进入STANDBY模式及唤醒方式,可以按照下述表格中的步骤执行即可:

1.jpg

CKS32F4xx系列标准库把进入STANDBY模式这部分的操作封装到PWR_EnterSTANDBYMode函数中了,需要先通过PWR_CR设置PDDS位以及SLEEPDEEP位,使得芯片进入深度睡眠时进入待机模式,接着调用__force_stores函数确保存储操作完毕后再调用WFI指令,从而进入待机模式。需要注意的是,调用本函数前,还需要清空WUF 寄存器位才能进入待机模式。

RTC时钟简介

CKS32F4xx系列的RTC,是一个独立的BCD定时器/计数器,RTC提供一个日历时钟(包含年月日时分秒信息)、两个可编程闹钟(ALARM A和ALARM B)中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC还包含用于管理低功耗模式的自动唤醒单元。两个32位寄存器包含二进码十进数格式(BCD)的秒、分钟、小时(12或24小时制)、星期几、日期、月份和年份。此外,还可提供二进制格式的亚秒值。系统可以自动将月份的天数补偿为28、29(闰年)、30和31天。并且还可以进行夏令时补偿。其它32位寄存器还包含可编程的闹钟亚秒、秒、分钟、小时、星期几和日期。此外,还可以使用数字校准功能对晶振精度的偏差进行补偿。RTC模块和时钟配置是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变,只要后备区域供电正常,那么RTC将可以一直运行。但是在系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前,先要取消备份区域(BKP)写保护。RTC的框图,如下图所示:

2.png

采用RTC周期性唤醒STANDBY模式实验

程序设计主要要点如下:

① RTC初始化;

② RTC周期性自动唤醒;

③ 清除WUF标志位,进入待机状态。

1)初始化RTC配置函数

void CKS_RTC_Init(void)
{  
    uint16_t retry = 0x1FFF;   
    RTC_InitTypeDef  RTC_InitStructure;
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);   
    PWR_BackupAccessCmd(ENABLE); 
   
   RCC_LSEConfig(RCC_LSE_ON);     
   while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)    
   {    
       retry--;     
       Delay(0xffff);  
   }  
   if(retry == 0)  
   {    
       return 1;   
   }  
   RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);       
   RCC_RTCCLKCmd(ENABLE);   
   
   RTC_InitStructure.RTC_AsynchPrediv  =  0x7F;  
   RTC_InitStructure.RTC_SynchPrediv   =  0xFF;  
   RTC_InitStructure.RTC_HourFormat   =  RTC_HourFormat_24;  
   RTC_Init(&RTC_InitStructure);
}

在CKS_RTC_Init函数中,用来初始化RTC配置以及日期和时钟,但只在首次设置时间,随后重新上电/复位都不再进行时间设置(前提是备份电池有电)。为了时间更为精准,这里选用了LSE,即外部32.768kHz晶振作为RTC_CLK的时钟源,而RTC时钟核心,要求提供1Hz的时钟,所以接着是设置RTC_CLK的预分频系数,包括异步和同步两个,这里设置异步分频因子为ASYNCHPREDIV为(127),同步分频因子为ASYNCHPREDIV(255),则产生的时钟CK_SPRE=32.768/(127+1)*(255+1)=1HZ,即每秒更新一次。

2)RTC周期性唤醒配置函数

void RTC_Set_WakeUp(uint32_t wksel, uint16_t cnt)
{   
    NVIC_InitTypeDef   NVIC_InitStructure;  
    EXTI_InitTypeDef   EXTI_InitStructure;
   
   RTC_WakeUpCmd(DISABLE);  
   RTC_WakeUpClockConfig(wksel);   
   RTC_SetWakeUpCounter(cnt-1); 
  
   RTC_ClearITPendingBit(RTC_IT_WUT);   
   EXTI_ClearITPendingBit(EXTI_Line22);    
   
   RTC_ITConfig(RTC_IT_WUT, ENABLE);   
   RTC_WakeUpCmd(ENABLE); 
   
   EXTI_InitStructure.EXTI_Line = EXTI_Line22;  
   EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;   
   EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;   
   EXTI_InitStructure.EXTI_LineCmd = ENABLE;  
   EXTI_Init(&EXTI_InitStructure); 
   
   NVIC_InitStructure.NVIC_IRQChannel = RTC_WKUP_IRQn;   
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;  
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;  
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   
   NVIC_Init(&NVIC_InitStructure); 
}

在RTC_Set_WakeUp函数中,首先通过RTC_WakeUpCmd函数,关闭WakeUp,接着调用RTC_WakeUpClockConfig函数,配置WakeUp时钟分频系数及来源,然后通过调用RTC_SetWakeUpCounter,设置WakeUp自动装载寄存器,随后使能WakeUp,最后开启配置闹钟中断以及NVIC中断优先级。鉴于此处为RTC唤醒待机实验,仅做demo例程使用,因而不用编写中断服务函数。

3)芯片进入STANDBY模式

查阅CKS32F4xx系列标准库及相关手册,我们了解到使能RTC周期性唤醒,在进入STANDBY模式前,需要进行以下操作,代码如下:

void CKS_Set_Standby_Mode(void)
{      
    RTC_ITConfig(RTC_IT_TS|RTC_IT_WUT|RTC_IT_ALRB|RTC_IT_ALRA, DISABLE);   
    RTC_ClearITPendingBit(RTC_IT_TS|RTC_IT_WUT|RTC_IT_ALRB|RTC_IT_ALRA);   
    PWR_ClearFlag(PWR_FLAG_WU);    
    
    RTC_Set_WakeUp(RTC_WakeUpClock_CK_SPRE_16bits, 3);   
    
    PWR_EnterSTANDBYMode();         
}

在CKS_Set_Standby_Mode函数中,先禁止RTC中断(ALRAIE、ALRBIE、TSIE、WUTIE和TAMPIE等),接着清零对应中断标志位,以及清除PWR唤醒(WUF)标志,然后调用RTC_Set_WakeUp函数,设置每3s后唤醒STANDBY模式,同时该函数中也重新使能RTC对应中断,最后调用PWR_EnterSTANDBYMode进入STANDBY模式。

4)主函数配置

本例程中主函数主要对上文所述函数调用,程序编译下载至开发板,先进行相关外设初始化后,直接进入STANDBY模式,每隔3s由RTC唤醒,随即又进入STANDBY模式,循环往复,主函数代码如下:

int main(void)
{    
    CKS_RTC_Init();          
    while (1)  
    {            
        CKS_Set_Standby_Mode();      
    }
}

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 11

页面

订阅 RSS - CKS32F4xx