xr871 sdk evb_audio 的 adc_button 逻辑分析

/**
   xr871 sdk evb_audio 工程代码中的 adc_button 逻辑分析
 */

################################################################
主函数 main.c
################################################################
	void ad_button_init()
	{
		AD_Button_Irq irq;------------------------------------------1.1
		DRV_AD_ButtonInit(&AD_Button_Cfg); -------------------------1.2
		irq.arg = NULL;
		irq.buttonCallback = ad_button_Cb;
		DRV_AD_ButtonCallBackRegister(&irq);------------------------1.3
		if (OS_ThreadCreate(&g_ad_button_ctrl_thread,
							"",
							ad_button_ctrl_task,--------------------1.4
							NULL,
							OS_THREAD_PRIO_APP,
							AD_BUTTON_CTRL_THREAD_STACK_SIZE) != OS_OK) {
			COMPONENT_WARN("thread create error\n");
		}
		COMPONENT_TRACK("end\n");
	}
########################################
1.1 实例化 AD_Button_Irq 对象:
########################################

	/**
	  * @brief AD button callback set and irq mode set.
	  */
	typedef struct {
		/*!< The callback for ad button. irq_sta is interrupt status, ad_Value is ad capture value */
		void (*buttonCallback) (void *arg, ADC_IRQState irq_sta, uint32_t ad_Value);
		void *arg;
	}AD_Button_Irq;
########################################
1.2 实例化 AD_Button_Config 对象
########################################

	typedef struct {
		ADC_Channel channel;				/*!< The ad channel used for ad button*/
		ADC_IRQMode ad_Button_Irq_Mode; 	/*!< The channel's interrupt mode */
		uint32_t lowValue;					/*!< lower limit value in interrupt mode of ADC_IRQ_LOW,
																		  ADC_IRQ_LOW_DATA, ADC_IRQ_LOW_HIGH or ADC_IRQ_LOW_HIGH_DATA*/
		uint32_t highValue; 				/*!< Upper limit value in interrupt mode of ADC_IRQ_HIGH,
																		  ADC_IRQ_HIGH_DATA, ADC_IRQ_LOW_HIGH or ADC_IRQ_LOW_HIGH_DATA*/
	}AD_Button_Config;


	static AD_Button_Config AD_Button_Cfg = {
		ADC_CHANNEL_3, ------------通道参数
		ADC_IRQ_LOW,   ------------初始中断模式
		3000,          ------------低于此阈值产生adc_irq_low 中断
		3500,          ------------高于此阈值产生adc_irq_high 中断
	};
	
	/**
	  * @brief Init the ad button.
	  * @note This function is used to configure the ad button pin and interrut triggering conditions.
	  * @param ad_button_info:
	  *        @arg ad_button_info->channel:The channels used for ad button.
	  *        @arg ad_button_info->ad_Button_Irq_Mode: interrupt triggering conditions.
	  *        @arg ad_button_info->lowValue:  lower limit value in interrupt mode of ADC_IRQ_LOW,
	  *            ADC_IRQ_LOW_DATA, ADC_IRQ_LOW_HIGH or ADC_IRQ_LOW_HIGH_DATA
	  *        @arg ad_button_info->highValue: Upper limit value in interrupt mode of ADC_IRQ_HIGH,
	  *            ADC_IRQ_HIGH_DATA, ADC_IRQ_LOW_HIGH or ADC_IRQ_LOW_HIGH_DATA
	  * @retval Component_Status: The status of driver.
	  */
	typedef struct {
		uint16_t			chanPinMux;
		ADC_State			state;
		ADC_WorkMode		mode;
		uint32_t			lowPending;
		uint32_t			highPending;
		uint32_t			dataPending;

		ADC_IRQCallback		IRQCallback[ADC_CHANNEL_NUM];
		void			   *arg[ADC_CHANNEL_NUM];
	} ADC_Private;

	static ADC_Private gADCPrivate;

	Component_Status DRV_AD_ButtonInit(AD_Button_Config *ad_button_info)
	{
		AD_Button = *ad_button_info;
		ADC_InitParam initParam;
		initParam.delay = 10;            
		initParam.freq = 500000;         ---------adc采样率
		initParam.mode = ADC_CONTI_CONV; ---------adc工作模式

		HAL_Status sta = HAL_ADC_Init(&initParam); --------adc 时钟,采样率,工作模式,中断使能
			{{{
				在 hal_adc_init() 中
				gADCPrivate->chanPinMux = 0;
				gADCPrivate->lowPending = 0;
				gADCPrivate->highPending = 0;
				gADCPrivate->dataPending = 0;
				gADCPrivate->mode = initParam->mode; //此处为 ADC_CONTI_CONV 在上面的 DRV_AD_ButtonInit() 方法中

			}}}
		if (sta == HAL_OK || sta == HAL_BUSY) {
			HAL_ADC_ConfigChannel(AD_Button.channel, ADC_SELECT_ENABLE, AD_Button.ad_Button_Irq_Mode,
							      AD_Button.lowValue, AD_Button.highValue);
									----------------- adc 通道,阈值,中断模式配置
			DRV_EnableAD_Button();  ----------------- adc 中断回调注册 1.2.1
			if (sta == HAL_OK)
				HAL_ADC_Start_Conv_IT();------------- adc 开启持续中断转换 ADC_EnableADC
			return COMP_OK;
		} else
			COMPONENT_WARN("AD init error %d\n", sta);
		COMPONENT_TRACK("end\n");
		return COMP_ERROR;
	}

