]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * This file is part of the PulseView project. | |
3 | * | |
4 | * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk> | |
5 | * Copyright (C) 2016 Soeren Apel <soeren@apelpie.net> | |
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, see <http://www.gnu.org/licenses/>. | |
19 | */ | |
20 | ||
21 | #include "analog.hpp" | |
22 | #include "analogsegment.hpp" | |
23 | #include "decode/row.hpp" | |
24 | #include "logic.hpp" | |
25 | #include "logicsegment.hpp" | |
26 | #include "signalbase.hpp" | |
27 | #include "signaldata.hpp" | |
28 | ||
29 | #include <pv/binding/decoder.hpp> | |
30 | #include <pv/session.hpp> | |
31 | ||
32 | using std::dynamic_pointer_cast; | |
33 | using std::make_shared; | |
34 | using std::shared_ptr; | |
35 | using std::tie; | |
36 | ||
37 | namespace pv { | |
38 | namespace data { | |
39 | ||
40 | const int SignalBase::ColourBGAlpha = 8 * 256 / 100; | |
41 | ||
42 | SignalBase::SignalBase(shared_ptr<sigrok::Channel> channel, ChannelType channel_type) : | |
43 | channel_(channel), | |
44 | channel_type_(channel_type), | |
45 | conversion_type_(NoConversion) | |
46 | { | |
47 | if (channel_) | |
48 | internal_name_ = QString::fromStdString(channel_->name()); | |
49 | } | |
50 | ||
51 | SignalBase::~SignalBase() | |
52 | { | |
53 | // Wait for the currently ongoing conversion to finish | |
54 | if (conversion_thread_.joinable()) | |
55 | conversion_thread_.join(); | |
56 | } | |
57 | ||
58 | shared_ptr<sigrok::Channel> SignalBase::channel() const | |
59 | { | |
60 | return channel_; | |
61 | } | |
62 | ||
63 | QString SignalBase::name() const | |
64 | { | |
65 | return (channel_) ? QString::fromStdString(channel_->name()) : name_; | |
66 | } | |
67 | ||
68 | QString SignalBase::internal_name() const | |
69 | { | |
70 | return internal_name_; | |
71 | } | |
72 | ||
73 | void SignalBase::set_name(QString name) | |
74 | { | |
75 | if (channel_) | |
76 | channel_->set_name(name.toUtf8().constData()); | |
77 | ||
78 | name_ = name; | |
79 | ||
80 | name_changed(name); | |
81 | } | |
82 | ||
83 | bool SignalBase::enabled() const | |
84 | { | |
85 | return (channel_) ? channel_->enabled() : true; | |
86 | } | |
87 | ||
88 | void SignalBase::set_enabled(bool value) | |
89 | { | |
90 | if (channel_) { | |
91 | channel_->set_enabled(value); | |
92 | enabled_changed(value); | |
93 | } | |
94 | } | |
95 | ||
96 | SignalBase::ChannelType SignalBase::type() const | |
97 | { | |
98 | return channel_type_; | |
99 | } | |
100 | ||
101 | unsigned int SignalBase::index() const | |
102 | { | |
103 | return (channel_) ? channel_->index() : (unsigned int)-1; | |
104 | } | |
105 | ||
106 | QColor SignalBase::colour() const | |
107 | { | |
108 | return colour_; | |
109 | } | |
110 | ||
111 | void SignalBase::set_colour(QColor colour) | |
112 | { | |
113 | colour_ = colour; | |
114 | ||
115 | bgcolour_ = colour; | |
116 | bgcolour_.setAlpha(ColourBGAlpha); | |
117 | ||
118 | colour_changed(colour); | |
119 | } | |
120 | ||
121 | QColor SignalBase::bgcolour() const | |
122 | { | |
123 | return bgcolour_; | |
124 | } | |
125 | ||
126 | void SignalBase::set_data(shared_ptr<pv::data::SignalData> data) | |
127 | { | |
128 | if (data_) { | |
129 | disconnect(data.get(), SIGNAL(samples_cleared()), | |
130 | this, SLOT(on_samples_cleared())); | |
131 | disconnect(data.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)), | |
132 | this, SLOT(on_samples_added(QObject*, uint64_t, uint64_t))); | |
133 | } | |
134 | ||
135 | data_ = data; | |
136 | ||
137 | if (data_) { | |
138 | connect(data.get(), SIGNAL(samples_cleared()), | |
139 | this, SLOT(on_samples_cleared())); | |
140 | connect(data.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)), | |
141 | this, SLOT(on_samples_added(QObject*, uint64_t, uint64_t))); | |
142 | } | |
143 | } | |
144 | ||
145 | shared_ptr<data::Analog> SignalBase::analog_data() const | |
146 | { | |
147 | shared_ptr<Analog> result = nullptr; | |
148 | ||
149 | if (channel_type_ == AnalogChannel) | |
150 | result = dynamic_pointer_cast<Analog>(data_); | |
151 | ||
152 | return result; | |
153 | } | |
154 | ||
155 | shared_ptr<data::Logic> SignalBase::logic_data() const | |
156 | { | |
157 | shared_ptr<Logic> result = nullptr; | |
158 | ||
159 | if (channel_type_ == LogicChannel) | |
160 | result = dynamic_pointer_cast<Logic>(data_); | |
161 | ||
162 | if (((conversion_type_ == A2LConversionByTreshold) || | |
163 | (conversion_type_ == A2LConversionBySchmittTrigger))) | |
164 | result = dynamic_pointer_cast<Logic>(converted_data_); | |
165 | ||
166 | return result; | |
167 | } | |
168 | ||
169 | void SignalBase::set_conversion_type(ConversionType t) | |
170 | { | |
171 | if (conversion_type_ != NoConversion) { | |
172 | // Wait for the currently ongoing conversion to finish | |
173 | if (conversion_thread_.joinable()) | |
174 | conversion_thread_.join(); | |
175 | ||
176 | // Discard converted data | |
177 | converted_data_.reset(); | |
178 | } | |
179 | ||
180 | conversion_type_ = t; | |
181 | ||
182 | if ((channel_type_ == AnalogChannel) && | |
183 | ((conversion_type_ == A2LConversionByTreshold) || | |
184 | (conversion_type_ == A2LConversionBySchmittTrigger))) { | |
185 | ||
186 | shared_ptr<Analog> analog_data = dynamic_pointer_cast<Analog>(data_); | |
187 | ||
188 | if (analog_data->analog_segments().size() > 0) { | |
189 | AnalogSegment *asegment = analog_data->analog_segments().front().get(); | |
190 | ||
191 | // Begin conversion of existing sample data | |
192 | // TODO Support for multiple segments is missing | |
193 | on_samples_added(asegment, 0, 0); | |
194 | } | |
195 | } | |
196 | ||
197 | conversion_type_changed(t); | |
198 | } | |
199 | ||
200 | #ifdef ENABLE_DECODE | |
201 | bool SignalBase::is_decode_signal() const | |
202 | { | |
203 | // DecodeSignal class overrides this method, all others shall return false | |
204 | return false; | |
205 | } | |
206 | ||
207 | shared_ptr<pv::data::DecoderStack> SignalBase::decoder_stack() const | |
208 | { | |
209 | // DecodeSignal class overrides this method, all others shall return nothing | |
210 | return nullptr; | |
211 | } | |
212 | #endif | |
213 | ||
214 | void SignalBase::save_settings(QSettings &settings) const | |
215 | { | |
216 | settings.setValue("name", name()); | |
217 | settings.setValue("enabled", enabled()); | |
218 | settings.setValue("colour", colour()); | |
219 | settings.setValue("conversion_type", (int)conversion_type_); | |
220 | } | |
221 | ||
222 | void SignalBase::restore_settings(QSettings &settings) | |
223 | { | |
224 | set_name(settings.value("name").toString()); | |
225 | set_enabled(settings.value("enabled").toBool()); | |
226 | set_colour(settings.value("colour").value<QColor>()); | |
227 | set_conversion_type((ConversionType)settings.value("conversion_type").toInt()); | |
228 | } | |
229 | ||
230 | uint8_t SignalBase::convert_a2l_threshold(float threshold, float value) | |
231 | { | |
232 | return (value >= threshold) ? 1 : 0; | |
233 | } | |
234 | ||
235 | uint8_t SignalBase::convert_a2l_schmitt_trigger(float lo_thr, float hi_thr, | |
236 | float value, uint8_t &state) | |
237 | { | |
238 | if (value < lo_thr) | |
239 | state = 0; | |
240 | else if (value > hi_thr) | |
241 | state = 1; | |
242 | ||
243 | return state; | |
244 | } | |
245 | ||
246 | void SignalBase::conversion_thread_proc(QObject* segment, uint64_t start_sample, | |
247 | uint64_t end_sample) | |
248 | { | |
249 | const uint64_t block_size = 4096; | |
250 | ||
251 | // TODO Support for multiple segments is missing | |
252 | ||
253 | if ((channel_type_ == AnalogChannel) && | |
254 | ((conversion_type_ == A2LConversionByTreshold) || | |
255 | (conversion_type_ == A2LConversionBySchmittTrigger))) { | |
256 | ||
257 | AnalogSegment *asegment = qobject_cast<AnalogSegment*>(segment); | |
258 | ||
259 | // Create the logic data container if needed | |
260 | shared_ptr<Logic> logic_data; | |
261 | if (!converted_data_) { | |
262 | logic_data = make_shared<Logic>(1); // Contains only one channel | |
263 | converted_data_ = logic_data; | |
264 | } else | |
265 | logic_data = dynamic_pointer_cast<Logic>(converted_data_); | |
266 | ||
267 | // Create the initial logic data segment if needed | |
268 | if (logic_data->segments().size() == 0) { | |
269 | shared_ptr<LogicSegment> lsegment = | |
270 | make_shared<LogicSegment>(*logic_data.get(), 1, asegment->samplerate()); | |
271 | logic_data->push_segment(lsegment); | |
272 | } | |
273 | ||
274 | LogicSegment *lsegment = dynamic_cast<LogicSegment*>(logic_data->segments().front().get()); | |
275 | ||
276 | // start_sample=end_sample=0 means we need to figure out the unprocessed range | |
277 | if ((start_sample == 0) && (end_sample == 0)) { | |
278 | start_sample = lsegment->get_sample_count(); | |
279 | end_sample = asegment->get_sample_count(); | |
280 | } | |
281 | ||
282 | if (start_sample == end_sample) | |
283 | return; // Nothing to do | |
284 | ||
285 | float min_v, max_v; | |
286 | tie(min_v, max_v) = asegment->get_min_max(); | |
287 | ||
288 | vector<uint8_t> lsamples; | |
289 | lsamples.reserve(block_size); | |
290 | ||
291 | uint64_t i = start_sample; | |
292 | ||
293 | if (conversion_type_ == A2LConversionByTreshold) { | |
294 | const float threshold = (min_v + max_v) * 0.5; // middle between min and max | |
295 | ||
296 | // Convert as many sample blocks as we can | |
297 | while ((end_sample - i) > block_size) { | |
298 | const float* asamples = asegment->get_samples(i, i + block_size); | |
299 | for (uint32_t j = 0; j < block_size; j++) | |
300 | lsamples.push_back(convert_a2l_threshold(threshold, asamples[j])); | |
301 | lsegment->append_payload(lsamples.data(), lsamples.size()); | |
302 | i += block_size; | |
303 | lsamples.clear(); | |
304 | delete[] asamples; | |
305 | } | |
306 | ||
307 | // Convert remaining samples | |
308 | const float* asamples = asegment->get_samples(i, end_sample); | |
309 | for (uint32_t j = 0; j < (end_sample - i); j++) | |
310 | lsamples.push_back(convert_a2l_threshold(threshold, asamples[j])); | |
311 | lsegment->append_payload(lsamples.data(), lsamples.size()); | |
312 | delete[] asamples; | |
313 | ||
314 | samples_added(lsegment, start_sample, end_sample); | |
315 | } | |
316 | ||
317 | if (conversion_type_ == A2LConversionBySchmittTrigger) { | |
318 | const float amplitude = max_v - min_v; | |
319 | const float lo_thr = min_v + (amplitude * 0.1); // 10% above min | |
320 | const float hi_thr = max_v - (amplitude * 0.1); // 10% below max | |
321 | uint8_t state = 0; // TODO Use value of logic sample n-1 instead of 0 | |
322 | ||
323 | // Convert as many sample blocks as we can | |
324 | while ((end_sample - i) > block_size) { | |
325 | const float* asamples = asegment->get_samples(i, i + block_size); | |
326 | for (uint32_t j = 0; j < block_size; j++) | |
327 | lsamples.push_back(convert_a2l_schmitt_trigger(lo_thr, hi_thr, asamples[j], state)); | |
328 | lsegment->append_payload(lsamples.data(), lsamples.size()); | |
329 | i += block_size; | |
330 | lsamples.clear(); | |
331 | delete[] asamples; | |
332 | } | |
333 | ||
334 | // Convert remaining samples | |
335 | const float* asamples = asegment->get_samples(i, end_sample); | |
336 | for (uint32_t j = 0; j < (end_sample - i); j++) | |
337 | lsamples.push_back(convert_a2l_schmitt_trigger(lo_thr, hi_thr, asamples[j], state)); | |
338 | lsegment->append_payload(lsamples.data(), lsamples.size()); | |
339 | delete[] asamples; | |
340 | ||
341 | samples_added(lsegment, start_sample, end_sample); | |
342 | } | |
343 | } | |
344 | } | |
345 | ||
346 | void SignalBase::on_samples_cleared() | |
347 | { | |
348 | if (converted_data_) | |
349 | converted_data_->clear(); | |
350 | ||
351 | samples_cleared(); | |
352 | } | |
353 | ||
354 | void SignalBase::on_samples_added(QObject* segment, uint64_t start_sample, | |
355 | uint64_t end_sample) | |
356 | { | |
357 | if (conversion_type_ != NoConversion) { | |
358 | ||
359 | // Wait for the currently ongoing conversion to finish | |
360 | if (conversion_thread_.joinable()) | |
361 | conversion_thread_.join(); | |
362 | ||
363 | conversion_thread_ = std::thread( | |
364 | &SignalBase::conversion_thread_proc, this, | |
365 | segment, start_sample, end_sample); | |
366 | } | |
367 | ||
368 | samples_added(segment, start_sample, end_sample); | |
369 | } | |
370 | ||
371 | void SignalBase::on_capture_state_changed(int state) | |
372 | { | |
373 | return; | |
374 | if (state == Session::Stopped) { | |
375 | // Make sure that all data is converted | |
376 | ||
377 | if ((channel_type_ == AnalogChannel) && | |
378 | ((conversion_type_ == A2LConversionByTreshold) || | |
379 | (conversion_type_ == A2LConversionBySchmittTrigger))) { | |
380 | ||
381 | shared_ptr<Analog> analog_data = dynamic_pointer_cast<Analog>(data_); | |
382 | ||
383 | if (analog_data->analog_segments().size() > 0) { | |
384 | // TODO Support for multiple segments is missing | |
385 | AnalogSegment *asegment = analog_data->analog_segments().front().get(); | |
386 | ||
387 | if (conversion_thread_.joinable()) | |
388 | conversion_thread_.join(); | |
389 | ||
390 | conversion_thread_ = std::thread( | |
391 | &SignalBase::conversion_thread_proc, this, asegment, 0, 0); | |
392 | } | |
393 | } | |
394 | } | |
395 | } | |
396 | ||
397 | } // namespace data | |
398 | } // namespace pv |