Krumeltee

-> Elektronik, Mikrocontroller und Retro-Computing <-

Archive for the ‘AVR’ Category

Eagle Libraries

Posted by krumeltee - 7. Oktober 2011

Hier ein paar Eagle Libs, die ich im Lauf der Zeit selbst machen musste.

 

Advertisements

Posted in AVR, AVR32, mini2440 | Leave a Comment »

Kontrast für HD44780 LCD Displays per Software (PWM) regeln

Posted by krumeltee - 27. August 2011

Oft muss man im Betrieb den Kontrast oder auch die Hintergrundbeleuchtung für Displays ändern. Für den Kontrast ist normalerweise ein Poti vorgesehen, für die Hintergrundbleuchtung gibt es zig verschiedene Varianten, jenachdem wie die Hintergrundbeleuchtung gemacht ist. Die Steuerung der Hintergrundbeleuchtung beschreibe ich in einem anderen Artikel.

Hier zeige ich zwei Methoden zur Regelung des Kontrasts und eine, ganz einfache, für die Hintergrundbeleuchtung.

Für den Kontrast gibt es die Möglichkeiten per:

  • DA-Wandler + Operationsverstärker (einfach, teuer)
  • PWM + RC-Glied (sehr einfach, billig)

Die Methode mit dem DA-Wandler ist nur dann sinnvoll, wenn entweder der Mikrocontroller einen DA-Wandler eingebaut hat oder sowieso schon einer im „System“ vorhanden ist. Für meine Versuche habe ich einen MAX520 und einen LM358 als Spannungsfolger beschaltet verwendet. Der Ausgang vom DA-Wandler auf den Eingang vom OP, der Ausgang vom OP direkt auf den Kontrast-Pin vom LCD. Mehr ist nicht notwendig, keine Widerstände, keine Kondensatoren, nichts.

Da mir diese Methode aber etwas zu teuer und wenig sinnvoll ist, habe ich mich für PWM + RC-Glied entschieden.

Diese Methode lässt sich für alle Displays einsetzen, welche nur über ein Poti von GND nach VCC im Kontrast geregelt werden, also nicht nur die HD44780, sondern auch viele T6963, KS0107/KS0108 Grafik-LCDs, …

Hier der Schaltplan:

Die Schaltung funktioniert so:

C1 und R2 bilden einen Tiefpass, der die PWM-Spannung in eine (annähernd) Gleichspannung umwandelt. R1 und R3 bilden einen Spannungsteiler, der einen „Mindestpegel auf den Kontrastpin legt.

Die meisten LCDs sind bei einer Kontrastspannung von ca. 0,8V bis 1,2V am besten lesbar. Mit dieser Schaltung lässt sich der Pegel des Kontrastpins von ca. 0,125V bis 2,8V in 256 Schritten einstellen.

Der Tiefpass+Spannungsteiler ist nötig, da ein reines PWM-Signal das Display sehr stark flackern lässt. Die PWM-Frequenz in meinem Beispiel beträgt ca. 31khz, ab 25khz wurde es schön lesbar, ohne Flackern ausmachen zu können. Weiter unten habe ich dazu noch eine kurze Erklärung.

Folgender Beispielcode ist gemeinsam für Hintergrundbeleuchtung und Kontrast gemacht. Er läuft auf einem ATMega16/ATMega32 bei 16Mhz und benutzt die Pins OC1A und OC1B. Die Helligkeit und der Kontrast lassen sich regeln, in dem man einfach die Werte von 0 bis 255 in die Register OCR1A und OCR1B schreibt:

int main(void) {
    char buffer[25];
    unsigned char contrast = 0;

    lcd_init(LCD_DISP_ON);
    lcd_clrscr();
    lcd_puts("hello world");

    DDRD |= (1<<PD5)|(1<<PD4);    // OC1A, OC1B for contrast and backlight

    DDRB &= ~((1<<PB0)|(1<<PB1));    // buttons
    PORTB |= (1<<PB0)|(1<<PB1);    // pullups for buttons

    TCCR1A = (1<<WGM10) | (1<<COM1A1) | (1<<COM1B1);
    TCCR1B = (1<<CS10);
    OCR1A = 0xff;
    OCR1B = 0x60;

    for (;;) {
        if(!(PINB&(1<<PB0))) { OCR1A++; _delay_ms(25); }
        if(!(PINB&(1<<PB1))) { OCR1B++;  _delay_ms(25); }
    }
}

An PB0 und PB1 sind zwei Taster angeschlossen, die, wenn man sie drückt den Wert in den Registern für Kontrast und Beleuchtung einfach hochzählen. Sobald der Wert grösser als 255 wäre, hüpft er automatisch auf 0 herunter und geht dann wieder weiter nach oben.

Hier mal ein paar Fotos von einem Weiss-Schwarzen Display mit obigem Code:

Erklärung zur PWM Frequenz:

Die PWM-Frequenz für den Kontrast muss im Vergleich zum Dimmen von LEDs, Lampen usw. sehr hoch sein, da sich das Display ja stetig aktualisiert. Wenn nun in dem Moment, wo das Display seinen Inhalt anzeigt, der Kontrast für kurze Zeit fehlt und gleich wieder da ist, dann flimmert alles. Das sieht dann ungefähr so aus, wie wenn man mit einer einfachen Kamera einen Fernseher/Monitor filmt und dort dann die Streifen quer durchs Bild rollen.

Erklärung zum Tiefpass:

Die PWM-Spannung wird vom Tiefpass in eine nahezu glatte Gleichspannung geglättet. Das Thema Tiefpass ist hierfür zu Umgangreich und wird schon auf zig anderen Seiten Disskutiert. Hier aber noch zwei Oszilloskop-Bilder, das eine vor dem Tiefpass, direkt an der PWM, das andere nach dem Tiefpass.

  Man sieht hier genau die PWM mit 5V und einer Frequenz von etwa 31khz.

Hier sieht man die geglättete Kontrastspannung von etwa 1,2V für das Display. Die Spannung „schwankt im Dreieck“ um etwa +-50mV (0,05V), was schon sehr gut ist. Das liesse sich durch einen 1µF Kondensator nach GND nahezu komplett auf ca. -+10mV ausbügeln, was aber völlig unnötig ist.

Jeder 7805 und ähnliche haben einen grösseren Fehler als das 😉

Posted in AVR, C | 7 Comments »

TLC540 8Bit, 11/12 Channel, AD-Wandler am AVR

Posted by krumeltee - 20. August 2011

Der TLC540 hat 12Analog- Kanäle, wovon 11 Kanäle auf Pins herausgeführt sind. Der Zwölfte Kanal ist für eine interne Referenzspannung benutzt worden um den Controller eine Art „Selbsttest“ machen lassen zu können.

Die 11 Kanäle werden mit einer Auflösung von  8 Bit gewandelt, die Geschwindigkeit pro Wandlung ist laut Datenblatt in etwa 17µS, dort ist allerdings noch nicht die Übertragung usw mit eingerechnet, nur die Wandlung im Chip an sich.

Mit meiner Software komme ich für eine AD-Wandlung auf etwa 45µS, was wohl so ziemlich an der Grenze des Machbaren liegt. Der TLC540 besitzt zwei unabhängige Clocks. Einen Systemtakt und einen IO-Takt. Der Systemtakt treibt die Wandlungen voran, der IO-Takt startet die Wandlungen und schiebt die Daten raus.

System-Clock ist laut Datenblatt mit 4,1Mhz angeben, IO-Clock mit 2Mhz. Meine Software liefert einen Systemtakt von 4,22Mhz und einen IO-Clock von 2Mhz. Der Systemtakt ist also mit etwa 0,12Mhz „übertaktet“. Der Grund dafür ist, dass die Software so etwas einfacher ist und stören tuts auch nicht. Ich habe mir das sehr genau angesehen und ausprobiert. Der ADC funktioniert so ohne irgendwelche Probleme.

Insgesamt habe ich so 12 verschiedene TLC540 probiert, keine Probleme aufgetreten.

Die Software von mir für den TLC540 ist ganz einfach für beliebig viele TLCs anpassbar. In meinem Beispiel hier verwende ich zwei, es funktioniert allerdings genauso mit einem, ohne eine Zeile Code ändern zu müssen. Man darf einfach nur die Channels 0-11 abfragen, 12-23 sind am zweiten AD-Wandler.

Mein Beispielprogramm ist für einen ATMega16 geschrieben, welcher mit 8Mhz läuft. Die 8Mhz können problemlos vom internen Taktgeber ezeugt werden, es ist also nicht unbedingt ein Quarz nötig.