########################################
1.2.1 DRV_EnableAD_Button
########################################
	/**
	  * @brief Enable ad button.
	  * @note This function is used to enable the ad button, if ad button is enable,
	  *           when you push the button, the interrupt will be tigger.
	  * @retval Component_Status: The status of driver.
	  */
	Component_Status DRV_EnableAD_Button()
	{
		HAL_ADC_EnableIRQCallback(AD_Button.channel, AD_Button_Cb, NULL); ---------------------->
		return COMP_OK;
	}

	----------------------------------------------------------->下面分析 HAL_ADC_EnableIRQCallback

	/**
	 * @brief Enable interrupt callback function for the specified ADC channel
	 * @param[in] chan The specified ADC channel
	 * @param[in] cb The interrupt callback function
	 * @param[in] arg Argument of the interrupt callback function
	 * @retval HAL_Status, HAL_OK on success
	 */
	HAL_Status HAL_ADC_EnableIRQCallback(ADC_Channel chan, ADC_IRQCallback cb, void *arg)
	{
		unsigned long	flags;
		ADC_Private	   *priv;

		ADC_ASSERT_CHANNEL(chan);

		flags = HAL_EnterCriticalSection();
		priv = &gADCPrivate;
		if ((priv->state == ADC_STATE_READY) || (priv->state == ADC_STATE_BUSY)) {
			priv->arg[chan] = arg;
			priv->IRQCallback[chan] = cb; ---------- 回调注册 即:AD_Button_Cb
		} else {
			priv = NULL;
		}
		HAL_ExitCriticalSection(flags);

		if (priv == NULL) {
			HAL_WRN("ADC state: %d\n", gADCPrivate.state);
			return HAL_ERROR;
		}

		return HAL_OK;
	}

