PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : 1S LiPo Überwachung



Siro
07.05.2020, 22:02
Hallo zusammen,
ich möchte euch hier mal meine Minischaltung für eine 1S LiPo Überwachung vorstellen.

In meiner Heli-Gruppe haben inzwischen mehrere von uns diesen kleinen 3D Hubschrauber
Modell: XK Blast K110
Betrieben wird er mit einemn 1S LiPo Akku.

34985

Das Problem:
Irgendwann ist der Akku leer und der Heli fällt wie ein Stein vom Himmel....:(

Mal abgesehen davon, dass dies zu Schäden am Heli führen kann, ist das für den LiPo auch nicht gesund.
Ich habe mal geprüft wann der Heli aussteigt und das passiert bei ca. 2,8 Volt.
Dann ist der Akku also auch schon unterladen.
Unter 3 Volt sollte ein LiPo eigentlich nicht entladen werden.

So kam mir die Idee eine möglichst kleine/leichte Schaltung zu basteln,
die mittels Blinken den Akkustand anzeigt.

Da ich ein Microchip PIC Fetischist bin, schaute ich mal nach dem Kleinsten den man bekommen und auch noch selbst löten kann.
Die Wahl fiel dann auf den PIC10F322 mit 6 Beinchen im SOT23-6 Gehäuse.

Er hat eine interne Referenzspannung und er hat auch einen ADU Wandler.
Mehr benötige ich eigentlich auch nicht.

Den PIC bekommt man für 59 Cent bei Reichelt Elektronik
https://www.reichelt.de/mcu-picmicro-0-9-kb-16-mhz-sot-23-6-pic-10f322-iot-p121699.html?r=1

Schöne, kleine LEDs hatte ich schon aus Vorversuchen auch bei Reichelt besorgt.
Sogenannte "Point Leds"

grün:
https://www.reichelt.de/point-led-smd-gruen-710-mcd-120-lt-p4sg-p111531.html?r=1

rot:
https://www.reichelt.de/point-led-smd-rot-560-mcd-120-lr-p47f-p111528.html?&nbc=1&trstct=lsbght_sldr::111531

Absolut geil würde ich die mal nennen.
Mit 3mA sind die schon derart hell, dass man kaum reingucken kann.
Kosten der grünen 35 Cent das Stück, die roten sogar nur 24 Cent

Ausser einem Vorwiderstand für die LED und ein Entkopplungskonensator für den PIC
benötigt man nichts weiter. Ich habe dennoch 2 LEDs spendiert und 2 Widerstände, einfach der Optik wegen.

Also hier mal die Schaltung:
34986 34992

Der Keramikkondensator C1 war ursprünglich mit 100nF völlig ausreichend.
Auf dem Steckbrett brauchte ich auch diesen nicht einmal.
Versuche später direkt am Hubi ließen aber die gesamte Schaltung ausflippen.
Ursache sind die Motoren des Hubis. Die ziehen mal locker 4 Ampere und das spiegelt sich
mit einem Ripple auf der Vorsorgung wieder. Ich hab jetzt einfach mal den Kondi auf 10µF vergrößert und das reicht völlig aus.

Die gesamte Schaltung, Leiterplatte bestückt mit Zinn und Kabel wiegt weniger als ein halbes Gramm.
34987


Die Leiterplatte habe ich mit dem Photoverfahren selbst belichtet/entwickelt/geätzt.
Als Platinenmaterial habe ich 0,5mm Stärke genommen, das kann man sogar noch mit einer Schere zurecht schnippeln.
Das Platinenmaterial (einseitig mit Fotolack) habe ich bei Segor Electronic gekauft.
34988 bestückt: 34991

Die Leiterplatte habe ich so ausgelegt, dass man sie direkt mit doppelseitigem Klebeband hinten auf die beiden Servos
kleben kann. So kann man die LEDs auch gut erkennen wenn der Heli mit dem Heck vor einem schwebt.
34989


Die beiden Kabel Plus und Minus werden durch die beiden Servos bis nach vorne verlegt und dort,
als hätte man mit meiner Schaltung schon gerechnet, gibt es zwei Lötpunkte bezeichnet mit B+ und B-
wo sie dann einfach aufgelelötet werden. Mehr gibt es nicht zu tun.
34990


Den Programmcode und die genaue Funktionsweise gibt es natürlich auch noch....

- - - Aktualisiert - - -

Anbei der momentane Programmcode:


/*
File: main.c
Date: 07.05.2020
Author: Siro

Projekt: 1S LiPo-Blitzer mit PIC10F322
*/

// PIC10F322 Configuration Bit Settings
// 'C' source line config statements

// CONFIG
#pragma config FOSC = INTOSC // Oscillator Selection bits (INTOSC oscillator: CLKIN function disabled)
#pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF // MCLR Pin Function Select bit (MCLR pin function is digital input, MCLR internally tied to VDD)
#pragma config CP = OFF // Code Protection bit (Program memory code protection is disabled)
#pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)
#pragma config LPBOR = OFF // Brown-out Reset Selection bits (BOR disabled)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)

#include <xc.h> // die einzige Headerdatei die benötigt wird.

/*----------------------------------------------------------------------*/
/* hier habe ich meine eigenen Datentypen definiert: */
typedef unsigned char BOOL; /* 0=FALSE, all others are TRUE */
typedef unsigned char U8; /* 8 Bit ohne Vorzeichen */
typedef unsigned short U16; /* 16 Bit ohne Vorzeichen */
typedef unsigned long U32; /* 32 Bit ohne Vorzeichen */

/* dem Datentyp BOOL sollten nur diese Konstanten zugeordnet werden: */
#define FALSE (0) /* Eine 0 bedeutet FALSE (falsch) */
#define TRUE (!FALSE) /* alle anderen Werte bedeuten TRUE (wahr) */

// Die HARDWARE : lediglich 2 LEDs

// LED 1 connected to pin RA0
#define LED1_ON LATA0=1
#define LED1_OFF LATA0=0

// LED 2 connected to pin RA1
#define LED2_ON LATA1=1
#define LED2_OFF LATA1=0

//------ einige Konstanten

// die LEDs blinken kurz hintereinander 1, 2, 3 oder 4 mal
// danach folgt eine längere Pause von 1,5 Sekunden
#define EXTRA_LONG_TIME 1500; // längere Zeit zwischen den Blinkphasen
#define LONG_TIME 300 // LED on time (ms)
#define SHORT_TIME 200 // LED off time (ms)

// Blinkphase wenn Akku leer < 3,2 Volt
#define FAST_SHORT_TIME 100 // LED on time (ms)
#define FAST_LONG_TIME 100 // LED off time (ms)

#define ACCU_EMPTY_MV 3200 // empty if voltage is below, fast blink
#define SLEEP_VOLTAGE_MV 3000 // go in sleepmode below this voltage

#define TIME_BEFORE_STANDBY 2000 // after 2 sec below 3,0 Volt set Standby Mode

// die beiden Zeitwerte werden im 1ms Interrupt verändert, deshalb volatile
volatile U16 BlinkTimeMS;
volatile U16 UnderVoltageTime; // Zeit wie lange wir unter 3 Volt waren

BOOL BlinkPhase;
U8 BlinkCount;
U8 VoltageBlinkCount;

U32 value; // wird zur Berechnung benötigt 32 Bit Wert
U16 mV_Value; // calculated Millivolts


//------------------------------------------------------------------------------
// global interrupt function
void __interrupt() isr(void)
{
if (TMR0IF) // Timer 0 ueberlauf 1ms
{
TMR0 = 7; // restart value for timer 0
TMR0IF = 0; // clear interrupt flag from timer 0

if (BlinkTimeMS)
BlinkTimeMS--;

if (UnderVoltageTime) // der 2 Sekunden Timer
UnderVoltageTime--;
}
}

/*----------------------------------------------------------------------------*/
// !!! Timer 0 ist nur 8 Bit
// wird auf 1 Millisekunde eingestellt

void InitTimer0(void)
{
/* Eingangstakt ist Fosc 16Mhz ==> 250ns */
/* wir benoetigen einen Teiler von 1ms / 250 ns = 4000 */
/* da wir nur einen 8 Bit Timer haben wird der Vorteiler */
/* 4000 / 256 = 15,68 also Vorteiler = 16 */
/* 1 Schritt = 250ns * 16 = 4us */
/* 1ms / 4us = 250 */
/* zaehlen muessen wir als 250 Schritte, denn 250*4us = 1ms */
/* der Timer 1 zaehlt aber vorwaerts, also muessen wir den */
/* den Wert 256-250 = 6 ins Counterregister laden */
/* erreicht der Zaehler den Wert 256 gibt es einen Interrupt */
/* !!!! SYNC 2 TCY laut Datenblatt */

/* presacle assigned to timer 0 */
PSA = 0;

/* !!!! OPTION_REG steht nach einem Reset komplett auf 0xFF */
PS2 = 0;

/* clock is internal instruction cycle Fosc/4 */
T0CS = 0;

TMR0 = 7; // value for 1 ms mit Simulator ausgetestet 4000 Cycles=1ms

TMR0IF = 0; // clear interrupt flag from timer 0
TMR0IE = 1; // enable interrupt timer 0
}

//------------------------------------------------------------------------------
void StartAdu(void)
{
GO_nDONE = 1; // start ADU conversion

while (GO_nDONE) ; // wait until adu ready
ADIF = 0; // clear interrupt flag
}

//------------------------------------------------------------------------------
// Wenn der Akku leer wird, soll kontinuierlich geblinkt werden.
// Falls momentan noch eine Blinkzeit länger als die
// lange Blinkphase für Akku leer aktiv ist, wird die momentane Zeit gekürzt
// um schnell die Blinkphase für AKKU leer einzuleiten
// Die beiden LEDs sollen abwechselnd blinken
void FastBlink(void)
{ U16 time;

di(); // DISABLE;
time=BlinkTimeMS;

if (time > FAST_LONG_TIME) // maybe
BlinkTimeMS=FAST_LONG_TIME; // shorten the time

ei(); // ENABLE;

if (time) return; // time not ready, exit

BlinkPhase = !BlinkPhase; // invert the Blinkphase

if (BlinkPhase)
{
LED1_ON;
LED2_OFF;
time = FAST_SHORT_TIME;
} else
{
LED1_OFF;
LED2_ON;
time = FAST_LONG_TIME;
}

di(); // DISABLE;
BlinkTimeMS = time;
ei(); // ENABLE;
}
//------------------------------------------------------------------------------
void DoLed(void)
{ U16 time;

di(); // DISABLE;
time=BlinkTimeMS; // get the current time
ei(); // ENABLE;
if (time) return; // time not ready, exit

if (BlinkCount==0) BlinkPhase = FALSE;

if (BlinkPhase)
{
LED1_OFF; // switch off the Leds
LED2_OFF;
time = SHORT_TIME; // hold leds off for this time
BlinkPhase = FALSE;
} else
{
LED1_ON; // switch on the Leds
LED2_ON;
time = LONG_TIME; // hold leds on for this time
BlinkPhase = TRUE;

if (BlinkCount) BlinkCount--;
else
{
BlinkCount = VoltageBlinkCount; // restart the Blink counter
time = EXTRA_LONG_TIME; // but first we set a long time
}
}

di(); // DISABLE;
BlinkTimeMS = time; // set the new time
ei(); // ENABLE;
}

/*----------------------------------------------------------------------------*/
// Wenn Spannung unter 3 Volt sinkt
// Aufmerksamkeitsblinker nur ganz kurz blitzen damit man sieht,
// das der Akku noch dran ist.
// Die beiden LEDs sollen blitzen
// !!! Zeiten haben sich verdoppelt weil der Clock runter getaktet wurde..
void SleepBlink(void)
{ U16 time;

di(); // DISABLE;
time=BlinkTimeMS;
ei(); // ENABLE;

if (time) return; // time not ready, exit

BlinkPhase = !BlinkPhase; // invert the Blinkphase

if (BlinkPhase)
{
LED1_ON;
LED2_ON;
time = 10; // 20ms
} else
{
LED1_OFF;
LED2_OFF;
time = 1500; // 3 Sekunden
}

di(); // DISABLE;
BlinkTimeMS = time;
ei(); // ENABLE;
}
//------------------------------------------------------------------------------
void GoSleeping(void)
{
IRCF0 = 0; // switch clock to default 8MHz 500ns Instruction cycle
ADON = 0; // disable ADU

// Always set the amplifier output selection to off (0) before disabling the FVR module.
// see errata sheet
ADFVR0 = 0; // 00 = ADC Fixed Voltage Reference Peripheral output is off.
ADFVR1 = 0; // 00 = ADC Fixed Voltage Reference Peripheral output is off.

FVREN = 0; // disable Fixed Voltage Reference

while (1) SleepBlink(); // nur noch blitzen, kein Sleep Modus
}
//------------------------------------------------------------------------------
void main(void)
{
IRCF0 = 1; // switch clock from default 8MHz to 16 MHz ==> 250ns Instruction cycle

TRISA = 0x00; // all pins to output
RA2 = 1; // optional switch, not used at time

// internal fixed voltage reference
// set first the FREN Bit before changing the gain bits
// see errata sheet
FVREN = 1; // Fixed Voltage Reference is enabled

ADFVR1=1; // select the 2,048 Volt Reference

// init the ADC
// ANSELA=1; // AN0 as analog input, all others as digital output
// we dont need an ADU input
ADCON = (0x07 << 5) // conversion clock =FOSC/64
// + (0x00 << 2) // select channel AN0, we dont need a channel
+ (0x07 << 2) // select Fixed Voltage Reference
+ (0x01 << 0); // ADC enable

nWPUEN = 0; // enable global pullups
WPUA = 8; // pullup only on RA3/MCLR

InitTimer0(); // set Timer 0 to 1ms Interrupt

UnderVoltageTime = TIME_BEFORE_STANDBY;

while (1)
{
StartAdu();

// calculate the LiPo Voltage:
value = (U32)(2048) << 8; // 2041 gemessen constant 32 Bit = 524.288
value /= ADRES; // durch den ADU Wert teilen
mV_Value = (U16)value; // 32 to 16 Bit convert

// bei < 3,0 Volt soll nach 2 Sekunden der Sleepmodus aktiviert werden
if (mV_Value < SLEEP_VOLTAGE_MV) // wenn Spannung < 3,0 Volt
{
if (UnderVoltageTime == 0) // und wenn 2 Sekunden vorbei
GoSleeping(); // do not undercharge the accu
} else UnderVoltageTime=TIME_BEFORE_STANDBY; // restart 2 Sekunden Timer, Spannung liegt über 3 Volt


if (mV_Value < ACCU_EMPTY_MV) FastBlink(); // < 3,2 Volt schnell blinken, es sollte gelandet werden
else
{
VoltageBlinkCount=1; // oberhalb von 3,6 Volt wird nur 1 mal geblinkt
if (mV_Value < 3600) VoltageBlinkCount=2; // kleiner 3,6 Volt 2 mal blinken
if (mV_Value < 3400) VoltageBlinkCount=3; // kleiner 3,4 Volt 3 mal blinken
if (mV_Value < 3300) VoltageBlinkCount=4; // kleiner 3,3 Volt 4 mal blinken

DoLed(); // sorgt für das Blinken
} // else

} // while

} // main

Siro
08.05.2020, 09:41
---------------------------------------------------
Akkuspannung / Blinken

> 3,6V ==> 1 x blinken
< 3,6V ==> 2 x blinken
< 3,4V ==> 3 x blinken
< 3,3V ==> 4 mal blinken
< 3,2V ==> Wechselblinker rechts links dauerhaft
< 3,0V ==> alle 3 Sekunden ein kurzer Blitz

---------------------------------------------------
Zusätzliche Infos:

Wenn man sich mal die Schaltung ansieht, fragt man sich sicher wo ist denn da der ADU Eingang ?

Ursprünglich war dafür AN0 vorgesehen, dort wollte ich mit einem Spannungsteiler die Akkuspannung überwachen.

Als ich dann im Datenblatt suchte, wie ich dafür sorgen kann, dass der ADU die interne Referenz
als VREF+ heranzieht, musste ich feststellen das geht garnicht bei diesem PIC.

Vref+ ist IMMER VDD
Ich kann aber die Referenzspannung intern auf den ADU Eingang schalten und so die Referenz konvertieren.

So kam ich auf die Idee, immer die Referenzspannung zu konvertieren,
also gar keinen ADU Pin zu benutzen, denn die Referenzspannung ist ja ein fester Wert
und wenn ich jetzt die Versorgungsspannung ändere (Akkuspannung geht runter),
dann ändert sich der ADU Wert, weil als Referenz zur Konvertierung ja immer die
Versorgungsspannung herangezogen wird.
Das bedeutet der ADU Wert für die feste Referenz 2,048 Volt ändert sich.

Die Kurve hab ich dann mal präzise durchgefahren mit folgendem Ergebnis:


bei +5,500 Volt Versorgung count = 95 maximum PIC Voltage 10F322, ! nicht die "LF" Version
bei +5,400 Volt Versorgung count = 97
bei +5,300 Volt Versorgung count = 99
bei +5,200 Volt Versorgung count = 101
bei +5,100 Volt Versorgung count = 103
bei +5,000 Volt Versorgung count = 104
bei +4,900 Volt Versorgung count = 106
bei +4,800 Volt Versorgung count = 109
bei +4,700 Volt Versorgung count = 111
bei +4,600 Volt Versorgung count = 113
bei +4,500 Volt Versorgung count = 116
bei +4,400 Volt Versorgung count = 118
bei +4,300 Volt Versorgung count = 121
bei +4,200 Volt Versorgung count = 124 LiPo voll geladen
bei +4,100 Volt Versorgung count = 127
bei +4,000 Volt Versorgung count = 131
bei +3,900 Volt Versorgung count = 134
bei +3,800 Volt Versorgung count = 137
bei +3,700 Volt Versorgung count = 141
bei +3,600 Volt Versorgung count = 146 if( count > 146) ==> < 3,6 Volt 2 mal blinken
bei +3,500 Volt Versorgung count = 150
bei +3,400 Volt Versorgung count = 154 if (count > 154) ==> < 3,4 Volt 3 mal Blinken
bei +3,300 Volt Versorgung count = 158 if (count > 158) ==> < 3,3 Volt 4 mal Blinken
bei +3,200 Volt Versorgung count = 163 if (count > 168) ==> < 3,2 Volt dauerblinken
bei +3,100 Volt Versorgung count = 168
bei +3,000 Volt Versorgung count = 174 LiPo absolutes Minimum
bei +2,900 Volt Versorgung count = 180
bei +2,800 Volt Versorgung count = 187
bei +2,700 Volt Versorgung count = 194
bei +2,600 Volt Versorgung count = 202
bei +2,500 Volt Versorgung count = 210
bei +2,400 Volt Versorgung count = 218
bei +2,300 Volt Versorgung count = 228 minimum PIC Voltage 10F322
bei +2,200 Volt Versorgung count = 238
bei +2,100 Volt Versorgung count = 250

Übrigens die "LF" Variante des PICs kann auch runter bis 1,8 Volt arbeiten,
aber Achtung: maximal dann nur bis 3,6 Volt und das ist in dieser Anwendung zu wenig.

Daher unbedingt den PIC10F322 benutzen, NICHT den PIC12LF322.

Damit ergibt sich folgende Formel:
aduCount = FVR * 256 / Vdd
FVR ist die Fixed Voltage Reference 2,048 Volt
Vdd ist die Versorgungsspannung am PIC, also die Akkuspannung
Die 256 rührt von dem 8 Bit AD Wandler, also 2 hoch 8
Ich berechne alles in Millivolt
aduCount = 2048 * 256 / Vdd

umgekehrt ist die Akkuspannung dann
Vdd = 2048 * 256 / adCount
Ergebnis in mV
--------------------------------------------------------------
Ursprünglich wollte ich noch eine Kalibrierung mit einbauen, aber als ich anfing die Flash Erase, Flash Write usw. zu programmieren war der Speicher plötzlich voll.
Er hat ja nur ein halbes Kilobyte :rolleyes:
Mit geschickter Programmierung oder Assmbler ist das sicherlich möglich, aber ehrlich gesagt gibt das nicht wirklich Sinn für diesen Anwendungsfall,
das ist ja kein Meßgerät sondern eine Überwachung / Zustandsanzeige und funktioniert trotzdem sehr genau, da war ich wirklich erstaunt.
-------

Die Programmierung

Ein Problemchen stellt sich noch: wie bekomme ich den Code in den PIC ?

Während der Experimentierphase kann man die 8-Pin DIP Variante fürs Steckbrett benutzen.
Für die SOT-23 Variante habe ich eine Adapterplatine von SOT-23-6 auf DIP-6 genommen.

Als Programmer nehme ich den PicKit 3

Es gibt einen, bzw. sogar zwei spezielle Programmieradapter mit Schnellspannsockel von Microchip für die Gehäuse DFN und SOT-23-6

Leider kosten die knapp 50 Euro pro Stück :(

Der AC163020 ist für das SOT-23-6 Gehäuse zuständig.
https://www.mouser.de/ProductDetail/Microchip-Technology/AC163020?qs=fM4xO01eazNMiBoZGX6agg%3D%3D

Aber Achtung, unbedingt darauf achten wo Pin 1 ist, der befindet sich beim dem Sockel nämlich oben rechts.....
34993

Oder man lötet den PIC zunächst auf eine Adapterplatine, programmiert ihn auf dem Steckbrett und entlötet ihn wieder.
Hier kann man der Kreativität freien Lauf lassen...

34994 so in etwa...

Siro

Klebwax
10.05.2020, 06:16
Es wird immer gesagt, was man mit dem 555 so alles realisieren kann. Am Ende bleibts dann aber nicht bei einem 8 Pin Chip, eine Handvoll passive kommen noch dazu. So ein kleiner µC, ebenfalls im 8 Pin Gehäuse oder sogar im SOT23-6, ist da noch wesentlich flexibler. Du hast da einen feinen Custom-Chip gebaut. Jemand, der den fertig programmiert einsetzt, braucht noch nicht mal zu wissen, daß das ein µC ist. Schönes Projekt!

MfG Klebwax

P.S. Was mir dabei so durch den Kopf geht: Statt der LEDs zwei Optokoppler. Unter 3V sind beide aus, über 3V ist einer an und über 4,2V beide. Dies schaltet man an jede Zelle eines größeren Akkus und hat damit eine Einzelzellenüberwachung fürs Laden und Entladen. Custom BMS-Chip

Siro
11.05.2020, 01:01
@Klebwax:
Da freut man sich natürlich über die positive Rückmeldung, vielen Dank:

Ich habe deine Idee mal umgesetzt:


/*
File: main.c
Date: 11.05.2020
Author: Bernd Sirozynski

Idee von Klebwax umgesetzt:

*/


// PIC10F322 Configuration Bit Settings

// 'C' source line config statements

// CONFIG
#pragma config FOSC = INTOSC // Oscillator Selection bits (INTOSC oscillator: CLKIN function disabled)
#pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF // MCLR Pin Function Select bit (MCLR pin function is digital input, MCLR internally tied to VDD)
#pragma config CP = OFF // Code Protection bit (Program memory code protection is disabled)
#pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)
#pragma config LPBOR = OFF // Brown-out Reset Selection bits (BOR disabled)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>

typedef unsigned short U16; /* 16 Bit ohne Vorzeichen */
typedef unsigned long U32; /* 32 Bit ohne Vorzeichen */

// LED 1 connected to pin RA0
#define LED1_ON LATA0=1
#define LED1_OFF LATA0=0

// LED 2 connected to pin RA1
#define LED2_ON LATA1=1
#define LED2_OFF LATA1=0

U32 value; // wird zur Berechnung benötigt 32 Bit Wert
U16 mV_Value; // umgerechneter Wert in Millivolt

void main(void)
{
TRISA = 0x00; /* all pins to output */

// internal fixed voltage reference
// set first the FREN Bit before changing the gain bits
// see errata sheet
FVREN = 1; // Fixed Voltage Reference is enabled

ADFVR1=1; // set 2,048 Volt Reference

// init the ADC
ADCON = (0x07 << 5) // conversion clock =FOSC/64
+ (0x07 << 2) // select Fixed Voltage Reference
+ (0x01 << 0); // ADC enable


nWPUEN = 0; // enable global pullups
WPUA = 8; // pullup only on RA3/MCLR

while (1)
{

GO_nDONE = 1; // start ADU conversion
while (GO_nDONE) ; // wait until adu ready

value = (U32)(2048) << 8; // constant 32 Bit = 524.288
value /= ADRES; // durch den ADU Wert teilen
mV_Value = (U16)value;

// Auswertung der LiPo Spannung
if (mV_Value < 3000) // kleiner 3 Volt
{
LED1_OFF;
LED2_OFF;
} else if (mV_Value < 4200) // grosser 3 Volt aber kleiner 4,2 Volt
{
LED1_ON;
LED2_OFF;
} else // >= 4,2 Volt
{
LED1_ON;
LED2_ON;
}

} // while

} // main

Crazy Harry
13.05.2020, 15:59
Wenn du mehrere baust, würde ich dir einen Nadeladapter zum programmieren vorschlagen.

Siro
13.05.2020, 19:04
Da hast Du völlig recht, ebenso wenn ich die Software nochmal ändern will ;)

So habe ich mir mal folgendes (sündhaft teures Tool) besorgt:

https://sensepeek.com

35005 35006 35007


Das kann man sich aber auch selber basteln mit Akkupunkturnadeln..

Crazy Harry
13.05.2020, 19:52
Gratuliere, das hab ich mir auch gegönnt. Ja teuer, aber erstaunlich wertig verarbeitet .... naja bis auf die Isolierscheiben zum selber einkleben.

Ich dachte eher an sowas (siehe Bilder). Speziell nur für diese Platine. *** sorry aber das Forum läßt mich vom Tablet keine Bilder hoch laden. Ich ergänze das morgen:***

Die Nadeln, die in deinem teuren Tool drin sind, kann man einzeln im 100er Pack kaufen ;)

