Skip to content

Commit

Permalink
update servo part 1 to be fully c++
Browse files Browse the repository at this point in the history
  • Loading branch information
owenpark8 committed Sep 23, 2024
1 parent 9709726 commit 9b83ed4
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 241 deletions.
96 changes: 52 additions & 44 deletions docs/getting-started/starter/servo/part1-pwm.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,80 +66,88 @@ Once the .ioc is open, configure the pins for PWM.

![timer 1 configuration in stm32cubeide](servo-timer-config.webp)

You will now have to configure two values—Prescaler and Counter Period—in order to
We will now have to configure two values—Prescaler and Counter Period—in order to
correctly set up this PWM timer. These values are located in the "Parameter Settings" and must be
calculated. Refer to the timer [reference guide](../../../info/timers.md) for information on
calculating these values.

### 3. Creating the header file
### 3. Opening the header file

Having a Servo object will make it easier to adjust the number servos or where the servos are
in the future, so for good practice, we will create a servo class and declare any member variables
Having a servo object will make it easier to adjust the number servos or where the servos are
in the future, so for good practice, we will create a Servo class and declare any member variables
and member functions in a header file.

On the menu to the left, in `Core`→`Inc`, create a new header file named `servo.hpp`.
On the menu to the left, in `Core`→`Inc`, open the header file named `servo.hpp`.

![creating a header file in CubeIDE](create_header.webp)
Here, we can see the interface for the Servo class that we will be implementing.

![naming a header in CubeIDE](name_header.webp)

Near the top of the header file, copy in the following lines:

```c
#pragma once

#include <stdlib.h>

#include "stm32g4xx_hal.h"
```

Then, create a Servo class with 3 member variables:
The Servo class has 2 member variables:

* `TIM_HandleTypeDef *timer` : this tells the STM which timer is being used to generate the PWM signal
* `uint32_t channel` : this tells the STM which channel is being used for the PWM signal
* `uint32_t *output` : this is the address of the output register where the number of ticks that are set to high is stored


It also has 3 member functions:

Create the function prototypes for the 3 functions that are needed to create and use a Servo object:
* A default constructor that does not take in any parameters
* A constructor that initializes the servo with a timer, sets the channel, and
* `set_servo_angle` that converts an angle to the number of ticks for the CCR
* A constructor that takes in the timer and channel for the PWM signal
* A function `start_servo()` that starts the PWM generation
* A function `set_servo_angle(int angle)` that moves the servo to the specified angle


### 4. Implementing Servo functions

Now that you have a Servo struct and function prototypes, it's time to implement the functions.
Now that we know the interface for the Servo class, it's time to implement the functions.

On the menu to the left, in Core > Src, create a new .c file named servo.c
On the menu to the left, in `Core`&rarr;`Src`, open the C++ source file named `servo.cpp`.

