发新帖本帖赏金 50.00元(功能说明)我要提问
返回列表
[AT32F403/403A]

AT32学习记录——模拟数字转换(一)

[复制链接]
2408|3
手机看帖
扫描二维码
随时随地手机跟帖
zexin|  楼主 | 2024-10-14 15:04 | 显示全部楼层 |阅读模式
本帖最后由 zexin 于 2024-10-14 15:05 编辑

#申请原创#
AT32F403A模拟数字转换(ADC)01
一、简介
ADC是一个将模拟输入信号转换为数字信号的外设,每组ADC具有16个外部通道(ADCx_IN0~15)和2个内部通道(内部温度传感器内部参考电压),最高采样率可达2MSPS(Sample Per Second)。
09937c2f243bde7cff1f5e2c5c9bf83a
首先,模拟信号由GPIO端口进入,等检测到触发信号的上升沿后,ADC将模拟信号转换为数字信号,并存储在数据寄存器中,转换结束后可产生相应的中断和DMA请求。
二、功能
1.通道管理
(1)通道转换
转换分为普通通道转换和抢占通道转换,抢占通道的优先级高于普通通道;
在普通通道转换途中,若触发抢占通道转换,优先进行抢占通道的转换,等抢占通道转换完成之后再继续普通通道的转换;
在抢占通道转换途中,若触发普通通道转换,仍然执行抢占通道的转换,等抢占通道转换完成之后再开始普通通道的转换;
(2)内部温度传感器
b16ad1f74ccc2fdb9f7975e4c16d0deb
温度传感器连接到ADC1_IN16,可由以上公式推算温度。
注:内部温度传感器更适合检测温度的变化,而不是测量绝对温度。
(3)内部参考电压
内部参考电压连接到ADC1_IN17,典型值为1.2V,可用于推算外部参考电压(VREF)。
2.ADC流程
cf420796ecad33ac8b769f546386e023
ADC流程如图所示,第一次上电后先进行校准,之后等待触发引起ADC采样转换,最后读取转换数据
(1)上电与校准
fa9ceb29f0df0a4ac55109b54d49d115
先使能外设时钟,即ADC的时钟;
②配置预分频器,调整所需的ADCCLK(
ADCCLK不可大于28MHz)
③使能ADCEN,等待tSATB后进行ADCAL(校准);
校准完后硬件清除ADCAL,等待触发
(2)触发
①ADC触发分为普通通道触发和抢占通道触发;
使能OCTEN(普通通道触发使能)或PCTEN(抢占通道触发使能);
③ADC检测到触发信号的上升沿
④ADC响应并进行转换。
(3)采样转换
①ADC采样时间采样率有关;
②ADC转换时间12.5个ADCCLK周期
因此:(采样+转换)时间 = (采样时间 + 12.5)个ADCCLK周期
例如:
CSPTx(采样率)选择1.5个周期,则采样转换时间 = 1.5+12.5 = 14个ADCCLK周期;
CSPTx(采样率)选择7.5个周期,则采样转换时间 = 7.5+12.5 = 20个ADCCLK周期;
3.转换顺序
单通道:默认每次触发只转换单个通道;
多通道:序列、抢占自动转换、反复、分割模式;
(1)序列模式
一次触发将序列中的所有通道都依次转换一次。
5ce4eda20f115854110493081740624a
普通通道OSN1开始转换。
8bc65864a3892bded89ae0da03b4ab5e
抢占通道PSNx(x = 4 - PCLEN)开始。
注:
PCLEN = 0代表抢占通道长度为1;
PCLEN = 1代表抢占通道长度为2;
……
PCLEN = 3
代表抢占通道长度为4;
(2)抢占自动转换模式
b1b509d0f2be0df336edd86ca453168c
普通通道触发后,待普通通道转换结束后抢占通道自动发生转换
(3)反复模式
43a3b5bda6cd186690290b62e70c1400
普通通道触发后不断进行ADC转换 ,可与抢占自动转换模式共用。
(4)分割模式
524063b691aa898adff71f47db5fa197
普通通道:将序列长度分为较小的子组别,一次触发将转换子组别的通道;
52df1b824cd94209655a041fcb3fc125
抢占通道:将序列长度分为只有一个通道的子组别,一次触发将转换子组别的通道;
注:分割模式与反复模式不可共用。
4.数据管理
普通通道的转换数据可以由CPU或DMA读取ADC_ODT(普通数据寄存器)获得;
抢占通道的转换数据只能由CPU读取ADC_PDT(抢占数据寄存器)获得;
三、案例
1.外部电压采样
(1)功能
利用ADC内部参考电压(标准的1.2V)反推出ADC外部参考电压,再利用外部参考电压和外部电压的adc值计算出外部信号的电压。
(2)配置
介绍
ADC1_INT17---adc_value[0]
ADC1_INT4 ---adc_value[1]
步骤
启用ADC1,勾选通道4和内部参考电压通道
9be8ba44cdb2dc44ec1ca0d7e22f81f0
设置通道数目为2,开启序列模式,序列1为内部参考电压,序列2为通道4;
d00bb4690d9f06056b4cd3f6079b7792
添加ADC1至DMA1请求
e5dab3294b6390bc3018dfade4bb7c3b
生成代码。
(3)代码
main.c
#include "at32f403a_407_wk_config.h"
#include "wk_system.h"

