Ich versuche, einen Attiny85 (Digispark) zu verwenden, um einen anderen Controller aus dem Ruhezustand zu wecken (ein ESP8266).
Der Attiny ist mit einem IR-Empfänger verbunden, der einen aktiven Low-Ausgang hat. Grundsätzlich habe ich einen Ausgang vom Attiny mit dem Reset-Pin des ESP8266 verbunden. Wenn also ein IR-Signal empfangen wird, setzt es den ESP8266 zurück, ignoriert dann aber zukünftiges IR, bis es ein Signal vom ESP erhält, dass es wieder schlafen geht.
Der Attiny sollte auch schlafen, aber bei Pin-Interrupts an Pin 0 (IR in) oder Pin 2 aufwachen (resetEnable vom ESP, um anzuzeigen, dass der ESP in den Ruhezustand geht und geweckt werden muss).
Ich habe Code, der manchmal funktioniert, aber meistens (aber nicht immer) den Impuls zum Zurücksetzen zu verpassen scheint, den das ESP sendet, um zu benachrichtigen, bevor es in den Ruhezustand wechselt. Ich habe mit dem Oszilloskop überprüft, dass das ESP jedes Mal, wenn es in den Ruhezustand geht, einen 3 ms hohen Impuls sendet, aber dem Attiny fehlen die meisten dieser Impulse (dh das ResetEnable-Flag bleibt niedrig).
Ich stelle mir vor, dass es funktionieren sollte, indem die Interrupt-Routine bestimmt, welcher Pin sie ausgelöst hat. Wenn der Reset-Enable-Pin (Pin 2) hoch geht, wird ein Flag gesetzt, sodass das nächste Low am IR-Pin ein aktives Signal sendet Low-Reset-Impuls an den ESP-RST-Pin, der mit Pin 3 des Attiny verbunden ist).
Der erste IR-Impuls, der nach dem Booten des Attiny empfangen wird, setzt das ESP immer so zurück, wie es sollte, da das resetEnable-Flag in setup () auf true gesetzt ist. Dies lässt mich denken, dass der größte Teil meines Codes funktioniert, mit Ausnahme der Interrupt-Routine zum Setzen der Flags resetEnable und resetEsp.
Hier ist mein Code, der größtenteils aus aus dem Internet gestohlenen Teilen zusammengesetzt wurde:
#include <avr/sleep.h>
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
int pinIR = 0;
int pinLed = 1;
int pinRSTen = 2;
int pinRST = 3;
volatile bool resetEsp = false;
volatile bool resetEnabled = true;
void setup(){
pinMode(pinIR,INPUT);
pinMode(pinLed,OUTPUT);
pinMode(pinRSTen,INPUT);
pinMode(pinRST,OUTPUT);
digitalWrite(pinRST,HIGH); // let the Esp boot
flash(4,500); // flash the led to show attiny has started
sbi(GIMSK,PCIE); // Turn on Pin Change interrupt
sbi(PCMSK,PCINT0); // Which pins are affected by the interrupt
sbi(PCMSK,PCINT2);
sei();
}
void loop(){
system_sleep();
if (resetEsp) {
digitalWrite(pinRST, LOW); // reset the ESP
delayMicroseconds(240);
digitalWrite(pinRST, HIGH); // let the ESP Boot
flash(10, 50); // flash the led fast to show we're waking the ESP
resetEsp = false; // clear the flags
resetEnabled = false;
} else if (resetEnabled) {
flash(10, 500); // mostly never get here
} else {
flash(2, 500); // these are the flashes I see most of the time
}
}
// From http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/
void system_sleep() {
cbi(ADCSRA,ADEN); // Switch Analog to Digital converter OFF
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode
sleep_mode(); // System sleeps here
//sbi(ADCSRA,ADEN); // Switch Analog to Digital converter ON
}
ISR(PCINT0_vect) {
if (digitalRead(pinRSTen) == HIGH) {
resetEnabled = true; // set the flag so the next IR pulse will reset the ESP
}
if (resetEnabled && (digitalRead(pinIR) == LOW)) {
resetEsp = true; // reset the ESP
}
}
void flash(int num, int wait) {
for (int i=0;i<num;i++) {
digitalWrite(pinLed, HIGH);
delay(50);
digitalWrite(pinLed, LOW);
delay(wait);
}
}
Mein Code löst zuverlässig aus und gibt jedes Mal zwei Blitze, wenn ich IR an ihn sende. Nur das ResetEnable-Flag scheint nicht zuverlässig gesetzt zu werden, wenn das ESP den 3 ms hohen Impuls an Pin 2 sendet.
Nachdem ich den Interrupt-Code zuverlässig zum Laufen gebracht habe, möchte ich den Attiny auch dazu bringen, IR-Impulse von weniger als 320 us zu ignorieren, da der IR-Detektor hin und wieder für weniger als 320 us niedrig zu sein scheint, und ich möchte den ESP8266 nicht in diesem Fall geweckt werden.
Bearbeiten:
<meta>
Ich hätte nie gedacht, dass etwas, das so einfach erscheint, zu etwas so Kompliziertem werden kann! Vielen Dank an jms und JimmyB für Ihre Hilfe, Ihre Kommentare waren wie Gold für mich und ich habe viel gelernt. Es ist sehr schwierig, einen Attiny-Interrupt-Code zu debuggen, den ich nicht wirklich verstanden habe (der erste Interrupt-Code, den ich je verwendet habe), mit nur einer einzigen LED, mit der kommuniziert werden kann, und obendrein, während er blinkt einzelne LED, es fehlen tatsächlich Interrupts!</meta>
Jetzt habe ich das gesagt, hier ist die schlechte Nachricht ... Es funktioniert immer noch nicht. :-(
Ich habe den Code verwendet, den sowohl jms als auch JimmyB angegeben haben, sie waren fast identisch.
Leider schläft der Attiny aber immer noch nicht richtig.
Wenn der ResetEnable-Impuls auf PB2 eingeht, wird meiner Meinung nach das ResetEnable-Flag nicht gesetzt. Mit dem folgenden Code blinkt die LED einfach jedes Mal einmal, genauso wie wenn IR auf PB0 eingeht. Das einzige Mal, wenn die LED durchgehend leuchtet (um zu zeigen, dass resetEnable wahr ist), ist, wenn der Attiny zum ersten Mal gestartet wird. Es wird das ESP so zurücksetzen, wie es sollte, wenn zum ersten Mal IR eingeht, aber nie wieder, da resetEnable nicht wieder eingestellt wird, glaube ich.
Wenn ich die Zeile auskommentiere sleep_cpu();
, dann funktioniert zwar alles einwandfrei (aber offensichtlich schläft der Attiny nicht). Ich kann nicht herausfinden, warum es mit dem Schlaf dort nicht funktioniert.
Hier ist der genaue Code, den ich jetzt verwende:
#include <avr/sleep.h>
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
int pinIR = 0;
int pinLed = 1;
int pinRSTen = 2;
int pinRST = 3;
volatile bool resetEsp = false;
volatile bool resetEnabled = true;
void setup() {
pinMode(pinIR, INPUT);
pinMode(pinLed, OUTPUT);
pinMode(pinRSTen, INPUT);
pinMode(pinRST, INPUT); // set as input so ESP can auto-reset itself over USB serial
//digitalWrite(pinRST, HIGH); // let the Esp boot
flash(4, 500);
sbi(GIMSK, PCIE); // Turn on Pin Change interrupt
sbi(PCMSK, PCINT0); // Which pins are affected by the interrupt
sbi(PCMSK, PCINT2);
sei();
}
void loop() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode
cli(); // Disable interrupts to avoid race condition
if ( !resetEsp && !resetEnabled ) {
// Only go to sleep if we have nothing to do right now.
// Safe(*) code from the example in avr-libc:
sleep_enable();
sei();
sleep_cpu(); // if this line is commented out, it all works perfectly.
sleep_disable();
} else {
sei(); // Can go on processing IRQs.
}
if (resetEsp) {
cli();
resetEsp = false;
resetEnabled = false;
sei();
pinMode(pinRST, OUTPUT); // needed to do this so auto-reset works when programming the ESP
// over serial
digitalWrite(pinRST, LOW); // reset the ESP
delayMicroseconds(240);
digitalWrite(pinRST, HIGH); // let the ESP Boot
pinMode(pinRST, INPUT);
flash(10, 50); // show that we're resetting the ESP
} else if (resetEnabled) {
digitalWrite(pinLed, HIGH); // show that resets are enabled
//flash(4, 100);
} else {
flash(1, 50);
}
}
ISR(PCINT0_vect) {
if (resetEnabled && !(PINB & (1 << PB0))) {
resetEsp = true;
}
if ((PINB & (1 << PB2))) {
resetEnabled = true;
}
}
void flash(int num, int wait) {
for (int i = 0; i < num; i++) {
digitalWrite(pinLed, HIGH);
delay(50);
digitalWrite(pinLed, LOW);
delay(wait);
}
}
Würde es jms
Ihnen etwas ausmachen, mir genau zu zeigen, wie man ein einzelnes Zustandsbyte deklariert und verwendet? Ich habe versucht, danach zu googeln, konnte aber nicht die richtigen Schlüsselwörter finden, um etwas Nützliches zu finden. Ich glaube nicht, dass es eine ist enum
, vielleicht eine struct
? Wie Sie sagten, volatile uint8_t espIrRstState
mit drei möglichen Werten IR_RST_DISABLED
, IR_RST_ENABLED
, IR_RST_TRIGGERED
.
Dieses Ding macht mir den Kopf rein!! Ich würde es gerne zum Laufen bringen! Bitte hilf mir, damit ich mit meinem Leben weitermachen kann!
Sie gehen bei jeder Iteration von bedingungslos schlafen loop()
. Das ist nicht das, was Sie wollen. So wie Ihr Code jetzt ist, verpassen Sie jedes Weckereignis, das auftritt, während Ihr normaler Programmcode ausgeführt wird.
Ihr Code verbringt die meiste Zeit damit, innen zu verzögern flash()
. Und der Controller geht schlafen, nachdem flash()
er fertig ist. Stets. Dies bedeutet, dass jedes Mal, wenn ein Aufwachereignis auftritt, während die LED blinkt, kein Aufwachen verursacht wird. Das liegt nur daran, dass der Interrupt aufgetreten ist und gewartet wurde, bevor Sie schlafen gegangen sind.
Sehen Sie sich das Beispiel in der avr-libc-Dokumentation hier an .
Ihr Code sollte so aussehen:
void loop(){
cli(); // Disable interrupts to avoid race condition.
if ( !resetEsp ) {
// Only go to sleep if we have nothing to do right now.
// Safe(*) code from the example in avr-libc:
sleep_enable();
sei();
sleep_cpu();
sleep_disable();
} else {
sei(); // Can go on processing IRQs.
}
if (resetEsp) {
cli();
resetEsp = false; // clear the flags
resetEnabled = false;
sei();
digitalWrite(pinRST, LOW); // reset the ESP
delayMicroseconds(240);
digitalWrite(pinRST, HIGH); // let the ESP Boot
flash(10, 50); // flash the led fast to show we're waking the ESP
} else if (resetEnabled) {
flash(10, 500); // mostly never get here
} else {
flash(2, 500); // these are the flashes I see most of the time
}
}
Der Punkt hier ist, dass wir alle Interrupts deaktivieren, während wir prüfen, ob wir schlafen gehen wollen. Auf diese Weise stellen wir sicher, dass kein Interrupt passieren kann, nachdem wir überprüft haben, aber bevor wir tatsächlich schlafen. Entscheidend ist jedoch, dass wir kurz vor dem Einschlafen wieder Interrupts aktivieren, sonst werden wir nie wieder geweckt.
Außerdem hat @jms Recht mit der Aussage, dass Sie beim Zurücksetzen Ihrer Flaggen eine andere potenzielle Rennbedingung haben, also fügen Sie dort auch einige cli/sei hinzu.
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
hineingehen kann setup()
. Und wenn Sie kein Raumschiff steuern, gilt dasselbe für sleep_enable();
, während Sie es sleep_disable();
vollständig weglassen.
(*) Der oben als "sicher" bezeichnete Code ist nicht absolut kugelsicher, da der Compiler theoretisch während der Optimierung entscheiden könnte, einige Anweisungen neu anzuordnen, sodass sie sei()
möglicherweise sleep_cpu()
nicht in direkter Reihenfolge stehen. Überprüfen Sie zur Sicherheit den generierten Assembler-Code ( gcc -save-temps
) oder schreiben Sie die erforderlichen Anweisungen einfach selbst als Inline-Assembler. (Kann so einfach sein wie asm volatile (" sei \r\n sleep \r\n " :::);
.)
if ( !resetEsp && !resetEnabled ){...
verhindert, dass der ATtiny schläft, wenn Resets vom ESP8266 aktiviert wurden.resetEnabled
. Möglicherweise nicht das, was das OP wirklich will.SLEEP_MODE_PWR_DOWN
. Sie benötigen einen vollständigen Stapel ausstehender Zustandsänderungen, wenn Sie alle Zustandsänderungen verfolgen und die LEDs blinken lassen möchten, um jede auftretende Zustandsänderung widerzuspiegeln.Ich bin mir ziemlich sicher, dass dies ein klassisches Parallelitätsproblem ist, das dadurch entsteht, dass Sie die beiden Flags nach dem Zurücksetzen des ESP löschen.
ATtiny boots, initializes, resetEnabled set
Main loop starts, ATtiny goes asleep.
IR pulse arrives
ATtiny wakes up, ISR executes, resetEsp set
Main loop code resets ESP ==> ESP wakes up
Main loop code starts flashing LED ESP does stuff
ISR executes, doesn't do anything <== ESP enables IR reset
Main loop code finishes flashing LED ESP goes asleep
ResetEsp and resetEnabled cleared
ATtiny goes asleep, resetEnabled is never set again
IR pulse arrives, ISR executes, ESP reset skipped
Das ATtiny funktioniert manchmal wie beabsichtigt, wenn das ESP lange genug braucht, um seine Sache zu erledigen, bevor es den Interrupt auslöst.
Die Lösung ist einfach, löschen Sie die Flags, bevor Sie das ESP zurücksetzen.
void loop()
{
system_sleep();
if(resetEsp)
{
cli(); //prevent an interrupt from firing while
//..clearing the flags and messing them up
resetEsp = false; // clear the flags
resetEnabled = false;
sei();
digitalWrite(pinRST, LOW); // reset the ESP
delayMicroseconds(240);
digitalWrite(pinRST, HIGH); // let the ESP Boot
flash(10, 50); // flash the led fast to show we're waking the ESP
}
else
flash(2, 500); //reset by IR not enabled
}
Wie @JimmyB feststellte, gibt es sowohl im Code der ursprünglichen Frage als auch in meiner ursprünglichen Antwort ein zweites Versehen. Wenn ESP-IR-Resets aktiviert sind und ein IR-Impuls eintrifft , während der ATtiny wach ist , geht der ATtiny in den Ruhezustand, ohne den AVR zurückzusetzen, und es dauert einen zweiten, überflüssigen IR-Impuls, um tatsächlich einen Reset des ESP auszulösen.
Um dies zu beheben, sollte das ResetEsp-Flag kurz vor dem Einschlafen erneut überprüft werden (während Interrupts deaktiviert sind), und die allerletzte Anweisung vor der Sleep-Anweisung sollte die Interrupts wieder aktivieren. Auf diese Weise wird, wenn ein IR-Impuls ankommt, während das resetEsp-Flag erneut überprüft wird, der Schlafmodus-Übergang durch den anstehenden Interrupt abgebrochen.
Dieser Code sollte auch dieses Problem beheben.
void loop()
{
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
cli();
if(!resetEsp)
{
sleep_enable(); //Set the sleep enable bit, allowing the sleep instruction to work.
sei(); //Enable interrupts one cycle before falling asleep. If an
sleep_cpu(); //..interrupt is pending, the ISR will abort the sleeping process
sleep_disable(); //Clear the sleep enable bit
}
else
sei();
if(resetEsp)
{
cli(); //prevent an interrupt from messing with the flags while
//..main loop code is in the process of clearing them
resetEsp = false; // clear the flags
resetEnabled = false;
sei();
digitalWrite(pinRST, LOW); // reset the ESP
delayMicroseconds(240);
digitalWrite(pinRST, HIGH); // let the ESP Boot
flash(10, 50); // signal that the ESP has been reset
}
else
{
if(resetEnabled)
flash(10, 500); //signal that the ESP has enabled IR resets
else
flash(2, 500); //signal that the IR pulse was ignored
}
}
Hier ist der vollständige Code einer Implementierung, die auf einer einzelnen Zustandsvariablen basiert. Ich habe auch modifiziert, wie die ESP-Reset-Leitung angesteuert wird: Jetzt wird die Reset-Leitung normalerweise ungetrieben schwebend gelassen und kurz auf Masse gebracht, wenn ein Reset befohlen wird.
#include <avr/sleep.h>
#include <cstdint.h>
//IO pin bit definitions
#define IR_IN 0
#define LED_OUT 1
#define RST_ENABLE_IN 2
#define RST_OUT 3
//State definitions
#define IR_RST_DISABLED 0
#define IR_RST_WAITING 1
#define IR_RST_TRIGGERED 2
//State variable
volatile uint8_t state;
void setup()
{
DDRB = (0<<IR_IN) |
(1<<LED_OUT) |
(0<<RST_ENABLE_IN) |
(0<<RST_OUT); //set RST_OUT as input. This lets the reset line float undriven.
PORTB |= (0<<RST_OUT);
flashLed(4,500); // flash the led to show attiny has started
state = IR_RST_WAITING; //start up with the IR reset enabled
//enable IR_IN and RST_ENABLE_IN as pin change interrupt sources.
PCMSK |= (1<<PCINT0) | (1<<PCINT2);
GIMSK |= (1<<PCIE);
sei();
}
void loop()
{
switch(state)
{
case IR_RST_DISABLED:
default:
flashLed(2, 500); //Signal that an IR pulse was received but ignored.
break;
case IR_RST_WAITING:
flashLed(10, 500); //Signal that IR Resets are now enabled
break;
case IR_RST_TRIGGERED:
//Clear the state. No risk of race conditions, this takes just a single cycle.
state = IR_RST_DISABLED;
//Reboot the ESP
DDRB |= (1<<RST_OUT); //set RST_OUT as output. This drives the pin low, as the corresponding bit in PORTB is 0
delayMicroseconds(240);
DDRB &= ~(1<<RST_OUT); //set RST_OUT as input. This lets the reset line float undriven.
flashLed(10, 50); //Signal that the ESP was reset.
break;
}
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
cli(); //disable interrupts so that the state won't change while we are falling asleep
if(state != IR_RST_TRIGGERED)
{
sleep_enable(); //Set the sleep enable bit, allowing the sleep instruction to work.
sei(); //Enable interrupts one cycle before falling asleep. If an interrupt source occurred after
sleep_cpu(); //..disabling interrupts, the ISR will now trigger and abort the sleeping process
sleep_disable(); //Clear the sleep enable bit, locking sleep functionality from accidential use
}
sei(); //enable interrupts back again and resume normal code execution.
}
ISR(PCINT0_vect)
{
if(state == IR_RST_DISABLED)
{
if(PINB & (1 << RST_ENABLE_IN))
state = IR_RST_WAITING;
}
else
{
if(PINB & (1 << IR_IN))
state = IR_RST_TRIGGERED;
}
}
void flashLed(int flashCount, int offTime)
{
while (flashCount-- > 0) {
PORTB |= (1<<LED_OUT); //LED output high
delay(50);
PORTB &= ~(1<<LED_OUT); //LED output low
delay(offTime);
}
}
Wenn Sie die LED-Blinkfunktion nicht mehr möchten, können Sie einfach die gesamte switch-Anweisung durch ein einfaches if ersetzen:
if(state == IR_RST_TRIGGERED)
{
//Clear the state. No risk of race conditions, this takes just a single cycle.
state = IR_RST_DISABLED;
//Reboot the ESP
DDRB |= (1<<RST_OUT); //set RST_OUT as output. This drives the pin low, as the corresponding bit in PORTB is 0
delayMicroseconds(240);
DDRB &= ~(1<<RST_OUT); //set RST_OUT as input. This lets the reset line float undriven.
}
Wenn Sie die Blinkfunktion entfernen, können Sie auch ein einfaches Debugging mit der LED implementieren: Schalten Sie den LED-Status um, wenn ein bestimmter Codeabschnitt ausgeführt wird.
Beispielsweise könnten Sie die LED jedes Mal umschalten, wenn der RST_ENABLE_IN-Pin jemals den Zustand ändert, sodass Sie feststellen können, ob die Reset-Enable-Leitung überhaupt jemals den Interrupt auslöst
ISR(PCINT0_vect)
{
if(PINB & (1 << RST_ENABLE_IN))
PORTB ^= (1 << LED_OUT); //XOR the LED output with itself, toggling its state.
...
volatile uint8_t espIrRstState
mit drei möglichen Werten IR_RST_DISABLED
, IR_RST_ENABLED
, IR_RST_TRIGGERED
). Auf diese Weise ist das Zurücksetzen des Zustandsbytes eine atomare Operation, und es besteht keine Notwendigkeit, Interrupts zu deaktivieren und zu aktivieren.volatile uint8_t state = 1;
beim Start manuell eingestellt habe. Wenn es den ResetEnable-Impuls empfängt, sieht es so aus, als würde es meiner Meinung nach nur als normaler IR-Impuls angesehen, da die LED blinkt, um anzuzeigen, dass etwas empfangen wurde. Heute Abend werde ich mich weiter damit beschäftigen. Ich musste die Zeile auskommentieren, #include <cstdint.h>
um einen Compilerfehler zu vermeiden, aber ich glaube nicht, dass das irgendetwas beeinflusst hätte? Ich bin dabei, auf diesem dummen Attiny herumzutrampeln!sleep_cpu();
Zeile hat leider nicht dazu geführt, dass es mit Ihrem Code funktioniert. Wie gesagt, ich werde heute Abend mehr damit spielen. Danke noch einmal.#include <cstdint>
(die C ++ - Version der C lib ), aber ich habe stattdessen stdint.h
eine nicht vorhandene eingefügt . cstdint.h
Sie müssen es nicht wirklich in die Arduino-Umgebung aufnehmen, wie Sie entdeckt haben (es ist für Sie erledigt), ich habe es nur aus Gewohnheit getan. Haben Sie einen Pull-up auf der ESPs / Reset-Leitung? Wie ich angemerkt habe, habe ich den Code geändert, um nur die Reset-Leitung auf Low zu ziehen. Haben Sie versucht, die LED jedes Mal umzuschalten, wenn ein Interrupt auftritt, wie ich vorgeschlagen habe? Es sollte zeigen, ob es einen Fehler im Reset-bezogenen Code gibt oder ob die ISR aus irgendeinem Grund einfach nicht ausgelöst wird.volatile uint8_t state = 2;
am Anfang einzustellen, um heute Abend sicherzugehen.
JimmyB
lokaler Host
JimmyB
JimmyB