sobota, 5 października 2019

Raspberry Pi - Układ do bezpiecznego włączania/wyłączania

W tym poście chciałbym opisać układ będący częścią wcześniejszego postu o wyłączaniu i włączaniu Raspberry Pi.

Znalezione obrazy dla zapytania raspberry pi 3

Główny układ zarządzający sterowaniem jest ATTiny.

Programy sterujące zostały przygotowane zarówno dla Raspberry Pi jak i dla ATTiny.

Głównym powodem dla którego taki układ mógłby być przydatny jest konieczność wyłączania i włączania Raspberry Pi gdy niemożliwe jest wyłączenie np. komendą i ponowne wpięcie kabla do układu w celu uruchomienia urządzenia. 

Raspberry Pi:


W związku z tym, że linia I2C jest wykorzystywana to wyłączanie zasilania wykonałem na innym pinie. Tutaj można wykorzystać właściwie każdy z dostępnych pinów.

Jedynym zadaniem tego programu, jest oczekiwanie na wciśniecie przycisku. Gdy zostanie on już wciśnięty to następuje odliczenie czasu, około 10s, co zostało rozwiązane za pomocą opóźnienia Po czasie 1 sekundy zostaje zwiększony licznik z danymi przechowującymi ilość odczekanych sekund.

Jak już odliczanie dobiegnie końca to następuje wyłączenie Raspberry Pi za pomocą komendy:

  1. sudo shutdown -h now

Cały kod umieszczony na raspberry został napisany w języku Python. I wygląda następująco:

  1. from gpiozero import Button, LED
  2. import time
  3. import os
  4. statusLine = LED(4)
  5. stopButton = Button(26)
  6. checkValue = 0
  7. statusLine.on()
  8. while True:
  9.     if stopButton.is_pressed:
  10.         time.sleep(1)
  11.         if stopButton.is_pressed:
  12.             checkValue = checkValue + 1
  13.         else:
  14.             checkValue=0
  15.         if checkValue==10:
  16.             checkValue=0
  17.             statusLine.off()
  18.             os.system("sudo shutdown now -h")
  19.             statusLine.off()
  20.     if checkValue==0:
  21.         time.sleep(1)

Do sterowania portami wykorzystałem bibliotekę gpiozero. Button odpowiada za sygnał wejściowy, LED za wyjściowy.

Skrypt musi być uruchamiany przy każdym włączeniu urządzenia. Pod tym linkiem można znaleźć kilka sposobów na wywołanie programu po starcie systemu LINK.

ATTiny:


Tutaj dzieje się już znacznie więcej, natomiast też nie jest to mocno skomplikowany projekt.

Zacznę od opisu części sprzętowej. Poniżej schemat dodatkowej płytki pomagającej wykonanie funkcji resetu:


Głównym układem zarządzającym jest ATtiny13. Do płyty został podłączony jeden przycisk który odpowiada za włączenie/wyłączenie urządzenia. Drugi przycisk odpowiada za awaryjny reset urządzenia.

Kolejnym elementem jest wyjście zasilania, które podaje napięcie na Raspberry Pi. Po włączeniu zasilania i spełnieniu warunków do uruchomienia wysterowywany jest tranzystor p mosfet.

Ponieważ do projektu jest dołączona bateria to wykorzystuje także przetwornik ADC. Gdy poziom baterii będzie za niski to układ automatycznie wyłączony Raspberry Pi, bądź nie pozwoli na jego uruchomienie. 

W związku z tym, że wykorzystywane jest ADC to niezbędne jest stabilne napięcie zasilania. Dlatego został dołączony stabilizator napięcie 5V. Tak aby napięcie zasilajce zawsze wynosiło 5V bez względu na stan baterii.

Ostatnie dwa wyprowadzenia służą do ustawiania i sprawdzania stanu ustawionego na RPI. Linia PB1 ustawiania stan wysoki w celu rozpoczęcia przez RPI odliczania do wyłączenia systemu.
Druga linia PB4 sprawdza stan na RPI. Gdy system wystartował to ta linia ustawiana jest na wysoko (przez RPI). Po wyłączeniu raspbiana linia podciągana jest do masy. Dzięki temu po stanie pinu można określić kiedy odciąć zasilanie.

Po wciśnięciu przycisku układ wystawia stan wysoki na jeden z pinów. Tak aby Raspberry pi mogło rozpocząć odliczanie.

ATTiny program:


Zacznę od definicji poszczególnych portów:

  1.     DDRB  &= ~SYS_OK_PIN;
  2.     PORTB &= ~SYS_OK_PIN;
  3.    
  4.     DDRB |= STANDBY_PIN;
  5.     PORTB |= STANDBY_PIN;
  6.     DDRB  &= ~BUTTON_PIN;
  7.     PORTB &= ~BUTTON_PIN;
  8.     DDRB |= ON_OFF_DC_PIN;
  9.     PORTB |= ON_OFF_DC_PIN;
  10.    
  11.     ADMUX |= (1 << MUX0)|(1 << MUX1); // select ADC3 (PB3)
  12.     ADCSRA |= (1 << ADPS1) | (1 << ADPS0) | (1 << ADEN);
  13.     ADMUX |= (0 << REFS0);

Powyżej opisane są ustawienia dla portów:
  • SYS-OK - Wejście informujące o działaniu Raspberry Pi. Gdy system uruchomiony to linia jest wysterowana wysoko.
  • STANDBY - Pin wyjściowy sterujący włączeniem/wyłączeniem urządzenia. Podłączony bezpośrednio do Raspberry.
  • BUTTON - Pod tą linię zostały podpięty przycisk włączania/wyłączania Raspberry.
  • ON_OFF_DC - Uruchomienie zasilania. 
  • ADC - Pomiar stanu baterii.

W pętli głównej sprawdzane są poszczególne elementy np. wykonywane pomiary ADC itp.

Na samym początku sprawdzany jest stan przycisku:

  1. if ((PINB & BUTTON_PIN) == BUTTON_PIN)
  2. {  
  3.     if((analogData > 630) && ((PINB & ON_OFF_DC_PIN) == ON_OFF_DC_PIN) && (PINB & SYS_OK_PIN) != SYS_OK_PIN)
  4.     {
  5.         PORTB &= ~ON_OFF_DC_PIN;    //Uruchomienie zasilania
  6.         enablePowerFlag = 1;
  7.     }
  8.     else if((PINB & SYS_OK_PIN) == SYS_OK_PIN)
  9.     {
  10.         PORTB &= ~STANDBY_PIN;
  11.         enablePowerFlag = 0;
  12.         checkOffBtnState_Flag = 1;
  13.     }
  14.     else
  15.     {
  16.         PORTB |= STANDBY_PIN;
  17.     }
  18. }

Tutaj sprawdzam czy przycisk został wciśnięty jeśli tak to należy się upewnić czy urządzenia jest wyłączone czy nie na podstawie pinu SYS_OK. Aby urządzenie mogło zostać poprawnie uruchomione to bateria musi być odpowiednio naładowana. To sprawdzam przez pomiar ADC.

Pomiar wartości ADC z prostym uśrednianiem:

  1. analogData = 0;
  2. for(uint8_t i=0; i<8; i++)
  3. {
  4.     analogData += adc_read();
  5.     _delay_ms(1);
  6. }
  7.    
  8. analogData = analogData/8;
  9. if(analogData < 630)
  10. {
  11.     counterADCValues++;
  12.     if(counterADCValues >= MAX_ADC_BELOW_VALUES)
  13.     {
  14.         counterADCValues = 0;
  15.         PORTB &= ~STANDBY_PIN;
  16.     }
  17. }
  18. else
  19. {
  20.     counterADCValues=0;
  21. }

Odczyt danych z przetwornika ADC:

  1. int adc_read (void)
  2. {
  3.     ADCSRA |= (1 << ADSC);
  4.    
  5.     while (ADCSRA & (1 << ADSC));
  6.    
  7.     return ADC;
  8. }

Sprawdzenie ustawienia flagi i utrzymywania sygnału na pinie przycisku:

  1. if((checkOffBtnState_Flag == 1) && (PINB & BUTTON_PIN) != BUTTON_PIN)
  2. {
  3.     PORTB |= STANDBY_PIN;
  4.     checkOffBtnState_Flag = 0;
  5. }

Teraz część dotycząca wyłączania/załączania zasilania w zależności od stanu na pinach:

  1. if((PINB & SYS_OK_PIN) == SYS_OK_PIN)      
  2. {
  3.     enablePowerFlag = 0;
  4. }
  5. else if(((PINB & SYS_OK_PIN) != SYS_OK_PIN) && enablePowerFlag == 1)
  6. {
  7.     _delay_ms(1000);
  8.     bootingCounter++;
  9.    
  10.     if(bootingCounter == 40) //Błąd działania wyłącz urządzenie
  11.     {
  12.         bootingCounter = 0;
  13.         PORTB &= ~STANDBY_PIN;
  14.     }  
  15. }
  16. else if(((PINB & SYS_OK_PIN) != SYS_OK_PIN) && enablePowerFlag == 0)
  17. {
  18.     _delay_ms(10000);
  19.     PORTB |= ON_OFF_DC_PIN;
  20.     PORTB |= STANDBY_PIN;
  21. }

Tutaj sprawdzam stan na pinach. Po wykryciu sygnału niskiego na pinie SYS_OK system odczekuje krótki interwał czasowy po czym wyłącza zasilanie. Przerwa czasowa jest przewidziana na wyłączenie Raspberry, gdbyby z jakichś powodów zajęło to dłuższy odcinek czasu. W przypadku braku reakcji urządzenie zostanie wyłączone siłowo przez odcięcie zasilania jeśli czas na odłączenie będzie większy niż zadany.

Poniżej cały program dla ATTiny:

  1. #define F_CPU 9600000
  2. #include <avr/io.h>
  3. #include <util/delay.h>
  4. #include <avr/interrupt.h>
  5. #define ON_OFF_DC_PIN   (1<<PB0)
  6. #define STANDBY_PIN (1<<PB1)
  7. #define SYS_OK_PIN  (1<<PB4)
  8. #define AKU_ADC_PIN (1<<PB3)        //ADC3
  9. #define BUTTON_PIN  (1<<PB2)
  10. #define MAX_ADC_BELOW_VALUES  10
  11. volatile uint16_t analogData = 0;  
  12. volatile uint8_t counterADCValues = 0;
  13. volatile uint8_t enablePowerFlag = 0;
  14. volatile uint16_t bootingCounter = 0;
  15. volatile uint8_t checkOffBtnState_Flag = 0;
  16. int adc_read (void)
  17. {
  18.     ADCSRA |= (1 << ADSC);
  19.     while (ADCSRA & (1 << ADSC));
  20.     return ADC;
  21. }
  22. int main(void)
  23. {
  24.     DDRB  &= ~SYS_OK_PIN;
  25.     PORTB &= ~SYS_OK_PIN;
  26.     DDRB |= STANDBY_PIN;
  27.     PORTB |= STANDBY_PIN;
  28.     DDRB  &= ~BUTTON_PIN;
  29.     PORTB &= ~BUTTON_PIN;
  30.     //URUCHOMIENIE ZASILANIA
  31.     DDRB |= ON_OFF_DC_PIN;
  32.     PORTB |= ON_OFF_DC_PIN;
  33.    
  34.     ////ADC
  35.     ADMUX |= (1 << MUX0)|(1 << MUX1); // select ADC3 (PB3)
  36.     ADCSRA |= (1 << ADPS1) | (1 << ADPS0) | (1 << ADEN);
  37.     ADMUX |= (0 << REFS0);
  38.            
  39.     while(1)
  40.     {
  41.         if ((PINB & BUTTON_PIN) == BUTTON_PIN)
  42.         {  
  43.             if((analogData > 630) && ((PINB & ON_OFF_DC_PIN) == ON_OFF_DC_PIN) && (PINB & SYS_OK_PIN) != SYS_OK_PIN)
  44.             {
  45.                 PORTB &= ~ON_OFF_DC_PIN;
  46.                 enablePowerFlag = 1;
  47.             }
  48.             else if((PINB & SYS_OK_PIN) == SYS_OK_PIN)
  49.             {
  50.                 PORTB &= ~STANDBY_PIN;
  51.                 enablePowerFlag = 0;
  52.                 checkOffBtnState_Flag = 1;
  53.             }
  54.             else
  55.             {
  56.                 PORTB |= STANDBY_PIN;
  57.             }
  58.         }
  59.         //------------------------------------------------------------------------
  60.         if((checkOffBtnState_Flag == 1) && (PINB & BUTTON_PIN) != BUTTON_PIN)
  61.         {
  62.             PORTB |= STANDBY_PIN;
  63.             checkOffBtnState_Flag = 0;
  64.         }
  65.         //------------------------------------------------------------------------
  66.         //Wylaczenie zasilania gdy nie ma nic na pinie SYS_OK      
  67.         if((PINB & SYS_OK_PIN) == SYS_OK_PIN)      
  68.         {
  69.             enablePowerFlag = 0;
  70.             //System dzia�a
  71.         }
  72.         else if(((PINB & SYS_OK_PIN) != SYS_OK_PIN) && enablePowerFlag == 1)
  73.         {
  74.             _delay_ms(1000);
  75.             bootingCounter++;
  76.            
  77.             if(bootingCounter == 40) //B��d dzia�ania wy��cz urz�dzenie
  78.             {
  79.                 bootingCounter = 0;
  80.                 PORTB &= ~STANDBY_PIN;
  81.             }
  82.         }
  83.         else if(((PINB & SYS_OK_PIN) != SYS_OK_PIN) && enablePowerFlag == 0)
  84.         {
  85.             _delay_ms(10000);
  86.             PORTB |= ON_OFF_DC_PIN;
  87.             PORTB |= STANDBY_PIN;
  88.         }
  89.         //-----------------------------------------------------------------------
  90.         //Pomiar ADC
  91.         analogData = 0;
  92.         for(uint8_t i=0; i<8; i++)
  93.         {
  94.             analogData += adc_read();
  95.             _delay_ms(1);
  96.         }
  97.        
  98.         analogData = analogData/8;
  99.         if(analogData < 630)
  100.         {
  101.             counterADCValues++;
  102.             if(counterADCValues >= MAX_ADC_BELOW_VALUES)
  103.             {
  104.                 counterADCValues = 0;
  105.                 PORTB &= ~STANDBY_PIN;
  106.             }
  107.         }
  108.         else
  109.         {
  110.             counterADCValues=0;
  111.         }
  112.     }
  113. }