----------------------------------------------------------------------->  下面看 AD_Button_Cb
通过高低中断的切换触发达到检测按下和抬起的目的
	static void AD_Button_Cb(void *arg)
	{
		ADC_IRQState irq_sta = HAL_ADC_GetIRQState(AD_Button.channel);
		if (irq_sta == ADC_LOW_IRQ) { -------------- 低于阈值触发的中断 ADC_LOW_IRQ (检测按键按下)
			uint32_t ad_value = AD_button_filter( HAL_ADC_GetValue(AD_Button.channel)); --- 获取ad结果,滤波
			if (ad_value) {
				if (AD_Button_Private.buttonCallback)
					AD_Button_Private.buttonCallback(AD_Button_Private.arg,
											 irq_sta, ad_value); ----- 调用已经注册的按键回调 后面分析

				Private_ADC_Irq = ADC_IRQ_LOW_HIGH; ---------配置下次中断为高于阈值触发(检测按键提起)
				HAL_ADC_ConfigChannel(AD_Button.channel, ADC_SELECT_ENABLE, Private_ADC_Irq,
							     	 ad_value - 50, AD_Button.highValue);
				DRV_AD_BUTTON_DBG("<<<< @FILE: %s, @FUNC: %s, @LINE: %d >>>>, ad_value: %d\n", 
						__FILE__, __func__, __LINE__,  ad_value);
			}

		} else if (irq_sta == ADC_HIGH_IRQ) { --------------高于阈值触发的中断 ADC_HIGH_IRQ
			uint32_t ad_value = HAL_ADC_GetValue(AD_Button.channel);
			if (AD_Button_Private.buttonCallback)
				AD_Button_Private.buttonCallback(AD_Button_Private.arg,
											 irq_sta, HAL_ADC_GetValue(AD_Button.channel));
			Private_ADC_Irq = ADC_IRQ_LOW; -----------------配置下次中断为低于阈值触发(检测按键按下)
			HAL_ADC_ConfigChannel(AD_Button.channel, ADC_SELECT_ENABLE, Private_ADC_Irq,
							      AD_Button.lowValue, AD_Button.highValue);
			DRV_AD_BUTTON_DBG("<<<< @FILE: %s, @FUNC: %s, @LINE: %d >>>>, ad_value: %d\n", 
					__FILE__, __func__, __LINE__,  ad_value);
		}
	}

----------------------------------------------------------------------->  下面看 adc 中断服务方法
此方法在硬件adc中断触发后自动调用,在startup.s 中 vi project/common/startup/gcc/startup.s +326 
	.word	   GPADC_IRQHandler 注册adc异常中断服务的名称和入口地址
	.weak      GPADC_IRQHandler 弱类型symbol,可被强类型symbol覆盖 也就是下面的自定义中断服务方法                                                                                             
	.thumb_set GPADC_IRQHandler,Default_Handler   
	/******************************************************
	Tips:当一个异常出现以后,ARM会自动执行以下几个步骤:
		1.把下一条指令的地址放到连接寄存器LR(通常是R14).---保存位置
		2.将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中---保存CPSR
		3.根据异常类型,强制设置CPSR的运行模式位
		4.强制PC(程序计数器)从相关异常向量地址取出下一条指令执行,从而跳转到相应的异常处理程序中
	*********************************************************/	
		
	void GPADC_IRQHandler(void)
	{
		if(gADCPrivate.mode == ADC_BURST_CONV) { --------- 非burst此处,不进入
			uint32_t i;
			uint32_t fifoverunPending, fifodataPending;
			fifoverunPending = ADC_GetFifoOverunPending();
			fifodataPending  = ADC_GetFifodataPending();
			ADC_ClrFifoPending(fifoverunPending);
			ADC_ClrFifoPending(fifodataPending);

			if(fifodataPending) {
				for (i = ADC_CHANNEL_0; i < ADC_CHANNEL_NUM; i++) {
					if (ADC_GetChanPinMux(i) && (gADCPrivate.IRQCallback[i]))
						gADCPrivate.IRQCallback[i](gADCPrivate.arg[i]);
				}
			}
		} else { --------- 进入此分支
			uint32_t i;
			gADCPrivate.lowPending	= ADC_GetLowPending();
			gADCPrivate.highPending = ADC_GetHighPending();
			gADCPrivate.dataPending = ADC_GetDataPending();
			
			ADC_ClrLowPending(gADCPrivate.lowPending);    ------- clear挂起标志
			ADC_ClrHighPending(gADCPrivate.highPending);  
			ADC_ClrDataPending(gADCPrivate.dataPending);
			
			for (i = ADC_CHANNEL_0; i < ADC_CHANNEL_NUM; i++) {
				if (((HAL_GET_BIT(gADCPrivate.dataPending, HAL_BIT(i)) && ADC_GetChanDataIRQ(i))
					|| (HAL_GET_BIT(gADCPrivate.lowPending, HAL_BIT(i)) && ADC_GetChanLowIRQ(i))
					|| (HAL_GET_BIT(gADCPrivate.highPending, HAL_BIT(i)) && ADC_GetChanHighIRQ(i)))
					&& (gADCPrivate.IRQCallback[i])) {
					gADCPrivate.IRQCallback[i](gADCPrivate.arg[i]); --------- 调用注册的中断回调函数 AD_Button_Cb
				}
			}
		}
	}