uint16_t adc_value[2] = {0};//定义存放adc数值的数组
float vref, voltage;//定义外部参考电压、外部信号电压等变量

int main(void)
{
  wk_system_clock_config();
  wk_periph_clock_config();
  wk_nvic_config();
  wk_timebase_init();
  wk_dma1_channel1_init();
  wk_dma_channel_config(DMA1_CHANNEL1,
                        (uint32_t)&ADC1->odt,
                        DMA1_CHANNEL1_MEMORY_BASE_ADDR,
                        DMA1_CHANNEL1_BUFFER_SIZE);
  dma_channel_enable(DMA1_CHANNEL1, TRUE);

  wk_adc1_init();

  adc_ordinary_software_trigger_enable(ADC1, TRUE);//软件触发ADC

  while(1)
  {
        while(dma_flag_get(DMA1_FDT1_FLAG) == RESET);//等待dma传输完成
        dma_flag_clear(DMA1_FDT1_FLAG);
        vref = ((double)1.2 * 4095) / adc_value[0];//反推出ADC外部参考电压
        voltage = (double)adc_value[1] / 4095 * vref;//计算外部信号的电压
  }
}
at32f403a_407_wk_config.c
#include "at32f403a_407_wk_config.h"

void wk_system_clock_config(void)
{
  crm_reset();
  crm_clock_source_enable(CRM_CLOCK_SOURCE_LICK, TRUE);
  while(crm_flag_get(CRM_LICK_STABLE_FLAG) != SET)
  {
  }
  crm_clock_source_enable(CRM_CLOCK_SOURCE_HICK, TRUE);
  while(crm_flag_get(CRM_HICK_STABLE_FLAG) != SET)
  {
  }
  crm_pll_config(CRM_PLL_SOURCE_HICK, CRM_PLL_MULT_60, CRM_PLL_OUTPUT_RANGE_GT72MHZ);
  crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, TRUE);
  while(crm_flag_get(CRM_PLL_STABLE_FLAG) != SET)
  {
  }
  crm_ahb_div_set(CRM_AHB_DIV_1);
  crm_apb2_div_set(CRM_APB2_DIV_2);
  crm_apb1_div_set(CRM_APB1_DIV_2);
  crm_auto_step_mode_enable(TRUE);
  crm_sysclk_switch(CRM_SCLK_PLL);

  while(crm_sysclk_switch_status_get() != CRM_SCLK_PLL)
  {
  }
  crm_auto_step_mode_enable(FALSE);
  system_core_clock_update();
}

void wk_periph_clock_config(void)
{
  crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);
  crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE);
  crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
  crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);
  crm_periph_clock_enable(CRM_ADC1_PERIPH_CLOCK, TRUE);
}

void wk_nvic_config(void)
{
  nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
  NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 15, 0));
}