/* tlc540.c 
* driver for the tlc540 ad converter
* tlc540:
* 8bits resolution
* 17µS/conversion
* 11 channels + 1 internal test channel, connected to a test reference
* serial interface
* independent SYSTEM and SERIAL clocks
* SYSTEM clock up to 4mhz
* SERIAL clock up to 2,1mhz
* 
* (c) 2011 krumeltee, email: <nils.stec@googlemail.com>
* 
* this code is free software. you can use it and do with it whatever you like.
* if you redistribute this code or parts of it, please let there be a note
* with a link to the source of the code and the author.
* 
* this driver was written for a AVR ATmega16 running with internal clock source
* at 8mhz.
* a ctc timer generates a ~4.22mhz system clock for the TLC540. This slightly
* overclocks the chip (~0.12mhz = 122khz). It works great with 12 chips i have
* tested so far.
* the i/o clock is generated by the spi master hardware interface of the avr,
* with a frequency of 2mhz. 
* 
* this driver supports as many converters connected to your microcontroller
* as you have pins for Chip Select on each AD-Converter.
* i've tested 12 chips on my mega16, it worked.
* 
* the code is well commented and you should easily find out how to add more chips.
* now the code is configured for 2 chips, connected this way:
* 
* AVR | Chip1 | Chip2
* ------------|----------------|--------------
* SCK (PB7) | IO Clock | IO Clock
* MISO (PB6) | DATA OUT | DATA OUT
* MOSI (PB5) | ADR IN | ADR IN
* OC1A (PD5) | SYS CLOCK | SYS CLOCK
* SS (PB4) | CHIP SELECT | 
* PB3 | | CHIP SELECT
* 
* The speed of my tlc540() function is about 45µS for 1 conversion with the speed 
* settings written above. This results in about 22222.2 conversions per second.
*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include "lcd.h"

#ifndef F_CPU
#define F_CPU 8000000UL
#endif

#include <util/delay.h>

unsigned char tlc540(unsigned char channel) {
    unsigned char config_byte, i;

    PORTB |= (1<<PB4);    // SET CS, disable chip 0
    PORTB |= (1<<PB3);    // SET CS, disable chip 1

    if(channel <= 11) {
        PORTB &= ~(1<<PB3);        // CLR_CS enable chip 0 
    } else if(channel <= 23) {
        PORTB &= ~(1<<PB4);        // CLR_CS enable chip 1
        channel -= 12;
    }

    config_byte  = channel<<4;        // AD channel Y (YYYYxxxx) MSB .... LSB

    _delay_us(6);          // after enabling a chip wait at least 2 rising + the second falling edges of system clock

    SPDR = config_byte;        // set + get data
    while(!(SPSR & (1<<SPIF)));    

    for(i = 0; i < 36; i++) {    // 36 clock cycles to do the next conversion
        PORTB |=(1<<PB7);
        PORTB &=~(1<<PB7);
    }

    // make sure all chips are disabled, so we simply disable ALL cs
    PORTB |= (1<<PB3);
    PORTB |= (1<<PB4);

    return SPDR;    // our AD-value 
}

int main (void) {
    DDRB |= (1<<PB4);    // CS ad chip 0
    DDRB |= (1<<PB3);    // CS ad chip 1
    DDRB |= (1<<PB2);    // CS sr 0

    DDRB &= ~(1<<PB6);    // DATA (data from adc to controller)
    PORTB |= (1<<PB6);    // pullup

    DDRB |=(1<<PB7);    // Serial Clock

    DDRB |= (1<<PB5);    // ADRIN (adress input of adc)

    DDRD |= (1<<PD5);    // SYSTEM Clock via OC1A (CTC timer1)
    TCCR1A |=(1<<COM1A0);   // OC1A toggle on compare match
    TCCR1B |=(1<<WGM12);    // CTC mode 4
    TCCR1B |=(1<<CS10);     // no prescaling
    OCR1A=0x0;        // ~4,22mhz system clock @8mhz avr
                // use OCR1A = 0x01 for 16mhz

    SPCR = (1<<SPE)|(1<<MSTR);    // spi enable, master
    SPSR = 0;            // spi freq f_cpu/4 ~2mhz @ 8mhz avr
                    // use f_cpu/8 prescaler for 16mhz

    lcd_init(LCD_DISP_ON);
    lcd_clrscr();    
    sei();

    char buffer[17];
    unsigned char ad_result;
    int i;

    for(;;) {
        for(i = 0; i < 24; i++) {
            ad_result = tlc540(i);
            sprintf(buffer, "channel %02d: $%02x", i, ad_result);
            lcd_gotoxy(0,0);
            lcd_puts(buffer);
            //_delay_ms(1000);
        }
    };
}

Posted in AVR, C | Leave a Comment »

ADS7841 12Bit AD-Wandler am AVR

Posted by krumeltee - 1. August 2011

Der ADS7841 von Texas Instruments / Burr Brown ist ein 12Bit AD-Wandler, welcher sich per SPI ansteuern lässt.

Er verfügt über 4 Single Ended oder 2 Differential Eingänge. Die Versorgungsspannung liegt zwischen 2,7V und 5V.

Hier habe ich einen Beispielcode, welcher die 4 Kanäle im Single Ended Modus ausliest. Die 4 Pins des AD-Wandler, welche nötig sind um ihn anzusprechen lassen sich auf beliebige Ports und Pins des AVRs legen.

Hier die Standard-Beispiel-Schaltung aus dem Datenblatt:

Im Code werden die Ports/Pins per #define angegeben.

Hier der Code:

#define CLK PB0
#define DIN PB2
#define CS PB1
#define DOUT PB4
#define PORT_CLK PORTB
#define PORT_DIN PORTB
#define PORT_CS PORTB
#define PORT_DOUT PORTB
#define DDR_CLK DDRB
#define DDR_DIN DDRB
#define DDR_CS DDRB
#define DDR_DOUT DDRB
#define PIN_DOUT PINB
#define SET_CS PORT_CS |=(1<<CS)
#define CLR_CS PORT_CS &=~(1<<CS)
#define SET_CLK PORT_CLK |=(1<<CLK)
#define CLR_CLK PORT_CLK &=~(1<<CLK)
#define SET_DIN PORT_DIN |=(1<<DIN)
#define CLR_DIN PORT_DIN &=~(1<<DIN)
#define GET_DOUT (PIN_DOUT &(1<<DOUT))

DDR_DIN &= ~(1<<DIN);
PORT_DIN |= (1<<DIN);
DDR_DIN  |= (1<<DIN);
DDR_CLK  |= (1<<CLK);
DDR_CS   |= (1<<CS);

get_ads7841(0);

unsigned short get_ads7841(char channel) {
    unsigned char i, control_byte;    
    unsigned short dat = 0;            

    control_byte = 0x97;    // default channel 0
    switch(channel) {        // if a channel between 1 and 3 is given, change the control byte
        case 1: control_byte = 0xD7; break;
        case 2: control_byte = 0xA7; break;
        case 3: control_byte = 0xE7; break;
    }

    CLR_CLK;
    CLR_DIN;
    CLR_CS;

    for(i=0; i<8; i++) {                    // send control byte to ADS7841
        if(control_byte & 0x80) SET_DIN;
        else CLR_DIN;
        CLR_CLK;
        SET_CLK;
        control_byte = control_byte<<1;      /* Shift bit once next time */
    }
    CLR_CLK;
    SET_CLK;

    for(i=0; i<12; i++)    {                    // read data from ADS7841 (12bits)
        CLR_CLK;
        SET_CLK;
        dat = dat<<1;            
        dat = dat | GET_DOUT;     
    }

    for(i=0;i<4;i++) {                         // we have to do four clock transitions, to because we only need 12bits
        CLR_CLK;
        SET_CLK;
    }

    SET_CS; 

    return dat>>4;
}

Posted in AVR, C | Leave a Comment »

MAX127 I²C-AD-Wandler am AVR

Posted by krumeltee - 27. Juli 2011

Ich bin vor kurzem durch Zufall an zwei MAX127 im DIP-Gehäuse gekommen, sehr praktische ADCs.

Hier ein kurzer Überblick:

  • I²C Interface
  • 8 Channels
  • 12Bit Auflösung
  • einzelne 5V Versorgungsspannung, ausreichend genaue Referenz mit 2 Kondesatoren möglich
  • 0-5V, 0-10V, -5V – +5V, -10V – +10V Input Range
  • -16,5V – +16,5V Überspannungsschutz auf den Eingängen
  • Bis zu 9 Chips am Bus verwendbar (72 Analoge Eingänge max.)

Durch die sehr starke Input Protection von +-16,5V ist dieser ADC nahezu Idioten- und Steckbrettsicher. Die Kommunikation ist extrem einfach gehalten, auch die Einstellmöglichkeiten sind nicht allzu aufgebläht, sodass man das Datenblatt in 10 Minuten auswendig kann.

Die Auflösung von 12Bit ist auch sehr gut, das reicht für Basteleien, Roboter, Akkuladegeräte, als Eingangs-ADC für analog gesteuerte Lichttechnik, …, … dicke aus.

Die Kommunikation benötigt für eine AD-Wandlung 4 Byte, also lasst uns mal rechnen:

100kHz Bus: 1/(32bit/100.000bit pro sekunde) = 3125 Messungen pro Sekunde

400kHz Bus: 1/(32bit/400.000bt pro sekunde)=12500 Messungen pro Sekunde

Soviel zur Theorie, die Start- und Stopbits, der Protokolloverhead, noch nicht mitgerechnet. Dies macht allerdings nicht allzuviel aus. Das Datenblatt gibt 8000 Samples Per Second an, das entspricht 125µS pro Messung. Um nun alle 8 Channels einmal abzufragen benötigt man also eine Millisekunde.

Nun genug der Rechnerei, hier zur Hardware, welche SEHR SEHR einfach ist.

Bei mir hing der ADC zum Testen an einem atmega32, sollte aber mit jedem AVR mit I²C funktionieren, wahrscheinlich schon auf einem attiny2313 oder ähnliche mit 2kb Flash.

Die Adresse des Chips lässt sich über die Pins A0, A1, A2 einstellen. Sind alle drei Pins auf GND, so ist die Adresse 0x50. Will man eine andere Adresse, sollte man sich das Datenblatt durchlesen.

Die Software ist ebenso einfach wie die Hardware. Mein Beispielcode für den ADC baut auf der Fleury I²C-Library auf. Die Display-Ausgaben in den weiteren Beispielen bauen auf der Fleury LCD-Library für ein HD44780 Display auf.

Hier erstmal der Quellcode:

/* get_max127()
*
* address - I2C Bus Addresse des Chips
* channel - Der Kanal, von 0 bis 7
* mode - 0 = 0-5V messung, 1 = 0-10V messung
* 2 = +-5V messung, 3 = +-10V messung
*
*/ 
unsigned short get_max127(unsigned char address, unsigned char channel,unsigned char mode) {
    unsigned char high, low, ret, config_byte;
    unsigned short value;
    if(channel > 7) return 0;

     ret = i2c_start(address);                                      // start communication
    if(ret == 0) {                                                // chip found
        if(mode == 3) config_byte = (0x8c | (channel<<4));        // configuration byte, for information see datasheet
        else if(mode == 2) config_byte = (0x84 | (channel<<4));
        else if(mode == 1) config_byte = (0x88 | (channel<<4)); 
        else config_byte = (0x80 | (channel<<4));
         i2c_write(config_byte);
        i2c_stop();

        i2c_start(address+1);
        high = i2c_readAck();
        low = i2c_readNak();
        i2c_stop();
        value = ((high<<8)|low);
        value >>= 4;
    } else {
        return 0;
    }
    return value;
}

Hier dann noch ein paar Beispiele zum verwenden der Funktion:

/* Channel 0, Wert in Hex, Dezimal, Volt von 0-5V Messung per RS232 rausschicken */
char buffer[64];
unsigned short ad_value = get_max127(0x50, 0, 0);
sprintf(buffer, "hex:%03x dez:%d volt:%1.4fV", ad_value, ad_value, ((5.0/0xfff)*ad_value));
uart_puts(buffer);

/* Channel 3, Wert in Volt auf HD44780 LCD mit Fleury LCD Lib ausgeben */
char buffer[17];    // buffer ist 17 bytes gross, fuer 16 zeichen LCD + ''
unsigned short ad_value = get_max127(0x50, 3, 1);
sprintf(buffer, "%1.4fV", ((10.0/0xfff)*ad_value));
rs232_send(buffer);

 

Sollten noch Fragen sein, könnt ihr mir schreiben oder aber auch das Datenblatt lesen. Zum Schluss natürlich noch ein Foto:

 

Posted in AVR, C, I2C | 2 Comments »