Torc  0.1
torci2cpca9685.cpp
Go to the documentation of this file.
1 /* Class TorcI2CPCA9685
2 *
3 * This file is part of the Torc project.
4 *
5 * Copyright (C) Mark Kendall 2015-18
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20 * USA.
21 */
22 
23 // Torc
24 #include "torclogging.h"
25 #include "torci2cpca9685.h"
26 
27 // wiringPi
28 #include <wiringPiI2C.h>
29 
30 // PCA9685
31 #include <stdint.h>
32 #define MODE1 0x00
33 #define MODE2 0x01
34 #define LED0_ON_LOW 0x06
35 #define LED0_ON_HIGH 0x07
36 #define LED0_OFF_LOW 0x08
37 #define LED0_OFF_HIGH 0x09
38 #define PRESCALE 0xFE
39 #define CLOCKFREQ 25000000.0
40 #define PCA9685_RESOLUTION 4095
41 #define PCA9685_RANGE 4096
42 
43 #include <unistd.h>
44 #include <math.h>
45 
46 TorcI2CPCA9685Channel::TorcI2CPCA9685Channel(int Number, TorcI2CPCA9685 *Parent, const QVariantMap &Details)
47  : TorcPWMOutput(0.0, PCA9685, Details, PCA9685_RANGE),
48  m_channelNumber(Number),
49  m_channelValue(0),
50  m_parent(Parent)
51 {
52  // PCA9685 only operates at default 12bit accuracy
54  {
56  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Ignoring user defined resolution for PCA9685 channel - defaulting to %1").arg(m_maxResolution));
57  }
58 
59  m_parent->SetPWM(m_channelNumber, m_channelValue);
60 }
61 
63 {
64  // NB we don't turn the channel off, we set it to its default (which is probably off)
66 }
67 
69 {
70  return QStringList() << tr("I2C") << tr("PCA9685 16 Channel PWM") << QStringLiteral("%1 %2").arg(tr("Channel")).arg(m_channelNumber) << tr("Resolution %1").arg(m_resolution);
71 }
72 
74 {
75  QMutexLocker locker(&lock);
76 
77  // anything that doesn't change the range sent to the device is just noise, so
78  // calculate now and filter early
79  double newvalue = Value;
80  if (!ValueIsDifferent(newvalue))
81  return;
82 
83  // convert 0.0 to 1.0 to 0 to 4095
84  int channelvalue = lround(newvalue * (float)PCA9685_RESOLUTION);
85  if (channelvalue == m_channelValue)
86  return;
87 
88  m_channelValue = channelvalue;
89  m_parent->SetPWM(m_channelNumber, m_channelValue);
91 }
92 
93 TorcI2CPCA9685::TorcI2CPCA9685(int Address, const QVariantMap &Details)
94  : TorcI2CDevice(Address)
95 {
96  // nullify outputs in case they aren't created
97  memset(m_outputs, 0, sizeof(TorcI2CPCA9685Channel*) * 16);
98 
99  // open a handle to the device
100  m_fd = wiringPiI2CSetup(m_address);
101  if (m_fd < 0)
102  {
103  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to open I2C device at address 0x%1").arg(m_address, 0, 16));
104  return;
105  }
106 
107  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Opened %1 I2C device at address 0x%2")
108  .arg(PCA9685).arg(m_address, 0, 16));
109 
110  // reset
111  if (wiringPiI2CWriteReg8(m_fd, MODE1, 0x00) < 0 || wiringPiI2CWriteReg8(m_fd, MODE2, 0x04) < 0)
112  {
113  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to reset PCA9685 device"));
114  return;
115  }
116 
117  // set frequency to 1000Hz
118  bool success = true;
119  success &= wiringPiI2CWriteReg8(m_fd, MODE1, 0x01) > -1;
120  success &= wiringPiI2CWriteReg8(m_fd, PRESCALE, (uint8_t)((CLOCKFREQ / PCA9685_RANGE / 1000) - 1)) > -1;
121  success &= wiringPiI2CWriteReg8(m_fd, MODE1, 0x80) > -1;
122  success &= wiringPiI2CWriteReg8(m_fd, MODE2, 0x04) > -1;
123 
124  if (!success)
125  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to set up PCA9685 device"));
126 
127  // create individual channel services
128  // this will also reset each channel to the default value (0)
129  QVariantMap::const_iterator it = Details.begin();
130  for ( ; it != Details.constEnd(); ++it)
131  {
132  if (it.key() == QStringLiteral("channel"))
133  {
134  // channel needs a <number>
135  QVariantMap channel = it.value().toMap();
136  if (!channel.contains(QStringLiteral("number")))
137  {
138  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("PCA9685 channel has no number"));
139  continue;
140  }
141 
142  bool ok = false;
143  int channelnum = channel.value(QStringLiteral("number")).toInt(&ok);
144 
145  if (!ok || channelnum < 0 || channelnum >= 16)
146  {
147  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to parse valid PCA9685 channel number from '%1'").arg(channel.value(QStringLiteral("number")).toString()));
148  continue;
149  }
150 
151  if (m_outputs[channelnum])
152  {
153  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("PCA9685 channel '%1' is already in use").arg(channelnum));
154  continue;
155  }
156 
157  m_outputs[channelnum] = new TorcI2CPCA9685Channel(channelnum, this, channel);
158  }
159  }
160 }
161 
163 {
164  // remove channels
165  for (int i = 0; i < 16; i++)
166  {
167  if (m_outputs[i])
168  {
169  TorcOutputs::gOutputs->RemoveOutput(m_outputs[i]);
170  m_outputs[i]->DownRef();
171  m_outputs[i] = nullptr;
172  }
173  }
174 
175  // close device
176  if (m_fd > -1)
177  close(m_fd);
178 }
179 
182 bool TorcI2CPCA9685::SetPWM(int Channel, int Value)
183 {
184  if (m_fd < 0 || Channel < 0 || Channel > 15)
185  return false;
186 
187  int offtime = qBound(0, Value, PCA9685_RESOLUTION);
188  int ontime = 0;
189 
190  // turn completely on or off if required
191  // 'off' supercedes 'on' per spec
192  if (offtime < 1)
193  {
194  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Channel %1 turned completely OFF").arg(Channel));
195  offtime |= 0x1000;
196  }
197  else if (offtime >= PCA9685_RESOLUTION)
198  {
199  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Channel %1 turned completely ON").arg(Channel));
200  ontime |= 0x1000;
201  }
202 
203  bool success = true;
204  success &= wiringPiI2CWriteReg8(m_fd, LED0_ON_LOW + (4 * Channel), ontime & 0xFF) > -1;
205  success &= wiringPiI2CWriteReg8(m_fd, LED0_ON_HIGH + (4 * Channel), ontime >> 8) > -1;
206  success &= wiringPiI2CWriteReg8(m_fd, LED0_OFF_LOW + (4 * Channel), offtime & 0xFF) > -1;
207  success &= wiringPiI2CWriteReg8(m_fd, LED0_OFF_HIGH + (4 * Channel), offtime >> 8) > -1;
208 
209  if (!success)
210  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error writing to channel %1").arg(Channel));
211  return success;
212 }
213 
215 {
216  TorcI2CDevice* Create(int Address, const QString &Name, const QVariantMap &Details)
217  {
218  if (PCA9685 == Name)
219  return new TorcI2CPCA9685(Address, Details);
220  return nullptr;
221  }
#define PRESCALE
#define PCA9685
#define LED0_OFF_LOW
#define MODE1
#define PCA9685_RANGE
virtual bool DownRef(void)
#define MODE2
bool ValueIsDifferent(double &NewValue)
#define CLOCKFREQ
#define LED0_ON_HIGH
#define LED0_OFF_HIGH
TorcI2CPCA9685Factory TorcI2CPCA9685Factory
double defaultValue
Definition: torcdevice.h:61
virtual void SetValue(double Value)
Definition: torcdevice.cpp:115
QMutex lock
Definition: torcdevice.h:66
#define PCA9685_RESOLUTION
#define LED0_ON_LOW
TorcI2CPCA9685(int Address, const QVariantMap &Details)
QStringList GetDescription(void)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
void SetValue(double Value)
friend class TorcI2CPCA9685Channel
bool SetPWM(int Channel, int Value)
Set the hardware PWM for given channel.
TorcI2CPCA9685Channel(int Number, TorcI2CPCA9685 *Parent, const QVariantMap &Details)
uint m_maxResolution
Definition: torcpwmoutput.h:31