![creating a source file in CubeIDE](create_source.webp)
To start the servo, you must initialize the timer used to generate the PWM signal. To do this,
use `HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)`. Find more information about this
built-in HAL function [here](http://www.disca.upv.es/aperles/arm_cortex_m3/llibre/st/STM32F439xx_User_Manual/group__tim__exported__functions__group3.html).

Don't forget to #include your header file.

Here is an example of what a function that creates a new object should look like in C:

![new thermistor object example code](new_thermistor.webp)


To initialize a servo object, you must initialize the timer used to generate the PWM signal. To do this, use HAL_TIM_PWM_Start(). Find more information about this built-in HAL function [here](http://www.disca.upv.es/aperles/arm_cortex_m3/llibre/st/STM32F439xx_User_Manual/group__tim__exported__functions__group3.html)

When implementing set_servo_angle, keep in mind what PWM signal corresponds to what angle. Check back on the [servo datasheet](http://www.ee.ic.ac.uk/pcheung/teaching/DE1_EE/stores/sg90_datasheet.pdf) to determine this.
When implementing set_servo_angle, keep in mind what PWM signal corresponds to what angle.
Check back on the [servo datasheet](http://www.ee.ic.ac.uk/pcheung/teaching/DE1_EE/stores/sg90_datasheet.pdf)
to determine this. In order to set the CCR register to change the PWM signal, you can use
`__HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__)`. Below is information on this function:

```c
/**
* @brief Set the TIM Capture Compare Register value on runtime without calling another time ConfigChannel function.
* @param __HANDLE__ TIM handle.
* @param __CHANNEL__ TIM Channels to be configured.
* This parameter can be one of the following values:
* @arg TIM_CHANNEL_1: TIM Channel 1 selected
* @arg TIM_CHANNEL_2: TIM Channel 2 selected
* @arg TIM_CHANNEL_3: TIM Channel 3 selected
* @arg TIM_CHANNEL_4: TIM Channel 4 selected
* @arg TIM_CHANNEL_5: TIM Channel 5 selected
* @arg TIM_CHANNEL_6: TIM Channel 6 selected
* @param __COMPARE__ specifies the Capture Compare register new value.
* @retval None
*/
#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__)
```

### 5. Testing your servo functions

Now that you have the functions to create a servo object and change the angles, it's time to test them out in main.c.
Now that we have implemented our Servo class, it's time to test it out.

Go to main.c and make sure to #include servo.h in the /* USER CODE BEGIN Includes */ section
Since this is a C++ project, we will not be using the `main.c`. Instead, navigate to `Core`&rarr;`Src`
and open `new_main.cpp`.

![servo main include example code](main_include.webp)
In the `new_main()` function, create a new Servo using the constructor. The timer parameter for
should be a `TIM_HandleTypeDef*`. The name for the TIM_Handle that is being used is at the top of
`new_main.c`. The channel parameter should correspond with which timer channel you are using
(remember we set our pin to TIM1_CH1).

In the main function, create a new servo object using the new_servo function. Remember to put your code in a USER CODE spot. The timer parameter for `new_servo` should be a `TIM_HandleTypeDef*`, so look through main.c and find the name for the TIM_Handle that is being used. The channel parameter should correspond with which timer channel you are using (remember we set our pin to TIM1_CH1). The output parameter is the address of the register that holds the number of ticks that are set high in the output signal. For PWM signals, this is the CCR (compare and capture register), which is a member of the TIM object. Servo's output variable should be `&(TIM1->CCR1)` if using TIM1 and CH1.
Now, we can start the servo using the `start_servo()` function we created.

Once you have the servo created, add in the `initialize_servo` function to initialize the timer and set the starting angle. Then, in the `while(1)` loop, change the angle of the servo a few times to make sure your `set_servo` function works and that the PSC and ARR you selected in the .ioc are correct. Between each function call make sure to add a delay (Hint: there is a built in HAL function for delays).
Then, in the `while(1)` loop, change the angle of the servo a few times to make sure your
`set_servo_angle()` function works and that the PSC and ARR you selected in the .ioc are correct.
Between each function call make sure to add a delay (Hint: there is a built in HAL function for delays).

When you are satisfied with your code, make sure it builds and then get a Nucleo, a logic analyzer, and some jumper cables to check your PWM signals. If you think the PWM signals are correct based on the [datasheet](http://www.ee.ic.ac.uk/pcheung/teaching/DE1_EE/stores/sg90_datasheet.pdf), you can test your code on a servo. If you are unsure, just ask for help!
When you are satisfied with your code, make sure it builds and then get a Nucleo, a logic analyzer,
and some jumper cables to check your PWM signals. If you think the PWM signals are correct based on the
[datasheet](http://www.ee.ic.ac.uk/pcheung/teaching/DE1_EE/stores/sg90_datasheet.pdf), you can test
your code on a servo. If you are unsure, just ask for help!

#### Logic Analyzer
The simplest method for debugging a digital signal is often to use a logic analyzer. Please install [Logic](https://www.saleae.com/downloads/) on your laptop.
Expand Down
5 changes: 3 additions & 2 deletions starter-projects/servo/p1-pwm/.cproject
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.floatabi.290930714" name="Floating-point ABI" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.floatabi" useByScannerDiscovery="true" value="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.floatabi.value.hard" valueType="enumerated"/>
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_board.46319183" name="Board" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_board" useByScannerDiscovery="false" value="NUCLEO-G431RB" valueType="string"/>
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.defaults.287093602" name="Defaults" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.defaults" useByScannerDiscovery="false" value="com.st.stm32cube.ide.common.services.build.inputs.revA.1.0.6 || Debug || true || Executable || com.st.stm32cube.ide.mcu.gnu.managedbuild.option.toolchain.value.workspace || NUCLEO-G431RB || 0 || 0 || arm-none-eabi- || ${gnu_tools_for_stm32_compiler_path} || ../Core/Inc | ../Drivers/STM32G4xx_HAL_Driver/Inc | ../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy | ../Drivers/BSP/STM32G4xx_Nucleo | ../Drivers/CMSIS/Device/ST/STM32G4xx/Include | ../Drivers/CMSIS/Include || || || USE_NUCLEO_64 | USE_HAL_DRIVER | STM32G431xx || || Drivers | Core/Startup | Core || || || ${workspace_loc:/${ProjName}/STM32G431RBTX_FLASH.ld} || true || NonSecure || || secure_nsclib.o || || None || || || " valueType="string"/>
<option id="com.st.stm32cube.ide.mcu.debug.option.cpuclock.1870764986" superClass="com.st.stm32cube.ide.mcu.debug.option.cpuclock" useByScannerDiscovery="false" value="170" valueType="string"/>
<option id="com.st.stm32cube.ide.mcu.debug.option.cpuclock.1870764986" superClass="com.st.stm32cube.ide.mcu.debug.option.cpuclock" useByScannerDiscovery="false" value="72" valueType="string"/>
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.targetplatform.211022890" isAbstract="false" osList="all" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.targetplatform"/>
<builder buildPath="${workspace_loc:/servo-p1-pwm-cpp}/Debug" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.builder.967683024" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.builder"/>
<tool id="com.st.stm32cube.ide.mcu.gnu.managedbuild.tool.assembler.1547703957" name="MCU/MPU GCC Assembler" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.tool.assembler">
Expand Down Expand Up @@ -121,7 +121,7 @@
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.floatabi.134971308" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.floatabi" useByScannerDiscovery="true" value="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.floatabi.value.hard" valueType="enumerated"/>
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_board.328811400" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_board" useByScannerDiscovery="false" value="NUCLEO-G431RB" valueType="string"/>
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.defaults.1805435785" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.defaults" useByScannerDiscovery="false" value="com.st.stm32cube.ide.common.services.build.inputs.revA.1.0.6 || Release || false || Executable || com.st.stm32cube.ide.mcu.gnu.managedbuild.option.toolchain.value.workspace || NUCLEO-G431RB || 0 || 0 || arm-none-eabi- || ${gnu_tools_for_stm32_compiler_path} || ../Core/Inc | ../Drivers/STM32G4xx_HAL_Driver/Inc | ../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy | ../Drivers/BSP/STM32G4xx_Nucleo | ../Drivers/CMSIS/Device/ST/STM32G4xx/Include | ../Drivers/CMSIS/Include || || || USE_NUCLEO_64 | USE_HAL_DRIVER | STM32G431xx || || Drivers | Core/Startup | Core || || || ${workspace_loc:/${ProjName}/STM32G431RBTX_FLASH.ld} || true || NonSecure || || secure_nsclib.o || || None || || || " valueType="string"/>
<option id="com.st.stm32cube.ide.mcu.debug.option.cpuclock.1847903541" superClass="com.st.stm32cube.ide.mcu.debug.option.cpuclock" useByScannerDiscovery="false" value="170" valueType="string"/>
<option id="com.st.stm32cube.ide.mcu.debug.option.cpuclock.1847903541" superClass="com.st.stm32cube.ide.mcu.debug.option.cpuclock" useByScannerDiscovery="false" value="72" valueType="string"/>
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.targetplatform.606840389" isAbstract="false" osList="all" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.targetplatform"/>
<builder buildPath="${workspace_loc:/servo-p1-pwm-cpp}/Release" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.builder.1506825396" managedBuildOn="true" name="Gnu Make Builder.Release" parallelBuildOn="true" parallelizationNumber="optimal" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.builder"/>
<tool id="com.st.stm32cube.ide.mcu.gnu.managedbuild.tool.assembler.1299820523" name="MCU/MPU GCC Assembler" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.tool.assembler">
Expand Down Expand Up @@ -212,4 +212,5 @@
<autodiscovery enabled="false" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
</storageModule>
<storageModule moduleId="refreshScope"/>
</cproject>
Loading

0 comments on commit 9b83ed4

Please sign in to comment.