干货

MAX32630FTHR设计笔记

分类名:DIY日期:2017-07-08作者:Justice_Gao
分享到
微博
QQ
微信
LinkedIn

0.MAX32630开发工具和必备资料

MAX32630底层外设驱动的库函数库函数.c文件和头文件以及测试源代码:

 ARMCortexToolchain.rar 

MAX32630FTHR应用平台资料:硬件介绍及配置,原理图等 

 MAX32630FTHR.pdf 
MAX3263X  AN6349.pdf 

Getting Started in Eclipse User Guide - 用户指南 - Maxim:

 TUT6245.pdf 
MAX32630芯片的寄存器详细资料,如果想要熟悉这块芯片,这个文件必备,有些寄存器配置是关键:
MAX32630物理特性资料:

 MAX32630-MAX32631.pdf 


1.新建Keil工程文件(适用于KEIL MDK开发环境)

打开KEIL MDK,我用的是KEIL MDK V5.23版本的。设置好工程存放位置后,会出现Manage Run-Time environment的窗口,如下所示:

   

然后选择相应的配置(根据你本人需要用到的底层驱动选择相应的配置,后期可加减),如下所示:

  
接下来就是配置环境了,需要配置的东西不多,如图所示:

  
我的工程文件BOARD是评估板,现在我们使用了MAX32630FHR,可以不管这个BOARD的值
最后,完成工程的新建。

这里,我打开一个源代码(ADC采集程序),编译一下,可以看到编译通过,如图
  

所有源代码在开发工具中,前面已经给出。


2.流水灯(GPIO输出配置)及I2C驱动MAX14690

给大家提供一个GPIO口的输出配置及MAX14690电池管理芯片的驱动配置

/*******************************************************************
内容:GPIO口配置,输出,及I2C驱动MAX14690
作者:Justice_Gao
日期:2017年7月2日
开发工具:KEIL MDK
版本:V5.23
*******************************************************************/

(1)三色灯D1的GPIO口底层驱动
  

    如图所示,三色灯分别与MAX32630的P2_4、P2_5和P2_6连接,当P2_4=0、P2_5=1、P2_6=1时,亮红灯;当P2_4=1、P2_5=0、P2_6=1时,亮绿灯;当P2_4=1、P2_5=1、P2_6=0时,亮蓝灯;
则初始化GPIO的程序为

const gpio_cfg_t LED[] = {
    { PORT_2, PIN_4, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN_PULLUP },
    { PORT_2, PIN_5, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN_PULLUP },
    { PORT_2, PIN_6, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN_PULLUP },
};

void GPIO_Initial()
{
    GPIO_Config(&LED[0]);
    GPIO_Config(&LED[1]);
    GPIO_Config(&LED[2]);
     
}



这里我延伸一下,配置GPIO的输出类型有两种方法。

(1)GPIO口的配置及应用
/******************************************************************
max32630的GPIO的初始化设置相比较STM32F4的GPIO口初始化设置更为简单
初始化方法有两种:
(1)类似于STM32的方法:配置P3_0和P3_1管脚为输出管脚
gpio_cfg_t GPIO_OUTPUT_Pin; 

typedef struct {
    uint32_t port;      /// Index of GPIO port
    uint32_t mask;      /// Pin mask. Multiple bits can be set.
    gpio_func_t func;   /// Function type
    gpio_pad_t pad;     /// Pad type
} gpio_cfg_t;


GPIO_OUTPUT_Pin.port = PORT_3; //设置端口P0~P4
GPIO_OUTPUT_Pin.mask = PIN_0|PIN_1;                //设置pin位管脚,0~7
GPIO_OUTPUT_Pin.func = GPIO_FUNC_GPIO;//GPIO Function Selection 
GPIO_OUTPUT_Pin.pad  = GPIO_PAD_OPEN_DRAIN;// 适合有外设上拉电阻的输出管脚
GPIO_Config(&GPIO_OUTPUT_Pin);//初始化



则控制输出高低电平可直接调用函数GPIO_OutSet和GPIO_OutClr,
如输出高电平,则GPIO_OutSet(&GPIO_OUTPUT_Pin);
输出低电平,GPIO_OutClr(&GPIO_OUTPUT_Pin);


缺点:这种方法不适用设置多个输出类型pin管脚,比如P3_1输出高电平,P3_0输出低电平,这种方法就不适合


/*===================================================================
(2)第二种方法,可以说比前一种方便快捷的多:设置4个输出控制引脚

const gpio_cfg_t GAN_COL_Pin[] = {
    { PORT_4, PIN_0, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN },
    { PORT_4, PIN_1, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN },
    { PORT_4, PIN_2, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN },
    { PORT_4, PIN_3, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN },
};

在使用这些GPIO口前,首先需要初始化,

for(uint8_t i=0;i<4;i++)
{
        GPIO_Config(&GAN_COL_Pin);
}

然后直接调用输出,如

                        GPIO_OutSet(&GAN_COL_Pin[0]);  //P4_0输出高电平
                        GPIO_OutSet(&GAN_COL_Pin[1]);  //P4_1输出高电平
                        GPIO_OutClr(&GAN_COL_Pin[2]);  //P4_2输出低电平
                        GPIO_OutClr(&GAN_COL_Pin[3]);         //P4_3输出低电平


======================================================================*/


言归正传,下面我们来介绍I2C驱动MAX14690部分
(2)I2C驱动MAX14690
如果主函数对MAX14690没有初始化,则LDO2和LDO3不会输出电压3V3和L3OUT。代码如下:

