# Problem I have provided an `.ioc` file that you can use to create a new project. This project includes a file called `math.s` that has an assembly language power function implemented. When you run the program, switches 1 to 4 are used to set the value of the base, and switches 5 to 8 are used to set the value of the exponent. The base value is displayed in decimal on the seven segment characters 7 and 8. The exponent value is displayed on characters 5 and 6. The answer is displayed on characters 1 to 4. Make sure it builds and runs and use the switches to set inputs, and watch the answer update as you change the values. ## 1 Add a new function to the `math.s` file called `res_div(int r1, int r2)` that performs a resistor divider calculation for two parallel resistors in assembly language. The formula for this is `answer = (r1 * r2) / (r1 + r2)`. Note that since the result will be an integer, you can increase the accuracy by returning the answer times 100 so that the tenths and hundredths places are shown in the result: `answer = 100 * (r1 * r2) / (r1 + r2)`. Note that you should always do multiply before you divide to preserve precision in the answer. If you do this, you can turn on the decimal point on display character 3 to indicate where the decimal point is in the answer. The function `Seven_Segment_Digit(unsigned char digit, unsigned char hex_char, unsigned char dot)` has three passed parameters. If you omit the third parameter in the call, it defaults to 0. If you pass a value of 1 for the third parameter, the decimal point will be displayed on that particular seven segment digit. This is an example of how you can implement fixed point math using integers. ## 2 Modify the `main.c` file to allow for inputting the values for `r1` and `r2` (instead of base and exponent), and display the answer after calling the function. You will need to modify the `main.c` and `math.s` files to accomplish this. Use the power function code as an example to follow to add this new function. # Process An `.ioc` file is used by the STM32CubeMX IDE to store project configuration settings. We can create a project from the provided `.ioc` file by going to *File > New > STM32 Project from an Existing STM32CubeMX Configuration File (.ioc)*. This is the `main.s` file provided with the assignment: ```armasm /** ****************************************************************************** * @file math.s * @author Dean Wilkinson * @brief Assembly callable functions. * This module performs: * - Power function * * ****************************************************************************** */ .syntax unified .cpu cortex-m4 .fpu softvfp .thumb .global power .p2align 2 .type power,%function power: // ro=base/result, r1=exponent, r2=calculation .fnstart base .req r0 // .req is the equivalent to RN in Eclipse compiler exponent .req r1 scratchpad .req r2 MOV scratchpad, base // MOV r2, r0 // load r0 with base value to initialize for multiply CMP base, #0 // Is the base zero? BEQ done // Yes, so go to done CMP exponent, #0 // Is the exponent zero? MOV scratchpad, #1 // Yes, set the answer to 1 BEQ done // Yes, so go to done MOV scratchpad, base // MOV r2, r0 // load r0 with base value to initialize for multiply loop: SUBS exponent, exponent, 1 // SUBS r1, r1, 1 // #1 and 1 are equivalent expressions of a decimal constant BEQ done MUL scratchpad, scratchpad, base // MUL r2, r2, r0 B loop done: MOV base, scratchpad // MOV r0, r2 BX lr .fnend ``` We can use this as a model for the function we're tasked with writing. Since this is one of the first functions I've seen written in ARM assembly, I've decided to break down line by line to prepare for writing the `res_div` function: * `.syntax unified` - Sets the Instruction Set Syntax to the new `unified` syntax, which allows for mixing 16-bit Thumb and 32-bit ARM instructions in the same file. [(1)](https://sourceware.org/binutils/docs/as/ARM-Directives.html) [(2)](https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/useful-assembler-directives-and-macros-for-the-gnu-assembler) * `.cpu cortex-m4` - Sets the target processor to the ARM Cortex-M4. [(1)](https://sourceware.org/binutils/docs/as/ARM-Directives.html) * `.fpu softvfp` - Sets the floating-point unit to assemble for. `softvfp` specifies software floating-point linkage, which uses software libraries to perform floating-point arithmetic. Floating-point linkage refers to how floating-point arguments are passed to and returned from function calls. [(1)](https://developer.arm.com/documentation/dui0375/g/Compiler-Coding-Practices/Compiler-options-for-floating-point-linkage-and-computations?lang=en) [(2)](https://developer.arm.com/documentation/100748/0623/Using-Common-Compiler-Options/Selecting-floating-point-options) [(3)](https://developer.arm.com/documentation/101754/0623/armclang-Reference/armclang-Command-line-Options/-mfpu) * `.thumb` - Performs the same function as `.code 16`, which sets the instruction set to be generated to Thumb. The reason it's the same as `.code 16` is because Thumb instructions are 16-bit. 16-bit instructions allow for greater code density and efficiency. [(1)](https://sourceware.org/binutils/docs/as/ARM-Directives.html) * `.global power` - The function of the `.global` directive depends on whether or not the symbol it has been given is defined in the current compilation unit. If it hasn't been defined, it tells the assembler that the symbol must be found externally so it doesn't issue an unresolved reference error. At link time, the linker searches for the symbol's definition in external compilation units. This is equivalent to using the `.ref` directive. However, if the symbol has been defined, it tells the assembler that it should be accessible to other external compilation units. The assembler creates an entry in the symbol table for the symbol. In other words, it gives it external linkage. This is equivalent to using the `.def` directive. [(1)](https://armasm.com/docs/getting-to-hello-world/basics/) [(2)](https://stackoverflow.com/questions/1358400/what-is-external-linkage-and-internal-linkage) [(3)](https://developer.arm.com/documentation/101754/0624/armasm-Legacy-Assembler-Reference/armasm-Directives-Reference/EXPORT-or-GLOBAL-directive) [(4)](https://downloads.ti.com/docs/esd/SPNU118T/Content/SPNU118T_HTML/assembler-directives.html#IDglobaldesc) * `.p2align 2` - Pads the location counter to a particular storage boundary. With a value of 2, the location counter advances until it is a multiple of 4. The reason this is done is to ensure optimal performance, as ARM Cortex-M4 processors fetch instructions in 4-byte words. [(1)](https://sourceware.org/binutils/docs/as/P2align.html) * `.type power,%function` - Sets the type for a symbol. In this case, it sets the type for the `power` symbol to a function. It doesn't affect the compilation or execution of the program. It's useful for debugging and linking. [(1)](https://developer.arm.com/documentation/101754/0623/armclang-Reference/armclang-Integrated-Assembler/Type-directive) * `power:` - Creates a new label called `power`. [(1)](https://cpen432.github.io/resources/gnu_arm_ref.pdf) * `.fnstart` - Marks the start of a function with an unwind table entry. [(1)](https://sourceware.org/binutils/docs/as/ARM-Directives.html) * `base .req r0` - Creates an alias for register `r0` called `base`. [(1)](https://sourceware.org/binutils/docs-2.19/as/ARM-Directives.html) * `exponent .req r1` - Creates an alias for register `r1` called `exponent`. [(1)](https://sourceware.org/binutils/docs-2.19/as/ARM-Directives.html) * `scratchpad .req r2` - Creates an alias for register `r2` called `scratchpad`. [(1)](https://sourceware.org/binutils/docs-2.19/as/ARM-Directives.html) * `MOV scratchpad, base` - Copies the value from `base` (`r0`) to `scratchpad` (`r2`). * `CMP base, #0` - Sets the `Z` flag if `base` is equivalent to `0`. * `BEQ done` - Goes (branches) to the code starting at `done` if the `Z` flag has been set from the previous `CMP` instruction. [[ARM Instruction Set#^c0516a|(1)]] * `CMP exponent, #0` - Sets the `Z` flag if `exponent` (`r1`) is equivalent to `0`. * `MOV scratchpad, #1` - Set the value of `scratchpad` (`r2`) to `1` since an exponent of 0 with any base equals 0. * `BEQ done` - Goes (branches) to the code starting at `done` if the `Z` flag has been set from the previous `CMP` instruction. [[ARM Instruction Set#^c0516a|(1)]] * `MOV scratchpad, base` - Copies the value from `base` (`r0`) to `scratchpad` (`r2`). * `loop:` - Creates a new label called `loop`. [(1)](https://cpen432.github.io/resources/gnu_arm_ref.pdf) * `SUBS exponent, exponent, 1` - Subtracts `1` from the value in `exponent` (`r2`) and stores the result back in `exponent` (`r2`). * `BEQ done` - Goes (branches) to the code starting at `done` if the `Z` flag has been set from the previous `SUBS` instruction. [[ARM Instruction Set#^c0516a|(1)]] * `MUL scratchpad, scratchpad, base` - Multiplies the values in `scratchpad` (`r2`) and `base` (`r0`) and stores the result back in `exponent` (`r2`). * `B loop` - Goes (branches) to the code starting at `loop`. * `done:` - Creates a new label called `done`. [(1)](https://cpen432.github.io/resources/gnu_arm_ref.pdf) * `MOV base, scratchpad` - Copies the value from `scratchpad` (`r2`) to `base` (`r0`). * `BX lr` - Detects the instruction set type before going (branching) to the return address stored in the link register (`lr`). This is almost always used to return from a function / subroutine call because Thumb mode doesn't have a separate return instruction. [(1)](https://stackoverflow.com/questions/27084857/what-does-bx-lr-do-in-arm-assembly-language) * `.fnend` - Marks the end of a function with an unwind table entry. The unwind index table entry is created when this directive is processed. [(1)](https://sourceware.org/binutils/docs/as/ARM-Directives.html) The directives used at the top of the file apply to the entire contents of the file, so those don't need to be copied. This is the `main()` method from the `main.c` file provided with the assignment: ```c /* ... */ /* Declare the power function in main.c so it can be defined in math.s. */ extern int power(int base, int exponent); /* ... */ int main(void) { HAL_Init(); SystemClock_Config(); /* Initialize all configured peripherals. */ MX_GPIO_Init(); MX_TIM7_Init(); /* Configure GPIOs. */ GPIOD->MODER = 0x55555555; /* Use the Port D mode register to make all pins outputs. */ GPIOA->MODER |= 0x000000FF; /* Use the Port A mode register to make A0 to A3 analog pins. */ GPIOE->MODER |= 0x55555555; /* Use the Port E mode register to make all pins outputs. */ GPIOC->MODER |= 0x0; /* Use the Port C mode register to make all pins inputs.*/ GPIOE->ODR = 0xFFFF; /* Use the Port E output data register to set all pins high. */ /* Configure ADC1. */ RCC->APB2ENR |= 1<<8; /* Turn on ADC1 clock by forcing bit 8 to 1 while keeping other bits unchanged. */ ADC1->SMPR2 |= 1; /* 15 clock cycles per sample. */ ADC1->CR2 |= 1; /* Turn on ADC1 by forcing bit 0 to 1 while keeping other bits unchanged. */ TIM7->PSC = 199; /* 40Khz timer clock prescaler value, 40Khz = 16Mhz / 200. */ TIM7->ARR = 1; /* Count to 1 then generate interrupt (divide by 2), 20Khz interrupt rate to increment byte counter for 78Hz PWM. */ TIM7->DIER |= 1; /* Enable timer 7 interrupt. */ TIM7->CR1 |= 1; /* Enable timer counting. */ /* Declare the input and output variables for the resistor divider calculation. */ int answer = 0; int base = 2; int exponent = 3; /* Infinite loop. */ while (1) { /* Get the values for the exponentiation calculation from the first 8 switches. */ base = GPIOC->IDR & 0xF; exponent = (GPIOC->IDR >> 4) & 0xF; /* Display the base on the first two 7-segment digits from the left. */ Seven_Segment_Digit (6, base % 10, 1); Seven_Segment_Digit (7, (base / 10) % 10, 0); /* Display the exponent on the next two 7-segment digits from the left. */ Seven_Segment_Digit (4, exponent % 10, 1); Seven_Segment_Digit (5, (exponent / 10) % 10, 0); /* Perform the exponentiation calculation and store the result. */ answer = power(base, exponent); /* Display the result of the exponentiation calculation on the last four 7-segment digits from the left. */ Seven_Segment_Digit (0, answer % 10, 0); Seven_Segment_Digit (1, (answer / 10) % 10, 0); Seven_Segment_Digit (2, (answer / 100) % 10, 1); Seven_Segment_Digit (3, (answer / 1000) % 10, 0); } } /* ... */ ``` What I noticed first was the declaration of the `power()` function beginning with the `extern` keyword. The `extern` keyword directs the compiler to not allocate memory for the `power()` function declaration in this compilation unit and to not issue a diagnostic if no defining declaration for `power()` is found within the same compilation unit. That allows the linker to find the defining declaration in another compilation unit. In this case, the defining declaration is in the assembly file `main.s`. However, functions in C have external linkage by default. We could remove the `extern` keyword and the only thing the compiler will do is issue a warning. The program will still compile and link correctly without it. Having the keyword, however, makes it clear to anyone reading the program that the function is defined elsewhere. # Answer Updated `main.s` file: ```armasm .syntax unified .cpu cortex-m4 .fpu softvfp .thumb .p2align 2 .global power @ Make the power label visible in the symbol table. .type power, %function @ r0 = Base/Result, r1 = Exponent, r2 = Calculation @ extern int power(int base, int exponent); @ Perform exponentiation. power: .fnstart base .req r0 @ .req is the equivalent to RN in Eclipse compiler. exponent .req r1 scratchpad .req r2 MOV scratchpad, base @ Start the multiplication process with the base. CMP base, #0 @ Set the Z status flag if the base is 0. BEQ power_done @ If the base is 0, branch to the end. CMP exponent, #0 @ Set the Z status flag if the exponent is 0. MOV scratchpad, #1 @ Start the multiplication process with 1. BEQ power_done @ If the exponent is 0, branch to the end. MOV scratchpad, base @ Start the multiplication process with the base. power_loop: SUBS exponent, exponent, 1 @ Subtract 1 from the exponent. BEQ power_done @ If the exponent is 0, branch to the end. MUL scratchpad, scratchpad, base @ Multiply the base by the product of the previous products. B power_loop @ Unconditionally branch to the power_loop label. power_done: MOV base, scratchpad @ Move the result to the return register. BX lr @ Return from the function. r0 holds the return value. .fnend .global res_div @ Make the res_div label visible in the symbol table. .type res_div, %function @ r0 = Resistor 1, r1 = Resistor 2, r2 = Summation @ extern int res_div(int r1, int r2); @ Perform a resistor divider calculation. res_div: .fnstart @ It actually was easier if I didn't use the register equate directives @ to assign each register a name. ADCS r2, r0, r1 @ Add with carry both resistor values and update the CPU @ status register flags. BEQ res_div_done @ If the sum is 0, branch to the end. MULS r0, r0, r1 @ Multiply both resistor values and update the CPU status @ register flags. BEQ res_div_done @ If the product is 0, branch to the end. MOV r1, #100 @ Store 100 in the second register for scaling. MUL r1, r0, r1 @ Scale the product by 100. UDIV r0, r1, r2 @ Divide the product of both resistor values by the sum @ of both resistor values. res_div_done: BX lr @ Return from the function. r0 holds the return value. .fnend ``` Updated `main.c` file: ```c /* ... */ /* Declare the res_div function in main.c so it can be defined in math.s. */ extern int res_div(int r1, int r2); /* ... */ int main(void) { HAL_Init(); SystemClock_Config(); /* Initialize all configured peripherals. */ MX_GPIO_Init(); MX_TIM7_Init(); /* Configure GPIOs. */ GPIOD->MODER = 0x55555555; /* Use the Port D mode register to make all pins outputs. */ GPIOA->MODER |= 0x000000FF; /* Use the Port A mode register to make A0 to A3 analog pins. */ GPIOE->MODER |= 0x55555555; /* Use the Port E mode register to make all pins outputs. */ GPIOC->MODER |= 0x0; /* Use the Port C mode register to make all pins inputs.*/ GPIOE->ODR = 0xFFFF; /* Use the Port E output data register to set all pins high. */ /* Configure ADC1. */ RCC->APB2ENR |= 1<<8; /* Turn on ADC1 clock by forcing bit 8 to 1 while keeping other bits unchanged. */ ADC1->SMPR2 |= 1; /* 15 clock cycles per sample. */ ADC1->CR2 |= 1; /* Turn on ADC1 by forcing bit 0 to 1 while keeping other bits unchanged. */ TIM7->PSC = 199; /* 40Khz timer clock prescaler value, 40Khz = 16Mhz / 200. */ TIM7->ARR = 1; /* Count to 1 then generate interrupt (divide by 2), 20Khz interrupt rate to increment byte counter for 78Hz PWM. */ TIM7->DIER |= 1; /* Enable timer 7 interrupt. */ TIM7->CR1 |= 1; /* Enable timer counting. */ /* Declare the input and output variables for the resistor divider calculation. */ int r1, r2, answer; /* Infinite loop. */ while (1) { /* Get the values for the resistor divider calculation from the first 8 switches. */ r1 = (GPIOC->IDR >> 4) & 0xF; r2 = GPIOC->IDR & 0xF; /* Display the first parameter on the first two 7-segment digits from the left. */ Seven_Segment_Digit (7, (r1 / 10) % 10, 0); Seven_Segment_Digit (6, r1 % 10, 1); /* Display the second parameter on the next two 7-segment digits from the left. */ Seven_Segment_Digit (5, (r2 / 10) % 10, 0); Seven_Segment_Digit (4, r2 % 10, 1); /* Perform the resistor divider calculation and store the result. */ answer = res_div(r1, r2); /* Display the result of the resistor divider calculation on the last four 7-segment digits from the left. */ Seven_Segment_Digit (3, (answer / 1000) % 10, 0); Seven_Segment_Digit (2, (answer / 100) % 10, 1); Seven_Segment_Digit (1, (answer / 10) % 10, 0); Seven_Segment_Digit (0, answer % 10, 0); } } /* ... */ ```