Нежелательные ореолы со светодиодами charlieplexed
У меня есть очень простая печатная плата, которая использует 3 GPIO на ATtiny85 для управления 6 светодиодами с помощью мультиплексирования.
Когда я пытаюсь запрограммировать определенные паттерны горящих светодиодов (псевдо ШИМ) У меня некоторые светодиоды горят тускло, когда они должны быть полностью выключены.
Пример, когда попеременно должны гореть только верхний или нижний три светодиода:
Вы можете видеть, что светодиоды 2 и 5 тускло светятся, а не полностью выключены.
У меня нет проблем при последовательном освещении светодиодов по одному.
У меня нет проблем с попеременным освещением нечетных и четных светодиодов.
Схема такова:
Кнопки используются для переключения вверх и вниз через 6 режимов работы. Режим 0 последовательно загорает светодиоды по одному. Режим 1 чередует нечетные и четные светодиоды. Режим 2 освещает верхний или нижний три светодиода попеременно (и имеет проблему ореолов), другие режимы просто стабильно освещают соответствующий светодиод.
Код разбит на несколько файлов:
main.ino:
Button leftButton = Button(3);
Button rightButton = Button(4);
Sequencer sequencer = Sequencer(200); // мс на шаг
Alternator alternator = Alternator(0b00101010, 0b00010101);
void setup() {
pinMode(0, OUTPUT);
pinMode(1, OUTPUT);
pinMode(2, OUTPUT);
}
// ------------------------------------------------------------------
const int maxMode = 5;
int mode = 0;
void loop() {
if (leftButton.wasPressed()) {
if (++mode > maxMode) { mode = 0; }
initMode(mode);
}
if (rightButton.wasPressed()) {
if (--mode < 0) { mode = maxMode; }
initMode(mode);
}
switch(mode) {
case (0): sequencer.check(); break;
case (1):
case (2): alternator.check(); break;
default: lightLED(mode); break;
}
}
// ------------------------------------------------------------------
void initMode(int mode) {
switch (mode) {
case (0): sequencer.reset(); break;
case (1): alternator = Alternator(0b00101010, 0b00010101); break;
case (2): alternator = Alternator(0b00111000, 0b00000111); break;
}
}
Генератор переменного тока.ино
class Alternator {
private:
unsigned long whenChanged = 0;
int interval = 500; // мс
byte pattern, pattern1, pattern2; // растровое изображение
int count = 0;
public:
Alternator(byte pattern1, byte pattern2) {
this->pattern1 = pattern1;
this->pattern2 = pattern2;
}
void check() {
if ((millis() - whenChanged) > interval) {
if (pattern == pattern1) { pattern = pattern2; }
else { pattern = pattern1; }
whenChanged = millis();
}
if (++count > 5) { count = 0; }
if (((pattern >> count) & 1) == 1) {
lightLED(count);
}
}
};
Последовательность.ино
class Sequencer {
private:
int seq; // LED# 0-5
unsigned long whenLastStep; // ms
int stepTime; // ms
const int aDay = 24 * 3600 * 1000; // ms
public:
Sequencer(int stepTime) {
if (stepTime < 1) { stepTime = 1; }
if (stepTime > aDay) { stepTime = aDay; }
this->stepTime = stepTime;
this->seq = 0;
this->whenLastStep = 0;
}
void check() {
if ((millis() - whenLastStep) > stepTime) {
lightLED(seq++);
if (seq > 5) { seq = 0; }
whenLastStep = millis();
}
}
void reset() {
this->seq = 0;
}
};
Charlieplex.ino
const int highs[6] = {0,1,2,1,2,0}; // GPIOs для светодиодов 0-5
const int lows[6] = {1,0,1,2,0,2};
// Манипулирует 3 выходами GPIO для освещения одного из 6 светодиодов.
void lightLED(int i) {
int h, l; // pin для установки high, low
if (i < 0 || i >= 6) {
h = 99; l = 99; // все СВЕТОДИОДЫ выключены
} else {
h = highs[i];
l = lows[i];
}
for (int j=0; j<=2; j++) {
if (j==h || j==l) {
pinMode(j, OUTPUT);
if (j==h) {
digitalWrite(h, HIGH);
} else {
digitalWrite(l, LOW);
}
} else {
pinMode(j, INPUT); // tri-state
}
}
}
Пуговицы.ино
class Button {
private:
int pin;
const int DebounceDelay = 100; // миллисекунды
int lastState = HIGH; // Предыдущее состояние кнопки
unsigned long whenLastPressed = 0; // Последнее нажатие кнопки
public:
Button(int pin) {
this->pin = pin;
pinMode(pin, INPUT_PULLUP);
}
// Был ли переход вверх (ВЫСОКИЙ) к вниз (НИЗКИЙ) с момента последнего вызова и все еще удерживался нажатым?
bool wasPressed() {
bool result = false;
if ((millis() - whenLastPressed) > DebounceDelay) {
int state = digitalRead(pin);
if ((state == LOW) && (lastState == HIGH)) {
result = true;
whenLastPressed = millis();
}
lastState = state;
}
return result;
}
};
Я не уверен, где я ошибаюсь, есть какие-нибудь идеи?
@RedGrittyBrick, 👍2
Обсуждение2 ответа
Лучший ответ:
Код мультиплексирования не сбрасывает состояние неиспользуемого PINа, и если значение порта равно HIGH + mode INPUT, это означает, что подтягивание включено.
Решение состоит в том, чтобы сбросить все контакты до низкого уровня, прежде чем код начнет менять направление. И последним шагом будет установка правильного вывода на ВЫСОКИЙ уровень.
Если вы хотите сделать это в одном цикле, вы можете наблюдать действительно короткие импульсы (это может быть или не быть видно - на более высоких частотах это будет более очевидно).
Это также может быть намного быстрее, чем использование digitalWrite и pinMode, что-то вроде:
void charlieplex(uint8_t i) {
constexpr uint8_t clrpins = ~(_BV(PB0) | _BV(PB1) | _BV(PB2));
constexpr uint8_t dirs[] = { 0b011, 0b011, 0b110, 0b110, 0b101, 0b101};
constexpr uint8_t outs[] = { 0b001, 0b010, 0b010, 0b100, 0b001, 0b100};
PORTB &= clrpins; // очистить выходные контакты
DDRB &= clrpins; // clear direction pins (0 == вход, 1 == выход)
if (i < 6) { // такое простое условие, если вы используете: uint8_t i
DDRB |= dirs[i]; // И установите правильные контакты в качестве выходов
PORTB |= outs[i]; // И установите правильный контакт как высокий
}
}
Это не проверено, но это должно быть в значительной степени так
Этот код работает очень хорошо. Небольшое исправление, необходимое для моего конкретного заказа светодиода: constexpr uint8_t outs[] = { 0b001, 0b010, 0b100, 0b010, 0b100, 0b001};
, @RedGrittyBrick
возможно, я делал это по нумерации светодиодов, а здесь и в исходном коде похоже, что 3-4 и 5-6 поменялись местами, @KIIV
Ах да, я поменял светодиоды при прокладке печатной платы, чтобы упростить маршрутизацию трека, и не обновил схему! Так что моя вина. :-), @RedGrittyBrick
Простое добавление digitalWrite(pin, 0) перед каждым pinMode(pin, INPUT) в моем коде исправило ореолы светодиодов в моем проекте charlieplexing Badgelife. Просто и спасибо за объяснение, пропустил проблему с подтягиванием в других документах., @haa
Вторичная проблема заключается в том, что использование millis()
для обновления временных меток приведет к кумулятивному перетаскиванию интервала.
Чтобы сохранить интервал постоянным, обновите:
При изменении с
интервалом
whenLastStep
сstepTime
.
Генератор переменного тока.ино
void check() {
if ((millis() - whenChanged) > interval) {
if (pattern == pattern1) { pattern = pattern2; }
else { pattern = pattern1; }
whenChanged += interval; // Постоянный интервал.
}
if (++count > 5) { count = 0; }
if (((pattern >> count) & 1) == 1) {
lightLED(count);
}
}
Последовательность.ино
void check() {
if ((millis() - whenLastStep) > stepTime) {
lightLED(seq++);
if (seq > 5) { seq = 0; }
whenLastStep += stepTime; // Постоянный шаг по времени.
}
}
Эта тонкая разница очень хорошо объясняется @edgar-bonet в его ответе об опросе датчика на частоте 100 Гц.
Кроме того, Button::wasPressed()
подвержен отрицательному всплеску, возникающему во время выборки входного контакта, т. е. всплеск будет интерпретироваться как непрерывное нажатие DebounceDelay
(100 мс). Сравните с этим простым дебоунсером, который я поставил на GitHub.
- Проблема с датчиком температуры и влажности DHT11
- Получение ошибки ets 8 января 2013,rst cause:4,boot mode(1,6) wdt reset
- Выводы прерываний Arduino Mega 2560 и отображение портов с помощью поворотного энкодера
- Данные DHT11 из Arduino UNO в Firebase через ESP8266
- Объяснение кода MPU6050
- Измерение скорости двигателя постоянного тока с помощью поворотного энкодера
- Почему dtostrf() не работает для этого значения?
- Как я могу прервать задержку() при нажатии кнопки?
Я бы использовал прямой доступ к порту (но это не обязательно). И сначала установите все выходы нулевыми, затем все установите в качестве входов (если вы оставили какой-либо выход высоким и переключились в режим ввода, то будет включено внутреннее подтягивание). После этого вы можете установить режим вывода для правильных контактов и один из них как можно ВЫШЕ. (для трех контактов может быть достаточно просто СБРОСИТЬ все контакты, а затем установить правильный режим вывода и значения набора следующего цикла), @KIIV