int MAX14690_Init(const max14690_cfg_t *max14690cfg)
{
    uint8_t addr;
    uint8_t data[2];

    /* Setup the I2CM Peripheral to talk to the MAX14690 */
    I2CM_Init(MAX14690_I2CM, &max14690_sys_cfg, I2CM_SPEED_100KHZ);

    /* Attempt to read the ID from the device */
    addr = MAX14690_REG_CHIP_ID;
    if (I2CM_Read(MAX14690_I2CM, MAX14690_I2C_ADDR, &addr, 1, data, 2) != 2) {
        return E_COMM_ERR;
    }

    /* Configure the initial state of LDO2 */
    if (MAX14690_LDO2SetV(max14690cfg->ldo2mv) != E_NO_ERROR) {
        return E_COMM_ERR;
    }
    if (MAX14690_LDO2SetMode(max14690cfg->ldo2mode) != E_NO_ERROR) {
        return E_COMM_ERR;
    }
    /* Configure the initial state of LDO3 */
    if (MAX14690_LDO3SetV(max14690cfg->ldo3mv) != E_NO_ERROR) {
        return E_COMM_ERR;
    }    
    if (MAX14690_LDO2SetMode(max14690cfg->ldo2mode) != E_NO_ERROR) {
        return E_COMM_ERR;
    }
    return E_NO_ERROR;
}



其中#define MAX14690_I2CM       MXC_I2CM2   /**< Using I2C Master 0 Base Peripheral Address. */

/**
* I2C Master system configuration object for communicating with the MAX14690. 
*/
const sys_cfg_i2cm_t max14690_sys_cfg = {
        .clk_scale = CLKMAN_SCALE_DIV_1,
        .io_cfg = IOMAN_I2CM2(IOMAN_MAP_A, 1)
};


这些都在安装开发工具中都有。需要注意的是,这个开发工具安装好以后,源代码都是MAX32630-EVKIT评估板上的源代码,比如源代码驱动MAX14690是使用I2CM0的,而MAX32630FTHR应用平台是使用I2CM2的,需要做相应的修改才能驱动成功。

总结,其实从MAX32630-EVKIT评估板上移植程序到MAX32630FTHR应用平台上并不难,关键是看懂资料,所有资料都在这里开发必备资料。另外附上MAX32630-EVKIT评估板的原理图资料,大家可以做个比较,改程序就简单了 MAX32630-EVKIT-MAX32631-EVKIT.pdf 


最后给出我的main函数的程序:

/**
* @file    main.c
*

*/
#define MAX14690_I2C_ADDRESS       (0x50 >> 1)
#define MAX14690_I2CM2       MXC_I2CM2   /**< Using I2C Master 0 Base Peripheral Address. */
/***** Includes *****/
#include <stdio.h>
#include <stdint.h>
#include "mxc_config.h"
#include "board.h"
#include "lp.h"
#include "gpio.h"
#include "tmr.h"
#include "tmr_utils.h"
#include "max14690.h"
#include "i2cm.h"
const max14690_cfg_t max14690_cfg2 = {
  .ldo2mv = 3300, /**< 3.3v in mV, connected to VDDB */
  .ldo2mode = MAX14690_LDO_MPC1, /**< Enalbe LDO2 when +5v is present on VBUS */
  .ldo3mv = 3300,  /**< 3.3v is L3OUT -- optional */
  .ldo3mode = MAX14690_LDO_ENABLED /**< Enable the LDO. */
};
const gpio_cfg_t LED[] = {
    { PORT_2, PIN_4, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN_PULLUP },
    { PORT_2, PIN_5, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN_PULLUP },
    { PORT_2, PIN_6, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN_PULLUP },
};


void GPIO_Initial()
{
    GPIO_Config(&LED[0]);
    GPIO_Config(&LED[1]);
    GPIO_Config(&LED[2]);
     
}
#define CLOCK 23
/*------------------------------------------------------------
                         usÑóê±oˉêy 
------------------------------------------------------------*/
void delay_us(uint32_t us)
{
    uint32_t n;         
    while(us--)for(n=0;n<CLOCK;n++);      
}

/*------------------------------------------------------------
                         msÑóê±oˉêy
------------------------------------------------------------*/
void delay_ms(uint32_t ms)
{
    while(ms--)delay_us(1000);   
}

/******************************************************************************/
int main(void)
{
    GPIO_Initial();
    MAX14690_Init(&max14690_cfg2);
    while(1)
    {
            GPIO_OutClr(&LED[0]);  
            GPIO_OutSet(&LED[1]);  
            GPIO_OutSet(&LED[2]); 
            delay_ms(1000); 
            GPIO_OutSet(&LED[0]);  
            GPIO_OutClr(&LED[1]);  
            GPIO_OutSet(&LED[2]); 
            delay_ms(1000); 
            GPIO_OutSet(&LED[0]);  
            GPIO_OutSet(&LED[1]);  
            GPIO_OutClr(&LED[2]); 
            delay_ms(1000); 
    }
    
}



实验现象:



3.GPIO的输入模式配置及输出模式需要注意的问题

前面讲了GPIO的输出模式配置,现在来讲下GPIO的输入模式配置。


