1-Wire Implementierung für den AVR – Kampis Elektroecke (2024)

Über das von Dallas Semiconductor (mittlerweile Maxim Integrated) entwickelte 1-Wire Protokoll können zahlreiche Sensoren oder Peripheriebausteine über zwei oder drei Leitungen mit einem Master verbunden werden. Die Besonderheit an dieser Schnittstelle ist, dass die Datenleitung (DQ) gleichzeitig für die Spannungsversorgung genutzt werden kann (parasitäre Stromversorgung), wodurch der Bus mit nur zwei Leitungen arbeiten kann:

  • Die Datenleitung DQ
  • Eine Masseverbindung

Wenn die Datenleitung über einen Pull-up-Widerstand mit der Spannungsversorgung verbunden wird, sind, je nach verwendeter Leitung, Buslängen von 100 – 300 m möglich.

Schauen wir uns mal an, wie ein 1-Wire Bus auf einem AVR Mikrocontroller (hier ein XMega256A3BU) implementiert werden kann und wie dann mit Hilfe dieser Implementierung ein DS18B20 1-Wire Temperatursensor ausgelesen wird.

Grundlagen des 1-Wire Busses:

Der 1-Wire Bus verwendet keine separate Taktleitung für die Datensignale. Daher muss (ähnlich wie beim USART) ein vorgegebenes Timing eingehalten werden, damit eine funktionierende Kommunikation stattfinden kann. Beim 1-Wire gibt es zu jeder Zeit nur einen Master und die Datenleitung wird mit einem schwachen Pull-up Widerstand (meistens wird eine Spannung von 3 V oder 5 V verwendet) versehen.

Die Datenleitung ist bei einem Slave Device als bidirektionaler Open-Drain I/O Port ausgelegt, wodurch sowohl Master als auch die Slaves Daten senden können. Durch die Verwendung eines Open-Drain Treibers für die Datenleitung entsteht eine Wired-AND Verbindung, wodurch der Master immer nur mit einem einzelnen Slave kommunizieren kann. Der Master muss zudem in der Lage sein eine Verzögerung von 1 µs, bzw. von 0,25 µs für den sogenannten Overdrive Modus, einem schnellen Modus für eine höhere Datenübertragung von bis zu 142 KBit/s zu erzeugen.

Beim 1-Wire besteht eine Kommunikation aus insgesamt vier grundlegende Operationen:

  • Eine logische 1 senden
  • Eine logische 0 senden
  • Ein Bit einlesen
  • Reset auslösen

Bei jeder Datenübertragung durch den Master (egal ob lesend oder schreibend) wird zuerst ein Reset-Puls ausgelöst. Jeder angeschlossene Slave gibt am Ende des Resets einen sogenannten Presence Puls aus, indem es die Datenleitung auf Low zieht. Dieser Presence Pulse signalisiert dem Master, dass ein Gerät an den Bus angeschlossen ist. Erfolgt kein Puls, so ist kein Gerät angeschlossen. Nach einem Reset wird ein allgemeines ROM-Kommando gesendet um entweder die Busteilnehmer zu identifizieren oder einen Busteilnehmer auszuwählen. Sobald der Teilnehmer ausgewählt worden ist, wird ein gerätespezifisches Kommando abgesetzt.

Jede der grundlegenden Operationen ist aus einem festen Ablauf aus High/Low-Pegeln aufgebaut und muss zudem bestimmten Zeitvorgaben entsprechen.

Daraus ergibt sich der folgende Ablauf:

OperationBeschreibungUmsetzung
Logische 1 sendenEine 1 an die 1-Wire Slaves sendenBus auf Low ziehen, Delay A, Bus freigeben, Delay B
Logische 0 sendenEine 0 an die 1-Wire Slaves sendenBus auf Low ziehen, Delay C, Bus freigeben, Delay D
Bit einlesenEin Bit von den 1-Wire Slaves einlesenBus auf Low ziehen, Delay A, Bus freigeben, Delay E, Bus einlesen, Delay F
ResetDie 1-Wire Slaves resetenDelay G, Bus auf Low ziehen, Delay H, Bus freigeben, Delay I, Bus einlesen, Delay J

