Rudi's Homepage - AVR C-Projekte

EnglishDeutsch

JTAG

Beschreibung:

Für die Einarbeitung in die C-Programmierung von Mikrocontrollern habe ich mir günstig ein chinesisches USB-JTAG-Modul und ein STK-16-ähnliches Experimentiermodul geholt.

JTAG-Schnittstelle und Experimentiermodul.
JTAG-Schnittstelle und Experimentiermodul.

Doch die JTAG-Schnittstelle verweigerte anfangs ihren Dienst. Dank der Mithilfe aus dem Mikrocontroller.net-Forums konnte das Problem für Linux gelöst werden.

Ein Blick in das geöffnete Gerät offenbarte, daß im Inneren ein FT-232 und ein ATmega16 ihren Dienst tun. Forumsteilnehmer identifizierten den Aufbau somit als JTAG ICE mkI-Klon.

Allerdings wurde der USB-zu-Seriell-Konverter von Linux nicht richtig angesprochen - es wurde kein /dev/ttyUSBx-Geräteknoten angelegt.

Ein lsusb -v offenbarte die Hersteller-ID und die Geräte-ID des Bausteins:

  idVendor           0x0807
  idProduct          0x8001
  bcdDevice            4.00
  iManufacturer           1 LSTX
  iProduct                2 USB<==>JTAG ICE
  iSerial                 3 LSR6LPSK
...
     bInterfaceClass       255 Vendor Specific Class
     bInterfaceSubClass    255 Vendor Specific Subclass
     bInterfaceProtocol    255 Vendor Specific Protocol
     iInterface              2 USB<==>JTAG ICE
		
Diese Informationen müssen dem Kernelmodul ftdi_sio übergeben werden. Das Laden des Kernel-Moduls soll per udev beim Einstecken des Kabels erfolgen, deshalb muß die Datei
 /etc/udev/rules.d/99-usbftdi.rules
		
mit dem folgenden Inhalt angelegt werden:
 SYSFS{idProduct}=="8001", SYSFS{idVendor}=="0807", RUN+="/sbin/modprobe -q ftdi_sio product=0x8001 vendor=0x0807"
		
Nun sollte nach dem Einstecken des Kabels ein /dev/ttyUSBx-Geräteknoten und ein Kommentar des Vorgangs in der /var/log/messages vorhanden sein.

Wenn nun auch noch das Experimentiermodul angeschlossen ist, kann man mit

 avarice -1 -j /dev/ttyUSB0
		
dieses auch angesprochen werden. Die Antwort sieht dann wie folgt aus:
AVaRICE version 2.10, Dec 18 2009 16:10:11

Defaulting JTAG bitrate to 250 kHz.

JTAG config starting.
Hardware Version: 0xc1
Software Version: 0x80
Reported JTAG device ID: 0x9403
Configured for device ID: 0x9403 atmega16
JTAG config complete.
		


Start

Beschreibung:

Erste Schritte

Als erstes empfiehlt es sich, die Funktion des gesamten Entwicklungssystems mit einem möglichst einfachen Programm zu testen. Ich habe ein Programm gewählt, welches eine am Anschluß 1 des Ports A angeschlossene LED aufleuchten läßt.

#include <avr/io.h>

int main()
{
	DDRA = 0xFF;
	PORTA = 0x1;
	while(1==1);
}
		
Das Program schaltet alle Anschlüsse von Port A als Ausgänge, setzt den ersten Anschluß auf 1 und geht in eine Endlosschleife.

Übersetzt und in den Microcontroller hochgeladen wird das Program mit diesem Makefile, welches ich vom Mikrocontroller-Forum übernommen habe.

Blinken

Nun soll das Programm so erweitert werden, daß die LED im Sekundentakt blinkt. Für die Verzögerung kann komfortabel auf die _delay_ms()-Funktion der GNU-C-Funktionsbibliothek zurückgegriffen werden.

#ifndef F_CPU
// CPU-Takt
#warning "F_CPU war noch nicht definiert, wird nun mit 7372800 definiert"
#define F_CPU 7372800UL   
#endif
#include <util/delay.h>
#include <avr/io.h>

#define LED PA0

int main()
{
	 // Schalte alle Pins von PortA als Ausgänge
	 DDRA=0xFF;
	 while(1)
	 {
		 PORTA ^= (1<<LED); // Schalte LED um
		 _delay_ms(1000); // warte eine Sekunde
	 }
	 return 0;
}
		

Nun soll das Programm so erweitert werden, daß die Blinkfrequenz durch Tastendruck umgeschaltet werden kann. Der Taster wird am Anschluß PD2 angeschlossen, so daß die Reaktion auf den Tastendruck durch den INT0-Interrupt erfolgen kann. Der Interrupt wird mit

	GICR = (1<<INT0);
		
freigeschaltet und mit
	MCUCR = (1 << ISC01);
		
durch eine fallende Flanke an PD2 ausgelöst. Wichtig ist auch, daß mit
	PORTD = TASTER;
		
die Pull-Up-Widerstände aktiviert werden, damit ohne gedrückten Taster auch stabiler H-Pegel an PD2 anliegt.

