Getting Started with the ARM GCC Compiler on Windows, Part 4: Our First Real Project

If you've been following along, in part 1 of this tutorial we installed the free ope-source ARM GCC toolchain, GNU Make, and Eclipse. In part 2, we configured Eclipse to build the demonstration project that ships on the STM32F0DISCOVERY board. In part 3, we pared down the demo project to become a baseline template for future STM32F0 projects.

In this tutorial, we'll start with the template project we created in part 3, and build our first application for the STM32F0DISCOVERY board. This will be a simple project: it will blink the green LED. When the user button is pressed, it'll blink the blue LED instead. Though the project goals are simple, it nevertheless is a good learning exercise.

Step 1: Copy the Template Project

Launch Eclipse and open your STM32F0 workspace. The Template_Project project should already be in this workspace. Right-click on Template_Project in the Project Explorer, and select "Copy." Then right-click in an empty area of the Project Explorer and select "Paste." Eclipse will present the Copy Project dialog box and ask for a new project name. Call your new project "BlinkyLEDs." Leave the "Use default location" checkbox checked.

Naturally, Eclipse creates a copy of Template_Project and populates it with copies of your source code files. If you build the project as-is, it should build. You'll get a few warnings about unused variables in the Standard Peripheral Library, and a warning that main.c exits without returning a value. But it builds nonetheless.

Step 2: Write the Code

Like any C program, ours is going to start in function main(). That's where we'll write most of our code. Unlike a desktop computer, when a microcontroller starts, most of the peripherals are uninitialized. Our first order of business is to initialize the peripherals we want to use. In our case, the peripherals are pretty basic: we're using two output pins (one for the green LED and one for the blue), and one input pin (for the user button). There are two approaches to initializing these pins. Dr. Al-Hertani, in his tutorial, advocates manipulating the necessary registers directly. I tend to favor (at least in some cases) using the functions provided by ST's Standard Peripheral Library. Either approach will get you where you want to go. The libraries tend to produce larger code, but tend to be easier to use and (for the most part) easier to debug.

