
RIOT OS QN908x CPU 移植详解Cortex-M4F BLE 微控制器的 CPUID、ADC、Flexcomm 外设与看门狗实战【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT本篇指南基于 RIOT OS 中 cpu/qn908x/doc.md 文档展开系统讲解 NXP QN908x 系列 BLE 微控制器在 RIOT 中的 CPU 端口实现。读完后你将理解 QN908x 的 GPIO、ADC、I2C/SPI/UARTFlexcomm 复用、定时器、RTC 与看门狗驱动的配置方式掌握periph_conf.h中各外设配置表与位域标志的写法并能参考源码弄清 ADC 微伏模式、时钟树配置和 Bootloader 看门狗约束等关键实现细节。一、QN908x 芯片家族概览QN908x 家族如 QN9080是一颗面向超低功耗 IoT 应用的 Cortex-M4F 微控制器集成了Cortex-M4F内核带单精度浮点单元Bluetooth Low Energy无线通信USB 2.0接口部分 SKU如QN9080SIP额外集成NFC可选的DC-DC 与 LDO电源方案、低功耗睡眠定时器、I2C、SPI、ADC、SPIFIQuad-SPI Flash 接口等外设。RIOT 的 CPU 实现位于 cpu/qn908x 目录核心文件包括文件作用cpu/qn908x/cpu.cCPU 初始化与时钟树配置cpu/qn908x/periph/gpio.cGPIO 驱动cpu/qn908x/periph/adc.cADC 驱动cpu/qn908x/periph/flexcomm.cFlexcomm 复用初始化UART/SPI/I2C 公共层cpu/qn908x/periph/i2c.c、spi.c、uart.c各 Flexcomm 模式驱动cpu/qn908x/periph/timer.c、rtc.c、wdt.cCTIMER、RTC、WDT 驱动cpu/qn908x/include/periph_cpu.h所有 CPU 特有类型与位域定义RIOT 官方参考开发板是 boards/qn9080dk下文所有可运行配置均以该板的 periph_conf.h 为实证。CPU 初始化流程cpu.c 中的cpu_init()第 30–55 行依次完成cortexm_init()初始化 Cortex-M 内核若未启用periph_wdt模块则调用CLOCK_DisableClock(kCLOCK_Wdt)关闭看门狗——否则系统会在约 10 秒后复位详见第七节cpu_clock_init()配置时钟树early_init()初始化 stdio保证periph_init()中可用DEBUG()宏随后periph_init()触发各外设的静态初始化。时钟树配置Kconfigcpu/qn908x/Kconfig.clk 提供了 QN908x 的时钟配置项cpu_clock_init()cpu.c会依据这些选项调用 NXP 的CLOCK_AttachClk()/CLOCK_SetClkDiv()完成时钟源切换Kconfig 选项含义与约束CPU_CLK_32K_XTAL/CPU_CLK_32K_RCO32K 低频时钟源二选一外部 32.768 kHz 晶振需BOARD_HAS_XTAL32K或内部 32 kHz RCO 振荡器CPU_CLK_SYS_XTAL/CPU_CLK_SYS_OSC32M/CPU_CLK_SYS_32K系统时钟源三选一外部 16/32 MHz 晶振、内部 32 MHz 振荡器、或直接使用 32K 低频源超低功耗场景CPU_CLK_OSC32M_DIV/CPU_CLK_XTAL_DIV可选的 /2 分频把 32 MHz 源变成 16 MHzCPU_CLK_AHB_DIV1–8192默认 1AHB 分频。注意启用 BLE 时 AHB 时钟必须为 8/16/32 MHz从而限制了可取范围CPU_CLK_APB_DIV1–16默认 1APB 分频驱动 RTC、ADC、DAC、电容传感等外设BOARD_HAS_XTAL_16M/BOARD_HAS_XTAL_32M由板级定义声明外部高频晶振频率AHB 时钟驱动 ARM 内核、FlexcommUART/SPI/I2C、GPIO、BLE_AHB 与 DMAAPB 时钟驱动 RTC、ADC、DAC 以及可选的 WDTQN908x 端口把 WDT 固定跑在 APB 上见 cpu.c 的CLOCK_AttachClk(kAPB_to_WDT_CLK)。二、CPUID 驱动出厂蓝牙地址即芯片 IDQN908x 的 CPUID无需任何配置。芯片 ID 取自只读 Flash 信息页中由工厂烧写的默认蓝牙 MAC 地址在 periph_cpu.h 中定义#define CPUID_ADDR (FSL_FEATURE_FLASH_ADDR_OF_VENDOR_BD_ADDR) #define CPUID_LEN (6U)需要注意这是出厂默认蓝牙地址如果蓝牙模块实际使用的地址被另行烧写过二者可能不一致。因此 CPUID 适合作为设备唯一标识但不能等同于运行时 BLE 栈的实际 MAC 地址。三、GPIO 驱动与引脚编码GPIO 驱动同样无需配置使用GPIO_PIN(port, pin)宏声明引脚。QN908x 有 A、B 两个端口periph_cpu.hgpio_t为uint16_t编码方式为port 12 | pin#define GPIO_PIN(x, y) (((x) 12u) | (y))端口号左移 12 位是一种优化驱动可以直接对 GPIO_PIN 值做位掩码运算来推导端口地址避免额外的内存访问。引脚方向/上下拉等属性由GPIO_MODE(open_drain, out_enabled, pull_mode)位域生成bit 0 开漏、bit 1 输出使能、bit 4-5 上下拉选择中断边沿支持GPIO_RISING/GPIO_FALLING等GPIO_BOTH双边沿在该 CPU 上不受支持。四、ADC 驱动差分 Σ-Δ 转换器这是 QN908x 外设中最复杂的部分也是原doc.md篇幅最大的章节。4.1 硬件特性QN908x 的 ADC 是差分 Σ-Δsigma-delta转换器有 9 个外部信号 ADC0–ADC7 与 ADC_VREFI可连接到特定的外部 GPIO 引脚全部位于 Port A。外部信号与部分内部信号可以组合成多种测量通道但并非任意组合都合法。时钟与采样率ADC 模块可以从高速时钟源4 MHz 基准或低速时钟源32 kHz / 32.768 kHz取时钟并带附加分频器选取中间值。但注意——模块时钟不是采样率adc_sample()的一次采样需要在内部多次采样后经硬件滤波与抽取实际采样率约为所选时钟的1/128最大约 31.25 kS/s。adc.c 中给出了 9 档时钟选项及其CLKSEL寄存器编码时钟档位值ADC_CLOCK_4M–ADC_CLOCK_31K254 MHz / 2 MHz / 1 MHz / 500 kHz / 250 kHz / 125 kHz / 62.5 kHz / 31.25 kHzADC_CLOCK_32K32 kHz 或 32.768 kHz低速源低速源在高速时钟未运行低功耗场景时仍可供 ADC 工作。4.2 线路配置位域adc_conf_t 详解每条板级 ADC 线路用单个整数描述它是以下取值的逻辑或完整位域定义见 periph_cpu.h位字段说明bit 3ADC_PGA_ENABLE可选启用 1x 增益的 PGA仅对无驱动能力的信号源有意义硬件支持的其他 PGA 增益组合驱动暂未实现bit 4-5qn908x_adc_vinn_t仅对使用 Vinn 信号的通道有效ADC_VINN_AVSS默认Vss/ADC_VINN_VREF_21/2 Vref/ADC_VINN_VREF_3_43/4 Vref/ADC_VINN_VREFVrefbit 6-7qn908x_adc_gain_t差分输入增益ADC_GAIN_X05(0.5) /ADC_GAIN_X1(1默认) /ADC_GAIN_X15(1.5) /ADC_GAIN_X20(2)bit 8ADC_VREF_GAIN_X15可选把 Vref 乘以 1.5bit 9-13qn908x_adc_channel_t差分通道对见下表bit 14-15qn908x_adc_vref_t满量程参考电压ADC_VREF_1V2内部 1.2 V bandgap默认/ADC_VREF_VREF外部 ADC_VREFI 引脚/ADC_VREF_VEXT外部 ADC_VREFI 加驱动/ADC_VREF_VCCVcc通道列表第一信号接正极第二信号接负极ADC_CHANNEL_ADC0_ADC1在 ADC0 电压高于 ADC1 时读出正值ADC_CHANNEL_ADC0_ADC1、ADC_CHANNEL_ADC2_ADC3、ADC_CHANNEL_ADC4_ADC5、ADC_CHANNEL_ADC6_ADC7—— 四对外部差分对ADC_CHANNEL_ADCn_VINNn 0…7—— 外部 ADCn 对 VinnADC_CHANNEL_TEMP—— 内部温度传感器ADC_CHANNEL_VCC4_VINN—— 内部 Vcc/4电池监控对 VinnADC_CHANNEL_VINN_VINN、ADC_CHANNEL_VINN_VSS—— 纯内部组合。注意同一物理通道可以用不同参数配置为多条 ADC 线路只需在adc_config[]中给出不同的位域组合。4.3 分辨率与微伏模式ADC_RES_UV硬件分辨率固定为23 位有符号但adc_sample()允许请求更小的分辨率6/8/10/12/14/16/22 位加符号位返回值相应缩小。返回值是有符号整数差分输入为负时返回负值——例如某线路负端 Vinn 配为 Vss请求 8 位分辨率时正端电平到达 Vref 时返回最大值 255若 Vinn 配为 Vref 而正端低至 Vss则可能返回 -255。特殊模式ADC_RES_UV使用内部 1.2 V bandgap 作参考时adc_sample()直接返回**微伏µV**为单位的有符号整数若启用 x1.5 Vref 倍增满量程可达 ±1.8 V。该模式会读取 Flash 信息页中的1.2 V bandgap 出厂校准值以提高精度。adc.c 的_adc_bandgap_calib_mv()会对校准值做合理性检查1214–1228 mV越界则回退默认 1222 mV微伏换算公式在第 294–312 行if (res ADC_RES_MAX) { ret adc_data (22 - res); } else if (res ADC_RES_UV (conf ADC_CTRL_VREF_SEL_MASK) ADC_VREF_1V2) { uint32_t vref adc_bandgap_calib_mv; if (conf ADC_VREF_GAIN_X15) { vref vref vref / 2; } ret ((adc_data 2) * (int32_t)vref) / ((1 20) / 1000); }内部温度传感器选择ADC_CHANNEL_TEMP通道即可接入但返回值仍是代表温度相关电压的数值需应用层结合校准参数换算成温度。文档建议使用内部温度传感器时采用 1.2 V bandgap Vref、且 Vinn 取 1/2 Vref——因为室温下被测电压约 800 mV这样可充分利用量程。4.4 ADC 配置示例periph_conf.h原文档给出的完整配置示例static const adc_conf_t adc_config[] { /* Pin A11 to Vss, 1.8v Vref. */ ADC_CHANNEL_ADC7_VINN | ADC_VREF_GAIN_X15, /* Pin A10 to A11, 1.2V Vref. */ ADC_CHANNEL_ADC6_ADC7, /* Temperature (in V) over to 0.6 V, 1.2 V Vref. */ ADC_CHANNEL_TEMP | ADC_VINN_VREF_2, /* Internal battery monitor, Vcc/4 to Vss, 1.2V Vref. */ ADC_CHANNEL_VCC4_VINN | ADC_VINN_AVSS, }; #define ADC_NUMOF ARRAY_SIZE(adc_config) #define QN908X_ADC_CLOCK ADC_CLOCK_500KQN908X_ADC_CLOCK未定义时默认ADC_CLOCK_500K见 adc.c。实际参考QN9080DK 开发板在 periph_conf.h 中定义了 11 条 ADC 线路——6 条单端线路A0–A5均VinnAVSSADC_VREF_GAIN_X15、3 条差分线路A1-A0、A2-A3、A5-A4、1 条ADC_CHANNEL_TEMP温度线路和 1 条ADC_CHANNEL_VCC4_VINN电池监控线路。4.5 驱动实现细节从源码看adc.c单线程保护adc_lock互斥锁防止两个线程并发操作 ADC 块adc_sample()通过adc_sample_isr_lock 软件触发ADC_CTRL_SW_START阻塞等待isr_adc()中断把 23 位结果写入共享变量后解锁。初始化上电 ADC清除PMU_CTRL1中 ADC/BG/VREF 等 DIS 位→ 等待 100 µs 稳定 → 使能 ADC 时钟 → 复位 ADC → 配置为单转换、无扫描、软件触发模式 → 使能ADC_IRQn中断。通道到引脚的复用adc_init()会依据通道枚举把 Port A 相应引脚切到功能 1ADC 复用例如ADC_CHANNEL_ADC6_ADC7对应 PA10/PA11选择外部 VREFI 时自动把 PA7 切到 ADC 功能温度通道会置位ADC_BG_BF_TEMP_ENVcc/4 通道使能SYSCON_ANA_EN_BAT_MON_EN。已知限制驱动头部注释adc.c软件抽取固定为 32 样本未按线路开放配置PGA 仅支持 1x 增益——支持其他增益需要应用层配合调整共模电压超出该抽象接口范围。若板上有外部电容连接 PA06 稳定参考电压板级应定义BOARD_HAS_ADC_PA06_CAP驱动会置位VREFO_EN并配置 PA06 复用。五、I2C 与 Flexcomm 复用QN908x 内部有多个FLEXCOMM通用接口块但只有部分支持特定协议。各模式的可用块如下依据 doc.md 与 flexcomm.hFlexcomm 块UARTI2CSPIFLEXCOMM0USART0——FLEXCOMM1USART1I2C0—FLEXCOMM2—I2C1SPI0FLEXCOMM3——SPI1关键约束一个 FLEXCOMM 同一时间只能工作在 UART、I2C、SPI 三者之一因此USART1 与 I2C0 不能同时使用同为 FLEXCOMM1SPI0 与 I2C1 不能同时使用同为 FLEXCOMM2。flexcomm_init() 负责把某块 Flexcomm 初始化为所选模式并把中断分发回对应模块的 ISR。速度档位QN908x 的i2c_speed_t支持任意值直到 400 kbit/speriph_cpu.hI2C_SPEED_LOW约 10 kbit/s、I2C_SPEED_NORMAL约 100 kbit/s、I2C_SPEED_FAST约 400 kbit/sI2C_SPEED_FAST_PLUS与I2C_SPEED_HIGH在本 CPU 上不被支持会被截断到 400 kbit/s。I2C 配置示例periph_conf.hstatic const i2c_conf_t i2c_config[] { { .dev I2C0, .pin_scl GPIO_PIN(PORT_A, 6), /* 或 A8、A12、A20 */ .pin_sda GPIO_PIN(PORT_A, 7), /* 或 A9、A13、A21 */ .speed I2C_SPEED_FAST, }, { .dev I2C1, .pin_scl GPIO_PIN(PORT_A, 2), /* 或 A5、A23、A27 */ .pin_sda GPIO_PIN(PORT_A, 3), /* 或 A4、A22、A26 */ .speed I2C_SPEED_FAST, }, }; #define I2C_NUMOF ARRAY_SIZE(i2c_config)QN9080DK 板只启用了 I2C0A6/A7、fast 模式见 periph_conf.h。六、SPI 驱动6.1 硬件与时钟两个 FLEXCOMM 可作 SPISPI0 FLEXCOMM2、SPI1 FLEXCOMM3。注意FLEXCOMM2SPI0与 I2C1 共享二者不可同时使用。SPI Flexcomm 时钟直接由 AHB 总线驱动因此上限受核心/AHB 时钟约束假设 32 MHz 系统时钟时最高约 32 MHz可配/1 至 /65536 的分频生成更低时钟。每条总线最多挂4 个硬件 CSSPI_HWCS_NUMOF为 4软件 CS 数量不限。驱动采用 OSHA SPI Signal Names 命名COPI/CIPO虽然只实现了控制器模式但硬件本身也支持外设模式。6.2 CS 的两种用法在 SPI 驱动中配置片选时传SPI_HWCS(x)使用硬件 CS 模式该宏置 bit 15 加编号见 periph_cpu.h传任意GPIO_PIN引脚则以软件方式控制该 GPIO 作 CS。6.3 SPI 配置示例periph_conf.h以下示例只启用 1 个硬件 CS编号 0其余留空完整 CS 引脚清单请查阅芯片用户手册static const spi_conf_t spi_config[] { { .dev SPI0, .cipo_pin GPIO_PIN(PORT_A, 5), .copi_pin GPIO_PIN(PORT_A, 4), .clk_pin GPIO_PIN(PORT_A, 30), .cs_pin { GPIO_PIN(PORT_A, 3), /* Use as SPI_HWCS(0) */ GPIO_UNDEF, GPIO_UNDEF, GPIO_UNDEF }, }, };SPI 模式通过SPI_MODE_SEL(pol, pha)选择 0–3 模式spi_clk_t枚举100 kHz / 400 kHz / 1 MHz / 5 MHz / 10 MHz会在运行时依据 AHB 时钟与任意分频换算。QN9080DK 板上 SPI0 正是按此方式配置CS0 位于 PA3板上接有 MX25R2035F Quad-SPI Flashperiph_conf.hstatic const spi_conf_t spi_config[] { { .dev SPI0, /* Flexcomm 2 */ .cipo_pin GPIO_PIN(PORT_A, 5), .copi_pin GPIO_PIN(PORT_A, 4), .clk_pin GPIO_PIN(PORT_A, 30), .cs_pin { GPIO_PIN(PORT_A, 3), /* MX25R2035F CS# connected here. */ GPIO_UNDEF, GPIO_UNDEF, GPIO_UNDEF }, }, }; #define SPI_NUMOF ARRAY_SIZE(spi_config)七、看门狗WDTBootloader 留下的 10 秒窗口QN908x 的看门狗行为有一个必须知道的坑复位后CLK_EN寄存器中的CLK_WDT_EN位已置位看门狗时钟在运行但中断与复位功能处于禁用状态——即计时器在跑但不会咬你ROM Bootloader 交接到用户应用RIOT 内核后看门狗被启用超时10 秒如果你的板/应用不包含periph_wdt模块cpu_init()会自动禁用看门狗cpu.c无需任何配置如果包含periph_wdt看门狗将保持 Bootloader 设置的 10 秒超时你必须在启动后 10 秒内调用wdt_setup_reboot()或wdt_setup_reboot_with_callback()重新配置。其他要点WDT 时钟源由板级配置取决于板上是否焊接了相应晶振传入wdt_setup_reboot*的毫秒值会在调用时按当时配置的时钟源换算成时钟节拍。该函数可多次调用改参数或换时钟源后但之后必须再调wdt_start()才能开始工作从 periph_cpu.h 看WDT_HAS_STOP置 1即 WDT 时钟可独立停止时间上下限为 1 ms – 268435 msWDT 跑 16 MHz APB、32 位计数时约 4.4 分钟WDT 一旦触发就无法避免复位在回调里或回调结束后再调wdt_kick()均无效。但要注意若回调在CONFIG_WDT_WARNING_PERIOD之前返回CPU 会回到 WDT 中断前的代码继续执行。若不希望出现这种行为建议在回调完成安全操作后加一个无限循环。八、CTIMER 定时器与 RTC8.1 CTIMER标准计数器/定时器QN908x 有4 个标准计数器/定时器CTIMER对 APB 时钟计数带32 位预分频器可把 APB 时钟最多分频到 2^32从而在运行时可选出极大的定时器频率范围。每个定时器有4 个独立匹配通道匹配时可产生中断periph_cpu.h 中TIMER_CHANNEL_NUMOF 4、PERIPH_TIMER_PROVIDES_SET 1。原文档中保留了 TODOCTIMER 与 SCT 定时器都可用于 PWM能力集不同未来板级应能在 pwm 与 timer 两种功能间切分这些 CTIMER 与 SCT 块——也就是说当前 PWM 能力尚未在该 CPU 端口中开放。配置只需在periph_conf.h中#define TIMER_NUMOF 48.2 RTCRTC 块由CLK_32K时钟源驱动——外部 32.768 kHz 晶振或内部 32 kHz RCO即第一节 Kconfig 中 32K 时钟选择器选中的源。RTC 内部秒计数器的校准取决于调用rtc_init()时所选时钟源的频率。实现上的重要差异该 CPU 的 RTC 没有像 timer 外设那样的目标值匹配中断。RIOTrtc.h接口的alarm 功能是用每秒一次的中断 软件比对目标值实现的。注意功耗影响RTC 可在 CPU 处于 power-down 0 模式下继续运行但启用 alarm 意味着CPU 每秒都会被唤醒一小段会拉高整机功耗。RTC 无需专门配置。九、UART 驱动QN908x 没有独立的 UART 块只有两个 FLEXCOMM 支持 UART 模式FLEXCOMM0 与 FLEXCOMM1分别映射为 USART0 与 USART1。默认模式为8n1可用uart_mode()更改若只需要单向通信另一方向引脚可置GPIO_UNDEF关闭例如只收不发的传感器日志口。uart_conf_t结构periph_cpu.htypedef struct { USART_Type *dev; /* 指向 FLEXCOMM_Type 寄存器组 */ gpio_t rx_pin; /* RX 引脚GPIO_UNDEF 关闭 RX */ gpio_t tx_pin; /* TX 引脚GPIO_UNDEF 关闭 TX */ } uart_conf_t;UART 模式位uart_parity_t/uart_data_bits_t/uart_stop_bits_t与USART-CFG寄存器的 PARITYSEL / DATALEN / STOPLEN 字段值直接对应。注意UART_PARITY_MARK、UART_PARITY_SPACE、UART_DATA_BITS_5、UART_DATA_BITS_6在本 CPU 上标记为非法/不支持带UART_INVALID_MODE位。UART 配置示例periph_conf.hstatic const uart_conf_t uart_config[] { { .dev USART0, .rx_pin GPIO_PIN(PORT_A, 17), /* 或 A5 */ .tx_pin GPIO_PIN(PORT_A, 16), /* 或 A4 */ }, { .dev USART1, .rx_pin GPIO_PIN(PORT_A, 9), /* 或 A13 */ .tx_pin GPIO_PIN(PORT_A, 8), /* 或 A12 */ }, }; #define UART_NUMOF ARRAY_SIZE(uart_config)QN9080DK 板只配置了 USART0A16/A17通常用作控制台periph_conf.h。十、小结在 RIOT 中落地 QN908x 的关键检查清单综合 cpu/qn908x/doc.md 与源码为 QN908x 目标开发应用时最需要注意的约束是Flexcomm 互斥USART1 / I2C0 / SPI0 / I2C1 / USART0 的分配必须避开同一 Flexcomm 块第五节映射表这是最常见的板级配置错误来源看门狗窗口用了periph_wdt就必须在启动 10 秒内wdt_setup_reboot*wdt_start回调中避免返回即复活语义陷阱第七节ADC 是差分 Σ-Δ先想清楚 Vinn/Vref/增益的组合再选通道温度传感器推荐 1.2 V Vref Vinn1/2 Vref需要绝对电压值时优先用ADC_RES_UV内部 1.2 V bandgap带出厂校准第四节RTC alarm 的功耗代价秒级唤醒深睡眠场景慎用SPI 时钟上限直接受 AHB 时钟约束高速外设如 Quad-SPI Flash应确认系统时钟配置CPU_CLK_*满足要求。参考文件CPU 文档 cpu/qn908x/doc.md、核心定义 cpu/qn908x/include/periph_cpu.h、ADC 实现 cpu/qn908x/periph/adc.c、参考板配置 boards/qn9080dk/include/periph_conf.h、板级文档 boards/qn9080dk/doc.md。【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考