Autor: Lukapt
Redakcja: Dondu
Po raz kolejny postanowiłem wzbogacić swój skromny warsztat o nowe urządzenie. Tym razem zbudowałem prosty omomierz - „MiliOhm meter”.
Założenia projektu były następujące:
- ma być prosty (baaardzo ważne :))
- złożony z elementów które posiadam
- zakres badanej rezystancji 0-10om
- jak największa dokładność (w miarę możliwości)
- rozdzielczość 0,001om
- zasilanie z akumulatorka Li-ON
- wbudowana ładowarka zasilana z USB
a efekt taki:
W pierwszej części filmu używam sond w postaci chwytaków precyzyjnych. W drugiej natomiast do pomiarów ścieżek na przykładowych płytkach PCB użyłem sondy zakończone kawałkiem przewodu przylutowanym do końców pary przewodów poprowadzonych do miernika, dla tego tak ważny jest pomiar małych rezystancji czterema przewodami. Tak proste sondy wprowadziły potrzebę kalibracji co pokazane jest w drugiej części filmu.
Projekt
Z prawa Ohma wynika, że:
wystarczy więc znać prąd płynący przez badany rezystor i spadek napięcia na nim, by obliczyć jego rezystancję. Najdokładniejszy wynik otrzymamy stosując cztery przewody. Dwoma doprowadzamy prąd a pozostała parą mierzymy napięcie na rezystorze. W ten sposób eliminujemy rezystancję przewodów z otrzymanego wyniku.
W pierwszej kolejności zbudowałem źródło prądu 100mA.
Przykład źródła prądowego |
Vref- ustala prąd płynący przez badany rezystor. Op-amp -sterując MOSFET'em- stara się by napięcie na jego wejściu odwracającym było równe Vref. Napięcie na wejściu odwracającym jest proporcjonalne do poziomu otwarcia MOSFET'a i napięcia na rezystorze Rbase. To napięcie z kolei jest proporcjonalne do prądu jaki płynie przez rezystor.
Prąd płynący przez Rbase jest taki sam jak prąd płynący przez badany rezystor. Ważnymi parametrami którymi należy się kierować przy wyborze MOSFET'a w tej aplikacji to Vgs(th) - napięcie bramka-źródło przy którym tranzystor zaczyna przewodzić- powinno być jak najmniejsze oraz jak najmniejsza pojemność bramki. Pojemność bramki jest ważna z tego powodu, że Op-amp'y nie potrafią sterować obciążeń pojemnościowych. Cała część analogowa zasilana jest z regulatora napięcia 2,7V.
Jednym z podstawowych założeń była praca na jednym akumulatorku Li-ON, więc zastosowane komponenty muszą również pracować stabilnie na napięciu od ~2.8V- 4.2V. I tu punkt 2 założeń zaczął się 'sypać'. O ile na płytce stykowej w prototypie mogłem użyć zasilania z USB (+5V) o tyle do ostatecznej wersji miernika musiałem zamówić: Op-amp MCP6001 (kopia), który pracuje od 1,8V do 6V. MOSFET PMN28UN, którego Vgs(th)=0.7V i Ciss=740pF oraz wersję niskonapięciową mikrokontrolera ATMEGA88PA (kopia).
Ponieważ wspomniałem o mikrokontrolerze, to przyszedł czas na zbudowanie układu który odczyta, obliczy i wyświetli wartość rezystancji badanego rezystora.
Schemat części cyfrowej |
Zacznę może od końca, a mianowicie od wyświetlacza. To właśnie rozpracowanie wyświetlacza było najtrudniejsze w tym całym projekcie.
W szufladzie od jakiegoś czasu leżała stara (ale poczciwa) NOKIA 6310i. Postanowiłem, że wykorzystam jej wyświetlacz LCD. Pomysł wydawał się prosty, popularny telefon = popularny LCD = dużo projektów w sieci. Niestety, wujek Google odpowiedział tylko na jedno pytanie: jakie są funkcje poszczególnych pinów.
Na stronie www.module.ro/nokia_3510.html (kopia1, kopia2) była informacja, że ten wyświetlacz wykorzystuje kontroler Philips PCF8511/PCF8813, ta informacja jest jednak błędna ponieważ komendy inicjalizujące dla tego kontrolera nie działają z posiadanym LCD. Znalazłem noty katalogowe kilku kontrolerów podobnych wyświetlaczy i metodą prób i błędów udało się ożywić wyświetlacz.
Nota katalogowa którą wykorzystałem to OM6211.pdf (kopia). Nie jest to nota do kontrolera posiadanego wyświetlacza ponieważ LCD z Nokii ma rozdzielczość 65x96 a kontroler w nocie 48x84, ale komendy inicjalizacyjne 'wydają' się być takie jak potrzeba. Ważne, że działa.
Kod inicjalizujący LCD:
void Init_6310(void) { DDR_SPI |=(1<<MOSI)|(1<<SCL)|(1<<CS)|(1<<RST); //outputs PORT_SPI |=(1<<CS); //CS - hi PORT_SPI &=~(1<<RST); //hardware reset _delay_us(200); PORT_SPI |=(1<<RST); Send_6310(0, 0b00101111); //charge pump on Set_Vpr_6310(133); //charge pump voltage/set contrast Send_6310(0, 0b10100100); //display - "all pixels" off Send_6310(0, 0b00111100); //set multipl. factor Send_6310(0, 0b00110011); //bias Send_6310(0, 0b10101110); //Display off Send_6310(0, 0b10101101); //frame cal. start _delay_ms(200); //time needed for calibration Send_6310(0, 0b10101100); //frame cal. stop Send_6310(0, 0b10101111); //Display on }
Wyświetlacz, nie posiada żadnej zdefiniowanej tablicy znaków w swojej pamięci, wszystkie znaki i grafiki muszą być zdefiniowane w pamięci mikrokontrolera sterującego lub pamięci zewnętrznej. W tym projekcie tablice znaków zdefiniowałem w piliku main.c, jednak przy większej ilości danych - większym wyświetlaczu - dobrą praktyką jest wydzielenie osobnego pliku *.c dla tablic stałych. Dodatkowo takie tablice zapisuje się w pamięci FLASH mikrokontrolera.
Przykład grafiki dla LCD zapisanej w pamięci mikrokontrolera:
const uint8_t battery_ind [5][18] = { //empty {0x7E, 0x81, 0x81, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x81, 0x81, 0xBD, 0xC3, 0x24, 0x18}, //1/4 {0x7E, 0xFF, 0xFF, 0xFF, 0xF9, 0xE1, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xBD, 0xC3, 0x24, 0x18}, //2/4 {0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xE1, 0x81, 0x81, 0x81, 0x81, 0xBD, 0xC3, 0x24, 0x18}, //3/4 {0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xE1, 0xBD, 0xC3, 0x24, 0x18}, //full {0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x24, 0x18} };
Obsługa tego wyświetlacza jest bardzo prosta. Interfejs 9-cio bitowy, 3-Wire SPI tzn, że kontroler rozpoznaje czy dostarczona jest komenda czy dane do wyświetlenia po wartości 1-ego bitu. Jeżeli MSB=1 reszta danych to komenda, a jeżeli MSB=0 -dane do wyświetlenia.
Mikrokontroler nie obsługuje bezpośrednio takiego sposobu komunikacji, więc trzeba się posłużyć małym 'trikiem': Linie CS → Low, ustawiamy stan Hi/Low na pinie MOSI, przestawiamy stan na pinie SCLK – najpierw na Low a następnie Hi. Po tym wysyłamy resztę (8 bitów) w normalny sposób po SPI.
Funkcja wysyłająca 9 bitów przez SPI:
void Send_6310(char dc, uint8_t data) //dc- command=0, data=1 { SPCR &=~(1<<SPE); // disable SPI if (dc) PORT_SPI |=(1<<MOSI); else PORT_SPI&=~(1<<MOSI); //command=0, data=1 PORT_SPI &=~(1<<CS); //CS - low - select PORT_SPI &=~(1<<SCL); //SCLK - low PORT_SPI |=(1<<SCL); //SCLK - hi SPCR |=(1<<SPE); //Enable SPI - send rest SPDR =data; while (!(SPSR&(1<<SPIF))); PORT_SPI |=(1<<CS); //CS - hi - deselect }
Kolejnym krokiem była decyzja w sprawie odczytu wartości napięcia na badanym rezystorze. ADC w ATMEGA88 jest 10'cio bitowy, rozdzielczość nie wystarczająca do zmierzenia bardzo małych różnic w napięciu. ADC w mikrokontrolerze mierzy napięcie względem GND, ja potrzebowałem mierzyć napięcie względem innego napięcia odniesienia. Możliwe było użycie dwóch pinów ADC, zmierzenie napięć i obliczenie różnicy. Jednak rozdzielczość była główną przeszkodą aby zastosować wewnętrzny ADC.
Postanowiłem wykorzystać zakupiony parę miesięcy temu, mały 18'to bitowy przetwornik ADC firmy Microchip: MCP3421 (kopia). Przetwornik ten posiada możliwość wyboru rozdzielczości (12-18 bitów), programowalne wzmocnienie, stabilne napięcie referencyjne 2.048V i interfejs I2C.
Wykorzystanie tego okładu pozwoliło zwiększenie rozdzielczości budowanego miernika do 0,00015Ohm, przy użyciu 16'to bitowej rozdzielczości i 4'ro krotnej wartości wzmocnienia przy niskich wartościach rezystorów (<0.999Ohm).
Było to moje pierwsze spotkanie z tym sposobem komunikacji. Na szczęście noty mikrokontrolera i przetwornika bardzo dobrze opisują ten interfejs i sposób komunikacji. Wysyłamy komendę konfiguracyjna i odbieramy wartość napięcia w 3'ech bajtach i bajt konfiguracyjny by sprawdzić czy nie było błędów podczas przesyłania danych.
Ostatnia rzeczą było zbudowanie okładu ładowarki. Do tego celu użyłem scalaka MCP73831 i schematu z jego noty. P-MOSFET rozłącza układ miernika podczas gdy jest podłączony kabel USB i zostaje tylko podłączona ładowarka Po podłączeniu do USB układ ładuje akumulatorek prądem 100mA. Dioda LED sygnalizuje koniec lądowania(gaśnie). Dodałem odczyt napięcia przez ADC w mikrokontrolerze na akumulatorku, by moc wyświetlać stan na wyświetlaczu w postaci ikony.
Pełny schemat |
Po zbudowaniu prototypu na płytce stykowej i kilku wersjach programu przyszedł czas na wykonanie PCB i polutowanie elementów. Schemat i wzór płytki został wykonany w Eagle'u. PCB wykonałem osobiście, wykorzystując metodę foto-chemiczną. Metoda polega na przeniesieniu -wydrukowanego na kalce- wzoru płytki poprzez naświetlenie i wywołanie światło-czułej folii przyklejonej na laminat oraz wytrawienie w nadsiarczanie sodowym. Płytka posiada kilka przelotek, które zostały wykonane metodą elektro-chemiczną.
Na koniec zabezpieczyłem warstwę od strony elementów światło-czułą soldermaską i PCB była gotowa do montażu elementów. Drobnym wyzwaniem było przylutowanie ATMEGA88 w wersji SMD MLF28'io pinowej(raster 0,45mm). Na szczęście obyło się bez zwarć i nieprzyjemnych niespodzianek. Po sprawdzeniu poprawności montażu, nie miałem problemów z uruchomieniem i kalibracją miernika.
Program został napisany w środowisku Atmel Studio 6 i jest bardzo prosty, ogranicza się praktycznie do odczytu wartości ADC z MCP3421 i wyświetleniu przeliczonej wartości na LCD. Większość problemów na jakie napotkałem zostały opisane już wcześniej.
Algorytm |
Do kalibracji użyłem specjalnie zakupionego rezystora 100mOm, 0.1%, 15ppm. Jeden rezystor kosztował mniej więcej tyle co elementy potrzebne do budowy całego miernika.
Gdy wszystko działało jak należy, wykonałem obudowę i przewody pomiarowe. Każdy przewód pomiarowy składa się z dwóch przewodów w izolacji, aż do miejsca styku z badanym rezystorem.
Do pobrania
Do pobrania pliki programu, datasheet użytych elementów oraz pliki schematu i płytki PCB w formacie Egale: mili_ohm.rar (kopia)
Program
LCD_6310.h
/* * LCD_6310.h * * Created: 03/02/2014 18:08:03 * Author: Lukapt */ #define DDR_SPI DDRB #define PORT_SPI PORTB #define MOSI PB3 #define SCL PB5 #define CS PB2 #define RST PB1 void Init_6310(void); void Send_6310(char dc, uint8_t data); //dc- command=0, data=1 void GOTO_XY_6310(uint8_t x, uint8_t y); void Set_Vpr_6310(uint8_t Vpr); //sets Vpr 35<->250
Do pobrania: LCD_6310.h (kopia)
LCD_6310.c
/* * LCD_6310.c * * Created: 03/02/2014 18:05:29 * Author: Lukapt */ #include <avr/io.h> #include <util/delay.h> #include "LCD_6310.h" void Init_6310(void) { DDR_SPI |=(1<<MOSI)|(1<<SCL)|(1<<CS)|(1<<RST); //outputs PORT_SPI |=(1<<CS); //CS - hi PORT_SPI &=~(1<<RST); //hardware reset _delay_us(200); PORT_SPI |=(1<<RST); Send_6310(0, 0b00101111); //charge pump on Set_Vpr_6310(133); //charge pump voltage/set contrast Send_6310(0, 0b10100100); //display - "all pixels" off Send_6310(0, 0b00111100); //set multipl. factor Send_6310(0, 0b00110011); //bias Send_6310(0, 0b10101110); //Display off Send_6310(0, 0b10101101); //frame cal. start _delay_ms(200); //time needed for calibration Send_6310(0, 0b10101100); //frame cal. stop Send_6310(0, 0b10101111); //Display on } void Send_6310(char dc, uint8_t data) //dc- command=0, data=1 { SPCR &=~(1<<SPE); // disable SPI if (dc) PORT_SPI |=(1<<MOSI); else PORT_SPI&=~(1<<MOSI); //command=0, data=1 PORT_SPI &=~(1<<CS); //CS - low - select PORT_SPI &=~(1<<SCL); //SCLK - low PORT_SPI |=(1<<SCL); //SCLK - hi SPCR |=(1<<SPE); //Enable SPI - send rest SPDR =data; while (!(SPSR&(1<<SPIF))); PORT_SPI |=(1<<CS); //CS - hi - deselect } void GOTO_XY_6310(uint8_t x, uint8_t y) { Send_6310(0, 0b10110000 | y); Send_6310(0, 0b00010000 |(x>>4)); Send_6310(0, (x & 0x0F)); } void Set_Vpr_6310(uint8_t Vpr) //sets Vpr 35<->250 { Send_6310(0, 0b10000000 |(Vpr & 0x1f)); Send_6310(0, 0b00100000 | (Vpr>>5)); }
Do pobrania: LCD_6310.c (kopia)
i2c_twi.h
/* * i2c_twi.h * * Created: 19/11/2013 10:59:10 * Author: Lukapt */ #define ACK 1 #define NACK 0 void I2C_start(void); void I2C_stop(void); void I2C_write(uint8_t bajt); uint8_t I2C_read(uint8_t ack); void I2C_SetBitrate(uint16_t bitrateKHz); void MCP_read_buffer(uint8_t SLA, uint8_t len, uint8_t *buf); void MCP_write_buffer( uint8_t SLA, uint8_t buffer );
Do pobrania: i2c_twi.h (kopia)
i2c_twi.c
/* * i2c_twi.c * * Created: 19/11/2013 10:59:10 * Author: Lukapt */ #include <avr/io.h> #include "i2c_twi.h" void I2C_start(void) { TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTA); while (!(TWCR&(1<<TWINT))); } void I2C_stop(void) { TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO); while ( (TWCR&(1<<TWSTO)) ); } void I2C_write(uint8_t byte) { TWDR = byte; TWCR = (1<<TWINT)|(1<<TWEN); while ( !(TWCR&(1<<TWINT))); } uint8_t I2C_read(uint8_t ack) { TWCR = (1<<TWINT)|(ack<<TWEA)|(1<<TWEN); while ( !(TWCR & (1<<TWINT))); return TWDR; } void I2C_SetBitrate(uint16_t bitrateKHz) { uint8_t bitrate_div; bitrate_div = ((F_CPU/1000l)/bitrateKHz); if(bitrate_div >= 16) bitrate_div = (bitrate_div-16)/2; TWBR = bitrate_div; } void MCP_read_buffer(uint8_t SLA, uint8_t len, uint8_t *buf) { I2C_start(); I2C_write(SLA + 1); while (len--) *buf++ = I2C_read( len ? ACK : NACK ); I2C_stop(); } void MCP_write_buffer( uint8_t SLA, uint8_t buffer ) { I2C_start(); I2C_write(SLA); I2C_write(buffer); I2C_stop(); }
Do pobrania: i2c_twi.c (kopia)
mili ohm_meter.c
/* * mili_ohm_meter.c * * Created: 19/11/2013 10:59:10 * Author: Lukapt */ #include <avr/io.h> #include <util/delay.h> #include "i2c_twi.h" #include "LCD_6310.h" #define mcp_adr 0b11010010 /*18x24*/ const uint8_t digits [10][54]= { //0 { 0x00, 0xFC, 0xFE, 0xFC, 0xF8, 0x01, 0x03, 0x07, 0x07, 0x07, 0x07, 0x03, 0x01, 0xF8, 0xFC, 0xFE, 0xFC, 0x00, 0x00, 0xC3, 0xE7, 0xC3, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xC3, 0xE7, 0xC3, 0x00, 0x00, 0x3F, 0x7F, 0x3F, 0x1F, 0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, 0x1F, 0x3F, 0x7F, 0x3F, 0x00 }, //1 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFC, 0xFE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xC3, 0xE7, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x7F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00 }, //2 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x07, 0x07, 0x07, 0x03, 0x01, 0xF8, 0xFC, 0xFE, 0xFC, 0x00, 0x00, 0xC0, 0xE0, 0xC0, 0x80, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x01, 0x03, 0x07, 0x03, 0x00, 0x00, 0x3F, 0x7F, 0x3F, 0x1F, 0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00 }, //3 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x07, 0x07, 0x07, 0x03, 0x01, 0xF8, 0xFC, 0xFE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x81, 0xC3, 0xE7, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, 0x1F, 0x3F, 0x7F, 0x3F, 0x00 }, //4 { 0x00, 0xFC, 0xFE, 0xFC, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFC, 0xFE, 0xFC, 0x00, 0x00, 0x03, 0x07, 0x03, 0x01, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x81, 0xC3, 0xE7, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x7F, 0x3F, 0x00 }, //5 { 0x00, 0xFC, 0xFE, 0xFC, 0xF8, 0x01, 0x03, 0x07, 0x07, 0x07, 0x07, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x03, 0x01, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x80, 0xC0, 0xE0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, 0x1F, 0x3F, 0x7F, 0x3F, 0x00 }, //6 { 0x00, 0xFC, 0xFE, 0xFC, 0xF8, 0x01, 0x03, 0x07, 0x07, 0x07, 0x07, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xE7, 0xC3, 0x81, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x80, 0xC0, 0xE0, 0xC0, 0x00, 0x00, 0x3F, 0x7F, 0x3F, 0x1F, 0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, 0x1F, 0x3F, 0x7F, 0x3F, 0x00 }, //7 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x07, 0x07, 0x07, 0x03, 0x01, 0xF8, 0xFC, 0xFE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xC3, 0xE7, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x7F, 0x3F, 0x00 }, //8 { 0x00, 0xFC, 0xFE, 0xFC, 0xF8, 0x01, 0x03, 0x07, 0x07, 0x07, 0x07, 0x03, 0x01, 0xF8, 0xFC, 0xFE, 0xFC, 0x00, 0x00, 0xC3, 0xE7, 0xC3, 0x81, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x81, 0xC3, 0xE7, 0xC3, 0x00, 0x00, 0x3F, 0x7F, 0x3F, 0x1F, 0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, 0x1F, 0x3F, 0x7F, 0x3F, 0x00 }, //9 { 0x00, 0xFC, 0xFE, 0xFC, 0xF8, 0x01, 0x03, 0x07, 0x07, 0x07, 0x07, 0x03, 0x01, 0xF8, 0xFC, 0xFE, 0xFC, 0x00, 0x00, 0x03, 0x07, 0x03, 0x01, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x81, 0xC3, 0xE7, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, 0x1F, 0x3F, 0x7F, 0x3F, 0x00 } }; const uint8_t battery_ind [5][18] = { //empty { 0x7E, 0x81, 0x81, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x81, 0x81, 0xBD, 0xC3, 0x24, 0x18 }, //1/4 { 0x7E, 0xFF, 0xFF, 0xFF, 0xF9, 0xE1, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xBD, 0xC3, 0x24, 0x18 }, //2/4 { 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xE1, 0x81, 0x81, 0x81, 0x81, 0xBD, 0xC3, 0x24, 0x18 }, //3/4 { 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xE1, 0xBD, 0xC3, 0x24, 0x18 }, //full { 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x24, 0x18 } }; /*16x16*/ const uint8_t ohm [] = { 0x00, 0xC0, 0xF0, 0x38, 0x18, 0x0C, 0x0C, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x38, 0xF0, 0xC0, 0x00, 0x70, 0xE1, 0xC7, 0xCE, 0xCC, 0xF8, 0x78, 0x00, 0x00, 0x78, 0xF8, 0xCC, 0xCE, 0xC7, 0xE1, 0x70 }; const uint8_t m [] = {0x03, 0xFF, 0xFF, 0x0E, 0x07, 0xFF, 0xFE, 0x0E, 0x07, 0xFF, 0xFC, 0xC0};//8x12 // ohm_mode 1 400==>1ohm multiplayer 25 // mohm_mode 0 1600==>1ohm multiplayer 625 uint8_t dec_pos=0, mcp_buff_all[3], buff[4] = {0,0,0,0}, mode=1, disp_clr; uint16_t mcp_buff; void display_digit(uint8_t x_pos, uint8_t y_pos, uint8_t digit) //display single digit { //hight is 3 lines tall for(uint8_t y=0; y<3; y++) { //it must be divided //and send in 3 parts GOTO_XY_6310(x_pos,y+y_pos); switch(y) { case 0: for(uint8_t x=0; x<18; x++) { Send_6310(1, digits[digit][x]); } break; case 1: for(uint8_t x=18; x<36; x++) { Send_6310(1, digits[digit][x]); } break; case 2: for(uint8_t x=36; x<54; x++) { Send_6310(1, digits[digit][x]); } break; } } } void clear_6310(void) //clear screen { for(uint8_t y=0; y<3; y++) { GOTO_XY_6310(0,y+2); for(uint8_t x=0; x<96; x++) { Send_6310(1, 0x00); } } } void display_battery_ind(uint8_t level) //display battery level { GOTO_XY_6310(70,0); for(uint8_t x=0; x<18; x++) { Send_6310(1, battery_ind[level][x]); } } void display_ohm(uint8_t x_pos) //x=40 if ohm, x=48 if mohm //display Ohm icon { GOTO_XY_6310(x_pos,6); for(uint8_t x=0; x<16; x++) { Send_6310(1, ohm[x]); } GOTO_XY_6310(x_pos,7); for(uint8_t x=16; x<32; x++) { Send_6310(1, ohm[x]); } } void display_mohm(void) //display mOhm icon { GOTO_XY_6310(35,7); for(uint8_t x=0; x<12; x++) { Send_6310(1, m[x]); } } void print_buff(uint8_t buff[4], uint8_t dec_position) //display resistor value { static uint8_t old_dec; uint8_t x=8; for(uint8_t i=0; i<4; i++) { if(i==dec_position) { // add decimal point for(uint8_t j=0; j<3; j++) { GOTO_XY_6310(x+20*i+j, 4); Send_6310(1, 0x70); } } if(i>= dec_position) { x=12; } display_digit(x+20*i, 2, buff[i]); //display dig. } if(old_dec!=dec_position) { if(dec_position == 3) { for(uint8_t x=35; x<48; x++) { GOTO_XY_6310(x,6); Send_6310(1, 0x00); } display_mohm(); display_ohm(48); } else { for(uint8_t y=6 ; y<8 ; y++) { //clear space after mOhm for(uint8_t x=35 ; x<64 ; x++) { GOTO_XY_6310(x,y); Send_6310(1, 0x00); } } display_ohm(40); } old_dec=dec_position; } } void ADCtoText(uint32_t x, uint8_t buffer[4], uint16_t multiplayer, uint16_t divider) //convert ADC value to table { x*=multiplayer; x/=divider; buffer[0]=x/1000; x=x%1000; buffer[1]=x/100; x=x%100; buffer[2]=x/10; buffer[3]=x%10; } uint16_t ADC_measure(void) //measure ADC - battery { ADCSRA |= (1<<ADSC); //ADSC: start single conversion while(ADCSRA & (1<<ADSC)); //wait until finish return ADC; //return value } int main(void) { //init SPI SPCR |=(1<<SPE)|(1<<MSTR)|(1<<SPR0)|(1<<SPR1)| (1<<SPI2X); //SPI enable, master, Fosc/4 x2 //lcd init Init_6310(); //adc - battery init ADMUX |=(1<<REFS1)|(1<<REFS0); //internal Vref=1.1v ADMUX |=(1<<MUX1)|(1<<MUX0); //ADC3 select ADCSRA |=(1<<ADEN)|(1<<ADPS2); //ADC enable DDRC&=~(1<<PC3); //battery adc pin //mcp init I2C_SetBitrate(100); uint8_t mcp_config= 0b10010100; //14bits ==>> 250uV; (0b10011010)16bits ==>>62.50uV, gain 4x MCP_write_buffer(mcp_adr, mcp_config); while(1) { // check battery uint16_t Batt_ADC; Batt_ADC =ADC_measure(); if(Batt_ADC>930) { display_battery_ind(4); //4.00 =>full } else if(Batt_ADC>855) { display_battery_ind(3); //3.66 =>3/4 } else if(Batt_ADC>770) { display_battery_ind(2); //3.30 =>1/2 } else if(Batt_ADC>665) { display_battery_ind(1); //2.85 =>1/4 } else { display_battery_ind(0); //battery empty } //start measurement MCP_read_buffer(mcp_adr, 3 , mcp_buff_all); mcp_buff=mcp_buff_all[0]<<8 | mcp_buff_all[1]; if((mode) && (mcp_buff<400)) { //if resistor <0.999ohm mode=0; //16bits mode mcp_config=0b10011010; //gain 4x MCP_write_buffer(mcp_adr, mcp_config); MCP_read_buffer(mcp_adr, 3 , mcp_buff_all); mcp_buff=mcp_buff_all[0]<<8 | mcp_buff_all[1]; } if((!mode) && (mcp_buff>6399)) { //resistor >0.999ohm mode=1; //14bits mode mcp_config=0b10010100; MCP_write_buffer(mcp_adr, mcp_config); MCP_read_buffer(mcp_adr, 3 , mcp_buff_all); mcp_buff=mcp_buff_all[0]<<8 | mcp_buff_all[1]; } if((mcp_buff_all[2]|(1<<8))!=mcp_config) { // if MCP_ADC error, read again MCP_write_buffer(mcp_adr, mcp_config); MCP_read_buffer(mcp_adr, 3 , mcp_buff_all); mcp_buff=mcp_buff_all[0]<<8 | mcp_buff_all[1]; } //update display if(mcp_buff>8000) { mcp_buff=0; //if no resistor, display 0000 ADCtoText(mcp_buff, buff, 10, 10); } //else if(!mode) { if(disp_clr!=1) { clear_6310(); //mohm display disp_clr=1; } ADCtoText(mcp_buff, buff, 625, 400); print_buff(buff, 3); } else if(mcp_buff<4000) { if(disp_clr!=2) { clear_6310(); //ohm display disp_clr=2; } ADCtoText(mcp_buff, buff, 25, 10); print_buff(buff, 1); } else { if(disp_clr!=3) { clear_6310(); //tens ohm display disp_clr=3; } ADCtoText(mcp_buff, buff, 25, 100); print_buff(buff, 2); } _delay_ms(250); } }Do pobrania: mili ohm_meter.c (kopia)
"podstawić linki" przeoczyłeś to Dondu
OdpowiedzUsuńNo popatrz - ja redagowałem wersję na blog, autor sprawdzał, i ... taka plama - dziękuję za informację zaraz poprawię :)
UsuńHC4L!!!!!! :)
OdpowiedzUsuńNie myślałeś, żeby zamiast zewnętrznego ADC użyć jakiegoś Op-amp'a w układzie wzmacniacza różnicowego, żeby sobie tę małą róznicę napięć wzmocnić?
OdpowiedzUsuńNiestety, gdybym użył Op-amp'ów (jeden nie wystarczy) to projekt był by dużo bardziej skąplikowany (aby uzyskać tą samą rozdzielczość i dokładność pomiarów). To była najprostsza droga do celu jaka przyszła mi do głowy, na pewno nie jedyna słuszna.
UsuńŚwietny projekt, gratuluję!
OdpowiedzUsuńTaka niby mała rzecz, a świetnie pomaga znaleźć zwarcia na płytce, zwłaszcza jeśli już masz wszystko polutowane, a tu nagle zwarcie na jakiejś linii...
OdpowiedzUsuńProjekt na piątkę
Witam, czyli jeśli potrzebuję mierzyć rezystancję w zakresie 0R-1000R to muszę ustawić źródło prądowe na 2mA (0,002A) czy pomiary będą mierzyć z dokładnością 1% rezystancji? Projekt bardzo dydaktyczny.
OdpowiedzUsuńWitam, sorki za puzna odpowiedz, ale teraz bardzo rzadko tu zaladam:)
OdpowiedzUsuńNie wiem, czy chodzi ci o ten project czy "ogolnie".
"ogolnie": prad na rezystorze ustawiasz tak by bylo "wygodnie" pracowac z pomiarem. Np. ustawisz 100mA, pomiar miernikiem mnozysz tylko przez 10 i otrzymujesz wynik w Ohm'ach. Dokladnosc pomiaru zalezy od dokladnosci Multimetru lub ADC w uC.
[edit]i oczywiscie od tego jak precyzyjnie jest ustawione zrodlo pradowe
Usuń