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


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

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

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

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

    1. #include "main.h"
    2. /**************************************
    3. * @name main
    4. * @brief 主函数
    5. * @param None
    6. * @retval None
    7. **************************************/
    8. int main()
    9. {
    10. Hardware_Init.Sys_Init();
    11. while(1)
    12. {
    13. System.Run();
    14. }
    15. }

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

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

    1. #ifndef __Sys_init_H__
    2. #define __Sys_init_H__
    3. #include "main.h"
    4. //定义结构体类型
    5. typedef struct
    6. {
    7. void (*Sys_Init)(void); //系统初始化
    8. } Hardware_Init_t;
    9. extern Hardware_Init_t Hardware_Init;
    10. #endif

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

    1. #include <main.h>
    2. static void Sys_Init(void); //系统初始化
    3. Hardware_Init_t Hardware_Init =
    4. {
    5. Sys_Init
    6. };
    7. /**************************************
    8. * @name Sys_Init
    9. * @brief 系统初始化
    10. * @param None
    11. * @retval None
    12. **************************************/
    13. static void Sys_Init(void)
    14. {
    15. LED.LED_Fun(LED1,LED_ON);
    16. Public.Delay_ms(200);
    17. LED.LED_Fun(LED1,LED_OFF);
    18. LED.LED_Fun(LED2,LED_ON);
    19. Public.Delay_ms(200);
    20. LED.LED_Fun(LED2,LED_OFF);
    21. LED.LED_Fun(LED3,LED_ON);
    22. Public.Delay_ms(200);
    23. LED.LED_Fun(LED3,LED_OFF);
    24. }

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

    1. #ifndef __PUBLIC_H_
    2. #define __PUBLIC_H_
    3. #include "main.h"
    4. //数据类型重定义
    5. typedef signed char sint8_t;
    6. typedef signed short int sint16_t;
    7. typedef signed long int sint32_t;
    8. typedef unsigned char uint8_t;
    9. typedef unsigned short int uint16_t;
    10. typedef unsigned long int uint32_t;
    11. //定义结构体类型
    12. typedef struct
    13. {
    14. void (*Delay_ms)(uint16_t); //ms延时函数
    15. } Public_t;
    16. //定义外部变量
    17. extern Public_t Public;
    18. #endif

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

    1. #include <main.h>
    2. static void Delay_ms(uint16_t); //ms延时函数
    3. Public_t Public =
    4. {
    5. Delay_ms,
    6. };
    7. /*************************************
    8. * @name Delay_ms
    9. * @brief 毫秒延时函数
    10. * @param ms -> 需要延时的时间
    11. * @retval None
    12. *************************************/
    13. static void Delay_ms(uint16_t ms)
    14. {
    15. uint16_t i,j;
    16. for(i=0;i<ms;i++)
    17. for(j=0;j<990;j++);
    18. }

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

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

    1. #ifndef __LED_H__
    2. #define __LED_H__
    3. #include "main.h"
    4. #define MCU_Run_LED_ON (bit)0
    5. #define MCU_Run_LED_OFF (bit)1
    6. sbit LED1_Port = P1^0;
    7. sbit LED2_Port = P1^1;
    8. sbit LED3_Port = P1^2;
    9. //定义枚举类型
    10. typedef enum
    11. {
    12. LED1 = (uint8_t)0x01,
    13. LED2 = (uint8_t)0x02,
    14. LED3 = (uint8_t)0x03,
    15. }LED_Num_t;
    16. //定义结构体类型
    17. typedef struct
    18. {
    19. void (*LED_Fun)(uint8_t,void(*Callback)(uint8_t));
    20. }LED_t;
    21. //定义外部变量
    22. extern LED_t LED;
    23. //定义外部函数模型
    24. extern void LED_ON(uint8_t);
    25. extern void LED_OFF(uint8_t);
    26. extern void LED_Flip(uint8_t);
    27. #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文件里面的内容。

    1. #include "main.h"
    2. static void LED_Fun(uint8_t,void(*Callback)(uint8_t));
    3. LED_t LED =
    4. {
    5. LED_Fun,
    6. };
    7. /**************************************
    8. * @name LED_Fun
    9. * @brief LED功能函数,中间虚拟函数
    10. * @param Num -> 编号,Callback ->回调函数指针
    11. * @retval None
    12. **************************************/
    13. void LED_Fun(uint8_t LED_Num,void(*Callback)(uint8_t))
    14. {
    15. (*Callback)(LED_Num);
    16. }
    17. /**************************************
    18. * @name LED_ON
    19. * @brief 打开LED
    20. * @param Num -> 编号
    21. * @retval None
    22. **************************************/
    23. void LED_ON(uint8_t LED_Num) //打开
    24. {
    25. switch(LED_Num)
    26. {
    27. case LED1: LED1_Port = MCU_Run_LED_ON; break;
    28. case LED2: LED2_Port = MCU_Run_LED_ON; break;
    29. case LED3: LED3_Port = MCU_Run_LED_ON; break;
    30. default: LED1_Port = MCU_Run_LED_ON; break;
    31. }
    32. }
    33. /**************************************
    34. * @name LED_OFF
    35. * @brief 关闭LED
    36. * @param Num -> 编号
    37. * @retval None
    38. **************************************/
    39. void LED_OFF(uint8_t LED_Num) //关闭
    40. {
    41. switch(LED_Num)
    42. {
    43. case LED1: LED1_Port = MCU_Run_LED_OFF; break;
    44. case LED2: LED2_Port = MCU_Run_LED_OFF; break;
    45. case LED3: LED3_Port = MCU_Run_LED_OFF; break;
    46. default: LED1_Port = MCU_Run_LED_OFF; break;
    47. }
    48. }
    49. /**************************************
    50. * @name LED_Flip
    51. * @brief 取反LED
    52. * @param Num -> 编号
    53. * @retval None
    54. **************************************/
    55. void LED_Flip(uint8_t LED_Num) //翻转
    56. {
    57. //条件选择语句
    58. switch(LED_Num)
    59. {
    60. case LED1:
    61. if(LED1_Port == MCU_Run_LED_ON) LED1_Port = MCU_Run_LED_OFF;
    62. else LED1_Port = MCU_Run_LED_ON;break;
    63. case LED2:
    64. if(LED2_Port == MCU_Run_LED_ON) LED2_Port = MCU_Run_LED_OFF;
    65. else LED2_Port = MCU_Run_LED_ON;break;
    66. case LED3:
    67. if(LED3_Port == MCU_Run_LED_ON) LED3_Port = MCU_Run_LED_OFF;
    68. else LED3_Port = MCU_Run_LED_ON;break;
    69. default: LED1_Port = MCU_Run_LED_OFF; break;
    70. }
    71. }

            程序第 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开始。

    1. #ifndef __System_H__
    2. #define __System_H__
    3. //定义结构体类型
    4. typedef struct
    5. {
    6. void (*Run)(void); //系统运行
    7. } System_t;
    8. extern System_t System;
    9. #endif

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

    1. #include <main.h>
    2. static void Run(void); //系统运行
    3. System_t System =
    4. {
    5. Run,
    6. };
    7. /**************************************
    8. * @name Run
    9. * @brief 系统运行
    10. * @param None
    11. * @retval None
    12. **************************************/
    13. static void Run()
    14. {
    15. LED.LED_Fun(LED1,LED_Flip);
    16. Public.Delay_ms(500);
    17. }

            程序第 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文件开始。

    1. #include "main.h"
    2. /**************************************
    3. * @name main
    4. * @brief 主函数
    5. * @param None
    6. * @retval None
    7. **************************************/
    8. int main()
    9. {
    10. Hardware_Init.Sys_Init();
    11. while(1)
    12. {
    13. System.Run();
    14. }
    15. }

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

    1. static void Sys_Init(void)
    2. {
    3. LED.LED_Fun(LED1,LED_ON);
    4. Public.Delay_ms(200);
    5. LED.LED_Fun(LED1,LED_OFF);
    6. LED.LED_Fun(LED2,LED_ON);
    7. Public.Delay_ms(200);
    8. LED.LED_Fun(LED2,LED_OFF);
    9. LED.LED_Fun(LED3,LED_ON);
    10. Public.Delay_ms(200);
    11. LED.LED_Fun(LED3,LED_OFF);
    12. }

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

    1. #include "main.h"
    2. /**************************************
    3. * @name main
    4. * @brief 主函数
    5. * @param None
    6. * @retval None
    7. **************************************/
    8. int main()
    9. {
    10. Hardware_Init.Sys_Init();
    11. while(1)
    12. {
    13. System.Run();
    14. }
    15. }

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

    1. #include <main.h>
    2. static void Run(void); //系统运行
    3. System_t System =
    4. {
    5. Run,
    6. };
    7. /**************************************
    8. * @name Run
    9. * @brief 系统运行
    10. * @param None
    11. * @retval None
    12. **************************************/
    13. static void Run()
    14. {
    15. LED.LED_Fun(LED1,LED_Flip);
    16. Public.Delay_ms(500);
    17. }

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

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

  • 相关阅读:
    西宾猫耳下载工具(missevandown)
    MATLAB cell数组 (tuple)
    信息学奥赛一本通:1152:最大数max(x,y,z)
    机器学习的特征工程
    用户留存为何重要?
    微信撤回时间延长至3小时,真的?
    Redis知识-实战篇(2)
    十四、流式编程(2)
    Vue3实现粒子动态背景
    Java Spring Boot 目录结构介绍
  • 原文地址:https://blog.csdn.net/qq_45385706/article/details/127378410