This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Generating chirp/general waveforms on CC2650STK speaker/buzzer

Other Parts Discussed in Thread: CC2650STK, CC2650

Hello experts,

I'm trying to use the buzzer of the SensorTag to send some broadband signal in the ultrasonic range. My initial tests with the PWM have shown the onboard buzzer emits in that frequency range.

My task would be to send a signal that is precisely the same every time it is sent, without random influences due to timing differences from the hardware or other interrupts. It should also be possible to have the signal completely under control, without glitches and clicking that can be heard at lower frequencies.

So far, I have two ideas:

1) use PWM from the timer/counter peripheral and use DMA to reproducibly change the duty cycle and/or period.

2) use the Sensor Controller Studio (SCS) and just have a tight loop running on the Sensor Controller, manually generating the waveform.

What route do you suggest? Is there a better option that I've missed?

Cheers,

Flogo

  • Hi,

    Can you provide a bit more information? What frequency do you want to implement for your PWM signal? Do you need to change the frequency/duty cycle on the fly?

    As for the current version of SCS, you can't change the duty cycle during run time and there will be glitch if HF clk source changes between 48MHz RCOSCHF and 24MHz xtal during operation.
  • Hi Chrstin,

    thanks for your quick reply.

    I would like to send frequencies in the 16-22 kHz range. 

    It could be a frequency sweep in that range, so the easiest way to do that would be if the frequency of the PWM could be adjusted on the fly. Duty cycle should also be adjustable on the fly to fade the signal in, avoiding clicks in the audible range.

    I don't need the Sensor Controller to save energy (the M3 core has to keep running for some other tasks) but I thought it could be easier to use than configuring PWM and DMA peripherals.

    Couldn't I program various nested loops in SCS where the range of a counter determines the frequency (using a delay loop)? I had assumed I can trigger that task on the SC once and then it just runs until I'm done emitting the signal.

    Cheers,

    Florian

  • We have a pre-release PWM driver which can be found here:
    e2e.ti.com/.../474000

    It comes with some examples, which also shows you how to change the duty cycle on the fly.

    You can try to use scs, but I don 't think it's gonna be easier than using the pre-release PWM driver.
  • Thank you Christin,

    I'm already using the prerelease PWM driver. Changing the period or duty cycle on the fly is working but produces an audible glitch.

    Regarding SCS, I have installed the software but there were no examples for the CC2650STK.

    Could you walk me through what it takes to change the LED blinking example that comes with SCS to work on the SensorTag?

    Florian

     

  • LED blinker can't be run on CC2650STK by only modifying the software, you will have to modify the HW. The IOs used to control LED1 and LED2 on STK are DIO15 and DIO 10, which are not not AUX IO.
  • Thanks for your quick reply. Can I drive the buzzer on the SensorTag from the SC?
  • You can take a look at our TRM section 11.8 I/O Pin Mapping, which lists which DIOs can be used in sensor controller.

    TRM can be downloaded here :

    in STK schematic, buzzer is mapped to DIO21, which is not AUX IO too.

  • Thank you Christin!

    So how would the other route work? Is it possible to setup the DMA controller that it loads values for period and duty and after the PWM counter has run down to 0 that those get reloaded with the next pair of values?
  • According to my understand of CC26xx Timer/PWM, it is glitch-free design. So, you should be able to do it without problem. But since you are using pre-leased PWM driver, I don't know how the registers are controlled inside the driver. So, I would suggest you to control the registers by yourself.

    The timer/PWM load period and match registers can be set any time and updated (loaded) when time-out or next-cycle. Please check TRM page 1094 for register TAMR.TAMRSU and page 1109 for register TAMATCHR. It is helpful for you to read the whole chapter.

    Btw, you don't need SCS or DMA at all.
  • Thank you Rcfocus,

    I have followed your suggestion and now I'm controlling the registers directly. To get there, I went through the driver to find out what it does exactly and extracted the relevant parts. See below for my code. It does a simple down/up frequency sweep with output on the buzzer of the CC2650 SensorTag. The TAMRSU is set using the GPT_TAMR_TAMRSU_CYCLEUPDATE value.

    I still hear the glitches, even if I write always the same value to the GPT_O_TAILR register (see the commented line at the end of the while loop). When I completely comment out the writing to the GPT_O_TAILR register in the loop, I get a clear, glitch-free sound.

    It's pretty clear that writing the new period immediately causes an action, I assume it resets the counter value to its start value.

    Do you (or anybody else) still have further ideas how to solve this problem?

    Cheers,

    Flogo

    #include <ti/sysbios/BIOS.h>
    #include <ti/sysbios/knl/Clock.h>
    #include <ti/sysbios/knl/Task.h>
    #include <xdc/runtime/Log.h>
    
    #include <ti/drivers/PIN.h>
    #include <ti/drivers/PWM2.h>
    //#include <ti/drivers/PWM.h>
    
    #include <inc/hw_gpt.h>
    #include <driverlib/timer.h>
    
    
    #include "Board.h"
    
    
    //struct GPTimerCC26XX_Config
    //{
    //    GPTimerCC26XX_Object        *object;
    //    const GPTimerCC26XX_HWAttrs *hwAttrs;
    //    GPTimerCC26XX_Part          timerPart;
    //};
    //
    ///* GPTimer handle is pointer to configuration structure */
    //typedef GPTimerCC26XX_Config *              GPTimerCC26XX_Handle;
    
    /*!
     *  @brief
     *  Definitions for controlling timer debug stall mode
     */
    typedef enum GPTimerCC26XX_DebugMode
    {
        GPTimerCC26XX_DEBUG_STALL_OFF = 0,
        GPTimerCC26XX_DEBUG_STALL_ON,
    } GPTimerCC26XX_DebugMode;
    
    
    /*!
     *  @brief
     *  Definitions for input / output ports in IO controller to connect GPTimer
     *  to a pin. Used in gptimerCC26xxHWAttrs for static timer configuration
     *  PIN driver is used to mux a pin to the timer.
     *  @sa  PINCC26XX_setMux
     *  @sa  GPTimerCC26XX_getPinMux
     */
    typedef enum GPTimerCC26XX_PinMux
    {
        GPT_PIN_0A = IOC_PORT_MCU_PORT_EVENT0,
        GPT_PIN_0B = IOC_PORT_MCU_PORT_EVENT1,
        GPT_PIN_1A = IOC_PORT_MCU_PORT_EVENT2,
        GPT_PIN_1B = IOC_PORT_MCU_PORT_EVENT3,
        GPT_PIN_2A = IOC_PORT_MCU_PORT_EVENT4,
        GPT_PIN_2B = IOC_PORT_MCU_PORT_EVENT5,
        GPT_PIN_3A = IOC_PORT_MCU_PORT_EVENT6,
        GPT_PIN_3B = IOC_PORT_MCU_PORT_EVENT7,
    } GPTimerCC26XX_PinMux;
    
    
    #define PowerCC26XX_SB_DISALLOW             2
    
    
    // Task data
    Task_Struct pwmTask;
    Char pwmTaskStack[512];
    
    PWM_Handle hPWM;
    
    //#define PERIOD_US 1000
    #define PERIOD_US 500
    
    #define TIME_FACTOR 	48
    
    #define COUNTER_MAX (600 * TIME_FACTOR)
    #define COUNTER_MIN (300 * TIME_FACTOR)
    
    void taskFxn(UArg a0, UArg a1) {
    
      int baseAddr = 0x40010000;
      int offset = 0;
    
    //  PWM_Params pwmParams;
    //  PWM_Params_init(&pwmParams);
    //  pwmParams.idleLevel  = PWM_IDLE_LOW;
    //
    //  /* PWM in microseconds with period in microseconds */
    //
      uint32_t period = COUNTER_MAX;
    
    //  pwmParams.periodUnit  = PWM_PERIOD_COUNTS;
    //  pwmParams.periodValue = PERIOD_US * TIME_FACTOR;
    //  pwmParams.dutyUnit    = PWM_DUTY_COUNTS;
    //  pwmParams.dutyValue   = dutyValue * TIME_FACTOR;
    
    //  /* PWM open should will set to pin to idle level  */
    //  hPWM = PWM_open(CC2650_PWM0, &pwmParams);
    //
    //  if(hPWM == NULL) {
    //    Log_error0("Opening PWM failed");
    //    while(1);
    //  }
    
      int powerMngrId = 0;
    
      Power_setDependency(powerMngrId);
    
      int GPT_MODE_PWM_down = GPT_TAMR_TAMR_PERIODIC       | GPT_TAMR_TACDIR_DOWN |
                               GPT_TAMR_TAAMS_PWM          | GPT_TAMR_TACM_EDGCNT |
                               GPT_TAMR_TAPLO_CCP_ON_TO    | GPT_TAMR_TAPWMIE_EN  |
                               GPT_TAMR_TAMRSU_CYCLEUPDATE;
    
      HWREG(baseAddr + offset + GPT_O_CFG) = GPT_CFG_CFG_16BIT_TIMER;
      HWREG(baseAddr + offset + GPT_O_TAMR) = GPT_MODE_PWM_down;
    
      TimerStallControl(baseAddr, 0x000000FF, GPTimerCC26XX_DEBUG_STALL_OFF);
    
      PIN_Handle hPins = NULL;
      PIN_State  pinState;
    
      int pinConfig = PIN_TERMINATE;
      hPins     = PIN_open(&pinState, &pinConfig);
    
      uint32_t idleLevel = PIN_GPIO_HIGH;
    
      /* Generate pin config for PWM pin.
       *  Output is inverted to make PWM duty calculations independent of period
       */
      int pwmPin = Board_BUZZER;
      pinConfig = pwmPin | PIN_INPUT_DIS | PIN_GPIO_OUTPUT_EN | idleLevel |
                  PIN_INV_INOUT | PIN_PUSHPULL | PIN_DRVSTR_MAX;
    
      /* Fail if cannot add pin */
      if (PIN_add(hPins, pinConfig) != PIN_SUCCESS)
      {
          //Log_error2("PWM_open(%x): PIN (%d) already in use.", handle, hwAttrs->pwmPin);
          return;
      }
    
     // PWM_start(hPWM);
    
      GPTimerCC26XX_PinMux pinMux = GPT_PIN_0A;
      PINCC26XX_setMux(hPins, pwmPin, pinMux);
    
      HWREG(baseAddr + offset + GPT_O_TAILR) = period;
      HWREG(baseAddr + offset + GPT_O_TAMATCHR) = 20 * TIME_FACTOR;
    
      /* Enable timer */
      uint32_t ui32Timer = 255;
      HWREG(baseAddr + GPT_O_CTL) |= ui32Timer & (GPT_CTL_TAEN | GPT_CTL_TBEN);
      Power_setConstraint(PowerCC26XX_SB_DISALLOW);
    
    
      bool direction = 1; /* Initially increase duty */
    
      int counter = COUNTER_MIN;
    
      while (1) {
    
        /* Sleep */
        //Task_sleep(40 * PERIOD_US / Clock_tickPeriod);
    
    	int c;
    	for (c = 0; c < 100000; c++);
    
        /* Change duty cycle with 1% of period */
        if(direction)
        {
        	counter += 30;
        }
        else {
        	counter -= 30;
        }
    
        if(counter >= COUNTER_MAX)
        {
          counter = COUNTER_MAX;
          direction = 0;
        }
        else if(counter <= COUNTER_MIN) {
          counter = COUNTER_MIN;
          direction = 1;
        }
    
        period = counter;
    
        //PWM_setPeriod(hPWM, period);
    
        HWREG(baseAddr + offset + GPT_O_TAILR) = period;
        //HWREG(baseAddr + offset + GPT_O_TAILR) = 100000;
    
      }
    
    }
    
    
    PIN_Config pinConfig[] =  { PIN_ID(Board_LED1) | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    		PIN_ID(Board_LED2) | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    		PIN_ID(Board_BUZZER) | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    		PIN_TERMINATE };
    
    
    int main(void) {
    
      PIN_init(pinConfig);
    
      Task_Params taskParams;
    
      /* Configure the OS task */
      Task_Params_init(&taskParams);
      taskParams.stack = pwmTaskStack;
      taskParams.stackSize = sizeof(pwmTaskStack);
      taskParams.priority = 1;
      Task_construct(&pwmTask, taskFxn, &taskParams, NULL);
    
      /* Start TI-RTOS */
      BIOS_start();
    
      return 0;
    
    }

  • Hi Flogo,

    "Update on the next cycle" means the next timer counter cycle. What you need is to update when "next time-out", i.e. GPT_TAMR_TAMRSU_TOUPDATE. If you look at the definition of GPT_TAMR_TAMRSU_CYCLEUPDATE, it is actually defined as 0.

    Please try it and let me know if it can work.
  • Thank you, Robert!

    Is the time out when the counter and the match register have the same value or is it when the counter reaches zero (for down mode) or the max value (for up counting mode)?

    Cheers,

    Flogo

  • "time-out" means the timer counts to 0 if it is configured to count-down.

    There are three registers needs to be updated(reloaded) at time-out event. They are the interval load register, pre-scale register and match register. I just read the TRM again. It seems only pre-scale and match registers have effect when GPT_TAMR_TAMRSU_TOUPDATE is set. So, I think you will still get glitch when changing frequency i.e. "interval load register". But you won't get glitch when changing duty i.e. match register.

    Well, now the problem is to prevent update a smaller value to the interval load register (TxILR). Because it will cause an unexpected PWM output immediately. I think you can check-and-wait for the counter value to be smaller then the new interval value. Something like

    while(TAR > new_TAILR); // Wait until TAR is smaller than the new interval
    TAILR = new_TAILR;

    But if TAR is already very small, e.g. 2, then it will be reloaded from TAILR before TAILR is updated. So, we have to prevent it.

    while(TAR < 20);        // Prevent a too small value in TAR
    while(TAR > new_TAILR); // Wait until TAR is smaller than the new interval
    TAILR = new_TAILR;

    Please test to see if it can work.

  • I just found the TAR can be configured to load from TAILR when "time-out" by setting TAMR.TAILD bit. So, the above workaround is not necessary. You just need to set TAILD and TAMRSU bits of TAMR.

    Then, I don't why there is a NOTE in TRM page 1085:

    NOTE: Note that altering TnILR to a value smaller than the current counter value, may introduce transients on the PWM output even when the “Time Out UPDATE” mode is enabled.

  • Thanks Robert,

    I have the feeling I'm getting closer to a solution.

    Now I'm trying to get interrupts working to load a new duty value on timeout. Again, I've switched back to the prerelease driver to find out how it can be done.

    There is an interrupt mask that is configured when the interrupts are setup/enabled.

    I've found out that when I set the CAEMIS bit (see below) I get periodic interrupts at the correct rate.

    However, changing the duty value in the interrupt handler produces unexpected results. I assume some glitches are introduced but I don't know exactly because I don't have an oscilloscope here.

    I've also asked in the prerelease driver thread here:

    Do you have any further tips for me?

    Cheers,

    Flogo

    // Field:     [4] TAMMIS
    //
    // 0: No interrupt or interrupt not enabled
    // 1: RIS.TAMRIS = 1 && IMR.TAMIM = 1
    #define GPT_MIS_TAMMIS                                              0x00000010
    #define GPT_MIS_TAMMIS_BITN                                                  4
    #define GPT_MIS_TAMMIS_M                                            0x00000010
    #define GPT_MIS_TAMMIS_S                                                     4
    
    // Field:     [2] CAEMIS
    //
    // 0: No interrupt or interrupt not enabled
    // 1: RIS.CAERIS = 1 && IMR.CAEIM = 1
    #define GPT_MIS_CAEMIS                                              0x00000004
    #define GPT_MIS_CAEMIS_BITN                                                  2
    #define GPT_MIS_CAEMIS_M                                            0x00000004
    #define GPT_MIS_CAEMIS_S                                                     2
    
    // Field:     [1] CAMMIS
    //
    // 0: No interrupt or interrupt not enabled
    // 1: RIS.CAMRIS = 1 && IMR.CAMIM = 1
    #define GPT_MIS_CAMMIS                                              0x00000002
    #define GPT_MIS_CAMMIS_BITN                                                  1
    #define GPT_MIS_CAMMIS_M                                            0x00000002
    #define GPT_MIS_CAMMIS_S                                                     1
    
    // Field:     [0] TATOMIS
    //
    // 0: No interrupt or interrupt not enabled
    // 1: RIS.TATORIS = 1 && IMR.TATOIM = 1
    #define GPT_MIS_TATOMIS                                             0x00000001
    #define GPT_MIS_TATOMIS_BITN                                                 0
    #define GPT_MIS_TATOMIS_M                                           0x00000001
    #define GPT_MIS_TATOMIS_S                                                    0
    

  • Before using pre-released driver, you should make sure that there is no more glitch after setting TAILD and TAMRSU.

    CAEMIS is used for "Capture Mode', why do you need to use it? What you need is to output PWM signal, not to capture PWM input.

    Btw, I don't think you need to change duty inside ISR. You can change it in a task.
  • I just listed the possible options of the interrupt flags that I saw the driver can potentially set in the register.

    Do you know which of those flags would be the correct one for me to get an interrupt when I have a time out?

    I assume I could also change the duty in a task, but getting the timing correct would be difficult. I have the goal to have a simple PCM driver sending 10 bit samples to the buzzer. It is very important that I put out the next sample always at the right time, otherwise I will get distortion. 

  • Suggest you to study the TRM Chapter 13 Timers at first. The RIS register on pages 1103 and 1104 has detailed explanation about flags. Page 1084 explains the operation of PWM mode, from there you will known what registers will be used.
  • I'm also confused about the note, so I'll keep it in my when I get strange effects too.

    Thanks so much for your time and effort, this has solved my problem

  • Thanks Robert, now I got it working: changing period and duty in the PWM interrupt.

    Do you develop for this platform professionally or is it just a hobby?
  • You are welcome. It is really great that you finally make it work.

    So far, I use CC26xx for customers' projects. But it is also my hobby because I love both software programming and circuit design. I usually create the software framework based on my RTOS, not TI-RTOS. This is why I have to study all the low-level registers.