Lab2b - iLED Extending To C PDF
Lab2b - iLED Extending To C PDF
EE 2361 - Introduction to Microcontrollers
Laboratory # 2b
Page 1
EE 2361 - Lab # 2b ECE Department
Background
In programming, it can be easy to get stuck in doing what is comfortable. For example, the
previous lab concentrated on writing code to support iLED’s in assembly language. This was
justified because we needed a precise 6 cycle pulse to implement the iLED serial
communication protocol. It would be tempting to continue to develop further code in assembly to
utilize these devices. However, assembly language requires significantly more work to develop
complex code. Even moderately complex code can be hard to develop and very hard to read /
test. For example, converting a “long int” representing a 24-bit RGB value or, even, computing a
color “rainbow” of 1000’s of 24-bit RGB values and streaming each in succession out the serial
protocol will take up many many lines of assembly code.
Purpose
This lab is an extension on Lab #2a, it will explore how to integrate ASM and C in a single
project. You will package up assembly instructions into a C-compatible ASM-based library. Then
use that library to reimplement a function from the Adafruit NeoPixel Library on PIC24. In the
end you will have all the pieces needed to create sparkly light shows quickly and easily!
Supplemental Resources
PIC24FJ64GA004 - Family Datasheet
16-bit MCU and DSC Programmer's Reference Manual
MPLAB® XC16 User Guide - Section 8.3 on Data Types
C-Programing Tutorial - Header Files
C-Programing Tutorial - Data Types
C-Programming Tutorial - Type Casting
GNU C Lib “stdint.h”
Required Components:
Standard PIC24 requirements (caps, pullup, debug header, etc.)
iLED
100 Ohm Resistor
Page 2
EE 2361 - Lab # 2b ECE Department
Pre-Lab
In the pre-lab you will build a mixed C/ASM project. The main() function will be in C and we will
provide some of the functions from Lab4a for use in the C function global namespace. Using
these ASM functions, you will replicate the functionality of your original ASM-only code.
Page 3
EE 2361 - Lab # 2b ECE Department
WARNING: If you name your c-file and assembly-file with the same beginning file name you will
cause cryptic errors in compilation! Make sure file names differ by more than just the extension
(ie., orser_lab4b_asmLib_v001.s orser_lab4b_main_v001.c)
2.) You will need a different boilerplate for an ASM library. Use the code included below.
Specifically you won’t need the following:
● Configuration registers
● Global declaration of the _main function/label
● The .bss section: space for stack is allocated by the C compiler before calling
main(). Also, we are not going to define any variables in assembly: we can do all
our global variable definitions in C. It’s safer and easier to let the C compiler
handle data organization in data memory.
.include "xc.inc"
; This is a library, thus it can *not* contain a _main function: the C file will
; deine main(). However, we
; we will need a .global statement to make available ASM functions to C code.
; All functions utilized outside of this file will need to have a leading
; underscore (_) and be included in a comment delimited list below.
.global _example_public_function, _second_public_function
3.) Add to this file a few of your pre-made functions from Lab 2a. Rename the labels to start
with an underscore (_). This is needed for Global Function. You will need at least the
following functions:
● _write_0()
● _write_1()
● _wait_100us()
● _wait_1ms()
Page 4
EE 2361 - Lab # 2b ECE Department
4.) Add each of the above functions to the .global statement. It should look something like
this when you are done (note: “djo” are my initials: David J. Orser. You should use your
own initials):
_write_0:
; 2 cycles for a function call
bset LATA, #0
5.) Clean and Build Main Project to check your syntax, etc.
6.) Be polite: if you use any registers in your functions (e.g., if you use w8 in _wait_50us as
a loop counter), make sure that you save the value of the register at the beginning of the
function, and retrieve the value right before you exit. You can consider 1 cycle for the
push and 1 cycle for the pop instruction1. The code would look something like this:
_wait_50us:
push w8
mov #0x107, w8
...
pop w8
return
Call etiquette
1
The reason you save and retrieve the values of registers is that the caller (in our case main())
might be using the register as its own loop counter. We don’t want a seemingly harmless call to
a function wreck the value of that counter.
Page 5
EE 2361 - Lab # 2b ECE Department
Note: Declare and Define are two different things. You declare a function like so:
int foo(int);
Declare foo(), note this statement only specifies input and output arguments
Define foo()
We will now create a header file for our ASM library, then include it in our C program.
1.) Right Click on your Lab4b project in the Projects pane
2.) Click New → Other
3.) Select C → Header File
4.) Name it exactly the same thing as the .s source file, for example
“orser_lab4b_asmLib_v001.h”
5.) Click “Finish”
6.) Add the header file to your project under “Header Files” if it is not added already.
7.) Open up for edit your header file and add declarations for each of your assembly
functions. Remember these assembly functions do not take any arguments. In the future,
if you should need arguments in assembly functions, please see the XC16 Compiler
User's Guide (Section 13.8 - Function Call Conventions).
Page 6
EE 2361 - Lab # 2b ECE Department
#ifdef __cplusplus
extern "C" {
#endif
void djo_wait_100us(void);
void djo_wait_1ms(void);
void write_0(void);
void write_1(void);
#ifdef __cplusplus
}
#endif
8.) Now include your header file in your main C file. It should look something like this:
#include "xc.h"
#include "orser_lab4b_asmLib_v001.h"
9.) Clean and Build Main Project, now to verify syntax, etc.
Page 7
EE 2361 - Lab # 2b ECE Department
Mentally partition Lab2a into setup() and the infinite loop sections.
1.) Translate the code from Lab 2a before “foreverLoop:” into setup()
Hint: You can also go back to your Lab 1 project to help remember what to do here.
For example:
mov #0xffff,w0 AD1PCFG = 0x9fff;
mov w0,AD1PCFG
2.) Translate the “foreverLoop:” section of your Lab2a assembly file into the endless loop
inside main while(1) {...}. Don’t forget to call setup() in main.
For Example:
call _write_0 write_0();
Example of 24-bit pulse train showing the color #004000, utilizing C with ASM functions
7.) Switch to your programmer and program your PIC24. Hopefully your iLED changes
color!
Page 8
EE 2361 - Lab # 2b ECE Department
Pre-Lab Deliverables
● C Program that contains boilerplate, setup(), endless loop in main(), and when
run correctly sets the color of your iLED
● The following ASM function are accessible in your C program:
○ void wait_100us(void);
○ void wait_1ms(void);
○ void write_0(void);
○ void write_1(void);
Pre-Lab Checklist
❏ Take pre-lab quiz
❏ Complete the walk-through to create a mixed C and ASM project in MPLAB X
❏ Bring to lab a single assembly project that fulfills the Pre-Lab Deliverables
Page 9
EE 2361 - Lab # 2b ECE Department
Lab Procedure
During this lab you will create support C code to make your iLED shine.
Finally, don’t forget to hold the output low for >50us to latch the data into the iLED
(wait_100us();)
1.) Write a function that does the above with the following declaration:
void writeColor(int r, int g, int b);
2.) Run a simulation of your “hardcoded” color loop() function, look at the resulting output on
the Logic Analyzer.
QUESTION: How many cycles does your hard coded program take to write 24-bits?
3.) Replace the hard-coded calls to write_0(), write_1(), and wait_100us() with
your new writeColor() function.
4.) Simulate your new function.
QUESTION: How many cycles does your new function take to write 24-bits?
Page 10
EE 2361 - Lab # 2b ECE Department
Example of a gradient
1.) Write a program that uses the pattern above to display Red -> Purple -> Blue -> Purple
-> Red a nd repeats.
Keep in mind, after each color change you will need to delay for the user to observe the
color. Use your delay of 1ms for this purpose.
2.) When your code compiles successfully, load it on your PIC24 and test it out.
Due to the difference between human and machine perception rates, it is useful to control the
rate of an animation with a compiler constant.
Page 11
EE 2361 - Lab # 2b ECE Department
NOTE: This section is less guided than your previous lab procedures. Expect to get stuck
occasionally. When you get stuck, Google it, consult with your neighbors, talk to your TA, ask
your professor questions. If you’re working outside the lab, ask questions on the forum.
The Adafruit NeoPixel Library has a neat function buried in their example code. The Wheel()
function cycles through the full RGB color space, with a 1-dimensional number input. This is
useful for testing all three physical LEDs inside the iLED. It also looks kinda cool.
Packed Variables
The first thing to note is that wheel() doesn’t directly call write_color(). Instead
it returns an RGB pixel color. It does this because there are other things (like
brightness control, color correction, anti-aliasing) that may post process the
pixel color generated by wheel(). In addition, when processing animations of
many pixels it quickly becomes burdensome to pass the red, green, and blue
values individually between functions (like an 8x8 RGB matrix or 30 pixel
waterproof RGB strip.) The NeoPixel library compresses three variables into
one “packed color” of the type 32-bit integer. It is formatted as 0x00RRGGBB
(shown on the right). Eventually in your projects, you may need to compress
the data further using arrays of packed colors.
Page 12
EE 2361 - Lab # 2b ECE Department
Packing Examples
Packing and unpacking variables is actually very easy and can be accomplished in several
ways, here are some hints:
unsigned char Red = 0x40;
unsigned char Green = 0x20;
unsigned char Blue = 0x60;
unsigned long int RGBval = 0;
RGBval = ((long) Red << 16) | ((long) Grn << 8) | ((long) Blu);
Packing of an RGB value into a single 32-bit unsigned long int (shift and OR method)
Both of these examples use a technique called “type casting”, if you’re unfamiliar with this
notation, please read up on it at TutorialsPoint.
Page 13
EE 2361 - Lab # 2b ECE Department
Lab Report
1. Answer the two questions on Page 8. (How many cycles does your hard coded program
take to write 24-bits? How many cycles does your new function take to write 24-bits?)
2. Document your code. Make sure it is legible (i.e the indentation style is consistent).
Page 14
EE 2361 - Lab # 2b ECE Department
The following are the number of cycles between bits and the maximum bit updates rate for
iLED’s written using three different methods during the development of this lab:
● Hard Coded ASM - Constantly displays the same color
○ 26 cyc = 1625ns = 615 kHz max update rate
● Dynamic ASM - Can display any given color (shift and mask done in ASM)
○ 31 cyc = 2000ns = 500 kHz max update rate
● Dynamic C - Can display any given color (shift and mask done in C, write_0 and write_1
in ASM)
○ 40 cyc = 2500ns = 400 kHz max update rate
As one can see, with 24-bits of data and around 50 pixels we can achieve a visual update rate
of 10kHz (most TV’s update at ~60 Hz), thus there is no need for further hand tailored ASM
code.
It is possible to foresee a use case where there are thousands of iLEDs in a single chain. In that
case a 500kHz update rate would allow you to string together 20% more LEDs on a single
output while maintaining a reasonable refresh rate. If this is your use case it may be worthwhile
to look at handwriting a Dynamic ASM iLED driver.
Page 15