Zur automatischen Steuerung der Jalousien und Rolladen benötige ich einen Helligkeitssensor der unabhängig die Tageshelligkeit misst. Bis dato habe ich einen einfachen analogen LDR verwendet der über einen analogen Eingang des AVR einfach den Spannungsabfall misst und so "einen" Helligkeitswert bestimmt. Der ist dann jedoch weder kalibriert noch besonders reproduzierbar. Also sollte ein richtiger Sensor her.

Da es nichts Adäquates auf dem Markt gab welches meinen Ansprüchen genügte und - ganz wichtig - die Helligkeit/Soneneinstrahlung über CAN-Bus ausgeben kann, habe ichs eben selbst entwickelt.

Als Sensor habe ich einen kalibrierten "echten" Halbleitersensor verwendet, der mit SPI oder I2C ausgelesen werden kann. Hier habe ich den TSL2561 von TAOS mit I2C-Bus verwendet. Es gibt den gleichen Sensor auch für SPI-Busanschluss. Der Sensor kann mit 16 Bit-Auflösung einen Dynamikbereich von 1:1.000.000 erfassen. Er kann die Helligkeit in zwei Kanälen ch0 und ch1 mit unterschiedlichen (Licht)-Frequenzbereichen messen -  um sowohl den Infrarotpegel als auch den klassichen Helligkeitspegel zu erfassen. Eigentlich recht einfach. Der Sensor ist ein ausgezeichneter Umgebungslichtsensor der zwei Sensorelemente in einem winzigen Gehäuse vereint. Das Gehäuse ist 2.6mm * 3.8mm groß und erfordert zwingend SMD-Bestückung. Als Alternative kann man auch den aktuellen TSL25721 von AMS verwenden. Der hat nochmals einen erheblich weiter gesteigerten Dynamikbereich von 45.000.000 : 1. Allerdings stimmen da dei Anschlussbelegungen der beiden Chips nicht ganz überein und in der Software müssen die Registeradressen angepasst werden.

Pin TSL2561 TSL2572 -1,-2,-3 Function
1 +Ub +Ub  
2 ADR SCL  
3 GND GND  
4 SCL LDR  
5 INT INT  
6 SDA SDA  

Hier muss also nur die SCL-Leitung statt an Pin4 an Pin 2 geleitet werden.Auf der PCB ist dazu ein 0-Ohm Widerstand oder eine Brücke passend zu löten. Aber noch folgende Warnung: Der TSL25721 ist sehr schwer von Hand zu löten.

Zur Ansteuerung und zu Realiserung des CAN-Busses habe ich einen STM32F103CB-Prozessor verwendet. Dies ist ein kleiner Cortex-M3 Prozessor in TQFP 48 Gehäuse (6*6mm).

Die Schaltung soll in einem kleinem ABS-Gehäuse (50*52mm) untergebracht werden und im Hausbussystem mit 4-Draht-Bus (12V DC, GND und CAN+ und CAN-) angesprochen werden können.

Der Sensor bekommt noch einen kleinen LWL-Lichtleiter der an der Rückseite des Gehäuses bis zu 50mm herausschauen kann. Dadurch kann die Elektronik "innen" sein und der LWL-Stab über ein kleines Loch im Fensterrahmen kann nach draußen schauen. Dadurch stimmt zwar die Sensorkalibrierung im Infrarotbreich sicher nicht mehr, aber es krabbeln eben auch keine Insekten in die Elektronik die feuchtigkeitsgeschützt im Innenraum bleiben kann.

Fotos folgen noch.

Das Schaltbild im EAGLE-Format gibts übrigens unter dem nachfolgenbden Link:

Die Platine findet sich hier:

 

 

Der Sensor befindet sich auf der Rückseite der PCB und schaut dann unten am Gehäuse heraus.

 

Software

