【STM32H7教程】第77章 STM32H7的FMC总线应用之DMA双缓冲驱动AD7606(8通道同步采样, 16bit, 正负10V)

完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第77章       STM32H7的FMC总线应用之DMA双缓冲驱动AD7606(8通道同步采样, 16bit, 正负10V)

本章节为大家讲解FMC DMA双缓冲方式驱动数模转换器AD7606,实战性较强。

目录

第77章       STM32H7的FMC总线应用之DMA双缓冲驱动AD7606(8通道同步采样, 16bit, 正负10V)

77.1 初学者重要提示

77.2 ADC结构分类

77.3 AD7606硬件设计

77.4 AD7606关键知识点整理(重要)

77.5 AD7606的FMC接口硬件设计

77.6 AD7606的FMC DMA实现思路

77.6.1 定时器PWM输出控制AD7606转换

77.6.2 定时器UP更新事件触发DMA实现突发传输

77.6.3 不使用BUSY引脚如何保证读取正确的数据

77.6.4 FMC DMA双缓冲实现

77.7 AD7606的FMC接口驱动设计

77.7.1 第1步,AD7606所涉及到的GPIO配置

77.7.2 第2步,FMC的时钟源选择

77.7.3 第3步,FMC的时序配置(重要)

77.7.4 第4步,FMC的MPU配置

77.7.5 第5步,AD7606的FMC DMA实现(核心)

77.7.6 第6步,FMC DMA双缓冲

77.7.7 第7步,AD7606过采样设置

77.7.8 第8步,AD7606量程设置

77.7.9 第9步,DMA突发传输的1KB边界处理

77.8 AD7606板级支持包(bsp_fmcdma_ad7606.c)

77.8.1 函数bsp_InitAD7606

77.8.2 函数AD7606_SetOS

77.8.3 函数AD7606_SetInputRange

77.8.4 函数AD7606_Reset

77.8.5 函数AD7606_StartRecord

77.8.6 函数AD7606_StopRecord

77.9 J-Scope实时展示AD7606采集数据说明

77.9.1 J-Scope闪退问题解决办法

77.9.2 J-Scope多通道传输实现

77.9.3 J-Scope带宽问题

77.10          AD7606驱动移植和使用

77.11          实验例程设计框架   

77.12          实验例程说明(MDK)

77.13   总结


 

77.1 初学者重要提示

  1.   学习本章节前,务必优先学习第76章,本章是建立在76章的基础上。
  2.   本章77.6小节的知识点对于本章的理解尤其重要。
  3.   AD7606 的配置很简单,它没有内部寄存器,量程范围和过采样参数是通过外部IO控制的,采样速率由MCU或DSP提供的脉冲频率控制。
  4.   AD7606必须使用单5V供电。而AD7606和MCU之间的通信接口电平由VIO(VDRIVE)引脚控制。也就是说VIO必须接单片机的电源,可以是3.3V也可以是5V(范围2.3V – 5V)。
  5.   正确的理解过采样,比如我们设置是1Ksps采样率,64倍过采样。意思是指每次采样,AD7606会采样64次数据并求平均,相当于AD7606以64Ksps进行采样的,只是将每64个采样点的值做了平均,用户得到的值就是平均后的数值。因此,如果使用AD7606最高的200Ksps采样率,就不可以使用过采样了。
  6.   STM32H7驱动AD7606配合J-Scope实时输出,效果绝了,堪比示波器:http://www.armbbs.cn/forum.php?mod=viewthread&tid=97393 。使用方法详解本章节77.9小节。
  7.   本章配套例子的串口数据展示推荐使用SecureCRT,因为数据展示做了特别处理,方便采集数据在串口软件同一个位置不断刷新。
  8.   AD7606数据手册,模块原理图(通用版)和接线图都已经放到本章教程配置例子的Doc文件里。
  9.   ADC 的专业术语诠释文档,推荐大家看看:http://www.armbbs.cn/forum.php?mod=viewthread&tid=89414
  10.   测试本章配套例子前重要提示:
  •   测试时,务必使用外置电源为开发板供电,因为AD7606需要5V供电电压。板子上插入AD7606模块时,注意对齐。
  •   板子上电后,默认是100Ksps,2倍过采样。
  •   如果使用的JLINK速度不够快,导致J-Scope无法最高速度实时上传,可以使用摇杆上下键设置过采样来降低上传速度。
  •   默认情况下,程序仅上传了AD7606通道1采集的数据。

77.2 ADC结构分类

77.3 AD7606硬件设计

77.4 AD7606关键知识点整理(重要)

77.5 AD7606的FMC接口硬件设计

77.2,77.3,77.4和77.5小节的知识在第76章节有详细说明,本章不再赘述。

77.6 AD7606的FMC DMA实现思路

FMC的并行接线方式如下:


 

这里实现FMC DMA方式的关键就是BUSY引脚去触发DMA控制,如果是单纯的DMA正常模式,实现比较简单,接收到INT引脚的就绪状态,使用FMC DMA将8路数据全部读取出来即可。

难点在于驱动AD7606不像SRAM,SDRAM,仅需一个FMC接口就行,它还需要一个独立的时钟引脚,每次时钟触发要连续读取8次数据。针对这个问题,就可以使用DMAMUX的事件触发方式来实现,可以选择的主要是:

HAL_DMAMUX1_REQ_GEN_EXTI0
HAL_DMAMUX2_REQ_GEN_EXTI0
HAL_DMAMUX2_REQ_GEN_EXTI2

按照这个思路,尝试了下面三种方案,但实现都太复杂了。

  •   方案1:

定时器配合DMAMUX两级级联,苦于找不到合理级联触发源。

  •   方案2:

定时器触发DMAMUX,然后DMA触发MDMA,这个是可以实现的,就是MDMA玩起来有点复杂。

  •   方案3:

两路DMAMUX控制,不限制必须用定时器的PWM引脚,然后配置定时器做同步触发源,也是可以实现的,占用太多硬件资源。

 

最终这三种方案全部否决了,实现的略麻烦。最终有个第4套方案,实现这套方案有如下几点:

77.6.1 定时器PWM输出控制AD7606转换

通过定时器PWM输出控制AD7606转换比较容易实现,我们上一个章节就是这种方式控制的。

77.6.2 定时器UP更新事件触发DMA实现突发传输

有了定时器PWM控制AD7606转换。还需要保证每个PWM脉冲读取一次数据,而且是连续读取8路。这就需要用到下面两个知识点,非常关键:

  •   同时开启同一个定时器的PWM输出和UP更新事件。

这样可以保证每个PWM后都配有一个UP更新,通过UP更新来触发DMA传输。

  •   DMA突发功能实现每次触发连续读取8路数据。

STM32H7支持的突发方式如下,下面这个表格尤其重要,配置突发务必要按照这个表格来配置:

 

我们要实现的是连续读取8路16bit数据,上面表格中红色方框部分刚好支持。

