# jl_sdk_self_lib **Repository Path**: char-x/jl_sdk_self_lib ## Basic Information - **Project Name**: jl_sdk_self_lib - **Description**: JL SDK HAL implementation - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-08-06 - **Last Updated**: 2024-09-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 一种适用于JL系列SDK的HAL实现 ## 一、当前已实现的库 1. skey 按键驱动库 2. sled LED驱动库 3. sled_scene SLED情景驱动库 4. sled_scene_manager SLED情景驱动管理器 5. tws_notify 耳机-手机消息通知库 ## 二、各库的基本用法 ### (一)工程配置 1. 添加slef_hal文件夹到Include路径中(只需添加这个文件夹) 2. 添加模组.c文件到编译src/obj路径中,注:有些库使用了C++的class风格进行管理(xxx模组文件夹下有xxx_class子文件夹),编译时需要把class子文件夹中的xxx_class.c加入编译 3. 如果需要是用JL708系列的DEMO程序,在define中添加'DEVICE_BR50'宏 ### (二)库的代码调用 #### skey库(#include "skey/skey.h") 1. ***key_check_member_t***结构体 ***key_check_member_t***为skey的按键驱动单元,记录按键的绑定信息,触发条件,时间数据 (1) 结构体成员解析 ```C typedef struct key_check_member { struct list_head list; //链表头,内部使用 uint16_t combo_count; // 连击数,内部使用 uint8_t key_last_state; //上次状态,内部使用,消抖处理 uint8_t key_last_state_temp; //上次状态,内部使用,消抖处理 uint8_t combo_timeout; // 两次按键的最长间隔,初始化时设置,单位为tick uint8_t key_hold_trigger_state; // 按键按下状态,内部使用,记录各个按键状态 uint32_t key_trigger_type; // 激活的触发条件,初始化时设置,多个条件用|号分隔 uint16_t key_hold_time_counter; // 当前长按计时,内部使用 uint16_t key_hold_time_check[4];// 设定长按触发时间,0-3对应HOLD_0-HOLD_3时间的时间,初始化时设置 int (*getPortValueCallback)(void *port_info); // 按键值回调,要求0为Press,1为Release,初始化时设定 int (*portInitCallback)(void *port_info); // 按键初始化回调,初始化时设定,若不需要初始化传NULL void *port_info; // 按键信息,传(void*)指针,传入能表示IO的变量。回调中处理即可,初始化时设定 void (*trigger_callback)(void *p, keyTriggerTypeDef signal); // 有触发事件时的回调函数,p为sender,也就是结构体本身的指针,signal为触发的信号,连击时连击数会被按位或至低四位,初始化时设定 void *private_data; // 其它参数,回调用不上就不用传,初始化时设置 }key_check_member_t; ``` (2)用法示例(JL708 BR50) ```C const uint8_t io_num_check[6] = {7,5,8,10,0,6}; //每个port的io个数,用于检查输入 int getPortValue(void *port_info) { uint32_t bit = (uint32_t)port_info & 0x000f; uint32_t port = (uint32_t)port_info >> 4;; int i = bit; for(bit = 0x01; i>0; i--) { bit <<= 1; } return gpio_read((u32)port_info); } int portInit(void *port_info) { uint32_t bit = (uint32_t)port_info & 0x000f; uint32_t port = (uint32_t)port_info >> 4;; int i; struct gpio_config port_config = { .mode = PORT_INPUT_PULLUP_10K, .hd = PORT_DRIVE_STRENGT_24p0mA }; if(port > 5) { return GPIO_INIT_ERR_PORT_INVALID; } if((bit+1) > io_num_check[port]) { return GPIO_INIT_ERR_BIT_INVALID; } i = bit; for(bit = 0x01; i>0; i--) { bit <<= 1; } port_config.pin = bit; gpio_deinit((enum gpio_port)port, bit); log_info("init port:%d,pin:%d", port, bit); gpio_init((enum gpio_port)port, &port_config); return GPIO_INIT_ERR_PORT_OK; } void key_music_control_handler(void *sender, int32_t signal) { key_check_member_t *member = (key_check_member_t*)sender; log_info("Got singal from PORT%c, PIN%d\n",Convert_PORT_num_to_char(member->port_info),Convert_IO_BIT_to_num(member->port_info)); switch(signal) { case KEY_TRIGGER_CLICK: log_info("KEY CLICKED TRIGGER\n"); break; case KEY_TRIGGER_DOUBLE_CLICK: log_info("KEY DOUBLE_CLICK TRIGGER\n"); bt_cmd_prepare(USER_CTRL_AVCTP_OPID_NEXT,0,NULL); log_info("Next song\n"); break; case KEY_TRIGGER_TRIPLE_CLICK: log_info("KEY TRIPLE_CLICK TRIGGER\n"); bt_cmd_prepare(USER_CTRL_AVCTP_OPID_PREV,0,NULL); log_info("Previous song\n"); break; case KEY_TRIGGER_QUADRA_CLICK: log_info("KEY QUADRA_CLICK TRIGGER\n"); break; case KEY_TRIGGER_RELEASED: log_info("KEY RELEASED TRIGGER\n"); break; case KEY_TRIGGER_PRESSED: log_info("KEY PRESSED TRIGGER\n"); break; default: switch(signal & ~0xff) { case KEY_TRIGGER_HOLD_0: log_info("KEY HOLD_0 TRIGGER,combo:%d\n", signal & 0xff); led_scene->selectScene("Off"); break; default: log_info("Unknown key event\n"); break; } break; } } KEY_CHECK_MEMBER_REGISTER(B1) = { .combo_timeout = KEY_CHECK_DEFAULT_COMBO_TICK, // =7*30ms = 210ms .getPortValueCallback = getPortValue, .key_trigger_type = KEY_TRIGGER_HOLD_0 | KEY_TRIGGER_CLICK |KEY_TRIGGER_DOUBLE_CLICK, .key_hold_time_check[0] = KEY_CHECK_MS_TO_TICK(3000), .port_info = (void*)IO_PORTB_01, .portInitCallback = portInit, .trigger_callback = key_music_control_handler, }; ``` #### sled库 (#include "sled/sled.h") 1. sled采用c++风格的类编程,编译时需要加入sled_class.c。sled支持swith开关模式,pwm模式和呼吸灯模式,呼吸灯采用的是spwm驱动(基频6s,$duty(t)=5000*(sin(\frac{\pi}{3}(t-\frac{3}{2})) + 1)$)(程序中已经离散)由即占空比从0->0。对于LED呼吸灯而言正弦电压驱动曲线可能不是最佳的,要实现近似的亮度线性过渡应该需要使用对称的指数函数曲线。sled对外结构体是***sled_t***,对内结构体是***sled_class_t*** (1) ***sled_t***结构体(面向对象风格接口比较简单,不进行注释) ```C typedef struct sled { void (*init)(void); // sled实例需要显示在程序中调用该结构完成初始化 slde_mode_t (*GetCurMode)(void); void (*SetMode)(enum slde_mode mode); //设置LED工作模式:SLED_MODE_SWITCH:开关工作模式,SLED_MODE_PWM:PWM驱动模式,SLED_MODE_BREATHE:呼吸灯模式 void (*ConfigurePWM)(uint16_t duty); void (*ConfigureBreathe)(uint16_t breathe_speed); // 呼吸灯倍速,min:0 void (*ONBreathe)(void); //开始呼吸灯效 void (*ON1)(void); //仅SWITCH模式有效,表示推挽拉高 void (*ON0)(void); //仅SWITCH模式有效,表示推挽拉低 void (*OFF)(void); //所有模式有效,将io设置为浮空 }sled_t; typedef struct sled_class { /** public: **/ void (*init)(void); // sled实例需要显示在程序中调用该结构完成初始化 slde_mode_t (*GetCurMode)(void); void (*SetMode)(enum slde_mode mode); void (*ConfigurePWM)(uint16_t duty); void (*ConfigureBreathe)(uint16_t breathe_speed); void (*ONBreathe)(void); void (*ON1)(void); void (*ON0)(void); void (*OFF)(void); /** private: **/ uint32_t port_io; uint32_t io_dir; slde_mode_t WorkMode; spinlock_t lock; void (*spin_lock)(void); void (*spin_unlock)(void); int pwm_handle; int breathe_timer_handle; uint16_t pwm_duty; uint16_t breathe_speed; int16_t breathe_index; uint16_t breathe_dir; void *private_param; enum gpio_function pwm_func_enum; \ }sled_class_t; ``` 2. 用法示例 (1) sled_class.c中写了基本的回调函数,但不具备驱动功能,需要在外部.c重写类函数(见sled_board.c),重写完成后最后调用`SLED_CONTRUCTOR(sled_board,IO_PORTB_02);`语句完成构造,第一个参数为实例名称,第二个为绑定的IO。随后在sled.h中使用`EXPORT_SLED(sled_board)`将实例设置为全局变量。 (2) 调用代码实例 ```C sled_board->init(); //初始化实例 sled_board->SetMode(SLED_MODE_BREATHE); //设置为呼吸灯模式 sled_board->ConfigureBreathe(2); // 三倍速呼吸灯 sled_board->ON0(); //仅SWITCH模式有效,表示推挽拉低 sled_board->ON1(); //仅SWITCH模式有效,表示推挽拉高 sled_board->OFF(); //所有模式有效,将io设置为浮空 sled_board->ONBreathe(); //开始呼吸等效 ``` #### sled_scene/sled_scene_manager库(#include "sled_scene_manager/sled_scene_manager.h") sled_scene_manager和sled_scene库是为了实现LED情景设计的,scene_manager依赖scene库,此处仅解释manager库(scene库几乎不会被单独使用),sled_scene_manager同样使用了C++风格的类设计,因此需要添加对应的sled_scene_manager_class.c进入编译系统 1. sled_scene_manager接口 ,管理器的接口十分简单,仅保留了选择和获取两个接口 ```C typedef struct sled_scene_manager { int (*selectScene)(const char * sceneName); const char * (*getCurrentScene)(void); }sled_scene_manager_t; ``` 2. 情景添加与查找 情景在sled_scene_manager.c中定义,使用比较简单,详细看该文件,同样是使用`SLED_SCENE_MANAGER_CONSTRUCTOR(led_scene, sled_scene_board, sled_scene_register);`构造了一个管理器,第一个参数为名称,第二个参数为LED情景数组,第三个为初始化回调,至于为什么需要这个回调函数是因为sled是在运行时完成初始化的,它的指针需要到运行后才能确定,因此不能在编译时就写入各个动作的操作者上。需要在构造时使用一个回调函数完成指针的赋值,因此***sled的构造必须早于sled_scene_manager的构造*** 3. 接口使用 ```C sled_board->init(); led_scene->selectScene("Breathe3"); ``` #### tws_notify库(#include "tws_notify/tws_notify.h") tws_notify是设计完成耳机与手机的通知实现,目前走的是SPP协议,用来通知手机抢占发生以及允许手机回发通知控制耳机的抢占。***需要添加`MSG_FROM_TWS_NOTIFY`事件*** 1. 接口: ```C /** * @brief ## please `call in` spp recieve handler(`online_spp_recieve_cbk`) * @param rec_data recieved data pointer `with` frame head(raw data) * @param len data total length * @param bt_addr the address of the BT device who sent this packet * @author zhengziyang (zhengziyang@zh-jieli.com) */ void tws_notify_recieve_callback(void *rec_data,u16 len, u8 *bt_addr); /** * @brief ## send a packet to a specified BT device by BT address * @param bt_addr The BT device send to * @param tws_notify TWS notify type, LIKE `TWS_NOTIFY_XXX` * @param data Additional data, which will be hint in enum TWS_NOTIFY_XXX * @param len Additional data length * @return int * @author zhengziyang (zhengziyang@zh-jieli.com) */ int tws_notify_send(u8 *bt_addr, tws_notify_TypeDef tws_notify, void *data, int len); ``` `void tws_notify_recieve_callback(void *rec_data,u16 len, u8 *bt_addr);`该函数请置于消息接受函数下(走SPP的话放在online_spp_recieve_cbk下),传入本次数据流的原始数据,原始数据长促以及发送该数据的蓝牙设备的地址。 需要发送时调用`tws_notify_send()`接口。注意:接收处理函数是在static void tws_notify/tws_notify.c::tws_notify_msg_handler(int *msg)中,您也可以额外注册一个.from=MSG_FROM_TWS_NOTIFY的APP_MSG_HANDLER()来处理消息,传入处理函数的数据格式遵循`struct tws_notify_recieve_packet`结构体的包格式(见tws_notify.c) ## fixs 1. 20240814修改skey逻辑,长按后抬起不会触发连击事件 2. 添加情景管理器 3. 仍存在已知问题,MCPWM外设在系统启动后输出不正确,会叠加一个30ms周期的PWM波(与key扫描系统有关,但是不开skey扫描系统叠加周期更大)