آموزش STM32 با توابع LL قسمت 35: ارتباط OneWire

0
133
آموزش STM32 با توابع LL قسمت 35: ارتباط OneWire
آموزش STM32 با توابع LL قسمت 35: ارتباط OneWire

در قسمت قبلی درباره‌ی رابط گرافیکی برای wave player صحبت کردیم، اما در این بخش می‌خواهیم درباره‌ی ارتباط OneWire که بخش مهمی در دنیای الکترونیک هست صحبتی داشته باشیم.

در طول این آموزش با پروتکل‌های ارتباطی مختلفی کارکردیم که هرکدام مزیت‌ها و کاربردهای خودشان را داشتند. در این بخش می‌خواهیم ارتباط OneWire را بررسی کنیم. سپس به‌وسیله این پروتکل ارتباطی یک دماسنج را راه‌اندازی کنیم.

OneWire
ارتباط میکروکنتلر با سنسور DS18B20 از طریق پروتکل OneWire.

ارتباط OneWire

همان‌طور که در تصویر بالا مشخص است، این ارتباط تنها از یک سیم برای انتقال اطلاعات استفاده می‌کند. سیم دوم که وجود آن ضرورت دارد GND یا زمین است. وجود سیم سوم برای VDD (3.3 یا 5 ولت)، الزامی نیست و این ولتاژ می‌تواند از طریق همان پایه انتقال سیگنال، در زمان‌های مناسب، اعمال شود. البته در پروژه‌ای که در این آموزش بررسی می‌کنیم، از حالت 3 سیمه این پروتکل بهره می‌گیریم. مزیت‌های این ارتباط تعداد سیم مورد نیاز آن و همچنین امکان استفاده آن در فواصل نسبتاً طولانی (تا حدود 300 متر) است.

در ارتباط OneWire به‌صورت سه سیمه، از یک مقاومت برای Pull-up بین دو پایه DATA و VDD استفاده می‌شود. این ارتباط به‌صورت Half Duplex است. به این معنی که هم امکان ارسال و هم دریافت اطلاعات وجود دارد اما در هر زمان فقط یکی از این دو عمل انجام می‌شود. به عبارتی در هر زمان تنها یک دستگاه در این پروتکل می‌تواند داده ارسال کند.

نحوه انتقال اطلاعات نیز به این صورت است که وقتی فرستنده می‌خواهد مثلاً منطق صفر ارسال کند باید برای مدت‌زمان مشخصی سطح ولتاژ DATA را روی صفر نگه دارد و سپس خط داده را رها کند، در این زمان مقدار ولتاژ خط داده به‌وسیله مقاومت Pull-up به VDD بازگردانده می‌شود. به همین ترتیب برای ارسال منطق یک نیز سطح ولتاژ DATA برای مدت‌زمان مشخصی روی صفر نگه‌داشته می‌شود (معمولاً زمانی کوتاه‌تر، به نسبت منطق صفر) و سپس رها می‌شود. برای عمل خواندن (توسط Master) یک ولتاژ صفر به خط DATA اعمال و سپس رها می‌شود، بعدازآن کنترل خط داده توسط دستگاه جانبی یا Slave انجام می‌گیرد، در این حین Master از سطح ولتاژ نمونه‌گیری می‌کند. یک دیاگرام نمونه از انتقال اطلاعات با این پروتکل در شکل زیر نشان داده‌شده است:

ارتباط OneWire
.

دماسنج 18B20

در این پروژه قصد داریم از این پروتکل برای ارتباط با 18B20 (یک سنسور دماسنج)  استفاده کنیم.

دماسنج 18B20
دماسنج 18B20

