这是迷你系列的 第 5 部分 。在第 4 部分中,我描述了如何设置 FTM(Kinetis Flex 定时器模块)以生成用于 DMA 操作的所需波形(参见“ 教程:Adafruit WS2812B NeoPixels 与飞思卡尔 FRDM-K64F 板 - 第 4 部分:定时器 ”)。在这篇文章中,我描述了如何使用来触发 DMA(Direct To Memory)事件。目标是使用 Freescale FRDM-K64F 板驱动 Adafruit 的 NeoPixel (WS2812B):
带有 Adafruit NeoPixel 的 FRDM-K64F
迷你系列教程列表
- 教程:Adafruit WS2812B NeoPixels 与 Freescale FRDM-K64F 板 – 第 1 部分: 硬件
- 教程:Adafruit WS2812B NeoPixels 与 Freescale FRDM-K64F 板——第 2 部分: 软件工具
- 教程:Adafruit WS2812B NeoPixels 与 Freescale FRDM-K64F 板——第 3 部分: 概念
- 教程:Adafruit WS2812B NeoPixels 与 Freescale FRDM-K64F 板 – 第 4 部分: 定时器
- 教程:Adafruit WS2812B NeoPixels 与 Freescale FRDM-K64F 板 – 第 5 部分: DMA
大纲
在本文中,我使用 DMA(直接内存访问)进行内存到内存操作,以生成 WS2812B LED 所需的位流。在之前的教程中,我使用 FRDM-K64F 设备的 FTM 生成三个信号:
波形和时序
我将使用信号的“下降沿”来触发 DMA 传输,在以下时序图中标记为“M”:
使用 DMA 驱动位
在这篇文章中,我使用 Kinetis Design Studio v3.0.0 和 Kinetis SDK v1.2。
我们将在本文后面设置整个引擎。首先让我们做一件简单的事情:将 GPIO 引脚配置为 LED 的 DIN。
GPIO端口
要向 NeoPixel/WS2812 的 DIN 生成信号,我可以使用普通的 GPIO(通用输入/输出)引脚。如果我在这样的 GPIO 端口上使用多个引脚,我可以驱动像素阵列的多个“通道”。
:idea: 每个 LED/像素我需要 24 位(红色、绿色和蓝色各 8 位)。由于将字节写入 GPIO 端口的性质,我需要为每个 LED 分配 3 个字节的内存(通常是 RAM)。因此,拥有大量 LED 意味着大量 RAM。只有一个通道,每个字节中只使用一位。但是如果我有 8 个通道(比如端口位 0 到 7),那么每个像素仍然需要 3 个字节,但是我可以用这三个字节驱动 8 个 LED。因此,如果您有很多很多 LED,请使用多个通道来组合它们。这不仅减少了所需的内存量,还减少了发送比特流所需的时间。
要使用 GPIO 端口,我需要:
- 将 Pin 多路复用 到使用的端口。基本上这意味着将端口内部信号路由到外部引脚。
- 为端口计时 (启用时钟)。在没有计时的情况下访问端口寄存器将导致 硬故障 。
- 使用 GPIOx_PDDR(端口数据方向寄存器)将端口/引脚 配置 为 输出 引脚/端口。
- 要将引脚设置 为高电平 ,我可以将 1 位/值写入 GPIOx_PSOR(端口设置输出寄存器)
- 要将引脚设置 为低电平 ,我可以将 1 位/值写入 GPIOx_PCOR(端口清除输出寄存器)
- 要将引脚设置为 HIGH 或 LOW ,我可以将位/值写入 GPIOx_PDOR(端口数据输出寄存器)。
下图显示了创建 WS2812 比特流所需的端口输出寄存器写入:
GPIO 输出寄存器写入
我们可以从定时器中断中做到这一点,但这又太慢了。相反,这些端口输出寄存器写入应由 DMA 触发。
配置 GPIO 端口
在我的板上,我只使用一个通道/引脚连接到 WS2812B 的 DIN。我将为此使用 PTD0 (端口 D,引脚 0):
使用 PTD0 到 DIN
另外三根白线是连接到逻辑分析仪的三个FTM通道的引脚。
所以我需要扩展我的硬件初始化如下:
- 第 4 行:使能端口 D 的时钟门控
- 第 11 行:多路复用器 PTD0 作为 GPIO
- 第 12 行:写 PDDR(端口数据方向寄存器)一个 1 位以使用 PTD0 作为输出引脚。
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
您可能会注意到我正在使用不同的 API 来执行此操作。
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
是 Kinetis SDK 的一种方法。然而
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
正在使用 CMSIS-Core 风格的直接寄存器写入。木星直前。但是,要将一个引脚设置为输出引脚,需要在 SDK 中添加带有引脚描述符的层。对我而言,在此示例中使用 Kinetis SDK GPIO 层过于复杂,因此我仅使用 CMSIS 寄存器宏。
:idea: 我也想在这里展示 SDK 与 CMSIS 的混合搭配是我认为平衡易用性和复杂性的好事。
这样,我就配置了我的 GPIO 引脚。现在我需要用 DMA 写入端口寄存器。
直接内存访问
如 概念 帖子中所述,我需要一些非常快速的东西来编写 GPIO 端口寄存器。由于时间约为 0.3 μs,因此使用 CPU 肯定太快了,特别是如果我也希望 CPU 做其他事情。使用 DMA,无需 CPU 参与即可完成对内存的访问,这正是我所需要的。
我在 FRDM-KL25Z 板上使用 DMA 来进行 DIY 逻辑分析仪 中的读取端口或 驱动 WS2812 像素等 操作。 FRDM-K64F 板上的 ARM Cortex-M4F 微控制器上有一个 eDMA(增强型 DMA)控制器。它可以使用多达 16 个独立的 DMA 通道进行 DMA 操作,具有高级源/和目标地址计算。该 eDMA 控制器在 K64F 参考手册 中有描述。
eDMA 框图(来源:Freescale K64F 参考手册)
- 数据路径 :控制器可以从/向交叉开关读取/写入数据。交叉开关提供对内存和外围设备的访问。
- 地址路径 :此块正在计算源地址和目标地址。它进行计算,加上地址的任何递增或递减。为此,它使用传输控制描述符 (TCD)。
- 控制和通道仲裁 :该块负责从支持的请求源(例如,从定时器模块)接收 DMA 请求,并将标志写回它(比如告诉定时器模块 DMA 操作已完成)。
- 传输控制描述符 :该描述符用于描述在 DMA 操作中应该做什么:读/写多少字节,源地址和目标地址,传输后做什么,多少循环(内循环和外循环)。
基本的 DMA 流程如下: 当 DMA 外设请求进来时,它将使用 TCD 设置源地址和目标地址:
eDMA 操作,第 1 部分(来源:Freescale K64F 参考手册)
使用源地址和目标地址,控制器将执行读/写操作。根据 TCD 中的配置,这可以是具有“次要”和“主要”循环计数器的多个源/目标读/写:
eDMA 操作,第 2 部分(来源:Freescale K64F 参考手册)
在最后一步,TCD 被更新,例如地址值被改变并且标志被设置。此外,请求 DMA 传输的外设会收到操作已完成的通知:
eDMA 操作,第 3 部分
内存注意事项
请记住,我有三个 FTM 频道。每个通道都应触发一个 GPIO 端口操作:
- FTM0 通道 0 :将“1”写入 PSOR 以将 DIN 设置为高电平。
- FTM0 通道 1 :将数据位写入 PDOR 以保持 DIN 高电平('1' WS2812 位)或将 DIN 置于低电平('0' WS2812 位)。
- FTM0 通道 2 :将“1”写入 PCOR 以将 DIN 设置为低电平。
这需要为每个 WS2812 位完成,位数由 WS2812 LED 的数量给定(每个 24 位),这些位存储在缓冲区中:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
请记住,每个字节中只使用最低有效位,因为我只使用 WS2812 的单个通道。
:idea: 如果我使用 8 个通道(例如 8 个 NeoPixel 矩阵显示器,每个都连接到一个端口引脚,PTD0 到 PTD7),那么我将使用字节的每一位。每个 WS2812 像素需要 3 个字节的内存。
触发 DMA 请求
要从我的 FTM 通道启用 DMA 请求,我需要仔细阅读参考手册:
FTM DMA 请求
令我困惑的是两个设置(DMA=0|CHnIE=0 和 DMA=1|CHnIE=0)在做同样的事情?首先我认为这一定是手册中的复制粘贴错误。但是如果不启用“中断启用”(CHnIE)位,DMA 就无法工作 :-( 。所以看起来这两个位实际上都必须设置。这就是我在 FTM 初始化/重置例程中必须做的事情:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
DMA 驱动程序初始化
是时候初始化 SDK 的 DMA 驱动程序了。由于 eDMA 的复杂性,我再次混合使用 Kinetis SDK API 和 Kinetis SDK HAL API。我使用 SDK API 初始化 DMA:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
初始化相当简单:我将 DMA 通道仲裁(优先级调度)设置为 Round-Robin。这意味着 DMA 将一个接一个地执行,而不使用 DMA 通道优先级机制。因为我有一个固定的定时器通道事件序列,所以我保持简单并使用循环法。对于 noHaltOnError,我指定设备在出现错误时不应停止,这也是为了让事情简单化。
我将 DMA 驱动程序初始化为我的硬件初始化的一部分:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
将位传输到 DMA
到目前为止,我已经设置好了一切:
- FTM 计时器正在生成所需的信号,并启用了 DMA 触发
- 用于 LED 的 DIN 的 GPIO 已准备就绪
- eDMA 驱动程序已初始化
现在我可以开始 DMA 传输,我使用以下方法:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
请记住,我有一个缓冲区,其中包含 WS2812 LED 的位。为了将位发送到 PTD0,我可以使用
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
DMA传输
我将使用三个 DMA 通道,每个定时器通道一个。为了在 DMA_Transfer() 中使用 DMA 传输位,我执行以下操作:
- 重置 FTM :重置定时器寄存器。此时 FTM 没有计时。
- DMA Muxing :为 FTM0 通道 1、2 和 3 请求三个 DMA 通道
- 安装回调 :为 DMA 通道 3 安装“传输结束”中断处理程序。这样,当所有位的传输结束时,我会收到通知。
- 设置 DMA TCD :使用 DMA 通道的源/目标设置传输控制描述符。
- 启动/启用所有 DMA 通道 :这将打开/启用 DMA 通道。
- 启动 FTM :初始化“dmaDone”标志并打开 FTM 的时钟,让计时器运行。
- 等到 DMA 完成 :“传输中断结束”将设置“dmaDone”标志。
- 关闭 FTM :从 FTM 计时器中删除时钟。
- 禁用/停止所有 DMA 通道。
- De-Mux 和 de-install DMA 通道。
:idea: 您可能想知道为什么我要为每次传输(第 2 步和第 10 步)执行 Muxing 和 De-Muxing?答案是(我相信)DMA 控制器内部存在内部传播延迟。对 DMA 进行复用和解复用可确保 DMA 控制器重置其内部寄存器。我不得不通过艰难的方式学习这一点:DMA 在较低速度(比如 1 ms DMA 频率)下工作良好,因为模块内部有足够的时间和时钟使其进入正确的状态。但是在我这里使用的亚μs 时域中使用 DMA 肯定会显示一些奇怪的 DMA 行为和“幽灵”DMA 传输。我已经在 FRDM-KL25Z 上发生了这些奇怪的事情,请参阅“ NeoShield:带 DMA 和 nRF24L01+ 的 WS2812 RGB LED 屏蔽 ”。
下面是完整的例程,我会讨论一些细节
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
一个重要的部分是 TCD(传输控制描述符)的配置。我设置了三个描述符,每个 DMA 通道一个:
- 通道 0:将“1”写入 PSOR(端口设置输出)寄存器。
- 通道 1:将数据位写入 PDOR(端口数据输出)寄存器。
- 通道 2:将“1”写入 PCOR(端口清除输出)寄存器。
描述符有几个字段来配置 DMA 传输。基本上我对 DMA 传输的描述是“从这个源地址取出这个字节并将它写入这个目标地址”。此外,我指定“要读/写多少字节”以及是否应对源地址和目标地址执行一些地址计算。在接下来的部分中,我将解释不同的设置:
在 eDMA 中,可以在最后一次传输结束时进行特殊调整:因为我不需要对 WS2812 进行此调整,该设置是零偏移量:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
DMA 地址计算可以配置为“环绕”,例如,如果使用环形缓冲区:我将其禁用,因为我不需要该功能:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
下一个设置是指定在单个 DMA 传输中必须传输多少字节:我只需要将一个字节写入 GPIO 端口:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
在下一个设置中,我可以指定“次要”和“主要”循环:这样我就可以“嵌套”DMA 操作:
eDMA 多循环交互
在我的例子中,我只需要为每个 DMA 请求写入一个字节,因此次要循环计数器为“1”。但是,我需要为 DMA 操作写入多个字节(写入 transmitBuf[] 的所有字节,因此 majorLoopCount 是字节总数:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
下一个设置是指定目标地址应该发生什么。目标地址将是 GPIO 端口地址,因此无需更改。
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
以上设置对于所有三个 DMA 通道都是相同的。以下是用于每个 DMA 通道的特殊设置。
DNA 通道零将产生 DIN WS2812 信号的上升沿。要由 CPU 执行,我会这样写:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
翻译成 DMA 描述符是这样的:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
接下来是 DMA 通道 1,它将写入数据位。在“正常”代码中,它将是这样的:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
在“DMA 语言”中是这样的:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
最后,与 DMA 通道 0 一样,通道 2 将一个 1 写入 GPIO 寄存器:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
使用此自定义例程将每个描述符写入硬件寄存器:
你注意到那个 temp[2] 变量了吗?这是将 TCD 对齐到 32 字节边界所必需的。如果 TCD 的地址未与该边界对齐,则会发生硬故障
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
:idea: 警告:Kinetis SDK v1.2 中的 EDMA_DRV_ConfigLoopTransfer() 函数可能会产生硬故障,因为它不进行特殊对齐。
DMA 通道 0 和 1 配置为不创建任何中断。只有通道 2 配置了第三个参数以在“主要”迭代结束时引发中断(当传输所有字节时):
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
所以我必须在通道 2 上为 DMA 中断添加一个处理程序,否则我的应用程序将以未处理的中断结束。 DMA2_IRQHandler()是中断处理器,EDMA_DRV_IRQHandler()会调用回调EDMA_Callback():
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
我必须安装的那个处理程序
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
将所有 TCD 设置推送到 DMA 通道后,是时候启用所有通道了:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
然后我重置“完成”标志,启动 FTM 计时器并等待传输完成:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
发送完所有字节后,我停止 FTM 计时器,禁用通道并释放 DMA 通道:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
这样就完成了 DMA 传输,下一次传输可以重新开始。
“奇妙而多彩的事物”
是时候尝试一下了。以下程序写入三个 WS2812B 像素:绿色、红色和蓝色:
static void InitHardware(void) {
/* Enable clock for PORTs */
SIM_HAL_EnableClock(SIM, kSimClockGatePortC);
SIM_HAL_EnableClock(SIM, kSimClockGatePortD);
/* Setup board clock source. /
g_xtal0ClkFreq = 50000000U; / Value of the external crystal or oscillator clock frequency of the system oscillator (OSC) in Hz /
g_xtalRtcClkFreq = 32768U; / Value of the external 32k crystal or oscillator clock frequency of the RTC in Hz */
/* Use PTD0 as DIN to the Neopixels: mux it as GPIO and output pin /
PORT_HAL_SetMuxMode(PORTD, 0UL, kPortMuxAsGpio); / PTD0: DIN to NeoPixels /
GPIO_PDDR_REG(PTD_BASE_PTR) |= (1<<0); / PTD0 as output */
/* FTM and FTM Muxing /
InitFlexTimer(FTM0_IDX);
PORT_HAL_SetMuxMode(PORTC,1UL,kPortMuxAlt4); / use PTC1 for channel 0 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,2UL,kPortMuxAlt4); / use PTC2 for channel 1 of FTM0 /
PORT_HAL_SetMuxMode(PORTC,3UL,kPortMuxAlt4); / use PTC3 for channel 2 of FTM0 */
}
通过逻辑分析仪检查,我可以看到发送数据需要 91.1 μs:
三个WS2812定时传输数据
以下放大发送的前 8 位(绿色):
前 8 个绿色位
我还可以看到定时器/DMA 事件与端口位实际更改之前的时间之间的延迟:大约为 0.2 μs:
DMA 到 GPIO 延迟
但是“1”和“0”位的时序在规范内:-)
WS2812 位 1 时序
WS2812 位 0 时序
瞧,这就是我在 NeoPixel Matrix 上得到的:绿色、红色和蓝色的前三个 LED :-):
红色、绿色和蓝色像素
概括
我现在有 FTM 和 DMA 工作,它在一个或多个通道中从 GPIO 端口中取出位。我现在只使用一条车道,但它与多条车道的工作方式相同。有了 128 KB 的 RAM,我现在可以驱动的 WS2812 像素数量是巨大的:如果我使用单通道,我需要每个像素 24 字节。因此,对于一个 8×8 矩阵,我需要 1536 字节,但如果我使用八个 8×8 板和 8 个通道(PTD0 到 PTD7),我只需要每个像素 3 个字节:1536 个字节 :-)
:idea: 我可以将像素的所有 24 位打包成三个字节,然后进行多级 DMA 传输:解压这些位并将其发送到端口。我还没有想通,但也许这可以减少单通道配置所需的 RAM 量。
这个项目在 Freescale Kinetis 设备上使用 DMA,我尽力解释这里使用的方法。尽管如此,DMA 还有更多的功能和可能性。熟悉 DMA 需要一些时间,但它的功能是惊人的 :-) 。
我不得不混合使用 Freescale Kinetis SDK API、SDK HAL API 和 CMSIS 注册访问宏。 Freescale 正在推广 Kinetis SDK,但这个项目再次向我证实,仅靠 SDK 并不能涵盖开发嵌入式应用程序的所有需求:我仍然需要 CMSIS 注册访问 API。另一方面:SDK 中有一些很好的例程,尤其是 HAL 层,它使事情更容易使用。但与所有事情一样:学习所有这些东西需要时间。我希望本系列文章可以帮助您完成这个学习过程。
项目源位于 GitHub 上:
https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/KDS/FRDM-K64F120M/FRDM-K64F_NeoPixel_SDK
那么,接下来会发生什么?我可以描述/开发 WS2812 像素的“图形”驱动程序吗?或者也许这是我留给 Manya 的东西?发表评论让我知道你的想法 :-) 。
快乐的 DMAing :-)