Die Software zum Betrieb des Boards habe ich in C geschrieben. Diese übernimmt die Funktionen zum zyklischen Auslesen des Helligkeitssensor per I2C oder SPI, sowie die Bedienung des CAN-Bus.  Über einen 3-poligen Pfostenstecker kann man per ASCII-Terminal (z.B. Hyperterm oder Putty) einige Einstellungen (z.B. CAN-Bus Addresse, zyklisches Versenden der Messwerte, oder nur auf Anfrage per Buskommandio, oder beides) des Boards vornehmen und im Flashspeicher abspeichern. Dies ist aber eben nur für die Programmierung nach dem Flashen der Firmware. So kann man mehrere Helligkeitssensoren auf einem Bus betreiben. Ich verwende die Elektronik so, daß ich zyklische CAN-Bus Telegramme mit der aktuellen Helligkeit versende. So können einzelne Rolladen und Jalousiekontroller selbstständig auf Helligkeitspegel reagieren. Zusätzlich versendet die Software die Werte noch auf Anfrage.

Sobald ich die Software mit der Hardware ausreichend getestet habe, gibt es diese zum Download ebenfalls hier.

 der Platine als ELF-Flashfile.

 

Nachtrag:

Ich habe jetzt auch die Software ür den TSL 2572 geschrieben. Funktioniert im Prinzip nahezu gleich. Dieser Sensor war mir aufgrund des hohen Dynamikbereichs dann doch lieber. Den Sensor habe ich jedoch jetzt mir einem kleinen Breakoutboard in einer Plexiglaskuppel im Deckel des Gehäuses untergracht. Über den LWL-Lichtleiter kam bei niedrigen Helligkeiten doch zuwenig Licht durch.

Lichtsensor auf Breakoutboard für Plexiglaskuppel (Schauglas eines Ölsichtfensters)

Das Breakout-Board war nötig, da man den Sensor - für Testaufbauten etc. - sonst nahezu nicht verarbeiten kann.

Hier jetzt der Link zur :

 

Weil ich auch schon mehrfach danach gefragt wurde finden sich hier ein paar Tipps zur eigenen Herstellung von gedruckten Schaltungen. Im Web finden sich viele Anleitungen wie das grundsätzlich funktioniert und was man dazu benötigt, daher sind hier nur noch ein paar Tipps und Tricks aufgeführt.

Mittlerweile gibts ja - dank Internet - auch hier sehr preiswerte Anbieter die Platinen in guter Qualität erstellen und liefern, sodass man sich die handwerkliche Chemiearbeit sparen kann. Dennoch ist, wenn man dringend UND preiswert eine Platine benötigt, durchaus noch Bedarf an schneller Herstellung. denn meist will man keine 14 Tage auf die Lieferung warten. Die Selbstherstellung dauert im Mittel auch nur 1h.

Was braucht man überhaupt:

  • Platinenmaterial (einseitig, zweiseitig beschichtet)
  • Platinenentwurf (und damit auch ein Programm um diese zu erstellen z.B. Eagle, Target, Altium usw.)
  • Film zum Belichten
  • Belichtungsgerät
  • Entwicklerbad
  • Ätzbad
  • Stripper

Das klingt nach aufwendigem Vorgang, aber es ist eigentlich doch recht einfach. Mittlerweile schaffe ich es innerhalb einer Stunde eine Platine zu fertigen. Dies aber nur wenn man einen Platz hat wo man eben mal hingehen kann und die Arbeiten einfach machen kann (Garage, Keller usw.). Wenn man das jedoch in einer Etagenwohnung machen will, sollte man sich eine "große" Entwicklerschale (die gabs früher im Fotozubehörladen) von 50*50cm anschaffen. In der kann man dann alle Utensilien aufbewahren und macht auch keine "Chemieflecken". Diese Schale kann man dann z.B. auf dem Küchentisch oder in der Dusche aufbauen und auch schnell wieder wegräumen.

Zum Schalten von 230V AC lasten eignen sich neben Relais auch sogenannte Halbleiterschalter.  Diese haben den Vorteil dass die Kontakte nicht verbrennen können und quasi ewig halten. Zum Schalten von Rolladenmotoren habe ich die Sharp S202S12 sowie die S202S02 getestet. Diese Halbleiterrelais sind von Sharp und können opto-isoliert angesteuert werden. Laut Datenblatt können die Schalter zum Schalten von ohmschen Lasten bis 400V verwendet werden. Der Unterschied zwischen den beiden Varianten ist, dass der S202S12 bereits eine sogenannte Snubber-Circuit - also ein RC Glied eingebaut hat. Dies reduziert Induktions- und Gegenspannungen beim Schalten von induktiven Lasten.