详查 AD_Button_Cb 中的 代码段
	static AD_Button_Irq AD_Button_Private = {NULL, NULL};
	--------------------------------------------------------
		if (ad_value) {
			if (AD_Button_Private.buttonCallback)
				AD_Button_Private.buttonCallback(AD_Button_Private.arg,
										 irq_sta, ad_value); ----- 调用已经注册的按键回调 后面分析 1.3
			.......................
		}
	--------------------------------------------------------

########################################
1.3 DRV_AD_ButtonCallBackRegister
########################################
	irq.arg = NULL;
	irq.buttonCallback = ad_button_Cb;
	DRV_AD_ButtonCallBackRegister(&irq); 
	{
		 AD_Button_Private.buttonCallback = irq->buttonCallback;
		 AD_Button_Private.arg = irq->arg;
	}

最终回调到:ad_button_Cb
	void ad_button_Cb(void *arg, ADC_IRQState sta, uint32_t ad_value)
	{
		if (sta == ADC_LOW_IRQ) {                     // 按下
			if (ad_value)                             // 滤波后的ad值
				ad_button_id_refresh(ad_value);    ----------------------------> 下面分析
		}else if(sta == ADC_HIGH_IRQ)                 // 释放
			AD_Button = AD_BUTTON_ALL_RELEASE;
	}
标定值 
	typedef enum {
		AD_BUTTON_0_VALUE = 850,              -----------------------------理论值0.50v
		AD_BUTTON_1_VALUE =	1780,             -----------------------------理论值1.05v
		AD_BUTTON_2_VALUE = 2754,             -----------------------------理论值1.65v
		AD_BUTTON_VALUE_NULL = -1,
	}AD_BUTTON_VALUE;
		
	typedef struct {
		AD_Button_RepeatPressMode *RepeatMode;
		uint16_t long_Press_hold_Time_Ms;
		uint16_t short_Press_hold_Time_Ms;
		AD_BUTTON_VALUE button_Ad_Value;
		AD_BUTTON_ID	button_Id;
	}AD_Button_Info;	
	
	typedef struct {
		uint16_t repeat_Time_Ms;
		uint16_t repeat_Period_Ms;
	}AD_Button_RepeatPressMode;
	
	AD_Button_RepeatPressMode Ad_Button_2_Repeat = {700, 10};

	AD_Button_Info AD_Button_Register[AD_BUTTON_NUM] = {
		{NULL, 0, 10, AD_BUTTON_0_VALUE, AD_BUTTON_0},
		{NULL, 0, 10, AD_BUTTON_1_VALUE, AD_BUTTON_1},
		{&Ad_Button_2_Repeat, 0, 10, AD_BUTTON_2_VALUE, AD_BUTTON_2},
	};
// 判定哪颗按键按下,并将按键id赋值给全局变量AD_Button
	void ad_button_id_refresh(uint32_t ad_value)
	{
		int16_t i = 0;
		uint32_t d_v = 0;
		AD_Button_Info *p = AD_Button_Register;

		for (i = 0; i < AD_BUTTON_NUM; i++) {
		 	d_v = ad_d_Value(ad_value, p->button_Ad_Value); ------ 与标定值之间,计算差值,
			if (d_v <= AD_VALUE_DEVAATION) {                ------ 在± 100的偏差范围内 
				AD_Button = p->button_Id;                   ------ 确定哪颗按键按下 1.4 中使用
				return;
			}
			p ++;
		}
		AD_Button = AD_BUTTON_NUM;                          ------ 1.4 中使用
	}