Gruss
Harry3500835009

[Edit] Mit Trick gehts doch :)

Siro
13.05.2020, 20:27
Gratuliere, das hab ich mir auch gegönnt.
Is ja witzig, ja, die Isolierscheiben selber drauf machen, war etwas fummelig.

Die verbauten Pins mit den Federn heissen wohl PoGo Pins (kann das sein ?), die kannte ich auch noch nicht.

Selbst ist der Mann. Mit dem Adapter sieht doch supi aus.

Ich hab mir auch noch die Ossi Strippen 100 MHz bestellt, kommt die Tage...;)

Kleine Änderung notwenig:
Für ein Softwareupdate musste ich eine Leiterbahn unterbrechen, sonst ziehen mir die LEDs die Pegel weg....
Hier sollte ich eine Lötbrücke vorsehen, die einfach mit Lötzinn überbrückt wird.

35010 Die grün markierte Leitung muss getrennt werden, sonst bekommt man den Chip nicht mehr programmiert.
Es wird die Masseleitung der beiden LEDs damit getrennt.

Crazy Harry
14.05.2020, 15:56
Wenn du den Adapter nicht selber herstellen kannst, schick mir das Layout als dxf-Datei (Eagle: run dxf, in mm, flächen nicht füllen) dann bau ich dir den.

Harry

.... ich kenn das als Nadeladapter und hab davon sicherlich 500 Stück in div. Größen da.
Wo gibts dafür ein Oszikabel?

Siro
14.05.2020, 20:00
Hallo Harry,
mit dem Adapter von Dir ist super nett gemeint, aber meine "Serienproduktion" besteht grade aus 4 Stück https://www.roboternetz.de/phpBB2/images/smiles/icon_wink.gif und die sind bereits fertig....
Aber vielen Dank für dein Angebot.

Meine Tastköpfe sind heute gekommen, die habe ich bei Batronix bestellt

Der Link dorthin hat leider nicht funktioniert.
Aber wenn Du auf der Seite SensePeek 4013 eingibst funktioniert es.



35011

Grad mal an mein Rigol Ossi angeschlossen, sieht gut aus.
Kanal 1 der Senspeek Tastkopf 100 MHz
Kanal 2 der Original Rigol Tastkopf 100 MHz

35013 35012
Das sind mehr oder minder die Störungen von meinem PC Schaltnetzteil...

Hier mal ein 15 MHz Signal aus dem Funktionsgenerator. Auch hier verhalten sich die Tastköpfe recht identisch.
Also ein voller Ersatz für die originalen Rigol Tastköpfe würde ich sagen.

35014

