赞
踩
最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255
本章节为大家讲解FMC DMA双缓冲方式驱动数模转换器AD7606,实战性较强。
目录
第36章 STM32F429的FMC总线应用之DMA双缓冲驱动AD7606(8通道同步采样, 16bit, 正负10V)
36.7.4 第5步,AD7606的FMC DMA实现(核心)
36.8 AD7606板级支持包(bsp_fmcdma_ad7606.c)
36.2,36.3,36.4和36.5小节的知识在第35章节有详细说明,本章不再赘述。
FMC的并行接线方式如下:
这里实现FMC DMA方式的关键就是BUSY引脚去触发DMA控制,如果是单纯的DMA正常模式,实现比较简单,接收到INT引脚的就绪状态,使用FMC DMA将8路数据全部读取出来即可。
难点在于驱动AD7606不像SRAM,SDRAM,仅需一个FMC接口就行,它还需要一个独立的时钟引脚,每次时钟触发要连续读取8次数据。实现这套方案有如下几点:
通过定时器PWM输出控制AD7606转换比较容易实现,我们上一个章节就是这种方式控制的。
有了定时器PWM控制AD7606转换。还需要保证每个PWM脉冲读取一次数据,而且是连续读取8路。这就需要用到下面两个知识点,非常关键:
这样可以保证每个PWM后都配有一个UP更新,通过UP更新来触发DMA传输。
STM32H7支持的突发方式如下,下面这个表格尤其重要,配置突发务必要按照这个表格来配置:
我们要实现的是连续读取8路16bit数据,上面表格中红色方框部分刚好支持。
解决这个问题的关键就是AD7606支持转换期间读取:
这个功能正好用在本设计中,这里有四个关键时序参数:
表示最短的CONVST低电平脉冲,最小值25ns,这个时间我们用PWM脉冲低电平控制。
表示最短的CONVST高电平脉冲,最小值25ns,这个时间我们用PWM脉冲高电平控制。
表示CS上升沿和BUSY下降沿之间的最长时间,最大值25ns。这个参数的主要作用是限制用户一定要在BUSY转换有效之前立即读取。
表示AD7606转换时间,对于AD7606-8来说,最小值范围是3.45us到4.15us。
有了这三个参数,配置PWM的占空比就比较考究了,我们仅需配置好PMW低电平宽度,将其设置为接近于25ns的低电平脉宽时间,其余时间全是高电平即可。这样我们就保证了每次脉冲立即读取上一次的转换数据。
DMA双缓冲的实现比较简单,我们借助DMA半传输完成中断和DMA传输完成中断即可。其中半传输完成中断就是DMA数据传输完成一半的中断。
AD7606的程序驱动框架设计如下:
有了这个框图,程序设计就比较好理解了。
这里需要把用到的GPIO时钟、FMC时钟、GPIO引脚和复用配置好即可:
/* ********************************************************************************************************* * 函 数 名: AD7606_CtrlLinesConfig * 功能说明: 配置GPIO口线,FMC管脚设置为复用功能 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ /* 安富莱STM32-V6开发板接线方法: 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一起译码 PG9/FMC_NE2 --- 主片选(TFT, OLED 和 AD7606) */ /* 控制AD7606参数的其他IO分配在扩展的74HC574上 D13 - AD7606_OS0 D14 - AD7606_OS1 D15 - AD7606_OS2 D24 - AD7606_RESET D25 - AD7606_RAGE */ static void AD7606_CtrlLinesConfig(void) { /* bsp_fm_io 已配置fmc,bsp_InitExtIO(); 此处可以不必重复配置FMC,其它的要配置。 */ 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_VERY_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_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| GPIO_PIN_9; HAL_GPIO_Init(GPIOG, &gpio_init_structure); /* 配置BUSY引脚,默认是普通IO状态 */ { GPIO_InitTypeDef GPIO_InitStructure; __HAL_RCC_SYSCFG_CLK_ENABLE(); BUSY_RCC_GPIO_CLK_ENABLE(); /* 打开GPIO时钟 */ /* BUSY信号,使用的PE5,用于转换完毕检测 */ GPIO_InitStructure.Mode = GPIO_MODE_INPUT; /* 设置推挽输出 */ GPIO_InitStructure.Pull = GPIO_NOPULL; /* 无上拉下拉 */ GPIO_InitStructure.Pin = BUSY_PIN; HAL_GPIO_Init(BUSY_GPIO, &GPIO_InitStructure); } /* 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_HIGH; /* GPIO速度等级 */ GPIO_InitStructure.Pin = CONVST_PIN; HAL_GPIO_Init(CONVST_GPIO, &GPIO_InitStructure); } }
这里重点注意AD7606_BUSY引脚,上电后的默认配置是普通IO。另外还有过采样的3个引脚,量程配置的1个引脚和复位控制的1个引脚,均通过V6板子的扩展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)
STM32F429的FMC总线是挂在AHB3上的,频率与内核主频一样:
一般我们都是将F429的主频设置为168MHz或者180MHz,那么FMC的频率就是168MHz或者180MHz。
由于操作AD7606仅需要读操作,而且使用的是FMC总线的Mode_A,那么仅需按照如下时序图配置好即可:
根据这个时序图,重点配置好ADDSET地址建立时间和DATAST数据建立时间即可。
DATAST(DataSetupTime,数据建立时间)
DATAST实际上对应的就是35.4.4小节里面的t10 。RD读信号的低电平脉冲宽度,通信电压不同,时间不同,对于STM32来说,FMC通信电平一般是3.3V,即最小值21ns。
ADDST(AddressSetupTime,地址建立时间)
DATAST实际上对应的就是35.4.4小节里面的t11 或者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. 4-x-6-x-x-x : RD高持续35.7ns,低电平持续23.8ns. 读取8路样本数据到内存差不多就是476ns。 25. */ 26. hsram.Instance = FMC_NORSRAM_DEVICE; 27. hsram.Extended = FMC_NORSRAM_EXTENDED_DEVICE; 28. 29. /* FMC使用的HCLK,主频168MHz,1个FMC时钟周期就是5.95ns */ 30. SRAM_Timing.AddressSetupTime = 4; /* 4*5.95ns=23.8ns,地址建立时间,范围0 -15个FMC时钟周 31. 期个数 */ 32. SRAM_Timing.AddressHoldTime = 0; /* 地址保持时间,配置为模式A时,用不到此参数 范围1 -15 33. 个时钟周期个数 */ 34. SRAM_Timing.DataSetupTime = 6; /* 6*5.95ns=35.7ns,数据保持时间,范围1 -255个时钟周期个 35. 数 */ 36. SRAM_Timing.BusTurnAroundDuration = 0; /* 此配置用不到这个参数 */ 37. SRAM_Timing.CLKDivision = 0; /* 此配置用不到这个参数 */ 38. SRAM_Timing.DataLatency = 0; /* 此配置用不到这个参数 */ 39. SRAM_Timing.AccessMode = FMC_ACCESS_MODE_A; /* 配置为模式A */ 40. 41. hsram.Init.NSBank = FMC_NORSRAM_BANK2; /* 使用的BANK2,即使用的片选 42. FMC_NE2 */ 43. hsram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE; /* 禁止地址数据复用 */ 44. hsram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM; /* 存储器类型SRAM */ 45. hsram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_32; /* 32位总线宽度 */ 46. hsram.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE; /* 关闭突发模式 */ 47. hsram.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW; /* 用于设置等待信号的极性,关闭突 48. 发模式,此参数无效 */ 49. hsram.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS; /* 关闭突发模式,此参数无效 */ 50. hsram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE; /* 用于使能或者禁止写保护 */ 51. hsram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE; /* 关闭突发模式,此参数无效 */ 52. hsram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE; /* 禁止扩展模式 */ 53. hsram.Init.AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_DISABLE; /* 用于异步传输期间,使能或者禁止 54. 等待信号,这里选择关闭 */ 55. hsram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE; /* 禁止写突发 */ 56. hsram.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ONLY; /* 仅同步模式才做时钟输出 */ 57. hsram.Init.WriteFifo = FMC_WRITE_FIFO_ENABLE; /* 使能写FIFO */ 58. 59. /* 初始化SRAM控制器 */ 60. if (HAL_SRAM_Init(&hsram, &SRAM_Timing, &SRAM_Timing) != HAL_OK) 61. { 62. /* 初始化错误 */ 63. Error_Handler(__FILE__, __LINE__); 64. } 65. }
这里把几个关键的地方阐释下:
这部分代码是本章36.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. system_stm32f4xx.c 文件中 void SetSysClock(void) 函数对时钟的配置如下: 26. 27. HCLK = SYSCLK / 1 (AHB1Periph) 28. PCLK2 = HCLK / 2 (APB2Periph) 29. PCLK1 = HCLK / 4 (APB1Periph) 30. 31. 因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = PCLK1 x 2 = SystemCoreClock / 2; 32. 因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = PCLK2 x 2 = SystemCoreClock; 33. 34. APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13,TIM14 35. APB2 定时器有 TIM1, TIM8 ,TIM9, TIM10, TIM11 36. 37. ----------------------------------------------------------------------- */ 38. if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM9) || (TIMx == TIM10) || (TIMx == TIM11)) 39. { 40. /* APB2 定时器时钟 = 168M */ 41. uiTIMxCLK = SystemCoreClock; 42. 43. if (_ulFreq < 100) 44. { 45. usPrescaler = 10000 - 1; /* 分频比 = 10000 */ 46. usPeriod = (uiTIMxCLK / 10000) / _ulFreq - 1; /* 自动重装的值, usPeriod最小值168, 单 47. 位59us */ 48. pulse = usPeriod; /* 设置低电平时间59us,注意usPeriod已经进行了减1操作 */ 49. } 50. else if (_ulFreq < 3000) 51. { 52. usPrescaler = 100 - 1; /* 分频比 = 100 */ 53. usPeriod = (uiTIMxCLK / 100) / _ulFreq - 1; /* 自动重装的值, usPeriod最小值560,单位 54. 595ns */ 55. pulse = usPeriod-1; /* 设置低电平时间1.19us,注意usPeriod已经进行了减1操作 */ 56. } 57. else /* 大于4K的频率,无需分频 */ 58. { 59. usPrescaler = 0; /* 分频比 = 1 */ 60. usPeriod = uiTIMxCLK / _ulFreq - 1; /* 自动重装的值, usPeriod最小值840,单位5.95ns */ 61. pulse = usPeriod - 199; /* 设置低电平时间1.19us,注意usPeriod已经进行了减1操作 */ 62. } 63. } 64. else 65. { 66. /* APB1 定时器 = 84M */ 67. uiTIMxCLK = SystemCoreClock / 2; 68. 69. if (_ulFreq < 100) 70. { 71. usPrescaler = 10000 - 1; /* 分频比 = 10000 */ 72. usPeriod = (uiTIMxCLK / 10000) / _ulFreq - 1; /* 自动重装的值, usPeriod最小值84, 单位 73. 119us */ 74. pulse = usPeriod; /* 设置低电平时间119us,注意usPeriod已经进行了减1操作 */ 75. } 76. else if (_ulFreq < 3000) 77. { 78. usPrescaler = 100 - 1; /* 分频比 = 100 */ 79. usPeriod = (uiTIMxCLK / 100) / _ulFreq - 1; /* 自动重装的值, usPeriod最小值280,单位 80. 1.19us */ 81. pulse = usPeriod; /* 设置低电平时间1.19us,注意usPeriod已经进行了减1操作 */ 82. } 83. else /* 大于4K的频率,无需分频 */ 84. { 85. usPrescaler = 0; /* 分频比 = 1 */ 86. usPeriod = uiTIMxCLK / _ulFreq - 1; /* 自动重装的值, usPeriod最小值420,单位11.9ns */ 87. pulse = usPeriod - 99; /* 设置低电平时间1.19us,注意usPeriod已经进行了减1操作 */ 88. } 89. } 90. 91. 92. if (HAL_TIM_PWM_DeInit(&TimHandle) != HAL_OK) 93. { 94. Error_Handler(__FILE__, __LINE__); 95. } 96. 97. if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK) 98. { 99. Error_Handler(__FILE__, __LINE__); 100. } 101. 102. /* 配置定时器PWM输出通道 */ 103. sConfig.OCMode = TIM_OCMODE_PWM1; /* 配置输出比较模式 */ 104. sConfig.OCPolarity = TIM_OCPOLARITY_HIGH; /* 设置输出高电平有效 */ 105. sConfig.OCFastMode = TIM_OCFAST_DISABLE; /* 关闭快速输出模式 */ 106. sConfig.OCNPolarity = TIM_OCNPOLARITY_HIGH; /* 配置互补输出高电平有效 */ 107. sConfig.OCIdleState = TIM_OCIDLESTATE_SET; /* 空闲状态时,设置输出比较引脚为高电平 */ 108. sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET; /* 空闲状态时,设置互补输出比较引脚为低电平 */ 109. 110. /* 占空比 */ 111. sConfig.Pulse = pulse; 112. if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, CONVST_TIMCH) != HAL_OK) 113. { 114. Error_Handler(__FILE__, __LINE__); 115. } 116. 117. /* 使能定时器中断 */ 118. __HAL_TIM_ENABLE_DMA(&TimHandle, TIM_DMA_UPDATE); 119. 120. /* 启动PWM输出 */ 121. if (HAL_TIM_PWM_Start(&TimHandle, CONVST_TIMCH) != HAL_OK) 122. { 123. Error_Handler(__FILE__, __LINE__); 124. } 125. 126. /* 定时器UP更新触发DMA传输 */ 127. TIMDMA.Instance = TIMx_UP_DMA_STREAM; /* 例化使用的DMA数据流 */ 128. TIMDMA.Init.FIFOMode = DMA_FIFOMODE_ENABLE; /* 使能FIFO*/ 129. TIMDMA.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* 用于设置阀值 */ 130. TIMDMA.Init.MemBurst = DMA_MBURST_INC8; /* 用于存储器突发 */ 131. TIMDMA.Init.PeriphBurst = DMA_PBURST_INC8; /* 用于外设突发 */ 132. TIMDMA.Init.Request = TIMx_UP_DMA_REQUEST; /* 请求类型 */ 133. TIMDMA.Init.Direction = DMA_PERIPH_TO_MEMORY; /* 传输方向是从外设到存储器 */ 134. TIMDMA.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址自增禁止 */ 135. TIMDMA.Init.MemInc = DMA_MINC_ENABLE; /* 存储器地址自增使能 */ 136. TIMDMA.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; /* 外设数据传输位宽选择半字,即16bit */ 137. TIMDMA.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; /* 存储器数据传输位宽选择半字,即16bit */ 138. TIMDMA.Init.Mode = DMA_CIRCULAR; /* 循环模式 */ 139. TIMDMA.Init.Priority = DMA_PRIORITY_LOW; /* 优先级低 */ 140. 141. /* 复位DMA */ 142. if(HAL_DMA_DeInit(&TIMDMA) != HAL_OK) 143. { 144. Error_Handler(__FILE__, __LINE__); 145. } 146. 147. /* 初始化DMA */ 148. if(HAL_DMA_Init(&TIMDMA) != HAL_OK) 149. { 150. Error_Handler(__FILE__, __LINE__); 151. } 152. 153. /* 关联DMA句柄到TIM */ 154. //__HAL_LINKDMA(&TimHandle, hdma[TIM_DMA_ID_UPDATE], TIMDMA); 155. 156. /* 配置DMA中断 */ 157. HAL_NVIC_SetPriority(TIMx_UP_DMA_IRQn, 1, 0); 158. HAL_NVIC_EnableIRQ(TIMx_UP_DMA_IRQn); 159. 160. /* 注册半传输完成中断和传输完成中断 */ 161. HAL_DMA_RegisterCallback(&TIMDMA, HAL_DMA_XFER_CPLT_CB_ID, AD7606_DmaCplCb); 162. HAL_DMA_RegisterCallback(&TIMDMA, HAL_DMA_XFER_HALFCPLT_CB_ID, AD7606_DmaHalfCplCb); 163. 164. /* 启动DMA传输 */ 165. HAL_DMA_Start_IT(&TIMDMA, (uint32_t)AD7606_BASE, (uint32_t)g_sAd7606Buf, AD7606_BUFSIZE); 166. }
通过注册半传输完成中断和传输完成中断回调函数实现双缓冲:
/* 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]里面的数据。
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; } }
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 */ } }
针对突发传输,参考手册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双缓冲使用 */
AD7606驱动文件bsp_fmcdma_ad7606.c主要实现了如下几个API供用户调用:
函数原型:
void bsp_InitAD7606(void)
函数描述:
主要用于AD7606的初始化。
函数原型:
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。
函数参数:
函数原型:
void AD7606_SetInputRange(uint8_t _ucRange)
函数描述:
配置AD7606模拟信号输入量程。
函数参数:
函数原型:
void AD7606_Reset(void)
函数描述:
此函数用于硬件复位AD7606,复位之后恢复到正常工作状态。
函数原型:
void AD7606_StartRecord(uint32_t _ulFreq)
函数描述:
用于启动采集。
函数参数:
函数原型:
void AD7606_StopRecord(void)
函数描述:
此函数用于停止采集定时器。函数AD7606_StartRecord和AD7606_StopRecord是配套的。
J-Scope专题教程(实时展示要用J-Scope的RTT模式),本章配套例子也做了支持:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86881 。
看完专题教程,基本就会操作了,这里有三点注意事项需要大家提前有个了解。另外,推荐使用MDK版工程做测试J-Scope,IAR版容易测试不正常。
如下界面,不要点击选择按钮,闪退就是因为点击了这个选择按钮。
直接手动填写型号即可,比如STM32H743XI,STM32F429BI,STM32F407IG,STM32F103ZE等。
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);
多路效果:
普通的JLINK时钟速度8 - 12MHz时, J-Scope的速度基本可以达到500KB/S(注意,单位是字节)AD7606的最高采样率是200Ksps,16bit,那么一路采集就有400KB/S的速速,所以要根据设置的采样率设置要显示的J-Scope通道数,如果超出了最高通信速度,波形显示会混乱。
200Ksps时,实时显示1路
100Ksps时,实时显示2路
50Ksps时, 实时显示4路
25Ksps时, 实时显示8路
实际速度以底栏的展示为准,如果与设置的速度差异较大,说明传输异常了。
AD7606移植步骤如下:
这里要特别注意过采样引脚,量程控制引脚和复位引脚是采用的扩展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 /* 设置过采样的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)
/* 8路同步采集,每次采集16字节数据,防止DMA突发方式1KB边界问题,即每次采集不要有跨边界的情况 */ #define AD7606_BUFSIZE 16 __align(16) int16_t g_sAd7606Buf[AD7606_BUFSIZE]; /* DMA双缓冲使用 */
bsp_InitAD7606(); /* 配置AD7606所用的GPIO */
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
第1阶段,上电启动阶段:
第2阶段,进入main函数:
配套例子:
V6-017_ AD7606的FMC DMA双缓冲总线驱动方式实现(8通道同步采样, 16bit, 正负10V)
实验目的:
重要提示:
实验内容:
1、AD7606的FMC驱动做了两种采集方式
(1)软件定时获取方式,适合低速查询获取。
(2)FIFO工作模式,适合8路实时采集,支持最高采样率200Ksps。
2、将模拟输入接地时,采样值是0左右。
3、模拟输入端悬空时,采样值在某个范围浮动(这是正常的,这是AD7606内部输入电阻导致的浮动电压)。
4、出厂的AD7606模块缺省是8080 并行接口。如果用SPI接口模式,需要修改 R1 R2电阻配置。
5、配置CVA CVB 引脚为PWM输出模式,周期设置为需要的采样频率,之后MCU将产生周期非常稳定的AD转换信号。
实验操作:
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
J-Scope波形效果:
模块插入位置:
程序设计:
系统栈大小分配:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* STM32F429 HAL 库初始化,此时系统用的还是F429自带的16MHz,HSI时钟: - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。 - 设置NVIV优先级分组为4。 */ HAL_Init(); /* 配置系统时钟到168MHz - 切换使用HSE。 - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。 - 默认不开启,如果要使能此选项,务必看V5开发板用户手册第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder并开启 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */ bsp_InitTimer(); /* 初始化滴答定时器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化扩展IO */ bsp_InitLed(); /* 初始化LED */ BEEP_InitHard(); /* 初始化蜂鸣器 */ /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */ bsp_InitAD7606(); /* 配置AD7606所用的GPIO */ }
主功能:
主程序实现如下操作:
/* ********************************************************************************************************* * 函 数 名: 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; } } } }
本章节涉及到的知识点非常多,实战性较强,需要大家稍花点精力去研究。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。