/* Name: sht11.c (Temperatur- und Feuchtesensor mit SHT11)
* Implementationsbeispiel für PnPSA auf ATtiny45
* Tabsize: 8, Encoding: UTF-8, LineEnds: LF
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include "usbdrv.c"
/************
* Hardware *
************
Eigenschaften des SHT11:
Verkorkstes I²C als Schnittstelle
Messbereich: -40 .. 120 °C, 0..100 % rH
Auflösung: 14 bit (Temperatur), 12 bit (Feuchte)
Genauigkeit: ± 0,4 K (Temperatur), ± 3 % (Feuchte) im mittleren Messbereich
Zeitkonstante: 5..30 s (Temperatur), 8 s (Feuchte)
Speisespannung: 2,4 .. 5,5 V @ 3 mW
Portpin-Zuordnung: ┌──┐ ┌──┐
PB0 (5) = Fokus-LED-Ausgang !RESET ┤ └─┘ ├ Ucc
PB1 (6) = USB D+ SCL ┤ ATMEL ├ USB D-
PB2 (7) = USB D- SDA ┤tiny45 ├ USB D+
PB3 (2) = SCK (immer Ausgang) GND ┤ ├ !Fokus
PB4 (3) = SDA (Richtung wechselnd) └───────┘
*/
#define elemof(x) (sizeof(x)/sizeof(*(x)))
enum {false,true};
typedef uchar bool;
/* USB report descriptor */
#define W(x) (x)&0xFF,(x)>>8
#define D(x) W(x&0xFFFF),W(x>>16)
PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
0x06, W(0xFF00), // G Usage Page (0xFF00 = hersteller-eigen)
0x09, 0x01, // L Usage (PnP-Sensor)
0xa1, 0x01, // M Collection (Application)
// Status- und Steuerbits
0x85, 7, // G Report ID (7)
0x15, 0, // G Logical Minimum (0)
0x25, 1, // G Logical Maximum (1)
0x75, 1, // G Report Size (1 bit)
0x95, 4, // G Report Count (4 Bits)
0x1A, W(0x220), // L Usage Minimum (Resolution Control)
0x2A, W(0x223), // L Usage Maximum (Test Mode)
0x89, 6, // L String Minimum (6 = Reduced resolution)
0x99, 9, // L String Maximum (9 = Test Mode)
0xB1, 0x02, // M Feature (Var,NoPreferred)
0x95, 1, // G Report Count (1 Bit)
0x09, 0xFF, // L Usage (Focus LED)
0x79, 10, // L String Index (10 = Focus)
0xB1, 0x02, // M Feature (Var,NoPreferred)
0x09, 0xFE, // L Usage (Transmission Failed)
0x79, 11, // L String Index (11 = Transmission Error)
0xB1, 0x82, // M Feature (Var,NoPreferred,Volatile)
0x0A, W(0x226), // L Usage (Low battery warning)
0x79, 12, // L String Index (12 = End Of Battery)
0xB1, 0x82, // M Feature (Var,NoPreferred,Volatile)
0xB1, 0x03, // M Feature (Const)
// Temperatur
0x85, 3, // G Report ID (3)
0x26, W(16380), // G Logical Maximum (16380)
0x67, D(0x10001), // G Unit (Temperature in K)
0x55, 0x0E, // G Unit Exponent (0.01 K)
0x36, W(23315), // G Physical Minimum (-40.00 °C = 233.15 K)
0x47, D(39695), // G Physical Maximum (123.80 °C = 396.95 K)
0x75, 16, // G Report Size (16 bits)
0x95, 1, // G Report Count (1 Wert)
0x79, 4, // L String Index (4 = Air Temperature)
0x09, 0xE7, // L Usage (Temperatur)
0xB1, 0xA2, // M Feature (Var,NoPreferred,Volatile)
0x85, 1, // G Report ID (1)
0x79, 4, // L String Index (4 = Air Temperature)
0x09, 0xE7, // L Usage (Temperatur)
0x81, 0x22, // M Input (Var,NoPreferred)
// Feuchte
0x85, 5, // G Report ID (5)
0x26, W(3500), // G Logical Maximum (3500)
0x65, 0x01, // G Unit (no dimension)
0x55, 0x0C, // G Unit Exponent (0.0001, not in percent!)
0x35, 0, // G Physical Minimum (0.0000 = 0.00 %)
0x46, W(10000), // G Physical Maximum (1.0000 = 100.00 %)
0x79, 5, // L String Index (5 = Relative Humidity)
0x09, 0xEA, // L Usage (Feuchte)
0xB1, 0xB2, // M Feature (Var,NoPreferred,NonLinear,Volatile)
0x85, 1, // G Report ID (1)
0x79, 5, // L String Index (5 = Relative Humidity)
0x09, 0xEA, // L Usage (Feuchte)
0x81, 0x22, // M Input (Var,NoPreferred)
0xc0}; // M End Collection
#define WAIT wait();
#define SCKL {PORTB&=~0x08; WAIT;}
#define SCKH {PORTB|= 0x08; WAIT;}
#define SDAQ (PINB&0x10)
#define SDAL {PORTB&=~0x10; DDRB|=0x10; WAIT;} // Low
#define SDAH {DDRB&=~0x10; PORTB|=0x10; WAIT;} // HiZ
void wait(void) {
_delay_us(2.5); // seltsamerweise funktioniert es nict mit 1 µs!
}
// n==1: Sendet Bit 7, schiebt <b> nach links und empfängt Bit 0
// n==8: Sendet und empfängt 8 Bits
static uchar I2cBits(uchar b, uchar n) {
do{
if (b&0x80) SDAH else SDAL;
SCKH;
b<<=1;
if (SDAQ) b|=1;
SCKL;
}while(--n);
return b;
}
// Sendet 8 Bits, empfängt 1 Bit (Bit 0 vom Ergebnis, übrige Bits = 0)
static uchar I2cSend(uchar b) {
I2cBits(b,8); // ohne Kollision sollte wieder <b> rauskommen!
return I2cBits(0x80,1);
}
// Empfängt 8 Bits, sendet 1 Bit (Bit 7 von <nak>)
static uchar I2cRecv(uchar nak) {
uchar b=I2cBits(0xFF,8);
I2cBits(nak,1);
return b;
}
static volatile unsigned tick;
#define TIMEOUT 400 // ms
static void Yield(void);
EMPTY_INTERRUPT(PCINT0_vect);
static short WaitReady(void) {
unsigned t=tick;
GIFR=0x20; // PCIF löschen
GIMSK=0x60; // USB und SDA
PINB|=0x01; // LED aufblitzen lassen
while (SDAQ) { // warten solange High
if (tick-t>TIMEOUT) {
GIMSK=0x40; // nur USB
return -2;
}
Yield();
}
PINB|=0x01; // LED wieder aus
GIMSK=0x40; // nur USB
return 0;
}
static void ConnectionReset(void) {
uchar i;
for (i=0;i<9;i++) { // Startsequenz
SCKH;
WAIT;
SCKL;
}
}
register uchar crc_start asm("r2");
register uchar crc asm("r3");
register bool failed asm("r4");
static void TransmissionStart(void) {
if (failed) ConnectionReset();
SCKH;
SDAL;
SCKL;
WAIT;
SCKH;
SDAH;
SCKL;
}
static void cs_put(uchar x) { // Prüfsummen-Maschinerie
uchar i;
for (i=0; i<8; i++) {
crc=(crc>>1)^(((signed char)(x^(crc<<7))>>7)&0x8C);
x<<=1;
}
}
// Holt Messwert (code=3: Temperatur, code=5: Feuchte) oder Statusbits (code=7)
static short getVal(uchar code) {
short w;
uchar b;
TransmissionStart();
if (I2cSend(code)) {failed=true; return -1;} // "Übertragungsfehler!"
crc=crc_start;
cs_put(code);
if (WaitReady()==-2) {failed=true; return -2;} // "Zeitüberschreitung!"
w=b=I2cRecv(0);
cs_put(b);
if (code==7) {
crc_start=b&0x0F;
}else{
b=I2cRecv(0);
cs_put(b);
w=(w<<8)|b;
}
b=I2cRecv(0xFF);
failed = crc-b; // "Prüfsummenfehler!"
return w;
}
// Setzt Statusbits
static void setVal(uchar v) {
TransmissionStart();
failed = I2cSend(6) || I2cSend(v); // "Übertragungsfehler!"
if (!failed) crc_start=v&0x0F;
}
static uchar replyBuf[128] __attribute((section(".noinit")));
static uchar idleRate; // in 4 ms, Minimum 100 ms via Deskriptor, 0=∞
static unsigned idleTimer __attribute((section(".noinit"))); // in 1 ms
static struct{
uchar id;
short temp,humi;
}inputReport __attribute((section(".noinit")));
static bool fillInputReport() {
short w=getVal(3);
if (w>=0) {
inputReport.temp=w;
w=getVal(5);
if (w>=0) {
inputReport.humi=w;
return true;
}
}
return false;
}
USB_PUBLIC uchar usbFunctionSetup(uchar data[8]) {
usbRequest_t *rq = (void *)data;
/* class request type (x01x xxxx) */
if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){
switch (rq->bRequest) {
case USBRQ_HID_GET_REPORT: {
if (rq->wValue.bytes[1]==3) { // Feature
uchar b=rq->wValue.bytes[0]; // wValueH: ReportType, wValueL: ReportID
*replyBuf=b;
bool f=failed; // vorher
short w=getVal(b);
if (b==7) {
if (f||failed) w|=0x20; // Bit ohne Deskriptor
if (!(PORTB&0x01)) w|=0x10; // Fokus-LED-Bit (zz. unschön!)
}
*(short*)(replyBuf+1)=w;
usbMsgPtr = replyBuf;
return rq->wLength.bytes[0];
}else{
if (!idleRate && !fillInputReport()) break;
usbMsgPtr=&inputReport.id;
return sizeof inputReport;
}
}break;
case USBRQ_HID_SET_REPORT: {
if (rq->wValue.word=0x0307) return 0xFF; // usbFunctionWrite() tut es
}break;
case USBRQ_HID_GET_IDLE: {
usbMsgPtr = &idleRate;
return 1;
}break;
case USBRQ_HID_SET_IDLE: {
idleRate = rq->wValue.bytes[1]; // wValueH
idleTimer = 1; // sofort Wert auslesen (Doku richtig gelesen?)
}break;
}
}
return 0;
}
USB_PUBLIC uchar usbFunctionWrite(uchar *data, uchar len) {
if (len==2 && data[0]==7) { // nur für einziges Feature-Out
setVal(data[1]&0xCF);
if (data[1]&0x10) PORTB&=~0x01; else PORTB|=0x01; // Fokus-LED schalten
return 1; // OK
}
return 0xFF; // STALL
}
#include <stdlib.h> // wchar_t
// itoa() ist zu fett! Daher diese Hex-Ersatzroutine.
static wchar_t hexDigit(uint16_t sn) {
sn>>=12;
if (sn>=10) sn+=7;
sn+='0';
return sn;
}
// Die 16-Bit-Seriennummer liegt diesmal an EEPROM-Adresse FFF8.
// Damit ist Platz für 32 Bits sowie für den avrdude-Zykluszähler
static wchar_t* insertSerial(wchar_t*d) {
// uint16_t sn=eeprom_read_byte((const uint8_t*)0xFFF8)|eeprom_read_byte((const uint8_t*)0xFFF9)<<8;
uint16_t sn;
EEAR=0xFFF8;
EECR|=0x01;
sn=EEDR;
EEARL|=0x01;
EECR|=0x01;
sn|=EEDR<<8;
char i=4;
do{
*d++=hexDigit(sn);
sn<<=4;
}while(--i);
return d;
}
// Variante nur für BMP (alles was ohne Surrogates in UTF-16 passt)
// Ohne jedwede Überlaufprüfung! Strings müssen kurz genug sein.
static uchar fromUtf8(PGM_P s) {
uchar l;
wchar_t c,*d=(wchar_t*)(replyBuf+2);
while (c=pgm_read_byte(s++)) {
if (c==1) d=insertSerial(d);
else{
if (c&1<<7) { // UTF-8-Führungsbyte?
c=c<<6&0xFFF|pgm_read_byte(s++)&0x3F; // UTF-8-Trailbytes
if (c&1<<11) c=c<<6|pgm_read_byte(s++)&0x3F;
}
*d++=c; // avr-gcc ist little-endian, passt!
}
}
l=(uchar*)d-replyBuf;
replyBuf[0]=l;
replyBuf[1]=USBDESCR_STRING;
usbMsgPtr=replyBuf;
return l;
}
static prog_char en0[]="\xD0\x87\xD0\x89"; // 0x407 0x409
static prog_char en1[]="haftmann#software (www.tu-chemnitz.de/~heha)";
static prog_char en2[]="SHT11 Temperature/Humidity";
static prog_char en3[]="heha@hrz.tu-chemnitz.de:\1";
static prog_char en4[]="Air Temperature";
static prog_char en5[]="Relative Humidity";
static prog_char en6[]="Reduced resolution";
static prog_char en7[]="No OTP reload";
static prog_char en8[]="Heater";
static prog_char en9[]="Test mode, do not use";
static prog_char enA[]="Focus";
static prog_char enB[]="Transfer failed";
static prog_char enC[]="End Of Battery";
static prog_char de2[]="SHT11 Temperatur/Luftfeuchte";
static prog_char de4[]="Lufttemperatur";
static prog_char de5[]="Relative Feuchte";
static prog_char de6[]="Reduzierte Auflösung";
static prog_char de7[]="Kein OTP-Reload";
static prog_char de8[]="Heizer";
static prog_char de9[]="Testmodus, nicht benutzen!";
static prog_char deA[]="Fokus";
static prog_char deB[]="Übertragungsfehler";
static prog_char deC[]="Unterspannung";
static const PROGMEM const char*de[]={en0,en1,de2,en3,de4,de5,de6,de7,de8,de9,deA,deB,deC};
static const PROGMEM const char*en[]={en0,en1,en2,en3,en4,en5,en6,en7,en8,en9,enA,enB,enC};
USB_PUBLIC uchar usbFunctionDescriptor(struct usbRequest *rq) {
int idx=rq->wValue.bytes[0];
if (idx>=elemof(en)) return 0;
return fromUtf8((PGM_P)pgm_read_word(rq->wIndex.bytes[0]==7?de+idx:en+idx));
}
// Diese Routine beachtet Grenzen von OSCCAL und die zwei Bereiche
// der ATtinyX5 nicht! Bis jetzt geht's trotzdem.
static void tuneOscillator(void) {
static uchar lastValue;
#define SOLL 14 // usbSofDiff sollte 16500/8%256 = 14.5 sein
#define MABW 7 // Abweichung kleiner ±6 ist OK und wird ignoriert
schar t = OCR0B-lastValue-SOLL; // zentriere Ergebnis um die Null
lastValue = OCR0B;
GPIOR0&=~0x20;
if (t<-MABW) OSCCAL++; // schneller machen
if (t> MABW) OSCCAL--; // langsamer machen
}
/*****************
* Hauptprogramm *
*****************/
int main(void) __attribute__((noreturn));
int main(void) {
uchar mcusr = MCUSR;
MCUSR = 0;
ACSR |= 0x80; // Analogvergleicher abschalten: – 70 µA
WDTCR = 0x18; // Watchdog überlebt RESET: abschalten!
WDTCR = 0;
GPIOR0= 0;
if (mcusr & (1 << WDRF)) {
if (USBIN & USBMASK) { // no SE0 state?
GIMSK = 0x40;
MCUCR = 0x30; // SLEEP_MODE_PWR_DOWN
PORTB = 0x18; // SCK und DATA auf High per Pull-Up
sei(); // Ohne Interruptfreigabe kein WakeUp
sleep_cpu(); // Alle Takte abschalten
cli();
}
}
MCUCR = 0x20; // SLEEP_MODE_STANDBY (wir brauchen T0)
PRR = 0x0B; // USI,ADC,T1 aus
PORTB = 0x11; // SCK = LOW, DATA = PullUp, kein !Fokus
DDRB = 0x09; // SCK, !Fokus = Ausgang
PCMSK = 0x10; // SDA mit Pegelwechsel-Interrupt
GIMSK = 0x40;
TCCR0B = 0x02; // T0 mit Achtel-Taktfrequenz
usbInit();
WDTCR = 0x08; // Watchdog mit kürzestmöglicher Zeit (16 ms) aktivieren
inputReport.id = 1;
sei();
for(;;) {
Yield();
usbPoll();
if (GPIOR0&0x40) { // TimeOut: Messwerte holen?
if (fillInputReport()) GPIOR0|=0x80; // Daten verfügbar
GPIOR0&=~0x40;
}
if (usbInterruptIsReady() && GPIOR0&0x80) { // neuer Messwert verfügbar
usbSetInterrupt((void*)&inputReport,sizeof inputReport);
GPIOR0&=~0x80; // abgeschickt
}
}
}
// typischerweise 1 ms bis zum SOF schlafen
static void Yield(void) {
if (USBIN&USBMASK) sleep_cpu(); // schlafen, AUSSER bei SE0, bis SOF
wdt_reset();
if (GPIOR0&0x20) { // SOF detektiert?
tuneOscillator();
tick++;
if (idleRate && !--idleTimer) {
idleTimer=idleRate<<2; // 4-ms-Schritte
GPIOR0|=0x40; // Messwert auslesen lassen, aber nur in der Hauptschleife
}
}
}
Vorgefundene Kodierung: UTF-8 | 0
|