Crazy Harry
15.05.2020, 16:27
Der Tastkopf sieht echt gut aus und so teuer finde ich den nicht. Ist das tatsächlich ein auf 10:1 fest eingestellter Kopf und nicht auf 1:1 umschaltbar? Trotzdem könnte ich mir da mal 2 gönnen :).

Kannst mir das Layout trotzdem schicken? Würde mich interessieren ob ich das hin bekomm bei 0.95mm Pinabstand. Aber wenn dann dxf. Ich hab nur eine uralte Eagle-Lizenz.

Letztes Bild 15MHz Sinus?

Gruss
Harry

Siro
15.05.2020, 19:42
Der Tastkop ist immer 1:10 lässt sich nicht umschalten.
Die 15 MHz waren natürlich kein Sinus, ich wollt nur zeigen dass beide Signale identisch sind.

Hier ist ein 15 MHz Sinus, mehr gibt mein Generator nicht her.
35017

mal gucken wie das geht mit dem DXF, habe ich noch nie benötigt.
Habe einen Eagle 5.9.0 also hoch aktuell....:)

ich weis garnicht wie ich Dir eine Datei schicken kann... bei email finde ich gar keine Möglichkeit für Anhang.
geht das überhaupt über das Forum

Crazy Harry
16.05.2020, 07:04
Du hast ne PM von mir ;)

Siro
20.04.2021, 16:33
Ich habe meine Akkuüberwachung verbessert:

Jetzt hat der Hubi eine LED-Leiste auf dem HECK mit 18 RGB Leds Größe 2020.
Die Ansteuerung erfolgt über die identische Elektronik, da hab ich nichts geändert.
Aber die Software musste natürlich extrem angepasst werden.
Der PIC hat leider sehr wenig RAM und da musste ich etwas tricksen und habe notgedrungen
sogar unbenutzte File-Register mitbenutzt.
Das Assschieben der RGB Daten ging nur in Assembler, der C-Compiler hat mir keinen einwandfrei lauffähigen Code erzeugt,
er benötigte auch zu viel RAM.

Bei vollem Akku sind alle LEDs grün,
je nach Spannung wechseln immer mehr LEDs zu rot, bis die kritische Schwelle 3,2 Volt erreicht ist,
dann blinkt die gesamte Kette abwechseln rot / blau.

RA2 ist der RGB Datenstrom

Über die Leitung RA3 kann zusätzlich die Helligkeit auf FULL oder HALF eingestellt werden.
RA3 low LED heller 0xFF
RA3 high LED dunkler 0x7F

Die vorhandenen beiden LEDs auf der Leiterplatte, aus der ursprünglichen Schaltung /Software bliken dann zusätzlich wie gehabt abwechselnd.