Wichtig ist auch, daß die Variable für die Blinkdauer (delay) als volatile deklariert wird - so wird sie im Speicher und nicht im Register abgelegt und steht somit auch der Interruptroutine zur Verfügung. Der komplette Quelltext des geänderten Programs:

#ifndef F_CPU
// CPU-Takt
#warning "F_CPU war noch nicht definiert, wird nun mit 7372800 definiert"
#define F_CPU 7372800UL   
#endif
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define LED (1<<PA0)
#define TASTER (1<<PD2)

volatile unsigned int delay = 100;

ISR(INT0_vect)
{
  delay = (delay==1000) ? 100 : 1000; 
}

int main()
{
	DDRA = LED;
	DDRD = 0;
	PORTD = TASTER; /* activate pull-up resistors */
	GICR = (1<<INT0);
	MCUCR = (1 << ISC01);
	sei();
	while(1==1)
	  {
	    PORTA ^= LED;
		_delay_ms(delay);
	  }
}
		
		

Nun soll das Umschalten der LED durch einen Zeitgeberinterrupt erledigt werden, damit die CPU in der Zeit zwischen den Umschalten der LED in den Schlafmodus versetzt werden kann.

Zu diesem Zweck wird der Compare-Interrupt des 16-Bit-Zählers Timer1 verwendet. Dieser wird mit

	TIMSK = (1<<OCIE1A);
		
aktiviert und lt einen Interrupt aus, wenn der Zählwert des Zählers den im Register OCR1A gespeicherten Wert erreicht. Da der Vorteiler des Zählers mit
	TCCR1B = (1<<CS02);		
		
auf 256 gesetzt. Da der Zähler nun mit der Quarzfrequenz/256 zählt, erreicht er nach einer Sekunde folglich den Zählerstand Quarzfrequenz/256. Deshalb werden die Zielwerte für eine und eine Zehntelsekunde auf
const int time1=F_CPU/256;
const int time2=F_CPU/2560;	
		
gesetzt. In der Serviceroutine wird die LED umgeschaltet und, wichtig, der Zählerstand des Zählerregisters auf 0 gesetzt. Andere Interrupts werden dafür abgeschaltet, da das Laden eines 16-Bit-Registers keine atomare Operation ist. Das Statusregister wird gesichert, damit darin gespeicherte Interruptflags und auch der Zustand der für Vergleichsoperationen wichtigen logisch/arithmetischen Flags nicht verändert wird.
ISR(TIMER1_COMPA_vect)
{
  char sreg=SREG;
  cli();
  PORTA ^= LED;
  TCNT1 = 0;
  sei();
  SREG=sreg;
}
		
Die Endlosschleife im Hauptprogramm wird so modifiziert, daß die CPU zwischen den Interrupts im Schlafmodus ist.
	while(1)
	  {
             set_sleep_mode(SLEEP_MODE_IDLE);
             sleep_mode();
	  }
		
Der komplette Quelltext wie folgt.
#ifndef F_CPU
// CPU-Takt
#warning "F_CPU war noch nicht definiert, wird nun mit 7372800 definiert"
#define F_CPU 7372800UL   
#endif
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>


#define LED (1<<PA0)
#define TASTER (1<<PD2)


const int time1=F_CPU/256;
const int time2=F_CPU/2560;

/**
 * @brief Timer 1 Compare A interrupt service routine.
 * Used to switch the LED.
 */
ISR(TIMER1_COMPA_vect)
{
  char sreg=SREG;
  cli();
  PORTA ^= LED;
  TCNT1 = 0;
  sei();
  SREG=sreg;
}

/**
 * @brief Interrupt service routine to handle keypress.
 * This ISR switches the blinking frequency of the LED.
 */
ISR(INT0_vect)
{
  char sreg=SREG;
  cli();
  OCR1A = ((OCR1A == time1) ? time2 : time1);
  TCNT1 = 0;
  sei();
  SREG=sreg;
}

int main()
{
	DDRA = LED;
	DDRD = 0;
	PORTD = TASTER; /* activate pull-up resistors */
	GICR = (1<<INT0); /* activate INT0 interrupt on falling edge */
	MCUCR = (1 << ISC01);
	OCR1A = time1;
	TIMSK = (1<<OCIE1A); /* enable output-compare interrupt on Timer1 level A */
	/* set prescaler of Timer 1 to 256 */
	TCCR1B = (1<<CS02);
	sei();
	while(1)
	  {
             set_sleep_mode(SLEEP_MODE_IDLE);
             sleep_mode();
	  }
}		
		

Das letzte Programm kann durch Ausnutzung einer Sonderfunktion des Zeitgebers 1 effizienter umgesetzt werden. Dazu muß die LED am Anschluß PD5 (OC1A) angeschlossen werden. Der Zeitgeber 1 wird dazu in den CTC-Modus geschaltet. Er zählt dann bis zum Wert im OCR1A-Register, lößt dann eine Aktion aus und wird auf 0 zurückgesetzt. Die eingestellte Aktion ist das Umschalten des OC1A-Anschlusses.

