使用臂组件在 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 时钟 将 GPIO 18 和 19 设置为输入(这是多余的,不必要的) 将 GPIO 18 和 19 设置为备用功能 5(将它们更改为 PWM 输出而不是输入) 开始无限循环写入 DAT1 和 DAT2,值在 0 和(范围最大值)之间变化

注意事项:

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 3B+:相机 V1.3 不工作

如何在 Raspberry Pi 4 上使用 Azure 语音服务 C# SDK 设置麦克风

在树莓派2代B型/3代 上安装Fedora23 - Installing Fedora 23 on Raspberry Pi 2 model B or Raspberry Pi 3

sh Raspberry Pi 3接入点设置

在 Raspberry Pi 3 上使用 pymssql 的 SQL Server 连接

无法使用 Raspberry Pi 3 在 Windows IoT 上打开 UART 端口