Mikrokontrolery - Jak zacząć?

... czyli zbiór praktycznej wiedzy dot. mikrokontrolerów.

niedziela, 20 marca 2011

DIY: Prosty mili-omomierz


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)


Oceń artykuł.
Wasze opinie są dla nas ważne, gdyż pozwalają dopracować poszczególne artykuły.
Pozdrawiamy, Autorzy
Ten artykuł oceniam na:

7 komentarzy:

  1. "podstawić linki" przeoczyłeś to Dondu

    OdpowiedzUsuń
    Odpowiedzi
    1. No popatrz - ja redagowałem wersję na blog, autor sprawdzał, i ... taka plama - dziękuję za informację zaraz poprawię :)

      Usuń
  2. 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ń
    Odpowiedzi
    1. 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ń
  3. Świetny projekt, gratuluję!

    OdpowiedzUsuń
  4. 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...
    Projekt na piątkę

    OdpowiedzUsuń

Działy
Działy dodatkowe
Inne
O blogu




Dzisiaj
--> za darmo!!! <--
1. USBasp
2. microBOARD M8


Napisz artykuł
--> i wygraj nagrodę. <--


Co nowego na blogu?
Śledź naszego Facebook-a



Co nowego na blogu?
Śledź nas na Google+

/* 20140911 Wyłączona prawa kolumna */
  • 00

    dni

  • 00

    godzin

  • :
  • 00

    minut

  • :
  • 00

    sekund

Nie czekaj do ostatniego dnia!
Jakość opisu projektu także jest istotna (pkt 9.2 regulaminu).

Sponsorzy:

Zapamiętaj ten artykuł w moim prywatnym spisie treści.