I2C
در قسمت بیستو سوم آموزش میکروکنترلر STM8 به بررسی رابط سریال(SPI)پرداختیم. در این قسمت از آموزش میکروکنترلرSTM8 قصد دارد I2C را موردبررسی قراردهد. با ما همراه باشید.
I2C یکیدیگر از روشهای ارتباطی محبوب در ارتباطات سریال سنکرون است که توسط NXP توسعهیافته است. فقط از دوسیم برای ارتباط استفاده میکند و بههمینترتیب، رابط دوسیم(TWI)نامیده میشود. درست مانند SPI، رابط I2C بهطورگسترده در ارتباط با ساعتهای زمان واقعی(RTC)، سنسورهای دیجیتال، تراشههای حافظه و… استفاده میشود. این ویژگیها با SPI مشترک هستند. اما درمقایسه با SPI، آن را کندتر و محدودتر میکند. تا ۱۲۷دستگاه میتواند در یک باسI2C همکاری کند.
ارتباط I2C بین دستگاههایی که سطح ولتاژمنطقی متفاوتی دارند امکانپذیر نمیباشد.
در I2C دو سیم ارتباطی بطورمستقیم بههم وصل میشوند یعنی SDA به SDA و SCL به SCL.
باس I2C نیز مانند SPI، دارای یک میکروکنترلر مستر و یک یا چند Slave است. میکروکنترلر مستر مسئولیت ایجاد سیگنالهای ساعت و برقراری ارتباط را برعهده دارد. زمانیکه مستر شناسه Slave را با دستورخواندن/نوشتن ارسالکند ارتباط برقرار میشود. Slave با پردازش درخواست مستر و ارسال داده به این فرمان پاسخ میدهد.
اگر تمایلدارید اطلاعات بیشتری درباره باس I2C بدست آورید لینکهایزیر مناسب هستند:
پروتکلهای دیگر مانند SMBus و I2S شباهتهای بسیاری با I2C دارند، بنابراین با یادگیریI2C میتوانید با دیگر پروتکلها نیز ارتباط برقرارکنید.
اتصالات سختافزاری
نمونهکد I2C
این کد نشان میدهد، چگونه ارتباط سنسور نور دیجیتال BH1750 I2C را با STM8 برقرارکنیم. LCD نیز برای نمایش خروجی سنسور نور در لوکس استفاده میشود.
main.c
#include "STM8S.h" #include "BH1750.h" #include "stm8s_lcd.h" void delay_ms(uint16_t ms); void clock_setup(void); void GPIO_setup(void); void I2C_setup(void); void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value); void main() { unsigned int LX = 0x0000; unsigned int tmp = 0x0000; clock_setup(); GPIO_setup(); I2C_setup(); LCD_init(); BH1750_init(); LCD_clear_home(); LCD_goto(0, 0); LCD_putstr("STM8 I2C"); LCD_goto(0, 1); LCD_putstr("Lx"); delay_ms(10); while(TRUE) { tmp = get_lux_value(cont_L_res_mode, 20); if(tmp > 10) { LX = tmp; } else { LX = get_lux_value(cont_H_res_mode1, 140); } lcd_print(3, 1, LX); delay_ms(200); }; } void clock_setup(void) { CLK_DeInit(); CLK_HSECmd(DISABLE); CLK_LSICmd(DISABLE); CLK_HSICmd(ENABLE); while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE); CLK_ClockSwitchCmd(ENABLE); CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8); CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV2); CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, DISABLE, CLK_CURRENTCLOCKSTATE_ENABLE); CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE); CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, ENABLE); CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE); CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE); CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE); CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE); CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE); CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE); } void GPIO_setup(void) { GPIO_DeInit(GPIOB); GPIO_Init(GPIOB, GPIO_PIN_4, GPIO_MODE_OUT_OD_HIZ_FAST); GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_OD_HIZ_FAST); } void I2C_setup(void) { I2C_DeInit(); I2C_Init(100000, BH1750_addr, I2C_DUTYCYCLE_2, I2C_ACK_CURR, I2C_ADDMODE_7BIT, (CLK_GetClockFreq() / 1000000)); I2C_Cmd(ENABLE); } void lcd_print(unsigned char x_pos, unsigned char y_pos, unsigned int value) { char tmp[5] = {0x20, 0x20, 0x20, 0x20, 0x20} ; tmp[0] = ((value / 10000) + 0x30); tmp[1] = (((value / 1000) % 10) + 0x30); tmp[2] = (((value / 100) % 10) + 0x30); tmp[3] = (((value / 10) % 10) + 0x30); tmp[4] = ((value % 10) + 0x30); LCD_goto(x_pos, y_pos); LCD_putstr(tmp); }
BH1750.h
#include "STM8S.h" #define BH1750_addr 0x46 #define power_down 0x00 #define power_up 0x01 #define reset 0x07 #define cont_H_res_mode1 0x10 #define cont_H_res_mode2 0x11 #define cont_L_res_mode 0x13 #define one_time_H_res_mode1 0x20 #define one_time_H_res_mode2 0x21 #define one_time_L_res_mode 0x23 void BH1750_init(void); void BH1750_write(unsigned char cmd); unsigned int BH1750_read_word(void); unsigned int get_lux_value(unsigned char mode, unsigned int delay_time);
BH1750.c
#include "BH1750.h" void delay_ms(uint16_t ms) { uint32_t j= ms *100; while(j--) ; } void BH1750_init(void) { delay_ms(10); BH1750_write(power_down); } void BH1750_write(unsigned char cmd) { I2C_GenerateSTART(ENABLE); while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(BH1750_addr, I2C_DIRECTION_TX); while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(cmd); while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(ENABLE); } unsigned int BH1750_read_word(void) { unsigned long value = 0x0000; unsigned char num_of_bytes = 0x02; unsigned char bytes[2] = {0x00, 0x00}; while(I2C_GetFlagStatus(I2C_FLAG_BUSBUSY)); I2C_GenerateSTART(ENABLE); while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(BH1750_addr, I2C_DIRECTION_RX); while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); while(num_of_bytes) { if(I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED)) { if(num_of_bytes == 0) { I2C_AcknowledgeConfig(I2C_ACK_NONE); I2C_GenerateSTOP(ENABLE); } bytes[(num_of_bytes - 1)] = I2C_ReceiveData(); num_of_bytes--; } }; value = ((bytes[1] << 8) | bytes[0]); return value; } unsigned int get_lux_value(unsigned char mode, unsigned int delay_time) { unsigned long lux_value = 0x00; unsigned char dly = 0x00; unsigned char s = 0x08; while(s) { BH1750_write(power_up); BH1750_write(mode); lux_value += BH1750_read_word(); for(dly = 0; dly < delay_time; dly += 1) { delay_ms(1); } BH1750_write(power_down); s--; } lux_value >>= 3; return ((unsigned int)lux_value); }
توضیحات
در ابتدا CPU و کلاک داخلی را تنظیم میکنیم.
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8); CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV2); …. …. CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, ENABLE);
ورودی/خروجیهای I2C بهعنوان خروجیهای open drain تنظیمشدهاست، زیرا دارای مقاومت پول-آپ خارجی هستند که ورودی/خروجیهای باس را به VDD وصل میکنند. پین SCL همیشه بهعنوان خروجی میکروکنترلر میزبان است، اما جهت پین SDA با توجه به عملیات خواندن و نوشتن متفاوت است. که بهطور خودکار توسط سختافزارI2C انجام میشود.
GPIO_DeInit(GPIOB); GPIO_Init(GPIOB, GPIO_PIN_4, GPIO_MODE_OUT_OD_HIZ_FAST); GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_OD_HIZ_FAST);
I2C دارای پارامترهای زیادی است که نیازبه تنظیم دارد. اولاً سرعت باس I2C، شناسهI2C، کلاک duty cycle، مد آدرس، تایید نوع و سرعت کلاک داخلی. در اینجا شناسه مستر و Slave تنظیم میشوند، ما از STM8 بهعنوان یک Slave استفاده نمیکنیم.
void I2C_setup(void) { I2C_DeInit(); I2C_Init(100000, BH1750_addr, I2C_DUTYCYCLE_2, I2C_ACK_CURR, I2C_ADDMODE_7BIT, (CLK_GetClockFreq() / 1000000)); I2C_Cmd(ENABLE); }
اکثر نمونهایی که برای I2Cدر اینترنت وجود دارد ،I2C را فقط با 24 مجموعه EEPROM نشان میدهند. در اینجا کار متفاوتی انجام میدهیم و بهجای تکرار نمونههای EEPROM از سنسور دیجیتال BH1750 I2C استفاده میکنیم.
unsigned int BH1750_read_word(void) { unsigned long value = 0x0000; unsigned char num_of_bytes = 0x02; unsigned char bytes[2] = {0x00, 0x00}; while(I2C_GetFlagStatus(I2C_FLAG_BUSBUSY)); I2C_GenerateSTART(ENABLE); while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(BH1750_addr, I2C_DIRECTION_RX); while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); while(num_of_bytes) { if(I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED)) { if(num_of_bytes == 0) { I2C_AcknowledgeConfig(I2C_ACK_NONE); I2C_GenerateSTOP(ENABLE); } bytes[(num_of_bytes - 1)] = I2C_ReceiveData(); num_of_bytes--; } }; value = ((bytes[1] << 8) | bytes[0]); return value; }
همانند SPI، اول باید آزادبودن سختافزارI2C را بررسیکنیم. سپس، نخستین قدم برای ارتباط I2C را برداشته و وضعیت مستر و Slave بودن را چک میکنیم. سپس، با فرمان خواندن آدرس یا شناسه Slave را ارسال میکنیم، که نشان میدهد، میخواهیم از روی Slave بخوانیم. مجدداً قبلاز ادامه پرچم بررسی میشود. سپس سنسور ۱۶بیت خروجی میدهد که باید بهصورت دو مقدار ۸بیتی آنرا استخراج کنیم. این عملیات در حلقه “while” انجام میشود. درنهایت، دوبایت بههم متصل میشوند که نشاندهنده خروجی نور است.
تابعزیر عملکرد تعیین ارزش لوکس از سنسور را ساده میکند. این تابع را در حلقه اصلی برای استخراج میانگین نور در لوکس بهکار میبریم.
unsigned int get_lux_value(unsigned char mode, unsigned int delay_time) { unsigned long lux_value = 0x00; unsigned char dly = 0x00; unsigned char s = 0x08; while(s) { BH1750_write(power_up); BH1750_write(mode); lux_value += BH1750_read_word(); for(dly = 0; dly < delay_time; dly += 1) { delay_ms(1); } BH1750_write(power_down); s--; } lux_value >>= 3; return ((unsigned int)lux_value); }
منبع: سیسوگ