LEDs状态灯任务(线程)设计(基于RTOS)

640?wx_fmt=png

我们学习MCU开发,大部分都是面向过程的开发,但实际项目一般要求我们有面向对象(模块化)的方式来开发。

刚学习C语言开发的朋友,应该常常听说面向对象,但实际对于面向对象开发可能还是不太了解。

为了初学者进一步理解,本文结合实际项目(LEDs状态灯)给大家带来比较基础的模块化设计。

关于C语言的模块化

对于MCU的开发,大部分人都还是习惯性用的C语言,原因之一在于C语言具有高效的特点。


可以了解一下,许多操作系统的内核使用的编程语言,其实都用到了C语言,这就是C语言的优点,也是C语言这么多年不衰败的原因。

说回来,对于MCU的开发,除了C语言,当然还可以其它语言,像C++有许多人就用上了。


C++语言本身就是面向对象的开发语言,定义一个类,可以包含许多成员。站在C语言的角度,可以理解成定义一个结构体,里面包含许多数据类型。 如下面要说的LEDs数据结构体:

typedef struct
{
  uint8_t  Mode;                  //模式(常灭 常亮 闪烁)

  uint8_t  Status;                //当前状态(灭 亮)
  uint16_t OffTimes;              //灭时间(xLED_COUNT_PERIOD毫秒)
  uint16_t OnTimes;               //亮时间
  uint16_t Counter;               //计数(计时)

  void (*OffFun)(void);           //灭函数接口
  void (*OnFun)(void);            //亮函数接口
}LED_TypeDef;

可以看到,结构体里面包含整型变量,函数指针。

补充,指针函数与函数指针的区别

1、指针函数:本质是一个函数,函数返回类型是某一类型的指针。

格式:  类型标识符    *函数名(参数表)

如:int *f(x,y);

2、函数指针:本质是一个指针,指向函数的指针变量。

格式:类型说明符 (*函数名)(参数)

如:int (*f) (int x);

为什么要模块化设计

假如一个系统中做的事情非常多,比如:采集两个增量式编码器、两个绝对值编码器、控制4个电机、控制多个LED状态灯、通信收发数据,采集温度、湿度、超声波雷达等···许多模块,那么问题来了,这么多模块,你的软件该如何设计

答案就是需要模块化设计。

模块化设计,包含底层驱动,中间接口函数,应用程序等。对于MCU级别的开发,为了规范,建议大家从底层设计到应用层设计都按照模块化的方式来设计。

简单的来说,模块化就是源文件、数据结构、变量、函数命名等需要按照模块的方式来设计。比如LEDs状态灯:IO口的定义用LED(模块),文件名用led,变量、函数名抬头用LED,定义一个LED数据结构(模块的数据结构)等。

模块化的设计优点在于:便于源代码管理、移植、理解等等。(相信有许多自己写的代码,放一段时间之后,重新再次阅读,可能看了半天都不明白源代码的意思。)

LEDs实例讲述

 为方便大家理解,拿一个简单的LEDs状态灯的实例来分析。里面使用到了RTOS简单系统延时(本文不讲述关于RTOS的知识)。文末提供例程下载地址。

1.描述

绿、黄、红三个(可以自己添加许多个)LED状态灯,可实现常灭、常亮、闪烁三个模式。

闪烁:灭、亮时间可设置(提供函数接口修改)。

在一个线程(任务)里面执行。

3个LED不同亮灭时间效果

640?wx_fmt=gif

2.数据结构

typedef struct
{
  uint8_t  Mode;                  //模式(常灭 常亮 闪烁)

  uint8_t  Status;                //当前状态(灭 亮)
  uint16_t OffTimes;              //灭时间(xLED_COUNT_PERIOD毫秒)
  uint16_t OnTimes;               //亮时间
  uint16_t Counter;               //计数(计时)

  void (*OffFun)(void);           //灭函数接口
  void (*OnFun)(void);            //亮函数接口
}LED_TypeDef;

为了方便理解,只使用一个数据结构(实际大的项目可能有多个包含,类似C++继承关系)。

