Terms of service
Brokking
 
 
.net
Let's keep it simple
 

 
   Home
   Projects
 

   Articles
 

   Donation
         
 
 
 
 

 
Brokking.net - Video: STM32 for Arduino - Connecting a RC receiver via input capture mode

STM32 for Arduino - Connecting a RC receiver via input capture mode

This page contains the full script that I used for making this video.

Hello and welcome to another one of my videos. In the previous video the conclusion was that connecting a RC-receiver via the function attachInterrupt() is very inefficient. In this video I will explain how to connect a RC-receiver to this STM32 microcontroller via the more efficient capture input mode. And while doing this I will try to give you some background information about the STM32.

Ok... a quick summary, as explained in the previous videos, a RC-receiver outputs a pulse that varies in length from 1000 till 2000us as you can see here. So to measure the length of the pulse we need to know the begin and end time of the pulse. By subtracting these two values we get the total length of the pulse.

Going through the datasheet of the STM32F100 series I found this function in the timer section: input capture mode. As you can see there is a short description that describes how this feature works and a setup example. Also, in the beginning of the timer section this block diagram can be found.

To explain the input capture mode example I reduced the block diagram from the datasheet to this block diagram.

So, we have a 16 bit hardware timer that counts from 0 to the value of the auto reload register. In this case 65535. This is the maximum value of the 16 bit timer. When this value is reached the timer is reset to 0 and starts again. because this is a hardware timer it's not influenced by interrupts. It just keeps counting.

The pulse for the clock is generated by the main system clock that runs on 72MHz. This prescaler is set to 72 and will reduce the clock speed to 1 MHz. As a result the clock increments every microsecond.

Here you can see the input where the receiver is connected. When the connected input is changing from low to high the edge is detected and the timer value is stored in the capture register. When this happens an interrupt is generated that can be used to execute some code.

And basically that's it.

But as always there is a problem. The example in the datasheet describes a capture on the rising edge of the pulse. So the falling edge is not captured. Now we could change this to the falling edge by changing the CCER or capture compare enable register. But then only the falling edge will be detected and the rising edge is skipped.

This might give you the idea that the input capture mode will not work for reading the pulse length. But what if we changed the CCER in the interrupt routine? So, after the detection of the rising edge the CCER is changed to capture the falling edge.

As always, there is only one way to know if it works, so let's give it a try.

As I explained in the first video I will use the STM32 for Arduino add-on that can be downloaded from this Github page that is maintained by Roger Clark. It's a work in progress as you can read here and you are using it at your own risk.

Because it's a work under process I will try to keep the STM32 for Arduino add-on core files unchanged. And as we will see later in the video, that's a challenge.

Ok, back to the input capture mode example.

To get it to work some registers are need to be set as described in the datasheet example. But first I will explain what a register is and how to access them.

On the last page of the timer section there is a register map that shows all the timer registers. And to keep it simple, a register is nothing more than a predefined 32 bit variable that is fixed somewhere in the memory of the STM32. Depending on its function it has read and write access capabilities.

By setting individual bits in the registers the specific functions of the timer can be set.

Because the registers are part of the microcontrollers memory its necessary to know the register address before we can do anything useful with them. In the datasheet the Register boundary addresses are given for all registers. I will focus on timer2 because this will be the timer that I will use during this video.

This is the address location in hexadecimal form of the first timer2 register. Now let's go back to the timer register map.

CR1 is the first register and has this address location. Here we can see the offset of the following registers. Every register holds 4 bytes. Meaning that the next register address starts 4 bytes later. As a result register CR2 gets this address location. And we can do this for all the registers.

Ok, this makes sense so far. Now that we know all the register addresses, how can we access them?

Let's take the first timer2 register TIM2_CR1 as an example.

By the way, the notation reserved means that these bits must not be changed when writing the registers.

Now, when I want to set the counter enable or CEN bit to enable the timer I want to do something like this in my program. In short: an unsigned one that is shifted 0 positions to the left is written to the TIM2_CR1 register.

To get this to work we need to tell the compiler that the register name TIM2_CR1 points to this register address. And that is exactly what is needed to get it to work: pointers.

In simple terms: a pointer is a bridge between one point and a memory address location. The first point can be a variable or a pointer constant. And the pointer constant technique is used for addressing registers to names.

For our TIM2_CR1 register the pointer constant looks like this. Where __io will be replaced by the qualifier volatile as you can see here in the types.h file.

If you have no idea what a volatile is you might want to watch this video that I made about the qualifier volatile. And why it slows down your program.

Ok, back to the pointer constant.

So, when this line is used in a program the counter enable or CEN bit is set. Because this code is completely unreadable during programming we can use the define method to create a name for this pointer constant. And my personal choice would be TIM2_CR1.

And finally the counter enable bit can be set with this line.

Ok, let's have a look in the main folder of the STM32 for Arduino add-on. If I search for the timer 2 boundary address in the STM32F1 folder these files pops up.