Da ich im Web kaum Berichte oder Erfahrungen zum Thema "Schalten von Rolladenmotoren mit Halbleiterrelais" gefunden habe, musste ich es wohl selbst herausfinden.

Hier ist übrigens ein Bild des Sharp-Relais im Vergleich mit einer 3mm-Leuchtdiode.

 

Die Größe ist für die Leistung von 8A rms wirklich sehr klein. Die Ansteuerung des Relais kann mit 10-15mA erfolgen. Laut Datenblatt soll das Relais 4kV-Isolationsspannung haben. Damit ist es perfekt für den Einsatz in meinen Rolladen- und Jalousiesteuerungen zum Einsatz auf PCBs.

Zum Testaufbau habe ich einen Rolladenmotor mit

  • Relais ohne Snubber
  • Relais und externem Snubber
  • Halbleiterschalter mit Nulldurchgangsschalter
  • Halbleiterschalter mit Nulldurchgangsschalter und eingebautem Snubber

getestet. Hierzu habe ich die einzelnen Aktoren von einem Microkontroller ansteuern lassen und die Störungen und Signale auf den Versorgungsgleitungen (3.3V) gemessen. Teilweise habe ich Transienten mit bis zu 40V gemessen. Dies wohlgemerkt hauptsächlich bei induktiven Lasten. Ohmsche Lasten sind in allen Fällen problemlos gewesen.

Grundsätzliches Problem sind die Störungen die durch den Ausschaltvorgang bei induktiven Lasten auftreten. Hier wird bei normalen Relais durch die Relaiskontakte ein Störsignal in der Erregerwicklung induziert.

Wie zu erwarten war ist die reine Relaisschaltung die Problematischste. Sobald man eine externes RC-Glied über die Kontakte schaltet ist es schon erheblich besser - aber immer noch bis zu 8V über der Versorgungspannung der Relais.

Die zweitbeste Lösung ist das Schalten mit dem Halbleiterschalter und Nulldurchgangsdetektor.  Hier werden auch noch große Störsignale erzeugt - aber das fast ausschliesslich beim Abschalten.

Am besten schneidet - wie zu erwarten war - das Relais mit Nulldurchgangsschalter und eingebautem Snubber ab. Hier sind nur noch leichte Störungen auf den Versorgungsleitungen.

Übrigens die 400V/600V benötigt man bei 230V Netzspannung unbedingt (ich sage nur Wurzel 2). Damit ergibt sich eine Spitzenspannung von 325V bei ohmschen Lasten. Natürlich sind die gemessenen Signale nicht representativ, da der Aufbau, Abstand zu stromführenden Leitungebn und natürlich die Last einen Einfluss haben. Im Vergleich sollte man aber das Ergebnis bewerten können.

Nachfolgend finden sich einige Screenshots der Transienten beim Abschalten der Last.

 Fazit:

Zum Schalten von Rolladenmotoren eignen sich Halbleiterrelais durchaus. Die Sharp S202S12 Optorelais konnte ich im Wechselbetrieb mit Rolladenmotoren betrieben. Die Störungen sind erheblich geringer und man braucht auch keine externen Entstörglieder.

Sofern man induktive Lasten schalten möchte, kann die Spannung ohne weiteres auf das 2-3-fache der Nennspannung ansteigen. Durch den im Halbleiterschalter eingebauten Snubber wird die Gegeninduktionsspannung erheblich reduziert.

Langzeiterfahren werde ich hier noch nachtragen.

ACHTUNG: Beim Aufbau von Schaltungen am 230V-Netz ist äußerste Vorsicht geboten. Auch mit den optoisolierten Halbleiterrelais ist auf ausreichenden Sicherheitsabstand beim Aufbau und Test zu achten. Schaltungen müssen vor Inbetriebnahme in ein allseitig geschlossenes Gehäuse eingebaut sein.

Zum Test immer einen Sicherheitstrenntrafo verwenden.

