add qr and low bat

This commit is contained in:
5breeze
2025-11-05 13:24:00 +08:00
parent 88a22a07e4
commit db808f006c
7 changed files with 393 additions and 139 deletions

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"python-envs.defaultEnvManager": "ms-python.python:conda",
"python-envs.defaultPackageManager": "ms-python.python:conda",
"python-envs.pythonProjects": []
}

View File

@@ -10,7 +10,7 @@
<TargetName>DA14585</TargetName> <TargetName>DA14585</TargetName>
<ToolsetNumber>0x4</ToolsetNumber> <ToolsetNumber>0x4</ToolsetNumber>
<ToolsetName>ARM-ADS</ToolsetName> <ToolsetName>ARM-ADS</ToolsetName>
<pCCUsed>6130001::V6.13.1::.\ARMCLANG</pCCUsed> <pCCUsed>6220000::V6.22::ARMCLANG</pCCUsed>
<uAC6>1</uAC6> <uAC6>1</uAC6>
<TargetOption> <TargetOption>
<TargetCommonOption> <TargetCommonOption>

View File

@@ -74,6 +74,13 @@ int fb_draw_font_info(int x, int y, const u8 *font_data, int color);
int fb_draw_font(int x, int y, int ucs, int color); int fb_draw_font(int x, int y, int ucs, int color);
void fb_test(void); void fb_test(void);
void draw_qr_code(
int start_x,
int start_y,
int pix_size,
const unsigned char img[31][4]
);
void select_layout(int xres, int yres); void select_layout(int xres, int yres);

View File

@@ -14,7 +14,6 @@ int fb_h;
u8 fb_bw[FB_SIZE]; u8 fb_bw[FB_SIZE];
u8 fb_rr[FB_SIZE]; u8 fb_rr[FB_SIZE];
/******************************************************************************/ /******************************************************************************/
@@ -100,6 +99,41 @@ void draw_box(int x1, int y1, int x2, int y2, int color)
} }
} }
/**
* 绘制二维码到墨水屏
*
* @param start_x 绘制起始X位置
* @param start_y 绘制起始Y位置
* @param pix_size 每个二维码像素点的宽高(单位像素)
* @param img 二维码C数组尺寸31x4每行4字节
*/
void draw_qr_code(
int start_x,
int start_y,
int pix_size,
const unsigned char img[31][4]
) {
for (int y = 0; y < 31; y++) {
for (int x = 0; x < 31; x++) {
int byte_idx = x / 8; // 每4字节一行8bit一个字节
int bit_idx = 7 - (x % 8); // 图像从高位到低位存储
int bit = (img[y][byte_idx] >> bit_idx) & 1;
int color = (bit == 1) ? WHITE : BLACK;
// 放大绘制
int x1 = start_x + x * pix_size;
int y1 = start_y + y * pix_size;
int x2 = x1 + pix_size - 1;
int y2 = y1 + pix_size - 1;
draw_box(x1, y1, x2, y2, color);
}
}
}
/******************************************************************************/ /******************************************************************************/

View File

