PCB Design and Etching

I recently designed a breakout board for the STM32F0. The official STM board is great (and cheap) but I wanted a single-sided board. My laptop has a metal case, and it's tempting to rest a PCB on it sometimes. I'd hate to short out the pin headers.

The end result:

The board is simple: an F0, pin headers for every pin, test points for every pin, a reset button, and a USB port with an LDO regulator to provide 3.3V. I couldn't find surface mount headers locally, so I made some through-hole ones work.

Step 1: Layout the PCB with gEDA.


Step 2: Print the design to a transparency, cut a board to size, and expose it with a UV lamp.


Step 3: Develop the board, then rinse it off.


Step 4: Etch the board. I used sodium persulfate for this project.


Step 5: Remove the remaining mask by placing the board back into the developer.


Step 6: Tin plate the board.


Step 7: Sand the board to clean up the edges.


Step 8: Apply solder paste for the SMDs.


Step 9: Place the SMDs.


Step 10: Bake until the solder melts.


Step 11: Hand-solder the pin headers, then clean the board with some flux remover.


Now I have a board that can be placed on an electrically-conductive surface without fear. I think the four-way layout of pin headers will also make it easier to transition from a prototype to a custom PCB layout.

Custom-Built Servo Tester

When evaluating and repairing servos you need a good servo tester. It needs to be portable, have an easy-to-read screen, have lots of adjustable parameters, and it should have an internal battery while still allowing an external power source. There are several testers on the market but none of them did everything I wanted. So I made my own servo tester. This was my first real project with the STM32F4 and it turned out great. I later ported it to the STM32F0 (since the F4 is overkill for this task) and it performed just as well.

There are four modes: sweep, three positions, two positions, or one position. The sweep mode is useful when checking for damaged (but not stripped) gears. The three-position and two-position modes are great for testing the motor -- it should be warm, not hot, after half an hour of cycling. Lastly, the one-position mode is helpful when setting up a new model.

Each mode has several parameters that can be adjusted. The number of cycles can be limited. The framerate (specified as a period) can be changed. A blinking underline specifies which parameter is currently selected. The buttons on the top adjust the parameter's value, while the buttons on the front change parameters. The rest should be obvious:

It was a fun project. Download the source code for the STM32F4 or STM32F0. See the README file in each archive for details.

Saving Some Money with the STM32F0

When you don't need lots of processing power the F0 is a great option. They're about one-third the price of the F4, while still having plenty of power for most hobby projects. The F0 Discovery board costs around $8 and the bare MCUs are around $2 - $4.

The linux stlink project doesn't fully support the F0 yet, but OpenOCD works well. I have found OpenOCD to be quicker and more stable, so I now use it for the F4 and F0.

Check out my earlier post on the F4 for information on setting up the compiler. Then download and build a version of the STM F0 library with makefiles:

$ mkdir ~/stm32f0 $ cd ~/stm32f0 $ git clone https://github.com/szczys/stm32f0-discovery-basic-template.git $ cd stm32f0-discovery-basic-template/Libraries/ $ make

Download the latest version of OpenOCD, then:

$ cd ~/stm32f0/openocd-0.6.0/ $ ./configure --enable-stlink $ make $ sudo make install $ sudo cp contrib/openocd.udev /etc/udev/rules.d/openocd.rules $ sudo udevadm control --reload-rules

You can use one of the examples in the library as a starting point, or download my empty project template and use it to get started.

STM32F4 Basics: Timers (Part 1)

These STM32F4 Basics posts aren't really tutorials so much as they are slightly-organized notes with short and contrived examples. Many aspects and nuances are ignored. Look through the documentation and headers referenced throughout this post to fill in the gaps. The Timer chapters in RM0090 would be a good place to start.

A timer can be used in several different ways:

  • As a timebase: a counter that can periodically run a block of code.
  • As a counter that tracks how often a pin has transitioned high or low.
  • For pulse-width modulation (output or input.)
  • ...

There are several clock sources, multiplexers, prescalers and clocks that come into play. The clock tree (RM0090, Section 5.2, p.85) should be kept in mind, and double-checked when things don't work as expected. The system_stm32f4xx.c file is used to setup this stuff.

Remember that IRQs will not go into effect until they are enabled in the NVIC. ISR names must match the names defined in startup_stm32f4xx.s.

There are 14 timers with varying abilities. (DM00037051, Table 3, p.29)