Videos mit LEDs scheint generell problematisch zu sein,
aber ich hoffe man sieht trotzdem die Funktionsweise.

https://www.dropbox.com/s/c5lvxdgf3ng3q1c/K110_PIC10F322_RGB.mp4?dl=0

Siro

der Code:



/*
File: main.c
Date: 06.02.2021
Author: Siro

Projekt: 10F322 RGB-Ansteuerung
18 LEDs für den K110 Hubschrauber
Einige Assembler Optimierungen vorgenommen
!!! PMDATH und PMDATL sind nicht initialisiert nach einem Reset
250ns Instruction Cycle, 4 MHz

Der Stripe mit 18 LEDs zieht knapp 100mA bei voller Helligkeit "einer" von 3 LEDs ROT oder GRÜN oder BLAU
Bei halber Helligkeit 0x7F nur ca. 50mA

RA3 low LED heller 0xFF
RA3 high LED dunkler 0x7F

Stripe mit 18 LEDs wiegt knapp 0,6 Gramm
Auch bei 1,65 Volt lief die Schaltung noch mit LEDs !!!!!
die blauen LEDs brauchen aber mindestens 2,5 Volt und sind dann noch recht dunkel
*/

// PIC10F322 Configuration Bit Settings
// 'C' source line config statements

// CONFIG
#pragma config FOSC = INTOSC // Oscillator Selection bits (INTOSC oscillator: CLKIN function disabled)
#pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF // MCLR Pin Function Select bit (MCLR pin function is digital input, MCLR internally tied to VDD)
#pragma config CP = OFF // Code Protection bit (Program memory code protection is disabled)
#pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)
#pragma config LPBOR = OFF // Brown-out Reset Selection bits (BOR disabled)
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)

#include <xc.h> // die einzige Headerdatei die benötigt wird.

// benötigen wir nicht, das Delay erzeugen wir selbst mittel Timer 0
// #define _XTAL_FREQ 16000000 // für 4 MHz instrction cylce time wegen der 4 stage pipeline

/*----------------------------------------------------------------------*/
/* hier habe ich meine eigenen Datentypen definiert: */
typedef unsigned char BOOL; /* 0=FALSE, all others are TRUE */
typedef unsigned char U8; /* 8 Bit ohne Vorzeichen */
typedef signed char S8;
typedef unsigned short U16; /* 16 Bit ohne Vorzeichen */
typedef unsigned long U32; /* 32 Bit ohne Vorzeichen */

/* dem Datentyp BOOL sollten nur diese Konstanten zugeordnet werden: */
#define FALSE (0) /* Eine 0 bedeutet FALSE (falsch) */
#define TRUE (!FALSE) /* alle anderen Werte bedeuten TRUE (wahr) */

// Die HARDWARE : 2 LEDs, 1 RGB Stripe WS2812 / SK68 kompatibel mit 18 LEDs

// LED 1 connected to pin RA0
#define LED1_ON LATA0=1
#define LED1_OFF LATA0=0

// LED 2 connected to pin RA1
#define LED2_ON LATA1=1
#define LED2_OFF LATA1=0

// Declaration einer einzelnen RGB LED
// Jede LED hat 3 Bytes, insgesamt also 24 Bits
typedef struct // __pack ? bei 8 Bittern unnötig ist immer packed
{
U8 green; /* 8 Bit fuer die Helligkeit gruen */
U8 red; /* 8 Bit fuer die Helligkeit rot */
U8 blue; /* 8 Bit fuer die Helligkeit blau */
} TLed; /* Type Bezeichner ist TLed */

#define LED_COUNT 18 // Anzahl der anzusteuernden RGB Leds max 18 ???

TLed LedArray[LED_COUNT]; // gesamtes Datenarray der Ledkette
// dieses Array wird dann per Assmblercode ausgeschoben

// U8 ArraySize = sizeof(LedArray); // die Groesse des Arrays in Bytes

// hier liegt eine Tabelle mit den ADU Werten für die entsprechende Spannung
// ADU ==> Voltage.
const U8 ADU_Table[] =
{
161, // 3,25
160, // 3,28
158, // 3,31
157, // 3,34
156, // 3.37
154, // 3,40
153, // 3,43
152, // 3,46
150, // 3,39
149, // 3,52
148, // 3,55
146, // 3,58
145, // 3,61
144, // 3,64
143, // 3,67
142, // 3,70
141, // 3,73
128 // 4,10
};


//------------------------------------------------------------------------------
// für 250ns Instruction Cycle Time 4 MHz
// PMDATL enthält das auszuschiebene Datenbyte für die WS2812 RGB-Led Kette
// wird also als Zwischenspeicher benutzt. (8 Bit Register)
// PMADRL wird für den Bytezähler benutzt (8 Bit Register)
// PMDATH wird als Bitzähler benutzt !!! ist nur ein 6 Bit Register
// das reicht aber, wir zählen ja nur von 8 bis 0
// !!! PMDATH ist nach reset nicht initialisiert, aber selbst mit dem Wert FF
// habe ich das optisch nicht wahrnehmen können.

void asm_LedShiftOut(void)
{
// asm ("clrf PMDATH"); // wegen optimierten bsf PMDATH,3 in der NextByte Loop
// asm ("incf PMDATH,F"); // testweise mal gucken

GIE=0; // global interrupt disable

asm("movlw LOW _LedArray"); // address of LedArray
asm("movwf FSR"); // to FSR index register

asm("movlw 18*3");
asm("movwf PMADRL"); // use PMADRL as byteCount

asm("Label_NextByte:"); // !! WICHTIG, der Doppelpunkt ab XC8 V2.20
asm("bsf PMDATH,3"); // PMDATH = 8
asm("movf INDF,W"); // load byte from LedArray
asm("movwf PMDATL"); // save Databyte to PMDATL

asm("Label_ByteLoop:");
asm("btfsc PMDATL,7"); // scip if databit is clear (MSB)
asm("goto Label_High"); // else databit ist set
asm("bsf LATA,2"); // set WS2812 DataLine to High 250ns
asm("bcf LATA,2"); // set WS2812 DataLine to Low
asm("goto Label_nextBit");

asm("Label_High:"); // High Databit
asm("bsf LATA,2"); // set WS2812 DataLine to High
asm("goto $+1");
asm("bcf LATA,2"); // set Line to Low

asm("Label_nextBit:");
asm("rlf PMDATL,F"); // databyte left
asm("decfsz PMDATH,F"); // bitCount-1, scip if all bits done, bit count -1
asm("goto Label_ByteLoop"); // next bit

asm("incf FSR,F"); // address pointer to next Led Byte
asm("decfsz PMADRL,F"); // byteCount-1 scip if all bytes done
asm("goto Label_NextByte"); // else send next byte

GIE=1; // global interrupt enable

// __delay_us(100); // Das Ende der Datenübertragung erreicht wenn die Leitung länger als 50us Low bleibt.
// !!! mindestens 80us bei SK68 Leds

// hier brauche ich eigentlich nicht warten, weil der restliche Programmcode benötigt ja schon die Zeit ???
// asm("movlw 0x85"); // das sind 100us
// asm("movwf PMDATL");
// asm("Label_delay100:");
// asm("decfsz PMDATL,F");
// asm("goto Label_delay100");

// asm("NOP"); // nur um Breakpoint zu setzen, Zeit messen
}
//------------------------------------------------------------------------------
// global interrupt function
// PWM1DCH wird runtergezählt wenn noch nicht 0
// PWM1DCH dient also als globale Zählvariable

void __interrupt() isr(void) // ORG 0x0004
{
TMR0 = 100; // restart timer 0 value for 10ms
TMR0IF = 0; // clear the timer 0 interrupt flag

if (PWM1DCH) // wenn die Zählvariable noch nicht 0 ist,
PWM1DCH--; // dann ziehen wir eins ab.
}
//------------------------------------------------------------------------------
// PWM1DCH wird im Timer 0 Interrupt alle 10ms um eins runtergezählt
// solange der Wert noch nicht 0 ist.
// !! RAM ist knapp:
// PWM1DCH wird als Zählvariable benutzt, ein 8 Bit Register welches wir nicht
// benötigen.
void Delay_10ms(U8 cnt)
{
PWM1DCH = cnt; // setzte Wert für 10ms * cnt
while (PWM1DCH) ; // warte bis count 0 wird.
}

//------------------------------------------------------------------------------
void FillGreen(void) // wird nicht benutzt
{ U8 i;
for (i=0; i<LED_COUNT; i++)
{
LedArray[i].red = 0x00;
LedArray[i].green = 0x7F;
LedArray[i].blue = 0x00;
}
}

