目录

前言:

一、CS32L015系列单片机ADC资源简介:

二、ADC配置流程:

三、ADC配置完整代码及注释:

1、配置GPIO口模拟输入功能:

2、配置ADC相关参数:

3、链接重构ADC中断相关函数:

4、将ADC转换为实际电压值:

四、如果这篇文章能帮助到你,请点个赞鼓励一下吧ξ( ✿>◡❛)~


前言:

        长颈鹿最近在使用芯海的CS32L015C8T6单片机进行工程开发,发现网上有关这款单片机的资料非常少,而且官方提供的驱动代码有些不适用,因此决定记录一下造轮子的过程,前车之鉴,后车之师,希望能够帮助后来的开发者减少一些重复性造轮子的工作,少走一些弯路。

一、CS32L015系列单片机ADC资源简介:

        这款MCU只有一个ADC,以下是对其资源的介绍和点评

        1、12位精度ADC,分辨率4096,能够适用一些简单场景的ADC功能开发;

        2、有17个外部输入通道和5个内部输入通道;

       3、没有DMA功能,硬件资源是比较紧凑的,这款MCU使用Cortex-M0+内核,主频最高只有24MHz,属于慢速单片机,如果需要使用ADC或者串口接收大量数据,还是建议选择具有DMA功能的单片机,不然要消耗大量的CPU计算资源,系统运行速度会很慢;

        4、CS32L015C8T6胜在价格实惠;

二、ADC配置流程:

        CS32L015的ADC配置思路和大部分单片机是一样的,主要区别就在于官方提供的HAL库中的函数实现或者参数不同。以下是配置的流程:

  1. 配置GPIO模拟输入功能;
  2. 配置ADC参数,将ADC特定通道映射到GPIO的端口;
  3. 设置ADC中断优先级;
  4. 开启ADC中断,链接中断回调函数;
  5. 启动ADC中断模式;

三、ADC配置完整代码及注释:

1、配置GPIO口模拟输入功能:

        芯海的片子在将GPIO口配置为ADC模拟输入的功能时,官方例程中把GPIO中与ADC无关的功能也都配置了一遍,例如GPIO硬件按键消抖、负载驱动能力等参数,这些结构体参数在硬件层面都映射到了具体的寄存器,这是芯海的一个特点, 区别于ST单片机。

      例如当GPIO口需要配置为输入模式时,不管是模拟信号输入还是普通的输入模式, GPIOx.OpenDrain 这个参数一定要配置成  GPIO_OPENDRAIN(输出模式一定要配置成开漏输出,不能是推挽),即使输入模式和输出选项并不相关。芯海的单片机也一定要这样配置,因为牵涉到底层寄存器的硬件映射,不能觉得是无关项就爱谁谁。

/*****************************************************************************
[函数名称]MX_ADC_MspInit
[函数功能]ADC底层GPIO初始化
[参    数]
[备    注]
*****************************************************************************/
void MX_ADC_MspInit()
{
	GPIO_InitTypeDef GPIOx = {0};
	
	GPIOx.Mode 				= GPIO_MODE_ANALOG;			//工作模式:模拟输入
	GPIOx.OpenDrain 		= GPIO_OPENDRAIN;			//输出模式:开漏输出
	GPIOx.SlewRate 			= GPIO_SLEW_RATE_HIGH;		//端口速度:高速
	GPIOx.DrvStrength 		= GPIO_DRV_STRENGTH_HIGH;	//驱动负载能力:强,无关项
	GPIOx.Pull 				= GPIO_NOPULL;				//上下拉电阻:无
	GPIOx.Debounce.Enable 	= GPIO_DEBOUNCE_DISABLE;	//按键抖动过滤:关闭,无关项
	
	GPIOx.Pin 				= AD_BAT_Pin;				//GPIO端口选择:AD_BAT_Pin
	HAL_GPIO_Init( AD_BAT_GPIO_Port, &GPIOx );			//GPIO初始化
}

2、配置ADC相关参数:

        CS32L015在配置ADC时,有一些参数和STM32是有明显区别的,例如这个“AutoAccumulation”参数,控制ADC采集数据自动累加的功能,如果设置了多个通道,开启这个功能后,按照用户手册的意思,ADC会自动将所有通道采集的数据累加到一个结果中,最终输出的结果需要手动除以 “NbrOfConversion”参数中配置的 通道数和采样次数 + 1,这个除数数值需要额外+1,这是长颈鹿通过不断测试得出的结论,至于为什么,芯海的官方文档中没有写,例程代码中甚至也是错误的用法,除以除数(N+1)才能得到一个正确的均值滤波后的值,我认为这个功能是为了弥补这款MCU没有DMA的缺点,设计出的一个“折中”方案,可减少中断触发次数,节省MCU本就不富裕的算力资源,

        本例程开启一个ADC通道,使用了ADC自动累加“AutoAccumulation”功能。

