查看: 3325|回复: 1
打印 上一主题 下一主题

使用 STM32F303VCT6 的 ADC

[复制链接]
跳转到指定楼层
沙发
发表于 2016-7-22 21:03:27 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
这篇帖子将介绍如何使用 STM32F303VCT6 的 ADC 功能,属于[记录我的 STM32 示波器的研发经历]的副产品。STM32F3 系列的模拟电路部分功能比较强(相对于ST其它的MCU),其中STM32F303VCT6 最高主频 72MHz,256K Flash,48K RAM;最牛的,是它的72MHz的ADC时钟,以及6bit分辨率下8个时钟周期的采样+转换时间,这相当于9M/s的采样率了 。

而 STM32F3 Discovery 开发板的价格也非常便宜,作为学习的起点是个不错的选择。


系统时钟的设置
上面刚刚说到Discovery开发板便宜,额,其实是有代价的:没有外部晶振,开发板上STM32F303VCT6的时钟是那个做ST-Link2的STM32F103C8T6的MCO给的8MHz。。。
所以,你最好是把官网提供的固件程序里面的system_stm32f30x.c文件拷贝到自己项目里面用,而不要用外设示例程序里面的,否则,你可能就会和我刚开始一样:在下载程序到MCU时提示你丢失目标MCU。如果你不幸干了这事儿,请去ST官网下载 STM32 ST-LINK Utility 来恢复。
Discovery 固件程序里面的system_stm32f30x.c也没有什么秘密,主要是里面定义了这个宏:
  • #define PLL_SOURCE_HSE_BYPASS

[color=rgb(51, 102, 153) !important]复制代码


然后,剩下的代码自己就知道如何正确的配置时钟了。

ADC的设置
GPIO的设置
需要配置采样通道对应的GPIO为模拟端口。顺便说一句:为了省电,应该将所有不用的IO都设置成模拟端口。
因为要双ADC交替采样,所以下面这2个IO口需要连接在一起。
  •         GPIO_InitTypeDef         gs;
  •         RCC_AHB1PeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
  •         gs.GPIO_Mode = GPIO_Mode_AN;
  •         gs.GPIO_OType = GPIO_OType_PP;
  •         gs.GPIO_PuPd = GPIO_PuPd_NOPULL;
  •         gs.GPIO_Speed = GPIO_Speed_50MHz;
  •         gs.GPIO_Pin = GPIO_Pin_3;
  •         GPIO_Init(GPIOA, &gs);
  •         gs.GPIO_Pin = GPIO_Pin_7;
  •         GPIO_Init(GPIOA, &gs);

[color=rgb(51, 102, 153) !important]复制代码


ADC 时钟
STM32F3 的ADC时钟比其它系列的要复杂(越复杂越强嘛),它可以有2个时钟源:


  • PLL输出。这个设置的目的是可以实现不受HCLK影响的自由时钟频率配置,分频系数可以是:1, 2, 4, 6, 8, 12, 16, 32, 64, 128, 256 等等。缺点是,不保证和内部TIM的同步(也不是什么大问题,可以配置ADC的触发方式和TIM同步就可以了,当然,外部触发会慢一个时钟。
  • HCLK。这个设置可以保证ADC时钟和TIM时钟肯定是同步的。缺点是分频系数选择少:1,2,4。没了。

从制作示波器的角度看,PLL其实更加合适,因为你用片上RAM做存储,数据深度不大的情况下,要看低频波形,还是调低ADC频率比较方便。
摘录对应的代码(完整的设置代码在最后):
  • ADC_CommonInitTypeDef cs;
  • RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div1);
  • RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);
  • cs.ADC_Clock = ADC_Clock_AsynClkMode;

[color=rgb(51, 102, 153) !important]复制代码



规则通道的采样模式
STM32的ADC通道可以分为规则通道(Regualar Channel)和注入通道(Injected Channel)两种。从灵活性上面来说,注入通道更灵活,可以随意插入正在进行采样的规则通道序列中,也可以以交替模式安排4个ADC里面的每个采样通道按照触发顺序依次采样。更重要的,注入模式中每个采样通道都有独立的数据寄存器(STM32F3有4个ADC,每个ADC的注入模式最多有4个通道,所以一共对应有4X4=16个数据寄存器),从而不会发生Overrun的情况。
然而就示波器的应用来说,规则通道更合适,因为它可以达到最大的采样率,且配置简单。最重要的:它可以用DMA!注入通道的交替触发采样是无法用DMA的(坑爹啊)!