77.6.3 不使用BUSY引脚如何保证读取正确的数据

解决这个问题的关键就是AD7606支持转换期间读取:

 

这个功能正好用在本设计中,这里有四个关键时序参数:

  •   t2

表示最短的CONVST低电平脉冲,最小值25ns,这个时间我们用PWM脉冲低电平控制。

  •   t3

表示最短的CONVST高电平脉冲,最小值25ns,这个时间我们用PWM脉冲高电平控制。

  •   t6

表示CS上升沿和BUSY下降沿之间的最长时间,最大值25ns。这个参数的主要作用是限制用户一定要在BUSY转换有效之前立即读取。

  • conv

表示AD7606转换时间,对于AD7606-8来说,最小值范围是3.45us到4.15us。

 

有了这三个参数,配置PWM的占空比就比较考究了,我们仅需配置好PMW低电平宽度,将其设置为接近于25ns的低电平脉宽时间,其余时间全是高电平即可。这样我们就保证了每次脉冲立即读取上一次的转换数据。

77.6.4 FMC DMA双缓冲实现

DMA双缓冲的实现比较简单,我们借助DMA半传输完成中断和DMA传输完成中断即可。其中半传输完成中断就是DMA数据传输完成一半的中断。

77.7 AD7606的FMC接口驱动设计

AD7606的程序驱动框架设计如下:

有了这个框图,程序设计就比较好理解了。

77.7.1 第1步,AD7606所涉及到的GPIO配置

这里需要把用到的GPIO时钟、FMC时钟、GPIO引脚和复用配置好即可:

/*
*********************************************************************************************************
*    函 数 名: AD7606_CtrlLinesConfig
*    功能说明: 配置GPIO口线,FMC管脚设置为复用功能
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
/*
    安富莱STM32-H7开发板接线方法:4片74HC574挂在FMC 32位总线上。1个地址端口可以扩展出32个IO
    PD0/FMC_D2
    PD1/FMC_D3
    PD4/FMC_NOE        ---- 读控制信号,OE = Output Enable , N 表示低有效
    PD5/FMC_NWE        -XX- 写控制信号,AD7606 只有读,无写信号
    PD8/FMC_D13
    PD9/FMC_D14
    PD10/FMC_D15
    PD14/FMC_D0
    PD15/FMC_D1

    PE7/FMC_D4
    PE8/FMC_D5
    PE9/FMC_D6
    PE10/FMC_D7
    PE11/FMC_D8
    PE12/FMC_D9
    PE13/FMC_D10
    PE14/FMC_D11
    PE15/FMC_D12
    
    PG0/FMC_A10        --- 和主片选FMC_NE2一起译码
    PG1/FMC_A11        --- 和主片选FMC_NE2一起译码
    PD7/FMC_NE1        --- 主片选(OLED, 74HC574, DM9000, AD7606)    

     +-------------------+------------------+
     +   32-bits Mode: D31-D16              +
     +-------------------+------------------+
     | PH8 <-> FMC_D16   | PI0 <-> FMC_D24  |
     | PH9 <-> FMC_D17   | PI1 <-> FMC_D25  |
     | PH10 <-> FMC_D18  | PI2 <-> FMC_D26  |
     | PH11 <-> FMC_D19  | PI3 <-> FMC_D27  |
     | PH12 <-> FMC_D20  | PI6 <-> FMC_D28  |
     | PH13 <-> FMC_D21  | PI7 <-> FMC_D29  |
     | PH14 <-> FMC_D22  | PI9 <-> FMC_D30  |
     | PH15 <-> FMC_D23  | PI10 <-> FMC_D31 |
     +------------------+-------------------+
*/

/* 
    控制AD7606参数的其他IO分配在扩展的74HC574上
    X13 - AD7606_OS0
    X14 - AD7606_OS1
    X15 - AD7606_OS2
    X24 - AD7606_RESET
    X25 - AD7606_RAGE    
    
    PE5 - AD7606_BUSY
*/
static void AD7606_CtrlLinesConfig(void)
{
    /* bsp_fm_io 已配置fmc,bsp_InitExtIO();
       此处可以不必重复配置 
    */

    GPIO_InitTypeDef gpio_init_structure;

    /* 使能 GPIO时钟 */
    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_GPIOF_CLK_ENABLE();
    __HAL_RCC_GPIOG_CLK_ENABLE();
    __HAL_RCC_GPIOH_CLK_ENABLE();
    __HAL_RCC_GPIOI_CLK_ENABLE();

    /* 使能FMC时钟 */
    __HAL_RCC_FMC_CLK_ENABLE();

    /* 设置 GPIOD 相关的IO为复用推挽输出 */
    gpio_init_structure.Mode = GPIO_MODE_AF_PP;
    gpio_init_structure.Pull = GPIO_PULLUP;
    gpio_init_structure.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_init_structure.Alternate = GPIO_AF12_FMC;
    
    /* 配置GPIOD */
    gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_7 |
                                GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 |
                                GPIO_PIN_15;
    HAL_GPIO_Init(GPIOD, &gpio_init_structure);

    /* 配置GPIOE */
    gpio_init_structure.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 |
                                GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 |
                                GPIO_PIN_15;
    HAL_GPIO_Init(GPIOE, &gpio_init_structure);

    /* 配置GPIOG */
    gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1;
    HAL_GPIO_Init(GPIOG, &gpio_init_structure);
    
    /* 配置GPIOH */
    gpio_init_structure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12
                        | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOH, &gpio_init_structure);

    /* 配置GPIOI */
    gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_6
                        | GPIO_PIN_7 | GPIO_PIN_9 | GPIO_PIN_10;
    HAL_GPIO_Init(GPIOI, &gpio_init_structure);
    
    /* CONVST 启动ADC转换的GPIO = PC6 */
    {
        GPIO_InitTypeDef   GPIO_InitStructure;
        CONVST_RCC_GPIO_CLK_ENABLE();

        /* 配置PC6 */
        GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;        /* 设置推挽输出 */
        GPIO_InitStructure.Pull = GPIO_NOPULL;            /* 上下拉电阻不使能 */
        GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM;  /* GPIO速度等级 */    

        GPIO_InitStructure.Pin = CONVST_PIN;    
        HAL_GPIO_Init(CONVST_GPIO, &GPIO_InitStructure);    
    }
}

这里重点注意AD7606_CONVST,上电后的默认配置是普通IO。另外还有过采样的3个引脚,量程配置的1个引脚和复位控制的1个引脚,均通过V7板子的扩展IO实现:

/* 设置过采样的IO, 在扩展的74HC574上 */
#define OS0_1()        HC574_SetPin(AD7606_OS0, 1)
#define OS0_0()        HC574_SetPin(AD7606_OS0, 0)
#define OS1_1()        HC574_SetPin(AD7606_OS1, 1)
#define OS1_0()        HC574_SetPin(AD7606_OS1, 0)
#define OS2_1()        HC574_SetPin(AD7606_OS2, 1)
#define OS2_0()        HC574_SetPin(AD7606_OS2, 0)

/* 设置输入量程的GPIO, 在扩展的74HC574上 */
#define RANGE_1()    HC574_SetPin(AD7606_RANGE, 1)
#define RANGE_0()    HC574_SetPin(AD7606_RANGE, 0)

/* AD7606复位口线, 在扩展的74HC574上 */
#define RESET_1()    HC574_SetPin(AD7606_RESET, 1)
#define RESET_0()    HC574_SetPin(AD7606_RESET, 0)

77.7.2 第2步,FMC的时钟源选择

使用FMC可以选择如下几种时钟源HCLK3,PLL1Q,PLL2R和PER_CK:


 

我们这里直接使用HCLK3,配置STM32H7的主频为400MHz的时候,HCLK3输出的200MHz,这个速度是FMC支持的最高时钟,正好用于这里:

77.7.3 第3步,FMC的时序配置(重要)

由于操作AD7606仅需要读操作,而且使用的是FMC总线的Mode_A,那么仅需按照如下时序图配置好即可:

 

根据这个时序图,重点配置好ADDSET地址建立时间和DATAST数据建立时间即可。

  •   DATAST(DataSetupTime,数据建立时间)

DATAST实际上对应的就是76.4.4小节里面的t10 。RD读信号的低电平脉冲宽度,通信电压不同,时间不同,对于STM32来说,FMC通信电平一般是3.3V,即最小值21ns。

 

  •   ADDST(AddressSetupTime,地址建立时间)

DATAST实际上对应的就是76.4.4小节里面的t11 或者t12。

    •   如果采用CS(NEx)片选和RD(NOE)读信号独立方式,对应的时间最小15ns,即t11 。
    •   如果采用CS(NEx)片选和RD(NOE)读信号并联方式,对应的时间最小22ns,即t12  。

我们这里将t12作为最小值更合理,因为CS(NEx)片选信号,每读取完毕一路,拉高一次。

 

有了这些认识后,再来看FMC的时序配置就比较好理解了:

1.    /*
2.    ******************************************************************************************************
3.    *    函 数 名: AD7606_FSMCConfig
4.    *    功能说明: 配置FSMC并口访问时序
5.    *    形    参: 无
6.    *    返 回 值: 无
7.    ******************************************************************************************************
8.    */
9.    static void AD7606_FSMCConfig(void)
10.    {
11.        /* 
12.           DM9000,扩展IO,OLED和AD7606公用一个FMC配置,如果都开启,请以FMC速度最慢的为准。
13.           从而保证所有外设都可以正常工作。
14.        */
15.        SRAM_HandleTypeDef hsram = {0};
16.        FMC_NORSRAM_TimingTypeDef SRAM_Timing = {0};
17.            
18.       /*
19.        AD7606规格书要求(3.3V时,通信电平Vdriver):RD读信号低电平脉冲宽度最短21ns,对应DataSetupTime
20.        CS片选和RD读信号独立方式的高电平脉冲最短宽度15ns。
21.        CS片选和RD读信号并联方式的高电平脉冲最短宽度22ns。
22.        这里将22ns作为最小值更合理些,对应FMC的AddressSetupTime。
23.        
24.            5-x-5-x-x-x  : RD高持续25ns, 低电平持续25ns. 读取8路样本数据到内存差不多就是400ns。
25.        */
26.        hsram.Instance  = FMC_NORSRAM_DEVICE;
27.        hsram.Extended  = FMC_NORSRAM_EXTENDED_DEVICE;
28.        
29.        /* FMC使用的HCLK3,主频200MHz,1个FMC时钟周期就是5ns */
30.        SRAM_Timing.AddressSetupTime       = 5; /* 5*5ns=25ns,地址建立时间,范围0 -15个FMC时钟周期个数 */
31.        SRAM_Timing.AddressHoldTime        = 2; /* 地址保持时间,配置为模式A时,用不到此参数 范围1 -15个
32.                                                    时钟周期个数 */
33.        SRAM_Timing.DataSetupTime          = 5;  /* 5*5ns=25ns,数据建立时间,范围1 -255个时钟周期个数 */
34.        SRAM_Timing.BusTurnAroundDuration  = 1;  /* 此配置用不到这个参数 */
35.        SRAM_Timing.CLKDivision            = 2;  /* 此配置用不到这个参数 */
36.        SRAM_Timing.DataLatency            = 2;  /* 此配置用不到这个参数 */
37.        SRAM_Timing.AccessMode             = FMC_ACCESS_MODE_A; /* 配置为模式A */
38.        hsram.Init.NSBank             = FMC_NORSRAM_BANK1;              /* 使用的BANK1,即使用的片选
39.                                                                            FMC_NE1 */
40.        hsram.Init.DataAddressMux     = FMC_DATA_ADDRESS_MUX_DISABLE;   /* 禁止地址数据复用 */
41.        hsram.Init.MemoryType         = FMC_MEMORY_TYPE_SRAM;           /* 存储器类型SRAM */
42.        hsram.Init.MemoryDataWidth    = FMC_NORSRAM_MEM_BUS_WIDTH_32;   /* 32位总线宽度 */
43.        hsram.Init.BurstAccessMode    = FMC_BURST_ACCESS_MODE_DISABLE;  /* 关闭突发模式 */
44.        hsram.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW;   /* 用于设置等待信号的极性,关闭突
45.                                                                            发模式,此参数无效 */
46.        hsram.Init.WaitSignalActive   = FMC_WAIT_TIMING_BEFORE_WS;      /* 关闭突发模式,此参数无效 */
47.        hsram.Init.WriteOperation     = FMC_WRITE_OPERATION_ENABLE;     /* 用于使能或者禁止写保护 */
48.        hsram.Init.WaitSignal         = FMC_WAIT_SIGNAL_DISABLE;        /* 关闭突发模式,此参数无效 */
49.        hsram.Init.ExtendedMode       = FMC_EXTENDED_MODE_DISABLE;      /* 禁止扩展模式 */
50.        hsram.Init.AsynchronousWait   = FMC_ASYNCHRONOUS_WAIT_DISABLE;  /* 用于异步传输期间,使能或者禁止
51.                                                                            等待信号,这里选择关闭 */
52.        hsram.Init.WriteBurst         = FMC_WRITE_BURST_DISABLE;        /* 禁止写突发 */
53.        hsram.Init.ContinuousClock    = FMC_CONTINUOUS_CLOCK_SYNC_ONLY; /* 仅同步模式才做时钟输出 */
54.        hsram.Init.WriteFifo          = FMC_WRITE_FIFO_ENABLE;          /* 使能写FIFO */
55.    
56.        /* 初始化SRAM控制器 */
57.        if (HAL_SRAM_Init(&hsram, &SRAM_Timing, &SRAM_Timing) != HAL_OK)
58.        {
59.            /* 初始化错误 */
60.            Error_Handler(__FILE__, __LINE__);
61.        }    
62.    }

这里把几个关键的地方阐释下:

  •   第15- 16行,对作为局部变量的HAL库结构体做初始化,防止不确定值配置时出问题。
  •   第30行,地址建立时间,对于AD7606来说,这个地方最小值22ns。保险起见,这里取值5个FMC时钟周期,即25ns。
  •   第31行,地址保持时间,对于FMC模式A来说,此参数用不到。
  •   第33行,数据建立时间,对于AD7606来说,这个地方最小值是21ns,保险起见,这里取值5个FMC时钟周期,即25ns。
  •   第34 – 36行,当前配置用不到这三个参数。
  •   第38行,使用的BANK1,即使用的片选FMC_NE1。

77.7.4 第4步,FMC的MPU配置

实际测试发现,使能FMC_NE1所管理的存储区的Cache功能后,会出现扩展IO的NE片选和NWE信号输出2次的问题。经过各种Cache方式配置、FMC带宽配置、操作FMC时的数据位宽设置,发现禁止了Cache功能就正常了,也就是说,设置FMC_NE1所管理的存储区MPU属性为Device或者Strongly Ordered即可。

    /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

MPU配置中直接从FMC_NE1的首地址开始配置,设置了64KB空间的属性。将FMC_NE1通过译码器所管理的所有设备地址全部设置为此配置:

 

77.7.5 第5步,AD7606的FMC DMA实现(核心)

这部分代码是本章77.6小节的完美体现:

1.    static void AD7606_SetTIMOutPWM(TIM_TypeDef* TIMx, uint32_t _ulFreq)
2.    {
3.        TIM_OC_InitTypeDef sConfig = {0};    
4.        GPIO_InitTypeDef   GPIO_InitStruct;
5.        uint16_t usPeriod;
6.        uint16_t usPrescaler;
7.        uint32_t uiTIMxCLK;
8.        uint32_t pulse;
9.    
10.        
11.        /* 配置时钟 */
12.        CONVST_RCC_GPIO_CLK_ENABLE();
13.        CONVST_TIM8_CLK_ENABLE();
14.        TIMx_UP_DMA_STREAM_CLK_ENABLE();
15.        
16.        /* 配置引脚 */
17.        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
18.        GPIO_InitStruct.Pull = GPIO_PULLUP;
19.        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
20.        GPIO_InitStruct.Alternate = CONVST_AF;
21.        GPIO_InitStruct.Pin = CONVST_PIN;
22.        HAL_GPIO_Init(CONVST_GPIO, &GPIO_InitStruct);
23.        
24.        /*-----------------------------------------------------------------------
25.            bsp.c 文件中 void SystemClock_Config(void) 函数对时钟的配置如下: 
26.    
27.            System Clock source       = PLL (HSE)
28.            SYSCLK(Hz)                = 400000000 (CPU Clock)
29.            HCLK(Hz)                  = 200000000 (AXI and AHBs Clock)
30.            AHB Prescaler             = 2
31.            D1 APB3 Prescaler         = 2 (APB3 Clock  100MHz)
32.            D2 APB1 Prescaler         = 2 (APB1 Clock  100MHz)
33.            D2 APB2 Prescaler         = 2 (APB2 Clock  100MHz)
34.            D3 APB4 Prescaler         = 2 (APB4 Clock  100MHz)
35.    
36.            因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz;
37.            因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz;
38.            APB4上面的TIMxCLK没有分频,所以就是100MHz;
39.    
40.            APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1
41.            APB2 定时器有 TIM1, TIM8 , TIM15, TIM16,TIM17
42.    
43.            APB4 定时器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5
44.    
45.        ----------------------------------------------------------------------- */
46.        if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM15) || (TIMx == TIM16) || (TIMx == TIM17))
47.        {
48.            /* APB2 定时器时钟 = 200M */
49.            uiTIMxCLK = SystemCoreClock / 2;
50.        }
51.        else    
52.        {
53.            /* APB1 定时器 = 200M */
54.            uiTIMxCLK = SystemCoreClock / 2;
55.        }
56.    
57.        if (_ulFreq < 100)
58.        {
59.            usPrescaler = 10000 - 1;                  /* 分频比 = 10000 */
60.            usPeriod =  (uiTIMxCLK / 10000) / _ulFreq  - 1; /* 自动重装的值, usPeriod最小值200, 单位50us*/
61.            pulse = usPeriod;                   /* 设置低电平时间50us,注意usPeriod已经进行了减1操作 */
62.        } 
63.        else if (_ulFreq < 3000)
64.        {
65.            usPrescaler = 100 - 1;                    /* 分频比 = 100 */
66.            usPeriod =  (uiTIMxCLK / 100) / _ulFreq -1;/* 自动重装的值, usPeriod最小值666,单位500ns */
67.            pulse = usPeriod-1;                   /* 设置低电平时间1us,注意usPeriod已经进行了减1操作 */
68.        }
69.        else    /* 大于4K的频率,无需分频 */
70.        {
71.            usPrescaler = 0;                    /* 分频比 = 1 */
72.            usPeriod = uiTIMxCLK / _ulFreq - 1;    /* 自动重装的值, usPeriod最小值1000,单位5ns */
73.            pulse = usPeriod - 199;                  /* 设置低电平时间1us,注意usPeriod已经进行了减1操作 */
74.        }
75.        
76.        /*  PWM频率 = TIMxCLK / usPrescaler + 1)/usPeriod + 1)*/
77.        TimHandle.Instance = TIMx;
78.        TimHandle.Init.Prescaler         = usPrescaler;         /* 用于设置定时器分频 */
79.        TimHandle.Init.Period            = usPeriod;            /* 用于设置定时器周期 */
80.        TimHandle.Init.ClockDivision     = 0;                   /* 用于指示定时器时钟 (CK_INT) 频率与死区
81.                                                                    发生器以及数字滤波器(ETR、 TIx)所使用
82.                                                                    的死区及采样时钟 (tDTS) 之间的分频比*/
83.        TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;  /* 用于设置计数模式,向上计数模式 */
84.        TimHandle.Init.RepetitionCounter = 0;  /* 用于设置重复计数器,仅 TIM1 和 TIM8 有,其它定时器没有 */
85.        TimHandle.Init.AutoReloadPreload = 0; /* 用于设置定时器的 ARR 自动重装寄存器是更新事件产生时写入有
86.                                                  效 */
87.        
88.        if (HAL_TIM_PWM_DeInit(&TimHandle) != HAL_OK)
89.        {
90.            Error_Handler(__FILE__, __LINE__);        
91.        }
92.        
93.        if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK)
94.        {
95.            Error_Handler(__FILE__, __LINE__);
96.        }
97.    
98.        /* 配置定时器PWM输出通道 */
99.        sConfig.OCMode       = TIM_OCMODE_PWM1;         /* 配置输出比较模式 */
100.        sConfig.OCPolarity   = TIM_OCPOLARITY_HIGH;     /* 设置输出高电平有效 */
101.        sConfig.OCFastMode   = TIM_OCFAST_DISABLE;      /* 关闭快速输出模式 */
102.        sConfig.OCNPolarity  = TIM_OCNPOLARITY_HIGH;    /* 配置互补输出高电平有效 */
103.        sConfig.OCIdleState  = TIM_OCIDLESTATE_SET;     /* 空闲状态时,设置输出比较引脚为高电平 */
104.        sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET;  /* 空闲状态时,设置互补输出比较引脚为低电平 */
105.    
106.        /* 占空比 */
107.        sConfig.Pulse = pulse;
108.        if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, CONVST_TIMCH) != HAL_OK)
109.        {
110.            Error_Handler(__FILE__, __LINE__);
111.        }
112.        
113.        /* 使能定时器中断  */
114.        __HAL_TIM_ENABLE_DMA(&TimHandle, TIM_DMA_UPDATE);
115.        
116.        /* 启动PWM输出 */
117.        if (HAL_TIM_PWM_Start(&TimHandle, CONVST_TIMCH) != HAL_OK)
118.        {
119.            Error_Handler(__FILE__, __LINE__);
120.        }
121.        
122.        /* 定时器UP更新触发DMA传输 */        
123.        TIMDMA.Instance                 = TIMx_UP_DMA_STREAM;      /* 例化使用的DMA数据流 */
124.        TIMDMA.Init.FIFOMode            = DMA_FIFOMODE_ENABLE;     /* 使能FIFO*/
125.        TIMDMA.Init.FIFOThreshold       = DMA_FIFO_THRESHOLD_FULL; /* 用于设置阀值 */
126.        TIMDMA.Init.MemBurst            = DMA_MBURST_INC8;           /* 用于存储器突发 */
127.        TIMDMA.Init.PeriphBurst         = DMA_PBURST_INC8;           /* 用于外设突发 */
128.        TIMDMA.Init.Request             = TIMx_UP_DMA_REQUEST;     /* 请求类型 */  
129.        TIMDMA.Init.Direction           = DMA_PERIPH_TO_MEMORY;    /* 传输方向是从外设到存储器 */  
130.        TIMDMA.Init.PeriphInc           = DMA_PINC_DISABLE;        /* 外设地址自增禁止 */ 
131.        TIMDMA.Init.MemInc              = DMA_MINC_ENABLE;         /* 存储器地址自增使能 */  
132.        TIMDMA.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; /* 外设数据传输位宽选择半字,即16bit */ 
133.        TIMDMA.Init.MemDataAlignment    = DMA_MDATAALIGN_HALFWORD; /* 存储器数据传输位宽选择半字,即16bit */    
134.        TIMDMA.Init.Mode                = DMA_CIRCULAR;            /* 循环模式 */
135.        TIMDMA.Init.Priority            = DMA_PRIORITY_LOW;        /* 优先级低 */
136.        
137.         /* 复位DMA */
138.        if(HAL_DMA_DeInit(&TIMDMA) != HAL_OK)
139.        {
140.            Error_Handler(__FILE__, __LINE__);     
141.        }
142.        
143.         /* 初始化DMA */
144.        if(HAL_DMA_Init(&TIMDMA) != HAL_OK)
145.        {
146.            Error_Handler(__FILE__, __LINE__);     
147.        }
148.        
149.        /* 关联DMA句柄到TIM */
150.        //__HAL_LINKDMA(&TimHandle, hdma[TIM_DMA_ID_UPDATE], TIMDMA);    
151.        
152.        /* 配置DMA中断 */
153.        HAL_NVIC_SetPriority(TIMx_UP_DMA_IRQn, 1, 0);
154.        HAL_NVIC_EnableIRQ(TIMx_UP_DMA_IRQn);
155.        
156.        /* 注册半传输完成中断和传输完成中断 */
157.        HAL_DMA_RegisterCallback(&TIMDMA, HAL_DMA_XFER_CPLT_CB_ID, AD7606_DmaCplCb);
158.        HAL_DMA_RegisterCallback(&TIMDMA, HAL_DMA_XFER_HALFCPLT_CB_ID, AD7606_DmaHalfCplCb);
159.        
160.        /* 启动DMA传输 */
161.        HAL_DMA_Start_IT(&TIMDMA, (uint32_t)AD7606_BASE, (uint32_t)g_sAd7606Buf, AD7606_BUFSIZE);
162.    }
  •   第46 – 74行,配置PWM频率和占空比,特别是占比设计比较考究。
  •   第123-135行,配置DMA,特别注意突发和FIFO设置,完全按照78.6.2小节配置。
  •   第157-158行,注册半传输完成中断和传输完成中断的回调函数。

77.7.6 第6步,FMC DMA双缓冲

通过注册半传输完成中断和传输完成中断回调函数实现双缓冲:

/* DMA传输完成回调函数,弱定义 */
__weak void AD7606_DmaCplCb(DMA_HandleTypeDef *hdma)
{
    
}

/* DMA半传输完成回调函数,弱定义 */
__weak void AD7606_DmaHalfCplCb(DMA_HandleTypeDef *hdma)
{
    
}

比如用户设置的DMA缓冲是int16_t buf[16],那么进入半传输完成回调,用户就可以处理buf[0]到buf[7]里面的数据,进入传输完成中断里面,处理buf[8]到buf[15]里面的数据。

77.7.7 第7步,AD7606过采样设置

AD7606的过采样实现比较简单,通过IO引脚就可以控制,支持2倍,4倍,8倍,16倍,32倍和64倍过采样设置。

/*
*********************************************************************************************************
*    函 数 名: AD7606_SetOS
*    功能说明: 配置AD7606数字滤波器,也就设置过采样倍率。
*              通过设置 AD7606_OS0、OS1、OS2口线的电平组合状态决定过采样倍率。
*              启动AD转换之后,AD7606内部自动实现剩余样本的采集,然后求平均值输出。
*
*              过采样倍率越高,转换时间越长。
*              0、无过采样时,AD转换时间 = 3.45us - 4.15us
*              1、2倍过采样时 = 7.87us - 9.1us
*              2、4倍过采样时 = 16.05us - 18.8us
*              3、8倍过采样时 = 33us - 39us
*              4、16倍过采样时 = 66us - 78us
*              5、32倍过采样时 = 133us - 158us
*              6、64倍过采样时 = 257us - 315us
*
*    形    参: _ucOS : 过采样倍率, 0 - 6
*    返 回 值: 无
*********************************************************************************************************
*/
void AD7606_SetOS(uint8_t _ucOS)
{
    g_tAD7606.ucOS = _ucOS;
    switch (_ucOS)
    {
        case AD_OS_X2:
            OS2_0();
            OS1_0();
            OS0_1();
            break;

        case AD_OS_X4:
            OS2_0();
            OS1_1();
            OS0_0();
            break;

        case AD_OS_X8:
            OS2_0();
            OS1_1();
            OS0_1();
            break;

        case AD_OS_X16:
            OS2_1();
            OS1_0();
            OS0_0();
            break;

        case AD_OS_X32:
            OS2_1();
            OS1_0();
            OS0_1();
            break;

        case AD_OS_X64:
            OS2_1();
            OS1_1();
            OS0_0();
            break;

        case AD_OS_NO:
        default:
            g_tAD7606.ucOS = AD_OS_NO;
            OS2_0();
            OS1_0();
            OS0_0();
            break;
    }
}

77.7.8 第8步,AD7606量程设置

AD7606支持两种量程,±5V和±10V,实现代码如下:

/*
*********************************************************************************************************
*    函 数 名: AD7606_SetInputRange
*    功能说明: 配置AD7606模拟信号输入量程。
*    形    参: _ucRange : 0 表示正负5V   1表示正负10V
*    返 回 值: 无
*********************************************************************************************************
*/
void AD7606_SetInputRange(uint8_t _ucRange)
{
    if (_ucRange == 0)
    {
        g_tAD7606.ucRange = 0;
        RANGE_0();    /* 设置为正负5V */
    }
    else
    {
        g_tAD7606.ucRange = 1;
        RANGE_1();    /* 设置为正负10V */
    }
}

77.7.9 第9步,DMA突发传输的1KB边界处理

针对突发传输,参考手册DMA章节有如下说明:

 

注意正确理解这段话的含义,意思是说突发传输期间,不可以跨越1KB对齐的地址,比如0x2000 0400、0x2000 0800、0x2000 0C00等地址。我们程序里面是设置的每次突发传输16个字节数据,这16个连续数据不能有跨越这些地址的情况。这对这个问题,有个比较巧妙的解决办法,直接设置DMA缓冲区16字节对齐即可,这样每次突发都不会有跨越这些地址的情况:

/* 8路同步采集,每次采集16字节数据,防止DMA突发方式1KB边界问题,即每次采集不要有跨边界的情况 */
#define AD7606_BUFSIZE        16
__align(16) int16_t g_sAd7606Buf[AD7606_BUFSIZE];   /* DMA双缓冲使用 */

77.8 AD7606板级支持包(bsp_fmcdma_ad7606.c)

AD7606驱动文件bsp_fmcdma_ad7606.c主要实现了如下几个API供用户调用:

  •   bsp_InitAD7606
  •   AD7606_SetOS
  •   AD7606_SetInputRange
  •   AD7606_Reset
  •   AD7606_StartConvst
  •   AD7606_ReadNowAdc
  •   AD7606_EnterAutoMode
  •   AD7606_StartRecord
  •   AD7606_StopRecord
  •   AD7606_FifoNewData
  •   AD7606_ReadFifo
  •   AD7606_FifoFull

77.8.1 函数bsp_InitAD7606

函数原型:

void bsp_InitAD7606(void)

函数描述:

主要用于AD7606的初始化。

77.8.2 函数AD7606_SetOS

函数原型:

void AD7606_SetOS(uint8_t _ucOS)

函数描述:

此函数用于配置AD7606数字滤波器,也就设置过采样倍率。通过设置 AD7606_OS0、OS1、OS2口线的电平组合状态决定过采样倍率。启动AD转换之后,AD7606内部自动实现剩余样本的采集,然后求平均值输出。

过采样倍率越高,转换时间越长。

无过采样时,AD转换时间 = 3.45us - 4.15us。

2倍过采样时 = 7.87us - 9.1us。

4倍过采样时 = 16.05us - 18.8us。

8倍过采样时 = 33us - 39us。

16倍过采样时 = 66us - 78us。

32倍过采样时 = 133us - 158us。

64倍过采样时 = 257us - 315us。

函数参数:

  •   第1个参数为范围0 – 6,分别对应无过采样,2倍过采样,4倍过采样,8倍过采样,16倍过采样,32倍过采样和64倍过采样。

77.8.3 函数AD7606_SetInputRange

函数原型:

void AD7606_SetInputRange(uint8_t _ucRange)

函数描述:

配置AD7606模拟信号输入量程。

函数参数:

  •   第1个参数为0 表示正负5V ,1表示正负10V。

77.8.4 函数AD7606_Reset

函数原型:

void AD7606_Reset(void)

函数描述:

此函数用于硬件复位AD7606,复位之后恢复到正常工作状态。

77.8.5 函数AD7606_StartRecord

函数原型:

void AD7606_StartRecord(uint32_t _ulFreq)

函数描述:

用于启动采集。

函数参数:

  •   第1个参数是采样频率,范围1-200KHz,单位Hz。

77.8.6 函数AD7606_StopRecord

函数原型:

void AD7606_StopRecord(void)

函数描述:

此函数用于停止采集定时器。函数AD7606_StartRecord和AD7606_StopRecord是配套的。

77.9 J-Scope实时展示AD7606采集数据说明

J-Scope专题教程(实时展示要用J-Scope的RTT模式),本章配套例子也做了支持:

http://www.armbbs.cn/forum.php?mod=viewthread&tid=86881

看完专题教程,基本就会操作了,这里有三点注意事项需要大家提前有个了解。另外,推荐使用MDK版工程做测试J-Scope,IAR版容易测试不正常。

77.9.1 J-Scope闪退问题解决办法

如下界面,不要点击选择按钮,闪退就是因为点击了这个选择按钮。


 

直接手动填写型号即可,比如STM32H743XI,STM32F429BI,STM32F407IG,STM32F103ZE等。

77.9.2 J-Scope多通道传输实现

J-Scope的多通道传输配置好函数SEGGER_RTT_ConfigUpBuffer即可,主要是通过第2个参数实现的。

    /*
        配置通道1,上行配置
        默认情况下,J-Scope仅显示1个通道。
        上传1个通道的波形,配置第2个参数为JScope_i2
        上传2个通道的波形,配置第2个参数为JScope_i2i2
        上传3个通道的波形,配置第2个参数为JScope_i2i2i2
        上传4个通道的波形,配置第2个参数为JScope_i2i2i2i2
        上传5个通道的波形,配置第2个参数为JScope_i2i2i2i2i2
        上传6个通道的波形,配置第2个参数为JScope_i2i2i2i2i2i2
        上传7个通道的波形,配置第2个参数为JScope_i2i2i2i2i2i2i2
        上传8个通道的波形,配置第2个参数为JScope_i2i2i2i2i2i2i2i2
    */    
    SEGGER_RTT_ConfigUpBuffer(1, "JScope_i2", buf, 20480, SEGGER_RTT_MODE_NO_BLOCK_SKIP);

使用函数SEGGER_RTT_Write上传数据时,要跟配置的通道数匹配,比如配置的三个通道,就需要调用三次函数:

SEGGER_RTT_Write(1, &(g_tAD7606.sNowAdc[0]), 2);
SEGGER_RTT_Write(1, &(g_tAD7606.sNowAdc[1]), 2);    
SEGGER_RTT_Write(1, &(g_tAD7606.sNowAdc[2]), 2);

多路效果:

 

77.9.3 J-Scope带宽问题

普通的JLINK时钟速度8 - 12MHz时, J-Scope的速度基本可以达到500KB/S(注意,单位是字节)AD7606的最高采样率是200Ksps,16bit,那么一路采集就有400KB/S的速速,所以要根据设置的采样率设置要显示的J-Scope通道数,如果超出了最高通信速度,波形显示会混乱。

 

       200Ksps时,实时显示1路

       100Ksps时,实时显示2路

       50Ksps时, 实时显示4路

       25Ksps时, 实时显示8路

 

实际速度以底栏的展示为准,如果与设置的速度差异较大,说明传输异常了。

 

77.10          AD7606驱动移植和使用

AD7606移植步骤如下:

  •   第1步:复制bsp_fmcdma_ad7606.c和bsp_fmcdma_ad7606.h到自己的工程目录,并添加到工程里面。
  •   第2步:根据使用的CONVST引脚,FMC DMA,过采样引脚,量程控制引脚,复位引脚,修改bsp_fmcdma_ad7606.c开头的宏定义。

这里要特别注意过采样引脚,量程控制引脚和复位引脚是采用的扩展IO,需要大家根据自己的情况修改。

/* CONVST 启动ADC转换的GPIO = PC6 */
#define CONVST_RCC_GPIO_CLK_ENABLE    __HAL_RCC_GPIOC_CLK_ENABLE
#define CONVST_TIM8_CLK_ENABLE      __HAL_RCC_TIM8_CLK_ENABLE
#define CONVST_RCC_GPIO_CLK_DISBALE    __HAL_RCC_GPIOC_CLK_DISABLE
#define CONVST_TIM8_CLK_DISABLE     __HAL_RCC_TIM8_CLK_DISABLE
#define CONVST_GPIO        GPIOC
#define CONVST_PIN             GPIO_PIN_6
#define CONVST_AF             GPIO_AF3_TIM8
#define CONVST_TIMX        TIM8
#define CONVST_TIMCH         TIM_CHANNEL_1

/* FMC DMA */
#define TIMx_UP_DMA_STREAM_CLK_ENABLE      __HAL_RCC_DMA2_CLK_ENABLE
#define TIMx_UP_DMA_STREAM_CLK_DISABLE  __HAL_RCC_DMA2_CLK_DISABLE
#define TIMx_UP_DMA_STREAM             DMA2_Stream1
#define TIMx_UP_DMA_CHANNEL            DMA_CHANNEL_7
#define TIMx_UP_DMA_IRQn               DMA2_Stream1_IRQn
#define TIMx_UP_DMA_IRQHandler         DMA2_Stream1_IRQHandler

/* BUSY 转换完毕信号 = PE5 */
#define BUSY_RCC_GPIO_CLK_ENABLE __HAL_RCC_GPIOE_CLK_ENABLE
#define BUSY_GPIO        GPIOE
#define BUSY_PIN        GPIO_PIN_5
#define BUSY_IRQn        EXTI9_5_IRQn
#define BUSY_IRQHandler    EXTI9_5_IRQHandler

/* 设置过采样的IO, 在扩展的74HC574上 */
#define OS0_1()        HC574_SetPin(AD7606_OS0, 1)
#define OS0_0()        HC574_SetPin(AD7606_OS0, 0)
#define OS1_1()        HC574_SetPin(AD7606_OS1, 1)
#define OS1_0()        HC574_SetPin(AD7606_OS1, 0)
#define OS2_1()        HC574_SetPin(AD7606_OS2, 1)
#define OS2_0()        HC574_SetPin(AD7606_OS2, 0)

/* 启动AD转换的GPIO : PC6 */
#define CONVST_1()        CONVST_GPIO->BSRR = CONVST_PIN
#define CONVST_0()        CONVST_GPIO->BSRR = ((uint32_t)CONVST_PIN << 16U)

/* 设置输入量程的GPIO, 在扩展的74HC574上 */
#define RANGE_1()    HC574_SetPin(AD7606_RANGE, 1)
#define RANGE_0()    HC574_SetPin(AD7606_RANGE, 0)

/* AD7606复位口线, 在扩展的74HC574上 */
#define RESET_1()    HC574_SetPin(AD7606_RESET, 1)
#define RESET_0()    HC574_SetPin(AD7606_RESET, 0)
  •   第3步:根据具体用到的FMC引脚,修改函数AD7606_CtrlLinesConfig里面做的IO配置
  •   第4步:根据需要设置DMA缓冲大小:
/* 8路同步采集,每次采集16字节数据,防止DMA突发方式1KB边界问题,即每次采集不要有跨边界的情况 */
#define AD7606_BUFSIZE        16
__align(16) int16_t g_sAd7606Buf[AD7606_BUFSIZE];   /* DMA双缓冲使用 */
  •   第5步:根据使用的FMC BANK,修改函数AD7606_FSMCConfig里面的BANK配置,这点非常容易疏忽。
  •   第6步:注意MPU配置,详情见本章78.7.4小节。
  •   第7步:初始化AD7606。
bsp_InitAD7606();    /* 配置AD7606所用的GPIO */
  •   第8步:AD7606驱动主要用到HAL库的FMC驱动文件,简单省事些可以添加所有HAL库C源文件进来。
  •   第9步:应用方法看本章节配套例子即可。

77.11          实验例程设计框架   

通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

  第1阶段,上电启动阶段:

  • 这部分在第14章进行了详细说明。

  第2阶段,进入main函数:

  •   第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
  •   第2部分,应用程序设计部分,测试AD7606。

77.12          实验例程说明(MDK)

配套例子:

V7-057_ AD7606的FMC DMA双缓冲总线驱动方式实现(8通道同步采样, 16bit, 正负10V)

实验目的:

  1. 学习AD7606的FMC DMA双缓冲驱动方式实现。

重要提示:

  1. 板子上电后,默认是100Ksps的2倍过采样。
  2. 如果使用的JLINK速度不够快,导致J-Scope无法最高速度实时上传,可以使用摇杆上下键设置过采样来降低上传速度。
  3. 默认情况下,程序仅上传了AD7606通道1采集的数据。
  4. 串口数据展示推荐使用SecureCRT,因为数据展示做了特别处理,方便采集数据在串口软件同一个位置不断刷新。

实验内容:

1、AD7606的FMC驱动做了两种采集方式

(1)软件定时获取方式,适合低速查询获取。

(2)FIFO工作模式,适合8路实时采集,支持最高采样率200Ksps。

2、将模拟输入接地时,采样值是0左右。

3、模拟输入端悬空时,采样值在某个范围浮动(这是正常的,这是AD7606内部输入电阻导致的浮动电压)。

4、出厂的AD7606模块缺省是8080 并行接口。如果用SPI接口模式,需要修改 R1 R2电阻配置。