این قطعه، با اسم پکیج‌ها متفاوت و توسط تولیدکننده‌های مختلفی عرضه می‌شود. در این آموزش از 7Q-tek 18B20 استفاده‌شده است. اما به‌صورت کلی، دماسنج 18B20 یک سنسور دیجیتال اندازه‌گیری دما است که از ارتباط one-wire استفاده می‌کند. این سنسور قابلیت اندازه‌گیری دما در بازه -55 تا +125 درجه سانتی‌گراد را دارد. همچنین دقت این دماسنج‌ها، بسته به مدل و تولیدکننده آن‌ها معمولاً حدود 0.5± درجه سانتی‌گراد است. مشخصات کامل این دماسنج به‌صورت زیر است:

 

  • Usable temperature range: -55 to 125°C (-67°F to +257°F)
  • 9 to 12 bit selectable resolution
  • Uses 1-Wire interface- requires only one digital pin for communication
  • Unique 64 bit ID burned into chip
  • Multiple sensors can share one pin
  • ±0.5°C Accuracy from -10°C to +85°C
  • Temperature-limit alarm system
  • Query time is less than 750ms
  • Usable with 3.0V to 5.5V power/data

 

اکنون‌که با جزییات کار آشنا شدیم به سراغ ایجاد پروژه می‌رویم.

 

ایجاد پروژه

برای این پروژه تنها به یک پین GPIO نیاز داریم. البته برای نمایش اطلاعات مثل پروژه‌های قبلی واحد USART1 را نیز فعال می‌کنیم. مانند گذشته کلاک و دیباگ رو تنظیم می‌کنیم و به سراغ تنظیم پین GPIO می‌رویم؛

ایجاد پروژه

همان‌طور که در تصویر هم مشخص است، پایه خروجی (در اینجا PA8) باید در حالت Open Drain و NO pull-up and no pull-d تنظیم شود.

اکنون پروژه را ایجاد می‌کنیم و به سراغ نوشتن کد می‌رویم.

 

نوشتن کد پروژه

برای شروع کار، باید توابع موردنیاز برای راه‌اندازی پروتکل OneWire را بنویسیم و سپس با استفاده از این توابع به سنسور دما فرمان بدهیم و یا اطلاعات آن را بخوانیم. پس فایل‌های onewire.c و onewire.h را برای نوشتن توابع ایجاد می‌کنیم.

ابتدا به سراغ فایل onewire.h می‌رویم. کد موردنیاز برای این فایل به‌صورت زیر نوشته می‌شود:

/********************/
/********///includes:

#include <stdint.h>
#include "stm32f1xx_ll_gpio.h"
#include "stm32f1xx_ll_tim.h"
/********************/
/********///definitions:

typedef struct {
GPIO_TypeDef* GPIOx; /*!< GPIOx port to be used for I/O functions */
uint32_t GPIO_Pin; /*!< GPIO Pin to be used for I/O functions */
uint8_t LastDiscrepancy; /*!< Search private */
uint8_t LastFamilyDiscrepancy; /*!< Search private */
uint8_t LastDeviceFlag; /*!< Search private */
uint8_t ROM_NO[8]; /*!< 8-bytes address of last search device */
} OneWire_t;

#define ONEWIRE_TIM TIM1

/* OneWire commands */
#define ONEWIRE_CMD_RSCRATCHPAD 0xBE
#define ONEWIRE_CMD_WSCRATCHPAD 0x4E
#define ONEWIRE_CMD_CPYSCRATCHPAD 0x48
#define ONEWIRE_CMD_RECEEPROM 0xB8
#define ONEWIRE_CMD_RPWRSUPPLY 0xB4
#define ONEWIRE_CMD_SEARCHROM 0xF0
#define ONEWIRE_CMD_READROM 0x33
#define ONEWIRE_CMD_MATCHROM 0x55
#define ONEWIRE_CMD_SKIPROM 0xCC

/********************/
/********///Functions: 

void OneWire_Delay(uint16_t time_us);

void OneWire_Low(OneWire_t *gp);

void OneWire_High(OneWire_t *gp);

void OneWire_Input(OneWire_t *gp);

void OneWire_Output(OneWire_t *gp);

void OneWire_Init(OneWire_t* OneWireStruct, GPIO_TypeDef* GPIOx, uint32_t GPIO_Pin);

uint8_t OneWire_Reset(OneWire_t* OneWireStruct);

void OneWire_WriteBit(OneWire_t* OneWireStruct, uint8_t bit);

uint8_t OneWire_ReadBit(OneWire_t* OneWireStruct);

void OneWire_WriteByte(OneWire_t* OneWireStruct, uint8_t byte);