########################################
1.4 线程服务 ad_button_ctrl_task
########################################
	void ad_button_ctrl_task(void *arg)
	{
		DRV_AD_BUTTON_CTRL_DBG("%s\n", __func__);
		AD_BUTTON_ID button_id = AD_BUTTON_NUM;
		while (1) { ---------------------------------------------------一直循环
			AD_Button_Info *button_info = NULL;
			if (AD_Button != AD_BUTTON_NUM && AD_Button != AD_BUTTON_ALL_RELEASE) { // 按下分支
				if (AD_Button != button_id && AD_Button_Is_Trigger == 0) {
					button_id = AD_Button;                              -------------------- 按键id
					DRV_AD_BUTTON_CTRL_DBG("AD_Button %d\n", AD_Button);
					AD_Button_Press_Time = OS_JiffiesToMSecs(OS_GetJiffies());   ----------- 记录按下时间点
					DRV_AD_BUTTON_CTRL_DBG("AD_Button_Press_Time %d\n", AD_Button_Press_Time);
				}
			} else if (button_id != AD_BUTTON_NUM && AD_Button == AD_BUTTON_ALL_RELEASE) { // 抬起分支
				//RELEASE
				DRV_AD_BUTTON_CTRL_DBG("release\n");
				button_info = &AD_Button_Register[button_id]; 
				ad_button_short_press(button_info);           ------------ 1.4.2 检测短按,并推送短按事件
				ad_button_release_cmd (button_info);          ------------ 1.4.3 推送释放事件
				button_id = AD_BUTTON_NUM;
				AD_Button_Is_Trigger = 0;
			}

			if (button_id != AD_BUTTON_NUM) { // 按下分支 
				button_info = &AD_Button_Register[button_id]; ------------ 按键信息提取赋值
				ad_button_check(button_info);                 ------------ 检查 1.4.1
			}
			OS_MSleep(10);       ----------- sleep 10 ms
		}
	}

########################################
1.4.1 ad_button_check(button_info);
########################################

	void ad_button_check(AD_Button_Info *button)
	{
		uint32_t os_time = OS_JiffiesToMSecs(OS_GetJiffies());  ----------- 获得此刻的系统运行时间
		ad_button_repeat(button, os_time);               下----> 检测重复按,并推送重复按事件
		ad_button_long_press(button, os_time);           下----> 检测长按,并推送长按事件
	}

	typedef enum {
		AD_BUTTON_CMD_LONG_PRESS,
		AD_BUTTON_CMD_SHORT_PRESS,
		AD_BUTTON_CMD_REPEAT,
		AD_BUTTON_CMD_RELEASE,
		AD_BUTTON_CMD_NULL,
	}AD_BUTTON_CMD;

	typedef enum {
		AD_BUTTON_0,
		AD_BUTTON_1,
		AD_BUTTON_2,
		AD_BUTTON_NUM,
		AD_BUTTON_ALL_RELEASE,
	}AD_BUTTON_ID;

	typedef struct {
		AD_BUTTON_CMD cmd;
		AD_BUTTON_ID id;
	}AD_Button_Cmd_Info;

	void ad_button_repeat(AD_Button_Info *button, uint32_t os_time)
	{
		static uint32_t last_d_time = 0;
		if (button->RepeatMode != NULL) { -------即:{&Ad_Button_2_Repeat, 0, 10, AD_BUTTON_2_VALUE, AD_BUTTON_2},
			uint32_t repeat_time = button->RepeatMode->repeat_Time_Ms;     ---------------- 重复时间 700ms
			uint32_t repeat_period = button->RepeatMode->repeat_Period_Ms; ---------------- 重复周期 10ms
			
			/**< 在任务大循环中持续获取系统时间 并与按下时间点做差值 */
			uint32_t d_time = ad_button_d_time(AD_Button_Press_Time, os_time); ---- 1.4 中(记录按下时间点)
			if (d_time >= repeat_time) { ------------ > 700 ms, 认为repeat
				AD_Button_Is_Trigger = 1;                     ----- 设置触发标志
				if (d_time - last_d_time >= repeat_period) {
					AD_Button_Cmd_Info * p = &AD_Button_Cmd;  -----全局 static AD_Button_Cmd_Info AD_Button_Cmd;
					p->cmd = AD_BUTTON_CMD_REPEAT;            -----重复按下
					p->id = button->button_Id;
					ad_button_send_vkey(p);                  -------------------------------> 
					last_d_time = d_time;
				}
			}
		}
	}

