之前已经介绍了几个ADC的笔记和实验了,链接如下:
关于ADC的笔记1_Mr_rustylake的博客-CSDN博客
STM32-ADC单通道采集实验_Mr_rustylake的博客-CSDN博客
STM32-单通道ADC采集(DMA读取)实验_Mr_rustylake的博客-CSDN博客
接下来介绍这次的实验要求:
通过ADC1通道0/1/2/3/4/5(PA0/1/2/3/4/5)采集测试电压,并现实ADC转换的数字量和换算后的电压值。
首先确定我们的最小刻度,Vref = 3.3V,所以0V <= Vin <= 3.3V,所以最小刻度是3.3V / 4096(2^12)。
接下来确定转换时间。采样时间239.5个ADC时钟周期为例,可以得到转换时间为21us。
时间转换公式参考如下公式:Tcvtmin=(12.5+X)周期=(12.5 + X)/(12MHz)=21us。
下图是对应的通道表:
这里的模式考虑到需要多通道扫描,所以启动扫描模式,并启动连续模式。通道数记得改为6,注意设置通道数目和对应的转换顺序。
接下来编写函数的代码:
先编写函数文件adc.h:
#include "./BSP/ADC/adc.h"
ADC_HandleTypeDef g_adc_nch_handle;
DMA_HandleTypeDef g_dma_nch_handle;
uint8_t g_adc_dma_sta; //标志DMA的传输是否完成
void adc_nch_dam_init(uint32_t mar){
ADC_ChannelConfTypeDef adc_ch_conf;
__HAL_RCC_DMA1_CLK_ENABLE();
g_dma_nch_handle.Instance = DMA1_Channel1;
g_dma_nch_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; //外设到内存
g_dma_nch_handle.Init.PeriphInc = DMA_PINC_DISABLE; //因为选取的是DMA1的数据寄存器,选择不增量
g_dma_nch_handle.Init.MemInc = DMA_MINC_ENABLE; //对于存储器需要存储多个数据,所以选择增量模式
g_dma_nch_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //外设数据位宽,我们选择16位半字(全字可以理解为全角中文字符)
g_dma_nch_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; //存储器数据位宽,我们也选择16位半字
g_dma_nch_handle.Init.Mode = DMA_NORMAL; //选择普通模式,因为在传输完成之后我们需要进行进一步操作现实我们获取到的值,所以选择normal
g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; //只有1个DMA随便选
HAL_DMA_Init(&g_dma_nch_handle);
//联系DMA和ADC的句柄
__HAL_LINKDMA(&g_adc_nch_handle, DMA_Handle, &g_dma_nch_handle); //第二个参数为第一个ADC句柄的第三个成员,指向对应的DMA句柄
g_adc_nch_handle.Instance = ADC1;
g_adc_nch_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; //右对齐
g_adc_nch_handle.Init.ScanConvMode = ADC_SCAN_ENABLE; //扫描
g_adc_nch_handle.Init.ContinuousConvMode = ENABLE; //连续模式
g_adc_nch_handle.Init.NbrOfConversion = 6; //转换通道数为6,6通道
g_adc_nch_handle.Init.DiscontinuousConvMode = DISABLE; //不用间断模式
g_adc_nch_handle.Init.NbrOfDiscConversion = 0; //无间断模式则无间断通道
g_adc_nch_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //外部软件触发
HAL_ADC_Init(&g_adc_handle);
adc_ch_conf.Channel = ADC_CHANNEL_0;
adc_ch_conf.Rank = ADC_REGULAR_RANK_1; //转换顺序
adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);
adc_ch_conf.Channel = ADC_CHANNEL_1;
adc_ch_conf.Rank = ADC_REGULAR_RANK_2; //转换顺序
adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);
adc_ch_conf.Channel = ADC_CHANNEL_2;
adc_ch_conf.Rank = ADC_REGULAR_RANK_3; //转换顺序
adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);
adc_ch_conf.Channel = ADC_CHANNEL_3;
adc_ch_conf.Rank = ADC_REGULAR_RANK_4; //转换顺序
adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);
adc_ch_conf.Channel = ADC_CHANNEL_4;
adc_ch_conf.Rank = ADC_REGULAR_RANK_5; //转换顺序
adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);
adc_ch_conf.Channel = ADC_CHANNEL_5;
adc_ch_conf.Rank = ADC_REGULAR_RANK_6; //转换顺序
adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_239CYCLES_5; //设置为最大值
HAL_ADC_ConfigChannel(&g_adc_nch_handle, &adc_ch_conf);
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 2, 3);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
HAL_ADCEx_Calibration_Start(&g_adc_nch_handle);
HAL_DMA_Start_IT(&g_dma_nch_handle, (uint32_t)&ADC1->DR, mar, 0);
HAL_ADC_Start_IT(&g_adc_nch_handle, &mar, 0);
}
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc){
if(hadc->Instance == ADC1){
GPIO_InitTypeDef gpio_init_struct;
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能ADC时钟
__HAL_RCC_ADC1_CLK_ENABLE(); //使能GPIO时钟
gpio_init_struct.Pin = GPIO_PIN_0;
gpio_init_struct.Mode = GPIO_MODE_ANALOG; //模拟模式
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_1;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_2;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_3;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_4;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_5;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC; //选择ADC外设时钟设置
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6; //选择6分频,72/6=12MHz
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init, &g_adc_nch_handle);
}
}
uint32_t adc_get_result(void){
HAL_ADC_Start(&g_adc_nch_handle);
HAL_ADC_PollForConversion(&g_adc_nch_handle, 10); //第二个参数比1大就行
return (uint16_t)HAL_ADC_GetValue(&g_adc_nch_handle);
}
uint32_t adc_get_result_average(uint32_t ch, uint8_t times){
uint32_t temp_val = 0;
uint8_t t;
for(t = 0; t < times; t++){
temp_val += adc_get_result();
delay_ms(5);
}
return temp_val / times;
}
void adc_dma_enable(uint16_t cndtr){
/*
ADC1->CR2 &= ~(1 << 0); //关闭ADC
DMA1_Channel1->CCR &= ~(1 << 0);//关闭DMA
while(DMA1_Channel1->CCR & (1 << 0));
DMA1_Channel1->CNDTR = cndtr;
DMA1_Channel1->CCR |= (1 << 0); //开启DMA
ADC1->CR2 |= (1 << 0); //开启ADC
ADC1->CR2 |= (1 << 22); //触发规则组转换
*/
//hal库法
__HAL_ADC_DISABLE(&g_adc_nch_handle);
__HAL_DNA_DISABLE(&g_dma_nch_handle);
while(__HAL_DMA_GET_FLAG(&g_dma_nch_handle, __HAL_DMA_GET_FLAG_INDEX(&g_dma_nch_handle)));
DMA1_Channel1->CNDTR = cndtr;
__HAL_DMA_ENABEL(&g_dma_nch_handle);
__HAL_ADC_ENABLE(&g_adc_nch_handle);
HAL_ADC_Start(&g_adc_nch_handle);
}
void DMA1_Channel1_IRQHandle(void){
if(DMA1->ISR & (1 << 1)){
g_adc_dma_sta = 1;
DMA1->IECR |= 1 << 1;
}
}
接下来在编写函数文件的头文件adc.h:
#ifndef __ADC_H
#define __ADC_H
#include "SYSTEM/sys/sys.h"
#include "BSP/DMA/dma.h"
extern ADC_HandleTypeDef g_adc_handle;
void adc_nch_dam_init(uint32_t mar);
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc);
uint32_t adc_get_result(void);
uint32_t adc_get_result_average(uint32_t ch, uint8_t times);
void adc_dma_enable(uint16_t cndtr);
void DMA1_Channel1_IRQHandle(void);
#endif
最后编写主函数代码main.c:
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc.h"
#define ADC_DMA_BUF_SIZE 50 * 6 /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍,50表示转换50次 */
uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */
extern uint8_t g_adc_dma_sta; /* DMA传输状态标志, 0,未完成; 1, 已完成 */
int main(void)
{
uint16_t i,j;
uint16_t adcx;
uint32_t sum;
float temp;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
adc_nch_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "ADC 6CH DMA TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 12, 12, "ADC1_CH0_VAL:", BLUE);
lcd_show_string(30, 122, 200, 12, 12, "ADC1_CH0_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 140, 200, 12, 12, "ADC1_CH1_VAL:", BLUE);
lcd_show_string(30, 152, 200, 12, 12, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 170, 200, 12, 12, "ADC1_CH2_VAL:", BLUE);
lcd_show_string(30, 182, 200, 12, 12, "ADC1_CH2_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 200, 200, 12, 12, "ADC1_CH3_VAL:", BLUE);
lcd_show_string(30, 212, 200, 12, 12, "ADC1_CH3_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 230, 200, 12, 12, "ADC1_CH4_VAL:", BLUE);
lcd_show_string(30, 242, 200, 12, 12, "ADC1_CH4_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 260, 200, 12, 12, "ADC1_CH5_VAL:", BLUE);
lcd_show_string(30, 272, 200, 12, 12, "ADC1_CH5_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */
while (1)
{
if (g_adc_dma_sta == 1)
{
/* 循环显示通道0~通道5的结果 */
for(j = 0; j < 6; j++) /* 遍历6个通道 */
{
sum = 0; /* 清零 */
for (i = 0; i < ADC_DMA_BUF_SIZE / 6; i++) /* 每个通道采集了50次数据,进行50次累加 */
{
sum += g_adc_dma_buf[(6 * i) + j]; /* 相同通道的转换数据累加 */
}
adcx = sum / (ADC_DMA_BUF_SIZE / 6); /* 取平均值 */
/* 显示结果 */
lcd_show_xnum(108, 110 + (j * 30), adcx, 4, 12, 0, BLUE); /* 显示ADCC采样后的原始值 */
temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */
adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
lcd_show_xnum(108, 122 + (j * 30), adcx, 1, 12, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
lcd_show_xnum(120, 122 + (j * 30), temp, 3, 12, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
}
g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */
}
LED0_TOGGLE();
delay_ms(100);
}
}
到这里我们的实验代码就写完了。