#ifndef F_CPU
// CPU-Takt
#warning "F_CPU war noch nicht definiert, wird nun mit 7372800 definiert"
#define F_CPU 7372800UL   
#endif
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define LED (1<<PD5)
#define TASTER (1<<PD2)


const int time1=F_CPU/256;
const int time2=F_CPU/2560;

/**
 * @brief Interrupt service routine to handle keypress.
 * This ISR switches the blinking frequency of the LED.
 */
ISR(INT0_vect)
{
  OCR1A = ((OCR1A == time1) ? time2 : time1);
  TCNT1 = 0;
}

int main()
{
  DDRD = LED;
  PORTD = TASTER; /* activate pull-up resistors */
  GICR = (1<<INT0); /* activate INT0 interrupt on falling edge */
  MCUCR = (1 << ISC01);
  OCR1A = time1;
  /* enable CTC mode, toggle OC1A pin*/
  TCCR1A =  (1 << COM1A0);
  /* set prescaler of Timer 1 to 256 */
  TCCR1B = (1<<WGM12) | (1<<CS02);
  sei();
  while(1)
    {
      set_sleep_mode(SLEEP_MODE_IDLE);
      sleep_mode();
    }
}

		


PWM

Beschreibung:

Projektbeschreibung

In diesem Projekt soll die Helligkeit einer LED per Pulsweitenmodulation geregelt werden. Die Helligkeit soll über zwei Tasten erhöht und verringert werden.

Umsetzung

Das Prinzip der PWM ist einfach: solange der Zählerwert kleiner ist als der Vorgabewert ist die LED eingeschaltet, bei Erreichen des Vorgabewerts wird sie ausgeschaltet. Der Vorgang läuft periodisch ab, da der Zähler nach dem Erreichen des Maximalwerts wieder auf 0 zurückspringt. Je größer der Vorgabewert, um so länger ist die LED innerhalb des Zykluses ein. Die mittlere Helligkeit ist somit proportional zum Vorgabewert.

Verwendet wird diesmal der 8-Bit-Zähler Timer0. Dieser wird mit

  TCCR0 = (1<<WGM01) | (1<<WGM00) | (1<<COM01) | (1<<CS00);
		
auf nichtinvertierten PWM-Betrieb geschaltet, sein Vorteiler ist 1. Der Vorgabewert für die PWM muß in das Register OCR0 geschrieben werden. Damit ist die eigentliche PWM auch schon umgesetzt.

Was noch fehlt ist die Regelung der Helligkeit. Dafür werden die externen Interrupts INT0 und INT1 verwendet. In den respektiven Serviceroutinen wird der Vorgabewert mit Sättigung erhöht bzw. verringert.

/**
 * @brief Interrupt service routine to handle keypress.
 * This ISR increases the intensity of the LED.
 */
ISR(INT1_vect)
{
  uint8_t tmp_sreg;  // temporaerer Speicher fuer das Statusregister
  tmp_sreg = SREG;
  cli();
  if(OCR0 != 255) ++OCR0;
  _delay_ms(10);
  sei();
  SREG=tmp_sreg;
}
		
Durch die Verzögerung um 10ms wird die Regelung etwas weicher gestaltet.

Der komplette Quelltext des Programms:

#ifndef F_CPU
#warning "F_CPU war noch nicht definiert, wird nun mit 7,3728 MHz definiert"
#define F_CPU 737280UL   
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h> 
#include <avr/sleep.h>


#define LED1 (1<<PB3)
#define LED2 (1<<PB4)
#define TASTER1 (1<<PD2)
#define TASTER2 (1<<PD3)

/**
 * @brief Interrupt service routine to handle keypress.
 * This ISR decreases the intensity of the LED.
 */
ISR(INT0_vect)
{
  uint8_t tmp_sreg;  // temporaerer Speicher fuer das Statusregister
  tmp_sreg = SREG;
  cli();
  if(OCR0 != 0) --OCR0;
  _delay_ms(10);
  sei();
  SREG=tmp_sreg;
}

/**
 * @brief Interrupt service routine to handle keypress.
 * This ISR increases the intensity of the LED.
 */
ISR(INT1_vect)
{
  uint8_t tmp_sreg;  // temporaerer Speicher fuer das Statusregister
  tmp_sreg = SREG;
  cli();
  if(OCR0 != 255) ++OCR0;
  _delay_ms(10);
  sei();
  SREG=tmp_sreg;
}

int main()
{
  DDRB = LED1 | LED2;
  PORTD = TASTER1 | TASTER2; /* activate pull-up resistors */
  GICR = (1<<INT0) | (1<<INT1); /* activate INT0/1 interrupt on falling edge */
  OCR0 = 0;
  /* enable non-inverted PWM mode for timer 0*/
  /* set prescaler of Timer0 to 1 */
  TCCR0 = (1<<WGM01) | (1<<WGM00) | (1<<COM01) | (1<<CS00);
  sei();
  while(1)
    {
      set_sleep_mode(SLEEP_MODE_IDLE);
      sleep_mode();
    }
}