uint8_t OneWire_ReadByte(OneWire_t* OneWireStruct);

 

همان‌طور که در این کد دیده می‌شود، ابتدا در سه خط اول، کتابخانه‌های موردنیاز، اضافه‌شده‌اند. سپس یک structure مربوط به ارتباط OneWire تعریف‌شده است که پین و پورت مورداستفاده و دیگر مشخصات مربوط به این ارتباط، در آن ذخیره می‌شود. پس‌ازآن نیز کد هگز مربوط به دستورات مختلف OneWire تعریف‌شده‌اند. در آخر، توابع موردنیاز اعلان می‌شوند.

اکنون وارد فایل onewire.c می‌شویم. در این فایل ابتدا onewire.h را اضافه می‌کنیم؛

 #include "onewire.h"

اولین تابع، یعنی OneWire_Delay، برای ایجاد زمان‌بندی‌های موردنیاز در ارتباط OneWire به‌کار می‌رود. این تابع به‌صورت زیر تعریف می‌شود:

// Delay(us) function used for creating the required one_wire timing singals
void OneWire_Delay(uint16_t time_us)
{
LL_TIM_SetCounter(ONEWIRE_TIM, 0);
while(LL_TIM_GetCounter(ONEWIRE_TIM) <= time_us);
}

همان‌طور که از کد مشخص است، این تابع تنها یک تأخیر به‌اندازه مقدار دریافت شده در ورودی و با واحد میکروثانیه ایجاد می‌کند. پس‌ازآن، تعریف توابع OneWire_Low و OneWire_High که به ترتیب برای 0 و 1 کردن خط OneWire استفاده می‌شوند، به‌صورت زیر است:

// function to set one_wire signal to low logic
void OneWire_Low(OneWire_t *gp)
{
LL_GPIO_ResetOutputPin(gp->GPIOx, gp->GPIO_Pin);
}

// function to set one_wire signal to high logic
void OneWire_High(OneWire_t *gp)
{
LL_GPIO_SetOutputPin(gp->GPIOx, gp->GPIO_Pin);
}

توابع OneWire_Input و OneWire_Output برای ورودی و خروجی کردن پین OneWire کاربرد دارند و به‌صورت زیر تعریف می‌شوند:

// function to set the one_wire pin as input
void OneWire_Input(OneWire_t *gp)
{
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

GPIO_InitStruct.Pin = gp->GPIO_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING;

LL_GPIO_Init(gp->GPIOx, &GPIO_InitStruct);
}

// function to set the one_wire pin as output
void OneWire_Output(OneWire_t *gp)
{
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

GPIO_InitStruct.Pin = gp->GPIO_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;

LL_GPIO_Init(gp->GPIOx, &GPIO_InitStruct);
}

تابع بعدی، OneWire_Init است که برای راه‌اندازی ارتباط OneWire استفاده می‌شود. این تابع را به‌صورت زیر تعریف می‌کنیم:

// function to initialize the one-wire communication
void OneWire_Init(OneWire_t* OneWireStruct, GPIO_TypeDef* GPIOx, uint32_t GPIO_Pin) 
{ 
LL_TIM_EnableCounter(ONEWIRE_TIM);

OneWireStruct->GPIOx = GPIOx;
OneWireStruct->GPIO_Pin = GPIO_Pin;
OneWire_Output(OneWireStruct);
OneWire_High(OneWireStruct);
OneWire_Delay(1000);
OneWire_Low(OneWireStruct);
OneWire_Delay(1000);
OneWire_High(OneWireStruct);
OneWire_Delay(2000);
}

تابع OneWire_Reset برای ریست کردن ارتباط OneWIre کاربرد دارد و تعریف آن به شکل زیر است:

uint8_t OneWire_Reset(OneWire_t* OneWireStruct)
{
uint8_t i;

/* Line low, and wait 500us */
OneWire_Low(OneWireStruct);
OneWire_Output(OneWireStruct);
OneWire_Delay(500);
/* Release line and wait for 70us */

OneWire_High(OneWireStruct);
OneWire_Delay(70);
/* Check bit value */
i = LL_GPIO_IsInputPinSet(OneWireStruct->GPIOx, OneWireStruct->GPIO_Pin);

/* Delay for 430 us */
OneWire_Delay(430);
/* Return value of presence pulse, 0 = OK, 1 = ERROR */
return i;
}

 

