完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
大数据本质上是模拟大数据,许多情况下模拟量数据对于数据分析更有价值。在这篇博文中,我们重点来谈谈Mbed OS 操作系统下的ADC高速数据采样。
Mbed OS 下的模拟量IO Mbed OS 的 API 中有模拟量IO: AnalogIn AnalogOut 它们是针对MCU 内部ADC 输入和DAC 输出。如果使用过它们的化,就知道它们很慢。根本没有办法适应高速数据采集。如果要实现高速ADC 输入,就需要使用STM32 的HAL 库自己来设计。如果外扩SPI 的ADC 芯片也是如此,如过低速的话可以使用DigitalOut 和SPI 类来实现。但是要实现高速,就需要HAL 来配合了。 内置ADC 采样 ADC 数据采集的方式有两种,一种是使用内部的ADC,其优点是和CPU集成在一起,MCU 厂商对内置ADC 的支持非常强大。在STM32 系列中,支持下面几种方式 查询方式 中断方式 DMA方式 显然,Mbed OS 支持的是查询方式。所以很慢。如果使用中断方式,那么每次采样一个模拟量需要产生一次中断,执行一大堆中断处理程序。反而比查询方式还要慢。要实现高速ADC 输入的化,唯有采用DMA 方式最合适。 内置ADC 的缺点是它们的精度只有12位。 使用DMA 方式实现ADC 输入,看上去并不难,其要点是: 1 使用一个定时器定时产生触发信号,触发ADC 采集数据 2 当ADC 采样完成时,触发DMA 将ADC 传输到内存 3 可以设置DMA 位循环方式,启动DMA 是指定一个缓冲区。这样DMA 可以连续采集ADC 数据到内存,期间不需要任何程序的干预。(别忘了,DMA 就是指 外设直接内存访问) 4 DMA 能够产生一个半完成中断(HAL_ADC_ConvHalfCpltCallback),和整个完成中断(HAL_ADC_ConvCpltCallback)。分别是当数据达到缓冲区长度的一半时产生中断和数据达到缓冲区最高位时产生中断。 网络上有许多人写了关于STM32 TIM ADC DMA 数据转换的方式。但是没有一个是完整的。而且存在各种坑,这也不能怪他们,各自的情况不同,而且作者编写和转发的时间也不同。STM32F 包袱也够多的,早期使用标准库,现在又使用HAL 库,又有各种版本cubeMX 工具,所以简单地拷贝/黏贴很难解决问题。 在Mbed OS 下,实现底层IO 程序设计是可行的,采用的是HAL 库。我采用方式是用STM32CubeMX 工具配置好参数之后,然后Copy 到Mbed 中来。 我写了一个mbed OS 例子,它采集两路内置ADC 的数据,并通过UDP 上传到PC 机上供python 做FFT 个显示,速度做到十几M没有问题。希望对大家有所帮助。程序是调通的,请放心参考。 #include "mbed.h" #include "stm32f4xx_hal.h" #include "EthernetInterface.h" static const char* mbedIp = "192.168.31.110"; //IP static const char* mbedMask = "255.255.255.0"; // Mask static const char* mbedGateway = "192.168.31.1"; //Gateway #define SERVER_PORT 2019 #define SERVER_ADDR "192.168.31.99" #define UDP_PORT 2018 EthernetInterface eth; UDPSocket udpsocket; DigitalOut led(PC_6); //DigitalOut led1(PC_7); AnalogOut aout(PA_5); Thread thread; uint16_t ADC_DMA_ConvertedValue[512]; bool bufFlg; ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; TIM_HandleTypeDef htim3; #define DMA_FLAG (1UL << 0) EventFlags dma_flags; void Error_Handler(void) { printf("HAL errorn"); } extern "C" void TIM3_IRQHandler(void) { HAL_TIM_IRQHandler(&htim3); } extern "C" void DMA2_Stream0_IRQHandler(void) { // led=!led; HAL_DMA_IRQHandler(&hdma_adc1); // dma_flags.set(DMA_FLAG); } void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc){ led=1; bufFlg=false; dma_flags.set(DMA_FLAG); } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){ led=0; bufFlg=true; dma_flags.set(DMA_FLAG); } /** * Enable DMA controller clock */ void DMA_Init(void){ __HAL_RCC_DMA2_CLK_ENABLE(); hdma_adc1.Instance = DMA2_Stream0; hdma_adc1.Init.Channel = DMA_CHANNEL_0; hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc1.Init.Mode = DMA_CIRCULAR; hdma_adc1.Init.Priority = DMA_PRIORITY_MEDIUM; hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE; HAL_DMA_Init(&hdma_adc1); __HAL_LINKDMA(&hadc1,DMA_Handle,hdma_adc1); HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); } void TIM_Init(void) { // TIM_SlaveConfigTypeDef sSlaveConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; __HAL_RCC_TIM3_CLK_ENABLE(); htim3.Instance = TIM3; htim3.Init.Prescaler = 72-1; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 100-1; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig); HAL_TIM_Base_Init(&htim3); //NVIC_EnableIRQ(TIM3_IRQn); } void ADC_Init(void){ GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_ADC1_CLK_ENABLE(); /**ADC1 GPIO Configuration PA3 ------> ADC1_IN3 PA4 ------> ADC1_IN4 PA5 ------> ADC1_IN5 PA6 ------> ADC1_IN6 */ GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); hadc1.Instance=ADC1; hadc1.Init.DataAlign=ADC_DATAALIGN_RIGHT; //右对齐 hadc1.Init.ScanConvMode=ENABLE; //不扫描模式 hadc1.Init.ContinuousConvMode=DISABLE; //不连续转换 hadc1.Init.NbrOfConversion=2; //一个规则通道转换 hadc1.Init.DiscontinuousConvMode=DISABLE; //禁止不连续采样模式 hadc1.Init.NbrOfDiscConversion=0; //不连续采样通道数为0 hadc1.Init.DMAContinuousRequests = ENABLE; hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO; HAL_ADC_Init(&hadc1); ADC_ChannelConfTypeDef ADC1_ChanConf; ADC1_ChanConf.Channel=3; //通道3 ADC1_ChanConf.Rank=1; ADC1_ChanConf.SamplingTime = ADC_SAMPLETIME_3CYCLES; HAL_ADC_ConfigChannel(&hadc1,&ADC1_ChanConf); ADC1_ChanConf.Channel=4; //通道4 ADC1_ChanConf.Rank=2; ADC1_ChanConf.SamplingTime = ADC_SAMPLETIME_3CYCLES; HAL_ADC_ConfigChannel(&hadc1,&ADC1_ChanConf); } void wave(void){ uint16_t sample = 0; while(1) { for (int i = 0; i < 8; i++) { sample=sample+512; aout.write_u16(sample); wait_us(2); } } } int main() { int i; printf("ADC DMA Testn"); eth.set_network(mbedIp,mbedMask,mbedGateway); eth.connect(); printf("nConnected IP Address : %sn", eth.get_ip_address()); udpsocket.open(ð); udpsocket.bind(eth.get_ip_address(),UDP_PORT); TIM_Init(); DMA_Init(); ADC_Init(); bufFlg=false; HAL_ADC_Start_DMA(&hadc1,(uint32_t *)ADC_DMA_ConvertedValue,512); HAL_TIM_Base_Start_IT(&htim3); thread.start(wave); while(1) { dma_flags.wait_any(DMA_FLAG); // for (i=0;i<8;i++) // printf("%d ",ADC_DMA_ConvertedValue); // printf("n"); if (bufFlg) udpsocket.sendto(SERVER_ADDR,SERVER_PORT, &ADC_DMA_ConvertedValue[256], 512); else udpsocket.sendto(SERVER_ADDR,SERVER_PORT, &ADC_DMA_ConvertedValue[0], 512); dma_flags.clear(DMA_FLAG); // wait(1); } } 外置ADC 方式 如果嫌弃内置ADC 的精度不够,那么可以考虑使用外部ADC 芯片方式。ADI ,TI 这些大公司提供了各自ADC 芯片。本人就使用过AD7689,ads1256,ads127L和ads1274 等芯片和STM32 连接。 这些芯片大多数是以SPI 接口与STM32 相连接。Mbed OS 本身支持SPI 接口,如果使用探询方式读取ADC 芯片的话,当然Mbed OS 的SPI 完全可以胜任。如果需要高速ADC 转换的话,就遇到和内置ADC 同样的问题了。需要使用DMA 方式。 深入地研究之后发现,SPI 接口ADC 芯片的DMA 方式也不是省油的灯。而且网络上的成功例子更加是少的可怜。 只有靠自己了,我采取的方案如下 ADC 芯片的主时钟 ADC 芯片需要一个主时钟,可以外接一个晶振。但是ADC 芯片内部通常有一个分频,不过也是固定的几种,也可以通过MCU 可编程输入。我倾向采用MCU 产生,多少也省了个晶振电路。 由 TIM4 的通道1 产生一个PWM 的脉冲信号作为 ADC 的工作时钟。由TIM2 CH1 (OC_1)输出。我选取 2MHz。 ADC芯片 的DRDY 信号的俘获 当ADC 完成一次采样后,DRDY 会产生一个低电平脉冲。MCU 需要尽快地将数据通过SPI 读取。在查询方式下,通常是采取while 语句实现。 while(DRDY){}; Data=ADS127L01_ReadData(); 不过 在SPI DMA 方式下,如何启动SPI 的DMA 呢?要知道,STM32 的DMA 是不可以通过GPIO 来触发启动的。 我们采取的方法是使用TIM3 ,将它设置成为 ETR 计数方式(1 个脉冲数),将DRDY 接入 TIM3_ETR 输入脚。一旦DRDY 下降沿到来,TIM3计一个脉冲,产生内部的触发信号TRGO。并且由该信号启动一个DMA,用它来触发SPI 发送的DMA传输。 SPI 的DMA SPI 是一种主从式同步串行通信,MCU 设置为主模式,ADC 芯片为从模式。当MCU 发送数据时会发送SCLK 时钟,在发送的同时,也接受了数据。说白了,就是通过发送来接受数据。 为了实现SPI 的DMA 传输,需要两个DMA 来实现,一个DMA 由TIM3 启动,用于发送,另一个DMA 用于SPI 接受。 所以说,实现ADC SPI 芯片 的DMA 传输,需要两个TIM ,两个DMA和一个SPI 硬件接口 TI ads127L01/ads1274 ADC 芯片和STM32F429ZI 接线方式。 ADC_CLK <-TIM4 CH1 DRDY -> TIM3_ETR DOUT -> SPI1 MISO DIN <- SPI MOSI SCLK <- SPI CLK 我编写了TI ads127L01 和ads1274两种芯片的驱动,源代码调试通过后在放出来吧! |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1771 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1619 浏览 1 评论
1070 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
724 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1673 浏览 2 评论
1935浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
728浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
568浏览 3评论
593浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
551浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-22 16:13 , Processed in 0.932401 second(s), Total 76, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号