توصیه شده, ARM, STM32, آموزش, آموزش STM32 با توابع LL, مقاله های سیسوگ

راه‌اندازی ارتباط I2C در STM32 | قسمت 25 آموزش STM32 با توابع LL

راه‌اندازی ارتباط I2C در STM32 | قسمت 25 آموزش STM32 با توابع LL

در قسمت قبلی با ADC آشنا شدیم و در آموزش‌های قبلی در مورد پروتکل‌های ارتباط سریال صحبت کردیم. در این قسمت می‌خواهیم در مورد یک پروتکل ارتباط سریال دیگر، یعنی Inter-Integrated Circuit یا همان I2C صحبت کنیم. این پروتکل، همان‌طور که از نام آن مشخص است، برای ارتباط چیپ‌های مختلف به‌کار می‌رود. در I2C تنها از دو سیم برای ارتباط استفاده می‌شود و نسبت به USART سرعت بالاتری دارد (در بعضی IC ها تا 3.4 Mbit/s) اما از پیچیدگی‌های راه‌اندازی بیشتری نیز برخوردار است و برای مسافت‌های خیلی کوتاه مناسب است. در ادامه با جزییات این ارتباط و نحوه راه‌اندازی آن بیشتر آشنا می‌شویم.

با سیسوگ همراه باشید.

شکل بالا ارتباط I2C بین یک میکروکنترلر و سه دستگاه دیگر را نشان می‌دهد. همان‌طور که دیده می‌شود، دراین‌ارتباط از یک سیم برای انتقال کلاک (SCL) و یک سیم برای انتقال اطلاعات داده (SDA) استفاده می‌شود. در این شکل میکروکنترلر وظیفه کنترل ارتباط را به عهده دارد و نقش Master را ایفا می‌کند. به همین دلیل کلاک نیز توسط میکرو تأمین می‌شود. هرچند در این شکل تنها یک Master وجود دارد، اما بیش از یک Master نیز می‌تواند وجود داشته باشد. بااین‌حال در هر زمان تنها یک دستگاه کنترل ارتباط را در دست می‌گیرد.

منظور از کنترل ارتباط، انتخاب Slave و همچنین جهت انتقال داده است. همان‌طور که در شکل دیده می‌شود، برای خط SDA از یک مقاومت Pull-up استفاده‌ شده است. درصورتی‌که بیش از Master وجود داشته باشد و یا از Clock stretching استفاده شود، در خط SCL نیز از مقاومت Pull-up استفاده می‌کنیم.

برای شروع ارتباط و تبادل داده، دستگاه Master باید شرط شروع ارتباط را ایجاد کند، سپس آدرس دستگاه موردنظر برای تبادل اطلاعات و همچنین جهت تبادل داده (خواندن یا نوشتن) را بفرستد. پس‌ازاینکه دستگاه Slave بیت تأیید یا ACK را فرستاد. فرستادن یا دریافت اطلاعات شروع می‌شود. برای درک بهتر به شکل زیر توجه کنید:

در ادامه به نحوه راه‌اندازی این ارتباط در میان Blue Pill و یک حافظه EEPROM می‌پردازیم.

 

ایجاد پروژه

در محیط Cube MX مثل گذشته تنظیمات کلاک، دیباگ و همچنین واحد USART1 (برای دیدن نتایج اجرا) را انجام می‌دهیم. سپس واحد I2C1 را مانند شکل زیر تنظیم می‌کنیم:

بعد تنظیمات درایورها بر روی LL کد پروژه را ایجاد می‌کنیم.

 

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

در ابتدا کتابخانه‌ها و ثابت‌های مورد نیاز را اضافه می‌کنیم؛

سپس مثل قبل توابع مربوط به ریدایرکت Printf را در این پروژه نیز کپی می‌کنیم و تغییرات لازم را انجام می‌دهیم. حالا باید توابع موردنیاز برای خواندن و نوشتن توسط I2C را بنویسیم؛

اکنون می‌توانیم با استفاده از این توابع، دو تابع جدید برای نوشتن روی حافظه EEPROM و خواندن از آن، بنویسیم. برای این عمل نیاز به دیتاشیت حافظه‌داریم. زیرا باید از نحوه آدرس‌دهی، تأخیر نوشتن و جزییات دیگر مربوط به حافظه اطلاع داشته باشیم. ما از یک حافظه 265kb با تأخیر نوشتن 5ms استفاده می‌کنیم.

کدهای زیر را قبل از int main می‌نویسیم؛

اکنون می‌توانیم به‌وسیله توابع نوشته‌شده، اطلاعات دلخواهمان را روی حافظه بنویسیم و از آن بخوانیم. قبل از آن، واحد I2C را فعال می‌کنیم و برای دریافت اطلاعات خوانده‌شده متغیرهای موردنیازمان را تعریف می‌کنیم:

حالا برای نمونه در آدرس 0000 مقدار 24 و در آدرس 0001 عدد 25 را می‌نویسیم. سپس این دو خانه‌ی حافظه را می‌خوانیم.

درستی عملیات نوشتن و خواندن را می‌توانیم در Logic Analyzer بررسی کنیم؛

همچنین می‌توانیم برای اطمینان از درست انجام شده عمل نوشتن اطلاعات خوانده شده را توسط USART ارسال کنیم؛

در ترمینال سریال می‌بینیم؛

 

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

انتشار مطالب با ذکر نام و آدرس وب سایت سیسوگ، بلامانع است.

شما نیز میتوانید یکی از نویسندگان سیسوگ باشید.   همکاری با سیسوگ

6 دیدگاه در “راه‌اندازی ارتباط I2C در STM32 | قسمت 25 آموزش STM32 با توابع LL

  1. Avatar for محمد ربانی محمد ربانی گفت:

    با عرض سلام و وقت بخیر
    آموزش بسیار خوبی ارائه داده اید فقط یک نکته اینکه برای بهبود کیفیت کد می توانید به جای استفاده از وقفه زمانی Z_NOP بعد از خطوط LL_I2C_GenerateStartCondition(I2C1)
    از عبارت { } while (!LL_I2C_IsActiveFlag_SB(I2C1))
    استفاده نمایید. این را من از چندین منبع متوجه شدم مثل لینک زیر
    https://community.st.com/t5/stm32-mcus-products/busy-bus-after-i2c-reading/td-p/327215

  2. Avatar for Amirmso Amirmso گفت:

    سلام
    ممنون بابت نوشتن تنها آموزش فارسی توابع LL
    من یه سنسور aht10/aht20 رو با توابع Hal راه اندازی کردم… حالا می‌خوام تبدیلش کنم به LL ولی نمی‌دونم چرا کدی که نوشتم به مشکل خورده و اجرا نمیشه…
    اگه ممکنه منو راهنمایی کنید.در ادامه کدی که با استفاده از Hal نوشتم رو قرار میدم:
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
    if(htim->Instance == TIM16)
    {
    /* Set every 100ms */
    T_100ms = 255;
    }
    }

    void AHT10_GET()
    {
    if(T_100ms)
    {
    if(AHT10_Switcher)
    {
    HAL_I2C_Master_Transmit_IT(&hi2c1, AHT10_ADRESS, (uint8_t*)AHT10_TmpHum_Cmd, 3); /* Send command (trigger measuremetns) + parameters */
    }
    else
    {
    HAL_I2C_Master_Receive_IT(&hi2c1, AHT10_ADRESS, (uint8_t*)AHT10_RX_Data, 6); /* Receive data: STATUS[1]:HIMIDITY[2.5]:TEMPERATURE[2.5] */
    }

    if(~AHT10_RX_Data[0] & 0x80)
    {
    /* Convert to Temperature in °C */
    AHT10_ADC_Raw = (((uint32_t)AHT10_RX_Data[3] & 15) << 16) | ((uint32_t)AHT10_RX_Data[4] << 8) | AHT10_RX_Data[5];
    AHT10_Temperature = (float)(AHT10_ADC_Raw * 200.00 / 1048576.00) – 50.00;

    /* Convert to Relative Humidity in % */
    AHT10_ADC_Raw = ((uint32_t)AHT10_RX_Data[1] << 12) | ((uint32_t)AHT10_RX_Data[2] <> 4);
    AHT10_Humidity = (float)(AHT10_ADC_Raw*100.00/1048576.00);
    }

    AHT10_Switcher = ~AHT10_Switcher; /* Invert */
    //GPIOC->ODR ^= GPIO_ODR_ODR13; /* Green LED */
    T_100ms = 0; /* Nulify */
    }
    }
    کد بالا رو تبدیل به کد زیر با استفاده از LL کردم البته از chatgpt هم کمک گرفتم:
    #define AHT20_ADDRESS 0x38 << 1
    #define AHT20_INIT_CMD 0xBE
    #define AHT20_MEASURE_CMD 0xAC
    #define AHT20_SOFT_RESET_CMD 0xBA

    void AHT20_Init(I2C_TypeDef *I2Cx) {
    uint8_t init_cmd[] = {AHT20_INIT_CMD, 0x08, 0x00};
    while (LL_I2C_IsActiveFlag_BUSY(I2Cx))
    ;
    LL_I2C_HandleTransfer(I2Cx, AHT20_ADDRESS, LL_I2C_ADDRSLAVE_7BIT, sizeof(init_cmd), LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE);
    while (!LL_I2C_IsActiveFlag_TXIS(I2Cx))
    ;
    for (uint8_t i = 0; i < sizeof(init_cmd); i++) {
    LL_I2C_TransmitData8(I2Cx, init_cmd[i]);
    while (!LL_I2C_IsActiveFlag_TXIS(I2Cx))
    ;
    }
    while (!LL_I2C_IsActiveFlag_STOP(I2Cx))
    ;
    LL_I2C_ClearFlag_STOP(I2Cx);
    LL_mDelay(20);
    }

    void AHT20_SoftReset(I2C_TypeDef *I2Cx) {
    uint8_t reset_cmd = AHT20_SOFT_RESET_CMD;
    while (LL_I2C_IsActiveFlag_BUSY(I2Cx))
    ;
    LL_I2C_HandleTransfer(I2Cx, AHT20_ADDRESS, LL_I2C_ADDRSLAVE_7BIT, 1, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE);
    while (!LL_I2C_IsActiveFlag_TXIS(I2Cx))
    ;
    LL_I2C_TransmitData8(I2Cx, reset_cmd);
    while (!LL_I2C_IsActiveFlag_TXIS(I2Cx))
    ;
    while (!LL_I2C_IsActiveFlag_STOP(I2Cx))
    ;
    LL_I2C_ClearFlag_STOP(I2Cx);
    LL_mDelay(20);
    }

    void AHT20_TriggerMeasurement(I2C_TypeDef *I2Cx) {
    uint8_t measure_cmd[] = {AHT20_MEASURE_CMD, 0x33, 0x00};
    while (LL_I2C_IsActiveFlag_BUSY(I2Cx))
    ;
    LL_I2C_HandleTransfer(I2Cx, AHT20_ADDRESS, LL_I2C_ADDRSLAVE_7BIT, sizeof(measure_cmd), LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE);
    while (!LL_I2C_IsActiveFlag_TXIS(I2Cx))
    ;
    for (uint8_t i = 0; i < sizeof(measure_cmd); i++) {
    LL_I2C_TransmitData8(I2Cx, measure_cmd[i]);
    while (!LL_I2C_IsActiveFlag_TXIS(I2Cx))
    ;
    }
    while (!LL_I2C_IsActiveFlag_STOP(I2Cx))
    ;
    LL_I2C_ClearFlag_STOP(I2Cx);
    LL_mDelay(80);
    }

    void AHT20_ReadData(I2C_TypeDef *I2Cx, float *temperature, float *humidity) {
    uint8_t data[6];
    while (LL_I2C_IsActiveFlag_BUSY(I2Cx))
    ;
    LL_I2C_HandleTransfer(I2Cx, AHT20_ADDRESS, LL_I2C_ADDRSLAVE_7BIT, sizeof(data), LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_READ);
    for (uint8_t i = 0; i < sizeof(data); i++) {
    while (!LL_I2C_IsActiveFlag_RXNE(I2Cx))
    ;
    data[i] = LL_I2C_ReceiveData8(I2Cx);
    }
    while (!LL_I2C_IsActiveFlag_STOP(I2Cx))
    ;
    LL_I2C_ClearFlag_STOP(I2Cx);

    uint32_t raw_humidity = ((uint32_t)data[1] << 12) | ((uint32_t)data[2] <> 4);
    uint32_t raw_temperature = ((uint32_t)(data[3] & 0x0F) << 16) | ((uint32_t)data[4] << 8) | data[5];

    *humidity = (float)raw_humidity * 100 / (1 << 20);
    *temperature = (float)raw_temperature * 200 / (1 << 20) – 50;
    }
    ممنون میشم راهنمایی کنید. واقعا رفرنس خوبی برای LL تو نت نیست…🤯🥵🙏🙏

  3. Avatar for پوریا پوریا گفت:

    با سلام و تبریک سال نو.
    ممنون بابت سایت و مطالب فوق العاده تون _\/_
    دوستان کسی میتونه یه توضیح کوتاه در مورد ()define Y_NOP و علت فراخوانیشون قبل وضعیت استارت بده؟

    1. Avatar for Zeus ‌ Zeus ‌ گفت:

      برای ایجاد تاخیره

  4. Avatar for ساسان ساسان گفت:

    سلام. با سپاس فراوان از آموزش فوق العاده شما
    من با همین برد میخوام ماژول ADS1117 رو راه اندازی کنم. امکانش هست راهنمایی بفرمایید؟

  5. Avatar for Soran Soran گفت:

    تشکر بابت آموزشهای مفید و کاربردی

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *