niedziela, 26 czerwca 2022

Kosiarka z regulatorem PID / PID controlled lawnmower






Do przepustnicy jest przymocowane serwo. Sygnał obrotów jest brany z alternatora sprzed mostka prostowniczego. Utrzymuje stałą zadaną liczbę obrotów, niezależnie od obciążenia. Nastawy dobrane eksperymentalnie. 

There is a servo attached to the throttle. The RPM signal is taken from the alternator before the rectifier bridge. It maintains a constant rpm, regardless of load. Settings selected experimentally.

CODE:


#include <Arduino.h>
#include <Servo.h>
#include <PID_v1.h>

#define TACH_PIN 3

#include <Tachometer.h>

Tachometer tacho;

double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint,1,1,0.125, REVERSE);

#define ERPM 800
#define MAX_SERVO 140
#define MIN_SERVO 70

int potpin = 0;  // analog pin used to connect the potentiometer
int val;    // variable to read the value from the analog pin
int currentFreq = 0;
double sum = 0;
double nanoseconds = 0;
int count = 0;
int currentServo = (MAX_SERVO+MIN_SERVO)/2;
double currentRPM = 0;
double LRPM = 0;
int bck1, bck2, bck3, bck4;
Servo myservo;  // create servo object to control a servo

void isr() {
  tacho.tick();   // сообщаем библиотеке об этом
}

void setup() {
  // пин тахометра вентилятора подтягиваем к VCC
  pinMode(TACH_PIN, 0);

  // настраиваем прерывание
  attachInterrupt(digitalPinToInterrupt(3), isr, FALLING);

  Setpoint = 800;

  //turn the PID on
  myPID.SetMode(AUTOMATIC);

  Serial.begin(9600);

   Serial.println("start setuu");
   Serial.println("pinMode 13 1");
  pinMode(13,1);

   Serial.println("zapalam diode");
  digitalWrite(13,1);
  Serial.println("attaching servo");
  myservo.attach(11);  // attaches the servo on pin to the servo object
 
 Serial.println("servo min");
  myservo.write(MIN_SERVO);  
  delay(500);
 Serial.println("servo center");
  myservo.write((MAX_SERVO+MIN_SERVO)/2);  
  delay(500);
   Serial.println("diode gasze");
  digitalWrite(13,0);
   Serial.println("koniec setupu");
   bck1 = TCCR0A;
   bck2 = TCCR0B;
  bck3 = TCCR1A;
   bck4 = TCCR1B;
   
}

void loop() {



    //Serial.println(tacho.getRPM());     // RPM
    //Serial.println(tacho.getHz());    // Hz
    //Serial.println(tacho.Us());       // us

currentRPM = (3*currentRPM + (tacho.getHz()*2.0))/4.0;

  //if(currentRPM < 750 && val > MIN_SERVO) val--;
  //if(currentRPM > 850 && val < MAX_SERVO) val++;
  //if(currentRPM < 20) val = (MAX_SERVO+MIN_SERVO)/2;
  //if(currentRPM > 1800) val = MAX_SERVO;


  Input = (currentRPM+LRPM)/2.0;

  myPID.Compute();
  val = map(Output, 0, 255, MIN_SERVO, MAX_SERVO);


  if(val > MAX_SERVO) val = MAX_SERVO;
  if(val < MIN_SERVO) val = MIN_SERVO;

Serial.print(Input);
Serial.print("  ");
Serial.print(Output);
Serial.print("  ");
Serial.println(val);

  myservo.write(val);                  // sets the servo position according to the scaled value
  digitalWrite(13,1);
  delay(50);                           // waits for the servo to get there
  digitalWrite(13,0);
  delay(50);
LRPM = currentRPM;
}


niedziela, 12 maja 2019

Sterownik RGB na ESP8266


Wykonałem go na koło naukowe techniki świetlnej. Na płytce PCB wytrawiłem przetwornicę na MC34063 z nawiniętą przeze mnie małą cewką, którą dobrałem podłączając do oscyloskop i sprawdzając częstotliwość rezonansu układu LC. Przetwornica step down 12V na 5V, do której podłączyłem płytkę z ESP8266, oraz 3 tranzystory do sterowania poszczególnymi kolorami. 

ESP8266 działa jako Access Point, podłącza się do niego przez wifi. po przejściu na jakąkolwiek stronę internetową w przeglądarce przekierowuje na serwerek też postawiony na ESP, gdzie widoczna jest tęcza po której przesuwając palcem można wybierać kolory.

Został użyty na korytarzu Wydzuału Elektrycznego Politechniki Białostockiej do sterowania paskiem RGB w lustrze nieskończoności.


Drukarka 3D z niczego


Kosztowała może ze 150zł. Zbudowałem ją z płyty wiórowej po to żeby przetestować wszystkie rzeczy które kupiłem serownik ramps głowicę silniki. Udało mi się wszystko skonfigurować i teraz myślę nad zrobieniem metalowej, trwałej wersji z ulepszoną głowicą, która tutaj cierpiała na zbyt duże siły podawania filamentu, które nagrzewały nadmiernie silnik, w następnej wersji trzeba będzie zastosować przełożenie.

piątek, 14 kwietnia 2017

Generator funkcyjny na Arduino


Generator został wykonany na AtMega8 z podłączoną do niej drabinką rezystorową i kilkoma opampami.


Po przylutowaniu diod i przycisków:

Testowanie:

Musiałem zmienić trochę konfigurację układu analogowego bo użyłem opampów które nie współpracują z niesymetrycznym zasilaniem.
Po włożeniu do obudowy:


Zmontowany układ podłączony do oscyloskopu:
Sinus:

Trapez:


Trójkąt:

PWM:


Na drugim BNC podłączyłem wyjście PWM 0-5V regulowane potencjometrem.





DAC z drabinki rezystorowej


Do Arduino podłączyłem drabinkę rezystorową R 2R. Mogłem dzięki temu generować dowolne kształty przebiegów na oscyloskopie.


Trójkąt:

Sinus:



czwartek, 30 marca 2017

Mikrofonowanie kondensatorów ceramicznych w oscyloskopie


Drgania mechaniczne przenoszone z konstrukcji oscyloskopu na kondensatory ceramiczne wewnątrz oscyloskopu powoduja zmiane pojemnosci kondensatora. Powoduje to zmiane wartosci napiec na kondensatorach i widoczny sygnal na ekranie oscyloskopu.
Problem dla roznych oscyloskopow jest pokazany w filmiku ponizej. 


Mi nie udało się wyzwolić oscyloskopu na tym sygnale niestety. Nie jest on zbyt podatny na stukanie w obudowę. Jedynie małą wartość sygnału udało mi się zarejestrować w trybie scan, kiedy bezpośrednio ostro tłukłem w BNC.

środa, 29 marca 2017

Prosta biblioteka do obsługi sterownika LCD HD44780 dla mikrokontrolerów PIC



Z braku dobrze działających i prostych w użyciu bibliotek postanowiłem napisać swoją, jak najprostszą w pisaniu i użyciu.

Poniżej umieściłem zestaw funkcji z biblioteki. Działa ona w 4-bitowym interfejsie z odczytem flagi BF. Można podpiąć RW wyświetlacza do masy i wtedy trzeba zastąpić funkcję busy opóźnieniem około 50us.

void BUS_set(unsigned char dataToSet){
    LCD_DATA4_LAT = (dataToSet & ( 1 << 0 )) >> 0;  
    LCD_DATA5_LAT = (dataToSet & ( 1 << 1 )) >> 1;   
    LCD_DATA6_LAT = (dataToSet & ( 1 << 2 )) >> 2;   
    LCD_DATA7_LAT = (dataToSet & ( 1 << 3 )) >> 3;   
}

void BUS_write(unsigned char dataToWrite){
LCD_RW_LAT = LOW;

LCD_E_LAT = HIGH;
BUS_set(dataToWrite >> 4);
LCD_E_LAT = LOW;

LCD_E_LAT = HIGH;
BUS_set(dataToWrite);
LCD_E_LAT = LOW;

while(LCD_busy());

}

void LCD_command(unsigned char commandToWrite){
LCD_RS_LAT = LOW;
BUS_write(commandToWrite);
}