/*****************************************************************************
[函数名称]MX_ADC_Init
[函数功能] ADC 初始化
[参    数]
[备    注]
*****************************************************************************/
ADC_HandleTypeDef	hadc = {0};		//全局变量
void MX_ADC_Init()
{
	MX_ADC_MspInit();	            //ADC底层GPIO初始化
	
	__HAL_RCC_ADC_CLK_ENABLE();		//ADC总线时钟:开启
	
	hadc.Instance                = ADC;		//ADC实例选择:ADC(只有一个)
	hadc.Init.SamplingTime       = HAL_ADC_SAMPLE_8CYCLE;	//采样时间:8周期
	hadc.Init.ClkSel             = HAL_ADC_CLOCK_PCLK_DIV4;	//时钟分频:4分频
	hadc.Init.ConvMode           = HAL_ADC_MODE_CONTINUE;	//转换模式:扫描模式
	hadc.Init.ContinueChannelSel = HAL_ADC_CHANNEL_2;	    //扫描通道设置:2
	hadc.Init.NbrOfConversion    = 10-1;                    //转换次数:10次	
	hadc.Init.AutoAccumulation   = HAL_ADC_AUTOACC_ENABLE;	//自动累加:开启
	hadc.Init.CircleMode         = HAL_ADC_MULTICHANNEL_NONCIRCLE;	//循环模式:单通道循环
	hadc.Init.Vref               = HAL_ADC_VREF_VCAP;		    //参考电压:VREF_VCAP,2.5V
	hadc.Init.ExtTrigConv0       = HAL_ADC_EXTTRIG_SW_START;	//中断触发:软件信号

	HAL_ADC_Init(&hadc);		            //初始化ADC
	//HAL_NVIC_SetPriority( ADC_IRQn, 2 );	//ADC中断优先级:2(0~3)
	HAL_NVIC_EnableIRQ( ADC_IRQn );			//ADC中断使能:开启
	HAL_ADC_Start_IT(&hadc);				//启动adc
}

3、链接重构ADC中断相关函数:

        配置外设的中断功能,都有相对固定的流程,设置优先级、开中断、链接中断回调函数。如果项目是用C/C++混合开发,在重构中断回调函数时,需要加上 extern “C”{ } 来区分C和C++的函数名称空间,否则编译器会找不到中断回调函数的入口。

void ADC_IRQHandler(void)			//链接 ADC 中断回调函数
{
    extern ADC_HandleTypeDef	hadc;
    HAL_ADC_IRQHandler(&hadc);	//ADC中断回调函数
}

        我们首先需要链接ADC中断回调函数,如上所示,“ADC_IRQHandler”是系统最底层的ADC中断回调函数,在系统启动文件中断向量表中为其分配了固定的入口地址,只要触发ADC中断,就会自动进入这个函数,但是这个函数很基础,只是提供了一个虚拟的入口,没有具体的实现,区分回调种类的具体实现在“HAL_ADC_IRQHandler”函数中

中断向量表中“ADC_IRQHandler”函数的入口地址

        “HAL_ADC_IRQHandler”函数是HAL库封装好的ADC中断处理函数,在这个函数中,实现了单通道转换完成回调、全通道转换完成回调以及范围溢出回调函数等功能,功能更加丰富,开发者只需要根据应用场景调用对应功能的入口函数,就可以实现ADC的中断回调函数实现。

HAL_ADC_IRQHandler函数会自动回调的功能函数:

HAL_ADC_ContConvCpltCallback();

ADC全部通道转换完成回调函数

HAL_ADC_LevelOutOfRangeCallback();

ADC超阈值回调函数

HAL_ADC_ChannelxCallback();

ADC单通道转换完成回调函数

        本例程中使用的是HAL_ADC_ContConvCpltCallback()功能回调函数,使用这个函数,可以在ADC通道累加完规定的所有数据之后,进入中断,在中断函数中采集的ADC值进行处理。

/*****************************************************************************
[函数名称]HAL_ADC_ChannelxCallback
[函数功能]ADC单通道转换完成 中断回调函数
[参    数]
[备    注]
*****************************************************************************/
extern "C" {
	
// 全部转换结束后回调,等待ADC值全部转换完成之后触发一个中断 
void HAL_ADC_ContConvCpltCallback(ADC_HandleTypeDef *hADC)
{
	HAL_ADC_Stop_IT(hADC);
	vUpdateData();            //调用ADC处理函数
}

}

4、将ADC转换为实际电压值:

ADC对应的电压转换函数如下:

void vUpdateData( void )
{
	uint16 m_au16ADAverage = 0;
	const f32 c_f32Uref = 2500.0f;						//参考电压对应2.5V,2500mV
	
	m_au16ADAverage = HAL_ADC_GetAccValue(&hadc) / 10;	//获取累加后的ADC值,并求平均
	
	//12位精度ADC,对应分辨率位4096,f32Voltage即为ADC转换后的实际电压
	f32 f32Voltage = m_au16ADAverage * c_f32Uref / 4096.0f;	
	
	m_bUpdateData = TRUE;
}

四、如果这篇文章能帮助到你,请点个赞鼓励一下吧ξ( ✿>◡❛)~

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