I'm working on a Raspberry Pi 3 (RPi3) bare-metal assignment for school and I am trying to get my assembly code to change the brightness of each diode in the RGB LED.
In simple, I am trying to get Pulse Width Modulation (PWM) to work on my RPi3 using assembly code and no libraries. After research (details below), I believe I have gathered all the steps necessary to setting up PWM to use on GPIOs 18 and 19. But, using my code, the LED is not even lighting up. Are there any steps that I am missing, or is there something I am not seeing in my code?
Steps Taken:
- I simplified my code to being a top down procedure, except for one sub-procedure _wait, which is tested to be working. I wanted to eliminate as many potential issues such as misplaced stack pointers.
- I am referencing the BCM2835 Arm Peripherals{1}.
I've tested the PWM on my Pi using Ultibo {2} and their PWM example with positive results. I even read/traced their source code to gather a list of procedures and hardware addresses they use. That is where I came up with my list that I use in my assembly code:
- Set RNG1 and RNG2
- Set MODE1 and MODE2
- Enable M/S Channels
- Set The Frequency:
- Stop the PWM Clock {a}
- Set the Frequency in the PWM CLK DIV Register {b}
- Set Clock to Oscillator
- Start the PWM Clock
Set GPIO 18 and 19 as Input(This is redundant an unnecessary)- Set GPIO 18 and 19 to Alternate Function 5 (Changes them to PWM Outputs instead of Inputs)
- Start an infinite loop writing to DAT1 and DAT2 with changing values between 0 and (range max value)
Notes:
{a} I used Ultibo Source Code to find the Address for PWM Clock because it is not in the BCM2835. Also, according to the Ultibo source code, the PWM clock uses the same design as the General Purpose Clock in page 107 and 108 of the BCM2835, so I will be using the same names.
{b} Also used Ultibo to see the formula to determine the numbers to put in for DIVI and DIVF: (Default Frequency/Desired Frequency) = DIVI remainder DIVF. The example uses a default of 19.2MHz and a desired frequency of 9.6MHz, which result in a DIVI of 2, and a DIVF of 0.
References:
{1} https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf
{2} https://github.com/ultibohub/Examples/tree/master/18-PWMControl/RPi3
Code
.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}
I'm expecing the LED to at least turn on and modulate on the clock, but it is not even turning on.
I've already tested the GPIOs for output and input functionality. Using the same hardware addresses I use in the code, I was able to get a blinking the LED, and also got another LED to Blink 3 times after a button press, so the GPIO function should be fine.
The only things I can't test as easily is if I am taking the correct steps to enabling PWM, or if I am properly setting the clock so that it doesn't fail.