void LCD_data(unsigned char dataToWrite)
{
LCD_RS_LAT = HIGH;
BUS_write(dataToWrite);
}

void LCD_text(char * text)
{
while(*text)
LCD_data(*text++);
}

void LCD_fills(unsigned char j)
{
while(j > 0){
LCD_data(0xFF);
j--;
}
}

void LCD_clear(void)
{
LCD_command(0x01);
delay(2);
}

void LCD_home(void)
{
LCD_command(0x02);
delay(2);

}

void LCD_pos(unsigned char row, unsigned char position)
{
    if(row == 0){
        LCD_command(0x80);
        while(position > 0){
            LCD_command(0x14);
            position--;
        }
    } else {
        LCD_command(0xC0);
        while(position > 0){
            LCD_command(0x14);
            position--;
        }
    }
}

void LCD_int(unsigned long  tmp){
char buffer[16]; 
sprintf(buffer,"%ld", tmp);
LCD_text(buffer);

}

void LCD_float(double  tmp){
char buffer[16]; 
sprintf(buffer,"%.3f", tmp);
LCD_text(buffer);
}


void LCD_initalize(void)
{
LCD_RW_DIR = OUTPUT;
LCD_E_DIR = OUTPUT;
LCD_RS_DIR = OUTPUT;
LCD_BUS_OUTPUT;
delay(15); 
LCD_RW_LAT = LOW;
LCD_RS_LAT = LOW;
LCD_E_LAT = HIGH;

for(unsigned char i = 0; i < 3; i++)
  {
  LCD_E_LAT = HIGH;
  BUS_set(0x03);
  LCD_E_LAT = LOW;
  delay(5);
  }

  LCD_E_LAT = HIGH;
  BUS_set(0x02);
  LCD_E_LAT = LOW;
  delay(1);


   LCD_command(40);
   LCD_command(1);
   delay(2);

   LCD_command(12);
   LCD_command(1);
   delay(5);
}

unsigned char LCD_busy(){
LCD_BUS_INPUT;
LCD_RW_LAT = HIGH;
LCD_RS_LAT = LOW;
unsigned char bf = LCD_DATA7_REA;
LCD_RW_LAT = LOW;
LCD_RS_LAT = LOW;
LCD_BUS_OUTPUT;
return bf;
}

Przekładnik prądowy z filtra przeciwzakłóceniowego



Nawinąłem na otwierany rdzeń ferrytowy 300 zwojów najcieńszego drutu nawojowego jakiego znalazłem.


Po podłączeniu lutownicy ok 40W i rezystora próbkującego na oscyloskopie pokazał mi się sygnał o Vpp około 30mV. Odpowiednio zmieniając rezystor na wyjściu przekładnika możnaby otrzymać żądaną zależność pomiędzy prądem a napięciem na wyjściu przekładnika. 







piątek, 24 marca 2017

Sprzężenie pojemnościowe - bezprzewodowy przesył energii elektrycznej




Podłączyłem pod płytkę MPLAB Xpress dwie elektrody zrobione z folii aluminiowej przyklejonej do plastikowej folii. Na nie położyłem dwie następne elektrody, podłączone bezpośrednio do diody LED. Na piny z podłączonymi elektrodami piściłem sygnał różnicowy prostokątny o częstotliwości niecałych 2MHz. Jakaś moc przepływała do diody bo jak widać się świeciła, jednak aby przesłać jej więcej należałoby zwiększyć częstotliwość sygnału aby przypominał on bardziej sinusoidę a nie prostkąt. Taśmy były po to potrzebne żeby zwiększyć nieco siłę nacisku elektrod.





Mini przetworniczka 3V na 12V

Schemat z noty katalogowej MC34063. Cewka dobrana oscyloskopem.



Pomysł na przekształtnik symetryczny podwyższający

Pomysł na wykonanie symetrycznego zasilacza np. do opampów +/- 12V np z akumulatorka Li-ion.


Zegar binarny




Zegar wykonałem na mikrokontrolerze PIC12F1840 i rejestrach szeregowych sterujących diodami. Poszczególne kolumny diod wyświetlają cyfry. Całość jest taktowana eksperymentalnie kwarcem 16MHz zamiast 32kHz. Schemat poniżej.




Płytka wykonana chałupniczo, jak najszybciej i bez pomyślunku niestety bo wypadałoby dodać parę rzeczy.







Program zegara jest bardzo prosty, konfiguracja IO i peryferiów mikrokontrolera została graficznie wykonana w MCC a reszta napisana przez mnie. Poniżej zawartość pliku main.c Nawet nie chciało mi sie konfigurować sprzętowego SPI do sterowania rejestrem więc zbitbangowałem go funkcją podobną do shiftOut z arduino. W godzinach od 21-7 rano zegar świeci około 10% mocy z jaką świeci w ciągu dnia. Brzydko to jest dość napisane ale mój czas jest zbyt cenny żeby marnować go na poprawianie głupiego zegarka 



#include "mcc_generated_files/mcc.h"

char setting = 0;
char ms = 0;
char s = 0;
char m = 0;
char h = 0;
char b1, b2;
void shiftOut(char val)
{
 
    for (char i = 0; i < 8; i++)  {
     
     
        LATA0 = !!(val & (1 << (7 - i)));
        SCK_SetHigh();
        SCK_SetLow();
       
    }
}
void shift(char byte1, char byte2){
     nSS_SetLow();
     
        shiftOut(byte1);
        shiftOut(byte2);
     
        nSS_SetHigh();
}
void checkTime(){
    if(ms > 9){ ms = 0; s++;}
    if(s > 59){ s = 0; m++; }
    if(m > 59){ m = 0; h++; }
    if(h > 23){ h = 0; m = 0; s = 0; ms = 0;}
   
}
void timer0interrupt(){
    ms++;
    checkTime();
}
void delay(unsigned int mss){
    while(mss > 1){
        __delay_ms(1);
        mss--;
    }
}
void prepareBuffer(){
   
     char h10, h1, m10, m1;
   
    h10 = h / 10;
        h1 = h % 10;
        m10 = m /10;
        m1 = m % 10;
     
        if(m10 & (1<<0)) b2 = b2 | (1<<0); else b2 &= ~(1<<0);
        if(m10 & (1<<1)) b2 = b2 | (1<<1); else b2 &= ~(1<<1);
        if(m10 & (1<<2)) b2 = b2 | (1<<2); else b2 &= ~(1<<2);
       
        if(m1 & (1<<0)) b2 = b2 | (1<<3); else b2 &= ~(1<<3);
        if(m1 & (1<<1)) b2 = b2 | (1<<4); else b2 &= ~(1<<4);
        if(m1 & (1<<2)) b2 = b2 | (1<<5); else b2 &= ~(1<<5);
        if(m1 & (1<<3)) b2 = b2 | (1<<6); else b2 &= ~(1<<6);
       
        if(h10 & (1<<0)) b1 = b1 | (1<<0); else b1 &= ~(1<<0);
        if(h10 & (1<<1)) b1 = b1 | (1<<1); else b1 &= ~(1<<1);
       
        if(h1 & (1<<0)) b1 = b1 | (1<<2); else b1 &= ~(1<<2);
        if(h1 & (1<<1)) b1 = b1 | (1<<3); else b1 &= ~(1<<3);
        if(h1 & (1<<2)) b1 = b1 | (1<<4); else b1 &= ~(1<<4);
        if(h1 & (1<<3)) b1 = b1 | (1<<5); else b1 &= ~(1<<5);
       
}
void buttonHandler(){
    if(BUTTON_GetValue() == 0){
        unsigned int cnt = 0;
        while(BUTTON_GetValue() == 0){
            __delay_ms(1);
            cnt++;
            if(cnt > 3500){
                shift(0xFF, 0xFF);
            }
        }
       
        if(cnt > 3500){
            if(setting == 1) setting = 0; else setting = 1;
        } else
            if(cnt < 1500 && cnt > 100){
               
       INTERRUPT_GlobalInterruptDisable();
       prepareBuffer();
       shift(b2, b1);
       delay(10);
       if(setting == 0){
           s = 0;
           ms = 0;
        h++;
        if(h > 23) h = 0;
       } else {
           s = 0;
           ms = 0;
         m++;
        if(m > 59) m = 0;
       }
        prepareBuffer();
       shift(b2, b1);
       delay(200);
       
       
        INTERRUPT_GlobalInterruptEnable();
   
            }
    }
}

