Solution

Solution to the assignment.

servo.c

/*!
 * \brief Initializes the servo pins
 *
 * Resources:
 * - SERVO_PWM | P3_10 | CT1_MAT0
 *
 * Configures CTimer1 to generate a PWM signal on MAT0 with a 50 Hz frequency.
 */
void servo_init(void)
{
    // From section 26.1.5 Initialization (NXP, 2024)
    //
    // 1. Select a clock source for the CTIMER using MRCC_CTIMER0_CLKSEL,
    //    MRCC_CTIMER1_CLKSEL, and MRCC_CTIMER2_CLKSEL registers.
    // 2. Enable the clock to the CTIMER via the
    //    CTIMERGLOBALSTARTEN[CTIMER0_CLK_EN],
    //    CTIMERGLOBALSTARTEN[CTIMER1_CLK_EN], and
    //    CTIMERGLOBALSTARTEN[CTIMER2_CLK_EN] fields. This enables the register
    //    interface and the peripheral function clock.
    // 3. Clear the CTIMER peripheral reset using the MRCC_GLB_RST0 registers.
    // 4. Each CTIMER provides interrupts to the NVIC. See MCR and CCR registers
    //    in the CTIMER register section for match and capture events. For
    //    interrupt connections, see the attached NVIC spreadsheet.
    // 5. Select timer pins and pin modes as needed through the relevant PORT
    //    registers.
    // 6. The CTIMER DMA request lines are connected to the DMA trigger inputs
    //    via the DMAC0_ITRIG_INMUX registers (See Memory map and register
    //    definition). Note that timer DMA request outputs are connected to DMA
    //    trigger inputs.

    // 1.
    //
    // MUX: [101] = CLK_1M
    MRCC0->MRCC_CTIMER1_CLKSEL = MRCC_MRCC_CTIMER1_CLKSEL_MUX(0b101);

    // HALT: [0] = Divider clock is running
    // RESET: [0] = Divider isn't reset
    // DIV: [0000] = divider value = (DIV+1) = 1
    MRCC0->MRCC_CTIMER1_CLKDIV = 0;

    // 2.
    //
    // CTIMER1_CLK_EN: [1] = CTIMER 1 function clock enabled
    SYSCON->CTIMERGLOBALSTARTEN |= SYSCON_CTIMERGLOBALSTARTEN_CTIMER1_CLK_EN(1);

    // 3.
    //
    // Enable modules and leave others unchanged
    // CTIMER1: [1] = Peripheral clock is enabled
    MRCC0->MRCC_GLB_CC0_SET = MRCC_MRCC_GLB_CC0_CTIMER1(1);

    // Release modules from reset and leave others unchanged
    // CTIMER1: [1] = Peripheral is released from reset
    MRCC0->MRCC_GLB_RST0_SET = MRCC_MRCC_GLB_RST0_CTIMER1(1);

    // 4.
    //
    // Not used

    // 5.
    //
    // Specifies the prescale value. 1 MHz / 1 = 1 MHz
    CTIMER1->PR = 0;

    // Match value: 1 MHz / 20000 = 50 Hz
    //
    // In PWM mode, use match channel 3 to set the PWM cycle length. The other
    // channels can be used for matches
    CTIMER1->MR[0] = 20000 - 1500;
    CTIMER1->MR[3] = 20000-1;

    // MR0S: [0] = Does not stop Timer Counter (TC) if MR0 matches Timer Counter
    //             (TC)
    // MR0R: [0] = Resets Timer Counter (TC) if MR0 matches its value.
    // MR0I: [0] = No interrupt when MR0 matches the value in Timer
    //             Counter (TC).
    // MR3S: [0] = Does not stop Timer Counter (TC) if MR3 matches Timer Counter
    //             (TC)
    // MR3R: [1] = Resets Timer Counter (TC) if MR3 matches its value.
    // MR3I: [0] = No interrupt when MR3 matches the value in Timer
    //             Counter (TC).
    CTIMER1->MCR |= CTIMER_MCR_MR3R(1);

    // Configure match outputs as PWM outputs.
    CTIMER1->PWMC |= CTIMER_PWMC_PWMEN3(1) | CTIMER_PWMC_PWMEN0(1);

    // Enable modules and leave others unchanged
    // PORT3: [1] = Peripheral clock is enabled
    MRCC0->MRCC_GLB_CC1_SET = MRCC_MRCC_GLB_CC1_PORT3(1);

    // Release modules from reset and leave others unchanged
    // PORT3: [1] = Peripheral is released from reset
    MRCC0->MRCC_GLB_RST1_SET = MRCC_MRCC_GLB_RST1_PORT3(1);

    // Configure P3_10
    // LK : [1] = Locks this PCR
    // INV: [0] = Does not invert
    // IBE: [0] = Disables
    // MUX: [0100] = Alternative 4 (CT1_MAT0)
    // DSE: [0] = low drive strength is configured on the corresponding pin,
    //            if the pin is configured as a digital output
    // ODE: [0] = Disables
    // SRE: [0] = Fast
    // PE:  [0] = Disables
    // PS:  [0] = n.a.
    PORT3->PCR[10] = PORT_PCR_LK(1) | PORT_PCR_MUX(4);

    // 6.
    //
    // Not used.

    // CEN: [1] = Enables the counters.
    CTIMER1->TCR |= CTIMER_TCR_CEN(1);

    // Configuration summary
    //
    // CLK_1M = 1 MHz
    // ctimer1_prescaler = 1
    // ctimer1_count_frequency = 1 MHz / 1 = 1 MHz
    // ctimer1_modulo_value = ctimer1_match3 = 20000 - 1
    // ctimer1_pwm_frequency = ctimer1_count_frequency / (ctimer1_match3 + 1)
    //                       = 1 MHz / 20000
    //                       = 50 Hz
    // ctimer1_duty_cycle: from 0 to ctimer1_match3

    // CTIMER1
    //
    // MAT3 (20000-1) - - - - - - - - - - - - - -+- - - - - - - - - - - -+ -
    //                                          /|                      /|
    //                                         / |                     / |
    //                                        /  |                    /  |
    //                                       /   |                   /   |
    //                                      /    |                  /    |
    // MAT0 (20000-1500) - - - - - - - - - + - - | - - - - - - - - + - - | -
    //                                    /.     |                /.     |
    //                                   / .     |               / .     |
    //                                  /  .     |              /  .     |
    //                                 /   .     |             /   .     |
    //                                /    .     |            /    .     |
    //                               /     .     |           /     .     |
    //                              /      .     |          /      .     |
    //                             /       .     |         /       .     |
    //                            /        .     |        /        .     |
    //                           /         .     |       /         .     |
    //                          /          .     |      /          .     |
    //                         /           .     |     /           .     |
    //                        /            .     |    /            .     |
    //                       /             .     |   /             .     |
    //                      /              .     |  /              .     |
    //                     /               .     | /               .     |
    //                    /                .     |/                .     |/
    // 0 ----------------+-----------------------+-----------------------+--
    //                 __.                 ._____.                 ._____.
    // CT1_MAT0        //|_________________|/////|_________________|/////|__
    //                   .                 .     .                 .     .
    //                   |<--------------->|<--->|<--------------->|<--->|
    //                         18.5ms       1.5ms      18.5ms       1.5ms
}

/*!
 * \brief Set the servo duty cycle
 *
 * Sets the PWM duty cycle as follows:
 * - 1000 =   0% = 1.0 ms pulse width = servo moves left
 * - 1500 =  50% = 1.5 ms pulse width = servo moves centre
 * - 2000 = 100% = 2.0 ms pulse width = servo moves right
 *
 * \param[in]  value Duty cycle of the PWM signal
 */
void servo_set(int32_t value)
{
    // Check range
    value = value < 1000 ? 1000 : value;
    value = value > 2000 ? 2000 : value;

    // Set new match value
    CTIMER1->MR[0] = 20000 - value;
}