单片机中面向对象的思维
前言:
面向对象的编程方式用在C语言中,听起来是不是很怪。从C语言入门开始,老师就和你说过,C语言是面向过程的,你现在却要用它来实现面向对象操作。可能是太久没谈对象了,想要new一个对象出来解闷。开玩笑的,面向对象自然有面向对象的好处,C语言是面向过程的语言,他的小弟C++是半面向对象,半面向过程的。所以我们使用C语言类比C++的方式实现面向对象的操作。面向对象三大考点,封装,继承,多态。有的时候我们在操作的时候会面临重复的操作,你有想到过面向对象的特点嘛,考虑面向对象的特点是不是就可以避免重复的操作。
一、面向对象特点与面向过程的类比
面向过程的代码就是便于直观的查看整体流程,而面向对象的则需要你能够将你所需要的部分抽象出来,但是不管是整么个方式,程序猿最重要的还是代码能跑就行,不然就得你,我的朋友跑了。C++中对象class(类)包含的的成员属性,成员函数,C语言是没有这个叫法的,但是我们可以通过struct结构体成员类比出来,通过void*万能指针实现成员函数的调用。
二、总结:你就得有抽象思维,只要够抽象就不怕找不到对象。
三、接下来我以按键为例简单展示一下单片机中的面向对象思维。
3.1 先简单说一下按键功能:
我们通常在单片机中用按键作为外部触发事件,按下按键,实现一段操作,松开按键实现另外的操作,每次按键的触发都会存在 “消抖” 问题,每次消抖都是在重复一样的操作,这个时候我就想到了,既然是重复的操作,那干嘛写那么多重复的函数呢,所以我就想到了面向对象的多态。只要创建对象,改写一下他的成员函数就可以了。下面是我原先按键的代码(面向过程的方式消抖)。
/*****************************************************************************函 数 名 : KEY1EintProc功能描述 : KEY1 消抖延时函数,双边沿中断触发中断后开启20ms定时任务输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
void KEY1EintProc(void)
{u8 mkeyStatu = KEY_VALUE_RELEASED;mkeyStatu=KEY1_STATUS_READ;if((mkeyStatu == KEY_VALUE_PRESSED)&&(KEY_VALUE_RELEASED == KEY1LastState)){DelayMs(20);mkeyStatu=KEY1_STATUS_READ;if(KEY_VALUE_PRESSED==mkeyStatu)//KEY1 按下{KEY1LastState=mkeyStatu;KEY1_NOTICE_OUT_EN_0;//按键1被拉低后,拉高对应引脚通知安卓端}}else if((mkeyStatu == KEY_VALUE_RELEASED)&&( KEY_VALUE_PRESSED== KEY1LastState))//松开{DelayMs(20);mkeyStatu=KEY1_STATUS_READ;if(KEY_VALUE_RELEASED==mkeyStatu)//KEY1释放{KEY1LastState=mkeyStatu;KEY1_NOTICE_OUT_EN_1;//按键1被拉高后,拉高对应引脚通知安卓端}}
}
/*****************************************************************************函 数 名 : KEY2EintProc功能描述 : KEY2 消抖延时函数,双边沿中断触发中断后开启20ms定时任务输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
void KEY2EintProc(void)
{u8 mkeyStatu = KEY_VALUE_RELEASED;mkeyStatu=KEY2_STATUS_READ;if((mkeyStatu == KEY_VALUE_PRESSED)&&(KEY_VALUE_RELEASED == KEY2LastState)){DelayMs(20);mkeyStatu=KEY2_STATUS_READ;if(KEY_VALUE_PRESSED==mkeyStatu)//KEY2按下{KEY2LastState=mkeyStatu;KEY2_NOTICE_OUT_0;//拉低}}else if((mkeyStatu == KEY_VALUE_RELEASED)&&( KEY_VALUE_PRESSED== KEY2LastState))//松开{DelayMs(20);mkeyStatu=KEY2_STATUS_READ;if(KEY_VALUE_RELEASED==mkeyStatu){KEY2LastState=mkeyStatu;KEY2_NOTICE_OUT_1;//拉高}}
}
/*****************************************************************************函 数 名 : KEY3EintProc功能描述 : PTT 消抖延时函数,双边沿中断触发中断后开启20ms定时任务输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
void KEY3EintProc(void)
{u8 mkeyStatu = KEY_VALUE_RELEASED;mkeyStatu=KEY3_STATUS_READ;if((mkeyStatu == KEY_VALUE_PRESSED)&&(KEY_VALUE_RELEASED == KEY3LastState)){DelayMs(20);mkeyStatu=KEY3_STATUS_READ;if(KEY_VALUE_PRESSED==mkeyStatu)//KEY3 按下{KEY3LastState=mkeyStatu;KEY3_NOTICE_OUT_0;//拉低,按下KEY3后对应操作}}else if((mkeyStatu == KEY_VALUE_RELEASED)&&( KEY_VALUE_PRESSED== KEY3LastState))//松开{DelayMs(20);mkeyStatu=KEY3_STATUS_READ;if(KEY_VALUE_RELEASED==mkeyStatu)//KEY3 松开相对操作{KEY3LastState=mkeyStatu;KEY3_NOTICE_OUT_1;//拉高}}
}
/*****************************************************************************函 数 名 :KEY4EintProc功能描述 : key4 消抖延时函数,双边沿中断触发中断后开启20ms定时任务输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
void KEY4EintProc(void)
{u8 mkeyStatu = KEY_VALUE_RELEASED;mkeyStatu=KEY4_STATUS_READ;if((mkeyStatu == KEY_VALUE_PRESSED)&&(KEY_VALUE_RELEASED ==KEY4LastState)){DelayMs(20);mkeyStatu=KEY4_STATUS_READ;if(KEY_VALUE_PRESSED==mkeyStatu)//{KEY4LastState=mkeyStatu;KEY4_OUT_0;//拉低}}else if((mkeyStatu == KEY_VALUE_RELEASED)&&( KEY_VALUE_PRESSED==KEY4LastState))//松开{DelayMs(20);mkeyStatu=KEY4_STATUS_READ;if(KEY_VALUE_RELEASED==mkeyStatu)//{KEY4LastState=mkeyStatu;KEY4_OUT_1;//拉高}}
}
可以看出在我消抖后的函数中一直在重复一样的内容,比较繁琐,但简单。
3.2 面向对象的操作,就需要我们将它的不变的内容抽象出来形成统一的模板。然后只需改变成员属性就可以了。下面是面向对象的操作方式。
3.2.1首先是整体对象属性
// 按键设备结构体
typedef struct {uint8_t (*read)(void); // 读取状态函数指针void (*setState)(uint8_t); // 读取状态后操作函数uint8_t lastState; // 上次状态
} KEY_Device;
3.2.2建立对象,以及初始化对象函数
//将消抖函数抽象出来
KEY_Device KEY1;
KEY_Device KEY2;
KEY_Device KEY3;
KEY_Device KEY4;
// 初始化PTT设备
void KEY_Device_init(KEY_Device* dev, uint8_t (*readFunc)(void), void (*setStateFunc)(uint8_t)) {dev->read = readFunc;dev->setState = setStateFunc;dev->lastState = KEY_VALUE_RELEASED;
}
3.2.3设备对象初始化
/*****************************************************************************函 数 名 : KEY_DevicesInit功能描述 : 输入设备面向对象初始化输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
void KEY_DevicesInit(void)
{// 初始化KEY1KEY1.read = readKEY1;KEY1.setState = KEY1PressFunc;KEY1.lastState = KEY_VALUE_RELEASED;// 初始化KEY2KEY2.read = readKEY2;KEY2.setState = KEY2PressFunc;KEY2.lastState = KEY_VALUE_RELEASED;// 初始化KEY3KEY3.read = readKEY3;KEY3.setState = KEY3PressFunc;KEY3.lastState = KEY_VALUE_RELEASED;// 初始化KEY4KEY4.read = readKEY4;KEY4.setState = KEY4Func;KEY4.lastState = KEY_VALUE_RELEASED;
}
3.2.4 抽象出来的通用部分
// 处理KEY事件的通用方法
/*****************************************************************************函 数 名 : KEY_Device_process功能描述 : 消抖函数输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
uint8_t KEY_Device_process(KEY_Device* dev) {uint8_t currentState = dev->read();// 按下检测(上升沿)if ((currentState == KEY_VALUE_PRESSED) && (dev->lastState == KEY_VALUE_RELEASED)) {DelayMs(20); // 防抖延时if (dev->read() == KEY_VALUE_PRESSED) {dev->lastState = KEY_VALUE_PRESSED;dev->setState(KEY_VALUE_PRESSED);return 1; // 返回按下事件}} // 释放检测(下降沿)else if ((currentState == KEY_VALUE_RELEASED) && (dev->lastState == KEY_VALUE_PRESSED)) {DelayMs(20); // 防抖延时if (dev->read() == KEY_VALUE_RELEASED) {dev->lastState = KEY_VALUE_RELEASED;dev->setState(KEY_VALUE_RELEASED);return 2; // 返回释放事件}}return 0; // 无状态变化
}
3.2.5 各自的按键操作函数
/*****************************************************************************函 数 名 : readKEY1功能描述 : 读取引脚函数输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
uint8_t readKEY1(void)
{ return KEY1_STATUS_READ;
}
/*****************************************************************************函 数 名 : KEY1PressFunc功能描述 : 读取按键后操作输入参数 : state:高低电平返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
void KEY1PressFunc(uint8_t state)
{if(state)//KEY1松开后{KEY1_NOTICE_OUT_EN_1;//通知安卓松开KEY1}else{ //KEY1 按下KEY1_NOTICE_OUT_EN_1; //通知安卓按下KEY1}
}
/*****************************************************************************函 数 名 : readKEY2功能描述 : 读取ptt2按键输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
// DMR PTT2 相关函数
uint8_t readKEY2(void)
{ return KEY2_STATUS_READ;
}
/*****************************************************************************函 数 名 : KEY_Device_process功能描述 : 读到状态后操作输入参数 : state:高低电平返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
void KEY2PressFunc(uint8_t state)
{if(state)//Ptt1松开后{KEY2_NOTICE_OUT_EN_1;//通知安卓松开KEY2//PA4 DMR 停止发射}else{ //ptt2 按下KEY2_NOTICE_OUT_EN_0; //通知安卓按下KEY2 //PA4 发射}
}
/*****************************************************************************函 数 名 : readKEY3功能描述 : 读取M6状态输入参数 : void返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
// M6 PTT 相关函数
uint8_t readKEY3(void)
{ return KEY3_STATUS_READ;
}
/*****************************************************************************函 数 名 : KEY3PressFunc功能描述 : 消抖函数输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
void KEY3PressFunc(uint8_t state)
{if(state)//KYE3松开后{KEY3_NOTICE_OUT_1; //}else{ //KYE3 按下KEY3_NOTICE_OUT_0;//PA4 发射}
}
/*****************************************************************************函 数 名 : KEY_Device_process功能描述 : 消抖函数输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
// KEY4 相关函数
uint8_t readKEY4(void)
{ return KEY4_STATUS_READ;
}
/*****************************************************************************函 数 名 : KEY_Device_process功能描述 : 消抖函数输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
void KEY4Func(uint8_t state)
{//KEY4按下后对应操作
}
/*****************************************************************************函 数 名 : KEY_Device_process功能描述 : 消抖函数输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
// 主处理函数
3.2.6 主函数
/*****************************************************************************函 数 名 : KEY_Device_process功能描述 : 消抖函数输入参数 : u8 power返 回 值 : void作 者 : Bright创建日期 : 20250607
*****************************************************************************/
// 主处理函数
uint8_t ExtiProc(uint8_t pttPort)
{// 使用switch处理不同端口switch(pttPort) {case KEY1_Port:return KEY_Device_process(&KEY1);case KEY2_Port:return KEY_Device_process(&KEY2);case KEY3_Port:return KEY_Device_process(&KEY3);case KEY4Statu_Port:return KEY_Device_process(&KEY4);default:return 0; // 未知端口}
}return KEY_Device_process(&KEY3);case KEY4Statu_Port:return KEY_Device_process(&KEY4);default:return 0; // 未知端口}
}
四、总体上只要找到找到函数的特点,就能够利用面向对象的思想来实现。面向对象的方式只要将抽象部分写完,后续只需要新建对象,写各自的函数就可以了,整体上看起来比较清爽。
但无论是是用哪种方式,只要能够跑就行了,怎么简单怎么来,公司怎么要求怎么来,希望以上内容对大家有帮助。