Blame view

RIOT/cpu/samd21/periph/pwm.c 4.91 KB
a752c7ab   elopes   add first test an...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
  /*
   * Copyright (C) 2014 Hamburg University of Applied Sciences
   *               2015 Freie Universitรคt Berlin
   *
   * This file is subject to the terms and conditions of the GNU Lesser General
   * Public License v2.1. See the file LICENSE in the top level directory for more
   * details.
   */
  
  /**
   * @ingroup     cpu_samd21
   * @ingroup     drivers_periph_pwm
   * @{
   *
   * @file
   * @brief       Low-level PWM driver implementation
   *
   * @author      Peter Kietzmann <peter.kietzmann@haw-hamburg.de>
   * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
   *
   * @}
   */
  
  #include <stdint.h>
  #include <string.h>
  
  #include "log.h"
  #include "cpu.h"
  #include "board.h"
  #include "periph/gpio.h"
  #include "periph/pwm.h"
  
  /* guard file in case no PWM device was specified */
  #ifdef PWM_NUMOF
  
  static inline int _num(pwm_t dev)
  {
      return ((int)(pwm_config[dev].dev) & 0xc00) >> 10;
  }
  
  static inline Tcc *_tcc(pwm_t dev)
  {
      return pwm_config[dev].dev;
  }
  
  static inline uint8_t _chan(pwm_t dev, int chan)
  {
      return pwm_config[dev].chan[chan].chan;
  }
  
  static int _clk_id(pwm_t dev)
  {
      if (_num(dev) == 2) {
          return TCC2_GCLK_ID;
      }
      return TCC0_GCLK_ID;
  }
  
  static uint8_t get_prescaler(unsigned int target, int *scale)
  {
      if (target == 0) {
          return 0xff;
      }
  
      if (target >= 512) {
          *scale = 1024;
          return TCC_CTRLA_PRESCALER_DIV1024_Val;
      }
      if (target >= 128) {
          *scale = 256;
          return TCC_CTRLA_PRESCALER_DIV256_Val;
      }
      if (target >= 32) {
          *scale = 64;
          return TCC_CTRLA_PRESCALER_DIV64_Val;
      }
      if (target >= 12) {
          *scale = 16;
          return TCC_CTRLA_PRESCALER_DIV16_Val;
      }
      if (target >= 6) {
          *scale = 8;
          return TCC_CTRLA_PRESCALER_DIV8_Val;
      }
      if (target >= 3) {
          *scale = 4;
          return TCC_CTRLA_PRESCALER_DIV4_Val;
      }
      *scale = target;
      return target - 1;
  }
  
  static void poweron(pwm_t dev)
  {
      PM->APBCMASK.reg |= (PM_APBCMASK_TCC0 << _num(dev));
      GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN |
                           GCLK_CLKCTRL_GEN_GCLK0 |
                           GCLK_CLKCTRL_ID(_clk_id(dev)));
      while (GCLK->STATUS.bit.SYNCBUSY) {}
  }
  
  uint32_t pwm_init(pwm_t dev, pwm_mode_t mode, uint32_t freq, uint16_t res)
  {
      uint8_t prescaler;
      int scale = 1;
      uint32_t f_real;
  
      if ((unsigned int)dev >= PWM_NUMOF) {
          return 0;
      }
  
      /* calculate the closest possible clock presacler */
      prescaler = get_prescaler(CLOCK_CORECLOCK / (freq * res), &scale);
      if (prescaler == 0xff) {
          return 0;
      }
      f_real = (CLOCK_CORECLOCK / (scale * res));
  
      /* configure the used pins */
      for (int i = 0; i < PWM_MAX_CHANNELS; i++) {
          if (pwm_config[dev].chan[i].pin != GPIO_UNDEF) {
              gpio_init(pwm_config[dev].chan[i].pin, GPIO_OUT);
              gpio_init_mux(pwm_config[dev].chan[i].pin, pwm_config[dev].chan[i].mux);
          }
      }
  
      /* power on the device */
      poweron(dev);
  
      /* reset TCC module */
      _tcc(dev)->CTRLA.reg = TCC_CTRLA_SWRST;
      while (_tcc(dev)->SYNCBUSY.reg & TCC_SYNCBUSY_SWRST) {}
      /* set PWM mode */
      switch (mode) {
          case PWM_LEFT:
              _tcc(dev)->CTRLBCLR.reg = TCC_CTRLBCLR_DIR;     /* count up */
              break;
          case PWM_RIGHT:
              _tcc(dev)->CTRLBSET.reg = TCC_CTRLBSET_DIR;     /* count down */
              break;
          case PWM_CENTER:        /* currently not supported */
          default:
              return 0;
      }
      while (_tcc(dev)->SYNCBUSY.reg & TCC_SYNCBUSY_CTRLB) {}
  
      /* configure the TCC device */
      _tcc(dev)->CTRLA.reg = (TCC_CTRLA_PRESCSYNC_GCLK_Val
                              | TCC_CTRLA_PRESCALER(prescaler));
      /* select the waveform generation mode -> normal PWM */
      _tcc(dev)->WAVE.reg = (TCC_WAVE_WAVEGEN_NPWM);
      while (_tcc(dev)->SYNCBUSY.reg & TCC_SYNCBUSY_WAVE) {}
      /* set the selected period */
      _tcc(dev)->PER.reg = (res - 1);
      while (_tcc(dev)->SYNCBUSY.reg & TCC_SYNCBUSY_PER) {}
      /* start PWM operation */
      _tcc(dev)->CTRLA.reg |= (TCC_CTRLA_ENABLE);
      /* return the actual frequency the PWM is running at */
      return f_real;
  }
  
  uint8_t pwm_channels(pwm_t dev)
  {
      return sizeof(pwm_config[dev].chan) / sizeof(pwm_config[dev].chan[0]);
  }
  
  void pwm_set(pwm_t dev, uint8_t channel, uint16_t value)
  {
      if ((channel >= PWM_MAX_CHANNELS) ||
          (pwm_config[dev].chan[channel].pin == GPIO_UNDEF)) {
          return;
      }
      _tcc(dev)->CC[_chan(dev, channel)].reg = value;
      while (_tcc(dev)->SYNCBUSY.reg & (TCC_SYNCBUSY_CC0 << _chan(dev, channel))) {}
  }
  
  void pwm_poweron(pwm_t dev)
  {
      poweron(dev);
      _tcc(dev)->CTRLA.reg |= (TCC_CTRLA_ENABLE);
  }
  
  void pwm_poweroff(pwm_t dev)
  {
      _tcc(dev)->CTRLA.reg &= ~(TCC_CTRLA_ENABLE);
  
      PM->APBCMASK.reg &= ~(PM_APBCMASK_TCC0 << _num(dev));
      GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_GEN_GCLK7 |
                           GCLK_CLKCTRL_ID(_clk_id(dev)));
      while (GCLK->STATUS.bit.SYNCBUSY) {}
  }
  
  #endif /* PWM_NUMOF */