-
Notifications
You must be signed in to change notification settings - Fork 6
Nucleo Servo Starter Project
This project will get you familiar with more features of a Nucleo, namely the timer. It will also help you get used to using git. By the end of the project you should have an understanding of timers and PWM (pulse width modulation) signals.
Just as a reminder, if you have any questions, feel free to reach out to any of the ESW leads or members. This project isn't meant to be high-stakes, so please reach out if you ever get stuck!
If you haven't already, please complete the LED Project and show it to the ESW lead. Please also make sure you have git set up, if not, refer to this wiki page.
As mentioned earlier, by the end of the project you should be able to drive a servo. This project will also teach some coding practices used for STM32 code. While you are working through the project, keep the following in mind:
- How could you decrease the amount of space or processing power needed for your code?
- How could you document your code so that others can easily read and understand it?
- How could you write your code so that it can easily be adjusted for different pins, different number of servos, etc.?
Since you already have practice creating a project, you will only need to clone a premade Cube project for this project.
To clone the project, go to the "Code" tab and copy the SSH URL.
In your terminal, make a new branch off of the starter-project branch by pasting the following lines:
$ git clone git@github.com:umrover/embedded-testbench.git
$ cd embedded-testbench
$ git checkout starter-projects
$ git checkout -b starter/<name>
Open the Cube project file in the Servo folder (should be called .project), this will take a while to load, if something weird happens please ask for help.
Once the .ioc is open, configure the pins for PWM. Select the PC0 pin and change it to TIM1_CH1. On the left side of the .ioc file, under Timers, select Tim1. Changed Channel1 from “Disable” to “PWM Generation CH1”.
Under Config (right below Mode), you can edit the prescaler and counter period, which are used to specify the PWM signal parameters.
I do not recommend copying the PSC and CCR shown in the picture, it'll make your math really hard later on
A timer is used for counting, which works well for producing PWM signals. PWM signal is a digital signal that is set to high and low for a set amount of time. Devices that are controlled by PWM use the fraction that the signal is high for to set something (eg. velocity, position, brightness).
There are 2 parameters that control what type of pulses the output pin can produce, the prescaler (PSC) and the auto-reload register (ARR):
- PSC: changes the frequency of the clock by dividing it, which adjusts how fast or slow the cycles are. For example, if a clock has a frequency of 8MHz, a PSC of 7 would make the frequency 1 MHz ( Clk/(PSC+1) ).
- ARR: defines the number of ticks in one cycle, which adjusts how fine the PWM signal can be. For example, if the same clock as the previous example, with the same PSC (so 1MHz) is used with an ARR of 49, then each tick would be 20ns long ( 1ms/(ARR+1) ) and the width of each pulse can increment by 20ns.
If you are still confused, here's a helpful slide deck that goes more in depth.
Read the datasheet for the servo and determine what PSC and ARR should be given that the clock frequency of the Nucleo is 72MHz.
Once you have determined and edited the timer config, save the file and generate code. Note: you can always come back to the .ioc to make changes.
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 object and function prototypes in a header file.
On the menu to the left, in Core > Inc, create a new header file named servo.h
Near the top of the header file, copy in the following lines:
#pragma once
#include "stm32f3xx_hal.h"
#include <stdlib.h>
Then, create a Servo struct with 3 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
To avoid getting errors when using your Servo struct later on, make sure to declare it like this:
typedef struct {
// TODO: fill this in
} Servo;
Create the function prototypes for the 3 functions that are needed to create and use a Servo object:
new_servo
initialize_servo
set_servo_angle
Try to fill out the header on your own, but here are some hints if you get stuck:
-
new_servo
should take in parameters and return a Servo* -
initialize_servo
should initialize an existing Servo* to have a certain starting angle, it should also start the timer -
set_servo_angle
should convert an angle to the number of ticks for the CCR
Now that you have a Servo struct and function prototypes, it's time to implement the functions.
On the menu to the left, in Core > Src, create a new .c file named servo.c
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:
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
When implementing set_servo_angle, keep in mind what PWM signal corresponds to what angle. Check back on the servo datasheet to determine this.
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.
Go to main.c and make sure to #include servo.h in the /* USER CODE BEGIN Includes */ section
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.
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).
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, you can test your code on a servo. If you are unsure, just ask for help!
The simplest method for debugging a digital signal is often to use a logic analyzer. Please install Logic on your laptop.
To use Logic, connect PC0 to one of the pins of the logic analyzer and make sure the logic analyzer is grounded to the Nucleo. Then, flash the code and press the play button. If you wrote code in the main while loop to change the servo angle a few times (which you should), you'll notice the signal in Logic changing. Play around with Logic:
- Zoom in and out
- Pause the display
- Mouse over the different parts of the signal
- Try to understand what all the different numbers mean
In order to properly wire the servo, first consult the datasheet.
- How much voltage does the servo need?
- Which wires are signal, power, and ground?
In order for the signal to work, the servo and the Nucleo must have a common ground. Connect the servo's ground wire to one of the ground pins on the Nucleo, the power to the appropriate power pin on the Nucleo, and the signal wire to PC0.
If you have any questions, don't be afraid to reach out to the ESW lead, SAM(s), or Embedded lead.