• Home   /  
  • Archive by category "1"

Ee2024 Assignment Of Lease

EE2024 assignment 2 was a big headache for me. I had no idea what’s going until nearing the end. The first lab simply asked us to play around with the demo. For the rest of them, we were pretty much left on our own. In fact, I felt that most of the labs can be done at home, if not for the lab attendance marks and the scarce lab briefings.

In the end, I really bucked up, worked on understanding the lecture material and hacked at the assignment really hard. Although not amazing, my team’s board did not crash during the assessment. In this guide, I am going to use my old assignment as an example to help anyone with similar trouble with their EE2024 assignment 2.

If all you are looking for is some sample code, you can get my source code here from GitHub. Otherwise, if you need more help, let me walk you through my code. Note that the details and requirements of my assignment are listed on the GitHub page. My project (shown below) will no doubt be different from yours, but the fundamentals should still be the same. I recommend that you open two screens or browser tabs to read the code and this article.

Contents

1) Using the libraries (CMSIS, MCU & EaBaseBoard)


a. Why use libraries?

Understanding the 3 libraries above will greatly aid in your EE2024 assignment 2. Remember the C programs you wrote in CS1010? Those that were used to solve weird arbitrary problems? Apparently, that can be extended to interact with hardware within a microprocessor. C is an ideal choice of programming language for this because it sits at a right level between hardware and software. I think this means it is 1) fast enough and 2) can handle memory efficiently, both which are very critical for embedded applications. You can find out more yourself.

However, although we want to use C to interact with the hardware within the LPC1769, we are not going to rewrite all the basic code to interact with the hardware. This takes too much time. Experts in the industry have already done this and compiled them into several libraries like the Lib_CMSIS, Lib_MCU, and Lib_EaBaseBoard.

Instead of rebuilding the wheel, we will stand on the shoulders of these experts and extend/modify/mix their library functions to suit our own project need. Having said that, we still need to understand each C library to use it well. This is taught in Chapter 5.

b. Benefits of using the libraries:

  • You do not need to deal with hard-coded addresses of the GPIO peripheral register by using Lib_MCU. What this means is that you no longer have to search for the hex address of a particular register and define it as a constant like “#define FIO0DIR 0X2009C000” before you can use the register. Instead, just include “lpc17xx_pinsel.h” and “lpc17xx_gpio.h” and call the register by its given name. This makes embedded programming easier and more changes-friendly.
  • Also, with the libraries, you can get precise timing by using System Clock instead of having to use a for-loop to iterate a few thousands times to delay say 2 seconds.

c. Structure of the 3 Libraries given to us

This overview of the files structure might help you to understand the libraries better. I bolded the 5 important files in Lib_CMSISv1p30_LPC17xx that are explained in the lecture notes. For the other two libraries, I listed some examples of files that you will most likely use.

i) Lib_CMSISv1p30_LPC17xx (note the 5 files in bold)

include/

core_cm3.h
LPC1769.h
system_LPC1769.h

src/

core_cm3.c
system_LPC1769.c(suppose to have access functions for peripherals here in CMSIS, but these are put in Lib_MCU for LPC1769)

C projects/

cr_startup_device (I can’t find this file in my workspace, it’s probably within the compiler)

ii) Lib_MCU

src/

lpc17xx_gpio.c
lpc17xx_pinsel.c
lpc17xx_uart.c

iii) Lib_EaBaseboard

inc/

led7seg.h
light.h
temp.h
rotary.h


In this section, I will explain the purpose of the important files within the library.

i) Lib_CMSISv1p30_LPC17xx