3.底层LED函数接口

void LEDGreen_Off(void);

void LEDGreen_On(void);

void LEDYellow_Off(void);

void LEDYellow_On(void);

void LEDRed_Off(void);

void LEDRed_On(void);

主要就是亮灭函数接口,这里提供三组LED(根据需求可添加)。


4.定义局部变量

static LED_TypeDef sLEDG_Structure;              //绿灯
static LED_TypeDef sLEDY_Structure;              //黄灯
static LED_TypeDef sLEDR_Structure;              //红灯


5.初始化变量

/************************************************
函数名称 : LED_Data_Init
功    能 : 数据初始化
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
static void LED_Data_Init(void)
{
  /* 绿灯 */
  sLEDG_Structure.Mode = LED_MODE_FLICKER;       //初始化为闪烁
  sLEDG_Structure.OffTimes = 50;                 //灭亮时间
  sLEDG_Structure.OnTimes = 50;
  sLEDG_Structure.Counter = 0;                   //计数归零
  sLEDG_Structure.OffFun = LEDGreen_Off;         //灭函数接口
  sLEDG_Structure.OnFun = LEDGreen_On;           //亮函数接口

  /* 黄灯 */
  sLEDY_Structure.Mode = LED_MODE_ON;            //初始化为常亮
  sLEDY_Structure.OffTimes = 0;                  //灭亮时间
  sLEDY_Structure.OnTimes = 0;
  sLEDY_Structure.Counter = 0;                   //计数归零
  sLEDY_Structure.OffFun = LEDYellow_Off;        //灭函数接口
  sLEDY_Structure.OnFun = LEDYellow_On;          //亮函数接口

  /* 红灯 */
  sLEDR_Structure.Mode = LED_MODE_ON;            //初始化为常亮
  sLEDR_Structure.OffTimes = 0;                  //灭亮时间
  sLEDR_Structure.OnTimes = 0;
  sLEDR_Structure.Counter = 0;                   //计数归零
  sLEDR_Structure.OffFun = LEDRed_Off;           //灭函数接口
  sLEDR_Structure.OnFun = LEDRed_On;             //亮函数接口

  /* 对外调用接口(例子) */
  LEDG_Set(LED_MODE_FLICKER, 50, 50);
  LEDY_Set(LED_MODE_FLICKER, 50, 10);
  LEDR_Set(LED_MODE_FLICKER, 20, 30);
}


这里重要的就是要初始化灭亮函数接口。


6.LEDs任务(线程)

/************************************************
函数名称 : LED_Task_Proc
功    能 : 状态灯任务程序
参    数 : pvParameters --- 可选参数
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
static void LED_Task_Proc(void *pvParameters)
{
  static TickType_t xLastWakeTime;

  xLastWakeTime = xTaskGetTickCount();  for(;;)
  {
    //间隔固定计数周期(采样时间)
    vTaskDelayUntil(&xLastWakeTime, LED_COUNT_PERIOD);    /* 浏览LEDs */
    LED_Scan(&sLEDG_Structure);
    LED_Scan(&sLEDY_Structure);
    LED_Scan(&sLEDR_Structure);
  }
}

流程图:

640?wx_fmt=png

7.LED浏览(或者说处理)