Und für die Verzögerungen gelten die folgenden Werte:

ParameterVerzögerungszeit [µs]
StandardOverdrive
A61,0
B647,5
C607,5
D102,5
E91,0
F557
G02,5
H48070
I708,5
J41040

Okay, die Grundlagen des 1-Wire Busses sind nun klar. Schauen wir uns mal die Implementierung an…

Implementierung des 1-Wire Busses:

Für die Implementierung verwende ich mein XMEGA-A3BU Xplained Entwicklungsboard und als Slaves einen DS18B20 1-Wire Temperatursensor. Die Implementierung soll allgemein umgesetzt werden, sodass der fertige 1-Wire Treiber z. B. auch auf einem ATmega32 oder einer anderen Plattform, wie dem Raspberry Pi, verwendet werden kann.

Im ersten Schritt muss der I/O für die Datenleitung DQ, in diesem Beispiel der Pin 0 des Portes E, initialisiert werden. Der I/O muss als Ausgang geschaltet und gesetzt werden um einen High-Pegel auf den Bus zu legen.

GPIO_SetDirection(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ), GPIO_DIRECTION_OUT);GPIO_SetPullConfig(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ), GPIO_OUTPUTCONFIG_WIREDANDUP);GPIO_Set(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ));

Für den benötigten Pull-up Widerstand nutze ich den internen Pull-up Widerstand des Mikrocontrollers.

Hinweis:

Je nach verwendetem Mikrocontroller werden die Pull-up Widerstände anders eingeschaltet. Diese Zeile muss dem entsprechend an den verwendeten Mikrocontroller angepasst werden.

Nach der Initialisierung gebe ich den Busteilnehmern etwas Zeit um die interne Initialisierung abzuarbeiten. Anschließend führe ich einen kompletten Reset aller Busteilnehmer durch:

_delay_us(100);OneWire_Reset();

Der Aufbau der Reset-Funktion ergibt sich aus den Timings der Spezifikation und dem Ablauf eines 1-Wire Resets:

OneWire_Error_t OneWire_Reset(void){uint8_t State = 0x00;uint8_t Reg = CPU_IRQSave();_delay_us(ONEWIRE_DELAY_G);GPIO_Clear(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ));_delay_us(ONEWIRE_DELAY_H);GPIO_Set(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ));_delay_us(ONEWIRE_DELAY_I);GPIO_SetDirection(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ), GPIO_DIRECTION_IN);State = GPIO_Read(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ));_delay_us(ONEWIRE_DELAY_J);GPIO_SetDirection(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ), GPIO_DIRECTION_OUT);CPU_IRQRestore(Reg);if(State != 0x00){return ONEWIRE_RESET_ERROR;}return ONEWIRE_NO_ERROR;}

Um Timingprobleme durch Interrupts zu vermeiden wird direkt nach dem Funktionsaufruf das SREG gespeichert und die globalen Interrupts deaktiviert. Nach der Verzögerungszeit I wird DQ als Eingang geschaltet und der Busstatus eingelesen. Wenn der Bus keinen Low-Pegel führt, ist der Reset fehlgeschlagen und es wird eine entsprechende Fehlermeldung zurückgegeben.

Analog zu der Reset-Funktion werden die Funktionen zum Senden eines Bits/Byte

void OneWire_WriteBit(const uint8_t Bit){uint8_t Reg = CPU_IRQSave();GPIO_Clear(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ));if(Bit) _delay_us(ONEWIRE_DELAY_A);else _delay_us(ONEWIRE_DELAY_C);GPIO_Set(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ));if(Bit)_delay_us(ONEWIRE_DELAY_B);else _delay_us(ONEWIRE_DELAY_D);CPU_IRQRestore(Reg);}void OneWire_WriteByte(const uint8_t Data){for(uint8_t i = 0x01; i != 0x00; i <<= 0x01){OneWire_WriteBit(Data & i);}}

