《结构化编程》— LED灯为例

本文通过LED灯代码演示如何实现结构化编程,提高代码复用和可维护性。通过剖析main.c、Hardware_Init、System和LED相关的.c文件,展示如何通过定义结构体和函数指针降低耦合,提升代码灵活性和开发效率。
摘要由CSDN通过智能技术生成

        你有过看很久以前项目代码看不太懂的情况吗?
        你有过做一个项目就要从零重构代码的烦恼吗?
        你有过那种遇到项目后无从入手编写的困扰吗?
        ……
        诸君且看,我是如何用一个LED灯代码去做结构化编程的,希望这种编程思想,能解决你上面遇到的几种情况。

        好的程序不在与你完成了你什么样的功能,更不在于你写了多少代码,而是在于你这个代码的复用性有多高,移植性有多强。减少代码耦合性,使程序应用层与底层剥离开来我觉得很有必要,这样做你的代码会很有利于维护,也大大的方便了自己的项目开发,缩短了自己的开发周期,可维护性强,这些都是结构化编程的优点。以后自己写过的每一个项目代码,都将成为自己最好的参考例程。

        今天我就用点亮一个LED灯的方法来演示一下这种编程思想的好处。

        我们先从main.c文件开始着手讲解,代码如下:

#include "main.h"

/**************************************
	* @name   main
	* @brief  主函数
	* @param  None	
	* @retval None      
**************************************/
int main()
{
	Hardware_Init.Sys_Init();
	while(1)
	{
		System.Run();
	}
}

        我们的主文件特别简单,我们逐行进行分析:
        程序第 1 行: #include "main.h" ,这是我们自定义的一个公共头文件,下面用到的所有的头文件都会放在这里面,方便其他.c文件调用。
        程序第 11 行:Hardware_Init.Sys_Init(); 这是我们写的一个程序初始函数指针,作用就是进行程序的初始化工作,后面会讲到。
        程序第 14 行:System.Run(); 这是我们写的一个程序运行函数指针,作用就是把本来应该写在主程序while(1)里面的程序单独抽出来放在另一个.c文件里,降低程序耦合性,方便移植代码,也会让主程序看起来更简洁一些。

        接下来我们看一下程序第 11 行提到的Hardware_Init.Sys_Init();,此函数指针来自何处Sys_init.h文件。

#ifndef __Sys_init_H__
#define __Sys_init_H__

#include "main.h"

//定义结构体类型
typedef struct
{
  void (*Sys_Init)(void);            //系统初始化
} Hardware_Init_t;

extern Hardware_Init_t Hardware_Init;

#endif

        Sys_init.h文件里看出,我们定义了一个结构体类型Hardware_Init_t,结构体里面定义了指针函数void (*Sys_Init)(void)。接下来我们会在Sys_init.c文件里书写这个指针函数。

#include <main.h>

static void Sys_Init(void);            //系统初始化

Hardware_Init_t Hardware_Init = 
{
	Sys_Init
};

/**************************************
	* @name   Sys_Init
	* @brief  系统初始化
	* @param  None
	* @retval None      
**************************************/
static void Sys_Init(void)
{	
	LED.LED_Fun(LED1,LED_ON);
	Public.Delay_ms(200);
	LED.LED_Fun(LED1,LED_OFF);
	
	LED.LED_Fun(LED2,LED_ON);
	Public.Delay_ms(200);
	LED.LED_Fun(LED2,LED_OFF);
	
	LED.LED_Fun(LED3,LED_ON);
	Public.Delay_ms(200);
	LED.LED_Fun(LED3,LED_OFF);
}

        程序第 5-8 行:我们重新定义了一个类型为Hardware_Init_t的结构体Hardware_Init,并且对里面的指针函数进行了初始化。
        程序第 16 行:对Sys_Init()函数进行书写,并定义为static类型,使程序只在本文件中有效,外部要想使用必须通过结构体调用。
        对于程序18-28行:我们用到了Public.c文件和led.c文件,这些我们下面也会讲。先从简单一点的Public.c文件开始吧,首先看一下它的头文件Public.h。

#ifndef __PUBLIC_H_
#define __PUBLIC_H_

#include "main.h"

//数据类型重定义
typedef	signed   char      sint8_t;
typedef signed   short int sint16_t;
typedef signed   long  int sint32_t;

typedef unsigned char      uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned long  int uint32_t;

//定义结构体类型
typedef struct
{
	void (*Delay_ms)(uint16_t);            //ms延时函数
} Public_t;

//定义外部变量
extern Public_t  Public;

