Source file: /~heha/mb-iwp/Durchflussmesser/Adapter-Firmware.zip/Firmware/Flow.c

/* Programm für Durchflussmesser
 * Hardware (vorläufig, Extra-Anschluss nicht benutzt):
 * Das Gerät benutzt 2 A/D-Wandler und 1 Impulszähler.
 * ADC2 misst die 24-V-Versorgung
 * ADC3 misst den Sensor-Strom
 * ICP dient zur Periodendauermessung
 * Eine Zweifarb-LED dient zur Kontrollanzeige:
 * grün = OK, rot = Bootloader
 *
 * tabsize = 8, encoding = utf-8?, lineends = LF
 */

#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>

#include "usbdrv.h"

/************
 * Hardware *
 ************/
/*
Verwendung der Ports:
PB0	(12)	ICP	Impuls pro Volumeneinheit
PB1	(13)	-	-
PB2	(14)	OC1B	zum Gate des Hochsetzsteller-Schalttransistors
PB3	(15)	MOSI	Programmieranschluss
PB4	(16)	MISO	Programmieranschluss
PB5	(17)	SCK	Programmieranschluss
PB6	(7)	-	- (Quarz nicht bestückt)
PB7	(8)	-	- (Quarz nicht bestückt)

PC0	(23)	ADC0	Extra-Eingang E3
PC1	(24)	ADC1	Extra-Eingang E2 ODER verstärkte Differenzspannung
PC2	(25)	ADC2	24-V-Spannungskontrolle, U = U24 * 0,0909
PC3	(26)	ADC3	Sensorstrom, U = I * 120 Ω, 4 mA ↔ 192, 20 mA ↔ 960
PC4	(27)	-	LED grün Anode
PC5	(28)	-	LED rot Anode
PC6	(29)	/RESET	Programmieranschluss

PD0	(30)	-	RxD-Debuganschluss
PD1	(31)	-	TxD-Debuganschluss
PD2	(32)	-	USB D+
PD3	(1)	INT1	USB D-
PD4	(2)	-	Debug-Ausgang: Toggle für ADC2 (ca. 1 kHz)
PD5	(9)	-	Jumper low-aktiv für USB-Bootloader
PD6	(10)	AIN0	Extra-Eingang E3
PD7	(11)	AIN1	Extra-Eingang E2 ODER verstärkte Differenzspannung

-	(20)	AREF	Interene Referenzspannung 2,56 V (Stützkondensator)

Verwendung der Zähler:
Timer0 (8 bit) = für OSCCAL-Synchronisation an USB
Timer1 (16bit) = PWM für Speisespannungserzeugung (8 bit genutzt)

Daten des Strömungssensors ifm SD6000
Ausgänge sind programmierbar, sinnfälligste Programmierung:
OUT2 = Analogausgang 4..20 mA, Zuordnung nichtlinear!
	mA	Nm³/h
	4,3	1,8
	6,1	11
	6,7	14
	7,9	18
	8,7	24
	10,9	35
	17,3	67,5
OUT1 = Mengenzähler, einstellbar 0,001 .. 1000 m³

Umrechnung in HID-Einheiten: 1 Nm³/h = 277,7 cm³/s; 1 cm³/s = 0,0036 Nm³/h

Aderfarben ifm SD6000:
1	br	19..30 V 100 mA
2	ws	OUT2: Analoger Stromausgang 4..20 mA gegen Masse
3	bl	Masse
4	sw	OUT1: Impulsausgang oder Schaltausgang
*/

static struct __attribute__((packed)) {
 short S2;	// A/D-Wert vom Strom
 short S3;	// Volumeneinheit-Zähler (umlaufend)
 short M24P;	// A/D-Wert der Speisespannung (typ. 24 V)
 short E2;	// A/D-Wert von E2
}InputReport={0x8000,0x8000,0x8000,0};	// NaNs

static struct __attribute__((packed)) {
 short factor;	// Konstante
 short S24P;	// Sollwert für Speisespannung (typ. 24 V)
 uchar BootStart;	// Bit0 setzen wenn Bootloader starten soll
}FeatureReport={2778,19*109/3,0};

register uchar SregSave asm("r2");	// für schnelle Interruptbehandlung
register uchar IsrSave  asm("r3");

// Capture-Interrupt nur zum Zählen der Volumen-Impulse
ISR(TIMER1_CAPT_vect) __attribute__((naked));
ISR(TIMER1_CAPT_vect) {
 asm volatile(
"	in	r2,__SREG__		\n"
"	lds	r3,InputReport+2	\n"
"	inc	r3			\n"
"	sts	InputReport+2,r3	\n"
"	brne	nsH			\n"
"	lds	r3,InputReport+3	\n"
"	inc	r3			\n"
"	sts	InputReport+3,r3	\n"
"nsH:	out	__SREG__,r2		\n"
"	reti				\n");
}

// Regelung der 24-V-Ausgangsspannung
// Stabilität lässt noch zu wünschen übrig, aber für den Durchflussmesser dürfte es reichen
static void Control(short newIst) {
// Empirische Festlegung:
#define MIN_PWM 10
#define MAX_PWM 255-30
#define ADD_PWM 70	// empirisch gefundene Zahl, wenn kleiner kommt der Wandler nicht hoch!
 uchar pwm=OCR1BL;
 uchar softmax=newIst>>2;	// 5 V → 45, 19 V → 172
 if (softmax>MAX_PWM-ADD_PWM) softmax=MAX_PWM;
 else softmax+=ADD_PWM;
 if (newIst<FeatureReport.S24P) {
  if (newIst<=InputReport.M24P) pwm++;	// Spannung zu klein und fällt?
 }else{
  if (newIst>FeatureReport.S24P+50) pwm=MIN_PWM;	// Spannung viel zu groß? (Lastabwurf?)
  else if (newIst>=InputReport.M24P) pwm--;	// Spannung zu groß und steigt?
 }
 InputReport.M24P=newIst;
 if (pwm>softmax) pwm=softmax;
 if (pwm<MIN_PWM) pwm=MIN_PWM;
 OCR1BL=pwm;
}

// ADU2 läuft mit doppelter Abfragefrequenz als ADU1 und ADU3,
// um der Regelschleife möglichst wenig Totzeit zu bieten.
// Rechnerisch 2 kSa/s für ADU2 und 1 kSa/s für die beiden anderen.
static void HandleAdc() {
 if (ADCSRA&0x40) return;
 short val=ADC;		// A/D-Wert fertig
 uchar next;		// nächste Multiplexer-Stellung
 switch (ADMUX&7) {
  case 3: InputReport.S2=val; next=0xC2; break;
  case 1: InputReport.E2=val; next=0xC2; break;
  default:
  Control(val);
  PORTD^=0x10;			// Durchlauf anzeigen
  next=0xC1;
  if (PORTD&0x10) next=0xC3;	// und als Merkbit benutzen
 }
 ADMUX=next;
 ADCSRA=0xC7;
}

/*******
 * LED *
 *******/
static uchar Led_T;	// flashing time in ms, 0 = LED is steady
static uchar Led_F;	// flashing frequency in ms (half period)
static uchar Led_C;	// flash-counter (via SOF pulses)

void Led_Start(uchar t) {
 if (!Led_T) {
  Led_C=t;
  DDRC&=!0x10;		// LED ausschalten
 }
 Led_T=Led_F=t;
}

static void Led_On1ms(void) {
 uchar led_t=Led_T, led_c;	// lokal vorhalten
 if (!led_t) return;
 led_c=Led_C;
 if (!--led_c) {
  DDRC^=0x10;			// LED ein- oder ausschalten
  led_c=Led_F;
 }
 if (!--led_t) DDRC|=0x10;	// LED einschalten
 Led_T=led_t;
 Led_C=led_c;
}

/*******
 * USB *
 *******/
#define DEFW(x) (x)&0xFF,(x)>>8
#define DEFD(x) DEFW((x)&0xFFFF),DEFW((x)>>16)

// USB Report-Deskriptor
PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
 0x06, DEFW(0xFF00),	// G Usage Page (PnPSA)
 0x09, 0x01,		// L Usage (Sensor only)
 0xA1, 0x01,		// M Collection (Application)
 0x95, 1,		// G  Report Count (1)
 0x75, 16,		// G  Report Size (16)
 0xA4,			// G  Push
 0xA1, 0x80,		// M  Collection (Wandler und Vorsatz)
 0x16, DEFW(192),	// G   Logical Minimum (192)
 0x26, DEFW(960),	// G   Logical Maximum (960)
 0x35, 4,		// G   Physical Minimum (4 mA)
 0x45, 20,		// G   Physical Maximum (20 mA)
 0x55, -3,		// G   Unit Exponent (-3)
 0x67, DEFD(0x00100001),// G   Unit (A)
 0x09, 0x44,		// L   Usage (Single Sensor)
 0x79, 4,		// L   String (4)
 0x81, 0x02,		// M   Input (Var)
 
 0x0B, DEFD(0x00010048),// L   Usage (Generic Desktop : Resolution Multiplier)
 0x55, -3,		// G   Unit Exponent (-3)
 0x67, DEFD(0x00F0F031),// G   Unit (cm³/s/A)
 0xC7, DEFD(0x00010001),// G   Uncertainty (1 % + 1 lsb)
 0x09, 0x45,		// L   Usage (Conditioner Factor)
 0x79, 8,		// L   String (8) = Calibration Data
 0xB1, 0x03,		// M   Feature (Const,Var)
 0xC0,			// M  End Collection
 
 0x15, 0,		// G  Logical Minimum (0)
 0x27, DEFD(65535),	// G  Logical Maximum (65535)
 0x35, 0,		// G  Physical Minimum (0)
 0x47, DEFD(65535),	// G  Physical Maximum (65535 m³)
 0x55, 6,		// G  Unit Exponent (6)
 0x65, 0x31,		// G  Unit (cm³)
 0x09, 0x81,		// L  Usage (Counted Volume)
 0x79, 7,		// L  String (7)
 0x81, 0x0A,		// M  Input (Var,Wrap)

 0x26, DEFW(1023),	// G  Logical Maximum (1023)
 0x09, 0x45,		// L  Usage (Power Supply Voltage)
 0x79, 5,		// L  String (5)
 0xA1, 0x82,		// M  Collection (Ist- und Sollwert)
 0x46, DEFW(2815),	// G   Physical Maximum (28,15 V)
 0x55, 5,		// G   Unit Exponent (5)
 0x67, DEFD(0x00F0D121),// G   Unit (V)
 0x09, 0x85,		// L   Usage (Istwert)
 0x81, 0x02,		// M   Input (Var)
 0x09, 0x86,		// L   Usage (Sollwert)
 0xB1, 0x02,		// M   Feature (Var)
 0xC0,			// M  End Collection

 0x09, 0x44,		// L  Usage (Single Sensor)
 0x79, 6,		// L  String (6)
 0x81, 0x02,		// M  Input (Var)
 
 0xB4,			// G  Pop 
 0x25, 1,		// G  Logical Maximum (1)
 0x75, 1,		// G  Report Size (1)
 0x09, 0x1F,		// L  Usage (Boot Loader Control)
 0xA1, 0x02,		// M  Collection (Logical)
 0x09, 0x82,		// L   Usage (Boot Loader Activation)
 0xB1, 0x02,		// M   Feature (Var)
 0x95, 7,		// G   Report Count (7)
 0xB1, 0x03,		// M   Feature (Const,Var)
 0xC0,			// M  End Collection
 0xC0};			// M End Collection
 
typedef int wchar_t;
 
PROGMEM char usbDescriptorString0[]={6,3,DEFW(0x0407),DEFW(0x0409)};
PROGMEM wchar_t strVolumeFlow[0x16]=L"\x032C" L"Volumenstrom in Nm³/h";
PROGMEM wchar_t strSupplyVolt[0x14]=L"\x0328" L"Speisespannung in V";
PROGMEM wchar_t strExtraInput[0x14]=L"\x0328" L"Extra-Spannung in V";
PROGMEM wchar_t strFlowTicks [0x18]=L"\x0330" L"Gezähltes Volumen in m³";
PROGMEM struct __attribute__((packed)) {
 uchar bDescriptorLength;
 uchar bDescriptorType;
 float fLinTable[14];
}strCalibData={2+14*sizeof(float),USBDESCR_STRING,{
 4.3,1.8, 6.1,11, 6.7,14, 7.9,18, 8.7, 24, 10.9, 35, 17.3, 67.5}};

/***********************
 * SETUP data received *
 ***********************/
usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) {
 uchar *p;
 if (rq->wValue.bytes[1] == USBDESCR_STRING) {	// wValueH == 3
  switch (rq->wValue.bytes[0]) {		// wValueL == String-ID
   case 4: p = (uchar*)strVolumeFlow; goto e;
   case 5: p = (uchar*)strSupplyVolt; goto e;
   case 6: p = (uchar*)strExtraInput; goto e;
   case 7: p = (uchar*)strFlowTicks; goto e;
   case 8: p = (uchar*)&strCalibData; goto e;
  }
 }
 return 0;
e:
 usbMsgPtr = p;
 return USB_READ_FLASH(p);
}