Die DOG-M Display Serie von Electronic Assembly sind sogenannte Display-on-Glass LCD-Displays die sehr kompakt und preiswert sind. Hier folgt jetzt ein Beispiel wie man diese an der Cortex-CPU von ST STM32F103RB betreiben kann. Ich verwende das EA DOG-M162 Display mit 2 Zeilen a 16 Character oder auch das DOG-M163 mit drei Zeilen. Das nachfolgende Bild zeigt das Display auf einem Experimentierboard als 2-zeiliges 162er Display oder als 3-zeiliges 163er Display:

 

 

Die meisten CPUs werden mit 3.3V betrieben - daher wird hier auch nur darauf eingegangen wie man diese Displays unter 3.3V betreibt und programmiert. Zunächst muss man wissen, dass das Display bei unterschiedlichen Betriebsspannungen unterschiedlich initialisiert werden muss, sonst schaltet sich die integrierte Ladungspumpe nicht ein und das Display bleibt dunkel.

Hier kommt das wichtigste - die Initialisierungssequenz des DOGM 162/163 für SPI Mode bei 3.3V:

0x39 0x1C 0x55 0x6D 0x74 0x38 0x04 0x01 0x06

 

Für die Initialisierung des SPI Busses benötigt man ebenfalls umfangreiche Einstellungen und es bedarf auch zusätzlicher Funtionen wenn man mehrere SPI-Devices auf demselben Bus betreiben will.

Die Programmierung der SPI1 Clock erfolgt durch den folgenden Befehl:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC| RCC_APB2Periph_SPI1, ENABLE);

Im Beipielprogramm wird dies richtig programmiert.

Entwickelt und getestet habe ich das Programm auf dem STM32 Olimexino Board von Olimex.

Der Anschluss an den Prozessor erfolgt nach folgendem Ausschnitt aus dem Datenblatt des Displays:

 

Der Anschluss an das Olimexino STM32-Board erfolgt über die SPI1 Schnittstelle und ist wie folgt:

* SPI Connections STM32F103     DOG-M Connections
* D6    : PA8     CS                nc
* xD10    : PA4     SPI1 NSS        RS Pin 39 yellow
* xD11    : PA7     MOSI1            SI Pin 28 white
* D12    : PA6    MISO1            nc    - Display data cannot be read back
* xD13    : PA5     SCK                CLK Pin 29 blue

Unter dem folgenden Link findet sich ein Beispielprogramm sowie eine DOGM Library für den STM32 Prozessor.  Eigentlich einfach zu benutzen - man benötigt nur wenige Funktionen:

dogm_display_string(char *string) um einen Text auszugeben und

void dogm_cursor( uint8_t row, uint8_t column ) um vorher den Cursor an die richtige Stelle zu setzen. 

Kommunikation über I2C mit MCP32017

So schön die Prozessorfamilie von ST rund um die STM3-Bit Prozessorfamile auch ist, es gibt ein paar Dinge die sind wirklich schrecklich. Eins davon ist die I2C-Kommunikation. Sofern man nicht ganz(!) genau die diversen States und Flags beachtet geht eigentlich garnichts - nicht mal im Ansatz.

Hintergrund: Als Grundlage für meine geplante Alarmanlage benötige ich noch die Erweiterung von IOs mit sogenannten Port-Extendern.

An anderer Stelle habe ich geschrieben wie man die I2C-Kommunikation mit dem Beagle-Bone oder dem Raspberry Pi hinbekommt. Hier folgen jetzt die Beispiele für den STM32F103x Prozessor.

MCP23017 is ein Portextender von Microchip der meiner Meinung einige ganz gute Eigenschaften hat. Eine davon ist die Möglichkeit einen Interruptausgang anzusteuern wenn sich Port-Bits ändern. So muss man nicht ständig den Zustand pollen. Aber dazu später mehr.

Wie funktioniert die I2C Kommunikation ? Die Grundlagen findet man bei Wikipedia ausreichend, das muss man nicht näher erklären. Aber wie geht das mit einem echten I2C-Busteilnehmer ?

Anschluss des Microchip Expander MCP23017 an den STM32F103RB

MCP23017 Portexpander with STM32F103

Im obigen Schaltbild sind die I2C-Leitungen SCL und SDA direkt mit den passenden Eingängen des Portexpander verbunden. Die STM32-Chips besitzen interne Pullup-Widerstände, sodass die externen I2C-Widerstände (2*3.3k) entfallen können. Der STM32 agiert in den Beispielen als I2C-Master.

Die LEDs und die Taster sind nur zu Test- und Demozwecken vorgesehen. Ich habe den I2C2-Port verwendet, da ich den UART2 bereits verwendet habe und daher der I2C1 nicht gleichzeitig benutzt werden kann.

I2C-Kommunikation benötigt im Prinzip nur die Funktionen I2C-Write und I2C-Read, größere Datenmengen könnte man auch per DMA transferieren.

Hier ist jetzt die grundsätzliche Ansprache des MC23017 zum setzen von Registern:

1. Initialisierung des I2C2-Subsystems im STM32

InitI2C2();

2. I2C-Write Sequenz:

a) Sende Startsequenz

I2C_GenerateSTART

b) sende Adressbyte des MCP-Chips mit RW-Flag=Write

I2C_Send7bitAddres

c) sende Registeradresse des MCP23017 welche beschrieben werden soll

I2C_SendData(I2C2, reg);

d) sende Registerwert

I2C_SendData(I2C2, byte);

e) Sende I2C-Stopsequenz

I2C_GenerateSTOP

Nachfolgend sieht man wie dies im Logikdiagramm aussieht das Register (OLATA=14) wird mit dem Wert 0xFF beschrieben (alle Signale von PortA sind Ausgänge):

Man muss darauf achten, dass man auch ein Acknowledge-Signal vom Expanderchip bekommt. Wenn kein Acknowledge-Bit (im Bild das Symbol A) gesetzt wird stimmt die I2C-Adresse nicht. Dies kann durchaus sein, wenn man vergisst, dass die I2C-Adressen immer 7-Bit-Adressen sind, und diese ein Bit nach links geschoben werden um Platz für das R/W-Flag zu lassen. Die Basisadresse des MCP23017 ist 0x20, d.h. wenn alle Addressbits A0..A2 auf 0 gesetzt werden, analog zu folgendem Adressschema.

 

Folgende MCP23017-Register sind zu programmieren

Port-A als Ausgang:

IODIRA = 0x00 : Port A alles auf Ausgang

OLATA   = 0x00 : Alle Ausgänge auf 0 entsprechend 0xFF alle Ausgänge auf 1

Port B als Eingang:

IODIRB = 0xFF : Port B alles auf Eingang

GPPUB = 0xFF : Port B enable internal Pullups

Um Taster einzulesen liest man einfach das entsprechende Register des jeweiligen Ports z.B. GPPUB.

1. Initialisierung des I2C2-Subsystems im STM32

InitI2C2();

2. I2C-Write Sequenz:

a) Sende Startsequenz

I2C_GenerateSTART

b) sende Adressbyte des MCP-Chips mit RW-Flag=Write

I2C_Send7bitAddres

c) sende Registeradresse des MCP23017 welche beschrieben werden soll

I2C_SendData(I2C2, reg);

d) sende Registerwert

I2C_GenerateSTART (start repeat)

I2C_Send7bitAddres

c) sende Registeradresse des MCP23017 welche gelesen werden soll

I2C_SendData(I2C2, byte);

I2C_ReceiveData(I2C2);

e) Sende I2C-Stopsequenz

I2C_GenerateSTOP

Um Änderungen abzufragen gibts drei Möglichkeiten:

1. Register GPPUB pollen und damit zyklisch abfragen und vergleichen ob sich was geändert hat.

2. Interrupt Ausgang des MCP23017 so programmieren, dass dieses Ausgangssignal gesetzt wird wenn sich einer der Porteingänge geändert hat. Dieser Interruptausgang kann man dann mit einem Interrupteingang des STM32 verbinden und darauf reagieren.

Nachfolgend ist der Code zum anschauen bzw. zum direkten herunterladen.

 // Wolframs I2C-Lib for STM32 CPUs
#include "stm32f10x_conf.h"
#include "stm32_i2c.h"
void InitI2C2()
{
    /* Initialize I2C on I2C2 port of STM32F103RB Wolframs */
    GPIO_InitTypeDef GPIO_InitStructure;
    I2C_InitTypeDef I2C_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
    //GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;not necessary for I2C2
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C2, ENABLE);
    I2C_DeInit(I2C2);
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_OwnAddress1 = 0x12;                                   //own address only in slave mode
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Disable;
    //I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    //
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;   //Adress mode 7 bits
    I2C_InitStructure.I2C_ClockSpeed = 100000;                                  //clock rate scl 50kHz
    I2C_ITConfig(I2C2, I2C_IT_ERR, ENABLE);
    I2C_Init(I2C2, &I2C_InitStructure);
    // enable the various interrupt events
    NVIC_InitTypeDef NVIC_InitStructure;
    // Configure the I2C Interrupts for Errors on I2C Bus
    NVIC_InitStructure.NVIC_IRQChannel                   = I2C2_ER_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    I2C_ITConfig(I2C2, I2C_IT_ERR, ENABLE);             // Enable the interrupt in I2C Subsystem
 
    I2C_ITConfig(I2C2, I2C_IT_EVT, ENABLE);
    I2C_ITConfig(I2C2, I2C_IT_BUF, ENABLE);
 
    I2C_Cmd(I2C2, ENABLE);                              // finally enable the I2C Port
    I2C_StretchClockCmd(I2C2, ENABLE);
}
 
void InitI2C1()
{
    /* Initialize I2C on I2C1 port of STM32F103RB Wolframs */
    GPIO_InitTypeDef GPIO_InitStructure;
    I2C_InitTypeDef I2C_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, ENABLE);
    I2C_DeInit(I2C1);
    // define i2c settings
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_OwnAddress1 = 0x12;        //own address
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    //I2C_InitStructure.I2C_Ack = I2C_Ack_Disable;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;   //Adress mode 7 bits
    I2C_InitStructure.I2C_ClockSpeed = 30000;        //clock rate
    I2C_Init(I2C1, &I2C_InitStructure);
    I2C_Cmd(I2C1, ENABLE);
}
 
//
// Wolframs read_i2c routine
//
uint8_t read_i2c(uint8_t i2c_address,uint8_t reg){
    uint8_t rec_byte;
    // shift address one bit left (7 bit addressing plus rw-flag)
    I2C_GenerateSTART (I2C2,ENABLE);
    while(!I2C_CheckEvent  (I2C2, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C2, i2c_address << 1, I2C_Direction_Transmitter);
    while(!I2C_CheckEvent  (I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    I2C_SendData(I2C2, reg);
    while(!I2C_GetFlagStatus(I2C2,I2C_FLAG_BTF));
    I2C_GenerateSTART (I2C2,ENABLE);                    // Start repeat within active I2C communication
    while(!I2C_GetFlagStatus(I2C2, I2C_FLAG_SB));
    I2C_Send7bitAddress(I2C2, i2c_address << 1, I2C_Direction_Receiver);
    while(!I2C_CheckEvent  (I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
    //I2C_AcknowledgeConfig(I2C2, DISABLE);
    while (!I2C_CheckEvent  (I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED));
    rec_byte = I2C_ReceiveData(I2C2);
    I2C_GenerateSTOP(I2C2, ENABLE);
    return rec_byte;
}
// Wolframs write I2C
void write_i2c(uint8_t i2c_address,uint8_t reg,uint8_t byte){
    I2C_GenerateSTART (I2C2,ENABLE);
    while(!I2C_GetFlagStatus(I2C2, I2C_FLAG_SB));
    // shift address one bit left (7 bit addressing plus rw flag)
    I2C_Send7bitAddress(I2C2, i2c_address<< 1, I2C_Direction_Transmitter);
    while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    I2C_SendData(I2C2, reg);                    // address target register
    while(!I2C_GetFlagStatus(I2C2,I2C_FLAG_TXE));
    I2C_SendData(I2C2, byte);                   // write byte value into the addressed register
    while(!I2C_GetFlagStatus(I2C2,I2C_FLAG_TXE));
    I2C_GenerateSTOP(I2C2, ENABLE);             // stop I2C transission and release bus
    while(!I2C_GetFlagStatus(I2C2, I2C_FLAG_BTF));
}

Und hier noch das Beispielprogramm:

 
#include "stm32f10x_conf.h"         // STM32 includes
#include "stm32_i2c.h"              // Wolframs i2c Lib for STM32 CPUs
#include "mcp23017.h"               // Wolfram MCP23017 Constants
#include "stdio.h"
#include "core_cm3.h"
 
/*
#include "stm32f10x_gpio.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_i2c.h"
*/
 
 
/* Test program to communicate from STM32F103RB via I2C with MCP 23017 E/SP Port expander
 * Following connections to the MCP chip are necessary
 * The STM32 chip I2C interface 2 is used to avoid conflicts with CAN or UART IOs
 * Connections
 * MCP                              STM32
 * Pin 1..8 and Pin 21..28 GPIOs
 * Pin 9    +Ub
 * Pin 10   GND
 * Pin 12   SCL                     PB10 D29 Pin5 on Ext green
 * Pin 13   SDA                     PB11 D30 Pin6 on Ext orange
 * Pin 18   Reset   +Ub
 * Pin 15   A0  GND
 * Pin 16   A1  GND
 * Pin 17   A2  +UB
 * Address set to 24h (value is 2x )
 * Program PCP to output: set DDRA 00 FF
 * to set all IOA to outputs
 *
 * (c) Wolfram Koerver 5.7.2015
 *
 */
 
// Function prototypes
void RCC_Init(void);
void USART2_SendString(char* data);
 
void delay_us(uint32_t nus);
void delay_ms(uint32_t time_ms);
 
void Int2Str(char *pStr, unsigned int value, int charCount);
void InitUart2(void);
 
void Init_Led(void);
void blink_yellow(void);
void blink_green(void);
 
void init_extint1(void);
 
int main(void)
{
    uint8_t in;
    char            out_text[50];
 
    out_text[49] = '\0';            // termination
 
    SystemInit();
    RCC_Init();
    Init_Led();
    InitUart2();
    InitI2C2();
    blink_yellow();
 
    //init_extint1();                   // in case you want to use the interrupt outputs of MCP
 
    while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
    USART2_SendString("Wolframs Test Program for I2C-Extender MCP23017 \n\r");
    blink_green();
    write_i2c(0x20,IODIRA,0x00);
 
    write_i2c(0x20,GPINTENB,0xFF);  // enable pin change interrupt on port b
 
    write_i2c(0x20,IODIRB,0xFF);    // set port b as input
    write_i2c(0x20,GPPUB,0xFF);     // enable pull ups on port b
 
    in=read_i2c(0x20,MGPIOB);       // read once the input bits
 
 
    while(1)
    {
        write_i2c(0x20,OLATA,0x00);                 // Set all PortA Bits to 0
        delay_ms(200);
        write_i2c(0x20,OLATA,0xFF);                 // Set all PortA Bits to 1
        delay_ms(200);
        in=read_i2c(0x20,MGPIOB);                   // Read Input PORT B
        delay_ms(50);
        sprintf(out_text,"In portb: %3d  \r",in);
        USART2_SendString(out_text);
    }
}
 
void delay_us(uint32_t time_us)
{                                                               // wks version
  SysTick->LOAD  = 8 * time_us-1;
  SysTick->VAL   = 0;                                          /* Loadthe SysTick Counter Value */
  SysTick->CTRL  |= SysTick_CTRL_ENABLE_Msk;                   /* Enable SysTick Timer */
 
  do
  {
  } while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG)==0);
  SysTick->CTRL  &= ~SysTick_CTRL_ENABLE_Msk;                  /*Disable SysTick Timer */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
}
void delay_ms(uint32_t time_ms) //Delay in msec
{
  while (time_ms>0)
  {
    delay_us(1000);
    time_ms--;
  }
}
 
void Int2Str(char *pStr, unsigned int value, int charCount)
{
    // this implements sprintf(strVal, "%d", temp); faster
    // note that this is just for values >= 0, while sprintf covers negative values.
    // this also does not check if the pStr pointer points to a valid allocated space.
    // caller should make sure it is allocated.
    // point to the end of the array
    pStr = pStr + (charCount - 1);
 
    // convert the value to string starting from the ones, then tens, then hundreds etc...
    do
    {
        *pStr-- = (value % 10) + '0';
        value /= 10;
    } while(charCount--);
}
 
 
void Init_Led(void)
{
    // the following statements are for the LEDs of the Olimexino STM32 board
    /* Initialize GPIO Structure, configure clock on peripheral bus
     * Enable GPIO Pins for LED which are connected to PA1,5*/
    /* Configure the GPIO_LED pin  PC6 = Green LED PC7 = Yellow LED
     * Configure direction and clock speed*/
 
    GPIO_InitTypeDef  GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}
 
 
void blink_yellow()
{
    GPIOA->BRR = GPIO_Pin_1;                     //reset bit
    GPIOA->BSRR = GPIO_Pin_1;                        //set bit
    delay_us(10000);
    GPIOA->BRR = GPIO_Pin_1;                     //reset bit
}
 
void blink_green()
{
    GPIOA->BRR = GPIO_Pin_5;                     //reset bit
    GPIOA->BSRR = GPIO_Pin_5;                        //set bit
    delay_us(1000);
    GPIOA->BRR = GPIO_Pin_5;                     //reset bit
}
void RCC_Init(void)
{
    // Route and enable clocks for low and high speed bus
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOD, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
}
 
 
void InitUart2(void)
{
 
    // Initialize Structs
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    USART_ClockInitTypeDef USART_ClockInitStructure;
 
    // USART1 RX-Pin initialize PA3
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
 
    // USART1 TX-Pin initialize PA2
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
 
    // USART initialize
    USART_ClockStructInit(&USART_ClockInitStructure);
    USART_ClockInit(USART2, &USART_ClockInitStructure);
 
    USART_InitStructure.USART_BaudRate = 38400;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
 
    USART_Init(USART2, &USART_InitStructure);
    USART_Cmd(USART2, ENABLE);
}
 
 
void USART2_SendString(char* data)
{
    while (*data)
    {
        USART_SendData(USART2, (uint16_t) *data);
        while(USART_GetFlagStatus(USART2, USART_FLAG_TC)==RESET);
        data++;
    }
}
 
 
 
 
void init_extint1(void)
{
    // configure ext interrupt function on pin PB0
    // for MCP23017 interrupt outputs connected to.
 
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
 
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
 
    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    //EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
    //EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
 
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
 
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
 
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
 
void EXTI0_IRQHandler(void){
    // Interrupt on PB0
    USART2_SendString("Pin Change Interrupt \n\r");
    EXTI_ClearITPendingBit(EXTI_Line0);
 
}
 
void i2c_handleErrorInterrupt(void){
  I2C_GenerateSTOP(I2C2, ENABLE);
 
  I2C_ClearFlag(I2C2, I2C_FLAG_AF);
  I2C_ClearFlag(I2C2, I2C_FLAG_ARLO);
  I2C_ClearFlag(I2C2, I2C_FLAG_BERR);
  USART2_SendString("I2C Error Interrupt Handler \n\r");
}
 
void I2C2_EV_IRQHandler(void){
    while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
    //USART2_SendString("Event Interrupt Handler \n\r");
}
 
void I2C2_ER_IRQHandler(void){
    I2C_InitTypeDef I2C_InitStructure;
    // This handler is called when there is any error on the I2C Bus; Must be enabled by NVIC
      I2C_GenerateSTOP(I2C2, ENABLE);
    while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
    USART2_SendString("ER Interrupt Handler \n\r");
 
    I2C_ClearFlag(I2C2, I2C_FLAG_BERR);
    I2C_ClearFlag(I2C2, I2C_FLAG_SMBALERT);
    I2C_ClearFlag(I2C2, I2C_FLAG_TIMEOUT);
    I2C_ClearFlag(I2C2, I2C_FLAG_PECERR);
    I2C_ClearFlag(I2C2, I2C_FLAG_OVR);
    I2C_ClearFlag(I2C2, I2C_FLAG_AF);
    delay_ms(500);
    I2C_DeInit(I2C2);
  //NVIC_SystemReset(); // This function works very well in case there is a I2C-Bus error. BUT it does also reset the complete CPU
}