#endif

        程序 7-13 行:我们对需要常用的一些数据类型做了重定义,更有利于我们后面编写代码。
        程序 16-19 行:我们定义了一个结构体类型Public_t,结构体里面定义了指针函数void (*Delay_ms)(uint16_t)。接下来我们会在Public.c文件里书写这个指针函数。

#include <main.h>

static void Delay_ms(uint16_t);               //ms延时函数

Public_t  Public = 
{
	Delay_ms,
};

/*************************************
	* @name   Delay_ms
	* @brief  毫秒延时函数
	* @param  ms -> 需要延时的时间
	* @retval None      
*************************************/
static void Delay_ms(uint16_t ms)
{
	uint16_t i,j;
	for(i=0;i<ms;i++)
		for(j=0;j<990;j++);
}

        程序第 5-8 行:我们重新定义了一个类型为Public_t的结构体Public,并且对里面的指针函数进行了初始化。
        程序第 16 行:对Delay_ms()函数进行书写,并定义为static类型,使程序只在本文件中有效,外部要想使用必须通过结构体调用。

        我们再来看一下led.c文件,还是先是从它的头文件led.h开始。

#ifndef __LED_H__
#define __LED_H__

#include "main.h"

#define   MCU_Run_LED_ON 	     (bit)0
#define   MCU_Run_LED_OFF        (bit)1

sbit LED1_Port = P1^0;
sbit LED2_Port = P1^1;
sbit LED3_Port = P1^2;

//定义枚举类型
typedef enum
{
	LED1 = (uint8_t)0x01,
	LED2 = (uint8_t)0x02,
	LED3 = (uint8_t)0x03,
}LED_Num_t;

//定义结构体类型
typedef struct
{
	void (*LED_Fun)(uint8_t,void(*Callback)(uint8_t)); 
}LED_t;

//定义外部变量
extern LED_t LED;

//定义外部函数模型
extern void LED_ON(uint8_t); 
extern void LED_OFF(uint8_t);
extern void LED_Flip(uint8_t);
#endif

        相信有了前面几个代码文件的理解,我们在看到这个led.c文件时会更容易理解一些了。
        程序 6-7 行:
        #define   MCU_Run_LED_ON          (bit)0
        #define   MCU_Run_LED_OFF        (bit)1

        我们首先对定义两个宏常量MCU_Run_LED_ON  MCU_Run_LED_OFF 这么做也是考虑到我们代码的移植和更改,如果下次遇到高电平为打开LED灯,我们只需要更改定义宏常量时后面的电平属性就行了,而不必对代码进行大规模改动,节省时间。

        程序 14 - 19 行:
        typedef enum
        {
            LED1 = (uint8_t)0x01,
            LED2 = (uint8_t)0x02,
            LED3 = (uint8_t)0x03,
        }LED_Num_t;

        我们定义了一个枚举类型LED_Num_t,里面有三个枚举成员,也就是我们将要控制的三个LED灯。
        程序 22-25 行:
        typedef struct
        {
            void (*LED_Fun)(uint8_t,void(*Callback)(uint8_t)); 
        }LED_t;

        我们定义了一个结构体类型LED_t,里面定义了一个回调函数成员,这个回调函数也是这个程序里面的难点和重点,理解以后将会很有利于我们以后的编程。

        接下来我们就直接看一下led.c文件里面的内容。

#include "main.h"

static void LED_Fun(uint8_t,void(*Callback)(uint8_t)); 

LED_t LED =
{
	LED_Fun,
};

/**************************************
	* @name   LED_Fun
	* @brief  LED功能函数,中间虚拟函数
	* @param  Num -> 编号,Callback ->回调函数指针
	* @retval None      
**************************************/
void LED_Fun(uint8_t LED_Num,void(*Callback)(uint8_t))
{
	(*Callback)(LED_Num);
}

