Hi:
I use ncs 3.3.0 and nrf54lm20dk.
USB mode running phenomenon: Mouse stuttering.
But use ncs 3.1.0 mouse is very smooth.
Since the ncs3.1.0 version does not support nrf54lm20 BLE, I switched to ncs3.3.0.
/*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <stdio.h>
#include <string.h>
#include <nrfx_spim.h>
#include <nrfx_gpiote.h>
#include <hal/nrf_spim.h>
#include <hal/nrf_gpiote.h>
#include "peripheral.h"
/* ------------------------------------------------------------------ */
/* 引脚定义 */
/* ------------------------------------------------------------------ */
#define SCK_PIN NRF_GPIO_PIN_MAP(1, 11)
#define MOSI_PIN NRF_GPIO_PIN_MAP(1, 14)
#define MISO_PIN NRF_GPIO_PIN_MAP(1, 12)
#define CS_PIN NRF_GPIO_PIN_MAP(1, 10)
/* ------------------------------------------------------------------ */
/* 驱动实例 */
/* ------------------------------------------------------------------ */
// static nrfx_spim_t spim_inst = NRFX_SPIM_INSTANCE(21);
// static nrfx_gpiote_t gpiote_inst = NRFX_GPIOTE_INSTANCE(20);
static nrfx_spim_t *spim_inst;
static nrfx_gpiote_t *gpiote_inst;
/* 传输完成标志 */
static volatile bool spim_xfer_done = false;
/* ------------------------------------------------------------------ */
/* CS 控制(高电平 = 释放,低电平 = 选中) */
/* ------------------------------------------------------------------ */
static inline void cs_assert(void)
{
/* 拉低 CS,选中从设备 */
nrfx_gpiote_out_clear(gpiote_inst, CS_PIN);
}
static inline void cs_deassert(void)
{
/* 拉高 CS,释放从设备 */
nrfx_gpiote_out_set(gpiote_inst, CS_PIN);
}
/* ------------------------------------------------------------------ */
/* SPIM 事件回调 */
/* ------------------------------------------------------------------ */
static void spim_event_handler(nrfx_spim_event_t const *p_event,
void *p_context)
{
if (p_event->type == NRFX_SPIM_EVENT_DONE) {
spim_xfer_done = true;
}
}
ISR_DIRECT_DECLARE(spim21_direct_isr)
{
nrfx_spim_irq_handler(spim_inst);
return 0;
}
ISR_DIRECT_DECLARE(gpiote_20_direct_isr)
{
nrfx_gpiote_irq_handler(gpiote_inst);
return 0;
}
/* ------------------------------------------------------------------ */
/* 硬件初始化 */
/* ------------------------------------------------------------------ */
void spi_init(void)
{
int err;
gpiote_inst = get_gpiote_inst();
spim_inst = get_spim_inst();
printf("spi gpiote_inst = %p\n", (void*)gpiote_inst);
/* 1. 连接 IRQ(必须最先,且 CONFIG_GPIO=n 时才有效) */
IRQ_DIRECT_CONNECT(GPIOTE20_0_IRQn, 4, gpiote_20_direct_isr, 0);
IRQ_DIRECT_CONNECT(GPIOTE20_1_IRQn, 4, gpiote_20_direct_isr, 0);
irq_enable(GPIOTE20_0_IRQn);
irq_enable(GPIOTE20_1_IRQn);
// /* --- GPIOTE 初始化 --- */
err = nrfx_gpiote_init(gpiote_inst, NRFX_GPIOTE_DEFAULT_CONFIG_IRQ_PRIORITY);
if (err) {
printf("nrfx_gpiote_init failed: 0x%x\n", err);
return;
}
/* --- SPIM 初始化(内部自动注册 IRQ)--- */
nrfx_spim_config_t spim_config = NRFX_SPIM_DEFAULT_CONFIG(
SCK_PIN, MOSI_PIN, MISO_PIN,
NRF_SPIM_PIN_NOT_CONNECTED /* CS 由 GPIOTE 手动控制 */
);
spim_config.frequency = NRFX_MHZ_TO_HZ(1);
spim_config.mode = NRF_SPIM_MODE_3;
spim_config.bit_order = NRF_SPIM_BIT_ORDER_MSB_FIRST;
err = nrfx_spim_init(spim_inst, &spim_config, spim_event_handler, NULL);
if (err) {
printf("nrfx_spim_init failed: 0x%x\n", err);
return;
}
/* --- 配置 CS 引脚为推挽输出 --- */
nrfx_gpiote_output_config_t output_config = NRFX_GPIOTE_DEFAULT_OUTPUT_CONFIG;
err = nrfx_gpiote_output_configure(gpiote_inst,
CS_PIN,
&output_config,
NULL); /* 不使用 GPIOTE task */
if (err) {
printf("nrfx_gpiote_output_configure failed: 0x%x\n", err);
return;
}
IRQ_DIRECT_CONNECT(SERIAL21_IRQn, 4, spim21_direct_isr, 0);
irq_enable(SERIAL21_IRQn);
/* 初始化完成后拉高 CS(释放从设备) */
cs_deassert();
}
void spi_cs_pin(bool enable)
{
if(enable) {
cs_assert(); /* 拉低 CS */
} else {
cs_deassert(); /* 拉高 CS */
}
}
/* ------------------------------------------------------------------ */
/* SPI 写操作 */
/* ------------------------------------------------------------------ */
/**
* @brief SPI 写数据
* @param p_tx_buf 发送缓冲区指针
* @param tx_len 发送字节数
* @return 0 成功,负值失败
*/
int spi_write(const uint8_t *p_tx_buf, size_t tx_len)
{
nrfx_spim_xfer_desc_t xfer = {
.p_tx_buffer = p_tx_buf,
.tx_length = tx_len,
.p_rx_buffer = NULL, /* 只写,不接收 */
.rx_length = 0,
};
spim_xfer_done = false;
int err = nrfx_spim_xfer(spim_inst, &xfer, 0);
if (err) {
cs_deassert();
printf("spi_write xfer failed: 0x%x\n", err);
return -1;
}
/* 等待传输完成 */
while (!spim_xfer_done) {
/* 可替换为 RTOS 事件等待,如 k_event_wait() */
}
return 0;
}
/* ------------------------------------------------------------------ */
/* SPI 读操作 */
/* ------------------------------------------------------------------ */
/**
* @brief SPI 写后等待指定时间再读
* @param p_tx_buf 发送缓冲区指针(命令/地址)
* @param tx_len 发送字节数
* @param p_rx_buf 接收缓冲区指针
* @param rx_len 接收字节数
* @param delay_us 写完到读之间的等待时间(微秒)
* @return 0 成功,负值失败
*/
int spi_read(uint8_t *p_rx_buf, size_t rx_len)
{
int err;
/* ---- 第三步:读 ---- */
nrfx_spim_xfer_desc_t xfer_rx = {
.p_tx_buffer = NULL,
.tx_length = 0,
.p_rx_buffer = p_rx_buf,
.rx_length = rx_len,
};
spim_xfer_done = false;
err = nrfx_spim_xfer(spim_inst, &xfer_rx, 0);
if (err) {
cs_deassert();
printf("spi_read phase failed: 0x%x\n", err);
return -1;
}
/* 等待读传输完成 */
while (!spim_xfer_done) {
}
//cs_deassert(); /* 拉高 CS */
return 0;
}
#include <zephyr/kernel.h>
#include <stdio.h>
#include <hal/nrf_egu.h>
#include <nrfx_gpiote.h>
#include <hal/nrf_gpiote.h>
#include "spi.h"
#include "pmw3360.h"
#include "../main.h"
#include "../peripheral/peripheral.h"
#include "../peripheral/timer.h"
#include "../rf/rf_module.h"
#if CONFIG_USBD_HID_SUPPORT
#include "../usb/usb_module.h"
#endif
enum state {
STATE_IDLE,
STATE_MOTION,
};
/* 线程参数 */
#define PMW3360_THREAD_STACK_SIZE 1024
#define PMW3360_THREAD_PRIORITY 5
#define SPI_WRITE_BIT 0x80
#define PMW3360_MOTION_BURST_SIZE 6
#define PMW3360_DX_POS 2
#define PMW3360_DY_POS 4
/* ========== 添加时序定义 ========== */
/* Timings defined by spec (from original driver) */
#define T_NCS_SCLK 1 /* 120 ns - CS建立/保持时间 */
#define T_SRX (20 - T_NCS_SCLK) /* 20 us - 读操作后的CS释放时间 */
#define T_SCLK_NCS_WR (35 - T_NCS_SCLK) /* 35 us - 写操作等待时间 */
#define T_SWX (180 - T_SCLK_NCS_WR) /* 180 us - 写操作后的处理时间 */
#define T_SRAD 160 /* 160 us - 地址到数据读取延迟 */
#define T_SRAD_MOTBR 35 /* 35 us - 运动突发读取的地址延迟 */
#define T_BEXIT 1 /* 500 ns - 突发模式退出时间 */
#define T_BRSEP 15 /* 15 us - SROM突发写入字节间隔 */
#define RING_BUF_SIZE (32 * sizeof(motion_data_t))
RING_BUF_DECLARE(motion_ring_buf, RING_BUF_SIZE);
/* 全局事件对象 */
static nrfx_gpiote_t *gpiote_inst;
struct k_event pmw3360_event;
static bool initialized = false;
static bool last_read_burst = false;
static enum state state = STATE_IDLE;
K_MUTEX_DEFINE(pmw3360_mutex);
/* 固件数据(需要从外部文件引入) */
extern const size_t pmw3360_firmware_length;
extern const uint8_t pmw3360_firmware_data[];
/* --- 中断回调函数 --- */
static void gpiote_evt_handler(nrfx_gpiote_pin_t pin,
nrfx_gpiote_trigger_t trigger,
void *p_context)
{
//printf("Interrupt on pin %d\n", pin);
/* 使用 k_uptime_get() 手动添加时间戳 */
//printf("[%lld ms] Interrupt pin\n", k_uptime_get());
pmw3360_irq_disable();
k_event_post(&pmw3360_event, PMW3360_EVENT_DATA_READY);
}
static void pmw3360_irq_init(void)
{
int err;
/* 添加 static const,确保生命周期贯穿整个运行时 */
static const nrf_gpio_pin_pull_t pull_config = NRF_GPIO_PIN_PULLUP;
static const nrfx_gpiote_trigger_config_t trigger_config = {
.trigger = NRFX_GPIOTE_TRIGGER_LOW,
.p_in_channel = NULL,
};
static const nrfx_gpiote_handler_config_t handler_config = {
.handler = gpiote_evt_handler,
.p_context = NULL,
};
static const nrfx_gpiote_input_pin_config_t input_pin_config = {
.p_pull_config = &pull_config,
.p_trigger_config = &trigger_config,
.p_handler_config = &handler_config,
};
err = nrfx_gpiote_input_configure(gpiote_inst,
IRQ_PIN,
&input_pin_config);
if (err) {
printf("nrfx_gpiote_input_configure failed: 0x%x\n", err);
return;
}
}
void pmw3360_irq_enable(void)
{
/* 中断使能代码... */
//printf("[%lld ms] Interrupt enable\n", k_uptime_get());
nrfx_gpiote_trigger_enable(gpiote_inst, IRQ_PIN, true);
// 重新使能后检查引脚是否仍为低电平
if (nrf_gpio_pin_read(IRQ_PIN) == 0) {
// 引脚仍为低,手动补发事件
k_event_post(&pmw3360_event, PMW3360_EVENT_DATA_READY);
}
}
void pmw3360_irq_disable(void)
{
nrfx_gpiote_trigger_disable(gpiote_inst, IRQ_PIN);
}
/* ========== 辅助函数:CS控制 ========== */
static int spi_cs_ctrl(bool enable)
{
/* 根据你的硬件实现CS控制 */
if (!enable) {
k_busy_wait(T_NCS_SCLK); /* 延迟1: CS释放前的建立时间 */
}
/* 实际的CS控制代码... */
spi_cs_pin(enable);
if (enable) {
k_busy_wait(T_NCS_SCLK); /* 延迟2: CS使能后的保持时间 */
}
return 0;
}
/* PMW3360读寄存器 - 添加时序延迟 */
static int pmw3360_reg_read(uint8_t reg, uint8_t *value)
{
//uint8_t tx[2] = {reg, *value};
uint8_t tx = reg;
/* 延迟3: 开始传输前确保CS已建立 */
spi_cs_ctrl(true); /* 内部会有T_NCS_SCLK延迟 */
spi_write(&tx, 1);
/* 延迟4: 地址发送后到读取数据的等待时间 T_SRAD (160 us) */
k_busy_wait(T_SRAD); /* 关键延迟:等待传感器准备数据 */
spi_read(value, 1);
/* 延迟5: 结束传输后的CS释放时间 T_SRX (20 us) */
spi_cs_ctrl(false); /* 内部会有T_SRX延迟 */
k_busy_wait(T_SRX);
/* 清除突发读取标志 */
last_read_burst = false;
return 0;
}
/* PMW3360写寄存器 - 添加时序延迟 */
static int pmw3360_reg_write(uint8_t reg, uint8_t value)
{
uint8_t tx[2] = {SPI_WRITE_BIT | reg, value};
/* 延迟6: 开始传输前确保CS已建立 */
spi_cs_ctrl(true);
spi_write(tx, 2);
/* 延迟7: 写入完成后的等待时间 T_SCLK_NCS_WR (35 us) */
k_busy_wait(T_SCLK_NCS_WR);
spi_cs_ctrl(false);
/* 延迟8: 写操作后的处理时间 T_SWX (180 us) */
k_busy_wait(T_SWX); /* 关键延迟:传感器处理写入命令 */
/* 清除突发读取标志 */
last_read_burst = false;
return 0;
}
/* PMW3360突发写(用于固件上传)- 单字节发送方式 */
static int pmw3360_burst_write(uint8_t reg, const uint8_t *data, size_t len)
{
int ret = 0;
uint8_t buf;
if (data == NULL || len == 0) {
printf("Error: Invalid data or length\n");
return -1;
}
printf("fw length = %zu\n", len);
/* 延迟: 突发模式开始前的CS建立 */
spi_cs_ctrl(true);
/* 第一步:发送寄存器地址(第一个字节) */
uint8_t reg_byte = SPI_WRITE_BIT | reg;
spi_write(®_byte, 1);
/* 延迟:字节间隔 T_BRSEP (15 us) */
k_busy_wait(T_BRSEP);
printf("pmw3360_firmware_data\n");
/* 第二步:逐个发送固件数据字节 */
for (size_t i = 0; i < len; i++) {
buf = data[i];
/* 发送一个字节 */
spi_write(&buf, 1);
/* 每个字节后延迟 T_BRSEP (15 us) */
/* 注意:最后一个字节后也需要延迟 */
k_busy_wait(T_BRSEP);
/* 可选:每发送一定字节数打印进度 */
// if ((i + 1) % 512 == 0) {
// printf("FW progress: %zu/%zu bytes\n", i + 1, len);
// }
}
/* 延迟: 突发模式退出时间 T_BEXIT (500 ns) */
spi_cs_ctrl(false);
k_busy_wait(T_BEXIT);
last_read_burst = false;
printf("FW upload complete: %zu bytes\n", len);
return ret;
}
int pmw3360_motion_burst_read(int16_t *dx, int16_t *dy)
{
uint8_t tx;
uint8_t rx[PMW3360_MOTION_BURST_SIZE];
int err;
/* ============================================================
* 步骤1: 触发突发模式(仅在需要时)
* ============================================================ */
if (!last_read_burst) {
/* 只有上次不是突发读取时,才需要写入触发命令 */
err = pmw3360_reg_write(PMW3360_REG_MOTION_BURST, 0x00);
if (err) {
printf("Failed to trigger motion burst\n");
return err;
}
/* reg_write 内部已经包含了:
* - CS控制 (拉低→写→拉高)
* - T_SCLK_NCS_WR (35us)
* - T_SWX (180us)
*/
}
/* ============================================================
* 步骤2: 读取突发数据
* ============================================================ */
/* 2.1 CS拉低,开始SPI传输 */
spi_cs_ctrl(true);
/* 2.2 发送突发读取地址 (0x50) */
tx = PMW3360_REG_MOTION_BURST;
spi_write(&tx, 1);
/* 2.3 关键延迟:等待传感器准备数据 (35us) */
/* 注意:这个延迟必须在地址发送之后、数据读取之前 */
k_busy_wait(T_SRAD_MOTBR);
/* 2.4 连续读取6个字节 */
/* 注意:读取时发送0xFF来产生SPI时钟 */
// for (int i = 0; i < PMW3360_MOTION_BURST_SIZE; i++) {
// uint8_t dummy = 0xFF;
// spi_read(&rx[i], 1);
// /* 如果SPI是半双工,可以分开操作:
// * spi_write(&dummy, 1);
// * spi_read(&rx[i], 1);
// */
// }
spi_read(rx, PMW3360_MOTION_BURST_SIZE);
/* 2.5 结束SPI传输 */
spi_cs_ctrl(false);
k_busy_wait(T_BEXIT);
/* ============================================================
* 步骤3: 标记已完成突发读取
* ============================================================ */
last_read_burst = true;
/* ============================================================
* 步骤4: 解析数据
* ============================================================ */
if (dx && dy) {
/* 注意:PMW3360的数据是小端格式 */
*dx = (int16_t)((rx[PMW3360_DX_POS + 1] << 8) | rx[PMW3360_DX_POS]);
*dy = (int16_t)((rx[PMW3360_DY_POS + 1] << 8) | rx[PMW3360_DY_POS]);
/* 可选:数据有效性检查 */
/* PMW3360运动数据范围通常是 -32768 ~ 32767 */
/* 如果dx和dy都为0,可能表示静止或读取失败 */
}
return 0;
}
static int pmw3360_upload_firmware(void)
{
int err;
uint8_t val;
printf("Uploading PMW3360 firmware...\n");
/* 读取运动寄存器清除状态 */
for (uint8_t reg = 0x02; reg <= 0x06; reg++) {
pmw3360_reg_read(reg, &val);
}
/* 禁用Rest模式 */
pmw3360_reg_write(PMW3360_REG_CONFIG2, 0x00);
/* 初始化SROM */
pmw3360_reg_write(PMW3360_REG_SROM_ENABLE, 0x1D);
/* 延迟17: 等待SROM初始化完成 (50 ms) */
k_sleep(K_MSEC(10)); /* 关键延迟:固件加载前的准备时间 */
/* 开始SROM下载 */
pmw3360_reg_write(PMW3360_REG_SROM_ENABLE, 0x18);
/* 上传固件 */
//printf("PMW3360_REG_SROM_LOAD_BURST fw=%d\n", pmw3360_firmware_length);
pmw3360_burst_write(PMW3360_REG_SROM_LOAD_BURST,
pmw3360_firmware_data,
pmw3360_firmware_length);
/* 延迟18: 等待固件加载完成 (10 ms) */
k_sleep(K_MSEC(10)); /* 关键延迟:固件验证前的等待时间 */
/* 验证固件ID */
err = pmw3360_reg_read(PMW3360_REG_SROM_ID, &val);
if (err) return err;
printf("Firmware ID: 0x%02X (expected: 0x%02X)\n", val, PMW3360_FIRMWARE_ID);
if (val != PMW3360_FIRMWARE_ID) {
printf("Invalid firmware ID\n");
return -EIO;
}
/* 验证产品ID */
err = pmw3360_reg_read(PMW3360_REG_PRODUCT_ID, &val);
if (err) return err;
if (val != PMW3360_PRODUCT_ID) {
printf("Invalid product ID: 0x%02X\n", val);
return -EIO;
}
/* 启用Rest模式 */
err = pmw3360_reg_write(PMW3360_REG_CONFIG2, 0x20);
if (err) return err;
printf("Firmware upload successful\n");
return 0;
}
/* ==================== PMW3360 配置 ==================== */
static int pmw3360_configure(uint32_t cpi)
{
uint8_t value;
/* 设置CPI */
if (cpi < 100 || cpi > 12000) {
printf("Invalid CPI value: %u\n", cpi);
return -EINVAL;
}
value = (cpi / 100) - 1;
pmw3360_reg_write(PMW3360_REG_CONFIG1, value);
/* 2. ? 禁用Rest模式,强制Run模式(最高帧率) */
pmw3360_reg_write(PMW3360_REG_CONFIG2, 0x00);
printf("PMW3360 configured: CPI=%u, Rest mode disabled (always Run)\n", cpi);
return 0;
}
/* 初始化PMW3360 */
int pmw3360_init(uint32_t cpi)
{
int err;
gpiote_inst = get_gpiote_inst();
printf("pmw3xx gpiote_inst = %p\n", (void*)gpiote_inst);
pmw3360_irq_init(); /* 初始化运动中断 */
printf("pmw3360_irq_init\n");
/* 复位传感器 */
err = pmw3360_reg_write(PMW3360_REG_POWER_UP_RESET, 0x5A);
printf("pmw3360_reg_write err=%d\n", err);
if (err) {
printf("Reset failed. err=%d\n", err);
return err;
}
/* 延迟19: 复位后等待传感器稳定 (1 ms) */
k_sleep(K_MSEC(50)); /* 关键延迟:上电复位后的稳定时间 */
/* 上传固件 */
err = pmw3360_upload_firmware();
if (err) {
printf("Firmware upload failed. err=%d\n", err);
return err;
}
/* 配置传感器 */
err = pmw3360_configure(cpi);
if (err) {
printf("Configuration failed. err=%d\n", err);
}
initialized = true;
printf("PMW3360 initialized (CPI=%u)\n", cpi);
nrfx_gpiote_trigger_enable(gpiote_inst, IRQ_PIN, true);
return 0;
}
/* 线程入口函数 */
void pmw3360_thread(void *p1, void *p2, void *p3)
{
int err;
enum state new_state = STATE_IDLE;
int16_t dx=0,dy=0;
k_event_init(&pmw3360_event);
spi_init();
err = pmw3360_init(1600);
//pmw3360_irq_enable();
while (1) {
/* 替代 k_event_wait_safe */
uint32_t events = k_event_wait(&pmw3360_event,
PMW3360_EVENT_DATA_READY,
false, /* reset=false */
K_FOREVER);//K_MSEC(200)
//if (events & PMW3360_EVENT_DATA_READY)
{
bool is_timed_tx = (atomic_get(&ptx_delay) > 0);
if(!is_timed_tx) {
pmw3360_motion_burst_read(&dx, &dy);
} else {
dx = 1;
dy = 1;
start_motion_idle_timer();
}
/* 手动原子清除,紧接在判断之后 */
k_event_clear(&pmw3360_event, PMW3360_EVENT_DATA_READY);
//printf("PMW3360: dx=%d,dy=%d\n",dx,dy);
#if CONFIG_USBD_HID_SUPPORT
//printf("rf status=%d.\n", rf_motion_status());
// 如果是定时发送,且(USB有动作 或 RF有动作),则跳过
if (is_timed_tx && (usb_motion_status())) { //|| rf_motion_status()
#else
if (is_timed_tx && rf_motion_status()) {
#endif
pmw3360_irq_enable();
continue;
}
/* 只有有实际运动时才写入,减少无效数据 */
if (dx != 0 || dy != 0) {
motion_data_t data = { .dx = dx, .dy = dy };
//printf("[%lld ms] mutex_lock\n", k_uptime_get());
k_mutex_lock(&pmw3360_mutex, K_FOREVER);
uint32_t written = ring_buf_put(&motion_ring_buf,
(uint8_t *)&data,
sizeof(data));
k_mutex_unlock(&pmw3360_mutex);
//printf("[%lld ms] mutex_unlock\n", k_uptime_get());
if (written == sizeof(data)) {
//new_state = STATE_MOTION;
struct main_msg msg;
msg.type = MAIN_MSG_MOTION;
msg.cnt = 1;
int err = k_msgq_put(&main_msgq, &msg, K_NO_WAIT);
if (err) {
printf("Cannot put button event to message queue: %d\n", err);
}
} else {
/* 缓冲区满,可在此记录溢出或丢弃 */
printf("ring buf full, data dropped\n");
}
}
}
pmw3360_irq_enable();
}
}
/* 编译时定义并启动线程 */
K_THREAD_DEFINE(pmw3360_tid,
PMW3360_THREAD_STACK_SIZE,
pmw3360_thread,
NULL, NULL, NULL,
PMW3360_THREAD_PRIORITY,
0,
0); /* delay=0,立即启动 */
