当前文档版本为 v0.5.0,您可以访问当前页面的 开发中 版本以获取最近可能的更新。

Solution: Multi Model Mouse

重要

此例程仅存在于特殊版本的SDK中,如有需要请联系Panchip。

1 功能概述

此sample为pan108xxb5(64pin芯片)在实体鼠标板下的应用

具体支持的feature如下:

  • 通用功能:

    1. 光电传感:通过sensor进行鼠标基础坐标获取,可以通过按键进行切换Sensor DPI切换(PAW3325 800-1600(默认)-2400-3200-6400-12000)

    2. QDEC滚轮模块:支持去抖的正交解码器,反馈鼠标滚轮变化情况

    3. 按键模块:

      a. 基础按键左中右,中键长按1s切换2.4G上报率(1000-500-250-125),左中右长按进行强制对码

      b. 底部具有模式切换键

    4. LED模块:反馈对码情况(2.4G未对码/强制对码快闪黄灯,已存储配对信息慢闪黄灯,已连接RB紫色灯呼吸)

    5. 电量检测:ADC采集电量信息,通过串口反馈电量情况

    6. 供电及模式切换:

      ​ a. 电池供电:底部按键可以切换2.4G模式与蓝牙模式,开关拨到中间中间为断电状态,USB接入PC时为优先级最高的USB模式

      ​ b. USB仅供电:底部按键可以切换2.4G模式与蓝牙模式,中间为保电状态

      ​ c. USB插入PC:USB接入PC时为优先级最高的USB模式

  • 2.4G模式(PRF增强型模式)

    1. 跳频:在信号质量不好(连续FREQ_HOP_NOACK_THREHOLD=15个ack未收到)/对码前在8个频点进行跳频

    2. 对码:上电跳频找到dongle端的频点后,通信互发对端的MAC地址后2字节,之后切换到私有地址进行通信

    3. 重传:丢包时会进行重传,以最快速度进行重传直到收到对端回复

    4. ACK:可解析的ACK,具有演示代码

    5. 性能:默认上报率1000的情况下,近距离稳定在990包以上,8-9米稳定在960包以上;近距离可以切换上报率进行测试(可选)

    6. 测试模式(可选):宏控制自动画圈;关闭PWM灯测试功耗;设置功率测试;ACK测试

  • BLE模式

    1. 蓝牙白名单,分时连接

    2. 配对,保存

    3. 连接,重连功能:

    4. OTA(未实现):与mcuboot配合通过NRF工具进行升级

    5. 性能:133HZ上报率

    6. 性能:兼容性

  • USB模式

    1. 性能:USB2.0最高速率1000hz

    2. 电脑的休眠唤醒(未做)

    3. 升级(未做):与mcuboot配合通过USB工具进行升级

  • EMI测试(未做)

2 环境要求

  • board: pan108xxb5(芯片型号)鼠标板

  • uart (option): overlay中默认P24显示串口log

  • USB升级工具(未有)

  • 鼠标测试工具:MouseTest.exe

3 编译和烧录

例程位置:zephyr\samples_panchip\solutions\multi_model_mouse

使用 ZAL 工具可以对其进行编译、烧录、打开 VS Code 调试等操作。关于 ZAL 工具的详细介绍请参考:Zephyr APP Launcher 工具介绍

4 演示说明

芯片全部擦除还原默认状态,准备好烧录prf_model_dongle的接收器

4.1 电池供电模式

  1. 电池上电,鼠标端默认中键关闭状态

  2. 鼠标端开关拨到左边为2.4G模式,黄色灯快闪,插入接收器至PC,黄色灯变为紫色呼吸灯,可以测试鼠标基础功能,最大上报率近距离达到1000,远距离8-9m达到900+,通过按DPI键切换DPI,通过长按中键测试上报率

  3. 鼠标端开关拨到右边为蓝牙模式,可以在开启蓝牙的PC端搜索到名为Pan_Mouse的蓝牙设备,连接后进行控制,最大可以达到133hz(蓝牙性能最优情况下,目前暂未稳定)

  4. 拔出dongle,鼠标端通过拨动模式切换键再次进入2.4G模式,黄色灯慢闪,插入dongle变为呼吸灯,鼠标功能恢复

  5. 鼠标端通过拨动模式切换键再次进入BLE模式,等待蓝牙重新连接至PC,鼠标功能恢复

  6. 不拔出dongle,鼠标端再次通过拨动模式切换键进入2.4G模式,黄色灯慢闪,随后变为呼吸灯,鼠标功能恢复

  7. 长按左中右3键,黄色灯快闪,进入强制对码模式,此时重新复位dongle,鼠标对码成功呼吸灯启动,鼠标功能恢复