دو تابع OneWire_WriteBit و OneWire_ReadBit برای نوشتن و خواندن یک بیت در ارتباط OneWire به کار می‌روند. این دو تابع را به این صورت می‌نویسیم:

// function to write 1-bit to the one-wire line
void OneWire_WriteBit(OneWire_t* OneWireStruct, uint8_t bit)
{
if (bit) 
{
/* Set line low */
OneWire_Low(OneWireStruct);
OneWire_Output(OneWireStruct);
OneWire_Delay(10);

/* Bit high */
OneWire_High(OneWireStruct); 
/* Wait for 60 us and release the line */
OneWire_Delay(55);
} 
else 
{
/* Set line low */

OneWire_Low(OneWireStruct);
OneWire_Output(OneWireStruct); 
OneWire_Delay(65);

/* Bit high */

OneWire_High(OneWireStruct); 
/* Wait for 5 us and release the line */
OneWire_Delay(5);

}

}

// function to read 1-bit of the one-wire line
uint8_t OneWire_ReadBit(OneWire_t* OneWireStruct) 
{
uint8_t bit = 0;

/* Line low */
OneWire_Output(OneWireStruct);
OneWire_Low(OneWireStruct);

OneWire_Delay(2);

/* Release line */
OneWire_High(OneWireStruct);
OneWire_Delay(10);

/* Read line value */
if (LL_GPIO_IsInputPinSet(OneWireStruct->GPIOx, OneWireStruct->GPIO_Pin)) 
{
/* Bit is HIGH */
bit = 1;
}

/* Wait 50us to complete 60us period */
OneWire_Delay(50);

/* Return bit value */
return bit;
}

در آخر، توابع OneWire_WriteByte و OneWire_ReadByte برای نوشتن و خواندن یک بایت در ارتباط OneWire، به ترتیب با استفاده از OneWire_WriteBit و OneWire_ReadBit تعریف می‌شوند؛

// function to write 1-byte to the one-wire line
void OneWire_WriteByte(OneWire_t* OneWireStruct, uint8_t byte) {
uint8_t i = 8;
/* Write 8 bits */
while (i--) {
/* LSB bit is first */
OneWire_WriteBit(OneWireStruct, byte & 0x01);
byte >>= 1;
}
}

// function to read 1-byte to the one-wire line
uint8_t OneWire_ReadByte(OneWire_t* OneWireStruct)
{
uint8_t i = 8,byte;

/* Read 8 bits */
while (i--) {
byte >>= 1;
byte |= (OneWire_ReadBit(OneWireStruct) << 7);
}

return byte;
}

در این مرحله، کتابخانه موردنیاز برای ارتباط OneWire را ایجاد کرده‌ایم. اکنون باید با استفاده از این کتابخانه، یک کتابخانه برای سنسور دماسنج 18B20 بنویسیم. پس سه فایل دیگر به نام‌های DS18B20Config.h، ‌DS18B20.h و DS18B20.c، ایجاد می‌کنیم، ابتدا به سراغ فایل DS18B20Config.h می‌رویم، این فایل برای تنظیم برخی پارامترهای مربوط به سنسور ایجادشده است. کد زیر را در این فایل می‌نویسیم:

#define _DS18B20_GPIO GPIOA // port used for one wire connection
#define _DS18B20_PIN LL_GPIO_PIN_8 // pin used for one wire connection(signal pin of the 18B20 sensor)

#define DS18B20_Scaling_factor 0.0078125 // Scaling factor for the sensor's converted temperature

اکنون وارد فایل DS18B20.h می‌شویم. در این فایل ابتدا فایل‌های موردنیاز را اضافه می‌کنیم:

 #include "onewire.h"
#include "DS18B20Config.h"

سپس کدهای هگز OneWire مربوط به سنسور و دیگر ثابت‌های موردنیاز را تعریف می‌نماییم:

/* in case of DS18B20 this is 0x28 and this is first byte of ROM address */
#define DS18B20_FAMILY_CODE 0x28
#define DS18B20_CMD_ALARMSEARCH 0xEC

/* Bits locations for resolution */
#define DS18B20_RESOLUTION_R1 6
#define DS18B20_RESOLUTION_R0 5

/* CRC enabled */
#ifdef DS18B20_USE_CRC 
#define DS18B20_DATA_LEN 9
#else
#define DS18B20_DATA_LEN 2
#endif

/*********************** DS18B20 Function Command Set ***********************/
#define DS18B20_CMD_CONVERTTEMP 0x44 /* Convert temperature */

درنهایت به اعلان توابع می‌پردازیم:

void DS18B20_Init(void);

float DS18B20_Convert(void);

اکنون باید توابع اعلان‌شده را در فایل DS18B20.c تعریف کنیم. وارد این فایل می‌شویم و ابتدا DS18B20.h را اضافه و متغیرهای موردنیاز را تعریف می‌کنیم:

/********///includes: 
#include "DS18B20.h"

/********///variables:
OneWire_t OneWire;
uint8_t OneWireDevices;

تعریف تابع اول یعنی DS18B20_Init که برای راه‌اندازی سنسور استفاده می‌شود، به‌صورت است:

// function to initialize the DS18B20 module
void DS18B20_Init(void)
{
OneWire_Init(&OneWire, _DS18B20_GPIO, _DS18B20_PIN); 
}

 

در این تابع، تنها تابع راه‌اندازی ارتباط OneWire فراخوانی شده است. تابع دوم، برای ارسال دستور convert یا همین تبدیل دما به سنسور و بازگرداندن مقدار دما خوانده‌شده است. این تابع را به شکل زیر تعریف می‌کنیم:

// function to start the temperature conversion via DS18B20 module
float DS18B20_Convert(void)
{
DS18B20_Init();
uint8_t data[2];

OneWire_WriteByte(&OneWire, ONEWIRE_CMD_SKIPROM); //skip ROM command
OneWire_WriteByte(&OneWire, DS18B20_CMD_CONVERTTEMP); //start conversion command

OneWire_Delay(800); //time interval necessary for conversion
OneWire_Reset(&OneWire);

OneWire_WriteByte(&OneWire, ONEWIRE_CMD_SKIPROM);
OneWire_WriteByte(&OneWire, ONEWIRE_CMD_RSCRATCHPAD); //Read Scratch pad of 18B20 to get temperature

for (int i = 0; i < 2; i++) 
{
/* Read 2bytes of the converted temperature byte by byte */
data[i] = OneWire_ReadByte(&OneWire);
}

/* scale the converted value regarding to the scaling factor */
uint16_t fpTemperature = (((uint16_t) data[1]) << 11)
| (((uint16_t) data[0]) << 3);

return ((float) fpTemperature) * 0.0078125;
}

 

اکنون کتابخانه سنسور DS18B20.c تکمیل‌شده است و می‌توانیم از آن استفاده کنیم، به این منظور به main.c می‌رویم و کتابخانه‌ها را اضافه می‌کنیم:

#include "stdio_usart1.h"
#include "DS18B20.h"

سپس در بدنه حلقه while(1) کد زیر را برای دریافت دما و چاپ آن می‌نویسیم:

 float temp = DS18B20_Convert(); // Get temperature
printf("%f\r\n", temp); // Print temerature
LL_mDelay(1000);

حالا کد را کامپایل می‌کنیم و آن را روی میکرو می‌ریزیم. در صورت انجام صحیح همه مراحل و اتصال صحیح مدار، می‌توانیم دما خوانده‌شده از محیط را در ترمینال سریال مشاهده کنیم:

ر ترمینال سریال

 

لینک فایل این پروژه روی گیت‌هاب

 

 

منبع:سیسوگ

مطلب قبلیامبدد لینوکس قسمت هجدهم: Kernel linux (بخش دوم)
مطلب بعدیآموزش STM32 با توابع HAL قسمت ششم: جزییات پیشرفته‌تر از GPIO

پاسخ دهید

لطفا نظر خود را وارد کنید!
لطفا نام خود را در اینجا وارد کنید