And when I open the timer.h file we can see that the same technique is used and that the name that is assigned to the register address is TIMER2_BASE. But there is a difference. The pointer constant links to a structure that can be found here.

In simple terms: TIMER2_BASE defines the boundary address of the structure. The structure then holds the individual pointers to the specific registers.

Because the structure holds pointers we need to use the arrow operator to get access to the structure pointer members.

So, with the STM32 for Arduino add-on we can set the counter enable bit with this line.

Ok, this makes life a little easier. To make life really relaxing we will have a look in another timer.h file in the STM32F1 folder.

Here you can see that the definition TIMER_CR1_CEN will be replaced during compilation by the same line as I used in the example. And as you can see all the individual bits of the timer register are defined in the same way.

And finally we can use this human readable line to set the counter enable bit in the CR1 register.

With this information it's time to set the timer registers for the input capture mode.

In the timer register section of the datasheet we can find that all registers are set to zero when the microcontroller is started. This is a great starting point when setting registers manually.

However, the STM32 for Arduino add-on initializes all the timers at startup as we can see here in the boards.cpp file. As a result we need to assume that some unknown settings are made and that all registers need to be set in the main program.

So, how do we find the correct register settings? The process is very simple: check the datasheet. And don't be ashamed if you can't figure it out the first couple of times. To be honest, it took me a couple evenings before I got it to work.

To keep it simple I will use this block diagram again. First we need to enable the timer by setting the counter enable bit in the CR1 register.

And that is done with this line.

Next we need to make sure that the capture can trigger an interrupt. This is done by setting the capture/compare 1 interrupt enable bit in the DIER register.

And now it's time to connect the receiver input to the capture edge detector and enable the trigger on the rising edge.

And that is done in these two lines.

Finally the prescaler and the auto reload value are set in the corresponding register.

As shown in these two lines.

All the other registers are unused and reset back to zero. And basically, that's it.

Again, make sure to check the datasheet for detailed information and examples about the register settings.

Now that all the registers are correctly set I will focus on the interrupt handler that is called every time the compare register is set. The timer2 interrupt handler can be found in the file vector_table.S as shown here.

Normally this interrupt handler can be part of the main code like this. The extern "C" linkage is needed to get the interrupt handler to be recognized in the main program section. But when I compile this code you can see that the interrupt handler is also declared in the file timer.c.

Ok, long story and as explained earlier in the video, I will not change the core files of the STM32 for Arduino add-on. Meaning that I have to use something else.

In the timer.c file we can see that the interrupt handler calls this dispatch_general function. As a result the function that is normally attached via the attachinterrupt function is called.

So, when I call this Timer2.attachCompare1Interrupt function the subroutine handler_channel_1 is executed whenever the timer2 compare interrupt 1 is triggered. As you can see here in the code I added this line before the register setup because I want to setup the registers manually.

As a bonus the nested vector interrupt controller, or NVIC for short, is also enabled for the TIMER2 interrupt. I explained the working of the NVIC controller in the previous video. A link can be found in the description.

And after all this work we can finally setup the interrupt code that will measure the length of the pulse. And this is the easy part.

First a check is done to see if the input is high or low. If the pulse is high the rising edge has been detected and the captured time is stored in the variable channel_1_start for later use. Next the register CCER is changed so the edge detector is set to the falling edge of the pulse.

When the input is low the end of the pulse is detected and the pulse time is calculated by subtracting the previous time from the current time.

Once in a while the timer restarts from zero between the measurements. And the measured pulse time will be negative. When that happens the total time is incremented by the counter reload value. And of course the edge detector is set to detect the rising edge again.

In the main code I the channel_1 time is printed to the serial monitor.

So let's connect one receiver channel and test the program.

To figure out the correct pin we need to check the datasheet. The first channel of timer 2 is connected to pin PA0 as shown in this pinout diagram. And for those who have seen my previous videos about the STM32 will notice that there is no FT notation.

And I thought that this might be a problem. As it turns out most receivers work on 3.3V because they have to work fine on 4 rechargeable batteries. As a result the pulse output is also 3.3V. Meaning that they can be connected to non 5V tolerant pins without any problems.

And to check if your receiver has a 3.3V output you can connect it to an Arduino Uno. Connect 5V to the plus of the receiver, ground to ground and channel 1 to analog 0.

When you run a simple test program like this it will output the maximum measured voltage on the serial output. For this FlySky transmitter the output is around 3V.

Now that the receiver is connected I will upload the first program to the STM32 and open the serial monitor at 57.6kbps.

And there it is: 1500us with the stick in the center position. When I move the stick to the left the value on the screen will change to 1000us. And when the stick is full right the value is 2000us.

Now that everything is working I can expand the code for the other 5 channels. Because Timer2 has only 4 input capture channels Timer3 is used for the remaining 2 channels. But the principle will stay the same. And with everything in place it looks like this.

When I run this program you can see that all the channels are working fine.

The code that I made for this video can be downloaded via a link in the description.

And that wraps it up for this video. As always: I hope you learned something and if so, please take the time to give this video a thumbs up. Thank you for watching and see you next time.