PY32F030的ADC外设与STM32的ADC使用大同小异,差别不大。下面将介绍ADC 单通道 软件触发 规则组 DMA连续转化加均值滤波程序的编写流程。
1. ADC初始化函数ADC1_Init()
使用所有外设第一步都是开启与之相关的时钟,这里也不例外,首先复位ADC并开启其时钟。
选择ADC基地址,PY32F030只有一个12位ADC,所以这里我们选ADC1。然后设置ADC时钟,ADC时钟可选APB总线或者HSI时钟分频得到。设置转化分辨率,有6、8、10、12位可选。分辨率越高,转化精度越高,当然转化的时间也更长。数据对齐方式一般选择右对齐。扫描序列选择向上,即从通道0到通道11。然后将转化结束标志位作为结束/中断标志位,开启动态低功耗自动延迟,开启连续转换模式,关闭非连续转化模式。触发方式选择软件触发,禁用硬件触发检测。使能DMA,过载发生保留新数据,覆盖旧数据(当转化值没有及时取走时)。最后调用初始化函数HAL_ADC_Init()。
然后是ADC规则组和通道的初始化。选择需要用的通道,具体的通道映射要到数据手册查看,方便接下来的回调函数中初始化IO口。这里选这通道1,加入规则组序列,选择采样周期(采样周期加上12.5T就是采样所需的时间),最后配置使能规则组通道。
使用ADC前需要校准,不然可能出现较大误差。HAL_ADCEx_Calibration_Start(&adc1);
最后以DMA方式开启ADC转化。参数分别为ADC总控结构体、DMA传输的数组地址和传输的次数。
//ADC1的初始化函数
void ADC1_Init(void)
{
__HAL_RCC_ADC_FORCE_RESET();
__HAL_RCC_ADC_RELEASE_RESET(); /* 复位ADC */
__HAL_RCC_ADC_CLK_ENABLE(); /* ADC时钟使能 */
adc1.Instance = ADC1;
adc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; /* 设置ADC时钟 */
adc1.Init.Resolution = ADC_RESOLUTION_12B; /* 转换分辨率12bit */
adc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 数据右对齐 */
adc1.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD; /* 扫描序列方向:向上 */
adc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; /* 将转换结束标志作为结束/中断标志位 */
adc1.Init.LowPowerAutoWait = ENABLE; /* 等待转换模式开启 */
adc1.Init.ContinuousConvMode = ENABLE; /* 连续转换模式 */
adc1.Init.DiscontinuousConvMode = DISABLE; /* 不使能非连续模式 */
adc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 软件触发 */
adc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; /* 触发边沿无 */
adc1.Init.DMAContinuousRequests = ENABLE; /* 使能DMA */
adc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; /* 当过载发生时,覆盖上一个值 */
HAL_ADC_Init(&adc1); //配置ADC相关的寄存器
adc1_in1.Channel = ADC_CHANNEL_1; //使用通道1
adc1_in1.Rank = ADC_RANK_CHANNEL_NUMBER; //加入规则组
adc1_in1.SamplingTime = ADC_SAMPLETIME_41CYCLES_5; //采样周期41.5个 41.5+12.5=54个
HAL_ADC_ConfigChannel(&adc1,&adc1_in1); //配置规则组通道
HAL_ADCEx_Calibration_Start(&adc1); //自校准
HAL_ADC_Start_DMA(&adc1, (uint32_t *)adc1_dma_buff, 10); //开启转换,DMA方式, 10次传输
}
2. ADC回调函数HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
在回调函数中配置IO口和DMA。
配置IO口,打开PA口时钟,配置为模拟输入。具体IO口可以看数据手册,得到PA1有ADC通道1的重映射。
然后是DMA的初始化。具体步骤可参考WS2812那一节,只要把传输方向和地址改下就行。还有就是HAL_SYSCFG_DMA_Req(0); /* DMA1_MAP选择为ADC */。这个也是PY和ST的主要区别。
最后设置DMA中断优先级和使能DMA中断。
//HAL_ADC_Init函数调用的回调函数(初始化底层IO和时钟)
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
GPIO_InitTypeDef GPIO_InitType;
if(hadc -> Instance == ADC1)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_DMA_CLK_ENABLE();
__HAL_RCC_SYSCFG_CLK_ENABLE();
GPIO_InitType.Pin = GPIO_PIN_1;
GPIO_InitType.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &GPIO_InitType);
HAL_SYSCFG_DMA_Req(0); /* DMA1_MAP选择为ADC */
adc1_dma.Instance = DMA1_Channel1;
adc1_dma.Init.Direction = DMA_PERIPH_TO_MEMORY; //传输方向:外设->存储器
adc1_dma.Init.PeriphInc = DMA_PINC_DISABLE; //外设地址是否自增 否
adc1_dma.Init.MemInc = DMA_MINC_ENABLE; //存储器地址是否自增 是
adc1_dma.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //外设数据宽度:半字(16位)
adc1_dma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; //存储器数据宽度:半字(16位)
adc1_dma.Init.Mode = DMA_CIRCULAR; //循环模式
adc1_dma.Init.Priority = DMA_PRIORITY_HIGH; //优先级 高
__HAL_LINKDMA(hadc, DMA_Handle, adc1_dma); //链接
HAL_DMA_Init(&adc1_dma); //DMA初始化
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn,0,0); //DMA1的中断优先级
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn); //开启DMA1中断
// HAL_NVIC_SetPriority(ADC_COMP_IRQn,0,0); //ADC的中断优先级
// HAL_NVIC_EnableIRQ(ADC_COMP_IRQn); //开启ADC中断
}
}
3. DMA通道1中断函数
DMA通道1中断函数中调用DMA中断函数。
DMA中断函数会调用完成中断回调函数并清除中断标志位。
//DMA通道1中断函数
void DMA1_Channel1_IRQHandler(void)
{
HAL_DMA_IRQHandler(adc1.DMA_Handle); //DMA中断函数
}
4. ADC的DMA的完成中断回调函数
在完成中断回调函数中处理数据并打印到串口输出。将10次的ADC结果累加并取均值,减小误差。串口的配置就具体说了,有需要的参考下方代码,只需要重写重定向函数即可
//ADC的DMA的完成中断回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
uint32_t i,num;
if(hadc ->Instance == ADC1){ //判断是不是ADC1进入
num = 0;
for(i=0; i<10; i++)
{
num +=adc1_dma_buff[i];
}
printf("ADC1_IN1:%f V\r\n", (num/10.0)*(3.3/4096.0)); //串口打印10次电压值
// HAL_ADC_Start_IT(&adc1); //再次开启转换, 中断方式
}
}
/***************************************
* 串口引脚定义:
* USART1_TX---->PA2
* USART1_RX---->PA3
****************************************/
#include "Usart1.h"
UART_HandleTypeDef USART1_Handle;//USART1句柄
//USART1参数配置
void USART1_Config(void)
{
/*使能时钟*/
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA模块时钟
__HAL_RCC_USART1_CLK_ENABLE(); //开启USART1模块时钟
/*配置GPIO参数*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3; //PA2|PA3
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; //推免复用
GPIO_InitStruct.Pull = GPIO_PULLUP; //上拉模式
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //超高速模式
GPIO_InitStruct.Alternate = GPIO_AF1_USART1; //连接到USART1
HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);//根据GPIO结构体初始化引脚
/*配置USART1参数*/
USART1_Handle.Instance = USART1; //串口基地址为USART1
USART1_Handle.Init.BaudRate = 115200; //波特率设置为115200
USART1_Handle.Init.WordLength = UART_WORDLENGTH_8B; //数据位
USART1_Handle.Init.StopBits = UART_STOPBITS_1; //停止位
USART1_Handle.Init.Parity = UART_PARITY_NONE; //无校验
USART1_Handle.Init.Mode = UART_MODE_TX_RX; //工作模式选择发送与接收
USART1_Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件控制流
HAL_UART_Init(&USART1_Handle);//根据结构体配置初始化USART1
/* 使能NVIC */
HAL_NVIC_SetPriority(USART1_IRQn, 1, 2);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
//重定向printf
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&USART1_Handle, (uint8_t *)&ch, 1, 0xffff);//通过USART1发送数据
return ch;
}
5. mai()
首先初始化HAL库,然后配置系统设置,初始化串口,最后初始化ADC即可。
int main(void)
{
// uint8_t res;
/***** 系统 *****/
HAL_Init();//HAL库初始化
System_Clock_Config_HSI();//使用外部部高速时钟--->24MHz
/****** 用户 *****/
USART1_Config();//串口1参数配置
printf("hello\r\n");//打印测试
ADC1_Init();
while(1)
{
}
}