面向对象思想编写单片机程序其实很简单!

发布者:翠绿山水最新更新时间:2023-03-24 来源: zhihu关键字:单片机  程序 手机看文章 扫描二维码
随时随地手机看文章

摘要:在看别人单片机程序时,你也许是崩溃的,因为全局变量满天飞,不知道哪个在哪用了,哪个表示什么,而且编写极其不规范。自己写单片机程序时,也许你也是崩溃的。总感觉重新开启一个项目,之前的写过相似的代码也无法使用,得重新敲,代码重用度不高,编程效率低下,代码无法积累。而且感觉写这个代码没有思想,没有灵魂,没有框架,只是一个一个功能代码的堆砌,很空泛。


那么这个时候,你也许应该在单片机中引入面向对象的思想了,使代码更规范。

一、单片机程序框架

1、轮流执行

int main (void)
{
 while(1)
 {
  sing();
  dance();
  play();
 }
}

函数sing执行的时间比较长的话,函数dance就不能很快的被执行。任何一个函数死掉的话就会影响整个系统。


2、前后台

在使用 51、AVR、STM32 单片机裸机的时候一般都是在main函数里面用while(1)做一个大循环来完成所有的处理,即应用程序是一个无限的循环,循环中调用相应的函数完成所需的处理。有时候我们也需要中断中完成一些处理。相对于多任务系统而言,这个就是单任务系统,也称作前后台系统,中断服务函数作为前台程序,大循环while(1)作为后台程序。

对应的编程代码大概是这样的:

void EXTI_IRQHandler()
{
    flag = 1;
}
int main (void)
{
    while(1)
    {
        if (flag = 1)
        {
            do_something();
            flag = 0;
        }
    }
}

有什么问题?

前后台系统的实时性差,前后台系统各个任务(应用程序)都是排队等着轮流执行,不管你这个程序现在有多紧急,没轮到你就只能等着!相当于所有任务(应用程序)的优先级都是一样的。但是前后台系统简单啊,资源消耗也少啊!在稍微大一点的嵌入式应用中前后台系统就明显力不从心了。

3、多任务

void first_task()
{
    while (1)
    {
        if(has_data())
            put_data();
    }
}
void second_task()
{
    while (1)
    {
        if(get_data())
            do_something();
    }
}

int main(void)
{
    create_task(first_task);
    create_task(second_task);
    start_scheduler();
}

多任务系统会把一个大问题“分而治之”,把大任务划分成很多个小问题,逐步的把小任务解决掉,大任务也就随之解决了,这些任务是并发处理的。注意,并不是说同一时刻一起执行很多个任务,而是由于每个任务执行的时间很短,导致看起来像是同一时刻执行了很多个任务一样。

二、执行的程序怎么写?

以按键为例,点亮一个小灯!

1.常规写法

int mian(void)
{
    while (1)
    {
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) == GPIO_PIN_SET)
        {
            printf("按键按下rn");
        }
    }
}

2.面向对象的写法

首先我们把每一个按键都看成一个对象,既然是对象就肯定有属性和行为,比如我们定义一个学生,那么这个学生有什么属性呢?

肯定有姓名、年龄、身高、体重对吧,这些是一些基本的属性,我们可以用一些单独的变量来定义它,比如:

typedef struct
{
 uint8_t  *name; //姓名(变量)
 uint8_t  age;   //年龄(变量)
  uint8_t  height;//身高(变量)
  uint8_t  weight;//体重(变量)
} student_t;

但是一个学生还有很多行为对吧,它会唱歌、跳舞、打篮球、也会关注果果小师弟的公众号对吧,于是我们就可以这样定义:

typedef struct
{
 uint8_t  *name;  //姓名(变量)
 uint8_t  age;    //年龄(变量)
  uint8_t  height; //身高(变量)
  uint8_t  weight; //体重(变量)
 void (*Sing_song)(void); //会唱歌(函数指针)
 void (*Dance_latin)(void); //会跳舞(函数指针)
  void (*Wechat_zhiguoxin)(void); //会关注果果的公众号(函数指针)
} student_t;

好了,这里我们提到了函数指针,所以就来说一说函数指针。

函数指针,顾名思义它就是一个指针,只不过它是一个函数指针,所以指向的是一个函数。类比一般的变量指针,指针变量,实质上是一个变量,只不过这个变量存放的是一个地址,在32位单片机中,任何类型的指针变量都存放的是一个大小为4字节的地址。

重要的话说三遍!牢记在心!!!为什要记住函数指针,因为在单片机面向对象编程中,结构体的成员不是变量就是函数指针这两种类型。变量就不用说了,函数指针理解就好。

其实函数指针可以类比一般的变量,看下面:

int   a; < = > void Sing_song(void);
int * p; < = > void (*zhiguoxin)(void);
p=&a;   < = > zhiguoxin = &Sing_song;
  1. 左边走义变量a,右边定义函数Sing_song;

  2. 左边定义int指针,右边定义函数指针;

  3. 左边赋值指针,右边赋值函数指针;

那么函数指针怎么用呢?我们还是以单片机为例,把按键类比为一个对象,这个按键有按键标志位,有长按或者短按,按键还有行为:按键初始化、按键循环检测等。

所以我们创建下面这样一个结构体,当然这个结构体不一定仅仅有这些变量和函数,这完全取决于你自己的定义,你想怎么定义就怎么定义,你甚至可以定义按键的颜色都。

typedef struct
{
 uint8_t  KEY_Flag;  //标志位(变量)
 uint8_t  Click;//按下(变量)
 void (*KEY_Init)(void); //按键初始化(函数指针)
 void (*KEY_Detect)(void); //按键检测(函数指针)
} KEY_t;

现在已经定义了KEY_t这种类型的结构体,处理器还没有分配给这个结构体内存,因为我们只是声明这样一个类型,而类型是不占用内存的,只有我们定义对应的结构体类型的变量时才会在占用内存空间。

那么怎么定义一个结构体类型的变量呢?

KEY_t   KEY1;

然后就要初始化结构体的成员变量了。

KEY_t  KEY1 = {0,0,KEY_init,KEY_detect};

这里要注意了现在结构体有四个成员,前两个普通的变量,我们初始化为0,还有两个函数指针,我们是不是要把我们想写得函数的函数名字放在这里啊。

那么聪明的你肯定知道还要定义KEY_init();和KEY_detect();这两个函数。这两个函数可以这样写。

static void KEY_init()
{
  GPIO_InitTypeDef GPIO_InitStruct;
  GPIO_InitStruct.Pin = GPIO_PIN_3;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
static void KEY_detect() 
{
 uint8_t i = 0; 
 if(KEY1.KEY_Flag == 1)
 {
  HAL_Delay(100);
  if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3) == GPIO_PIN_SET)
  {
    printf("按键按下rn");
  }
  KEY1.KEY_Flag = 0;
 }
}

好了具体函数中的代码我就不需要解释了。这样一个按键的对象我们就定义好了,这个按键我们赋予了"他"生命,有属性(变量)有行为(函数)。

这样我们在主函数就可以这样的调用,来实现相应的功能了。按键使用了中断,这里并没有讲解。

void main(void)
{
  KEY1.KEY_Init();//初始化按键
  while(1) 
   {  
  KEY1.KEY_Detect();//按键检测
 }
}

如果理解了这些,那么面向对象的精髓你基本已经掌握了,接下来就是不断地去练习和实践了。

三、为什么要面向对象?

我们知道,现有的编程范式主要是:面向过程编程、面向对象编程、函数式编程。

对于流程清晰的简单程序,一般只有一条流程主线,很容易被划分成顺序执行的几个步骤,面向对象编程和面向过程编程没有太大差别,并且面向过程编程常常比面向对象编程更加直观高效。

但当我们面对一个大型的复杂程序,由于其错综复杂的流程和交互关系,很难将其简单地拆分成一条主线串成的简单步骤,而通常表现为一个网状关系结构。这个时候,面向过程编程的这种流程化和线性化的思维方式就会显得比较吃力,而面向对象编程的优势就比较明显了。

面向对象编程风格的代码更容易复用、扩展和维护、更高级、更人性化、更适合大规模复杂程序的开发。在Linux中就是用的面向对象编程,里面有很多的结构体、指针、链表等等。如果还没有接触到面向对象编程只能说明你做的东西还不够复杂。

在单片机举一个例子,一块开发板可能会适配不同的屏幕:

一块板子,三个屏幕

那么每一块板子肯定有不同的代码适配,在程序中我们可以读出屏幕的ID,然后通过if判断来执行不同的指令,就行这样。

果果小师弟

如果使用面向对象编程,那么就可以这样写代码。

typedef struct lcd{
 uint8_t type;
 void (*LCD_Init)(void)
}lcd_t, *plcd_t;

int Read_id()
{
 /* 0: LCDA
  * 1: LCDB
  */
 return 0; 
}

int Get_Lcd_Type(void)
{
 return Read_id();
}

void LCDA_Init(void)//屏幕A初始化
{
    LCD_WR_REG(0xCF);  
    LCD_WR_DATA(0x00); 
    LCD_WR_DATA(0xC1); 
    LCD_WR_DATA(0X30); 
}