void wk_adc1_init(void)
{
  gpio_init_type gpio_init_struct;
  adc_base_config_type adc_base_struct;

  gpio_default_para_init(&gpio_init_struct);

  gpio_init_struct.gpio_mode = GPIO_MODE_ANALOG;
  gpio_init_struct.gpio_pins = GPIO_PINS_4;
  gpio_init(GPIOA, &gpio_init_struct);

  crm_adc_clock_div_set(CRM_ADC_DIV_6);

  adc_tempersensor_vintrv_enable(TRUE);

  adc_combine_mode_select(ADC_INDEPENDENT_MODE);

  adc_base_default_para_init(&adc_base_struct);
  adc_base_struct.sequence_mode = TRUE;
  adc_base_struct.repeat_mode = FALSE;
  adc_base_struct.data_align = ADC_RIGHT_ALIGNMENT;
  adc_base_struct.ordinary_channel_length = 2;
  adc_base_config(ADC1, &adc_base_struct);

  adc_ordinary_channel_set(ADC1, ADC_CHANNEL_17, 1, ADC_SAMPLETIME_239_5);
  adc_ordinary_channel_set(ADC1, ADC_CHANNEL_4, 2, ADC_SAMPLETIME_239_5);

  adc_ordinary_conversion_trigger_set(ADC1, ADC12_ORDINARY_TRIG_SOFTWARE, TRUE);

  adc_ordinary_part_mode_enable(ADC1, FALSE);

  adc_dma_mode_enable(ADC1, TRUE);
  adc_enable(ADC1, TRUE);
  
  adc_calibration_init(ADC1);
  while(adc_calibration_init_status_get(ADC1));
  adc_calibration_start(ADC1);
  while(adc_calibration_status_get(ADC1));
}

void wk_dma1_channel1_init(void)
{
  dma_init_type dma_init_struct;

  dma_reset(DMA1_CHANNEL1);
  dma_default_para_init(&dma_init_struct);
  dma_init_struct.direction = DMA_DIR_PERIPHERAL_TO_MEMORY;
  dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_HALFWORD;
  dma_init_struct.memory_inc_enable = TRUE;
  dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_HALFWORD;
  dma_init_struct.peripheral_inc_enable = FALSE;
  dma_init_struct.priority = DMA_PRIORITY_LOW;
  dma_init_struct.loop_mode_enable = TRUE;
  dma_init(DMA1_CHANNEL1, &dma_init_struct);

  dma_flexible_config(DMA1, FLEX_CHANNEL1, DMA_FLEXIBLE_ADC1);
}

void wk_dma_channel_config(dma_channel_type* dmax_channely, uint32_t peripheral_base_addr, uint32_t memory_base_addr, uint16_t buffer_size)
{
  dmax_channely->dtcnt = buffer_size;
  dmax_channely->paddr = peripheral_base_addr;
  dmax_channely->maddr = memory_base_addr;
}
at32f403a_407_wk_config.h
#ifndef __AT32F403A_407_WK_CONFIG_H
#define __AT32F403A_407_WK_CONFIG_H

#ifdef __cplusplus
extern "C" {
#endif

#include "at32f403a_407.h"

#define DMA1_CHANNEL1_BUFFER_SIZE   2 //定义DMA传输数据量为2
#define DMA1_CHANNEL1_MEMORY_BASE_ADDR   (uint32_t)adc_value  //定义DMA传输的目标地址

  void wk_system_clock_config(void);
  void wk_periph_clock_config(void);
  void wk_nvic_config(void);
  void wk_adc1_init(void);
  void wk_dma1_channel1_init(void);
  void wk_dma_channel_config(dma_channel_type* dmax_channely, uint32_t peripheral_base_addr, uint32_t memory_base_addr, uint16_t buffer_size);

#ifdef __cplusplus
}
#endif

#endif
(4)现象
使用可调电压源向PA4输入电压模拟外部信号电压,在debug变量窗口中查看采集的电压。
外部信号输入1.5V
19721bcf0fd2838b3881b6b7c25d9b36
外部信号输入2.5V
46a60e767bfb4389ffbfae6feb6ffe97
由于作者水平有限,文中如有错误之处,恳请读者批评指正。
参考资料:
《RM_AT32F403A_407_CH_V2.06》的19模拟数字转换(ADC)
https://www.arterytek.com/file/download/1995

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 50.00 元 2024-10-25
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2024-10-25 17:36 回复TA
可读性较高,排版整洁,段落划分合理,图表和代码块使用得当。文章易于阅读和理解。深入分析技术细节,并非泛泛而谈,展现了作者对该文内容所涉及的领域的专业知识有深刻理解或独到的见解。条理清晰,逻辑连贯。 
zexin|  楼主 | 2024-10-14 15:09 | 显示全部楼层

使用特权

评论回复
问天少年| | 2024-10-16 14:03 | 显示全部楼层
自动校准很方便

使用特权

评论回复
发新帖 本帖赏金 50.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

9

主题

17

帖子

0

粉丝