void asm_FillGreen(void) // wird nicht benutzt
{
asm("movlw LOW _LedArray"); // address of LedArray
asm("movwf FSR"); // to FSR index register

asm("movlw 18"); // LED_COUNT Led Counter
asm("movwf PMADRL"); // use PMADRL as counter

asm("FillGreenLoop:");
asm("movlw 0x7F");

asm("movwf INDF"); // save byte to Array .green
asm("incf FSR,F");

asm("clrw");
asm("movwf INDF"); // save byte to Array .red
asm("incf FSR,F");

asm("movwf INDF"); // save byte to Array .blue
asm("incf FSR,F");

asm("decfsz PMADRL,F");
asm("goto FillGreenLoop");
}

void asm_FillRed(void)
{
asm("movlw LOW _LedArray"); // address of LedArray
asm("movwf FSR"); // to FSR index register

asm("movlw 18"); // LED_COUNT Led Counter
asm("movwf PMADRL"); // use PMADRL as counter

asm("FillRedLoop:");
asm("clrw");
asm("movwf INDF"); // save byte to Array .green
asm("incf FSR,F");

asm("movlw 0x7F");
asm("movwf INDF"); // save byte to Array .red
asm("incf FSR,F");

asm("clrw");
asm("movwf INDF"); // save byte to Array .blue
asm("incf FSR,F");

asm("decfsz PMADRL,F");
asm("goto FillRedLoop");
}

void asm_FillBlue(void)
{
asm("movlw LOW _LedArray"); // address of LedArray
asm("movwf FSR"); // to FSR index register

asm("movlw 18"); // LED_COUNT Led Counter
asm("movwf PMADRL"); // use PMADRL as counter

asm("FillBlueLoop:");
asm("clrw");
asm("movwf INDF"); // save byte to Array .green
asm("incf FSR,F");

asm("movwf INDF"); // save byte to Array .red
asm("incf FSR,F");

asm("movlw 0x7F");
asm("movwf INDF"); // save byte to Array .blue
asm("incf FSR,F");

asm("decfsz PMADRL,F");
asm("goto FillBlueLoop");
}

//------------------------------------------------------------------------------
void Calculate(void)
{ U8* p = (U8*)LedArray;
U8 i;

for (i=0; i < sizeof(ADU_Table); i++)
{
if (ADRES < ADU_Table[i]) // kleiner weil ADU Werte invertiert laufen
{
if (RA3) *p++=0x7F; // green
else *p++=0xFF; // more light if jumper RA3 is set
*p++=0x00; // red
*p++=0x00; // blue
} else
{
*p++=0x00; // green
if (RA3) *p++=0x7F; // red
else *p++=0xFF; // more light if jumper RA3 is set
*p++=0x00; // blue
}
}
}

//------------------------------------------------------------------------------
// internal FRC Conversion time measured 26,50µs for one conversion
// we calculate the average value over 128 measurements
// RAM ist knapp:
// Schleifenvariable i wird direkt im Register PMDATL ausgeführt
// Laufzeit ca. 3,8ms

void ReadAdu(void)
{ U16 value;

// LATA1 = 1; // testweise fuer die Zeitmessung

value = 0;
for (PMDATL=0; PMDATL<128; PMDATL++)
{
GO_nDONE = 1; // start ADU conversion
while (GO_nDONE) ; // wait until adu ready
value+=ADRES; // add the result
}
value >>=7; // divide by 128
ADRES=(U8)value; // write back the filtered ADU result

// LATA1 = 0; // testweise fuer die Zeitmessung

}
//------------------------------------------------------------------------------
void main(void)
{
PMDATH = 0; // ! register not initialized on reset
// used only in asm_LedShiftOut as bitcounter

OPTION_REG = 0x07; // prescale to timer 0, 1:256, enable pullups
// !!!! Pullps enabled with LOW on Bit 7
// WPUA = 0x08; // Pullup only on RA3, all pullups enable at power on
OSCCON = 0x70; // 16 MHz clock
INTCON = 0xF0; // enable global, peripheral, timer 0 interrupts
TRISA = 0x08; // all pins to output, RA3 is input only

// internal fixed voltage reference
// set first the FREN Bit before changing the gain bits
// see errata sheet
FVREN = 1; // Fixed Voltage Reference is enabled
ADFVR1 = 1; // select the 2,048 Volt Reference

// init the ADC
// ANSELA=1; AN0 as analog input, all others as digital output
// we dont need an ADU input, always convert the fixed voltage reference
ADCON = (0x07 << 5) // conversion clock internal FRC
// + (0x00 << 2) // select channel AN0, we dont need a channel
+ (0x07 << 2) // select Fixed Voltage Reference
+ (0x01 << 0); // ADC enable

while (1) // the main loop
{
ReadAdu(); // with average filter
if (ADRES > 161) // !! Abfrage invers < 3,25 Volt Unterspannung
{
asm_FillRed();
asm_LedShiftOut(); // RGB LEDs ausschieben
Delay_10ms(25);
LATA0 = 1; // erste LED Wechselblinker am Heck
LATA1 = 0; // zweite LED Wechselblinker am Heck

asm_FillBlue();
asm_LedShiftOut(); // RGB LEDs ausschieben
Delay_10ms(25);
LATA0 = 0; // erste LED Wechselblinker am Heck
LATA1 = 1; // zweite LED Wechselblinker am Heck
} else
{
Calculate(); // calculate the RGB Bargraph
asm_LedShiftOut();
Delay_10ms(2); // 20ms Anzeige
LATA0 = 1; // erste LED Wechselblinker am Heck
LATA1 = 1; // zweite LED Wechselblinker am Heck
} // else
} // while

} // main