bzw. zum Lesen eines Bits/Bytes programmiert:

uint8_t OneWire_ReadBit(void){uint8_t State = 0x00;uint8_t Reg = CPU_IRQSave();GPIO_Clear(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ));_delay_us(ONEWIRE_DELAY_A);GPIO_Set(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ));_delay_us(ONEWIRE_DELAY_E);GPIO_SetDirection(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ), GPIO_DIRECTION_IN);State = GPIO_Read(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ));_delay_us(ONEWIRE_DELAY_F);GPIO_SetDirection(GET_PERIPHERAL(ONEWIRE_DQ), GET_INDEX(ONEWIRE_DQ), GPIO_DIRECTION_OUT);CPU_IRQRestore(Reg);return State;}uint8_t OneWire_ReadByte(void){uint8_t Data = 0x00;for(uint8_t i = 0x00; i < 0x08; i++){Data = Data | (OneWire_ReadBit() << i);}return Data;}

Damit ist der Bus fertig initialisiert und die Funktionen zum Schreiben, bzw. Lesen des Buses sind erstellt. Der Master kann nun damit beginnen die Adressen der angeschlossenen Busteilnehmer in Erfahrung zu bringen, sodass er einzelne Busteilnehmer ansprechen und mit ihnen kommunizieren kann.

Eine Übertragung mittels 1-Wire folgt strikt dem selben Ablauf und eine falsche Reihenfolge führt automatisch zu einem Übertragungsfehler:

  1. Initialisierung der Teilnehmer durch einen Reset
  2. Übermittelung des ROM-Kommandos (1-Wire spezifisch)
  3. Übermittelung des Funktionskommandos (Zielgerät spezifisch)

Über die Adressen können dann die einzelnen Busteilnehmer angesprochen werden. Jeder 1-Wire Slave besitzt ein 64 Bit großes ROM über das sich die einzelnen Slaves identifizieren und welches vom Master genutzt wird um einzelne Geräte anzusprechen. Das ROM beinhaltet

  • eine 8 Bit CRC (Bits [63:56])
  • eine 48 Bit lange Seriennummer (Bits [55:8])
  • einen 8 Bit langen ID-Code für die Bausteinfamilie (Bits [7:0])

Es werden zwei Verfahren bereitgestellt, mit denen der Master an die Informationen in den ROMs gelangen kann:

  • Search ROM Kommando (Code 0xF0): Ein Suchalgorithmus, der die einzelnen Teilnehmer des Busses identifiziert und die Teilnehmeradressen ermittelt.
  • Read ROM Kommando (Code 0x33): Erfragt die Identifikation eines einzelnen Busteilnehmers. Deutlich einfacher als Search ROM, funktioniert aber nur wenn maximal ein Teilnehmer am Bus angeschlossen ist.

Der dritte Punkt in der Übertragung unterscheidet sich je nach Familie des angesprochenem Zielgerätes und wird für die Ermittelung der ROM-Codes nicht benötigt.

Wenn also der ROM-Code eines einzelnen Busteilnehmers erfragt werden soll, so muss der Master einen Reset durchführen, den Read ROM-Befehl senden und anschließend die Informationen aus dem ROM einlesen:

OneWire_Error_t OneWire_ReadROM(const OneWire_ROM_t* ROM){if(ROM == NULL){return ONEWIRE_PARAMETER_ERROR;}uint8_t* pROM = (uint8_t*)ROM;OneWire_Error_t ErrorCode = OneWire_Reset();if(ErrorCode == ONEWIRE_NO_DEVICE){return ErrorCode;}OneWire_WriteByte(ONEWIRE_READ_ROM);for(uint8_t i = 0x00; i < sizeof(OneWire_ROM_t); i++){*(pROM++) = OneWire_ReadByte();}return ONEWIRE_NO_ERROR;}