Regrettably, the documentation for the Standard Peripheral Library is... well... lacking. (And that's being kind to ST.) On the other hand, the library is well-organized, and the function names are usually self-explanatory. The library is organized by peripheral. The functions we want to use are found, not surprisingly, in the GPIO functions. Use the .chm help file that came with the library as a broad roadmap, and dig in where necessary to find the functions, structs, or constants you're looking for.

If you've read the Datasheet for the STM32F0 series (and if you haven't, you really should), you know that the GPIO pins on this microcontroller are very versatile. Any pin can be a digital input, a digital output, an analog input, or be assigned to alternate functions that map to the hardware peripherals on the chip. Pins can be configured to use an internal pull-up or pull-down resistor, and can be set to one of three speeds. (I didn't find any explicit references to what the speed settings do, but table 52 in the datasheet suggests that this affects the slew rate of the outputs, which might help reduce EMI. Hobbyists are unlikely to be concerned about output speed of GPIO pins.) Output pins can be push-pull or open drain. Many of the pins are 5V tolerant. (See Table 13 of the Datasheet.)

After the processor exits reset, the I/O pins are configured as inputs without pull-up or pull-down resistors (that is, they are 'floating.') The Standard Peripheral Library provides a function, GPIO_Init(), that allows us to initialize the GPIO pins the way we want. This function accepts two parameters: the first is which GPIO port we want to configure, the second is a struct (GPIO_InitTypeDef) that tells GPIO_Init how we want the pin(s) configured.

So first we must declare a variable of type GPIO_InitTypeDef, we must fill in its members, and then call GPIO_Init(). The code looks like this:

1
2
3
4
5
6
7
GPIO_InitTypeDef    GPIOInit;
GPIOInit.GPIO_Mode = GPIO_Mode_OUT;
GPIOInit.GPIO_OType = GPIO_OType_PP;
GPIOInit.GPIO_Pin = GREEN_LED_PIN;
GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIOInit.GPIO_Speed = GPIO_Speed_Level_3;
GPIO_Init(GREEN_LED_PORT, &GPIOInit);

The first line declares a variable, GPIOInit, of type GPIO_InitTypeDef. Lines 2 through 6 fill in the members of the structure. Line 2 says we want this pin to be an output. Line 3 says we want the driver to be push-pull (as opposed to open drain). Line 5 says we don't want pull-up or pull-down resistors. Line 6 says we want the pin set to the fastest setting. All of these constants can be found in the help file.

Line 4 is where we determine which pin(s) we want to affect. For example, if we have 6 pins on the same GPIO port that we want to configure identically, we can use a bitwise OR to combine these pins into a single call to GPIO_Init(). In this case, I've only selected one pin, GREEN_LED_PIN, which is #defined in main.h to be "GPIO_Pin_9," which itself is #defined in one of the Standard Peripheral Library headers.

We then call GPIO_Init(), passing it an indicator of which GPIO port we want to work with (GREEN_LED_PORT, which is #defined as "GPIOC"), and a pointer to the struct we just filled out. GPIO_Init() will configure the pin for us.

The complete GPIO initialization code for the BlinkyLEDs program looks like this: (The full code is presented later in the post, along with a download link.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Set up the pins for the LEDs and user button
GPIO_InitTypeDef    GPIOInit;
GPIOInit.GPIO_Mode = GPIO_Mode_OUT;
GPIOInit.GPIO_OType = GPIO_OType_PP;
GPIOInit.GPIO_Pin = GREEN_LED_PIN;
GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIOInit.GPIO_Speed = GPIO_Speed_Level_3;
GPIO_Init(GREEN_LED_PORT, &GPIOInit);

GPIOInit.GPIO_Pin = BLUE_LED_PIN;
GPIO_Init(BLUE_LED_PORT, &GPIOInit);

GPIOInit.GPIO_Mode = GPIO_Mode_IN;
GPIOInit.GPIO_Pin = USER_BUTTON_PIN;
GPIO_Init(USER_BUTTON_PORT, &GPIOInit);

You'll notice that I called GPIO_Init() three times: once for the green LED, once for the blue LED, and once for the user button. It so happens that on the STM32F0DISCOVERY board, both LEDs are on the same GPIO port. I could have consolidated both of these pins into the same call to GPIO_Init by using:

1
GPIOInit.GPIO_Pin = GREEN_LED_PIN | BLUE_LED_PIN;

That would have saved me some code. However, if I were developing a prototype and hadn't yet finalized my physical implementation, the original code structure would work even if I had to change the physical layout of my circuit late in the development cycle. I could simply edit the #define for my LED ports and recompile. Given the generous code space in the STM32F0 on this board (64K), I think that this type of defensive coding makes sense, at least in earlier stages of development.

As I alluded to in my Lab Notes post yesterday, I had a frustrating time getting the GPIO pins working as I expected them to. It turns out that every peripheral on the STM32F0, including the GPIO ports, has a gated clock. The clock to the peripheral can be turned on or off as needed. (Turning off the clock saves power, which is important in battery operated devices.) What I didn't expect was that even writing to the peripheral's registers doesn't have any effect unless the peripherals' clock is enabled! To enable the peripheral clock, we use a function from the RCC (reset and clock control) section of the Standard Peripheral Library:

1
2
3
4
//enable clocking to the GPIO ports
RCC_AHBPeriphClockCmd(GREEN_LED_PERIPH_CLOCK, ENABLE);
RCC_AHBPeriphClockCmd(BLUE_LED_PERIPH_CLOCK, ENABLE);
RCC_AHBPeriphClockCmd(USER_BUTTON_PERIPH_CLOCK, ENABLE);

Again, the contants are supplied by preprocessor #define statements that I'll share in a moment. Since the blue and green LEDs are on the same port, the first two calls are redundant, but my argument above works for this code choice as well.

Once the GPIO pins are configured, the code spins in an infinite loop, blinking LEDs on and off:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//loop forever
while (1)
{
    while (blinkFlag == 0) {}

    blinkFlag = 0;

    if (USER_BUTTON_IS_PRESSED)
    {
        //turn off the green LED
        GREEN_LED_PORT->BSRR = (GREEN_LED_PIN << 16);

        //toggle the blue LED
        BLUE_LED_PORT->ODR ^= BLUE_LED_PIN;
    }
    else
    {
        //turn off the blue LED
        BLUE_LED_PORT->BSRR = (BLUE_LED_PIN << 16);

        //toggle the green LED
        GREEN_LED_PORT->ODR ^= GREEN_LED_PIN;
    }
}

I'll be the first person to admit that I'm being wishy-washy here. While the Standard Peripheral Library does provide functions to read and write to the GPIO pins (GPIO_SetBits(), GPIO_ResetBits(), and GPIO_ReadInputData()), I elected to read from/write to the GPIO peripheral registers directly instead of using the library functions. I wish I could give you a good reason for this and demonstrate consistency in adhering to a philosophy, but the truth is that the library just seemed to be a better solution to me for initializing the pins, and accessing the registers directly seemed better for reading/writing the pins.

In any case, the code should be fairly straightforward. In line 4, we spin, waiting for a variable, blinkFlag, to be set to a value other than zero. (We'll cover shortly how that variable can be changed to a different value.) When blinkFlag tells us to blink, we first reset the variable. We then read the status of the user button. Depending on the state of the button, we turn off one LED and blink the other. Let's look at one of the paths through the if statement since the two are virtually identical.

If the button is not pressed, we want to turn off the blue LED and blink the green one. The STM32F0 has registers to set or clear GPIO pins atomically. The BSRR register is the "bit set and reset" register. This register (along with a few others) is a bit peculiar in that it's a write-only register. Writing a '1' to the lower 16 bits of this register will set the corresponding bit in the ODR (output data) register, which has the effect of setting the pin 'high' (as long as the pin is configured as an output). Writing a '1' to the upper 16 bits resets the corresponding pin. Writing a '0' to any bit in this register has no effect on the ODR. The BSRR register, along with the BRR (bit reset register) are convenient ways to change individual bits without doing a read-modify-write. (For more on RMW, see Dr. Al-Hertani's excellent blog post on the GPIO registers.)

Unfortunately, there is no corresponding 'bit toggle' register, so in order to toggle a bit, we must read the ODR and manually toggle it. The bitwise XOR operator does this handily, which is what we're doing in the code.

By the way, USER_BUTTON_IS_PRESSED is #defined as:

#define USER_BUTTON_IS_PRESSED (USER_BUTTON_PORT->IDR & USER_BUTTON_PIN)

That is, it's a bitwise AND of the GPIO port's input data register and the pin that we're testing. The schematic of the STM32F0DISCOVERY board shows that the user button has a 220K pull-down resistor to ground, and shorts to VDD when pressed. Therefore, the pin reads '0' when the button is not pressed and '1' when the button is pressed. If it were wired with a pull-up resistor and shorted to ground when pressed, these values would be reversed.

The full main() function is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
int main(void)
{
    //enable clocking to the GPIO ports
    RCC_AHBPeriphClockCmd(GREEN_LED_PERIPH_CLOCK, ENABLE);
    RCC_AHBPeriphClockCmd(BLUE_LED_PERIPH_CLOCK, ENABLE);
    RCC_AHBPeriphClockCmd(USER_BUTTON_PERIPH_CLOCK, ENABLE);

    //Set up the pins for the LEDs and user button
    GPIO_InitTypeDef    GPIOInit;
    GPIOInit.GPIO_Mode = GPIO_Mode_OUT;
    GPIOInit.GPIO_OType = GPIO_OType_PP;
    GPIOInit.GPIO_Pin = GREEN_LED_PIN;
    GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIOInit.GPIO_Speed = GPIO_Speed_Level_3;
    GPIO_Init(GREEN_LED_PORT, &GPIOInit);

    GPIOInit.GPIO_Pin = BLUE_LED_PIN;
    GPIO_Init(BLUE_LED_PORT, &GPIOInit);

    GPIOInit.GPIO_Mode = GPIO_Mode_IN;
    GPIOInit.GPIO_Pin = USER_BUTTON_PIN;
    GPIO_Init(USER_BUTTON_PORT, &GPIOInit);

    //set up the systick clock to interrupt
    SysTick_Config(4800);

    //loop forever
    while (1)
    {
        while (blinkFlag == 0) {}

        blinkFlag = 0;

        if (USER_BUTTON_IS_PRESSED)
        {
            //turn off the green LED
            GREEN_LED_PORT->BSRR = (GREEN_LED_PIN << 16);

            //toggle the blue LED
            BLUE_LED_PORT->ODR ^= BLUE_LED_PIN;
        }
        else
        {
            //turn off the blue LED
            BLUE_LED_PORT->BSRR = (BLUE_LED_PIN << 16);

            //toggle the green LED
            GREEN_LED_PORT->ODR ^= GREEN_LED_PIN;
        }
    }
}

The only thing we haven't talked about yet is line 25 above, a call to SysTick_Config().

And now we have to back up some. If you've read the STM32F0's datasheet and the reference manual, you won't find any description of the SysTick timer. And if, like me, you're coming from the world of PIC and AVR microcontrollers, this can be confusing. We need to talk a little bit about ARM, the company; ARM, the processor core; and the STM32F0; and how they all relate.

ARM Holdings, PLC, is a company based in Cambridge, England. Their largest business is designing the ARM processor cores. However, ARM does not manufacture any processors. Rather, ARM licenses the processor designs to other manufacturers (such as ST), and these manufacturers build the licensed core into their products, such as the STM32F0 line of microcontrollers. When ST makes these processors, ST bolts on all of the peripherals (GPIO, timers, I2C, SPI, UART, etc.) that are documented in the ST literature. ST doesn't document the ARM core, however. For that, we have to go to documentation from ARM. The SysTick timer is a feature of the ARM core, and to learn more about it we can read ARM's documentation on this feature. The SysTick timer is a 24-bit countdown timer. Every clock cycle, the timer value is decremented. When the value reaches zero, a flag is set, an interrupt is optionally generated, and the timer is reloaded from the reload value register. The SysTick_Config() function is provided by the CMSIS (Cortex Microcontroller Software Interface Standard) library, which is written by ARM and is included in ST's Standard Peripheral Library. I haven't really found any documentation on the functions in the CMSIS library, but the file core_cm0.h implements the SysTick_Config function. This function sets the reload value of the SysTick timer (to 4800, in our case), enables the SysTick timer, and enables interrupts when the timer reaches zero.

The SysTick interrupt handler is in file stm32f0xx_it.c. The code is:

1
2
3
4
5
6
7
8
9
void SysTick_Handler(void)
{
    sysTickOverflows ++;
    if (sysTickOverflows == SYSTICKS_PER_LED_TOGGLE)
    {
        blinkFlag = 1;
        sysTickOverflows = 0;
    }
}

Two variables are defined in this file:

1
2
3
/* Private variables --------------------------*/
uint8_t blinkFlag = 0;
uint32_t sysTickOverflows = 0;

With our processor running at 48MHz, the interrupt fires every 4800 clock cycles, which is 10,000 times a second. In stm32f0xx_it.h, I #defined SYSTICKS_PER_LED_TOGGLE as 2000. Therefore, the LED will toggle 5 times per second.

The only remaining bit of code is in main.c, where we declare the variable blinkFlag as an external variable:

1
2
/* Private variables ----------------*/
extern uint8_t blinkFlag;

The extern keyword tells the compiler that this variable is defined external to the module being compiled.

If you copy the template project that we created, the only files that need to be modified to get this project to work are main.c, main.h, stm32f0xx_it.c, and stm32f0xx_it.h. Complete listings of these files follow. You can download these files here: BlinkyLEDs_Source.

main.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
#define USER_BUTTON_IS_PRESSED (USER_BUTTON_PORT->IDR & USER_BUTTON_PIN)

/* Private variables ---------------------------------------------------------*/
extern uint8_t blinkFlag;

/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

int main(void)
{
    //enable clocking to the GPIO ports
    RCC_AHBPeriphClockCmd(GREEN_LED_PERIPH_CLOCK, ENABLE);
    RCC_AHBPeriphClockCmd(BLUE_LED_PERIPH_CLOCK, ENABLE);
    RCC_AHBPeriphClockCmd(USER_BUTTON_PERIPH_CLOCK, ENABLE);

    //Set up the pins for the LEDs and user button
    GPIO_InitTypeDef    GPIOInit;
    GPIOInit.GPIO_Mode = GPIO_Mode_OUT;
    GPIOInit.GPIO_OType = GPIO_OType_PP;
    GPIOInit.GPIO_Pin = GREEN_LED_PIN;
    GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIOInit.GPIO_Speed = GPIO_Speed_Level_3;
    GPIO_Init(GREEN_LED_PORT, &GPIOInit);

    GPIOInit.GPIO_Pin = BLUE_LED_PIN;
    GPIO_Init(BLUE_LED_PORT, &GPIOInit);

    GPIOInit.GPIO_Mode = GPIO_Mode_IN;
    GPIOInit.GPIO_Pin = USER_BUTTON_PIN;
    GPIO_Init(USER_BUTTON_PORT, &GPIOInit);

    //set up the systick clock to interrupt
    SysTick_Config(4800);

    //loop forever
    while (1)
    {
        while (blinkFlag == 0) {}

        blinkFlag = 0;

        if (USER_BUTTON_IS_PRESSED)
        {
            //turn off the green LED
            GREEN_LED_PORT->BSRR = (GREEN_LED_PIN << 16);

            //toggle the blue LED
            BLUE_LED_PORT->ODR ^= BLUE_LED_PIN;
        }
        else
        {
            //turn off the blue LED
            BLUE_LED_PORT->BSRR = (BLUE_LED_PIN << 16);

            //toggle the green LED
            GREEN_LED_PORT->ODR ^= GREEN_LED_PIN;
        }
    }
}

#ifdef  USE_FULL_ASSERT

/**
  * @brief  Reports the name of the source file and the source line number
  *   where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */

void assert_failed(uint8_t* file, uint32_t line)
{
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */


  /* Infinite loop */
  while (1)
  {}
}
#endif

main.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H

/* Includes ------------------------------------------------------------------*/
#include "stm32f0xx.h"

/* Exported types ------------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define GREEN_LED_PIN             GPIO_Pin_9
#define GREEN_LED_PORT            GPIOC
#define BLUE_LED_PIN              GPIO_Pin_8
#define BLUE_LED_PORT             GPIOC
#define USER_BUTTON_PIN           GPIO_Pin_0
#define USER_BUTTON_PORT          GPIOA
#define GREEN_LED_PERIPH_CLOCK    RCC_AHBPeriph_GPIOC
#define BLUE_LED_PERIPH_CLOCK     RCC_AHBPeriph_GPIOC
#define USER_BUTTON_PERIPH_CLOCK  RCC_AHBPeriph_GPIOA

/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
#endif /* __MAIN_H */

stm32f0xx_it.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/* Includes ------------------------------------------------------------------*/
#include "stm32f0xx_it.h"
#include "main.h"

/** @addtogroup STM32F0-Discovery_Demo
  * @{
  */


/** @addtogroup STM32F0XX_IT
  * @brief Interrupts driver modules
  * @{
  */


/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint8_t blinkFlag = 0;
uint32_t sysTickOverflows = 0;

/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/******************************************************************************/
/*            Cortex-M0 Processor Exceptions Handlers                         */
/******************************************************************************/

/**
  * @brief  This function handles NMI exception.
  * @param  None
  * @retval None
  */

void NMI_Handler(void)
{
}

/**
  * @brief  This function handles Hard Fault exception.
  * @param  None
  * @retval None
  */

void HardFault_Handler(void)
{
  /* Go to infinite loop when Hard Fault exception occurs */
  while (1)
  {
  }
}

/**
  * @brief  This function handles SVCall exception.
  * @param  None
  * @retval None
  */

void SVC_Handler(void)
{
}

/**
  * @brief  This function handles PendSVC exception.
  * @param  None
  * @retval None
  */

void PendSV_Handler(void)
{
}

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */

void SysTick_Handler(void)
{
    sysTickOverflows ++;
    if (sysTickOverflows == SYSTICKS_PER_LED_TOGGLE)
    {
        blinkFlag = 1;
        sysTickOverflows = 0;
    }
}

/******************************************************************************/
/*                 STM32F0xx Peripherals Interrupt Handlers                   */
/*  Add here the Interrupt Handler for the used peripheral(s) (PPP), for the  */
/*  available peripheral interrupt handler's name please refer to the startup */
/*  file (startup_stm32f0xx.s).                                               */
/******************************************************************************/

/**
  * @brief  This function handles PPP interrupt request.
  * @param  None
  * @retval None
  */

/*void PPP_IRQHandler(void)
{
}*/


/**
  * @}
  */


/**
  * @}
  */

stm32f0xx_it.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32F0XX_IT_H
#define __STM32F0XX_IT_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f0xx.h"

/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
#define SYSTICKS_PER_LED_TOGGLE 2000

/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */

void NMI_Handler(void);
void HardFault_Handler(void);
void SVC_Handler(void);
void PendSV_Handler(void);
void SysTick_Handler(void);

#ifdef __cplusplus
}
#endif

#endif /* __STM32F0XX_IT_H */

Step 3: Build and Upload

Once you have the files created, build the project and use the ST-LINK Utility to upload the project to your dev board. Reset the processor from the ST-LINK Utility or with the reset button, and get your blinkylights on!

As I said up front, this is an admittedly simple program. I hope that it's given you a foundation in programming for the STM32F0, and that you're able to move on from here to implement ideas of your own! Comments are always appreciated; let me know what you liked and didn't like about this post. I'll do my best to answer any questions you may have, but please keep in mind that I'm just a hobbyist; believe it or not, I don't have any formal education at all in electronics.