void LCDB_Init(void)//屏幕B初始化
{
    LCD_WR_REG(0X11);
    delay_ms(20);
    LCD_WR_REG(0XD0);
    LCD_WR_DATA(0X07); 
}

lcd_t openedv_com_lcds[] = {
 {0, LCDA_Init},
 {1, LCDB_Init},
};

plcd_t get_lcd(void)//获取到屏幕类型
{
 int type = Get_Lcd_Type();
 return &openedv_com_lcds[type];
}

int main(void )
{
 plcd_t lcd; 
 lcd = get_lcd();//获取到屏幕类型
 lcd-> LCD_Init();//初始化对应屏幕
 while (1)
  {} 
}

这里只是伪代码处理办法,原理就和上面所讲的一样,在结构体中使用变量和函数。

到这里你应该掌握了面向对象得单片机编程方法,一起来试验几个例子:

LED灯

typedef struct
{ 
 void (*LED_ON)(uint8_t LED_Num);     //打开
 void (*LED_OFF)(uint8_t LED_Num);    //关闭
 void (*LED_Flip)(uint8_t LED_Num);   //翻转
} LED_t;

按键KEY

typedef struct
{
 uint8_t  KEY_Flag;        //标志位(变量)
 uint8_t  Click;           //按下(变量)
 void (*KEY_Init)(void);   //按键初始化(函数指针)
 void (*KEY_Detect)(void); //按键检测(函数指针)
} KEY_t;

蜂鸣器BEEP

typedef struct
{
 uint8_t Status;      //状态
 void (*ON)(void);     //打开
 void (*OFF)(void);    //关闭
} BEEP_t;

串口UART

typedef struct
{
 USART_TypeDef *uart;/* STM32内部串口设备指针 */
 uint8_t *pTxBuf;   /* 发送缓冲区 */
 uint8_t *pRxBuf;   /* 接收缓冲区 */
 
 uint16_t usTxBufSize;  /* 发送缓冲区大小 */
 uint16_t usRxBufSize;  /* 接收缓冲区大小 */
 
  uint16_t usTxWrite; /* 发送缓冲区写指针 */
  uint16_t usTxRead;  /* 发送缓冲区读指针 */
  uint16_t usTxCount; /* 等待发送的数据个数 */

  uint16_t usRxWrite; /* 接收缓冲区写指针 */
  uint16_t usRxRead;  /* 接收缓冲区读指针 */
  uint16_t usRxCount; /* 还未读取的新数据个数 */
  
  void (*RS485_Set_SendMode)(void);  //RS-485接口设置为发送模式
  void (*RS485_Set_RecMode)(void);   //RS-485接口设置为接收模式
}UART_T;

面向对象的单片机编程


关键字:单片机  程序 引用地址:面向对象思想编写单片机程序其实很简单!

上一篇:单片机开发的正确姿势
下一篇:针对单片机开发的轻量级OTA组件

推荐阅读最新更新时间:2024-11-03 13:48

单片机多中断处理技术研究
1.引 言: 中断技术的应用大大提高了CPU的有效使用率,有效提高了资源的利用率。中断的性能同时也就成了衡量芯片性能的标准。新开发的芯片增加了很多的中断源,如 PIC16F877的中断源已经达到14个,可谓相当丰富;但同时也带来了一些难题:如此多的中断源在处理时很容易产生中断冲突,如何有效的处理中断到达时的时序,其算法应该如何实现成了首先需要解决的问题 2.中断处理技术 为了解决多中断带来的问题,有必要清楚单个中断到达时的处理技术,首先简要介绍中断的基本原理:计算机在执行某一程序过程中,由于计算机系统内外原因,而必须中止原程序的执行,转去执行相应的处理程序,待处理结束后再回来继续执行中止程序。当只有一个事件被响应,进入中断执
[单片机]
<font color='red'>单片机</font>多中断处理技术研究
基于STM32单片机ADC连续采集和DMA循环转换
描述:用ADC连续采集11路模拟信号,并由DMA传输到内存。ADC配置为扫描并且连续转换模式,ADC的时钟配置为12MHZ。在每次转换结束后,由DMA循环将转换的数据传输到内存中。ADC可以连续采集N次求平均值。最后通过串口传输出最后转换的结果。 程序如下: #i nclude “stm32f10x.h” //这个头文件包括STM32F10x所有外围寄存器、位、内存映射的定义 #i nclude “eval.h” //头文件(包括串口、按键、LED的函数声明) #i nclude “SysTickDelay.h” #i nclude “UART_INTERFACE.h” #i nclude #define N 50 //每通道
[单片机]
汽车mcu的功能和作用 ECU和MCU的关系
 汽车mcu的功能和作用   汽车MCU(Microcontroller Unit,微控制器单元)是指安装在汽车内部的一种嵌入式微控制器芯片,用于控制和管理汽车各个系统及其相关设备。汽车MCU的功能和作用如下:   控制和管理车辆电子系统:汽车MCU作为整个车辆电子系统的中枢处理器,负责控制和管理车辆各个系统的功能和操作。它可以与引擎管理系统、传输控制系统、底盘控制系统等其他电子控制单元进行通信,并对这些系统进行协调和调度。   数据采集和信号处理:汽车MCU能够接收来自各个传感器的数据,例如发动机转速、车速、轮胎压力、气囊状态等,然后进行实时的数据采集和信号处理。它可以对这些数据进行分析和计算,为其他系统提供必要的信息和参考
[嵌入式]
单片机+1602液晶数字时钟程序
本程序由好几个头文件组成,都在下面,你可以复制代码并保存为独立的文件 头文件STC12C5A.H下载: http://www.51hei.com/mcu/2564.html 程序: 首先是主程序: #include STC12C5A.H #include STDIO.h #include LCD1602.h #include interrupt.h void main() { LCD_init(); interrupts_init(); LCD_disp_cher(0, 1, 784729514 ); while(1) { sprintf(dsa, %d:%d:%d ,hour,minute,sec);
[单片机]
块设备驱动程序之nandflash——基本框架
我们先查看内核的启动信息,以搞清楚从哪个文件着手来分析: S3C24XX NAND Driver, (c) 2004 Simtec Electronics s3c2440-nand s3c2440-nand: Tacls=3, 30ns Twrph0=7 70ns, Twrph1=3 30ns NAND device: Manufacturer ID: 0xec, Chip ID: 0xda (Samsung NAND 256MiB 3,3V 8-bit) Scanning device for bad blocks Bad eraseblock 408 at 0x03300000 Bad eraseblock 441 at
[单片机]
基于单片机的继电器控制
一、实验目的 掌握用继电器控制的基本方法和编程。 二、实验内容 1、 实验原理图: 2、实验内容 利用P1口输出高低电平,控制继电器的开合,以实现对外部装置的控制。 3、预备知识 现代自动化控制设备都存在一个电子与电气电路的互相联结问题,一方面要使电子电路的控制信号能够控制电气电路的执行元件(电动机、电磁铁、电灯等),另一方面又要为电子电路和电气电路提供良好的电隔离,以保护电子电路和人身的安全,电子继电器便能完成这一桥梁作用。 本实验采用JZC—23F型继电器,其控制电压为5V。继电器电路中一般要在继电器的线圈两头加一个二极管以吸收继电器线圈断电时产生的反电势,防止干扰。 三、程序 程序清单: O
[单片机]
基于<font color='red'>单片机</font>的继电器控制
基于硅压式传感器和MSP430F149单片机的电子血压计设计
  1 引言   测量血压的传统仪器是机械式水银血压计,电子血压计近几年才在市场上出现。电子血压计与传统血压计相比,虽然操作简单、使用方便,但准确性、稳定性往往不太理想。本设计力求准确、稳定,以适用于老年人或病人随时监测自己血压情况及临床医学检测。   2 系统的硬件设计   本设计采用Motorola公司的MPX53GC硅压式传感器和TI公司MSP430F149单片机为主要器件, 构成电子血压计,系统构成如图1。系统由MCU、 传感器、LCD液晶显示器、操作面板、充放气控制 电路、气泵和气阀、蜂鸣器、存贮器、电源等部分 构成。      2.1 微处理器的选择   单片机是整个系统的大脑,它不仅要对系统进 行
[单片机]
基于硅压式传感器和MSP430F149<font color='red'>单片机</font>的电子血压计设计
基于STM32单片机的防丢失手环系统设计
一.系统设计 通过STM32单片机进行主控,两个NRF024L01进行数据的发射和接收,发射端的位置信息首先会在显示屏上进行显示,并且会通过无线模块将位置信息传输到接收端的显示屏上进行显示。系统内为了实现防丢功能设置了按键报警的功能,在发射端按下按键,在接收端就会进行报警,另外还可以通过按键设置活动区域,当使用者超出活动范围,在接收端也会进行报警。 图1 系统框图 二.硬件设计 防丢失手环系统内主要有单片机最小系统电路、GPS定位电路、显示电路、发射和接收电路、报警电路。 图2 硬件电路 三.软件设计 系统的软件逻辑清晰,GPS模块采集的位置数据会在显示屏上进行显示,并通过无线模块将数据上传到接收端进行显示,判断求
[单片机]
基于STM32<font color='red'>单片机</font>的防丢失手环系统设计
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved