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); -------- 推送按键释放消息
		}
	}







  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值