使用臂组件在 Raspberry Pi 3 上设置脉冲宽度调制的步骤是啥?
Posted
技术标签:
【中文标题】使用臂组件在 Raspberry Pi 3 上设置脉冲宽度调制的步骤是啥?【英文标题】:What are the steps to set up Pulse Width Modulation on the Raspberry Pi 3 using arm assembly?使用臂组件在 Raspberry Pi 3 上设置脉冲宽度调制的步骤是什么? 【发布时间】:2019-09-23 00:58:36 【问题描述】:我正在为学校做 Raspberry Pi 3 (RPi3) 裸机作业,我正在尝试获取我的汇编代码来更改 RGB LED 中每个二极管的亮度。
简单来说,我试图让脉冲宽度调制 (PWM) 在我的 RPi3 上使用汇编代码而不是库。经过研究(详情如下),我相信我已经收集了设置 PWM 以在 GPIO 18 和 19 上使用所需的所有步骤。但是,使用我的代码,LED 甚至没有亮起。有没有我遗漏的步骤,或者我的代码中没有看到什么?
采取的步骤:
我将代码简化为自上而下的过程,除了一个 子过程_wait,经过测试可以正常工作。我想 消除尽可能多的潜在问题,例如错误放置的堆栈指针。 我引用的是 BCM2835 Arm 外设1。我已经使用 Ultibo 2 及其 PWM 示例在我的 Pi 上测试了 PWM 有积极的结果。我什至阅读/跟踪了他们的源代码以收集 他们使用的程序和硬件地址的列表。那是我 想出了我在汇编代码中使用的列表:
-
设置 RNG1 和 RNG2
设置 MODE1 和 MODE2
启用 M/S 通道
设置频率:
停止 PWM 时钟 a
在 PWM CLK DIV 寄存器中设置频率 b
将时钟设置为振荡器
启动 PWM 时钟
注意事项:
a 我使用 Ultibo 源代码来查找 PWM 时钟的地址,因为它不在 BCM2835 中。另外,根据 Ultibo 源代码,PWM 时钟使用与 BCM2835 的第 107 页和第 108 页中的通用时钟相同的设计,所以我将使用相同的名称。
b 还使用 Ultibo 查看了确定要为 DIVI 和 DIVF 输入的数字的公式:(Default Frequency/Desired Frequency) = DIVI 余数 DIVF .该示例使用 19.2MHz 的默认值和 9.6MHz 的所需频率,这导致 DIVI 为 2,DIVF 为 0。
参考资料:
1https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf
2https://github.com/ultibohub/Examples/tree/master/18-PWMControl/RPi3
代码
.section .text
.global _start
_start:
@===============================================================
@ Pulse Width Modulator
@===============================================================
@ 1. Set Ranges
@ 2. Set Modes
@ 3. Enable M/S
@ 4. Enable Channels
@===============================================================
LDR R5, =0x3F20C000 @ R5 = CTL (PWM Control Register, See BCM2835 Pg 142-143)
LDR R6, =0x3F20C010 @ R6 = RNG1 (See BCM2835 Pg 141)
LDR R7, =0x3F20C020 @ R7 = RNG2 (All 32 bits dedicated to the value)
@ Set Ranges --------------------------------------------
MOV R1, #0xFF @ Desired Range from 0 to 255
STR R1, [R6] @ RNG1:=255
MOV R0, #10
BL _wait @ Wait 10 ms
STR R1, [R7] @ RNG2:=255
MOV R0, #10
BL _wait @ Wait 10 ms
@ Set Modes -------------------------------------------------
LDR R1, [R5] @ Get current Control Registers
BIC R1, #1<<1 @ Clear Bit 1 (MODE1:=PWM_Mode)
STR R1, [R5] @ Store back to CTL
MOV R0, #10
BL _wait @ Wait 10ms
LDR R1, [R5] @ Get current Control Registers again
BIC R1, #1<<9 @ Clear Bit 9 (MODE2:=PWM_Mode)
STR R1, [R5] @ Store bac
MOV R0, #10
BL _wait @ Wait 10ms
@ Enable M/S -----------------------------------------------
LDR R1, [R5] @ Get current Control Registers
ORR R1, #1<<7 @ Set Bit 7 (MSEN1:=1)
STR R1, [R5] @ Store back
MOV R0, #10
BL _wait @ Wait 10ms
LDR R1, [R5] @ Get current Control Registers
ORR R1, #1<<15 @ Set Bit 15 (MSEN2:=1)
STR R1, [R5] @ Store back
MOV R0, #10
BL _wait @ Wait 10ms
@ Enable Channels --------------------------------------------
LDR R1, [R5] @ Get current Control Registers
ORR R1, #1<<0 @ Set Bit 0 (PWEN1:=1)
STR R1, [R5] @ Store back
MOV R0, #10
BL _wait @ Wait 10ms
LDR R1, [R5] @ Get current Control Registers
ORR R1, #1<<8 @ Set Bit 8 (PWEN2:=1)
STR R1, [R5] @ Store back
MOV R0, #10
BL _wait @ Wait 10ms
@===========================================================
@ Clock Procedures
@===========================================================
@ 1. Stop Clock
@ 2. Set Frequency
@ 3. Set Clock to Oscillator Mode
@ 4. Start Clock
@===========================================================
LDR R5, =0x3F1010A0 @ PWM CLK CTL (Clock Control Register)
LDR R6, =0x3F1010A4 @ PWM CLK DIV (Clock Divisor Register)
LDR R7, =0x5A000000 @ PWM CLK PASSWD
@ Stop Clock ----------------------------------------------
LDR R3, [R5] @ Read PWM Clock Control Register
BIC R3, #1<<7 @ Clear BUSY bit
BIC R3, #1<<4 @ Clear ENAB bit (to turn it off)
ORR R3, R7 @ Set PASSWD
@ R3 is ready to write
STR R3, [R5] @ Store R3 to Clock Register (ENAB bit is cleared)
MOV R0, #10
BL _wait @ Wait 10ms
@ Set Frequency -------------------------------------------------
_clock_freq:
MOV R3, #2<<12 @ 19.2MHz divided by 9.6MHz = 2 remainder 0
@ Shift 12 places to Integer part of Divisor
@ (BCM2835 Pg 108 for bit-packing of general clocks)
ORR R3, R7 @ Set Password
@ R3 should now be 0x5A002000
_clock_freq_loop:
MOV R0, #10
BL _wait @ Wait 10ms each loop
LDR R1, [R5] @ Load Clock Control Register (to check BUSY bit)
LSR R1, #7 @ Move Busy Bit to Bit Position 0
AND R1, #1 @ Single out the Busy Bit
CMP R1, #0 @ Check to see if it is cleared
BNE _clock_freq_loop @ Try again if clock is busy
_clock_freq_end:
STR R3, [R6] @ Set Frequency to Clock Divisors Register
MOV R0, #10
BL _wait
@ Set Oscillator Mode --------------------------------------
_clock_osc:
LDR R3, [R5] @ Read PWM Clock Control Register
BIC R3, #1<<7 @ Clear BUSY bit
BIC R3, #0xF @ Clear SRC bits (0-3)
ORR R3, #1 @ Set SRC to Oscillator Mode
ORR R3, R7 @ Set PASSWD
@ R3 is ready to write
_clock_osc_loop:
MOV R0, #10
BL _wait @ Wait 10ms each loop
LDR R1, [R5] @ Load Clock Control Register
LSR R1, #7 @ Move Busy Bit to Bit Position 0
AND R1, #1 @ Single out the Busy Bit
CMP R1, #0 @ Check to see if it is cleared
BNE _clock_osc_loop @ Try again if clock is busy
_clock_osc_end:
STR R3, [R5] @ Store R3 to Clock Register (SRC = 1)
MOV R0, #10
BL _wait @ Wait 10ms
@ Start Clock -----------------------------------------
_clock_start:
LDR R3, [R5] @ Read PWM Clock Control Register
BIC R3, #1<<7 @ Clear BUSY bit
ORR R3, #1<<4 @ Set ENAB bit (to turn it back on)
ORR R3, R7 @ Set PASSWD
@ R3 is ready to write
_clock_start_loop:
MOV R0, #10
BL _wait @ Wait 10ms each loop
LDR R1, [R5] @ Load Clock Control Register
LSR R1, #7 @ Move Busy Bit to Bit Position 0
AND R1, #1 @ Single out the Busy Bit
CMP R1, #0 @ Check to see if it is cleared
BNE _clock_start_loop @ Try again if clock is busy
_clock_start_end:
STR R3, [R5] @ Store R3 to Clock Register (ENAB bit is set)
MOV R0, #10
BL _wait @ Wait 10ms
@======================================================
@ GPIO Procedures
@======================================================
@ 1. Set Pins 18 and 19 as Inputs
@ 2. Set to Alternate Function 5 (Overwrites Previous Step)
@======================================================
LDR R3, =0x3F200004 @ R3 = GPIO_FSEL1
@ Set Pins as Input ---------------------------------
LDR R1, [R3] @ Load current FSEL1 options
BIC R1, #0b111111<<24 @ Sets GPios 18 and 19 to Default (Input)
STR R1, [R3] @ Not sure why... but Ultibo does this in bcm2710.pas
MOV R0, #10
BL _wait @ Wait 10ms
@ Set Pins to Alternate Function 5 ---------------------
LDR R1, [R3] @ Load current FSEL1 options again
BIC R1, #0b111111<<24 @ Clear whatever is in FSEL for 18 and 19 (should already be clear)
ORR R1, #0b010010<<24 @ 010 010 (010 is alt function 5 for both GPIO 18 and 19)
@ Shifted over 8 sets of 3 bits
@ (8 * 3 = 24) into position for GPIO 18 and 19
STR R1, [R3] @ Store it into FSEL1
MOV R0, #10
BL _wait @ Wait 10ms
@==============================================
@ Set Data / Main Loop
@==============================================
LDR R5, =0x3F20C014 @ R3 = PWM_DAT1
LDR R6, =0x3F20C024 @ R4 = PWM_DAT2
@ Main Loop ----------------------------
MOV R3, #0
MOV R4, #255
_loop:
CMP R3, #255 @ Reset Counters When They
MOVGT R3, #0 @ Go Beyond Range 0..255
CMP R4, #0
MOVLT R4, #255
STR R3, [R5] @ Set DAT1 to a number between 0..255
MOV R0, #10
BL _wait @ Wait 10ms
STR R4, [R6] @ Set DAT2 to a number between 0..255
MOV R0, #10
BL _wait @ Wait 10ms
ADD R3, R3, #1 @ R3++
SUB R4, R4, #1 @ R4--
_reloop:
BAL _loop
@===============================
@ _wait
@===============================
@ Purpose:
@ Takes an initial time from
@ the TIMER, and continuously
@ checks if the amount of time
@ in R0 has elapsed.
@
@ Initial:
@ R0 = delay in ms
@
@ Registers Used:
@ R4-R7
@===============================
_wait:
_wait_prolog:
PUSH R4-R7, LR
MOV R6, #1000
MUL R4, R0, R6 @ R4 = desired delay (x1000 ms to us)
LDR R5, =0x3F003004 @ R5 = TIMER
LDR R6, [R5] @ initial time (us)
_wait_loop:
LDR R7, [R5] @ current time (us)
SUB R7, R7, R6 @ initial time - current time = time delayed
CMP R7, R4 @ if the desired delay has not been reached
BLT _wait_loop @ then loop again
_wait_epilog:
POP R4-R7, PC
我希望 LED 至少会打开并调制时钟,但它甚至没有打开。
我已经测试了 GPIO 的输出和输入功能。使用我在代码中使用的相同硬件地址,我能够让 LED 闪烁,并且在按下按钮后另一个 LED 闪烁 3 次,因此 GPIO 功能应该没问题。
唯一我不能轻易测试的事情是我是否采取了正确的步骤来启用 PWM,或者我是否正确设置了时钟以使其不会失败。
【问题讨论】:
写得很好的问题。不知道怎么回答。 +1 您是否首先在 C 之类的东西中尝试过这个,更容易编程(可以在 C 中做裸机)来证明算法?您是否尝试过先用推拉使 LED 闪烁,然后再尝试使用 pwm?您是一次只使用一个外围设备,还是只使用它并尝试一次为所有内容编写一堆代码?这里有很多,不知道你是怎么到这里的。 @old_timer 不,我还没用过 C。我对使用带有地址指针的 C 有点不熟悉,但我认为学习它会很好。对于我的组装,我首先让 LED 作为输出打开,只是为了测试我使用的基地址是否正确。然后我刚刚在网上下载了使用 PWM 用于 RPi3 的开源代码,这样我就可以看到他们采取了哪些步骤以及他们使用的地址。由于要启用它需要多少步骤,因此要测试 PWM 是否工作有点困难。 如果这对某些人来说并不容易,那就没问题了……在 raspberrypi.org 上有一个裸机论坛,您尝试过吗? 我也为问题的质量+1。无能为力,因为所有这些位和地址都没有告诉我任何信息。仔细阅读代码,没有发现任何“愚蠢”的错误,对 BAL 感到困惑大约 5 秒 :) 为了完整起见,您能否还显示 _wait 代码? (并确保它不会损坏任何东西)。 【参考方案1】:我没有尝试过代码,但乍一看似乎你永远不会离开等待子程序。
在@ Set Ranges
分支到_wait
,
等着你做
PUSH R4-R7, LR
,
这是正确的,因为LR
存储了调用子程序的点的地址。
但最终你做到了
POP R4-R7, PC
.
如果你推送LR
,你应该检索LR
而不是PC
,你还需要告诉子程序回到调用它的地址,保存到LR register
,然后能够回到BX LR
。
我不排除存在其他问题,但使用上述引用对代码进行简单更改可能是这样的:
_wait:
_wait_prolog:
PUSH R4-R7, LR
MOV R6, #1000
MUL R4, R0, R6 @ R4 = desired delay (x1000 ms to us)
LDR R5, =0x3F003004 @ R5 = TIMER
LDR R6, [R5] @ initial time (us)
_wait_loop:
LDR R7, [R5] @ current time (us)
SUB R7, R7, R6 @ initial time - current time = time delayed
CMP R7, R4 @ if the desired delay has not been reached
BLT _wait_loop @ then loop again
_wait_epilog:
POP R4-R7, LR
BX LR
【讨论】:
POP
进入 PC 是从函数返回的有效方式,但在 ARMv5 之前它不会在 ARM/Thumb 之间切换。树莓派 3 是 ARMv8,题中不包含 Thumb 代码。以上是关于使用臂组件在 Raspberry Pi 3 上设置脉冲宽度调制的步骤是啥?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Raspberry Pi 4 上使用 Azure 语音服务 C# SDK 设置麦克风
在树莓派2代B型/3代 上安装Fedora23 - Installing Fedora 23 on Raspberry Pi 2 model B or Raspberry Pi 3