In Part 1, we learned how to toggle a single Red LED on the MSP432 LaunchPad development board from Texas Instruments, using assembly instead of C / C++.
In this Instructable, we will do something similar - control an RGB LED that is also on that same board.
Along the way, we hope to further our knowledge of ARM assembly, and not just have fun lighting some LEDs.
Step 1: Let's Jump Right In
Really, the first video says it all. Not much more to add.
The main point of it is to drive home the idea that each I/O port on the MSP432 consists of a block of "register" addresses, which in turn consist of several bits each.
Furthermore, the bits are grouped in an orthogonal manner. That is, bit 0 of each register address refer to the same external I/O pin.
We repeated the idea that it takes several register addresses for that port, to do something with even just one bit or pin.
But that in this case, since we are dealing with an RGB LED, we need to deal with three bits for each register address.
We reinforced that we need several registers: the DIR register, the SEL0 register, the SEL1 register, and the OUTPUT register. And three bits each time.
Step 2: Improve Code - Add a Function
As you saw in the above Step, the main program loop had a lot of repeated code, namely, when we turn off the LEDs.
So we can add a function to the program. We still have to call that function every time we want to turn off the LEDs, but it causes some of the code to collapse to a single statement.
Had our LED-off code been more involved with many more instructions, this would have been a real memory-saver.
Part of embedded programming and micro-controllers is being much more aware of program size.
The video explains.
Essentially, we add a branching statement to our main code, and we have another block of code that is the function we branching to. And then once we are done, or at the end of the function, we branch back to the next statement within the main program.
Step 3: Add a Busy-Loop Delay
In the Declarations section of the code, add a constant to make it easy to tweek for the desired timing:
; any words after a semi-colon (';') starts a comment. ; the code in this part assigns a name to a value. ; you could also have used '.equ' but they're slightly different. ; '.equ' (I think) can not be changed, whereas '.set' means you can ; change the value of 'DLYCNT' later in the code if you wish. ;'DLYCNT' will be used as the countdown value in the delay subroutine. DLYCNT .set 0x30000
Add a new delay function:
delay: .asmfunc ; the start of the 'delay' subroutine or function. MOV R5,#DLYCNT ; load core cpu register R5 with value assigned to 'DLYCNT'. dlyloop ; this marks start of delay loop. assembler determines address. SUB R5,#0x1 ; subtract a 1 from current value in core cpu register R5. CMP R5,#0x0 ; compare current value in R5 to 0. BGT dlyloop ; branch if value in R5 is greater 0, to label(address) 'dlyloop'. BX LR ; if we got to here, means R5 value was 0. return from subroutine. .endasmfunc ; marks end of subroutine.
Then in the main body, within the main loop, invoke or call that delay function:
; this is a code fragment, of main body or main function (see file 'main.asm'). ; this is a loop in 'main', and shows how we call or use that new 'delay' function. ; the '#REDON' and '#GRNON' are also declarations (constants) (see top of 'main.asm'). ; they're just an easy way to set the specified color of RGB LED. loop MOV R0,#REDON ;Red - set core cpu register R0 with value assigned to 'REDON'. STRB R0,[R4] ;core register R4 was previously set with a GPIO output address. ;write what's in R0, into address specified by R4. BL delay ;branch to the new 'delay' function. BL ledsoff ;branch to the pre-existing 'ledsoff' function. BL delay ;ditto MOV R0,#GRNON ;Green - ditto STRB R0,[R4] ; and so on. BL delay BL ledsoff BL delay</p>
The video goes into detail.
Step 4: ARM Architecture Procedure Call Standard (AAPCS)
It is probably a good time to introduce something. It's an assembly-language convention. Also known as the Procedure Call Standard for the ARM Architecture.
There's a lot to this, but it's just a standard. It doesn't keep us from learning assembly programming, and we can adopt pieces of that standard as we go, once we feel comfortable with some concepts that we're learning.
Otherwise, we might feel like we're drinking from a huge water hose. Too much information.
Since we've become familiar with the MSP432's core registers, let's try to now adopt some of these standards. We'll conform to this when we write the next function (turn on / off an LED).
1) We are supposed to use R0 as a function parameter. If we wish to pass a value into the function (subroutine), we should use R0 to do so.
2) We are to use the Link Register for its intended purpose - it holds the address that indicates where to return to after the subroutine is complete.
You'll see how we apply these.
Step 5: Function With Parameter - Nested Functions
We can clean up our code and reduce the amount of memory that it occupies by combining repeated sections into a single function. The only difference in the main loop body is that we need a parameter so we can pass the various different colors we want to see of the RGB LED.
Take a look at the video for details. (sorry for the length)
Step 6: GPIO Input - Add Switches
Let's make it more interesting. It's time to add some switch-control to our assembly program.
This Instructable has images showing how the two on-board switches are connected to the MSP432.
Essentially: Switch 1 (SW1 or S1) is connected to P1.1, and Switch 2 (SW2 or S2) is connected to P1.4.
This makes things a bit interesting not only because we're dealing with inputs instead of outputs, but also because these two switches occupy or take up two bits of the same register address block as does the single red LED that is an output.
We dealt with toggling the single red LED in this Instructable, so we just need to add code to handle the switches.
Port 1 Register Address Block
Remember that we covered these in the previous Instructable, but we have to include a new one:
- Port 1 Input Register address = 0x40004C00
- Port 1 Output Register address = 0x40004C02
- Port 1 Direction Register address = 0x40004C04
- Port 1 Resistor Enable Register address = 0x40004C06
- Port 1 Select 0 Register address = 0x40004C0A
- Port 1 Select 1 Register address = 0x40004C0C
When using the ports as inputs, is good to use the MSP432's internal pull-up or pull-down resistors.
Since the Launchpad development board has wired the two switches to ground (LOW when pressed), that means we should use pull UP resistors to make sure we have a solid HIGH when they're not pressed.
Pull Up / Pull Down Resistors
It takes two different Port 1 Register addresses to tie those switch inputs to pull-up resistors.
1) Use the Port 1 Resistor-Enable register (0x40004C06) to just indicate that you want resistors (for those two bits),
2) and then use the Port 1 Output register (0x40004C02) to set the resistors as either pull-up or pull-down.
It might seem confusing that we're using an Output register on inputs. The Output register has almost like a dual-purpose.
So, to re-state another way, the Output register can either send out a HIGH or LOW to an output (such as the single red LED), and / or it is used to set pull-up or pull-down resistors for inputs , BUT ONLY if that feature has been enabled via the Resistor-Enable register.
Important in the above - when sending/setting a LOW or HIGH to any output bit, you'll need to maintain the pull-up/pull-down state of the input bits simultaneously.
(the video tries to explain)
Reading A Port Input Bit
- Set the SEL0 / SEL1 for GPIO functionality
- Set the DIR register as input for the switch bits, but as output for the LED (simultaneously in same byte)
- Enable resistors
- Set them as pull-up resistors
- Read the port
- You may wish to filter the value read to isolate just the bits you need (switch 1 and 2)