4.2 USB仅供电模式

​ USB供电与鼠标供电区别在于模式切换键在中间档位不断电,灯的状态暂时未固定,验证非断电模式切换正常即可

​ 模式切换键在中间状态下,插入USB供电,参考电池步骤5.6,测试2.4G与蓝牙下鼠标功能恢复

4.3 USB模式(USB插入PC)

​ 在以上任何情况下(电池供电下任何模式或者关闭/电池未供电),插入USB,鼠标变为USB功能,拔出USB退出到插入之前的状态

4.4 测试模式

4.4.1 自动画圈

#define MOUSE_AUTO_CIRCLE               1

4.4.2 开启低功耗(休眠唤醒)

CONFIG_BT_CTLR_SLEEP_CLOCK_SOURCE=1
CONFIG_PM=y

5 开发说明

5.1 架构说明

multi_model_mouse基于zephyr架构,进行多线程编程,线程静态初始化后,根据优先级进行先后初始化,之后各个线程运行至while(1)等待相应的信号量,以此通过控制信号量控制各个线程的调度关系

架构中主要包含PRF中断和多个线程

5.2 线程说明

线程定义方式如下

/**
 * @brief Statically define and initialize a thread.
 *
 * The thread may be scheduled for immediate execution or a delayed start.
 *
 * Thread options are architecture-specific, and can include K_ESSENTIAL,
 * K_FP_REGS, and K_SSE_REGS. Multiple options may be specified by separating
 * them using "|" (the logical OR operator).
 *
 * The ID of the thread can be accessed using:
 *
 * @code extern const k_tid_t <name>; @endcode
 *
 * @param name Name of the thread.
 * @param stack_size Stack size in bytes.
 * @param entry Thread entry function.
 * @param p1 1st entry point parameter.
 * @param p2 2nd entry point parameter.
 * @param p3 3rd entry point parameter.
 * @param prio Thread priority.
 * @param options Thread options.
 * @param delay Scheduling delay (in milliseconds), zero for no delay.
 *
 *
 * @internal It has been observed that the x86 compiler by default aligns
 * these _static_thread_data structures to 32-byte boundaries, thereby
 * wasting space. To work around this, force a 4-byte alignment.
 *
 */
#define K_THREAD_DEFINE(name, stack_size,                                \
			entry, p1, p2, p3,                               \
			prio, options, delay)                            \
	K_THREAD_STACK_DEFINE(_k_thread_stack_##name, stack_size);	 \
	struct k_thread _k_thread_obj_##name;				 \
	STRUCT_SECTION_ITERABLE(_static_thread_data, _k_thread_data_##name) = \
		Z_THREAD_INITIALIZER(&_k_thread_obj_##name,		 \
				    _k_thread_stack_##name, stack_size,  \
				entry, p1, p2, p3, prio, options, delay, \
				NULL, name);				 	 \
	const k_tid_t name = (k_tid_t)&_k_thread_obj_##name

根据线程定义,定义了如下几个线程

5.2.1 BLE线程

蓝牙线程,主要处理蓝牙的初始化,广播开启关闭,及蓝牙获取组包数据,

#define BLE_THREAD_PRIORITY                             2
#define BLE_THREAD_STACKSIZE                            1024
K_THREAD_DEFINE(ble, BLE_THREAD_STACKSIZE, thread_ble, NULL, NULL, NULL,
		BLE_THREAD_PRIORITY, 0, 0);

5.2.2 FRAME线程

组包线程,以timer为调度周期(给出信号量)(以上报率1000为例,调度周期1ms)

主要处理组包有关逻辑(ringbuffer的填入),组合按键检测(基础按键组包,DPI模式切换,上报率切换,低电量ADC检测)

#define FRAME_PACK_THREAD_PRIORITY                      1
#define FRAME_PACK_THREAD_STACKSIZE                     1024

K_THREAD_DEFINE(frame_pack, FRAME_PACK_THREAD_STACKSIZE, thread_frame_pack, NULL, NULL, NULL,
		FRAME_PACK_THREAD_PRIORITY, 0, 0);

5.2.3 LED线程

灯控线程,通过信号量控制主要灯的状态

#define LED_THREAD_PRIORITY             8
#define LED_THREAD_STACKSIZE            512

K_THREAD_DEFINE(led, LED_THREAD_STACKSIZE, thread_led, NULL, NULL, NULL,
		LED_THREAD_PRIORITY, 0, 0);

5.2.4 PM线程

低功耗休眠唤醒线程,控制低功耗休眠唤醒的逻辑

#define LOWPOWER_THREAD_PRIORITY                4
#define LOWPOWER_THREAD_STACKSIZE               256

K_THREAD_DEFINE(lowpower, LOWPOWER_THREAD_STACKSIZE, thread_lowpower, NULL, NULL, NULL,
		LOWPOWER_THREAD_PRIORITY, 0, 0);

5.2.5 FREQ_HOP线程

跳频线程,初始化对码线程会触发进入,在收到ACK少的时候会触发,通过信号量与PRF线程存在互斥关系

#define FREQ_HOP_THREAD_PRIORITY                2
#define FREQ_HOP_THREAD_STACKSIZE               512

K_THREAD_DEFINE(freq_hop, FREQ_HOP_THREAD_STACKSIZE, thread_freq_hop, NULL, NULL, NULL,
		FREQ_HOP_THREAD_PRIORITY, 0, 0);

5.2.6 PRF_PAIR线程

配对线程,强制对码/初始化会进入

#define PAIR_THREAD_PRIORITY            2
#define PAIR_THREAD_STACKSIZE           512

K_THREAD_DEFINE(pair, PAIR_THREAD_STACKSIZE, thread_pair, NULL, NULL, NULL,
		PAIR_THREAD_PRIORITY, 0, 0);

5.2.7 PRF线程

2.4G主线程,会进行重传处理,在重传条件下收到ACK数量低于阈值进入跳频线程

#define PRF_THREAD_PRIORITY             2
#define PRF_THREAD_STACKSIZE            512

K_THREAD_DEFINE(prf, PRF_THREAD_STACKSIZE, thread_prf, NULL, NULL, NULL,
		PRF_THREAD_PRIORITY, 0, 0);

5.2.8 TIMER线程

根据上报率处理MCU TIMER0的调度周期

#define TIMER0_THREAD_PRIORITY                          4
#define TIMER0_THREAD_STACKSIZE                         512
#define CONFIG_TIMER_IRQ_PRIO                           2

K_THREAD_DEFINE(timer0, TIMER0_THREAD_STACKSIZE, thread_timer0, NULL, NULL, NULL,
		TIMER0_THREAD_PRIORITY, 0, 0);

5.2.9 USB线程

USB线程,USB插入PC时进入,获取组包并且上报

#define USB_THREAD_PRIORITY                     2
#define USB_THREAD_STACKSIZE            512

K_THREAD_DEFINE(usb, USB_THREAD_STACKSIZE, thread_usb, NULL, NULL, NULL,
		USB_THREAD_PRIORITY, 0, 0);

5.3 RF中断说明

鼠标端为PRF TX端,增强型模式会在TX后自动转入RX, 中断中主要处理信号量sem_prf_isr的给出及ack_lost_cnt的计数

5.4 主要数据结构说明

5.4.1 枚举状态

5.4.1.1 配对状态
enum prf_pair_stat_t {
	prf_pair_start,
	prf_pair_comm,
	prf_pair_addr,
	prf_pair_end,
	prf_paired_private,
	prf_paired_public,
};
5.4.1.2.连接状态
enum ble_connect_stat_t {
	ble_disconnect_stat,
	ble_connect_stat,
};
5.4.1.3 工作模式
enum mouse_work_mode_t {
	mouse_null_mode,
	mouse_usb_mode,
	mouse_prf_mode,
	mouse_ble_mode,
};
5.4.1.4 2.4Grf状态
enum prf_trx_stat_t {
	prf_idle_stat,
	prf_tx_done_stat,
	prf_rx_done_stat,
	prf_rx_timeout_stat,
	prf_rx_crc_err_stat,
	prf_rx_pid_err_stat,
};
5.4.1.5 跳频状态
enum prf_freq_hop_stat_t {
	freq_hop_disconnect_stat,
	freq_hop_connecting_stat,
	freq_hop_done_stat,
};
5.4.1.6 USB状态
enum usb_plug_mode_t {
	usb_plug_in,
	usb_plug_out,
};
5.4.1.7 灯状态
enum mouse_led_stat_t {
	led_unpair_stat,
	led_paired_stat,
	led_low_batt_stat,
	led_prf_connected,
	led_key_stat,
};
5.4.1.8 休眠唤醒低功耗状态
enum mouse_low_power_stat_t {
	active_stat,
	prf_off_stat,
	deep_sleep_v1_stat,
	deep_sleep_v2_stat,
	standby_stat,
};

5.4.2 全局结构

5.4.2.1 组包ring_buf(zephyr)
/**
 * @brief A structure to represent a ring buffer
 */
struct ring_buf {
	uint32_t head;	 /**< Index in buf for the head element */
	uint32_t tail;	 /**< Index in buf for the tail element */
	union ring_buf_misc {
		struct ring_buf_misc_item_mode {
			uint32_t dropped_put_count; /**< Running tally of the
						     * number of failed put
						     * attempts.
						     */
		} item_mode;
		struct ring_buf_misc_byte_mode {
			uint32_t tmp_tail;
			uint32_t tmp_head;
		} byte_mode;
	} misc;
	uint32_t size;   /**< Size of buf in 32-bit chunks */

	union ring_buf_buffer {
		uint32_t *buf32; /**< Memory region for stored entries */
		uint8_t *buf8;
	} buf;
	uint32_t mask;   /**< Modulo mask if size is a power of 2 */

	struct k_spinlock lock;
};
5.4.2.2 传感器结构体
struct sensor_data_t {
	uint8_t motion;
	uint8_t observation;
	uint8_t x_delta_l;
	uint8_t x_delta_h;
	uint8_t y_delta_l;
	uint8_t y_delta_h;
};
5.4.2.3 packet格式结构体
struct pkt_detect_t {
	uint8_t key_value;
	int16_t x_value;
	int16_t y_value;
	int16_t roll_value;
	int8_t header;
	int8_t sequence;
	int16_t reserved;
};
5.4.2.4 收包计数结构体
struct prf_pkt_cnt_t {
	uint32_t tx_cnt;
	uint32_t rx_cnt;
	uint8_t prf_repeat_cnt;
};
5.4.2.5 配对信息结构体
struct pair_ctrl_t {
	enum prf_pair_stat_t prf_pair_stat;
	uint32_t prf_pair_timeout;
	uint8_t pair_own_addr[2];
	uint8_t pair_peer_addr[2];
	bool paired_flag;
};

6 补充说明

补充说明当前功耗测试情况,支持中遇到的问题(供参考)及已知仍可能存在的问题

6.1 功耗说明

功耗测试首先进行不开启休眠唤醒的测试,但需要关闭PWM灯进行测试

CONFIG_LED_THREAD_ENABLE=n

之后进行休眠唤醒电流测试,生成表格记录如下

设备

LED灯(clk off)

ADC配置

apb div

上报率

一级休眠mA

二级休眠mA

二级休眠uA QDEC

电流(静止)mA

电流(移动)mA

鼠标Y

OFF

50ms,空闲关闭

4

1000

3.68

0.04

29/38

7.1-7.7

12.0-12.4

鼠标Y

OFF

50ms,空闲关闭

4

500

2.94

0.05

29/38

4.9-5.4

9.8-10.1

鼠标Y

OFF

50ms,空闲关闭

4

250

2.58

0.05

29/38

3.7-4.1

8.5-8.9

鼠标Y

OFF

50ms,空闲关闭

4

125

2.37

0.04

29/38

3.2-3.4

8.0-8.1

6.2 已知问题(待测试)

No

已知问题

目前状态

1

qdec计数有时异常,滑动1格动2格 qdec需要按需求详细测试慢速,快速,反复上下滚动

2个电平变化配置为1次event QDEC_SetCntResolution(QDEC_CNT_RESOLUTION_2X); 上拉电阻已使用外部1m上拉,内部太小,会有漏电

2

鼠标USB异常

注意电压不要损坏硬件 DPDM不能接上拉 需要复现USB插入异常情况 fix点:延时确认+计数处理bugfix

3

电脑重启后,dongle异常

重启电脑插入dongle时可能有问题

4

DPI切模式可能存在SPI操作未成功

SPI存入和读取值不一样 SPI软件读写延时100us

5

蓝牙的长时间可能断连

存在若干小时蓝牙断连的可能性

6

蓝牙存在一些兼容性问题

有些电脑重连不是特别容易,重连上去上报率不能达到133Hz

7 RAM/Flash资源使用情况

Memory region         Used Size  Region Size  %age Used
FLASH:      252716 B      1020 KB     24.20
SRAM:       38324 B        52 KB     71.97%