CMSIS stands for Cortex Microcontroller Software Interface Standard.

  • Basically, just remember that it contains register names of core peripherals and names of core exception vectors.
  • It is used commonly to access peripheral registers and define exception vector
  • There are 2 CMSIS layers:
    • ARM provides the core peripheral access layer containing names definitions, address definitions and helper functions to access core registers and peripherals. These refer to core_cm3.h, core_cm3.c and likely LPC1769.h too.
    • NXP provides the device peripheral access layer, definitions for all device peripherals and optional helper functions for peripherals. Contains data structures and address mappings of device peripherals like UARTs and identify peripheral registers for that device. For LPC1769, this refers to peripheral helper functions in Lib_MCU.
1) LPC1769.h (Names and settings for core)
  • Provides interrupts number definitions for all core and device specific exceptions (15 in Cortex-M3) and interrupts.
    • You can see a bunch of interrupts names declared as an enum in here.
  • Provides configuration for core_cm3.h which is the actual configuration of the Cortex M3 processor. core_cm3.h is included here to implement access to processor registers and core peripherals.
    • For example, there is code to define _NVIO_PRIO_BITS 5 . Something you should learn under the Interrupts lecture.
2) core_cm3.h (Core registers access and data structures)
  • Provides core registers access like special registers.
  • Describes the data structures for the the Cortex-M3 processor and address mapping of these structures.
  • Helper functions such as those that enable, disable, and configure interrupts Eg. NVIC_EnableIRQ( … ).
3) core_cm3.c (Helper functions for core peripherals)
  • Defines helper functions that access and configure system core peripherals like System Control Block (SCB), SysTick, NVIC etc
4) system_lpc1769.c (System clock)
  • Configures System Clock frequency. If your CPU clocks at 100MHz,  it can execute 100 x 10^6 cycles in one second. If you want to clock 1 micro second, you need to tell your CPU to count from 0 to 100 x 10^3.
5) cr_startup_lpc1769 (Vector table)
  • Contains interrupt vectors for all device specific interrupts. Each interrupt handler is defined as a weak function so that they can be implemented by programmers. NMI_handler, HardFault_Handler

ii) Lib_MCU

Just remember that this library provides access functions for peripherals and optional helper functions for programming peripherals .

  • While these functions are usually in Lib_CMSIS, they pull the functions out and place them in another library called Lib_MCU for LPC1769.
  • They are part of the Device Peripheral Access layer of CMSIS.
  • Some examples of these peripherals are like GPIO, I2C and UART etc. Use it like this LPC_UART2→RBR or LPC_GPIOINT->IO2IntStatF etc
  • Since LPC_XXXn is basically a pointer to a register structure, so *LPC_XXXn is a register structure (likely the hex address of the register).
  • This layer is expanded by the silicon partners i.e NXP.

iii) Lib_EaBaseBoard

Lib_EaBaseBoard is a library that contains functions to access peripherals and devices on the baseboard. Several cool sensors and actuators are found on your baseboard including the temperature sensor, accelerometer, light sensor, speaker, 7 Segment Display, OLED screen and many more.


2) Writing the program

a) Declaring global variables

Global variables are widely used in embedded system programming. The following are some (not all) examples of my global variables.

We used a lot of global variables to store the constants for the sensors such as the light sensor interrupts,

const uint32_t lightLoLimit = 0; const uint32_t lightHiLimit = 2000; static const int FLARE_INTENSITY = 2000;

to keep track of the time when different events occur,

volatile uint32_t msTicks; // counter for 1ms SysTicks volatile uint32_t oneSecondTicks, unitTicks, threeSecondsTicks, readingResSensorsTicks, sw4PressedTicks;

to store variables for the results.

//Strings for OLED int8_t OLED_EXT_MODE[15]; int8_t OLED_X[15]; int8_t OLED_Y[15]; int8_t OLED_Z[15]; int8_t OLED_LIGHT[15]; int8_t OLED_TEMPERATURE[15];

Next, since we need three modes, we create an enumeration called system_mode_t for the three different modes. An enumeration is a data type with a set of named values called elements. You can assign this set of named values to any variable you create of the enumerated type. In our case, we created a variable called mode with the type system_mode_t.

typedef enum { MODE_BASIC, MODE_RESTRICTED, MODE_EXTENDED } system_mode_t; volatile system_mode_t mode;

The benefit of using an enumeration rather than assigning integer constants for each mode (int MODE_BASIC = 1) then using a switch statement is that an enumeration makes the code more self-documenting because it removes the magic number (constants appearing from nowhere). Also, it prevents any programmers from accidentally doing illogical operations on the values of enumerators.


3) Starting with the main function

In C, we usually start reading the code from the main function.

a. Initialization

In the main function, we started off with a bunch of initializations. On hindsight, these can be refactored into a method called setup( ) to mimic the Arduino style.

init_i2c(); init_ssp(); init_GPIO(); init_uart(); pca9532_init(); joystick_init(); acc_init(); oled_init(); led7seg_init(); temp_init(getTicks); light_enable(); rgb_init();

Most of the knowledge that you need to know to write the initialization code (other than UART) comes from Chapter 6 Pin Connect Block, Chapter 7 GPIO, Chapter 8 I2C and the additional baseboard manual. Learn to read the manuals. Also, be mindful of the jumpers configuration on the baseboard. Sometimes, a jumper needs to be added or removed in order for you to use a certain peripheral on the baseboard. These will be stated in the baseboard manual. Just think of the jumper as a route for electricity to pass through. Also, be careful because some jumpers have similar names.

Here’s an example of the initialization of I2C:

static void init_i2c(void) { PINSEL_CFG_Type PinCfg; /* Initialize I2C2 pin connect */ PinCfg.Funcnum = 2; PinCfg.Pinnum = 10; PinCfg.Portnum = 0; PINSEL_ConfigPin(&PinCfg); PinCfg.Pinnum = 11; PINSEL_ConfigPin(&PinCfg); // Initialize I2C peripheral I2C_Init(LPC_I2C2, 100000); /* Enable I2C1 operation */ I2C_Cmd(LPC_I2C2, ENABLE); }

b. Enable interrupts & handler

Next, we enable the interrupts starting from the light sensor. Two things to take note for light sensor: if the range is set as 4000, the sensor will return a value from 0-4000. If set to 1000, it will return 0-972. Since for our assignment, the trigger value is 2000, so I set the range as 4000. Note that the light sensor seems to use I2C so remember to add the initialization code above to set up I2C and choose function 2 (look at the schematics in the Baseboard Briefing manual).

// Setup light limit for triggering interrupt light_setRange(LIGHT_RANGE_4000); light_setLoThreshold(lightLoLimit); light_setHiThreshold(lightHiLimit); light_setIrqInCycles(LIGHT_CYCLE_1); light_clearIrqStatus(); LPC_GPIOINT->IO2IntClr = 1 << 5; LPC_GPIOINT->IO2IntEnF |= 1 << 5; //light sensor

The next step is to enable interrupt for the SW3 switch. If you look at the schematics of SW3 in the manual, you will realize that SW3 can connect to 2 pins depending on the jumper configuration. If you use PIO2_9 on the baseboard, you can choose the function to be 1 to select the function to be external interrupt 0.

However, I used SW3 as a GPIO interrupt not an external interrupt for EINT0. So I initialize port 2 pin 10 with function 0 to select the GPIO function. From the GPIO lecture notes, we know that “when GPIO interrupt occurs, EINT3 will be triggered and its interrupt handler will execute.” Therefore, I clear the GPIO interrupt for port 2 pin 10 first, configure it to trigger at a falling edge before enabling it.

LPC_GPIOINT->IO2IntClr = 1 << 10; //SW3 LPC_GPIOINT->IO2IntEnF |= 1 << 10; //switch NVIC_EnableIRQ(EINT3_IRQn);

In the EINT3 handler, you need to identify exactly which GPIO interrupt triggered your EINT3 handler. This is because multiple EINT3 interrupts can come from port 0 or port 2. Anyway, you identify the specific GPIO interrupt by checking the GPIO interrupt status register as shown below.

//EINT3 Interrupt Handler, GPIO0 and GPIO2 interrupts share the same position in NVIC with EINT3 - pg 24 GPIO notes void EINT3_IRQHandler(void) { //light sensor if ((LPC_GPIOINT->IO2IntStatF >> 5) & 0x1) { countSafe = 0; if (mode == MODE_BASIC) { isSwitchingToRestricted = 1; } unitTicks = msTicks; mode = MODE_RESTRICTED; LPC_GPIOINT->IO2IntClr = (1 << 5); } if ((LPC_GPIOINT->IO2IntStatF >> 10) & 0x1) { isTriggerPressed = 1; LPC_GPIOINT->IO2IntClr = (1 << 10); } }

Tip: Keep your Interrupt Service Routines (ISRs) as short as possible because your main code cannot run while the control is in an interrupt handler. Usually, we only change a few global variables in the ISR.


c. Writing the main loop

Embedded applications never quit! Although infinite while loops were scary in CS1010, here we are going to make use of them so that the LPC1769 repeatedly execute our instructions over and over again (until it is powered off).

Since the 7 segment counter must be incremented no matter what every second, we put it in the main while loop, outside of any switch statement. We use a variable counter7Seg and the % operator to ensure that the character displayed will always be from 0-9. We also modified the led7seg_setChar() function in led7seg.c to display the characters in inverted mode if desired. I will explain this later.

Again, regardless of the mode, we have to take readings whenever SW3 is pressed. Therefore, we conditioned our readSensor() code on whether SW3 is pressed. If it is pressed, the second if-statements trigger. Here, we toggle isTriggerPressed back to false, read the sensors and output the results on both OLED and the computer screen.

The third if statement is for entering extension mode when SW4 is pressed. This is because it is recommended that your extension features should not appear in the BASIC and RESTRICTED mode until it is called. Here, we basically just toggle the mode to MODE_EXTENDED if entering, or MODE_BASIC if exiting. We also toggle the invert7seg global variable to make the 7 Segment Display upside down in extension mode.

while (1) { //increment 7 segment if ((msTicks - oneSecondTicks) >= TICK_RATE_ONE_SEC) { oneSecondTicks = msTicks; counter7SegDisplay = '0' + counter7Seg % 10; led7seg_setChar(counter7SegDisplay, invert7Seg); counter7Seg = (counter7Seg + 1) % 10; } if (isTriggerPressed) { isTriggerPressed = 0; readSensors(&temperature, &light, &x, &y, &z); x = x + xoff; y = y + yoff; z = z + zoff; sprintf(result, "L%d_T%.1f_AX%d_AY%d_AZ%d\r", light, temperature / 10.0, x, y, z); OLED_printStr(result); UART_Send(LPC_UART3, (uint8_t *) result, strlen(result), BLOCKING); } btnSW4 = (GPIO_ReadValue(1) >> 31) & 0x01; if ((btnSW4 == 0) && (msTicks - sw4PressedTicks >= 500)) { sw4PressedTicks = msTicks; if (mode == MODE_BASIC || mode == MODE_RESTRICTED) { mode = MODE_EXTENDED; sprintf(result, "Entering Extended Mode"); } else { mode = MODE_BASIC; sprintf(result, "Leaving Extended Mode"); } invert7Seg = !invert7Seg; OLED_printStr(result); }

A question asked in my final assessment was whether SW4 can be used in interrupt mode instead of polling. I said no but you can find out whether that’s really correct. Also if you look at this schematics of SW4, you will realize that it is 3.3V when not pressed. When pressed, it will be grounded. This is why the third if-statement shown above uses the condition (btnSW4 == 0) and not 1 to indicate that the SW4 button is pressed.

d. Basic and Restricted Mode

The following code snippets show what happen in each mode. The hardest part will be incrementing the 16 LEDs in the restricted mode.

switch (mode) { case MODE_BASIC: setRGB(RGB_BLUE); //set blue for BASIC pca9532_setLeds(0xffff, 0x00); if (((msTicks - threeSecondsTicks) >= 3000) || (firstRun)) { threeSecondsTicks = msTicks; firstRun = 0; readSensors(&temperature, &light, &x, &y, &z); x = x + xoff; y = y + yoff; z = z + zoff; sprintf(result, "L%d_T%.1f_AX%d_AY%d_AZ%d\r", light, temperature / 10.0, x, y, z); OLED_printStr(result); UART_Send(LPC_UART3, (uint8_t *) result, strlen(result), BLOCKING); } break; case MODE_RESTRICTED: light_clearIrqStatus(); setRGB(RGB_RED); if (isSwitchingToRestricted) { msg ="Solar Flare Detected. Scheduled Telemetry is Temporarily Suspended.\r"; UART_Send(LPC_UART3, (uint8_t *) msg, strlen(msg), BLOCKING); isSwitchingToRestricted = 0; pca9532_setLeds(0x0000, 0xFFFF); } if ((msTicks - readingResSensorsTicks) >= 3000) { readingResSensorsTicks = msTicks; sprintf(result, "L%c_T%c_AX%c_AY%c_AZ%c\r", 'R', 'R', 'R', 'R', 'R'); OLED_printStr(result); } if ((numToIncrement) = ((msTicks - unitTicks) / 250)) { if (light_read() < FLARE_INTENSITY) { incrementLED(numToIncrement); } unitTicks = msTicks; } else if (countSafe == 0) { pca9532_setLeds(0x0000, 0xFFFF); } if (countSafe >= 65535) { mode = MODE_BASIC; msg ="Space Weather is Back to Normal. Scheduled Telemetry Will Now Resume.\r"; UART_Send(LPC_UART3, (uint8_t *) msg, strlen(msg), BLOCKING); countSafe = 0; } break;

Nothing much to say about these sections. We mainly use polling for each mode to carry out the required actions. We also use some interrupts to set some global variables when they occur (eg >2000lux). Theoretically, if we use more interrupts instead of polling, such as adding the timer inside Systick_handler, the application will be more accurate. However, you will soon realize how difficult it is to get interrupts to work correctly for this assignment. A lot of my friends who did this were still debugging their board the day before the assessment. You can give it a try.


4) UART

The last part of the requirements is to send a message via UART to a terminal program.

void init_uart(void) { UART_CFG_Type uartCfg; uartCfg.Baud_rate = 115200; uartCfg.Databits = UART_DATABIT_8; uartCfg.Parity = UART_PARITY_NONE; uartCfg.Stopbits = UART_STOPBIT_1; //pin select for uart3 pinsel_uart3(); //supply power and setup working parts for uart3 UART_Init(LPC_UART3, &uartCfg); //enable transmit for uart3 UART_TxCmd(LPC_UART3, ENABLE); }

The main knowledge you need to set up UART right comes from the last chapter about UART. You simply need to configure the baud rate to match that of your terminal program. We send via 8 bits, without parity but with 1 stop bit. The UART pin can be used for other purposes, so tell the pin select block that you want it for UART.

void pinsel_uart3(void) { PINSEL_CFG_Type PinCfg; PinCfg.Funcnum = 2; PinCfg.Pinnum = 0; PinCfg.Portnum = 0; PINSEL_ConfigPin(&PinCfg); PinCfg.Pinnum = 1; PINSEL_ConfigPin(&PinCfg); }

Note: Near the end of the assignment, you will be required to set the address of your XBee Modules. Remember to do this. Its purpose is so that your terminal will only receive the messages sent (via UART) from your baseboard and not other baseboards in the lab. If you never do this, you will see random messages appearing on your terminal (like some people did).

Some people chose to implement UART interrupts as an extension feature. But I didn’t so I cant help you much here. There isn’t any code samples for these in the UART notes so if you want to do this, I suggest you look for some code samples from GitHub. UART interrupts also appeared in tutorials and past year exams.

Anyway, I know that there are three types of UART interrupt namely 1) Receive Line Status (RLS) Interrupt, 2) Receive Data Available (RDA) Interrupt and lastly 3) Character Time-Out Indicator (CTI) Interrupt. All of them will trigger the same UART interrupt. Therefore, you need to identify exactly which type of interrupt triggered your UART interrupt by using the Interrupt Identification Register (IIR). Do something like this in a handler:

if (LPC_UART2->IIR & 0xE == 0x06){ //RLS is triggered ...do something } if (LPC_UART2->IIR & 0xE == 0x04){ //RDA is triggered ...do something } if (LPC_UART2->IIR & 0xE == 0x0C){ //CTI is triggered ...do something }

I know to use 0x06, 0x04 and 0x0C because it is listed under the IIR slide in the UART lecture notes. 0xE is used because it is 0000 0000 …. 1110 so &-ing with it will filter out the other non-relevant bits, leaving you with the interrupt identity. I just made this up so you better check with someone who have actually done this. Remember you have to fully understand UART interrupts should you choose to implement it. The GA will probably ask you to explain it during the assessment.

Reward for reading this guide

As a reward to you for reading this loonnnnggg guide, I will explain to you how to invert the 7 segment. Good for you if you already know. Although this sounds like a really simple task, not everyone knows how to do it and it took me a while to figure it out so shhhhhh don’t share this with too many people!

There might be other ways to do this but what I did is I modify led7seg.c. If you look at led7seg.c, you will notice that they declare an array of characters at the start. Since we are only displaying digits 0 – 9, just look at this portion:

If you convert the hex digits to their binary form you will get:

0x24 → 0010 0100
0xAF → 1010 1111
0xE0 → 1110 0000
0xA2 → 1010 0010
…..
0x22 → 0010 0010

I realize that each binary pattern above corresponds to a certain digit from 0 to 9 on the 7 segment display. Furthermore, when I look at the layout for a 7 segment display shown below, I realized that each bit in the 8-bit bit pattern will control a certain stroke of a 7 segment display. Somehow, 0010 0100 will show a 0 on the 7 segment, while 0010 0010 will show a 9.

It took me a while to remember that the 7 segment display is an active low device; a 0 in the bit pattern will make a certain stroke on the 7 segment light up, while a 1 will turn it off. However, the order is not ABCD EFGDp. After comparing the bit patterns and their corresponding digits, I finally figured out that the order is FBDpC AGED. To make your own digit appear on the 7 segment, you simply need to put a 0 in the correct position. So I computed the binary values that show each of the inverted digits and converted them back to their hex form. I declared them under a new array called invertedChars[ ]:

Finally, after declaring the inverted characters, you still need to modify setChar( ) to use it. Since we were not using rawMode for the assignment, I changed the second parameter name from rawMode to invertedMode and use it as a boolean value to use my inverted characters. My partner was pretty impressed.

That’s it! Now, when you want to display inverted digits in your 7 segment display, just call setChar( your_char, TRUE) and it will use your inverted characters.

Final Words

This post is just a guide to help students like me who face lots of trouble with the EE2024 assignment 2. Since I am just a student and not an expert in embedded programming, there will probably be some mistakes here or there in the code or writing. If you spot any mistakes, just comment below to alert the rest and me about it. I sincerely hope that your assignment topic won’t be too different and this post will at least be of some use to you. Good luck!

Share the knowledge

Posted in University and tagged programming.

- У Танкадо сказано: главная разница между элементами. - Господи Иисусе! - вскричал Джабба.  - Откуда нам знать, что для Танкадо было главной разницей.

One thought on “Ee2024 Assignment Of Lease

Leave a comment

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *