Controlling PWM of LED through direct access of TIM2 timer's registers.

Dependencies:   mbed

main.cpp

Committer:
Ladon
Date:
2019-07-24
Revision:
0:12efa8652054
Child:
1:8d34cf217c0a

File content as of revision 0:12efa8652054:

#include <mbed.h>

#include <iostream>
// ^cout...
// -
#include <iomanip>
// ^setw...
// -

using namespace std;

// Board: NUCLEO-F303RE
// -
// Datasheet:
//      https://www.st.com/resource/en/datasheet/stm32f303re.pdf
// Reference Manual:
//      https://www.st.com/resource/en/reference_manual/dm00043574.pdf
// Programming Manual:
//      https://www.st.com/resource/en/programming_manual/dm00046982.pdf
// (Some address #definitions): 
//      https://raw.githubusercontent.com/ARMmbed/mbed-os/master/targets/TARGET_STM/TARGET_STM32F3/TARGET_STM32F303xE/device/stm32f303xe.h
// -

// Direct port access is shown in this example:
//      https://os.mbed.com/users/Ladon/code/Write_to_port_directly__LED/
// CPU hang discussion:
//      https://community.arm.com/developer/tools-software/tools/f/keil-forum/33581/cpu-crash-when-enable-nvic-for-timer/82797#82797
// Possible clock definition:
//      https://os.mbed.com/users/mbed_official/code/mbed-dev/file/f392fc9709a3/targets/TARGET_STM/TARGET_STM32F3/TARGET_STM32F303xE/TARGET_NUCLEO_F303RE/system_clock.c/
//      We 're probably interested in APB1.
// -

//
// * Let 's use a timer to blink the LED.
// * Let 's use TIM6 or TIM7 from the datasheet.
// * For TIM6 there is IRQ 54 (TIM6_DAC1), as seen in the Reference Manual.
// * We find the appropriate alias (TIM6_DAC1_IRQn) in the "#definitions" ^^file above.
// * We 'll use ^this and the TIM6_DAC1_IRQHandler one, with NVIC CMSIS instructions
//   like NVIC_EnableIRQ and NVIC_GetPendingIRQ, as seen in the ^^programming manual.
//
// * About the interrupt period:
//      * Assuming that the APB1 peripheral clock, which is responsible for TIM6,
//        runs at 36Mhz, we can limit the TIM6 to 36000 using the ARR (Auto Reload Register),
//        and downscale it by 1000. This leaves us with 1 full timer counter per second:)
//      * Truth is, input clock seems to be 72Mhz..
//      * About setting arbitrary period:
//          * Knowing that the counter clock (probably 72Mhz) gets divided by (1+TIM6_PSC) we can:
//              * Divide by 1000 (1 + 999) and
//              * Limit Counter to 72000 which is not possible in 16 bits, but 72000 = 36000 * 2 so
//                let 's divide by 2 * 1000 instead. and limit to 36000.
//            That leaves us with the filling period of 1sec. In order to chose any other period,
//            we imagine the following equality:
//                  72Mhz          36000 steps                 
//            ----------------  = ------------- ==> 2000 + X = 2000 * T ==> X = 2000 * (T - 1)
//             (1 + 1999 + X)        T secs                   
//            Thus, PSC (prescaler) is : 1999 + X = 1999 + 2000 * (T - 1), where T is in seconds.
// -
// From the Reference Manual:
//
// TIM6 base address:
//      0x4000 1000
// -
// TIM6_CR1: Control Register 1:
//      Address offset: 0x00.
//      16bits.
//      Bit0: CEN: Counter Enable.
//          Enabled when 1.
//      Bit2: URS: Update Request Source.
//          1: Only counter overflow/underflow generates an update interrupt.
//
// TIM6_DIER: DMA/ Interrupt Enable Register:
//      Address offset: 0x0C.
//      16bits.
//      Bit0: UIE: Update Interrupt Enable.
//          1: Enabled.
//          I 'm not sure what this does..
//      Bit8: UDE: Update DMA Request Enable.
//          1: Enabled.
//
// TIM6_SR: Status Register:
//      Address offset: 0x10.
//      16bits.
//      Bit0: UIF: Update Interrupt Flag:
//          ??
//
// TIM6_EGR: Event Generation Register:
//      Address offset: 0x14.
//      16bits (1bit) w/o
//      Bit0: UG: Update Generation:
//          When set to 1, re-initializes the timer counter and generates an 
//          update of the registers; it is automatically cleared by hardware.
//          Sounds like a reset button.
//
// TIM6_CNT: Counter:
//      Address offset: 0x24.
//      32bits wide.
//      Bits 15..0: CNT: Counter value.
//      Bit31: UIFCPY: A read-only copy of UIF (update interrupt flag) bit from TIM6_SR (status register).
//      Note: In order to read the current count, mask the lower 16 bits.
//
// TIM6_PSC: Prescaler:
//      Address offset: 0x28.
//      16bits.
//      The counter clock frequency CK_CNT is equal to f_{CK_PSC} / (PSC + 1).
//
// TIM6_ARR: Auto Reload Register:
//      Address Offset: 0x2C.
//      16bits (r/w).
//      Info: The upper limit for the counter.
//
// RCC_APB1ENR: APB1 Peripheral Clock Enable Register:
//      Base register: (?)RCC at 0x40021000.
//      Address Offset: 0x1C.
//      32bits.
//      Bit4: TIM6EN: Timer 6 clock Enable.
//          0: Disable.
//          1: Enable.
// -

// For TIM6 register:
// -
volatile unsigned short* cr1   = reinterpret_cast<volatile unsigned short*>(0x40001000U);
volatile unsigned short* dier  = reinterpret_cast<volatile unsigned short*>(0x4000100CU);
volatile unsigned short* sr    = reinterpret_cast<volatile unsigned short*>(0x40001010U);
volatile unsigned short* egr   = reinterpret_cast<volatile unsigned short*>(0x40001014U);
volatile unsigned int*   cnt   = reinterpret_cast<volatile unsigned int*>(  0x40001024U);
volatile unsigned short* psc   = reinterpret_cast<volatile unsigned short*>(0x40001028U);
volatile unsigned short* arr   = reinterpret_cast<volatile unsigned short*>(0x4000102CU);

// For RCC register:
// -
volatile unsigned int* apb1enr  = reinterpret_cast<unsigned int*>(0x40021000U + 0x1CU);
volatile unsigned int* apb1rstr = reinterpret_cast<unsigned int*>(0x40021000U + 0x10U);

// For GPIOA register:
// -
volatile unsigned int*   moder = reinterpret_cast<volatile unsigned int*>(  0x48000000U);
volatile unsigned short* odr   = reinterpret_cast<volatile unsigned short*>(0x48000014U);

// Timer functions:
// -

void inline enable_timer6 ()
{
//    *cr1     = 0x8D;
    *cr1 = 5;
}

/*
void inline disable_timer6 ()
{
    *cr1 |= 2;
}
*/

void inline enable_timer6_interrupt ()
{
    *dier    = 1;
}

void inline clear_timer6_status ()
{
    *sr = 0;
}

void inline reset_timer6 ()
{
    *egr = 1;
    clear_timer6_status();
}

void inline downscale_timer6_by (const unsigned short& v)
{
    *psc = v;
}

void inline downscale_timer6_max ()
{
    downscale_timer6_by(0xFFFF);
}

void inline limit_timer6_counter_to (const unsigned short& v)
{
    *arr = v;
}

void inline set_interrupt_period_to (const double& T)
{
    limit_timer6_counter_to(36000);
    downscale_timer6_by(1999 + (T - 1) * 2e3);
}

void inline set_interrupt_period_to_1sec ()
{
    set_interrupt_period_to(1);
}

// GPIOA functions:
// -

void init_LED ()
{
    unsigned int tmp = *moder;
    tmp |= 0x00000400U;
    tmp &= 0xFFFFF7FFU;
    // ^ Write 0,1 to bits 11,10.
    // -
    *moder = tmp;
}

void toggle_LED ()
{
    *odr ^= 0x20;
}

// Other functions:
// -

void inline enable_clock_for_timer6 ()
{
    *apb1enr |= 0x00000010U;
}

// NVIC functions:
// -

extern "C" void TIM6_DAC1_IRQHandler (void)
{
    clear_timer6_status();
    toggle_LED();
}

inline void enable_interrupt ()
{
    NVIC_EnableIRQ(TIM6_DAC1_IRQn);
}

//
// -
//

int main ()
{
    cout << "Entered main()." << endl;
    init_LED();
    enable_interrupt();
    enable_clock_for_timer6();
    // ^ I think we need clock (?APB1?) running in order to setup the timer.
    // -
    set_interrupt_period_to(0.5);
    // ^ LED stays open for 0.5 and closed for 0.5 with a total of 1 sec.
    // -
    enable_timer6_interrupt();
    enable_timer6();
    cout << "Exiting main().." << endl;
    cout << endl;
}