@@ -191,6 +191,77 @@ int hour=0, minute=0, second=0;
int cal_minute=-1; int cal_minute=-1;
//GUIQRLB
// 来自 pic.py 自动生成的 C 数组
const unsigned char QR_31x31[31][4] = {
{0x00, 0x00, 0x00, 0x00},
{0x7F, 0x1E, 0x99, 0xFC},
{0x41, 0x3B, 0xC5, 0x04},
{0x5D, 0x5D, 0x6D, 0x74},
{0x5D, 0x11, 0x9D, 0x74},
{0x5D, 0x63, 0xD1, 0x74},
{0x41, 0x43, 0x59, 0x04},
{0x7F, 0x55, 0x55, 0xFC},
{0x00, 0x06, 0xB0, 0x00},
{0x25, 0x43, 0x96, 0xD0},
{0x74, 0x55, 0x2C, 0x74},
{0x7B, 0xB1, 0x22, 0xC4},
{0x5E, 0xB4, 0xE2, 0xE4},
{0x53, 0xBA, 0x6E, 0x98},
{0x72, 0x54, 0xE1, 0xF4},
{0x0B, 0xC8, 0xD5, 0x1C},
{0x32, 0xEA, 0xCD, 0x20},
{0x7B, 0x13, 0xCC, 0x4C},
{0x4A, 0xC0, 0x1A, 0x9C},
{0x1B, 0x55, 0xB5, 0x7C},
{0x1A, 0x8E, 0xF5, 0x54},
{0x77, 0xBD, 0x27, 0xE0},
{0x00, 0x6B, 0xDC, 0x74},
{0x7F, 0x0A, 0xED, 0x54},
{0x41, 0x1B, 0x3C, 0x64},
{0x5D, 0x55, 0x9F, 0xE4},
{0x5D, 0x3E, 0x48, 0x38},
{0x5D, 0x2B, 0x4E, 0x0C},
{0x41, 0x60, 0x20, 0x2C},
{0x7F, 0x0D, 0xB0, 0xC8},
{0x00, 0x00, 0x00, 0x00},
};
const unsigned char LB_31x31[31][4] = {
{0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x00, 0x00},
{0x00, 0x07, 0xC0, 0x00},
{0x00, 0x04, 0x40, 0x00},
{0x00, 0xFF, 0xFE, 0x00},
{0x00, 0x80, 0x02, 0x00},
{0x01, 0x80, 0x03, 0x00},
{0x01, 0x00, 0x01, 0x00},
{0x01, 0x00, 0x01, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x00, 0x01, 0x00},
{0x01, 0x00, 0x01, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x03, 0x81, 0x00},
{0x01, 0x00, 0x01, 0x00},
{0x01, 0x00, 0x01, 0x00},
{0x01, 0xC0, 0x07, 0x00},
{0x00, 0x40, 0x04, 0x00},
{0x00, 0x7F, 0xFC, 0x00},
{0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x00, 0x00},
};
/** /**
* 获取农历月份的天数 * 获取农历月份的天数
* *
@@ -593,6 +664,7 @@ static uint8_t batt_cal(uint16_t adc_sample)
return batt_lvl; return batt_lvl;
} }
/** /**
* 绘制电池电量图标 * 绘制电池电量图标
* *
@@ -722,6 +794,51 @@ static void epd_wait_timer(void)
} }
} }
void QR_draw()
{
// 此处添加QR码绘制逻辑
epd_hw_open();
epd_update_mode(UPDATE_FULL);
memset(fb_bw, 0xff, scr_h*line_bytes);
memset(fb_rr, 0x00, scr_h*line_bytes);
draw_qr_code(20, 5, 3, QR_31x31);
draw_text(120, 5, "USE WEB", BLACK);
draw_text(120, 20, "Scan QR", BLACK);
// 墨水屏更新显示
epd_init();
epd_screen_update();
epd_update();
// 更新时如果深度休眠,会花屏。 这里暂时关闭休眠。
arch_set_sleep_mode(ARCH_SLEEP_OFF);
epd_wait_hnd = app_easy_timer(40, epd_wait_timer);
}
void LB_draw()
{
// 此处添加低电压码的绘制逻辑
epd_hw_open();
epd_update_mode(UPDATE_FULL);
memset(fb_bw, 0xff, scr_h*line_bytes);
memset(fb_rr, 0x00, scr_h*line_bytes);
draw_qr_code(20, 10, 4, LB_31x31);
// 墨水屏更新显示
epd_init();
epd_screen_update();
epd_update();
// 更新时如果深度休眠,会花屏。 这里暂时关闭休眠。
arch_set_sleep_mode(ARCH_SLEEP_OFF);
epd_wait_hnd = app_easy_timer(40, epd_wait_timer);
}
/** /**
* 绘制时钟界面 * 绘制时钟界面
* *

View File

@@ -84,7 +84,8 @@ void clock_print(void);
void clock_set(uint8_t *buf); void clock_set(uint8_t *buf);
void clock_push(void); void clock_push(void);
void clock_draw(int full); void clock_draw(int full);
void QR_draw(void);
void LB_draw(void);
/** /**
**************************************************************************************** ****************************************************************************************

View File

@@ -3,30 +3,11 @@
* *
* @file user_peripheral.c * @file user_peripheral.c
* *
* @brief Peripheral project source code. * @brief 外设项目源代码文件主要实现BLE外设功能、时钟管理、EPD屏幕显示及OTP数据读取等功能
* *
* Copyright (C) 2015-2023 Renesas Electronics Corporation and/or its affiliates. * Copyright (C) 2015-2023 Renesas Electronics Corporation and/or its affiliates.
* All rights reserved. Confidential Information. * All rights reserved. Confidential Information.
* *
* This software ("Software") is supplied by Renesas Electronics Corporation and/or its
* affiliates ("Renesas"). Renesas grants you a personal, non-exclusive, non-transferable,
* revocable, non-sub-licensable right and license to use the Software, solely if used in
* or together with Renesas products. You may make copies of this Software, provided this
* copyright notice and disclaimer ("Notice") is included in all such copies. Renesas
* reserves the right to change or discontinue the Software at any time without notice.
*
* THE SOFTWARE IS PROVIDED "AS IS". RENESAS DISCLAIMS ALL WARRANTIES OF ANY KIND,
* WHETHER EXPRESS, IMPLIED, OR STATUTORY, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. TO THE
* MAXIMUM EXTENT PERMITTED UNDER LAW, IN NO EVENT SHALL RENESAS BE LIABLE FOR ANY DIRECT,
* INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE, EVEN IF RENESAS HAS BEEN ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGES. USE OF THIS SOFTWARE MAY BE SUBJECT TO TERMS AND CONDITIONS CONTAINED IN
* AN ADDITIONAL AGREEMENT BETWEEN YOU AND RENESAS. IN CASE OF CONFLICT BETWEEN THE TERMS
* OF THIS NOTICE AND ANY SUCH ADDITIONAL LICENSE AGREEMENT, THE TERMS OF THE AGREEMENT
* SHALL TAKE PRECEDENCE. BY CONTINUING TO USE THIS SOFTWARE, YOU AGREE TO THE TERMS OF
* THIS NOTICE.IF YOU DO NOT AGREE TO THESE TERMS, YOU ARE NOT PERMITTED TO USE THIS
* SOFTWARE.
* *
**************************************************************************************** ****************************************************************************************
*/ */
@@ -39,90 +20,86 @@
*/ */
/* /*
* INCLUDE FILES * 包含头文件
**************************************************************************************** ****************************************************************************************
*/ */
#include "rwip_config.h" // SW configuration #include "rwip_config.h" // 软件配置
#include "gattc_task.h" #include "gattc_task.h" // GATT客户端任务相关定义
#include "gap.h" #include "gap.h" // GAP层相关定义
#include "app_easy_timer.h" #include "app_easy_timer.h" // 应用层定时器功能
#include "user_peripheral.h" #include "user_peripheral.h" // 本文件接口声明
#include "user_custs1_impl.h" #include "user_custs1_impl.h" // 自定义服务1实现
#include "user_custs1_def.h" #include "user_custs1_def.h" // 自定义服务1定义
#include "co_bt.h" #include "co_bt.h" // 蓝牙协议协议相关定义
#include "hw_otpc.h" #include "hw_otpc.h" // OTP控制器硬件接口
#include "epd.h" #include "epd.h" // EPD电子纸屏幕驱动
/* /*
* TYPE DEFINITIONS * 类型定义
**************************************************************************************** ****************************************************************************************
*/ */
/* /*
* GLOBAL VARIABLE DEFINITIONS * 全局变量定义
**************************************************************************************** ****************************************************************************************
*/ */
int app_connection_idx __SECTION_ZERO("retention_mem_area0"); int app_connection_idx __SECTION_ZERO("retention_mem_area0"); // 连接索引,使用 retention 内存区域保存
timer_hnd app_clock_timer_used __SECTION_ZERO("retention_mem_area0"); //@RETENTION MEMORY timer_hnd app_clock_timer_used __SECTION_ZERO("retention_mem_area0"); // 时钟定时器句柄retention内存保存
timer_hnd app_param_update_request_timer_used __SECTION_ZERO("retention_mem_area0"); timer_hnd app_param_update_request_timer_used __SECTION_ZERO("retention_mem_area0"); // 参数更新请求定时器句柄retention内存保存
static int adv_state; static int adv_state; // 广播状态0-未广播1-正在广播
static int otp_btaddr[2]; static int otp_btaddr[2]; // 从OTP读取的蓝牙地址
static int otp_boot; static int otp_boot; // 从OTP读取的启动相关数据
static char adv_name[20]; static char adv_name[20]; // 广播名称缓冲区
char *bt_id = adv_name+12; char *bt_id = adv_name+12; // 蓝牙ID在广播名称中的起始位置
int clock_interval; int clock_interval; // 时钟更新间隔(秒)
int clock_fixup_value; int clock_fixup_value; // 时钟修正值
int clock_fixup_count; int clock_fixup_count; // 时钟修正计数器
// EPD版本信息volatile确保不被优化用于版本检测
const volatile u32 epd_version[3] = {0xF9A51379, ~0xF9A51379, EPD_VERSION}; const volatile u32 epd_version[3] = {0xF9A51379, ~0xF9A51379, EPD_VERSION};
extern int year,month; // 当前时间变量
/* /*
* FUNCTION DEFINITIONS * 函数定义
**************************************************************************************** ****************************************************************************************
*/ */
/** /**
**************************************************************************************** ****************************************************************************************
* @brief Add an AD structure in the Advertising or Scan Response Data of the * @brief 在GAPM_START_ADVERTISE_CMD参数结构的广播或扫描响应数据中添加AD结构
* GAPM_START_ADVERTISE_CMD parameter struct. * @param[in] cmd GAPM_START_ADVERTISE_CMD参数结构
* @param[in] cmd GAPM_START_ADVERTISE_CMD parameter struct * @param[in] ad_struct_data AD结构数据缓冲区
* @param[in] ad_struct_data AD structure buffer * @param[in] ad_struct_len AD结构长度
* @param[in] ad_struct_len AD structure length * @param[in] adv_connectable 是否为可连接广播事件控制广播数据最大长度可连接时28字节否则31字节
* @param[in] adv_connectable Connectable advertising event or not. It controls whether
* the advertising data use the full 31 bytes length or only
* 28 bytes (Document CCSv6 - Part 1.3 Flags).
**************************************************************************************** ****************************************************************************************
*/ */
static void app_add_ad_struct(struct gapm_start_advertise_cmd *cmd, void *ad_struct_data, uint8_t ad_struct_len, uint8_t adv_connectable) static void app_add_ad_struct(struct gapm_start_advertise_cmd *cmd, void *ad_struct_data, uint8_t ad_struct_len, uint8_t adv_connectable)
{ {
// 根据是否可连接确定广播数据最大长度
uint8_t adv_data_max_size = (adv_connectable) ? (ADV_DATA_LEN - 3) : (ADV_DATA_LEN); uint8_t adv_data_max_size = (adv_connectable) ? (ADV_DATA_LEN - 3) : (ADV_DATA_LEN);
// 优先添加到广播数据
if ((adv_data_max_size - cmd->info.host.adv_data_len) >= ad_struct_len) if ((adv_data_max_size - cmd->info.host.adv_data_len) >= ad_struct_len)
{ {
// Append manufacturer data to advertising data
memcpy(&cmd->info.host.adv_data[cmd->info.host.adv_data_len], ad_struct_data, ad_struct_len); memcpy(&cmd->info.host.adv_data[cmd->info.host.adv_data_len], ad_struct_data, ad_struct_len);
// Update Advertising Data Length
cmd->info.host.adv_data_len += ad_struct_len; cmd->info.host.adv_data_len += ad_struct_len;
} }
// 广播数据空间不足时添加到扫描响应数据
else if ((SCAN_RSP_DATA_LEN - cmd->info.host.scan_rsp_data_len) >= ad_struct_len) else if ((SCAN_RSP_DATA_LEN - cmd->info.host.scan_rsp_data_len) >= ad_struct_len)
{ {
// Append manufacturer data to scan response data
memcpy(&cmd->info.host.scan_rsp_data[cmd->info.host.scan_rsp_data_len], ad_struct_data, ad_struct_len); memcpy(&cmd->info.host.scan_rsp_data[cmd->info.host.scan_rsp_data_len], ad_struct_data, ad_struct_len);
// Update Scan Response Data Length
cmd->info.host.scan_rsp_data_len += ad_struct_len; cmd->info.host.scan_rsp_data_len += ad_struct_len;
} }
// 空间不足时触发断言警告
else else
{ {
// Manufacturer Specific Data do not fit in either Advertising Data or Scan Response Data
ASSERT_WARNING(0); ASSERT_WARNING(0);
} }
} }
@@ -130,27 +107,36 @@ static void app_add_ad_struct(struct gapm_start_advertise_cmd *cmd, void *ad_str
/** /**
**************************************************************************************** ****************************************************************************************
* @brief Parameter update request timer callback function. * @brief 参数更新请求定时器回调函数
* 当定时器超时,发起连接参数更新请求
**************************************************************************************** ****************************************************************************************
*/ */
static void param_update_request_timer_cb() static void param_update_request_timer_cb()
{ {
app_easy_gap_param_update_start(app_connection_idx); app_easy_gap_param_update_start(app_connection_idx); // 发起参数更新
app_param_update_request_timer_used = EASY_TIMER_INVALID_TIMER; app_param_update_request_timer_used = EASY_TIMER_INVALID_TIMER; // 重置定时器句柄
} }
/**
****************************************************************************************
* @brief 读取OTP一次性可编程存储器中的值
* 主要读取蓝牙地址和启动信息,并生成广播名称
****************************************************************************************
*/
static void read_otp_value(void) static void read_otp_value(void)
{ {
hw_otpc_init(); hw_otpc_init(); // 初始化OTP控制器
hw_otpc_manual_read_on(false); hw_otpc_manual_read_on(false); // 关闭手动读取模式
otp_boot = *(u32*)(0x07f8fe00); // 从OTP特定地址读取数据
otp_btaddr[0] = *(u32*)(0x07f8ffa8); otp_boot = *(u32*)(0x07f8fe00); // 读取启动相关数据
otp_btaddr[1] = *(u32*)(0x07f8ffac); otp_btaddr[0] = *(u32*)(0x07f8ffa8); // 读取蓝牙地址低32位
otp_btaddr[1] = *(u32*)(0x07f8ffac); // 读取蓝牙地址高32位
hw_otpc_disable(); hw_otpc_disable(); // 禁用OTP控制器
// 处理蓝牙地址,生成设备唯一标识
u32 ba0 = otp_btaddr[0]; u32 ba0 = otp_btaddr[0];
u32 ba1 = otp_btaddr[1]; u32 ba1 = otp_btaddr[1];
@@ -158,190 +144,279 @@ static void read_otp_value(void)
ba0 &= 0x00ffffff; ba0 &= 0x00ffffff;
ba0 ^= ba1; ba0 ^= ba1;
// 生成广播名称格式DLG-CLOCK-XXYYZZXXYYZZ为蓝牙地址后三段
u8 *ba = (u8*)&ba0; u8 *ba = (u8*)&ba0;
sprintf(adv_name+2, "DLG-CLOCK-%02x%02x%02x", ba[2], ba[1], ba[0]); sprintf(adv_name+2, "DLG-CLOCK-%02x%02x%02x", ba[2], ba[1], ba[0]);
int name_len = strlen(adv_name+2); int name_len = strlen(adv_name+2);
// 如果设备名称未设置,则使用生成的名称
if(device_info.dev_name.length==0){ if(device_info.dev_name.length==0){
device_info.dev_name.length = name_len; device_info.dev_name.length = name_len;
memcpy(device_info.dev_name.name, adv_name+2, name_len); memcpy(device_info.dev_name.name, adv_name+2, name_len);
} }
// 构造AD结构第一个字节为长度第二个字节为AD类型完整名称
adv_name[0] = name_len+1; adv_name[0] = name_len+1;
adv_name[1] = GAP_AD_TYPE_COMPLETE_NAME; adv_name[1] = GAP_AD_TYPE_COMPLETE_NAME;
} }
// 外部声明的区域表基地址(用于内存相关操作)
extern int Region$$Table$$Base; extern int Region$$Table$$Base;
/**
****************************************************************************************
* @brief 应用初始化函数
* 初始化OTP数据、定时器、屏幕、蓝牙等模块
****************************************************************************************
*/
void user_app_init(void) void user_app_init(void)
{ {
read_otp_value(); read_otp_value(); // 读取OTP数据初始化广播名称
printk("\n\nuser_app_init! %s %08x\n", __TIME__, epd_version[2]); printk("\n\nuser_app_init! %s %08x\n", __TIME__, epd_version[2]);
app_param_update_request_timer_used = EASY_TIMER_INVALID_TIMER; app_param_update_request_timer_used = EASY_TIMER_INVALID_TIMER; // 初始化参数更新定时器
app_clock_timer_used = EASY_TIMER_INVALID_TIMER; app_clock_timer_used = EASY_TIMER_INVALID_TIMER; // 初始化时钟定时器
clock_interval = 60; // 60s clock_interval = 60; // 时钟更新间隔设置为60
clock_fixup_value = 0; clock_fixup_value = 0; // 初始化时钟修正值
clock_fixup_count = 0; clock_fixup_count = 0; // 初始化时钟修正计数器
adv_state = 0; adv_state = 0; // 初始化为未广播状态
fspi_config(0x00030605); fspi_config(0x00030605); // 配置FSPI接口
selflash(otp_boot); selflash(otp_boot); // 根据OTP启动数据执行自闪存操作
epd_hw_init(0x23200700, 0x05210006, detect_w, detect_h, detect_mode | ROTATE_3); // 2.13黑白屏6个测试点 // 初始化EPD屏幕2.13黑白屏6个测试点
if(epd_detect()==0){ epd_hw_init(0x23200700, 0x05210006, detect_w, detect_h, detect_mode | ROTATE_3);
epd_hw_init(0x23111000, 0x07210120, detect_w, detect_h, detect_mode | ROTATE_3); // 2.13黑白屏,5个测试点 if(epd_detect()==0){ // 如果检测不到屏幕,尝试另一种配置(5个测试点
epd_hw_init(0x23111000, 0x07210120, detect_w, detect_h, detect_mode | ROTATE_3);
epd_detect(); epd_detect();
} }
app_connection_idx = -1; // 初始化连接索引为无效值
app_connection_idx = -1; default_app_on_init(); // 执行默认应用初始化
default_app_on_init();
} }
// 时钟快慢修正 /**
// minutes : 自上次对时后,经过的分钟数 ****************************************************************************************
// diff_sec: 自上次对时后,误差的秒 * @brief 时钟快慢修正函
// 误差为正数,说明时钟快了。将定时器加上一个修正值,让它慢一点。 * @param[in] diff_sec 自上次对时后的误差秒数(正数表示快了,负数表示慢了)
// 误差为负数,说明时钟慢了。将定时器减去一个修正值,让它快一点。 * @param[in] minutes 自上次对时后经过的分钟数
* @note 计算并累积时钟修正值,用于调整定时器间隔,补偿时钟误差
****************************************************************************************
*/
void clock_fixup_set(int diff_sec, int minutes) void clock_fixup_set(int diff_sec, int minutes)
{ {
// 计算新的修正值基于4096精度的分数计算
int new_fixup_value = diff_sec*100*4096/minutes; int new_fixup_value = diff_sec*100*4096/minutes;
clock_fixup_value += new_fixup_value; clock_fixup_value += new_fixup_value; // 累积修正值
} }
/**
****************************************************************************************
* @brief 应用时钟修正值
* @return 本次需要调整的毫秒数
* @note 从累积的修正计数器中提取整数部分作为本次调整值,保留余数
****************************************************************************************
*/
static int clock_fixup(void) static int clock_fixup(void)
{ {
int value; int value;
clock_fixup_count += clock_fixup_value; clock_fixup_count += clock_fixup_value; // 累积修正计数
value = clock_fixup_count>>12; value = clock_fixup_count>>12; // 右移12位除以4096得到整数部分
clock_fixup_count &= 0xfff; clock_fixup_count &= 0xfff; // 保留低12位作为余数
return value; return value; // 返回本次调整的毫秒数
} }
extern int adcval; // ADC电压值变量
/**
****************************************************************************************
* @brief 应用时钟定时器回调函数
* 定时更新时钟、推送时钟数据、处理屏幕显示,并根据需要重启广播
****************************************************************************************
*/
static void app_clock_timer_cb(void) static void app_clock_timer_cb(void)
{ {
int adj = clock_fixup(); int adj = clock_fixup(); // 获取时钟修正值
// 重启定时器应用修正后的间隔单位10ms故乘以100
app_clock_timer_used = app_easy_timer(clock_interval*100+adj, app_clock_timer_cb); app_clock_timer_used = app_easy_timer(clock_interval*100+adj, app_clock_timer_cb);
// 确定屏幕更新标志(根据时钟状态)
int flags = UPDATE_FLY; // 默认快速更新
// 更新时钟并打印
int stat = clock_update(clock_interval); int stat = clock_update(clock_interval);
clock_print(); clock_print();
// 如果已连接,则推送时钟数据
if(app_connection_idx!=-1){ if(app_connection_idx!=-1){
clock_push(); clock_push();
} }
//未进行初始化,则始终展示二维码
if(year==2025 && month<=5){
// 在2024年2月执行特定操作占位符
QR_draw();
user_app_adv_start();//持续开启广播
return;
}
int flags = UPDATE_FLY; // 如果是快速更新更新ADC数据,若电量不足则不继续执行任务
if(stat>=3){
flags = DRAW_BT | UPDATE_FULL;
}else if(stat>=2){
flags = DRAW_BT | UPDATE_FAST;
}
if(flags==4){ if(flags==4){
adc1_update(); adc1_update();
if(adcval<1360){
LB_draw();
return;
}
} }
if(stat>=3){
flags = DRAW_BT | UPDATE_FULL; // 需要蓝牙图标+全量更新
}else if(stat>=2){
flags = DRAW_BT | UPDATE_FAST; // 需要蓝牙图标+快速更新
}
// 如果需要显示蓝牙图标,启动广播
if(flags&DRAW_BT){ if(flags&DRAW_BT){
user_app_adv_start(); user_app_adv_start();
} }
// 根据状态或标志更新屏幕显示
if(stat>0 || flags&DRAW_BT){ if(stat>0 || flags&DRAW_BT){
clock_draw(flags); clock_draw(flags);
} }
} }
/**
****************************************************************************************
* @brief 重启应用时钟定时器
****************************************************************************************
*/
void app_clock_timer_restart(void) void app_clock_timer_restart(void)
{ {
app_easy_timer_cancel(app_clock_timer_used); app_easy_timer_cancel(app_clock_timer_used); // 取消当前定时器
// 以默认间隔重启定时器
app_clock_timer_used = app_easy_timer(clock_interval*100, app_clock_timer_cb); app_clock_timer_used = app_easy_timer(clock_interval*100, app_clock_timer_cb);
} }
/**
****************************************************************************************
* @brief 数据库初始化完成回调函数
* 当GATT数据库初始化完成后调用初始化ADC、显示时钟并启动广播
****************************************************************************************
*/
void user_app_on_db_init_complete( void ) void user_app_on_db_init_complete( void )
{ {
printk("\nuser_app_on_db_init_complete!\n"); printk("\nuser_app_on_db_init_complete!\n");
// 更新ADC值并打印电压
int adcval = adc1_update(); int adcval = adc1_update();
printk("Voltage: %d\n", adcval); printk("Voltage: %d\n", adcval);
// 打印并推送时钟数据
clock_print(); clock_print();
clock_push(); clock_push();
// 绘制时钟(带蓝牙图标+全量更新)并启动广播
clock_draw(DRAW_BT|UPDATE_FULL); clock_draw(DRAW_BT|UPDATE_FULL);
user_app_adv_start(); user_app_adv_start();
// 启动时钟定时器
app_clock_timer_used = app_easy_timer(clock_interval*100, app_clock_timer_cb); app_clock_timer_used = app_easy_timer(clock_interval*100, app_clock_timer_cb);
} }
/**
****************************************************************************************
* @brief 启动应用广播
* 构造广播数据包含设备名称和EPD版本并启动带超时的无向广播
****************************************************************************************
*/
void user_app_adv_start(void) void user_app_adv_start(void)
{ {
u8 vbuf[4]; u8 vbuf[4]; // 版本信息AD结构缓冲区
// 如果已在广播状态,直接返回
if(adv_state) if(adv_state)
return; return;
adv_state = 1; adv_state = 1; // 标记为正在广播
struct gapm_start_advertise_cmd* cmd = app_easy_gap_undirected_advertise_get_active(); // 获取广播命令结构
struct gapm_start_advertise_cmd* cmd = app_easy_gap_undirected_advertise_get_active();
// 添加设备名称AD结构
app_add_ad_struct(cmd, adv_name, adv_name[0]+1, 1); app_add_ad_struct(cmd, adv_name, adv_name[0]+1, 1);
// 构造版本信息AD结构长度+类型+版本号低两位)
vbuf[0] = 0x03; vbuf[0] = 0x03;
vbuf[1] = GAP_AD_TYPE_MANU_SPECIFIC_DATA; vbuf[1] = GAP_AD_TYPE_MANU_SPECIFIC_DATA;
vbuf[2] = EPD_VERSION&0xff; vbuf[2] = EPD_VERSION&0xff;
vbuf[3] = (EPD_VERSION>>8)&0xff; vbuf[3] = (EPD_VERSION>>8)&0xff;
app_add_ad_struct(cmd, vbuf, vbuf[0]+1, 1); app_add_ad_struct(cmd, vbuf, vbuf[0]+1, 1);
// 启动带超时的无向广播
//default_advertise_operation();
//app_easy_gap_undirected_advertise_start();
app_easy_gap_undirected_advertise_with_timeout_start(user_default_hnd_conf.advertise_period, NULL); app_easy_gap_undirected_advertise_with_timeout_start(user_default_hnd_conf.advertise_period, NULL);
printk("\nuser_app_adv_start! %s\n", adv_name+2); printk("\nuser_app_adv_start! %s\n", adv_name+2);
} }
/**
****************************************************************************************
* @brief 连接事件回调函数
* 当收到连接请求时调用,更新连接索引,检查连接参数并在需要时请求参数更新
* @param[in] connection_idx 连接索引
* @param[in] param 连接请求参数
****************************************************************************************
*/
void user_app_connection(uint8_t connection_idx, struct gapc_connection_req_ind const *param) void user_app_connection(uint8_t connection_idx, struct gapc_connection_req_ind const *param)
{ {
printk("user_app_connection: %d\n", connection_idx); printk("user_app_connection: %d\n", connection_idx);
// 检查连接是否有效
if (app_env[connection_idx].conidx != GAP_INVALID_CONIDX) if (app_env[connection_idx].conidx != GAP_INVALID_CONIDX)
{ {
app_connection_idx = connection_idx; app_connection_idx = connection_idx; // 更新连接索引
// 打印连接参数
printk(" interval: %d\n", param->con_interval); printk(" interval: %d\n", param->con_interval);
printk(" latency : %d\n", param->con_latency); printk(" latency : %d\n", param->con_latency);
printk(" sup_to : %d\n", param->sup_to); printk(" sup_to : %d\n", param->sup_to);
// Check if the parameters of the established connection are the preferred ones.
// If not then schedule a connection parameter update request. // 检查连接参数是否符合预期,不符合则调度参数更新请求
if ((param->con_interval < user_connection_param_conf.intv_min) || if ((param->con_interval < user_connection_param_conf.intv_min) ||
(param->con_interval > user_connection_param_conf.intv_max) || (param->con_interval > user_connection_param_conf.intv_max) ||
(param->con_latency != user_connection_param_conf.latency) || (param->con_latency != user_connection_param_conf.latency) ||
(param->sup_to != user_connection_param_conf.time_out)) (param->sup_to != user_connection_param_conf.time_out))
{ {
// Connection params are not these that we expect
app_param_update_request_timer_used = app_easy_timer(APP_PARAM_UPDATE_REQUEST_TO, param_update_request_timer_cb); app_param_update_request_timer_used = app_easy_timer(APP_PARAM_UPDATE_REQUEST_TO, param_update_request_timer_cb);
} }
// 推送时钟数据到客户端
clock_push(); clock_push();
} else { } else {
adv_state = 0; adv_state = 0; // 连接无效时,标记为未广播
} }
// 执行默认连接处理
default_app_on_connection(connection_idx, param); default_app_on_connection(connection_idx, param);
} }
/**
****************************************************************************************
* @brief 无向广播完成回调函数
* 当广播超时或异常结束时调用,更新广播状态并刷新屏幕
* @param[in] status 广播结束状态码
****************************************************************************************
*/
void user_app_adv_undirect_complete(uint8_t status) void user_app_adv_undirect_complete(uint8_t status)
{ {
printk("user_app_adv_undirect_complete: %02x\n", status); printk("user_app_adv_undirect_complete: %02x\n", status);
// 状态非0表示异常结束更新广播状态并刷新屏幕
if(status!=0){ if(status!=0){
adv_state = 0; adv_state = 0;
clock_draw(UPDATE_FLY); clock_draw(UPDATE_FLY);
@@ -349,22 +424,29 @@ void user_app_adv_undirect_complete(uint8_t status)
} }
/**
****************************************************************************************
* @brief 断开连接回调函数
* 当连接断开时调用,清理定时器,更新连接状态,并根据断开原因决定是否重启广播
* @param[in] param 断开连接参数(包含断开原因)
****************************************************************************************
*/
void user_app_disconnect(struct gapc_disconnect_ind const *param) void user_app_disconnect(struct gapc_disconnect_ind const *param)
{ {
printk("user_app_disconnect! reason=%02x\n", param->reason); printk("user_app_disconnect! reason=%02x\n", param->reason);
// Cancel the parameter update request timer // 取消参数更新请求定时器
if (app_param_update_request_timer_used != EASY_TIMER_INVALID_TIMER) if (app_param_update_request_timer_used != EASY_TIMER_INVALID_TIMER)
{ {
app_easy_timer_cancel(app_param_update_request_timer_used); app_easy_timer_cancel(app_param_update_request_timer_used);
app_param_update_request_timer_used = EASY_TIMER_INVALID_TIMER; app_param_update_request_timer_used = EASY_TIMER_INVALID_TIMER;
} }
app_connection_idx = -1; app_connection_idx = -1; // 重置连接索引为无效值
adv_state = 0; adv_state = 0; // 标记为未广播
// 非远程用户主动断开时,重启广播;否则仅刷新屏幕
if(param->reason!=CO_ERROR_REMOTE_USER_TERM_CON){ if(param->reason!=CO_ERROR_REMOTE_USER_TERM_CON){
// 非主动断开连接时, 重新广播.
user_app_adv_start(); user_app_adv_start();
}else{ }else{
clock_draw(UPDATE_FLY); clock_draw(UPDATE_FLY);
@@ -373,6 +455,16 @@ void user_app_disconnect(struct gapc_disconnect_ind const *param)
} }
/**
****************************************************************************************
* @brief 未处理消息的捕获处理函数
* 处理各类未被默认处理的消息包括特征值读写、参数更新、MTU变更等事件
* @param[in] msgid 消息ID
* @param[in] param 消息参数
* @param[in] dest_id 目标任务ID
* @param[in] src_id 源任务ID
****************************************************************************************
*/
void user_catch_rest_hndl(ke_msg_id_t const msgid, void user_catch_rest_hndl(ke_msg_id_t const msgid,
void const *param, void const *param,
ke_task_id_t const dest_id, ke_task_id_t const dest_id,
@@ -380,12 +472,12 @@ void user_catch_rest_hndl(ke_msg_id_t const msgid,
{ {
switch(msgid) switch(msgid)
{ {
// 特征值写入通知(值已写入数据库)
case CUSTS1_VAL_WRITE_IND: case CUSTS1_VAL_WRITE_IND:
{ {
/* 写特征值通知. 值已经写入Database中了. */
//printk("CUSTS1_VAL_WRITE_IND!\n");
struct custs1_val_write_ind const *msg_param = (struct custs1_val_write_ind const *)(param); struct custs1_val_write_ind const *msg_param = (struct custs1_val_write_ind const *)(param);
// 根据句柄分发到对应的处理函数
switch (msg_param->handle) switch (msg_param->handle)
{ {
case SVC1_IDX_CONTROL_POINT_VAL: case SVC1_IDX_CONTROL_POINT_VAL:
@@ -401,22 +493,22 @@ void user_catch_rest_hndl(ke_msg_id_t const msgid,
} }
} break; } break;
// Notification确认请求已发出
case CUSTS1_VAL_NTF_CFM: case CUSTS1_VAL_NTF_CFM:
{ {
/* Notification确认. 请求已经发出. */
} break; } break;
// Indication确认请求已发出
case CUSTS1_VAL_IND_CFM: case CUSTS1_VAL_IND_CFM:
{ {
/* Indication确认. 请求已经发出. */
} break; } break;
// 读ATT_INFO请求需要返回数据
case CUSTS1_ATT_INFO_REQ: case CUSTS1_ATT_INFO_REQ:
{ {
/* 读ATT_INFO请求. 需要返回数据. */
//printk("CUSTS1_ATT_INFO_REQ!\n");
struct custs1_att_info_req const *msg_param = (struct custs1_att_info_req const *)param; struct custs1_att_info_req const *msg_param = (struct custs1_att_info_req const *)param;
// 根据属性索引分发处理
switch (msg_param->att_idx) switch (msg_param->att_idx)
{ {
case SVC1_IDX_LONG_VALUE_VAL: case SVC1_IDX_LONG_VALUE_VAL:
@@ -429,16 +521,17 @@ void user_catch_rest_hndl(ke_msg_id_t const msgid,
} }
} break; } break;
// 连接参数更新通知
case GAPC_PARAM_UPDATED_IND: case GAPC_PARAM_UPDATED_IND:
{ {
// Cast the "param" pointer to the appropriate message structure
struct gapc_param_updated_ind const *msg_param = (struct gapc_param_updated_ind const *)(param); struct gapc_param_updated_ind const *msg_param = (struct gapc_param_updated_ind const *)(param);
printk("GAPC_PARAM_UPDATED_IND!\n"); printk("GAPC_PARAM_UPDATED_IND!\n");
// 打印更新后的参数
printk(" interval: %d\n", msg_param->con_interval); printk(" interval: %d\n", msg_param->con_interval);
printk(" latency : %d\n", msg_param->con_latency); printk(" latency : %d\n", msg_param->con_latency);
printk(" sup_to : %d\n", msg_param->sup_to); printk(" sup_to : %d\n", msg_param->sup_to);
// Check if updated Conn Params filled to preferred ones // 检查更新后的参数是否符合预期
if ((msg_param->con_interval >= user_connection_param_conf.intv_min) && if ((msg_param->con_interval >= user_connection_param_conf.intv_min) &&
(msg_param->con_interval <= user_connection_param_conf.intv_max) && (msg_param->con_interval <= user_connection_param_conf.intv_max) &&
(msg_param->con_latency == user_connection_param_conf.latency) && (msg_param->con_latency == user_connection_param_conf.latency) &&
@@ -448,51 +541,48 @@ void user_catch_rest_hndl(ke_msg_id_t const msgid,
} }
} break; } break;
// 特征值读取请求
case CUSTS1_VALUE_REQ_IND: case CUSTS1_VALUE_REQ_IND:
{ {
/* 读特征值. 什么时候会有这个事件? */
printk("CUSTS1_VALUE_REQ_IND!\n"); printk("CUSTS1_VALUE_REQ_IND!\n");
struct custs1_value_req_ind const *msg_param = (struct custs1_value_req_ind const *) param; struct custs1_value_req_ind const *msg_param = (struct custs1_value_req_ind const *) param;
// 处理未定义的读取请求,返回错误
switch (msg_param->att_idx) switch (msg_param->att_idx)
{ {
default: default:
{ {
// Send Error message
struct custs1_value_req_rsp *rsp = KE_MSG_ALLOC(CUSTS1_VALUE_REQ_RSP, struct custs1_value_req_rsp *rsp = KE_MSG_ALLOC(CUSTS1_VALUE_REQ_RSP,
src_id, src_id,
dest_id, dest_id,
custs1_value_req_rsp); custs1_value_req_rsp);
// Provide the connection index.
rsp->conidx = app_env[msg_param->conidx].conidx; rsp->conidx = app_env[msg_param->conidx].conidx;
// Provide the attribute index.
rsp->att_idx = msg_param->att_idx; rsp->att_idx = msg_param->att_idx;
// Force current length to zero.
rsp->length = 0; rsp->length = 0;
// Set Error status
rsp->status = ATT_ERR_APP_ERROR; rsp->status = ATT_ERR_APP_ERROR;
// Send message
KE_MSG_SEND(rsp); KE_MSG_SEND(rsp);
} break; } break;
} }
} break; } break;
// GATT事件请求指示确认未处理的指示以避免超时
case GATTC_EVENT_REQ_IND: case GATTC_EVENT_REQ_IND:
{ {
// Confirm unhandled indication to avoid GATT timeout
struct gattc_event_ind const *ind = (struct gattc_event_ind const *) param; struct gattc_event_ind const *ind = (struct gattc_event_ind const *) param;
struct gattc_event_cfm *cfm = KE_MSG_ALLOC(GATTC_EVENT_CFM, src_id, dest_id, gattc_event_cfm); struct gattc_event_cfm *cfm = KE_MSG_ALLOC(GATTC_EVENT_CFM, src_id, dest_id, gattc_event_cfm);
cfm->handle = ind->handle; cfm->handle = ind->handle;
KE_MSG_SEND(cfm); KE_MSG_SEND(cfm);
} break; } break;
// MTU最大传输单元变更指示
case GATTC_MTU_CHANGED_IND: case GATTC_MTU_CHANGED_IND:
{ {
struct gattc_mtu_changed_ind *ind = (struct gattc_mtu_changed_ind *) param; struct gattc_mtu_changed_ind *ind = (struct gattc_mtu_changed_ind *) param;
printk("GATTC_MTU_CHANGED_IND: %d\n", ind->mtu); printk("GATTC_MTU_CHANGED_IND: %d\n", ind->mtu);
} break; } break;
// 未处理的消息
default: default:
{ {
printk("Unhandled msgid=%08x\n", msgid); printk("Unhandled msgid=%08x\n", msgid);
@@ -500,4 +590,4 @@ void user_catch_rest_hndl(ke_msg_id_t const msgid,
} }
} }
/// @} APP /// @} APP