# Libs **Repository Path**: sklli/libs ## Basic Information - **Project Name**: Libs - **Description**: 记录常用外设或组件的模块化代码,尽量面向对象编程。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-04-16 - **Last Updated**: 2026-04-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DWT 基于STM32HAL库开发的,使用内核调试组件DWT进行延时和系统运行时长的记录。 **使用方法** 初始化:在您的工程中包含dwt.h头文件,然后调用`DWT_Init`函数初始化DWT,有一个传参,是主频(MHz)的大小。 延时:初始化后调用`DWT_Delay_s`、`DWT_Delay_ms`、`DWT_Delay_us`,进行延时,分别是秒级别、毫秒级别、微妙级别的延时。 运行时长:调用`DWT_GetTimeStamp_s`获取系统运行的秒数,另外的还有`DWT_GetTimeStamp_ms`、`DWT_GetTimeStamp_us`。 # error 简单的记录程序运行时出错处的文件,函数,行号。 **使用方法** 包含头文件后,调用`Error_Init`进行初始化,然后在需要记录错误的地方调用`Error_Set`。该函数有三个参数,为确保正确,请使用如下格式调用: ```C Error_Set(__FUNCTION__, __LINE__, __FILE__); ``` 这三个均是编译器内置的变量。 # IIC 基于STM32HAL库,使用软件IIC总线。 **使用方法** 根据IIC协议的六个基本单元编写函数,分别是启动、停止、发送字节、接受字节、应答、非应答。 **移植方法** 在头文件中将IIC的SCL和SDA的端口和引脚编号修改成实际的,并修改源码中函数`IIC_WriteSCL`、`IIC_WriteSDA`、`IIC_ReadSDA`中具体的引脚电平读取函数。 # 按键Key 在定时中断中周期判断用户注册的按键引脚的电平状态,从而记录单击、长按、双击、长按不放、按键按下时刻、按键松手时刻、按键按住时刻。通过状态机实现。 整个架构尽可能采用面向对象编程,将按键抽象出来,用户层只需要关注按键初始化和按键状态检查函数即可。 **使用方法** 首先确保在定时中断函数里周期调用`Key_Tick`函数,周期建议1ms。然后在应用层调用`Key_Register`注册按键,该函数使用简单内存池分配按键。 `Key_Register`需要一个参数,指向配置结构体的指针KeyConfig\*,并且返回指向按键实例的指针,该配置结构体和按键实例如下: ```C // Key按键结构体 typedef struct _Key { uint8_t mode; // 按键状态机标志,用户不可见 KeyState state; // 按键动作响应,用户不可见 KeyPinState curr_state; // 按键引脚当前电平,用户不可见 KeyPinState prev_state; // 按键引脚上次电平,用户不可见 uint16_t pressing_time; // 按键持续被按下的时长,单位ms, 用户不可见 uint16_t long_min_time; // 判断长按所需的最短时长,单位ms,需要用户初始化 uint16_t double_click_interval; // 双击间隔,单位ms,需要用户初始化 uint8_t period; // 按键执行的周期,单位ms,需要用户初始化 GPIO_TypeDef *gpio_port; // 引脚端口,需要用户初始化 uint16_t gpio_pin; // 引脚号,需要用户初始化 /* 如果所有按键释放和按下的电平状态均一致,那么该函数指针可以不用; 否则代表个别按键需要个性化读取电平状态 */ KeyPinState (*ReadPinState)(struct _Key *k); // 读取按键引脚电平状态的函数,需要用户初始化 } Key; // 用户使用KeyConfig结构体初始化Key结构体,因此你需要确保该结构体的所有成员都是合法且有效的 typedef struct _KeyConfig { uint16_t long_min_time; // 判断长按所需的最短时长,单位ms uint16_t double_click_interval; // 双击间隔 GPIO_TypeDef *gpio_port; // 引脚端口 uint16_t gpio_pin; // 引脚号 uint8_t period; // 检测按键的周期,单位ms KeyPinState (*ReadPinState)(struct _Key *k); // 读取按键引脚电平状态的函数 } KeyConfig; ``` 需要注意的是,结构体中参数函数指针`ReadPinState`建议指向内置函数`Key_ReadPin`,并且在该函数中修改实际的按键电平读取函数。具体注释都有,这里不再赘述。 头文件中指定了最多的按键数目`KEY_MAX_NUM`,如有需要请自行修改数目。 注册按键后,需要调用`Key_Check`判断按键的动作是否发生,可判断的动作如下,如果有对应的动作发生,该函数返回1,并清空对应状态。 ```C enum _KeyState { KEY_NOACTION, // 按键无动作 KEY_HOLD = 0x01, // 按键按住 KEY_UP = 0x02, // 按键释放瞬间 KEY_DOWN = 0x04, // 按键按下瞬间 KEY_LONG = 0x08, // 长按 KEY_REPEAT = 0x10, // 长按不放 KEY_CLICK = 0x20, // 单击 KEY_DOUBLE = 0x40 // 双击 }; typedef enum _KeyState KeyState; ``` 使用示例: ```C int main(void) { KeyConfig kc = { .gpio_pin = GPIO_PIN_11, .gpio_port = GPIOB, .period = KEY_PERIOD_MS, .ReadPinState = Key_ReadPin, .long_min_time = KEY_PRESS_MIN_TIME_MS, .double_click_interval = 200}; Key *key1 = Key_Register(&kc); if (Key_Check(key1, KEY_HOLD)) { } if (Key_Check(key1, KEY_DOWM)) { } if (Key_Check(key1, KEY_UP)) { } if (Key_Check(key1, KEY_REPEAT)) { } if (Key_Check(key1, KEY_CLICK)) { } if (Key_Check(key1, KEY_LONG)) { } if (Key_Check(key1, KEY_DOUBLE)) { } } ``` # LED 简易控制LED行为,亮、灭、翻转、闪烁,因为封装比较简单,不过多赘述。 # MPU6050 软件IIC协议获取并解析MPU6050的三轴姿态角,采用互补滤波。 **使用方法** 首先核对IIC的引脚是否正确。 ```C #define IIC_SCL_GPIO_PORT GPIOB #define IIC_SCL_GPIO_PIN GPIO_PIN_10 #define IIC_SDA_GPIO_PORT GPIOB #define IIC_SDA_GPIO_PIN GPIO_PIN_11 ``` 调用`MPU6050_Init`初始化MPU6050。 调用`MPU6050_GetRawData`获取原始的数据。 调用`MPU6050_GetIMUData`解析三轴姿态角。如果不需要原始数据,直接调用该函数即可,内部会自动调用`MPU6050_GetRawData`先获取原始数据再进行解析。 # 发布订阅模型 该模型通过相同的话题名使得发布者和订阅者建立联系并且相互独立,互相并不感知对方的存在。整体采用二维链表的形式,所有发布者构成一条链表,每个订阅者挂载到各自话题的发布者下形成二维链表。 链表通过message_center管理,包括消息的传递,发布者订阅者的建立和挂载。 如果该话题的订阅者先于发布者建立,那么依然会先隐式建立发布者,再建立订阅者,等发布者显式建立时返回先前隐式建立的指针。实现无感无阻塞建立。 发布者内部是不存在缓冲区的,而订阅者内部存在。这样设计的原因是发布者并不关注消息的具体内容,它只需要负责将用户想要传递的数据传递给订阅者的缓冲区即可。 **使用方法** 调用`SubRegister`和`PubRegister`建立订阅者或发布者。 `SubGetMessage`获得订阅者的消息,当然你也可以选择直接显式访问订阅者的queue成员,直接操纵数据,不过为了安全,简易使用该函数将订阅者的数据拷贝到另一个缓冲区进行操作。 `PubPushMessage`发布者推送消息到对应的订阅者。 # 环形缓冲区 顾名思义,适用于不定长数据帧的收发。 **使用方法** `RingBuf_Init`初始化环形缓冲区,该函数可以动态分配环形缓冲区的负载区域,也可自定义分配,返回指向RingBuf的指针,该结构体如下。 ```C /** * @brief 循环缓冲区结构体 * */ typedef struct { uint8_t buf_size; // 缓冲区大小 void *buf; // 缓冲区 uint8_t data_size; // 有效数据的大小 uint8_t read_index; // 消费者索引,指向当前待读取数据的空间 uint8_t write_index; // 生产者索引,指向下一个待添加数据的空间 uint8_t buf_dymanic_allocated; // buf是否属于动态分配. 1: 属于; 0: 不属于 } RingBuf; ``` `RingBuf_Write`向缓冲区写数据。 `RingBuf_Read`读数据。 此外还支持单字节写入和读取,分别是`RingBuf_WriteOne`、`RingBuf_ReadOne`。 # 任务中心组件 该组件适用于前后台系统中,模拟FreeRTOS的软件定时器思想,周期调用用户需要运行的各个任务,但是因为是软切换,所以无法保证所有任务都是精准周期运行,但是通过该组件,用户可以更同一的管理任务的运行。 **使用方法** 在定时中断中周期调用`Task_Center`,该函数将用户建立的任务的运行时刻+1,周期简易1ms。 在主循环中调用`Task_Run`,该函数将判断任务周期是否已到,如是则执行对应的任务函数。 调用`Task_Init`初始化任务,需要参数`TaskConfig *`,该结构体如下。 ```C typedef struct task_config { uint16_t run_period; // 运行周期,单位ms void (*run_callback)(void); // 任务的运行函数 } TaskConfig; ```