/************************************************
函数名称 : LED_Scan
功    能 : 状态灯扫描(修改状态)
参    数 : LED_Struct --- 状态灯数据结构
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
static void LED_Scan(LED_TypeDef *LED_Struct)
{
  /* 1.常灭模式 */
  if(LED_MODE_OFF == LED_Struct->Mode)
  {
    LED_Struct->Status = LED_STATUS_OFF;         //状态置为"灭"
    LED_Struct->OffFun();                        //灭灯
  }
  /* 2.常亮模式 */
  else if(LED_MODE_ON == LED_Struct->Mode)
  {
    LED_Struct->Status = LED_STATUS_ON;          //状态置为"亮"
    LED_Struct->OnFun();                         //亮灯
  }
  /* 3.闪烁模式 */
  else if(LED_MODE_FLICKER == LED_Struct->Mode)
  {
    /* 在灭的状态 */
    if(LED_STATUS_OFF == LED_Struct->Status)
    {
      LED_Struct->Counter++;      if(LED_Struct->Counter >= LED_Struct->OffTimes)
      {
        LED_Struct->Counter = 0;
        LED_Struct->OnFun();                     //亮灯
        LED_Struct->Status = LED_STATUS_ON;      //状态置为"亮"
      }
    }
    /* 在亮的状态 */
    else if(LED_STATUS_ON == LED_Struct->Status)
    {
      LED_Struct->Counter++;      if(LED_Struct->Counter >= LED_Struct->OnTimes)
      {
        LED_Struct->Counter = 0;
        LED_Struct->OffFun();                    //灭灯
        LED_Struct->Status = LED_STATUS_OFF;     //状态置为"灭"
      }
    }
    else
    {
      LED_Struct->Status = LED_STATUS_OFF;       //状态置为"灭"
    }
  }
  /* 4.未知模式 */
  else
  {
    LED_Struct->Status = LED_STATUS_OFF;         //状态置为"灭"
    LED_Struct->OffFun();                        //灭灯
  }
}

源代码工程下载地址:

链接:https://pan.baidu.com/s/1cNtwJDdCOfyYwsvKCclFyw 

密码:kk74

推荐阅读:

MDK-ARM编译器从V5升级到V6需要做哪些工作?

重温经典PID算法

最后

  1. 置顶公众号,不怕找不到我;

  2. 记得给我点赞哦!

  3. 微信搜索“strongerHuang” 或者扫描下面二维码、关注,在我的底部菜单查看更多精彩内容!

640?wx_fmt=jpeg

长按识别二维码 关注


640


赞赏是对作者的认可与支持!

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
字符驱动设备控制 LED 需要使用 GPIO 接口来控制引脚的状态。在 Linux 内核中,可以使用 gpio_leds_probe 函数来初始化引脚的状态: ``` static int __init gpio_leds_probe(struct platform_device *pdev) { int ret = 0; int i; struct gpio_led *led = pdev->dev.platform_data; for (i = 0; i < ARRAY_SIZE(gpio_leds); i++) { gpio_direction_output(gpio_leds[i].gpio, !gpio_leds[i].default_state); ret = gpio_request(gpio_leds[i].gpio, "gpio_leds"); if (ret < 0) { pr_err("gpio_request failed for pin %d\n", gpio_leds[i].gpio); goto err_gpio_request; } gpio_leds[i].cdev.name = "gpio-leds"; gpio_leds[i].cdev.brightness_get = gpio_leds_get_brightness; gpio_leds[i].cdev.brightness_set = gpio_leds_set_brightness; gpio_leds[i].cdev.default_trigger = led->default_trigger; gpio_leds[i].cdev.brightness = !gpio_leds[i].default_state; ret = gpio_led_classdev_register(&pdev->dev, &gpio_leds[i].cdev); if (ret < 0) { pr_err("gpio_led_classdev_register failed for pin %d\n", gpio_leds[i].gpio); goto err_gpio_led_classdev_register; } } return 0; err_gpio_led_classdev_register: err_gpio_request: for (i = 0; i < ARRAY_SIZE(gpio_leds); i++) { gpio_free(gpio_leds[i].gpio); } return ret; } ``` 该函数会遍历一个 gpio_leds 数组,该数组包含了需要控制的 LED 的引脚信息。对于每个引脚,函数会使用 gpio_direction_output 函数来设置其方向为输出,并设置其初始状态为默认状态的反向(因为 LED 是低电平点亮的,所以默认状态为高电平)。然后使用 gpio_request 函数来申请引脚,如果失败则返回错误码。接下来,函数会创建一个 gpio_led 结构体,并设置该结构体的成员变量。最后,使用 gpio_led_classdev_register 函数来注册字符设备驱动。如果注册失败,则会释放申请的引脚并返回错误码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

strongerHuang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值