void main(void)
{
 
    SYSTEM_Initialize();
TMR1_SetInterruptHandler(&timer0interrupt);
INTERRUPT_GlobalInterruptEnable();
INTERRUPT_PeripheralInterruptEnable();
   
   
 

    while (1)
    {
     
     
       
       prepareBuffer();
     
      if(s % 2 == 1) b2 = b2 | (1<<7); else b2 &= ~(1<<7);
      if(setting == 1) b1 &= ~(1<<7); else  b1 |= (1<<7);
      if(setting == 0) b1 &= ~(1<<6); else  b1 |= (1<<6);  
   
       if(h < 7 || h > 21){
           b2 &= ~(1<<7);
           if(s % 2 == 1) b1 &= ~(1<<7); else  b1 |= (1<<7);
       }
     
       buttonHandler();
     
       if(h < 7 || h > 21){
       shift(0x00,0x00);
       __delay_us(90);
        shift(b2, b1);
        __delay_us(10);
        shift(0x00,0x00);
       
       } else {
           shift(b2, b1);
       }
       
    }
}






Zegar na wyświetlaczu 7 segmentowym


Projekt został wykonany na mikrokontrolerze PIC i rejestrze szeregowym używanym do sterowania katodami zegara. Schemat zegara jest widoczny poniżej.


Płytkę wykonywałem i projektowałem jak najszybciej i bez zbędnych, dodatkowych elementów takich jak papier kredowy :) wystarczy gazeta. Płytka wyszła beznadziejna ale działała a to najważniejsze.




Program do mikrokontrolera pisałęm w MPLABie, też jak najszybciej i na odwał. Głównie zależało mi na tym aby działał. Do konfiguracji mikrokontrolera użyłem Microchip Code Configurator.

Miałem dorzucić jeszcze zmianę jasności zależnie od oświetlenia zewnętrznego ale wyświetlacz jest wystarczająco ciemny przez nieco za duże oporniki w stosunku do jego jakości ;)

#include "mcc_generated_files/mcc.h"
unsigned int s = 0;
unsigned int m = 0;
unsigned int h = 0;
char check_flag = 1;


char digits[10];
char number[4];

void checkTime(){
    if(check_flag == 1){
    if(s > 59){ s = 0; m++; }
    if(m > 59){ m = 0; h++; }
    if(h > 23){ h = 0; m = 0; s = 0;}
    }
}
void timer_int(){
   
    s++;
    checkTime();
   
   
}
void numbersToDisplay(unsigned int value, unsigned int value2){
   
  char digit = value % 10;
  number[3] = digits[digit];
  value /= 10;
  digit = value % 10;
  number[2] = digits[digit];
  value /= 10;
 
 
  digit = value2 % 10;
  number[1] = digits[digit];
  value2 /= 10;
 
  digit = value2 % 10;
  number[0] = digits[digit];
  value2 /= 10;
 
}