Timebases
The simplest timer starts at zero, counts up to some chosen number, then generates an update event. That event can be used to call an ISR. After that event the counter can start over again or stop. The rate at which the counter counts depends on its clock source. All timers can use the internal timer clock shown in the clock tree, and most timers can use other sources covered later on.

If the MCU is setup with a 168MHz system clock, an AHB prescaler of 1, and an APB prescaler of 4, the internal timer clock will run at 84MHz. A 16-bit prescaler is often used to slow down the timer. With a prescaler of 42000 there will be 2000 "ticks" per second -- two ticks per millisecond. The timer will count up to a 16-bit or 32-bit number called the "auto-reload" value, then generate an update event and reset the counter. With an auto-reload value of 2, and a prescaler of 42000, an update event will occur once per millisecond. The formula is:

Example:

// Illuminate the blue LED (D15) three seconds after power-on. #include "stm32f4xx.h" #include "core_cm4.h" int main() { RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; // Enable GPIOD clock GPIOD->MODER |= GPIO_MODER_MODER15_0; // Enable output mode for D15 RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; // Enable TIM6 clock TIM6->PSC = 41999; // Set prescaler to 41999 TIM6->ARR = 5999; // Set auto-reload to 5999 TIM6->CR1 |= TIM_CR1_OPM; // One pulse mode TIM6->EGR |= TIM_EGR_UG; // Force update TIM6->SR &= ~TIM_SR_UIF; // Clear the update flag TIM6->DIER |= TIM_DIER_UIE; // Enable interrupt on update event NVIC_EnableIRQ(TIM6_DAC_IRQn); // Enable TIM6 IRQ TIM6->CR1 |= TIM_CR1_CEN; // Enable TIM6 counter while(1) { } } void TIM6_DAC_IRQHandler() { if(TIM6->SR & TIM_SR_UIF != 0) // If update flag is set GPIOD->BSRRL = GPIO_BSRR_BS_15; // Set D15 high TIM6->SR &= ~TIM_SR_UIF; // Interrupt has been handled }

Externally-Clocked Counters
You can use a GPIO pin as the clock source instead of clocking the counter with some fraction of the system clock. The signal on that pin does not even need to be periodic. A clock pulse can be interpreted from each rising edge on that pin. The timer functions that a GPIO pin can accommodate vary from pin to pin. (DM00037051, Table 6, p.44) There are two external clock modes: external input and external trigger. The main differences are that the external trigger mode can have a prescaler, and there are fewer pins that can be used in that mode.

External Input Example:

// Count how often the blue pushbutton is pressed (how often A0 transitions high.) // The count is stored in TIM5->TIM_CNT, examine it in GDB with "print *(unsigned long*) 0x40000C24" // Unfortunately the pushbutton is not debounced in hardware and the software filters are not enough, // so the count can increment by more than one each time you press the button. #include "stm32f4xx.h" int main() { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // Enable GPIOA clock GPIOA->MODER |= GPIO_MODER_MODER0_1; // Enable AF mode for A0 GPIOA->AFR[0] |= 0b0010; // Select AF2 for A0 (TIM3/4/5) RCC->APB1ENR |= RCC_APB1ENR_TIM5EN; // Enable TIM5 clock TIM5->CCMR1 |= TIM_CCMR1_CC1S_0; // Input mode, map TI1 to IC1 TIM5->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP); // CC1P and CC1NP = 0 for rising edge TIM5->SMCR |= TIM_SMCR_SMS; // External clock mode 1 TIM5->SMCR |= TIM_SMCR_TS_0 | TIM_SMCR_TS_2; // Trigger selection: TI1 TIM5->CR1 |= TIM_CR1_CEN; // Enable TIM5 counter while(1) { } }

External Trigger Example:

// Count every fourth press of the blue pushbutton (pin A0 transitioning high.) // The count is stored in TIM2->TIM_CNT, examine it in GDB with "print *(unsigned long*) 0x40000024" // Unfortunately the pushbutton is not debounced in hardware and the software filters are not enough, // so the count will not be exact. #include "stm32f4xx.h" int main() { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // Enable GPIOA clock GPIOA->MODER |= GPIO_MODER_MODER0_1; // Enable AF mode for A0 GPIOA->AFR[0] |= 0b0001; // Select AF1 for A0 (TIM1/2) RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Enable TIM2 clock TIM2->SMCR |= TIM_SMCR_ETPS_1; // Prescaler = 4 TIM2->SMCR &= ~TIM_SMCR_ETP; // Trigger on rising edge TIM2->SMCR |= TIM_SMCR_ECE; // External clock mode 2 TIM2->CR1 |= TIM_CR1_CEN; // Enable TIM2 counter while(1) { } }