5、配置CVA CVB 引脚为PWM输出模式,周期设置为需要的采样频率,之后MCU将产生周期非常稳定的AD转换信号。

实验操作:

  1. 启动一个自动重装软件定时器,每100ms翻转一次LED2。
  2. K1键       : 切换量程(5V或10V)。
  3. K2键       : 进入FIFO工作模式。
  4. K3键       : 进入软件定时采集模式。
  5. 摇杆上下键 : 调节过采样参数。

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1。

 

J-Scope波形效果:


 

模块插入位置:


 

程序设计:

  系统栈大小分配:

 

  RAM空间用的DTCM:

 

  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*
*********************************************************************************************************
*    函 数 名: bsp_Init
*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
       - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
       - 设置NVIV优先级分组为4。
     */
    HAL_Init();

    /* 
       配置系统时钟到400MHz
       - 切换使用HSE。
       - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder并开启 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       
    bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    bsp_InitTimer();       /* 初始化滴答定时器 */
    bsp_InitLPUart();     /* 初始化串口 */
    bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
    bsp_InitLed();         /* 初始化LED */    
bsp_InitExtSDRAM(); /* 初始化SDRAM */

    /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */    
    bsp_InitAD7606();    /* 配置AD7606所用的GPIO */
}

 

  MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。

/*
*********************************************************************************************************
*    函 数 名: MPU_Config
*    功能说明: 配置MPU
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

    /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
    
    /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 数 名: CPU_CACHE_Enable
*    功能说明: 使能L1 Cache
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}

 

  每10ms调用一次按键处理:

按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。

/*
*********************************************************************************************************
*    函 数 名: bsp_RunPer10ms
*    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
*              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
    bsp_KeyScan10ms();
}

 

  主功能:

主程序实现如下操作:

  •   启动一个自动重装软件定时器,每100ms翻转一次LED2。
  •   K1键       : 切换量程(5V或10V)。
  •   K2键       : 进入FIFO工作模式。
  •   K3键       : 进入软件定时采集模式。
  •   摇杆上下键 : 调节过采样参数。
/*
*********************************************************************************************************
*    函 数 名: main
*    功能说明: c程序入口
*    形    参: 无
*    返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
    bsp_Init();        /* 硬件初始化 */
    
    PrintfLogo();    /* 打印例程名称和版本等信息 */

    DemoFmcAD7606(); /* AD7606测试 */
}

/*
*********************************************************************************************************
*    函 数 名: DemoFmcAD7606
*    功能说明: AD7606测试
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void DemoFmcAD7606(void)
{
    uint8_t ucKeyCode;
    uint8_t ucRefresh = 0;

    
    sfDispMenu();        /* 打印命令提示 */

    ucRefresh = 0;        /* 数据在串口刷新的标志 */
    
    AD7606_SetOS(AD_OS_NO);        /* 无过采样 */
    AD7606_SetInputRange(1);    /* 0表示输入量程为正负5V, 1表示正负10V */
    AD7606_StartConvst();        /* 启动1次转换 */
    
    /* 上电默认采样率 */
    g_tAD7606.ucOS = 1;                /* 2倍过采样 */
    AD7606_StartRecord(100000);        /* 启动100kHz采样速率 */
    AD7606_SetOS(g_tAD7606.ucOS);   /* 设置2倍过采样 */
    

    bsp_StartAutoTimer(0, 500);    /* 启动1个500ms的自动重装的定时器 */
    bsp_StartAutoTimer(3, 200);    /* 启动1个200ms的自动重装的定时器 */
    
    /*
        配置通道1,上行配置
        默认情况下,J-Scope仅显示1个通道。
        上传1个通道的波形,配置第2个参数为JScope_i2
        上传2个通道的波形,配置第2个参数为JScope_i2i2
        上传3个通道的波形,配置第2个参数为JScope_i2i2i2
        上传4个通道的波形,配置第2个参数为JScope_i2i2i2i2
        上传5个通道的波形,配置第2个参数为JScope_i2i2i2i2i2
        上传6个通道的波形,配置第2个参数为JScope_i2i2i2i2i2i2
        上传7个通道的波形,配置第2个参数为JScope_i2i2i2i2i2i2i2
        上传8个通道的波形,配置第2个参数为JScope_i2i2i2i2i2i2i2i2
    */    
    SEGGER_RTT_ConfigUpBuffer(1, "JScope_i2", buf, 20480, SEGGER_RTT_MODE_NO_BLOCK_SKIP);

    while(1)
    {
        bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
        
        /* 判断定时器超时时间 */
        if (bsp_CheckTimer(3))    
        {
            /* 每隔100ms 进来一次 */  
            bsp_LedToggle(4);
        }

        if (bsp_CheckTimer(0))
        {
            ucRefresh = 1;    /* 刷新显示 */
        }
        
        if (ucRefresh == 1)
        {
            ucRefresh = 0;

            /* 处理数据 */
            AD7606_Mak();
                                         
            /* 打印ADC采样结果 */
            AD7606_Disp();        
        }

        /* 按键检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。这个函数不会
        等待按键按下,这样我们可以在while循环内做其他的事情 */
        ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:                        /* K1键按下 切换量程 */
                    if (g_tAD7606.ucRange == 0)
                    {
                        AD7606_SetInputRange(1);
                    }
                    else
                    {
                        AD7606_SetInputRange(0);
                    }
                    ucRefresh = 1;
                    break;

                case KEY_DOWN_K2:                        /* K2键按下 */
                    g_tAD7606.ucOS = 1;                    /* 2倍过采样 */
                    AD7606_StartRecord(100000);            /* 启动100kHz采样速率 */
                    AD7606_SetOS(g_tAD7606.ucOS);       /* 设置2倍过采样 */
                    break;

                case KEY_DOWN_K3:                        /* K3键按下 */
                    AD7606_StopRecord();                /* 停止记录 */
                    break;

                case JOY_DOWN_U:                        /* 摇杆UP键按下 */
                    if (g_tAD7606.ucOS < 6)
                    {
                        g_tAD7606.ucOS++;
                    }
                    
                    AD7606_SetOS(g_tAD7606.ucOS);

                AD7606_StartRecord(AD7606_SampleFreq[g_tAD7606.ucOS]);/* 启动当前过采样下最高速度 */
                    
                    ucRefresh = 1;
                    break;

                case JOY_DOWN_D:                        /* 摇杆DOWN键按下 */
                    if (g_tAD7606.ucOS > 0)
                    {
                        g_tAD7606.ucOS--;
                    }
                    AD7606_SetOS(g_tAD7606.ucOS);
                    ucRefresh = 1;

                AD7606_StartRecord(AD7606_SampleFreq[g_tAD7606.ucOS]);    /* 启动当前过采样下最高速度 */
                    break;

                default:
                    /* 其他的键值不处理 */
                    break;
            }
        }
    }
}

 

77.13   总结

本章节涉及到的知识点非常多,实战性较强,需要大家稍花点精力去研究。

 

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值