Wobei OneWire_ROM_t eine entsprechende Struktur ist um die Informationen aus dem ROM zu speichern:

typedef struct{uint8_t FamilyCode;uint8_t SerialNumber[6];uint8_t Checksum;} __attribute__((packed)) OneWire_ROM_t;

Der Master ist nun bereits in der Lage ein einzelnes 1-Wire Gerät am Bus zu identifizieren. Hier mit einem DS18B20 Temperatursensor als Beispiel:

1-Wire Implementierung für den AVR – Kampis Elektroecke (2)

Falls sich mehrere (auch unterschiedliche) Teilnehmer am Bus befinden muss der Search ROM-Befehl genutzt werden um die einzelnen Teilnehmer zu identifizieren. Die Funktionsweise des Suchalgorithmus erklärt Maxim Integrated in der Application Note 187. Für die Implementierung des Suchalgorithmus in den 1-Wire Treiber nutze ich die angepasste Referenzimplementierung von Maxim Integrated:

static OneWire_Error_t OneWire_SearchROM(const OneWire_ROM_t* ROM){OneWire_Error_t ErrorCode = ONEWIRE_NO_ERROR;uint8_t* pROM = (uint8_t*)ROM;uint8_t id_bit;uint8_t cmp_id_bit;uint8_t ROM_Byte = 0x00;uint8_t CRC8 = 0x00;uint8_t search_direction = 0x00;uint8_t last_zero = 0x00;uint8_t ROM_Mask = 0x01;uint8_t id_bit_number = 0x01;if(ROM == NULL){return ONEWIRE_PARAMETER_ERROR;}if(!__LastDevice){ErrorCode = OneWire_Reset();if(ErrorCode != ONEWIRE_NO_ERROR){__LastDiscrepancy = 0x00;__LastDevice = FALSE;__LastDiscrepancy = 0x00;return ErrorCode;}if(isAlarm == TRUE){OneWire_WriteByte(ONEWIRE_ALARM_SEARCH);}else{OneWire_WriteByte(ONEWIRE_SEARCH_ROM);}do{id_bit = OneWire_ReadBit();cmp_id_bit = OneWire_ReadBit();if((id_bit == 0x01) && (cmp_id_bit == 0x01)){break;}else{if(id_bit == cmp_id_bit){if(id_bit_number == __LastDiscrepancy){search_direction = 0x01;}else{if(id_bit_number > __LastDiscrepancy){search_direction = 0x00;}else{search_direction = (((*(pROM + ROM_Byte)) & ROM_Mask) > 0);}}if(search_direction == 0x00){last_zero = id_bit_number;if(last_zero < 0x09){__LastFamilyDiscrepancy = last_zero;}}}else{search_direction = id_bit;}if(search_direction == 0x01){*(pROM + ROM_Byte) |= ROM_Mask;}else{*(pROM + ROM_Byte) &= ~ROM_Mask;}OneWire_WriteBit(search_direction);id_bit_number++;ROM_Mask <<= 0x01;if(ROM_Mask == 0x00){CRC8 = __OneWire_CRCTable[CRC8 ^ *(pROM + ROM_Byte)];ROM_Byte++;ROM_Mask = 0x01;}}}while(ROM_Byte < 0x08);if(!((id_bit_number < 65) || (CRC8 != 0x00))){__LastDiscrepancy = last_zero;if(__LastDiscrepancy == 0x00){__LastDevice = TRUE;__SearchActive = FALSE;}}}if((ErrorCode != ONEWIRE_NO_ERROR) || !(*pROM)){__LastDiscrepancy = 0x00;__LastDevice = FALSE;__LastFamilyDiscrepancy = 0x00;if(!(*pROM)){ErrorCode = ONEWIRE_CRC_ERROR;}}return ErrorCode;}

Info:

Einige 1-Wire Peripheriegeräte (wie z. B. der DS18B20) verfügen über die Möglichkeit durch bestimmte Ereignisse in einen Alarmzustand zu wechseln. Das 1-Wire Protokoll bietet die Möglichkeit alle Geräte, bei denen das Alarmflag gesetzt ist, zu suchen. Die Suche wird auf identische Art durchgeführt wie die ROM-Suche, nur das die Suche nach Alarmgeräten mit dem Befehlscode 0xEC anstatt mit 0xF0 gestartet wird.

Ein vollständiger Suchlauf besteht aus vier verschiedenen Funktionen:

FunktionsnameBeschreibung
OneWire_StartSearchInitialisiert den Algorithmus, startet die Suche und sucht den ersten Teilnehmer
OneWire_IsLastÜberprüft ob der letzte Teilnehmer gefunden wurde
OneWire_SearchNextSucht den nächsten Busteilnehmer
OneWire_StopSearchStoppt die Suche
static uint8_t __LastFamilyDiscrepancy;static uint8_t __LastDiscrepancy;static Bool_t __LastDevice;static Bool_t __SearchActive;static Bool_t __isAlarm;OneWire_Error_t OneWire_StartSearch(const OneWire_ROM_t* ROM, const Bool_t isAlarm){__LastFamilyDiscrepancy = 0x00;__LastDiscrepancy = 0;__LastDevice = FALSE;__SearchActive = TRUE;__isAlarm = isAlarm;if(ROM == NULL){return ONEWIRE_PARAMETER_ERROR;}return OneWire_SearchROM(ROM, __isAlarm);}Bool_t OneWire_IsLast(void){return __LastDevice;}OneWire_Error_t OneWire_SearchNext(const OneWire_ROM_t* ROM){if(__SearchActive == TRUE){return OneWire_SearchROM(ROM, __isAlarm);}return ONEWIRE_INACTIVE_SEARCH;}OneWire_Error_t OneWire_StopSearch(void){__SearchActive = FALSE;return OneWire_Reset();}

Nach jedem empfangenen Byte wird eine CRC berechnet um Übertragungsfehler zu erkennen. Da die Berechnung der CRC 1-Wire spezifisch ist, nutzt jedes 1-Wire Gerät das selbe CRC-Polynom. Daher kann die Berechnung der CRC, genau wie in der Referenzimplementierung, über eine feste Lookup-Table erfolgen.

Ein kompletter Suchlauf kann z. B. folgendermaßen implementiert werden (auch hier wieder für einen DS18B20 Temperatursensor):

static OneWire_Error_t DS18B20_SearchDevices(uint8_t* Found, uint8_t Search, OneWire_ROM_t* ROM, const Bool_t isAlarm){uint8_t DevicesFound = 0x00;if(Found == NULL){return ONEWIRE_PARAMETER_ERROR;}OneWire_Error_t ErrorCode = OneWire_StartSearch(ROM, isAlarm);if(ErrorCode == ONEWIRE_NO_ERROR){if(ROM->FamilyCode == DS18B20_ID){DevicesFound++;ROM++;}while((!OneWire_IsLast()) && (DevicesFound < Search)){ErrorCode = OneWire_SearchNext(ROM);if(ErrorCode != ONEWIRE_NO_ERROR){OneWire_StopSearch();break;}if(ROM->FamilyCode == DS18B20_ID){DevicesFound++;ROM++;}}}*Found = DevicesFound;return ErrorCode;}OneWire_Error_t DS18B20_GetDevices(uint8_t* Found, uint8_t Search, OneWire_ROM_t* ROM){return DS18B20_SearchDevices(Found, Search, ROM, FALSE);}

Bei der Suche werden nur Teilnehmer gespeichert, die den richtigen ID-Code (0x28) besitzen. Die Suche liefert die Anzahl der gefundenen DS18B20 Temperatursensoren zurück und speichert die ROM-Codes in einem Array.

Hinweis:

Da es sich bei einem Mikrocontroller (in der Regel) um ein System mit einer statischen Speicherzuweisung handelt, muss die Größe des ROM-Arrays im Vorfeld bekannt sein, sprich man muss wissen wie viele Teilnehmer am Bus angeschlossen sind oder wie viele Teilnehmer maximal gesucht werden sollen.

Der Code um alle angeschlossenen DS18B20 Sensoren zu ermitteln sieht somit folgendermaßen aus:

OneWire_Error_t DS18B20_Init(void){return OneWire_Init();}#define DS18B20_BUS_DEVICES0x03uint8_t Devices;OneWire_ROM_t DS18B20_ROM[DS18B20_BUS_DEVICES];if(DS18B20_Init() == ONEWIRE_NO_ERROR){if(DS18B20_GetDevices(&Devices, DS18B20_BUS_DEVICES, DS18B20_ROM) == ONEWIRE_NO_ERROR){...}}

Am Ende des ROM-Suchlaufs kennt der Master die ROM-Codes aller Busteilnehmer und kann damit beginnen mit den einzelnen Teilnehmern zu kommunizieren.

In der Regel kommuniziert der Master immer nur mit einem Teilnehmer gleichzeitig. Es gibt einige Ausnahmen, mit denen der Master einen Befehl an alle Teilnehmer senden kann (z. B. um bei einem DS18B20 eine Temperaturmessung zu starten) aber keine Kommunikation vom Slave in Richtung Master kann nur einzeln erfolgen. Zu Beginn der Kommunikation muss der Master das Zielgerät auswählen. Hierfür werden die ermittelten ROM-Codes genutzt.

Dazu sendet der Master einen Match ROM-Befehl (0x55), gefolgt von dem ROM-Code und anschließend den gerätespezifischen Befehl. Die Auswahl des Busteilnehmers kann z. B. folgendermaßen umgesetzt werden:

OneWire_Error_t OneWire_SelectDevice(const OneWire_ROM_t* ROM){OneWire_Error_t ErrorCode = OneWire_Reset();if(ErrorCode == ONEWIRE_NO_DEVICE){return ErrorCode;}if(ROM == NULL){OneWire_WriteByte(ONEWIRE_SKIP_ROM);}else{uint8_t* ROM_Temp = (uint8_t*)ROM;OneWire_WriteByte(ONEWIRE_MATCH_ROM);for(uint8_t i = 0x00; i < 0x08; i++){OneWire_WriteByte(*(ROM_Temp++));}}return ONEWIRE_NO_ERROR;}

Die Funktion erwartet als Übergabewert einen Zeiger auf den entsprechenden ROM-Code. Wenn die Adresse des Zeigers NULL ist, sprich keine ROM-Struktur übergeben wurde, wird ein Skip ROM-Befehl gesendet um alle Busteilnehmer zu adressieren. Falls eine Adresse übergeben wurde, so werden der Match ROM-Befehl und die acht Bytes des ROM-Codes übertragen.

Damit ist das Zielgerät ausgewählt und kann im Anschluss daran vom Master mit einem Funktionscode bedient werden. Dieser Funktionscode (und ggf. ein paar zusätzliche Datenbytes) werden vom Master nacheinander übertragen. Falls der Master das Zielgerät auslesen will, so sendet er erst den entsprechenden Funktionscode und ließt dann die benötigte Anzahl an Datenbytes ein. Nachfolgend ein kurzes Beispiel um das Scratchpad, also die Zwischenablage, eines DS18B20 zu beschreiben:

OneWire_Error_t DS18B20_WriteScratchpad(const OneWire_ROM_t* ROM, const uint8_t TH, const uint8_t TL, const uint8_t Config){OneWire_Error_t ErrorCode = OneWire_SelectDevice(ROM);if(ErrorCode != ONEWIRE_NO_ERROR){return ErrorCode;}OneWire_WriteByte(DS18B20_WRITE_SCRATCHPAD);OneWire_WriteByte(TH);OneWire_WriteByte(TL);OneWire_WriteByte(Config);return ONEWIRE_NO_ERROR;}

Damit ist der 1-Wire Treiber abgeschlossen und stellt grundlegende Funktionen bereit um mit 1-Wire Geräten zu kommunizieren. Den fertigen 1-Wire Treiber inkl. einer Beispielimplementierung für DS18B20 Temperatursensoren für einen XMega256A3BU findet ihr in meinem GitHub-Repository.

1-Wire Implementierung für den AVR – Kampis Elektroecke (2024)
Top Articles
Urban Dictionary: Book
Toomics - Die unendliche Welt der Comics online
Home Store On Summer
Nehemiah 6 Kjv
Can ETH reach 10k in 2024?
Goodall Brazier hiring Vice President in Arizona, United States | LinkedIn
Review: Chained Echoes (Switch) - One Of The Very Best RPGs Of The Year
Are Pharmacy Open On Sunday
Creglist Tulsa
Craig Woolard Net Worth
Mta Bus Time Q85
Jet Ski Rental Conneaut Lake Pa
Anchor Martha MacCallum Talks Her 20-Year Journey With FOX News and How She Stays Grounded (EXCLUSIVE)
Chubbs Canton Il
Gran Turismo Showtimes Near Regal Crocker Park
Die 12 besten Chrome Video Downloader im Überblick
20 Cozy and Creative Fall Front Porch Ideas to Welcome the Season in Style
Masdar | Masdar’s Youth 4 Sustainability Announces COP28 Program to Empower Next Generation of Climate Leaders
Katmoie
Emily Katherine Correro
Elektrische Arbeit W (Kilowattstunden kWh Strompreis Berechnen Berechnung)
Craigslist Westchester Cars For Sale By Owner
Fd Photo Studio New York
Tiffin Ohio Craigslist
895 Area Code Time Zone
Meine Erfahrung mit Textbroker als Autor (inkl. Beispiel zu Verdienst)
Wsbtv Traffic Map
Melanin - Altmeyers Enzyklopädie - Fachbereich Dermatologie
Harris Teeter Weekly Ad Williamsburg Va
Helloid Worthington Login
Peloton Guide Stuck Installing Update
14314 County Road 15 Holiday City Oh
Should Jenn Tran Join 'Bachelor in Paradise'? Alum Mari Pepin Weighs In
Bellagio Underground Tour Lobby
Family Violence Prevention Program - YWCA Wheeling
Culvers Flavor Of The Day Freeport Il
Enlightenment Egg Calculator
Bbc Weather In Mallorca
CNA Classes & Certification | How to Become a CNA | Red Cross
Craigslist Philly Free Stuff
Craigslist Boats For Sale By Owner Sacramento
Nusl Symplicity Login
Best Homemade Tartar Sauce
Gizmo Ripple Tank Answer Key
Culver's Flavor Of The Day Wilson Nc
Was genau ist eine pillow princess?
Sutter Health Candidate Login
Greythr Hexaware Bps
Kaiju Universe: Best Monster Tier List (January 2024) - Item Level Gaming
Diora Thothub
NBA 2K: 10 Unpopular Opinions About The Games, According To Reddit
Craigslist Org Sd Ca
Latest Posts
Article information

Author: Catherine Tremblay

Last Updated:

Views: 6024

Rating: 4.7 / 5 (47 voted)

Reviews: 86% of readers found this page helpful

Author information

Name: Catherine Tremblay

Birthday: 1999-09-23

Address: Suite 461 73643 Sherril Loaf, Dickinsonland, AZ 47941-2379

Phone: +2678139151039

Job: International Administration Supervisor

Hobby: Dowsing, Snowboarding, Rowing, Beekeeping, Calligraphy, Shooting, Air sports

Introduction: My name is Catherine Tremblay, I am a precious, perfect, tasty, enthusiastic, inexpensive, vast, kind person who loves writing and wants to share my knowledge and understanding with you.