一、工程简介
cotLed是一款轻量级的LED控制软件框架,可以十分方便地控制及自定义LED的各种状态,移植方便,无需修改,只需要在初始化时实现单片机硬件GPIO初始化,同时为框架接口提供GPIO写函数即可。
框架代码工程地址:https://gitee.com/cot_package/cot_led
工程代码结构也很精简,只用两个文件cot_led.c和cot_led.h实现具体功能,对外提供API接口函数,适用于裸机和带操作系统的应用代码。
通过该软件框架API,可以实现的LED控制接口功能有:
(1)、单个LED的亮灭、翻转、闪烁、呼吸灯、自定义(如多少秒快闪几次等)等多种功能;
(2)、多个LED组合的跑马灯、流水灯等功能;
(3)、上述各个功能模式功能实现的次数设置。
二、工程代码分析
在头文件cot_led.h中包括:
(1)定义了LED亮灭的枚举cotLedState_e;
(2)定义了写入IO状态的函数指针cotLedCtrl_f;
(3)定义了LED核心控制功能成员变量的结构体cotLedProc_t;
(4)外部声明的LED控制接口函数。
/* Define to prevent recursive inclusion -----------------------------------------------------------------------------*/
#ifndef _COT_LED_H_
#define _COT_LED_H_
/* Includes ----------------------------------------------------------------------------------------------------------*/
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
/* Exported types ----------------------------------------------------------------------------------------------------*/
// 状态bit位所使用的byte数,bit之间的控制时间为时间颗粒度
#define LED_STATE_BYTE_NUM 4
/**
* @brief LED亮灭枚举定义
*/
typedef enum{
COT_LED_OFF = 0, /*!< (0)灯灭 */
COT_LED_ON = !COT_LED_OFF /*!< (1)灯亮 */
} cotLedState_e;
typedef void (*cotLedCtrl_f)(cotLedState_e state);
typedef uint16_t led_t;
typedef struct
{
uint8_t validBits; /*!< 状态有效bit位 */
uint8_t offset; /*!< 状态bit位偏移 */
uint8_t count; /*!< 控制次数 */
uint8_t isSetCount : 1; /*!< 是否设置了控制次数 */
uint8_t defState : 1; /*!< 默认状态 */
uint8_t curState : 1; /*!< 当前状态 */
uint8_t pwmDir : 1; /*!< PWM增减方向 */
uint8_t isPwm : 1; /*!< PWM模式 */
uint16_t tic; /*!< 时间计数器 */
uint16_t interval; /*!< 每次控制的时间颗粒度,单位为1ms */
union
{
struct
{
uint16_t onTime; /*!< 亮的时长 */
uint16_t tic; /*!< PWM计时 */
} pwm;
uint8_t state[LED_STATE_BYTE_NUM]; /*!< 状态bit位 */
} data;
} cotLedProc_t;
typedef struct
{
cotLedProc_t proc;
cotLedCtrl_f pfnLedCtrl;
} cotLedCfg_t;
/* Exported constants ------------------------------------------------------------------------------------------------*/
/* Exported macro ----------------------------------------------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------------------------------------------------*/
extern int cotLed_Init(cotLedCfg_t pCfgTable[], size_t num);
extern int cotLed_SetState(led_t led, cotLedState_e state);
extern int cotLed_SetStateWithTime(led_t led, cotLedState_e state, uint16_t time);
extern int cotLed_ON(led_t led);
extern int cotLed_OFF(led_t led);
extern int cotLed_Toggle(led_t led);
extern int cotLed_Twinkle(led_t led, uint16_t time);
extern int cotLed_TwinkleWithCount(led_t led, uint16_t time, uint8_t count, cotLedState_e defState);
extern int cotLed_Breathe(led_t led, uint16_t period);
extern int cotLed_BreatheWithCount(led_t led, uint16_t period, uint8_t count, cotLedState_e defState);
extern int cotLed_Custom(led_t led, ...);
extern int cotLed_CustomWithCount(led_t led, uint8_t count, cotLedState_e defState, ...);
extern int cotLed_Marquee(led_t led[], uint8_t ledNum, int32_t time);
extern int cotLed_MarqueeWithCount(led_t led[], uint8_t ledNum, int32_t time, uint8_t count, cotLedState_e defState);
extern int cotLed_Waterfall(led_t led[], uint8_t ledNum, int32_t time);
extern int cotLed_WaterfallWithCount(led_t led[], uint8_t ledNum, int32_t time, uint8_t count, cotLedState_e defState);
extern int cotLed_Ctrl(uint32_t sysTime);
#endif
由于cot_led.c中代码比较多,这里暂时就不贴出,可以参考具体文件中的函数定义和注释内容。
在头文件cot_led.c中包括:
(1)呼吸灯软件模拟PWM频率;
(2)在LED控制接口中需要用到的一些宏定义;
(3)各类型的LED控制接口。
三、工程代码应用
工程代码的使用可以参考工程代码包中的examples和readme文件。这里以在stm32平台上使用带有freertos的应用代码中进行各个LED接口函数的测试,测试时使用宏开关TEST_LED_MODE控制分别进行接口功能测试。在应用代码中进行了接口使用的注释。
应用的流程顺序为:
(1)初始化各个LED对应GPIO口驱动。
(2)定义各个LED的IO状态控制函数,调用LED控制初始化函数cotLed_Init,入参为各个LED的IO状态控制函数和LED数量。
(3)创建周期执行逻辑代码,在带操作系统的代码中创建周期任务;而在裸机系统代码中则创建周期计数逻辑或定时器逻辑。
(4)在周期执行逻辑代码中周期调用LED控制函数cotLed_Ctrl,入参为当前时间戳ms级的系统时长。
(5)条件触发调用执行对应的各个LED控制接口函数。
应用时的一些注意点如下:
(1)调用接口函数时,不要重复调用,重复调用设置函数相关信息会复位,使用触发式调用即达到某一触发条件时调用接口一次。在实际测试时LED翻转接口cotLed_Toggle需要在循环中重复调用。
(2)在调用的带时间设置入参的LED控制接口时,该时间需要设置为任务调度周期的倍数;裸机调用时设置为定时器或计数器周期的倍数。比如调度周期或计数器周期为500ms,为该接口设置的时间参数就为500*N。
(3)如果设置某个LED为呼吸灯模式,则需要保证cotLed_Ctrl调用周期为1ms(优先级需要最高,或者定时器调度效果最好)。
(4)在操作系统下使用时非线程安全,最好可以使用读写锁,如果没有读写锁则至少使用互斥锁。
#include "sys.h"
#include "delay.h"
#include "FreeRTOS.h"
#include "task.h"
#include "cot_led.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED0_TASK_PRIO 2
//任务堆栈大小
#define LED0_STK_SIZE 50
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 3
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
typedef enum
{
LED_0 = 0,
LED_1,
/* 勿删除,用来统计LED的数目 */
LED_MAX_NUM
} LedType_e;
//LED的IO驱动初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); //使能GPIOF时钟
//GPIOF9,F10初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //LED0和LED1对应IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOF, &GPIO_InitStructure); //初始化GPIO
GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10); //GPIOF9,F10设置高,灯灭
}
void CtrlLed0(cotLedState_e state) //LED0的IO状态控制函数
{
state == COT_LED_ON ? GPIO_ResetBits(GPIOF, GPIO_Pin_9) : GPIO_SetBits(GPIOF, GPIO_Pin_9);
}
void CtrlLed1(cotLedState_e state) //LED1的IO状态控制函数
{
state == COT_LED_ON ? GPIO_ResetBits(GPIOF, GPIO_Pin_10) : GPIO_SetBits(GPIOF, GPIO_Pin_10);
}
void FML_LED_Init(void)
{
static cotLedCfg_t s_ledTable[LED_MAX_NUM] = {
{.pfnLedCtrl = CtrlLed0},
{.pfnLedCtrl = CtrlLed1},
};
LED_Init(); //初始化LED驱动IO端口
cotLed_Init(s_ledTable, LED_MAX_NUM); //LED控制初始化
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(168); //初始化延时函数
FML_LED_Init();
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
#define TEST_LED_MODE 1 //用于测试各个LED控制接口的宏开关
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
#if ((TEST_LED_MODE >= 1) && (TEST_LED_MODE <= 6))
//创建LED0任务
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
#else
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
#endif
taskEXIT_CRITICAL(); //退出临界区
}
//LED任务函数
void led0_task(void *pvParameters)
{
#if (TEST_LED_MODE == 1)
cotLed_Twinkle(LED_0, 2000); //设置LED0每隔2s进行闪烁(亮灭间隔时间)
cotLed_Twinkle(LED_1, 5000); //设置LED1每隔5s进行闪烁(亮灭间隔时间
#elif (TEST_LED_MODE == 2)
//设置LED0每隔2s秒进行闪烁,共5次(一亮一灭为一次),次数完成后灯灭
cotLed_TwinkleWithCount(LED_0, 2000, 5, COT_LED_ON);
//设置LED1每隔1s秒进行闪烁,共4次(一亮一灭为一次),次数完成后点亮
cotLed_TwinkleWithCount(LED_1, 1000, 4, COT_LED_OFF);
#elif (TEST_LED_MODE == 3)
cotLed_SetStateWithTime(LED_0, COT_LED_ON, 5000);//设置LED0亮持续5秒后熄灭
cotLed_SetStateWithTime(LED_1, COT_LED_OFF, 3000);//设置LED1灭持续5秒后点亮
#endif
while(1)
{
#if (TEST_LED_MODE == 4)
cotLed_Toggle(LED_0);//周期翻转LED0
cotLed_Toggle(LED_1);//周期翻转LED1
#elif (TEST_LED_MODE == 5)
if (xTaskGetTickCount() == 0) {
cotLed_ON(LED_0);//点亮LED0
cotLed_ON(LED_1);//点亮LED1
} else if (xTaskGetTickCount() == 5000) {
cotLed_OFF(LED_0);//熄灭LED0
cotLed_OFF(LED_1);//熄灭LED1
} else if(xTaskGetTickCount() == 10000) {
cotLed_ON(LED_0);
cotLed_ON(LED_1);
}
#elif (TEST_LED_MODE == 6)
if (xTaskGetTickCount() == 0) {
cotLed_SetState(LED_0, COT_LED_ON);//点亮LED0
cotLed_SetState(LED_1, COT_LED_ON);//点亮LED1
} else if(xTaskGetTickCount() == 5000) {
cotLed_SetState(LED_0, COT_LED_OFF);//熄灭LED0
cotLed_SetState(LED_1, COT_LED_OFF);//熄灭LED1
} else if(xTaskGetTickCount() == 10000) {
cotLed_SetState(LED_0, COT_LED_ON);
cotLed_SetState(LED_1, COT_LED_ON);
}
#endif
//周期调用LED控制函数,入参为当前时间戳ms级的系统时长
cotLed_Ctrl(xTaskGetTickCount());
vTaskDelay(100);
}
}
//LED1任务函数
void led1_task(void *pvParameters)
{
#if (TEST_LED_MODE == 7)
//LED0在2秒内1次快闪,亮100ms,灭1900ms,无限制次数
cotLed_Custom(LED_0, 100, -1900, 0);
//LED1在3秒内1次快闪,亮200ms,灭2800ms,无限制次数
cotLed_Custom(LED_1, 200, -2800, 0);
#elif (TEST_LED_MODE == 8)
//LED0在2秒内3次快闪,总共5次,次数完成后灯灭
cotLed_CustomWithCount(LED_0, 5, COT_LED_OFF, 100, -100, 100, -100, 100, -100, -1400, 0);
//LED1在3秒内2次快闪,总共8次,次数完成后灯亮
cotLed_CustomWithCount(LED_1, 8, COT_LED_ON, 200, -200, 200, -200, -2200, 0);
#elif (TEST_LED_MODE == 9)
cotLed_Breathe(LED_0, 2000);//LED0在2秒内完成一次呼吸亮灭操作,一直执行不限次数
cotLed_Breathe(LED_1, 3000);//LED1在3秒内完成一次呼吸亮灭操作,一直执行不限次数
#elif (TEST_LED_MODE == 10)
//LED0在2秒内完成一次呼吸亮灭操作,总共3次,次数完成后灯灭
cotLed_BreatheWithCount(LED_0, 2000, 3, COT_LED_OFF);
//LED1在3秒内完成一次呼吸亮灭操作,总共5次,次数完成后灯亮
cotLed_BreatheWithCount(LED_1, 3000, 5, COT_LED_ON);
#elif (TEST_LED_MODE == 11)
led_t led[] = {LED_0, LED_1};
cotLed_Waterfall(led, 2, 300);//流水灯一直执行,LED之间亮间隔300ms
#elif (TEST_LED_MODE == 12)
led_t led[] = {LED_0, LED_1};
//流水灯执行8次,LED之间亮间隔300ms,执行完后熄灭
cotLed_WaterfallWithCount(led, 2, 300, 8, COT_LED_OFF);
#elif (TEST_LED_MODE == 13)
led_t led[] = {LED_0, LED_1};
cotLed_Marquee(led, 2, 500);//跑马灯一直执行,LED之间亮间隔500ms
#elif (TEST_LED_MODE == 14)
led_t led[] = {LED_0, LED_1};
//跑马灯执行5次,LED之间亮间隔500ms,执行完后熄灭
cotLed_MarqueeWithCount(led, 2, 500, 5, COT_LED_OFF);
#endif
while(1)
{
//周期调用LED控制函数,入参为当前时间戳ms级的系统时长
cotLed_Ctrl(xTaskGetTickCount());
vTaskDelay(1);
}
}
↓↓↓更多技术内容和书籍资料获取,入群技术交流敬请关注“明解嵌入式”↓↓↓