双ADC的交替采样模式下,有几个参数是需要精心设计的:
  • 采样周期。共分8挡,最短1.5个ADC时钟周期,最长601个时钟周期。采样周期加上前面讲的PLL时钟分频系数,可以搭配出多种不同的采样率,在捕获低频信号时会非常有用。
  • 转换周期。这个参数和分辨率有关。采样周期+转换周期=总周期,采样频率/总周期=采样率。就这么简单。
  • 通道间采样间隔。别忘了我们可是双ADC采样,ADC1和ADC2在对同一个通道进行交替采样,在ADC1完成采样(注意,是采样,不是转换)后,要等几个(最少1个)ADC时钟周期才让开始ADC2的采样。

仍然以6bit分辨率为例:采样周期=1.5,转换周期=6.5,总周期=1.5+6.5=8,通道间采样间隔=3 时,ADC1在 t0 时刻采样,然后ADC2就会在 t0+1.5+3=t0+4.5 的时刻采样,然后如此继续下去。
这样,相当于8个ADC时钟周期里面,采样了2次(虽然2次并不是严格对齐的),72MHz/(8/2)=72MHz/4 = 18M/s采样率。

我为什么老抓住6bit分辨率不放?因为我的显示屏只有320X240啊,垂直不能同时显示2个8bit分辨率的波形啊!!!而且我SPI的显示屏啊,刷新率是硬伤啊,6bit分辨率意味着我能少刷几个像素啊!!!哎。。。(我后来都想整单色LCD了,那刷新率杠杠的,不过没关系,我还有STM32F429i Discovery,哈哈哈!)
Common 设置:
  • cs.ADC_Mode  = ADC_Mode_Interleave;
  • cs.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  • cs.ADC_DMAMode = ADC_DMAMode_OneShot;
  • cs.ADC_TwoSamplingDelay = 2;  //2就是3个周期的通道间间隔
  • ADC_CommonInit(ADC1, &cs);   //双通道模式下,只需配置Master ADC

[color=rgb(51, 102, 153) !important]复制代码


每个 ADC 的设置:
  •         ADC_StructInit(&adcs);
  •         ADC_VoltageRegulatorCmd(ADC1, ENABLE);
  •         ADC_VoltageRegulatorCmd(ADC2, ENABLE);
  •         osDelay(1); //延迟 1ms,需要用你自己的延时函数替换。其实2us就足够了,CMSIS_RTOS 没有那么精细,1ms算了。
  •         ADC_SelectCalibrationMode(ADC1, ADC_CalibrationMode_Single); ADC_StartCalibration(ADC1);
  •         ADC_SelectCalibrationMode(ADC2, ADC_CalibrationMode_Single); ADC_StartCalibration(ADC2);
  •         while(ADC_GetCalibrationStatus(ADC1) != RESET );
  •         while(ADC_GetCalibrationStatus(ADC2) != RESET );
  •         adcs.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Enable;
  •         adcs.ADC_Resolution = ADC_Resolution_6b;
  •         adcs.ADC_ExternalTrigConvEvent = ADC_ExternalTrigConvEvent_7;
  •         adcs.ADC_ExternalTrigEventEdge = ADC_ExternalTrigEventEdge_None;
  •         adcs.ADC_DataAlign = ADC_DataAlign_Right;
  •         adcs.ADC_OverrunMode = ADC_OverrunMode_Enable;
  •         adcs.ADC_AutoInjMode = ADC_AutoInjec_Disable;
  •         adcs.ADC_NbrOfRegChannel = 1;
  •         ADC_Init(ADC1, &adcs);
  •         ADC_Init(ADC2, &adcs);
  •         ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_1Cycles5);
  •         ADC_RegularChannelConfig(ADC2, ADC_Channel_4, 1, ADC_SampleTime_1Cycles5);
  •         ADC_Cmd(ADC1, ENABLE);
  •         ADC_Cmd(ADC2, ENABLE);

[color=rgb(51, 102, 153) !important]复制代码



DMA的设置
ADC转换那么快,用CPU等中断再往数组里面存那是不现实的哇!(我用注入交替触发模式试过,用中断存数组大约只有200K/s的速率)
ADC1、2做双交替采样的DMA设置,有2个选择:
  • 只用1个DMA通道,一次在ADC2的EOC事件后一次读取2个采样结果到一个数组中
  • 用2个DMA通道,分别读取2个ADC的采样结果到2个数组中