void multiplex(unsigned int times){
  for(int i = 0;i < times;i++){
        ANODE4_SetHigh();
        ANODE3_SetHigh();
        ANODE2_SetHigh();
        ANODE1_SetHigh();
   
       
ANODE1_SetLow();      
LATCH_SetLow();
SPI_Exchange8bit(number[0] & 0b11111110);
LATCH_SetHigh();
__delay_ms(3);
ANODE1_SetHigh();
     

ANODE2_SetLow();      
LATCH_SetLow();
SPI_Exchange8bit(number[1]);
LATCH_SetHigh();
__delay_ms(3);
ANODE2_SetHigh();
 

ANODE3_SetLow();      
LATCH_SetLow();
SPI_Exchange8bit(number[2] );
LATCH_SetHigh();
__delay_ms(3);
ANODE3_SetHigh();
   

ANODE4_SetLow();      
LATCH_SetLow();
SPI_Exchange8bit(number[3]);
LATCH_SetHigh();
__delay_ms(3);
ANODE4_SetHigh();
   
  }
 
}
void driveLEDs(unsigned int periods_number){
        checkTime();
        numbersToDisplay(m,h);
        if(s % 2 == 1) number[2] &= 0b11111110;
        multiplex(periods_number);
   
}
void debug1(unsigned int periods_number){
        //checkTime();
        numbersToDisplay(0,88);
        if(s % 2 == 1) number[2] &= 0b11111110;
        multiplex(periods_number);
   
}
void buttons(){
   
    if(BUTTON1_GetValue() == 0){
        check_flag = 0;
       driveLEDs(5);
        m++;
        if(m > 59) m = 0;
        s = 0;
        driveLEDs(50);
       
       
       
            while(BUTTON1_GetValue() == 0){
           
                m++;
                if(m > 59) m = 0;
                driveLEDs(10);
                 s = 0;
        }
    }
   
    if(BUTTON2_GetValue() == 0){
       check_flag = 0;
       driveLEDs(10);
        h++;
        if(h > 23) h = 0;
        driveLEDs(50);
       
       
       
    }
    check_flag = 1;
}
void lightSensor(){
   
    ADC1_StartConversion(LDR);
    while(ADC1_IsConversionDone() == 0) driveLEDs(1);
    if(ADC1_GetConversionResult() < 716)
    EPWM1_LoadDutyValue(ADC1_GetConversionResult());
}
void main(void)
{
digits[0] = 0b10000001;
digits[1] = 0b11110011;
digits[2] = 0b01001001;
digits[3] = 0b01100001;
digits[4] = 0b00110011;
digits[5] = 0b00100101;
digits[6] = 0b00000101;
digits[7] = 0b11110001;
digits[8] = 0b00000001;
digits[9] = 0b00100001;
SYSTEM_Initialize();
INTERRUPT_GlobalInterruptEnable();
INTERRUPT_PeripheralInterruptEnable();
TMR1_SetInterruptHandler(&timer_int);
TRISC5 = 0;
LATC5 = 0;
//EPWM1_Initialize();
LATC5 = 0;
for(int i = 0;i < 4;i++) number[i] = 0x00;
multiplex(200);
for(int i = 0;i < 4;i++) number[i] = 0xFF;
multiplex(30);
LATC5 = 0;
    while (1)
    {
        //debug1(1);
      driveLEDs(20);
     buttons();
     //lightSensor();
      // EPWM1_LoadDutyValue(100);
     
    }
}










Nuda mnie do tego zmusiła


Przetwornik C/A z drabinki rezystorowej, sterowany licznikiem, który jest kluczowany przez generator sygnału zegarowego na NE555. Wyjściem tego układu jest sygnał trójkątny.

Jest to pewna namiastka generatora DDS (direct digital synthesis - bezpośrednia synteza cyfrowa), gdyby zamiast bramek i licznika dorzucić mikrokontroler, można byłoby uzyskać dowolne kształty funkcji.

Sygnał wysyłany przez NFC w telefonie


Bezpieczniczek

Wysokonapięciowa wkładka bezpiecznikowa "tylko" 40A.

wtorek, 15 lipca 2014

Klon PICkit2 z regulacją napięcia



    Schematy klonów tego programatora jak i orygnalny schemat zawierają komponenty egzotyczne, których zdobycie może być ciężkie i dość drogie w porównaniu do mojej wersji. Ograniczyłem użycie dziwnych podzespołów pomijając mikrokontroler PIC18F2550, który musiałem zostawić. ;) 



Tranzystory użyte w mojej implementacji mogą być dowolne, byleby miały dozwolony prąd kolektora nie mniejszy niż kilkaset miliamper i zgadzały się z typem PNP lub NPN. W projekcie używłem najtańszego (1zł za 10 sztuk) opampa jakiego można sobie wyobrazić LM358. Niestety płytka była wykonana w montażu przewlekanym. Można znacznie zmniejszyć jej wymiary używając podzespołów SMD, przynajmniej oporników i opampa.



 Po zmontowaniu trzeba zkalibrować napięcia na wyjściu programatora w programie do jego obsługi, można go ściągnąć ze strony Microchipa.