-----------------------------------> 
推送发布按键按下的消息到g_sys_queue(event_queue),由 main_publisher 线程来 从 g_sys_queue 中 recv 监听
并且 notify 所有已经 attach 在 g_sys_publisher 的 subscribers
-----------------------------------> 

	static int ad_button_send_vkey(AD_Button_Cmd_Info *data)
	{
		// 消息类型,消息子类型, *data = {cmd = AD_BUTTON_CMD_REPEAT, id = button->button_Id}
		if (sys_event_send(CTRL_MSG_TYPE_VKEY, CTRL_MSG_SUB_TYPE_AD_BUTTON, (uint32_t)data, 0) != 0) {
			COMPONENT_WARN("send vkey error\n");
			return -1;
		}
		return 0;
	}

	void ad_button_long_press(AD_Button_Info *button, uint32_t os_time)
	{
		// long_Press_hold_Time_Ms = 0ms, short_Press_hold_Time_Ms = 10ms; 此时不成立,不进行长按检测
		if (button->long_Press_hold_Time_Ms > button->short_Press_hold_Time_Ms) { 
			uint32_t d_time = ad_button_d_time(AD_Button_Press_Time, os_time);
			if (d_time >= button->long_Press_hold_Time_Ms && AD_Button_Is_Trigger == 0) { // 长按检测优先级低于repeat
				AD_Button_Cmd_Info * p = &AD_Button_Cmd;
				p->cmd = AD_BUTTON_CMD_LONG_PRESS;
				p->id = button->button_Id;
				ad_button_send_vkey(p); -------- 推送按下消息
				AD_Button_Is_Trigger = 1;
			}
		}
	}

########################################
1.4.2 ad_button_short_press(button_info);
########################################

	void ad_button_short_press(AD_Button_Info *button)
	{   
		// 背景逻辑,非长按,非重复
		if (AD_Button_Is_Trigger == 0) {
			uint32_t os_time = OS_JiffiesToMSecs(OS_GetJiffies());
			uint32_t d_time = ad_button_d_time(AD_Button_Press_Time, os_time);
			if (d_time >= button->short_Press_hold_Time_Ms) { // 10 ms s
				AD_Button_Cmd_Info * p = &AD_Button_Cmd;
				p->cmd = AD_BUTTON_CMD_SHORT_PRESS;
				p->id = button->button_Id;
				ad_button_send_vkey(p); -------- 推送按下消息
			}
		}
	}

########################################
1.4.2 ad_button_release_cmd(button_info);
########################################

	void ad_button_release_cmd (AD_Button_Info *button)
	{
		if (AD_Button_Is_Trigger) {
			AD_Button_Cmd_Info * p = &AD_Button_Cmd;
			p->cmd = AD_BUTTON_CMD_RELEASE; --- 释放
			p->id = button->button_Id;
			ad_button_send_vkey(p); -------- 推送按键释放消息
		}
	}







### Rockchip ADC Button Usage and Information For Rockchip-based systems, the ADC (Analog-to-Digital Converter) buttons serve as a way to interface with analog signals that can be converted into digital values for processing by the system. This functionality is particularly useful in scenarios where hardware buttons are used but do not provide direct digital outputs. The configuration of ADC buttons on Rockchip platforms typically involves setting up GPIO pins or dedicated ADC channels to read voltage levels corresponding to button presses. The specific implementation details depend heavily on the particular Rockchip SoC being utilized[^1]. To utilize ADC buttons effectively: - **Initialization**: Ensure the necessary drivers are loaded within the Linux kernel environment supporting Rockchip SoCs. - **Configuration Files**: Modify device tree source files (.dts/.dtsi) associated with your board to define which pin(s) will act as an input channel for the ADC converter when pressed. Example snippet showing how one might configure such settings inside a `.dts` file: ```dts &adc { pinctrl-names = "default"; pinctrl-0 = <&adc_button_pins>; status = "okay"; rockchip-adc-button@0 { compatible = "rockchip,adc-button"; reg = <0>; /* Channel number */ linux,axis = <276>; /* Keycode value */ debounce-interval = <50>; }; }; ``` This code defines an entry under `&adc`, specifying properties like compatibility string (`compatible`) indicating it's related to Rockchip ADC buttons, register address representing the ADC channel index, key event mapping through `linux,axis`, and debouncing time interval.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值