我选了第二个,原因么:
  • 我不缺DMA
  • 以后从双采样切换到独立采样(也就是双通道示波器变身四通道示波器)时,比较自然
DMA里面需要确定你的采样存储深度,让DMA在读取完这么多组数据后自动停止,发送给LCD去显示。当然,你也可以让DMA继续采样来提高捕获率或者实现余辉效果,不过那需要内存支持,算法就复杂了,我还没有搞到那么深。等以后用到那个地步了,可能我也不会选择 soc 的 ADC 了。
  •         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1|RCC_AHBPeriph_DMA2, ENABLE);
  •         ds.DMA_PeripheralBaseAddr         = (uint32_t)&ADC1->DR;
  •         ds.DMA_MemoryBaseAddr                 = (uint32_t)&appADCValue1[0];
  •         ds.DMA_BufferSize                         = ADCDeepth;
  •         ds.DMA_DIR                                         = DMA_DIR_PeripheralSRC;
  •         ds.DMA_PeripheralDataSize         = DMA_PeripheralDataSize_Byte;
  •         ds.DMA_MemoryDataSize                 = DMA_MemoryDataSize_Byte;
  •         ds.DMA_PeripheralInc                 = DMA_PeripheralInc_Disable;
  •         ds.DMA_MemoryInc                         = DMA_MemoryInc_Enable;
  •         ds.DMA_Mode                                 = DMA_Mode_Circular;
  •         ds.DMA_Priority                         = DMA_Priority_VeryHigh;
  •         ds.DMA_M2M                                         = DMA_M2M_Disable;
  •         DMA_Init(DMA1_Channel1, &ds);
  •         ds.DMA_PeripheralBaseAddr         = (uint32_t)&ADC2->DR;
  •         ds.DMA_MemoryBaseAddr                 = (uint32_t)&appADCValue2[0];
  •         DMA_Init(DMA2_Channel1, &ds);
  •         DMA_Cmd(DMA1_Channel1, ENABLE);
  •         DMA_Cmd(DMA2_Channel1, ENABLE);
  •         DMA_ITConfig(DMA2_Channel1, DMA_IT_TC, ENABLE);
  •         ns.NVIC_IRQChannel = DMA2_Channel1_IRQn;
  •         ns.NVIC_IRQChannelPreemptionPriority = 4;
  •         ns.NVIC_IRQChannelSubPriority = 8;
  •         ns.NVIC_IRQChannelCmd = ENABLE;
  •         NVIC_Init(&ns);

[color=rgb(51, 102, 153) !important]复制代码



好了,最后启动 Master ADC的转换就开始了 ADC_StartConversion(ADC1);

