/* 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-8 | 0
|