/**************************************
	* @name   LED_ON
	* @brief  打开LED
	* @param  Num -> 编号
	* @retval None      
**************************************/
void LED_ON(uint8_t LED_Num)     //打开
{
	switch(LED_Num)
	{
		case LED1: LED1_Port = MCU_Run_LED_ON; break;
		case LED2: LED2_Port = MCU_Run_LED_ON; break;
		case LED3: LED3_Port = MCU_Run_LED_ON; break;
		default: LED1_Port = MCU_Run_LED_ON; break;
	}
}
/**************************************
	* @name   LED_OFF
	* @brief  关闭LED
	* @param  Num -> 编号
	* @retval None      
**************************************/
void LED_OFF(uint8_t LED_Num)    //关闭
{
	switch(LED_Num)
	{
		case LED1: LED1_Port = MCU_Run_LED_OFF; break;
		case LED2: LED2_Port = MCU_Run_LED_OFF; break;
		case LED3: LED3_Port = MCU_Run_LED_OFF; break;
		default: LED1_Port = MCU_Run_LED_OFF; break;
	}
}
/**************************************
	* @name   LED_Flip
	* @brief  取反LED
	* @param  Num -> 编号
	* @retval None      
**************************************/
void LED_Flip(uint8_t LED_Num)    //翻转
{
	//条件选择语句
	switch(LED_Num)
	{
		case LED1: 
			if(LED1_Port == MCU_Run_LED_ON) LED1_Port = MCU_Run_LED_OFF;
			else LED1_Port = MCU_Run_LED_ON;break;
		case LED2: 
			if(LED2_Port == MCU_Run_LED_ON) LED2_Port = MCU_Run_LED_OFF;
			else LED2_Port = MCU_Run_LED_ON;break;
		case LED3: 
			if(LED3_Port == MCU_Run_LED_ON) LED3_Port = MCU_Run_LED_OFF;
			else LED3_Port = MCU_Run_LED_ON;break;
		default: LED1_Port = MCU_Run_LED_OFF; break;
	}
}

        程序第 5-8 行:我们重新定义了一个类型为LED_t 的结构体LED,并且对里面的指针函数进行了初始化。
        程序第 16-19 行:
        /**************************************
    * @name   LED_Fun
    * @brief  LED功能函数,中间虚拟函数
    * @param  Num -> 编号,Callback ->回调函数指针
    * @retval None      
        **************************************/
        void LED_Fun(uint8_t LED_Num,void(*Callback)(uint8_t))
        {
            (*Callback)(LED_Num);
        }

        我们定义了一个名称为LED_Fun的函数,里面有两个参数,其中第二个函数为回调函数,它的执行原理是把第一个参数同样传给第二个回调函数做为参数执行的,传的第一个参数是LED灯的编号,第二个参数是LED将要执行的动作。这个如果不理解也没关系,你只需要对他的运行机制有个了解就行,下面我们对程序调用时会再深入讲解。
        程序第 27-36 行:
        /**************************************
            * @name   LED_ON
            * @brief  打开LED
            * @param  Num -> 编号
            * @retval None      
        **************************************/
        void LED_ON(uint8_t LED_Num)     //打开
        {
            switch(LED_Num)
            {
                case LED1: LED1_Port = MCU_Run_LED_ON; break;
                case LED2: LED2_Port = MCU_Run_LED_ON; break;
                case LED3: LED3_Port = MCU_Run_LED_ON; break;
                default: LED1_Port = MCU_Run_LED_ON; break;
            }
        }

        我们定义了一个LED_ON函数,功能是打开某个LED灯,传进去的参数LED_Num就是LED灯的编号,当参数为第几个灯时,我们就把相应的LED打开就行,例如当传进去的参数为LED1时,就执行case LED1: LED1_Port = MCU_Run_LED_ON; break; 我们把LED1端口设置为低电平。因为我们之前在led.h文件已经定义了 LED_Port 为 P10 端口,MCU_Run_LED_ON 为低电平。

        理解LED_ON()打开函数后,那么我们也应该会明白LED_OFF()关闭函数该怎么来做,LED_Flip()为电平翻转函数,即我们先判断当前LED灯端口电平是否为低电平,如果是就让它转换为高电平,否则转换为低电平。

        回过头来,我们来看一下 main.c 文件程序第 14 行提到的 System.Run(); ,我们一样先从System.h开始。

#ifndef __System_H__
#define __System_H__

//定义结构体类型
typedef struct
{
	void (*Run)(void);         //系统运行
} System_t;

extern System_t  System;

#endif

        程序第 5-8 行:我们定义了一个结构体类型System_t ,结构体里面定义了指针函数void (*Run)(void)。接下来我们会在System.c文件里书写这个指针函数。

#include <main.h>
   
static void Run(void);        //系统运行

System_t  System = 
{
	Run,
};

/**************************************
	* @name   Run
	* @brief  系统运行
	* @param  None
	* @retval None      
**************************************/
static void Run()
{
	LED.LED_Fun(LED1,LED_Flip);
	Public.Delay_ms(500);
}

        程序第 5-8 行:我们重新定义了一个类型为 System_t 的结构体System,并且对里面的指针函数进行了初始化。
        程序第 16-20 行:我们定义了一个静态函数static void Run(),程序调用之前定义的结构体LED,执行指针函数LED_Fun,之前我们提到过,LED_Fun()函数里面是有两个参数的,第一个参数为LED灯的编号,第二个参数为一个带有参数的回调函数,并且会把LED_Fun()的第一个参数传给这个回调函数当形参。所以在看这行代码 LED.LED_Fun(LED1,LED_Flip);它的意思就是执行LED_Fun运行函数,让LED1灯的状态进行翻转。有关对回调函数的理解,大家可以参考C 语言回调函数详解这篇文章进行进一步理解。Public.Delay_ms(500); 这条语句是我们前面写的一个Public.c文件,通过Public结构体来调用Delay_ms()函数,参数为500,即延时为500ms。

        最后让我们从头梳理一下程序的执行过程,一样是从maic.c文件开始。

#include "main.h"

/**************************************
	* @name   main
	* @brief  主函数
	* @param  None	
	* @retval None      
**************************************/
int main()
{
	Hardware_Init.Sys_Init();
	while(1)
	{
		System.Run();
	}
}

        首先我们进入 int main() ,然后执行 Hardware_Init.Sys_Init(); 通过 Hardware_Init 结构体,我们进入到 Sys_init.c 文件,执行 Sys_Iiit() 函数

static void Sys_Init(void)
{	
	LED.LED_Fun(LED1,LED_ON);
	Public.Delay_ms(200);
	LED.LED_Fun(LED1,LED_OFF);
	
	LED.LED_Fun(LED2,LED_ON);
	Public.Delay_ms(200);
	LED.LED_Fun(LED2,LED_OFF);
	
	LED.LED_Fun(LED3,LED_ON);
	Public.Delay_ms(200);
	LED.LED_Fun(LED3,LED_OFF);
}

        执行完初始化以后我们会再次回到 main.c 文件,然后执行 System.Run();

#include "main.h"

/**************************************
	* @name   main
	* @brief  主函数
	* @param  None	
	* @retval None      
**************************************/
int main()
{
	Hardware_Init.Sys_Init();
	while(1)
	{
		System.Run();
	}
}

        通过 System 结构体我们进入到 System.c 文件,去执行 System.Run 指向的 Run() 函数。

#include <main.h>
   
static void Run(void);        //系统运行

System_t  System = 
{
	Run,
};

/**************************************
	* @name   Run
	* @brief  系统运行
	* @param  None
	* @retval None      
**************************************/
static void Run()
{
	LED.LED_Fun(LED1,LED_Flip);
	Public.Delay_ms(500);
}

        本文章工程源文件已上传到资源,需要的朋友可点击 下载 

        最后感谢大家能阅读于此,笔者水平有限,文中如有错误或不妥之处,欢迎指正与交流,感激不尽。还望小伙伴多多支持,多多指教!

USB 可编程RGB LED灯条显示控制器描述: 该电路设计主要应用于可编程RGD LED灯条,控制板上自带电源插孔。为可编程LED灯条提供便捷的USB控制。USB 可编程RGB LED灯条控制器与Linux(包括Raspberry Pi),Mac和Windows平台兼容,并且能够以高帧率驱动多达700个LED RGB彩灯。RGB LED灯条显示基于Python的软件库BiblioPixel支持AllPixel Mini,实现对RGBLED灯条动画创建和控制。 USB 可编程RGB LED灯条显示控制器特性和支持的LED芯片组: 所有流行的LED条芯片组,包括:LPD8806,WS2801,WS2811 / WS2812(NeoPixel),WS2811 400kHz,APA102(DotStar),TM1809,TM1803,TM1804,UCS1903,P9813,SM16716,LPD1886 使用方便。无需担心芯片组协议,电平转换器或电源。只需插入电源,连接您的LED,设置芯片组代码,然后就可以显示 LED 灯条! 通过USB端口控制,并通过BilbioPixel库支持Python 。 驱动超过700像素与多个AllPixel Minis作为一个(BiblioPixel的一个功能)。 支持通过板载直流桶式插座(5A最大电流)为5V或12V LED类型供电。 通过安装附带的保护二极管,直接从USB电源驱动少量LED。 附件内容我们提供了动画库例子,让您快速启动并运行。 USB 可编程RGB LED灯条控制器效果图展示: USB 可编程RGB LED灯条控制器实物截图: USB 可编程RGB LED灯条显示控制器电路PCB截图: 新版本注意事项: 不需要焊接 - 所有零件都预先焊接 较小尺寸 - 1.6 x 1.2英寸(40.6 x 30.5 mm) 100%向后兼容:为AllPixel编写的任何代码将在Mini版本上运行 USB Micro连接器:更常见的连接器 所有附件内容截图:
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

朽木自雕i

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值