最后,把上面的代码整合一下,看个全貌(不是全部工程,但应该更容易应用到你自己到项目中去,因为少了很多乱七八糟的和我个人环境设置有关的东西):
  • #define ADCDeepth 300
  • uint8_t appADCValue1[ADCDeepth];
  • uint8_t appADCValue2[ADCDeepth];
  • void ADCInit(void){
  •         GPIO_InitTypeDef         gs;
  •         ADC_CommonInitTypeDef cs;
  •         ADC_InitTypeDef         adcs;
  •         DMA_InitTypeDef         ds;
  •         NVIC_InitTypeDef         ns;
  •         RCC_AHB1PeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
  •         gs.GPIO_Mode = GPIO_Mode_AN;
  •         gs.GPIO_OType = GPIO_OType_PP;
  •         gs.GPIO_PuPd = GPIO_PuPd_NOPULL;
  •         gs.GPIO_Speed = GPIO_Speed_50MHz;
  •         gs.GPIO_Pin = GPIO_Pin_3;
  •         GPIO_Init(GPIOA, &gs);
  •         gs.GPIO_Pin = GPIO_Pin_7;
  •         GPIO_Init(GPIOA, &gs);
  •         RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div1);
  •         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);
  •         cs.ADC_Clock = ADC_Clock_AsynClkMode;
  •         cs.ADC_Mode  = ADC_Mode_Interleave;
  •         cs.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  •         cs.ADC_DMAMode = ADC_DMAMode_OneShot;
  •         cs.ADC_TwoSamplingDelay = 2;  //2就是3个周期的通道间间隔
  •         ADC_CommonInit(ADC1, &cs);    //双通道模式下,只需配置Master ADC
  •         ADC_StructInit(&adcs);
  •         ADC_VoltageRegulatorCmd(ADC1, ENABLE);
  •         ADC_VoltageRegulatorCmd(ADC2, ENABLE);
  •         osDelay(1); //延迟 1ms,需要用你自己的延时函数替换。其实2us就足够了,CMSIS_RTOS 没有那么精细,1ms算了。
  •         ADC_SelectCalibrationMode(ADC1, ADC_CalibrationMode_Single); ADC_StartCalibration(ADC1);
  •         ADC_SelectCalibrationMode(ADC2, ADC_CalibrationMode_Single); ADC_StartCalibration(ADC2);
  •         while(ADC_GetCalibrationStatus(ADC1) != RESET );
  •         while(ADC_GetCalibrationStatus(ADC2) != RESET );
  •         adcs.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Enable;
  •         adcs.ADC_Resolution = ADC_Resolution_6b;
  •         adcs.ADC_ExternalTrigConvEvent = ADC_ExternalTrigConvEvent_7;
  •         adcs.ADC_ExternalTrigEventEdge = ADC_ExternalTrigEventEdge_None;
  •         adcs.ADC_DataAlign = ADC_DataAlign_Right;
  •         adcs.ADC_OverrunMode = ADC_OverrunMode_Enable;
  •         adcs.ADC_AutoInjMode = ADC_AutoInjec_Disable;
  •         adcs.ADC_NbrOfRegChannel = 1;
  •         ADC_Init(ADC1, &adcs);
  •         ADC_Init(ADC2, &adcs);
  •         ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_1Cycles5);
  •         ADC_RegularChannelConfig(ADC2, ADC_Channel_4, 1, ADC_SampleTime_1Cycles5);
  •         ADC_Cmd(ADC1, ENABLE);
  •         ADC_Cmd(ADC2, ENABLE);
  •         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1|RCC_AHBPeriph_DMA2, ENABLE);
  •         ds.DMA_PeripheralBaseAddr         = (uint32_t)&ADC1->DR;
  •         ds.DMA_MemoryBaseAddr                 = (uint32_t)&appADCValue1[0];
  •         ds.DMA_BufferSize                         = ADCDeepth;
  •         ds.DMA_DIR                                         = DMA_DIR_PeripheralSRC;
  •         ds.DMA_PeripheralDataSize         = DMA_PeripheralDataSize_Byte;
  •         ds.DMA_MemoryDataSize                 = DMA_MemoryDataSize_Byte;
  •         ds.DMA_PeripheralInc                 = DMA_PeripheralInc_Disable;
  •         ds.DMA_MemoryInc                         = DMA_MemoryInc_Enable;
  •         ds.DMA_Mode                                 = DMA_Mode_Circular;
  •         ds.DMA_Priority                         = DMA_Priority_VeryHigh;
  •         ds.DMA_M2M                                         = DMA_M2M_Disable;
  •         DMA_Init(DMA1_Channel1, &ds);
  •         ds.DMA_PeripheralBaseAddr         = (uint32_t)&ADC2->DR;
  •         ds.DMA_MemoryBaseAddr                 = (uint32_t)&appADCValue2[0];
  •         DMA_Init(DMA2_Channel1, &ds);
  •         DMA_Cmd(DMA1_Channel1, ENABLE);
  •         DMA_Cmd(DMA2_Channel1, ENABLE);
  •         DMA_ITConfig(DMA2_Channel1, DMA_IT_TC, ENABLE);
  •         ns.NVIC_IRQChannel = DMA2_Channel1_IRQn;
  •         ns.NVIC_IRQChannelPreemptionPriority = 4;
  •         ns.NVIC_IRQChannelSubPriority = 8;
  •         ns.NVIC_IRQChannelCmd = ENABLE;
  •         NVIC_Init(&ns);
  • }
  • void DMA2_Channel1_IRQHandler(void){
  •         if(DMA_GetITStatus(DMA2_IT_TC1)!=RESET){
  •                 DMA_ClearITPendingBit(DMA2_IT_TC1);
  •                 //数据拿到了,接下来看你的了!
  •         }
  • }




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入因仑

本版积分规则

快速回复 返回顶部 返回列表