(1)GPIO的输入模式配置
/************************************************************************
获取GPIO输入电平状态:调用函数GPIO_InGet();
举例:获取P3_3的输入电平状态


第一步:初始化,可以选择以上两种方法,这里我选择第二种方法

const gpio_cfg_t ADDR_Pin[]={
        { PORT_3, PIN_3, GPIO_FUNC_GPIO, GPIO_PAD_INPUT_PULLUP },
        { PORT_3, PIN_4, GPIO_FUNC_GPIO, GPIO_PAD_INPUT_PULLUP }
};


第二步:调用函数获取电平状态

GPIO_InGet(&ADDR_Pin[0]);

需要注意的是,和STM32F不同,这里获取不是逻辑1和0电平,这里GPIO_InGet(&ADDR_Pin[0])=0x00000008,
很好理解,对应的P3_3为高电平1,若其他为0,得到的值为0x00000008


缺点:在进行逻辑判断时,需要计算哪一位为1,比如说P3_7的第八位,对应的值需要计算


优化:max32625的库函数是只读属性,如果想要获取逻辑1和0状态,类似STM32F4的获取方法,则可修改函数GPIO_InGet()
首先,将gpio.h文件的只读属性去掉,源代码如下:

__STATIC_INLINE uint32_t GPIO_InGet(const gpio_cfg_t *cfg)
{
    return (MXC_GPIO->in_val[cfg->port] & cfg->mask);
}

然后,可将上面源代码改为

__STATIC_INLINE uint32_t GPIO_InGet(const gpio_cfg_t *cfg)
{
    return ((MXC_GPIO->in_val[cfg->port] & cfg->mask)>>(uint8_t )(log(cfg->mask)/log(2)));
}


这样的话,在进行if逻辑判断时,只需要判断GPIO_InGet()==1还是==0就可以了


**************************************************************************************************/


(2)输出模式需要注意的问题
/************************************************************************
评估板的源程序代码里面有个Board_Init();,这个函数在system_max3263x.c文件中的void $Sub$$__main_after_scatterload(void)函数中,
Board_Init()在系统上电后就运行,主要作用是初始化评估板的所有GPIO口,所以,如果你不注释掉会出现问题,比如当你设置GPIO口输出低电平,但实际输出为高电平。如果自己移植到代码时,可以自己定义GPIO口的设置,下面是Board_Init函数的内容,主要是对串口,LED,按键输入,以及MAX14690控制GPIO进行了初始化设置

int Board_Init(void)
{
    int err;

    if ((err = Console_Init()) != E_NO_ERROR) {
        MXC_ASSERT_FAIL();
        return err;
    }

    if ((err = LED_Init()) != E_NO_ERROR) {
        MXC_ASSERT_FAIL();
        return err;
    }

    if ((err = PB_Init()) != E_NO_ERROR) {
        MXC_ASSERT_FAIL();
        return err;
    }

    /* Configure PMIC voltages */
    if ((err = MAX14690_Init(&max14690_cfg)) != E_NO_ERROR) {
        MXC_ASSERT_FAIL();
        return err;
    }
    /* Configure PMIC LDO output on VBUS detection */
    if ((err = MAX14690_InterruptInit()) != E_NO_ERROR) {
      MXC_ASSERT_FAIL();
      return err;
    }

    return E_NO_ERROR;
}


(3)GPIO口设置输出1,结果为低电平
如果GPIO口外设接口有1.8V上拉电阻,则输出pad类型可以为GPIO_PAD_OPEN_DRAIN或GPIO_PAD_OPEN_DRAIN_PULLUP,
则逻辑1时输出2.4V高电平,逻辑0时输出0V,配置如下

const gpio_cfg_t GAN_COL_Pin[] = {
    { PORT_4, PIN_0, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN },
    { PORT_4, PIN_1, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN },
    { PORT_4, PIN_2, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN },
    { PORT_4, PIN_3, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN },
};

如果GPIO扣外设接口没有上拉电阻,为浮空输出,则输出pad类型为GPIO_PAD_OPEN_DRAIN或GPIO_PAD_NORMAL时,
无论逻辑1还是逻辑0输出都为低电平,为解决这种问题,需将输出pad类型设置为GPIO_PAD_OPEN_DRAIN_PULLUP,即

const gpio_cfg_t GAN_COL_Pin[] = {
    { PORT_4, PIN_0, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN_PULLUP },
    { PORT_4, PIN_1, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN_PULLUP },
    { PORT_4, PIN_2, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN_PULLUP },
    { PORT_4, PIN_3, GPIO_FUNC_GPIO, GPIO_PAD_OPEN_DRAIN_PULLUP },
};


两种方法比较下来,对于不熟悉STM32开发的新手来说,设置pad类型GPIO_PAD_OPEN_DRAIN_PULLUP较好


4.没有KEIL的PACK怎么办?用eclipse开发也行

首先可在本文最开始的地方找到安装开发工具MAX32630FTHR进行安装。
安装完成后,在“开始”菜单中找到eclipse,如图

  
进入eclipse以后,导入工程,工程文件地址为:MaximFirmwareMAX3263XApplications,导入成功后如图
  

编译工程,下载烧录方式见此文件  README.pdf 


5.10位ADC采样(解析及注意事项),中断式和非中断式

给大家分享一下MAX32630的ADC采样程序,包括中断式和非中断式。
首先,我们来比较一下STM32F4与MAX32630在ADC方面的区别:
STM32F4:
(1)12位ADC
(2)支持DMA
(3)每个ADC通道自带数据寄存器
(4)最大转换速率2.4MHz
MAX32630:
(1)10位ADC
(2)不支持DMA
(3)每个ADC通道共用一个数据寄存器
(4)最大转换速率8MHz

下面给出非中断式和中断式的ADC采样程序

/***** Includes *****/
#include <stdio.h>
#include <stdint.h>
#include "mxc_config.h"
#include "led.h"
#include "adc.h"
#include "nhd12832.h"
#include "board.h"
#include "max14690.h"
#include "tmr_utils.h"

/***** Definitions *****/
const max14690_cfg_t max14690_cfg2 = {
  .ldo2mv = 3300, /**< 3.3v in mV, connected to VDDB */
  .ldo2mode = MAX14690_LDO_MPC1, /**< Enalbe LDO2 when +5v is present on VBUS */
  .ldo3mv = 3300,  /**< 3.3v is L3OUT -- optional */
  .ldo3mode = MAX14690_LDO_ENABLED /**< Enable the LDO. */
};


#define CLOCK 23
/*------------------------------------------------------------
                         usÑóê±oˉêy 
------------------------------------------------------------*/
void delay_us(uint32_t us)
{
        uint32_t n;                    
        while(us--)for(n=0;n<CLOCK;n++);          
}

/*------------------------------------------------------------
                         msÑóê±oˉêy
------------------------------------------------------------*/
void delay_ms(uint32_t ms)
{
        while(ms--)delay_us(1000);         
}

/* Change to #undef USE_INTERRUPTS for polling mode */
#define USE_INTERRUPTS 1
//#undef USE_INTERRUPTS  注释掉为中断式,不注释则为非中断式
/***** Globals *****/
#ifdef USE_INTERRUPTS
volatile unsigned int adc_done = 0;
#endif

/***** Functions *****/

#ifdef USE_INTERRUPTS
void AFE_IRQHandler(void)
{
    ADC_ClearFlags(MXC_F_ADC_INTR_ADC_DONE_IF);
    /* Signal bottom half that data is ready */
    adc_done = 1;
    
    return;
}
#endif
/* ************************************************************************* */
void ADC0_StartConvert(mxc_adc_chsel_t channel, unsigned int adc_scale, unsigned int bypass)
{
  uint32_t ctrl_tmp;

  /* Clear the ADC done flag */
  ADC_ClearFlags(MXC_F_ADC_INTR_ADC_DONE_IF);
        MXC_ADC->intr |= MXC_F_ADC_INTR_ADC_DONE_IE;
  /* Insert channel selection */
  ctrl_tmp = MXC_ADC->ctrl;
  ctrl_tmp &= ~(MXC_F_ADC_CTRL_ADC_CHSEL);
  ctrl_tmp |= ((channel << MXC_F_ADC_CTRL_ADC_CHSEL_POS) & MXC_F_ADC_CTRL_ADC_CHSEL);
  
  /* Clear channel configuration */
  ctrl_tmp &= ~(MXC_F_ADC_CTRL_ADC_REFSCL | MXC_F_ADC_CTRL_ADC_SCALE | MXC_F_ADC_CTRL_BUF_BYPASS);

  /* ADC reference scaling must be set for all channels but two*/
  if ((channel != ADC_CH_VDD18) && (channel != ADC_CH_VDD12)) {
    ctrl_tmp |= MXC_F_ADC_CTRL_ADC_REFSCL;
  }

  /* Finalize user-requested channel configuration */
  if (adc_scale || channel > ADC_CH_3) {
    ctrl_tmp |= MXC_F_ADC_CTRL_ADC_SCALE;
  }
  if (bypass) {
    ctrl_tmp |= MXC_F_ADC_CTRL_BUF_BYPASS;
  }
  
  /* Write this configuration */
  MXC_ADC->ctrl = ctrl_tmp;
  
  /* Start conversion */
  MXC_ADC->ctrl |= MXC_F_ADC_CTRL_CPU_ADC_START;

}
int main(void)
{
    uint16_t adc_val[4];
    unsigned int overflow[4];
    uint8_t fmtstr[40];   
    /* Initialize ADC */
    ADC_Init();
                MAX14690_Init(&max14690_cfg2);
    
#ifdef USE_INTERRUPTS
    NVIC_EnableIRQ(AFE_IRQn);
#endif
    
    while(1) {

        /* Convert channel 0 */
#ifdef USE_INTERRUPTS
        adc_done = 0;
        ADC0_StartConvert(ADC_CH_0, 0, 1);
        while (!adc_done);
#else
        ADC_StartConvert(ADC_CH_0, 0, 1);
#endif
        overflow[0] = (ADC_GetData(&adc_val[0]) == E_OVERFLOW ? 1 : 0);
        /* Delay for 1/4 second before next reading */
        TMR_Delay(MXC_TMR0, MSEC(250));

    }
}


用KEIL开发的小伙伴们需要注意的几个问题是:
(1)//#undef USE_INTERRUPTS  注释掉为中断式,不注释则为非中断式
(2)ADC的参考电压为内部参考电压1.2V
 

 

(3)ADC源代码中断式采样会出现死循环,原因在于ADC_ClearFlags(MXC_F_ADC_INTR_ADC_DONE_IF);这条语句把ADC中断使能也清零了,如图所示,adc_done_ie清零,无法进入中断,程序会一直while (!adc_done);死循环。在源代码的void ADC0_StartConvert(这是我自己定义的函数,源代码中的为void ADC_StartConvert)中,在ADC_ClearFlags(MXC_F_ADC_INTR_ADC_DONE_IF);后加一条语句MXC_ADC->intr |= MXC_F_ADC_INTR_ADC_DONE_IE;,重新使能ADC中断,编译后测试成功
 

 


6.如何解决Undefined symbol __use_two_region_memory 问题

开发环境:KEIL MDK 5.23

MCU型号:MAX32630

在编译工程文件是,出现Undefined symbol __use_two_region_memory 和Undefined symbol __initial_sp,如下图所示

  

知其然就要知其所以然,我们先来了解一下 __use_two_region_memory是什么东西吧。

use_two_region_memory用于指定存储器模式为双段模式,即一部分储存区用于栈空间,其他的存储区用于堆空间,堆区空间可以为0。


在汇编代码中,通过 IMPORT __use_two_region_memory 表明使用双段模式;
在C语言中,通过 #pragma import(__use_two_region_memory)语句表明使用双段模式。
__use_two_region_memory是用在 startup.S里面的
如果__use_two_region_memory 未定义:在 startup.S加入即可。

如果在 startup.S有定义,则应该按照一下操作,如图勾选Use MircroLib就不会出现这种问题,编译通过



7.AD测量范围的设置,四种(及注意事项)


/*******************************************************************
内容:ADC配置,AIN0~AIN3
作者:Justice_Gao
日期:2017年7月15日
问题说明:
源代码程序的ADC采样使用的参考电压为内部参考电压1.2V,可测量的电压值范围为0~0.6V(参考电压/2),
这里我将提供ADC初始化设置,提供多种可测量电压值范围:
a.0~0.6V,适用于AIN0~AIN3
b.0~1.2V,适用于AIN0~AIN3
c.0~3V,适用于AIN0和AIN1
d.0~6V,适用于AIN0和AIN1,需要注意的是,这两个ADC接口最大容忍电压为5V
*******************************************************************/
/************************************************************************
ADC的初始化程序和转换程序在adc.c文件中,我们只需要修改ADC_StartConvert函数中几条语句就可以实现,程序如下
void ADC_StartConvert(mxc_adc_chsel_t channel, unsigned int adc_scale, unsigned int bypass)
{
  uint32_t ctrl_tmp;

  /* Clear the ADC done flag */
  ADC_ClearFlags(MXC_F_ADC_INTR_ADC_DONE_IF);
  
  /* Insert channel selection */
  ctrl_tmp = MXC_ADC->ctrl;
  ctrl_tmp &= ~(MXC_F_ADC_CTRL_ADC_CHSEL);
  ctrl_tmp |= ((channel << MXC_F_ADC_CTRL_ADC_CHSEL_POS) | (0x00000004UL<<MXC_F_ADC_CTRL_ADC_CHSEL_POS));//只需要改0x00000004UL,这里选择模式4,AIN0/5
  
  /* Clear channel configuration */
  ctrl_tmp &= ~(MXC_F_ADC_CTRL_ADC_REFSCL | MXC_F_ADC_CTRL_ADC_SCALE | MXC_F_ADC_CTRL_BUF_BYPASS);

  /* ADC reference scaling must be set for all channels but two*/
  if ((channel != ADC_CH_VDD18) && (channel != ADC_CH_VDD12)) {
    //ctrl_tmp |= MXC_F_ADC_CTRL_ADC_REFSCL; //如果不注释,则内部参考电压为0.6V,注释掉,内部参考电压为1.2V
  }

  /* Finalize user-requested channel configuration */
  if (adc_scale || channel > ADC_CH_3) {
    ctrl_tmp |= MXC_F_ADC_CTRL_ADC_SCALE;
  }
  if (bypass) {
    ctrl_tmp |= MXC_F_ADC_CTRL_BUF_BYPASS;
  }
  
  /* Write this configuration */
  MXC_ADC->ctrl = ctrl_tmp;
  
  /* Start conversion */
  MXC_ADC->ctrl |= MXC_F_ADC_CTRL_CPU_ADC_START;

}


以上程序是0~6V测量范围的配置,下面总结一下常用到的配置
(1)0~0.6V测量范围,x=0x00000000UL/0x00000001UL/0x00000002UL/0x00000003UL
ctrl_tmp |= ((channel << MXC_F_ADC_CTRL_ADC_CHSEL_POS) | (x<<MXC_F_ADC_CTRL_ADC_CHSEL_POS));
ctrl_tmp |= MXC_F_ADC_CTRL_ADC_REFSCL; 
(2)0~3V测量范围,x=0x00000004UL/x=0x00000005UL
ctrl_tmp |= ((channel << MXC_F_ADC_CTRL_ADC_CHSEL_POS) | (x<<MXC_F_ADC_CTRL_ADC_CHSEL_POS));
ctrl_tmp |= MXC_F_ADC_CTRL_ADC_REFSCL; 
(3)0~1.2V,x=0x00000000UL/0x00000001UL/0x00000002UL/0x00000003UL
ctrl_tmp |= ((channel << MXC_F_ADC_CTRL_ADC_CHSEL_POS) | (x<<MXC_F_ADC_CTRL_ADC_CHSEL_POS));
//ctrl_tmp |= MXC_F_ADC_CTRL_ADC_REFSCL; 
(4)0~6V测量范围,x=0x00000004UL/x=0x00000005UL
ctrl_tmp |= ((channel << MXC_F_ADC_CTRL_ADC_CHSEL_POS) | (x<<MXC_F_ADC_CTRL_ADC_CHSEL_POS));
//ctrl_tmp |= MXC_F_ADC_CTRL_ADC_REFSCL; 

如果同时使用两个ADC的话,上面的1个函数就不适用了,所以我们将函数改一下,将x作为形参输入,优化后的函数如下:

void ADC_StartConvert(mxc_adc_chsel_t channel, unsigned int adc_scale, unsigned int bypass, uint32_t mode)
{
  uint32_t ctrl_tmp;

  /* Clear the ADC done flag */
  ADC_ClearFlags(MXC_F_ADC_INTR_ADC_DONE_IF);
  
  /* Insert channel selection */
  ctrl_tmp = MXC_ADC->ctrl;
  ctrl_tmp &= ~(MXC_F_ADC_CTRL_ADC_CHSEL);
  ctrl_tmp |= ((channel << MXC_F_ADC_CTRL_ADC_CHSEL_POS) | (mode<<MXC_F_ADC_CTRL_ADC_CHSEL_POS));//只需要改0x00000004UL,这里选择模式4,AIN0/5
  
  /* Clear channel configuration */
  ctrl_tmp &= ~(MXC_F_ADC_CTRL_ADC_REFSCL | MXC_F_ADC_CTRL_ADC_SCALE | MXC_F_ADC_CTRL_BUF_BYPASS);

  /* ADC reference scaling must be set for all channels but two*/
  if ((channel != ADC_CH_VDD18) && (channel != ADC_CH_VDD12)) {
    //ctrl_tmp |= MXC_F_ADC_CTRL_ADC_REFSCL; //如果不注释,则内部参考电压为0.6V,注释掉,内部参考电压为1.2V
  }

  /* Finalize user-requested channel configuration */
  if (adc_scale || channel > ADC_CH_3) {
    ctrl_tmp |= MXC_F_ADC_CTRL_ADC_SCALE;
  }
  if (bypass) {
    ctrl_tmp |= MXC_F_ADC_CTRL_BUF_BYPASS;
  }
  
  /* Write this configuration */
  MXC_ADC->ctrl = ctrl_tmp;
  
  /* Start conversion */
  MXC_ADC->ctrl |= MXC_F_ADC_CTRL_CPU_ADC_START;

}


弄清楚这些之后,写主函数程序就很简单了,只要几条语句,如下:

int main(void)
{
    uint16_t adc_val[1];
    /* Initialize ADC */
    ADC_Init();
    
    while(1) {
                ADC_StartConvert(ADC_CH_0, 0, 1, 4);//调用优化后的ADC启动函数
                overflow[0] = (ADC_GetData(&adc_val[0]) == E_OVERFLOW ? 1 : 0);
    }
}

[color=#ff0000]需要注意的是,不同于STM32F4的ADC转换,这里采集一次数据,就要重新调用ADC_StartConvert,所以这里在while(1)一直循环[/color]
*************************************************************************************/


8.串口初始化、发送函数,接收中断及实验

/*******************************************************************
内容:串口配置,给出中断式串口通信,详细的寄存器作用解析
作者:Justice_Gao
日期:2017年7月29日

问题描述:
参考源代码中串口通信程序初始化设置以及通信的方式,比较难理解,和STM32F4的串口通信不同,特别是接收
这里我提供一个中断式的串口接收处理函数以及串口发送处理
思路采用STM32F4的方式,接收1个字节,触发一次中断,然后将接收到的字节存入数组中,或者环形缓存区,如ringbuffer
MAX32625的串口接收和发送自带了32字节深度的FIFO


注意事项:
(1)在调试debugger模式下,不要在中断中设置断点,因为FIFO一直在缓存数据,如果FIFO的接收缓存处理比串口接收传输
慢,则当接收数据大于32字节时,会出现溢出现象,会丢失32字节以后的数据,可以在主函数中设置断点,查看接收到的数据,这
一点非常重要
(2)若在发送数据前后设置RX_FIFO_EN的使能,需要加一定的延时,原因是,数据为发送完,就使能了RX_FIFO_EN,会丢失最后
两个字节


*******************************************************************/
(1)首先,介绍串口的初始化设置,配置串口UART1

/*******************************************************************************
* Function Name  : USART1_Configuration
* Description    : Configures the different GPIO ports.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void USART1_Configuration(uint32_t UART_BAUD)
{
    // Initialize the UART
    uart_cfg_t cfg;
    cfg.parity = UART_PARITY_DISABLE;
    cfg.size = UART_DATA_SIZE_8_BITS;
    cfg.extra_stop = 0;
    cfg.cts = 1;
    cfg.rts = 1;
    cfg.baud = UART_BAUD;

    sys_cfg_uart_t sys_cfg;
    sys_cfg.clk_scale = CLKMAN_SCALE_AUTO;
    sys_cfg.io_cfg = (ioman_cfg_t)IOMAN_UART(1, IOMAN_MAP_A, IOMAN_MAP_A, IOMAN_MAP_A, 1, 1, 1);
                UART_Init(MXC_UART1, &cfg, &sys_cfg);
                    //接收到1个字节触发中断
                MXC_UART1->rx_fifo_ctrl = (0 <<
                MXC_F_UART_RX_FIFO_CTRL_FIFO_AF_LVL_POS);
                //设置FIFO填充中断使能
                MXC_UART1->inten |= MXC_F_UART_INTEN_RX_FIFO_NOT_EMPTY;
        
}


在源代码中,是没有

//接收到1个字节触发中断
MXC_UART1->rx_fifo_ctrl = (0 <<
MXC_F_UART_RX_FIFO_CTRL_FIFO_AF_LVL_POS);
//设置FIFO填充中断使能
MXC_UART1->inten |= MXC_F_UART_INTEN_RX_FIFO_NOT_EMPTY;


这两条语句的,第一条语句的作用是设置接收到1个字节触发一次串口中断,即与STM32F4的串口接收方式相同;第二条语句的作用就是使能中断标志,如果串口接收FIFO不为空,则中断处理。

(2)初始化串口后,编写串口接收中断函数

void UART1_IRQHandler(void)
{
                uint8_t value;
//                static uint16_t count;
                if(((UART_GetFlags(MXC_UART1))&MXC_F_UART_INTFL_RX_FIFO_NOT_EMPTY))
                {
                        value=UART1_GetChar();
                       if(rb_can_write(&u_ring_buff) > 0)
                        {
                                  rb_write(&u_ring_buff, &value, 1);//将数据写入ringbuffer中
                                                
                        }
                        else
                        {
                                rb_new(&u_ring_buff);
                        }                                
                        UART_ClearFlags(MXC_UART1,MXC_F_UART_INTFL_RX_FIFO_NOT_EMPTY|MXC_F_UART_INTFL_RX_FIFO_AF); //清楚中断标志
                }
}

其中,UART1_GetChar();为接收一个字节函数,代码为

//单个字节接收,从RX_FIFO中读取
uint8_t UART1_GetChar(void)
{
    return MXC_UART1_FIFO->rx;
}


是不是很简单,思路和STM32F4一模一样,如果你按照源代码去调试,估计一时半会调不出来,我也研究了好久,建议大家debugger模式下多看看UART的寄存器值是如何变化的。


(3)串口发送函数

//单个字节发送,存入TX_FIFO中,然后发送
void UART1_PutChar(const uint8_t data)
{
    // Wait for TXFIFO to not be full
    while ((MXC_UART1->tx_fifo_ctrl & MXC_F_UART_TX_FIFO_CTRL_FIFO_ENTRY) == MXC_F_UART_TX_FIFO_CTRL_FIFO_ENTRY);
    MXC_UART1->intfl = MXC_F_UART_INTFL_TX_DONE; // clear DONE flag for UART_PrepForSleep
    MXC_UART1_FIFO->tx = data;
}

/*******************************************************************************
* Function Name  : SendToUart
* Description    : Send data to Uart
* Input          : buf:Start address; packLen:data len
* Output         : None
* Return         : None
* Attention                   :
*******************************************************************************/

void SendToUart(mxc_uart_regs_t * MXC_UARTx,uint8_t* buf, uint16_t packLen)
{
        uint16_t i;
        UART_DrainRX(MXC_UART1);
        for(i=0;i<packLen;i++)
        {
                UART1_PutChar(buf);
        }
        (MXC_UARTx->ctrl) |= MXC_F_UART_CTRL_RX_FIFO_EN;
}


发送函数较为简单,这里不做详细解析

以上接收一字节触发一次中断,效率上比较低,下面提供一种效率高的串口程序

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "mxc_config.h"
#include "mxc_sys.h"
#include "clkman.h"
#include "ioman.h"
#include "uart.h"
#include "board.h"
#include "nvic_table.h"

/***** Definitions *****/
#define UART_BAUD           115200
#define BUFF_SIZE           32

#define ComStt_Recv         1               //当前处于接收状态
#define ComStt_Send         2               //当前处于发送
#define ComStt_WaitProc     3               //等待处理

#define Const_DyRvPack      100             //定义帧接收超时时间
#define Const_MaxBufLen     256             //自己定义的缓存的总大小

typedef struct s_com                        //数据处理结构体
{
    unsigned char   ucStt;
    unsigned char   ucRecvTmr;
    unsigned int    ucLen;
    unsigned int    ucPos;
    unsigned char   ucBuf[Const_MaxBufLen];
}S_COM;
S_COM gs_ComGroup;

void proc_data(void)
{
    int i;
    unsigned len = 38;

    for(i = 0; i < len; i++)
    {
        gs_ComGroup.ucBuf = i;
    }    
    gs_ComGroup.ucLen = len;
}

void u1_str_rst(void)
{
    gs_ComGroup.ucStt = ComStt_Recv;
    gs_ComGroup.ucRecvTmr = 0;
    gs_ComGroup.ucLen = 0;
    gs_ComGroup.ucPos = 0;
    memset(gs_ComGroup.ucBuf, 0x00, Const_MaxBufLen);
}

void u1_init(void)
{
    uart_cfg_t cfg;

    cfg.parity = UART_PARITY_DISABLE;
    cfg.size = UART_DATA_SIZE_8_BITS;
    cfg.extra_stop = 0;
    cfg.cts = 0;
    cfg.rts = 0;
    cfg.baud = UART_BAUD;

    sys_cfg_uart_t sys_cfg;
    sys_cfg.clk_scale = CLKMAN_SCALE_AUTO;
    sys_cfg.io_cfg = (ioman_cfg_t)IOMAN_UART(1, IOMAN_MAP_A, IOMAN_MAP_A, IOMAN_MAP_A, 1, 1, 1);

    UART_Init(MXC_UART1, &cfg, &sys_cfg);
                                            //使能相关中断和使能UART
    MXC_UART1->inten = MXC_F_UART_INTEN_RX_FIFO_NOT_EMPTY | MXC_F_UART_INTEN_TX_DONE;
    MXC_UART1->ctrl |= MXC_F_UART_CTRL_UART_EN | MXC_F_UART_CTRL_RX_FIFO_EN | MXC_F_UART_CTRL_TX_FIFO_EN;
}

void ComBom_1msDy(void)                     //定时1ms调用的函数
{
    if(gs_ComGroup.ucRecvTmr > 0)
    {
        gs_ComGroup.ucRecvTmr--;
        if(0 == gs_ComGroup.ucRecvTmr)      //检测数据帧超时,那么表示一帧数据接收完成
        {
            gs_ComGroup.ucStt = ComStt_WaitProc;
        }
    }
}

void u1_send(void)
{
    unsigned char len = 0;
    unsigned char i;

    if(gs_ComGroup.ucStt == ComStt_Send)    //如果当前是发送状态
    {
        if(gs_ComGroup.ucLen >= 32)         //如果数据是超过32字节的,填满32字节的缓冲数据
        {
            len = 32;
            gs_ComGroup.ucLen -= len;
        }
        else if(gs_ComGroup.ucLen > 0)      //数据长度小于32字节的,填实际的数据
        {
            len = gs_ComGroup.ucLen;
            gs_ComGroup.ucLen -= len;
        }
        else
        {
            u1_str_rst();                   //误入,则初始化结构体
            return;
        }

        for(i = 0; i < len; i++)            //填数据操作
        {
            MXC_UART1_FIFO->tx = gs_ComGroup.ucBuf[gs_ComGroup.ucPos++];
        }
        MXC_UART1->tx_fifo_ctrl = 0;        //启动发送
    }
    else
    {
        u1_str_rst();                       //误入,初始化结构体
    }
}

void u1_recv(void)
{
    unsigned char i;
    unsigned char cnt = MXC_UART1->rx_fifo_ctrl & 0x1F;

    if(gs_ComGroup.ucStt == ComStt_Recv)
    {
        gs_ComGroup.ucRecvTmr = Const_DyRvPack;
        for(i = 0; i < cnt; i++)            //将缓冲里面的数据接收到自己的数据中去
        {
            gs_ComGroup.ucBuf[gs_ComGroup.ucLen++] = MXC_UART1_FIFO->rx;
        }
    }
}

void u1_start_send(void)                    //可参考u1_send函数的注释
{
    unsigned char len = 0;
    unsigned char i;

    gs_ComGroup.ucStt = ComStt_Send;

    if(gs_ComGroup.ucLen >= 32)
    {
        len = 32;
        gs_ComGroup.ucLen -= len;
    }
    else if(gs_ComGroup.ucLen > 0)
    {
        len = gs_ComGroup.ucLen;
        gs_ComGroup.ucLen -= len;
    }

    for(i = 0; i < len; i++)
    {
        MXC_UART1_FIFO->tx = gs_ComGroup.ucBuf[gs_ComGroup.ucPos++];
    }
    MXC_UART1->tx_fifo_ctrl = 0;
}

void UART1_IRQHandler(void)                 //UART1中断时调用
{
    if(MXC_UART1->intfl & MXC_F_UART_INTFL_RX_FIFO_NOT_EMPTY)
    {
        u1_recv();                          //有数据接收
        MXC_UART1->intfl |= MXC_F_UART_INTFL_RX_FIFO_NOT_EMPTY;
    }
    else if(MXC_UART1->intfl & MXC_F_UART_INTFL_TX_DONE)
    {
        u1_send();                          //发送完成
        MXC_UART1->intfl |= MXC_F_UART_INTFL_TX_DONE;
    }
    else                                    //如果是其他中断就清除其他中断位
    {
        unsigned long t = MXC_UART1->intfl;
        MXC_UART1->intfl = t;
    }
}



具体的解析我就不写了,大家自己选择用哪种,大家应该都比较习惯第一种吧,下面我用第1种方法做个实验。
实验结果:串口发送什么数据,则返回:已接收:xx数据
 


 重要的事情再说一遍,
注意事项:
(1)在调试debugger模式下,不要在中断中设置断点,因为FIFO一直在缓存数据,如果FIFO的接收缓存处理比串口接收传输慢,则当接收数据大于32字节时,会出现溢出现象,会丢失32字节以后的数据,可以在主函数中设置断点,查看接收到的数据,这一点非常重要
(2)若在发送数据前后设置RX_FIFO_EN的使能,需要加一定的延时,原因是,数据为发送完,就使能了RX_FIFO_EN,会丢失最后两个字节

   

9.定时器的使用(Keil版本)

(1)定时器资源介绍
 MAX32630系列的微控制器单元的定时器资源非常丰富,其定时器可以作为GPIO口的PWM输出,GPIO对应的定时器,如下图
 
定时器的使用可以输出不同占空比的PWM,也可以作为定时轮询中断触发事件,下面介绍一下定时器的定时中断使用

(2)定时器的初始化,以定时器TM0为例

uint8_t TIM0_Int_Init(uint32_t time_ms)
{
                int error = 0;
    tmr32_cfg_t cont_cfg;

    uint32_t IntervalTime = time_ms;//ms
    //enable timer interrupt
    NVIC_SetVector(TMR0_0_IRQn, TMR0_IRQHandler);
    TMR32_EnableINT(MXC_TMR0);

    //Setup GPIO to timer output function
    sys_cfg_tmr_t gpio;
    gpio.port = 3;
    gpio.mask = PIN_0;
    gpio.func = GPIO_FUNC_TMR;
    gpio.pad = GPIO_PAD_OPEN_DRAIN;

    //initialize timer and GPIO
    tmr_prescale_t prescale = TMR_PRESCALE_DIV_2_12;
    //error = TMR_Init(MXC_TMR0, prescale, &gpio);
                error = TMR_Init(MXC_TMR0, prescale, NULL);   //禁止定时器的输出功能
        
    if(error != E_NO_ERROR)
        return error;

    cont_cfg.mode = TMR32_MODE_CONTINUOUS;
    cont_cfg.polarity = TMR_POLARITY_INIT_LOW;        //start GPIO low

    //calculate the ticks values for given time
    error = TMR32_TimeToTicks(MXC_TMR0, IntervalTime, TMR_UNIT_MILLISEC, &(cont_cfg.compareCount));

    if(error != E_NO_ERROR)
        return error;

    //configure and start the timer
    TMR32_Config(MXC_TMR0, &cont_cfg);
    TMR32_Start(MXC_TMR0);

    return error;
}


这里我选用P3_0作为定时器TM0,但禁止其输出波形,函数的输入值为定时中断触发事件,单位毫秒


(2)定时器中断

uint8_t buffer[100];
uint16_t ADCValue[1];
void TMR0_IRQHandler()
{
        static uint8_t ADC_GET_Time=0;
        SysTime_Struct.Unit_1ms_CurTime++;
                ADC_GET_Time++;
LED0_TURN;

                TMR32_ClearFlag(MXC_TMR0);
}


中断触发事件,定时时间到达后,LED灯状态翻转一次


总结:
用KEIL C编写MAX32630程序,好处在于其编程方式雷同STM32编程,很多东西可以一直过来,包括串口,外部中断。因为MAX32630是基于M4内核,STM32F4系列的DSP算法都可以直接移植过来,串口中断触发可以参考帖子串口发送接收函数


关键字:MAX32630
阅读原文 浏览量:3704 收藏:0
此内容由EEWORLD论坛网友 Justice_Gao 原创,如需转载或用于商业用途需征 得作者同意并注明出处

上一篇: TPS61088-EVM评估板(10A step-up DC-DC )到手简测,效率、纹波和瞬态
下一篇: 开关电源稳定性的设计与测试!

评论

登录 | 注册 需要登陆才可发布评论    
评论加载中......
电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright ? 2005-2017 EEWORLD.com.cn, Inc. All rights reserved