uchar usbFunctionWrite(uchar *data, uchar len) {
 if (len==sizeof(FeatureReport)) {
  short Soll = *(short*)(data+2);
  if ((unsigned short)Soll<0x400) FeatureReport.S24P = Soll;
  uchar Boot = data[4];
  FeatureReport.BootStart = Boot;
 }
 return 1;
}

uchar usbFunctionSetup(uchar data[8]) {
 usbRequest_t *rq = (void*)data;
/* class request type (x01x xxxx) */
 if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
  if (rq->bRequest == USBRQ_HID_GET_REPORT) { // wValueH: ReportType, wValueL: ReportID
   if (rq->wValue.bytes[1] == 1) {		// Input
    usbMsgPtr = (uchar*)&InputReport;
    return sizeof(InputReport);
   }else if (rq->wValue.bytes[1] == 3) { 	// Feature
    usbMsgPtr = (uchar*)&FeatureReport;
    return sizeof(FeatureReport);
   }
  }else if (rq->bRequest == USBRQ_HID_SET_REPORT) { // wValueH: ReportType, wValueL: ReportID
   if (rq->wValue.bytes[1] == 3) {	 	// Feature
    return USB_NO_MSG;
   }
  }
 }
 return 0;
}

/*************************************
 * Initialisierung und Hauptschleife *
 *************************************/
EEMEM uchar BootloaderByte;
EEMEM short VoltageSoll = 19*109/3;	// vorerst 19 V (reicht)
EEMEM short FlowTicks = 0;

static void hardwareInit(void) {
 ACSR  = 0x80;
 PORTB = 0xFA;		// Pullups EIN (an offenen Eingängen)
 DDRB  = 0x04;
 PORTC = 0x50;		// grüne LED EIN
 DDRC  = 0x30;
 PORTD = 0x23;		// Pullup für Bootload-Jumper und freie Eingänge
 DDRD  = 0x12;
 short tmp;
 eeprom_read_block(&tmp,&VoltageSoll,sizeof(short));
 if (tmp!=-1) FeatureReport.S24P = tmp;
 eeprom_read_block((void*)&InputReport.S3,&FlowTicks,sizeof(short));
 TCCR0 = 0x03;		// Timer0: Vorteiler 64 (für USB)
 OCR1BL= MIN_PWM;	// schmale Pulse zum Start
 TCCR1A= 0x21;		// Schnelle PWM 8 bit, OC1B = PWM @ 50 kHz
 TCCR1B= 0xC9;		// Timer1: Vorteiler 1, Capture: Noise Canceler, steigende Flanke
 TIMSK = 0x20;		// Capture-Interrupt freigeben
 ADMUX = 0xC3;		// ADU mit Referenz 2,56 V und Kanal 3
 ADCSRA= 0xC7;		// ADU mit 100 kHz Takt starten (maximaler Vorteiler)
}

// Updates an ATmega-internal EEPROM location when different
static void ActualizeEepromByte(uchar *ee_addr, uchar b) {
 if (eeprom_read_byte(ee_addr)==b) return;
 EEDR=b;
 cli();
 EECR|=1<<EEMWE;
 EECR|=1<<EEWE;		// don't wait here!
 sei();
}

static void ActualizeEepromB(const uchar *pRam, uchar *pEe, size_t n) {
 if (!n) return;
 do{
  if (!eeprom_is_ready()) return;
  ActualizeEepromByte(pEe++,*pRam++);
 }while(--n);
}
#define ActualizeEeprom(r,e,l) ActualizeEepromB((const uchar*)r,(uchar*)e,l)

int __attribute__((noreturn)) main(void) {
 uchar SofCmp=0;
 MCUCR = 0x80;		// SLEEP_MODE_STANDBY
 OSCCAL= 0xCF;
 usbInit();
 sei();
 hardwareInit();

 for(;;){	// main event loop (infinite, possibly broken by watchdog)
  uchar t=usbSofCount;
  usbPoll();
  HandleAdc();
  if (!(t-SofCmp)) continue;
  SofCmp=t;
  Led_On1ms();
  if (usbInterruptIsReady()) {
// Generate interrupts at maximum rate
   usbSetInterrupt((uchar*)&InputReport,sizeof(InputReport));
  }
  if (eeprom_is_ready()) {
   ActualizeEepromByte(&BootloaderByte,FeatureReport.BootStart&1 ? 0x42 : 0);
   ActualizeEeprom(&FeatureReport.S24P,&VoltageSoll,sizeof(short));
   ActualizeEeprom(&InputReport.S3,&FlowTicks,sizeof(short));
  }
 }
}
Detected encoding: UTF-80