The rest will be covered in Part 2.

STM32F4 Basics: GPIOs

These STM32F4 Basics posts aren't really tutorials so much as they are slightly-organized notes with short and contrived examples. Many aspects and nuances are ignored. Look through the documentation and headers referenced throughout this post to fill in the gaps. The RCC and GPIO chapters in RM0090 would be a good place to start.

Working with registers means there will be lots of bitwise operations. Here's a cheat-sheet:
register |= (1 << bitNumber); // set a bit register &= ~(1 << bitNumber); // clear a bit register ^= (1 << bitNumber); // toggle a bit register & (1 << bitNumber) // check a bit: == 0 if bit was a zero, != 0 if bit was a one

The STM32F407VGT6 has 100 pins, and 80 of them can be used as general-purpose inputs or outputs. The GPIO pins are arranged into 5 channels (a, b, c, d, and e), with 16 pins each. Each channel has a separate clock on the AHB1 bus which must be enabled to use those pins.

Each pin has several qualities that are defined by setting bits in their corresponding registers:

  • Mode: input, output, analog or alternate function (SPI, USB, Timer, etc.)
  • Output type: push-pull or open-drain.
  • Output speed: 2MHz, 25MHz, 50MHz or 100MHz.
  • Pull-up or pull-down: none, pull-up or pull-down.
  • Alternate function low: sets the alternate function for pins 0 – 7.
  • Alternate function high: sets the alternate function for pins 8 – 15.

The reset (default) values for most pins are: input, push-pull, 2MHz, no pull-up/down, AF0. See RM0090 6.4.11 (p.153) for the few exceptions. Good coding practice would involve resetting the GPIO channel if subsequent code expects the reset values.

Note: Analog pins are not 5V tolerant.

Each pin can be used with one of the 15 possible alternate functions shown in RM0090 Figure 14 (p.141):

Any specific pin can only be used with certain alternate functions. See DM00037051 Table 8 (p.58) for a chart detailing what each pin can do. With the Discovery board some of the pins are already used by the on-board components, see UM1472 Table 5 (p.20) for details.

Read or write to each pin with the IO registers:

  • Input data: one bit per pin stores the value.
  • Output data: one bit per pin stores the value.
  • Bit set/reset: Set a bit in the lower half to set the pin, or set a bit in the higher half to clear the pin. This allows for atomic writes to individual pins.

Examples:

// Illuminate the four LEDs around the accelerometer (D12, D13, D14, D15) #include "stm32f4xx.h" int main() { RCC->AHB1RSTR |= RCC_AHB1RSTR_GPIODRST; // Reset GPIOD to ensure reset values exist RCC->AHB1RSTR = 0; // Exit reset state RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; // Enable GPIOD clock GPIOD->MODER |= GPIO_MODER_MODER12_0 | GPIO_MODER_MODER13_0 | // Enable output mode for D12-D15 GPIO_MODER_MODER14_0 | GPIO_MODER_MODER15_0; GPIOD->BSRRL = GPIO_BSRR_BS_12 | GPIO_BSRR_BS_13 | // Set D12-D15 high GPIO_BSRR_BS_14 | GPIO_BSRR_BS_15; } // Illuminate the red LED (D14) while the blue pushbutton is held down (while A0 is high) #include "stm32f4xx.h" int main() { RCC->AHB1RSTR |= RCC_AHB1RSTR_GPIOARST | RCC_AHB1RSTR_GPIODRST; // Reset GPIOA and GPIOD RCC->AHB1RSTR = 0; // Exit reset state RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIODEN; // Enable GPIOA and GPIOD clocks GPIOD->MODER |= GPIO_MODER_MODER14_0; // Enable output mode for D14 while(1) { if(GPIOA->IDR & GPIO_IDR_IDR_0 != 0) // If A0 is high GPIOD->BSRRL = GPIO_BSRR_BS_14; // Set D14 high else GPIOD->BSRRH = GPIO_BSRR_BS_14; // Set D14 low } }

The analog and alternate-function modes will be covered in later posts.

< Prev  6  Next >