完整教程下载地址: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.7.5 第5步,AD7606的FMC DMA实现(核心)
77.8 AD7606板级支持包(bsp_fmcdma_ad7606.c)
77.1 初学者重要提示
- 学习本章节前,务必优先学习第76章,本章是建立在76章的基础上。
- 本章77.6小节的知识点对于本章的理解尤其重要。
- AD7606 的配置很简单,它没有内部寄存器,量程范围和过采样参数是通过外部IO控制的,采样速率由MCU或DSP提供的脉冲频率控制。
- AD7606必须使用单5V供电。而AD7606和MCU之间的通信接口电平由VIO(VDRIVE)引脚控制。也就是说VIO必须接单片机的电源,可以是3.3V也可以是5V(范围2.3V – 5V)。
- 正确的理解过采样,比如我们设置是1Ksps采样率,64倍过采样。意思是指每次采样,AD7606会采样64次数据并求平均,相当于AD7606以64Ksps进行采样的,只是将每64个采样点的值做了平均,用户得到的值就是平均后的数值。因此,如果使用AD7606最高的200Ksps采样率,就不可以使用过采样了。
- STM32H7驱动AD7606配合J-Scope实时输出,效果绝了,堪比示波器:http://www.armbbs.cn/forum.php?mod=viewthread&tid=97393 。使用方法详解本章节77.9小节。
- 本章配套例子的串口数据展示推荐使用SecureCRT,因为数据展示做了特别处理,方便采集数据在串口软件同一个位置不断刷新。
- AD7606数据手册,模块原理图(通用版)和接线图都已经放到本章教程配置例子的Doc文件里。
- ADC 的专业术语诠释文档,推荐大家看看:http://www.armbbs.cn/forum.php?mod=viewthread&tid=89414 。
- 测试本章配套例子前重要提示:
- 测试时,务必使用外置电源为开发板供电,因为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)
实验目的:
- 学习AD7606的FMC DMA双缓冲驱动方式实现。
重要提示:
- 板子上电后,默认是100Ksps的2倍过采样。
- 如果使用的JLINK速度不够快,导致J-Scope无法最高速度实时上传,可以使用摇杆上下键设置过采样来降低上传速度。
- 默认情况下,程序仅上传了AD7606通道1采集的数据。
- 串口数据展示推荐使用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转换信号。
实验操作:
- 启动一个自动重装软件定时器,每100ms翻转一次LED2。
- K1键 : 切换量程(5V或10V)。
- K2键 : 进入FIFO工作模式。
- K3键 : 进入软件定时采集模式。
- 摇杆上下键 : 调节过采样参数。
上电后串口打印的信息:
波特率 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 总结
本章节涉及到的知识点非常多,实战性较强,需要大家稍花点精力去研究。