با سلام، در این قسمت از آموزش CH32 میریم سراغ I2C در این میکروکنترلر این قسمت از آموزش سختی خاصی نداره با کمی دقت متوجه مباحث میشوید.
در ابتدا بگم که ادامهدادن این آموزش نیازمند پیشزمینههایی از رابط i2C و نحوه کارکرد آن هست، اگر با آن آشنایی ندارید پیشنهاد میکنم آموزش دوستان در این زمینه رو حتماً مطالعه کنید.
بریم یکم با ساختار داخلی واحد I2C این میکرو آشنا بشیم:
واحد I2C این میکرو قابلیتهای زیادی داره مثل SMBus و آدرس 10 بیتی و ساپورت کردن از سرعت 400 کیلوهرتز که مناسب است برای دستگاههایی مثل LCDهایی که با I2C کار میکنن، از DMA و وقفه هم پشتیبانی میکنه.
این درخواستهای وقفهای هست که ساپورت میکنه.
این i2c بدون ACK هم میتونه کار کنه ولی توصیه نمیکنم در این مود ازش بهعنوان I2C استفاده کنید.
آموزش DMA در جلسات بعد داریم، اما داستان اصلی DMA همیشه مقصد و مبدأ هست، پس اگر خواستید I2C را با DMA راهاندازی کنید تنها کاری که لازم هست مشخصکردن مبدأ و مقصد هست که داخل آموزش DMA در موردش صحبت میکنیم.
این میکرو از PEC هم ساپورت میکنه، سعی کنید داخل پروژههای حساس حتماً ازش استفاده کنید. حالا شاید سؤالی براتون پیش بیاد که PEC چی هست؟
در پروتکل I2C ،PEC مخفف عبارت “Packet Error Checking” است. PEC یک مکانیزم برای بررسی و تأیید صحت دادهها در طول انتقال است. این مکانیزم بهمنظور کاهش خطاهای ناشی از نویز الکتریکی و دیگر مشکلات احتمالی در خطوط ارتباطی استفاده میشود.
PEC از یک روش (Checksum) استفاده میکند که به آن “Cyclic Redundancy Check” یا CRC گفته میشود. در پروتکل I2C، معمولاً از الگوریتم CRC-8 استفاده میشود. این الگوریتم یک بایت اضافی را به بستههای داده اضافه میکند که شامل نتیجه محاسبه CRC-8 بر روی تمامی بایتهای دادهای است که در بسته انتقال داده شدهاند.
در پروتکل I2C ،PEC معمولاً به صورت یک بایت اضافی در انتهای هر بسته داده اضافه میشود. گیرنده پس از دریافت دادهها و PEC، محاسبات CRC-8 خود را بر روی دادههای دریافت شده انجام میدهد و نتیجه را با PEC دریافت شده مقایسه میکند. اگر نتایج یکسان باشند، دادهها صحیح در نظر گرفته میشوند؛ در غیر این صورت، دادهها دارای خطا هستند و باید دوباره ارسال شوند.
به طور خلاصه، PEC در I2C یک ابزار مؤثر برای افزایش صحت و اطمینان در انتقال دادهها است و استفاده از آن میتواند به بهبود کارایی و قابلیت اطمینان سیستمهای مبتنی بر I2C کمک کند.
با بیشتر قابلیتهای واحد I2C این میکرو آشنا شدیم، حالا نوبت کدنویسی برای این واحد هست.
اولین کار برای راهاندازیاش فعالکردن کلاک واحد I2C هست و GPIO که قرار است واحد I2C ما بهش وصل بشود، برای تغییر پایههای پیشفرض I2C میتواند از واحد AFIO استفاده بکنید که یک جدول داخل دیتاشیت دارد که مشخص میکند با تعیین کدام بیتها این فانکشنها روی کدام پایهها سوئیچ میشوند و پایههای GPIO رو بهصورت Alternate function تعریف میکنیم و راهاندازی میکنیم، بهصورت زیر:
پس از راهاندازی این بخش میریم سراغ کانفیگ کردن خود واحد I2C که به شکل زیر است:
سرعت کلاک روی 100 کیلو هرتز تععین شده، نحوه آدرس دهی 7 بیتی است،پارامتر DutyCycle برای مود سریع I2C مورد استفاده قرار میگیره و عملا اینجا در کد ما تاثیری ندارد، ولی گذاشتمش که یک توضیح مختصری در موردش برای کسایی که میخوان در مود سریع کار کنن بدم،
طبق استاندارد I2C، در حالت Fast Mode، نسبت زمانبندی بهصورت زیر است:
در پروتکل I2C، “Duty Cycle” به نسبت زمانی که خط کلاک (SCL) در وضعیت بالا (High) یا پایین (Low) است، اشاره دارد. این نسبت در حالتهای مختلف سرعتی I2C، بهویژه در حالت “Fast Mode”، اهمیت زیادی دارد.
در حالت “Fast Mode” که سرعت انتقال دادهها تا 400 کیلوهرتز است، نسبتهای Duty Cycle به گونهای تنظیم میشوند که زمانبندی دقیقتر و بهینهتری برای انتقال دادهها فراهم شود. در Fast Mode، زمانبندی و Duty Cycleها به گونهای طراحی میشوند که سازگاری و کارایی در انتقال دادهها افزایش یابد.
کدی که برای پیادهسازی این عمل نوشته آن بهصورت زیر هست:
سپس مد رو روی I2C میزاریم، ACK رو لازم داریم پس فعالش میکنیم، مد کاری ما مستر هست و تعیینکردن Ownaddress تأثیری روی کار ما ندارد و برای حالت Slave استفاده میشود.
بعدش هم با I2C_CMD و I2C_Init واحد I2C را فعال میکنیم.
ما برای مثال میخوایم ثانیه رو از آی سی DS3231 بخونیم.
در اینجا ترتیب کدها مهم بود و کمی حجمشون بالا برای همین تبدیل به تابع کردم که خواناتر باشن.
در این قسمت به علت زیاد بودن فانکشنها در کد، در خود کد بیشتر توضیحات هر خط رو خدمتتون دادم.
نکته بعدی این است که خود توابع این توابع رو بهصورت پیشفرض دارند و میتونید ازشون استفاده کنید، اینجا من برای اینکه یک ارتباط I2C رو از شروع ارتباط تا پایانش رو ببینید و نوشته باشید، خودم این تابعها رو نوشتم، البته ناگفته نماند که اون توابع آماده همه کار رو انجام نمیدن و شروعکردن و پایاندادنش با خودتون هست که در ادامه آموزش یاد میگیریم.
این کد برای ارسال دو بار دیتا و یک بار آدرس هست، اول آدرس رو میفرسته سپس آدرس رجیستری که میخوایم داخل اون چیزی بنویسیم، سپس خود دیتا رو ارسال میکنه. Whileهایی که استفاده شده برای تأیید دریافت ACK هست؛ ولی شما میتونید این رو عوضش بکنید تا برنامهتون قفل نکنه اینجا چون صرفاً فقط راهاندازی یک ای سی خالی بود از While استفاده شده. بهتر است یک روتین جدا که به روتین برنامه اصلی کاری نداشته باشد برای چککردن ارورها داشته باشد.
این هم برای خواندن از رجیسترهای DS3231 هست:
خب برای استفاده از این توابع هم به این صورت عمل میکنیم:
اینجا سه رجیستر ثانیه و دقیقه و ساعت مقدار 0 و 0 و 12 داخلشون قرار گرفتن.
در اینجا مقدار رجیستر 0 که آدرس ریجستر ثانیه هست رو هر 100 میلی ثانیه میخونیم.
برای خواندن دقیقه رجیستر 1 رو میخونیم و برای ساعت ریجستر 2.
برای تنظیم بقیه مواردش هم میتونید در بقیه رجیسترهایش بنویسید و بخونید.
این هم کد کامل این قسمت از آموزش:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | #include "debug.h" /* Global typedef */ /* Global define */ #define DS3231_Address 0x68 void I2C_Writeregister(I2C_TypeDef *I2Cx, uint8_t deviceAddr, uint8_t registerAddr, uint8_t data); uint8_t I2C_Read_register(I2C_TypeDef *I2Cx, uint8_t deviceAddr, uint8_t registerAddr); /* Global Variable */ /********************************************************************* * @fn main * * @brief Main program. * * @return none */ int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); SystemCoreClockUpdate(); Delay_Init(); USART_Printf_Init(115200); printf("SystemClk:%d\r\n",SystemCoreClock); printf("ChipID:%08x\r\n", DBGMCU_GetCHIPID()); printf("This is printf example\r\n"); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_I2C; GPIO_I2C.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_I2C.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_I2C.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_I2C); //////////////////////////////////////////////////i2c config I2C_InitTypeDef i2ccfg; i2ccfg.I2C_ClockSpeed = 100000; i2ccfg.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; i2ccfg.I2C_DutyCycle = I2C_DutyCycle_2; i2ccfg.I2C_Mode = I2C_Mode_I2C; i2ccfg.I2C_Ack = I2C_Ack_Enable; i2ccfg.I2C_OwnAddress1 = 0xaa; I2C_Init(I2C1, &i2ccfg); I2C_AcknowledgeConfig(I2C1, ENABLE); I2C_Cmd(I2C1, ENABLE); // Example: Set time on DS3231 I2C_Writeregister(I2C1, DS3231_Address, 0x00, 0x00); // Set seconds to 00 I2C_Writeregister(I2C1, DS3231_Address, 0x01, 0x00); // Set minutes to 00 I2C_Writeregister(I2C1, DS3231_Address, 0x02, 0x12); // Set hour to 12 (24-hour format) while (1) { uint8_t seconds = I2C_Read_register(I2C1, DS3231_Address, 0x00); printf("Seconds: %02x\r\n", seconds); Delay_Ms(100); } } void I2C_Writeregister(I2C_TypeDef *I2Cx, uint8_t deviceAddr, uint8_t registerAddr, uint8_t data) { // Generate START condition I2C_GenerateSTART(I2Cx, ENABLE); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)); //for ACK // Send device address for write I2C_Send7bitAddress(I2Cx, deviceAddr << 1, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//for ACK // Send register address I2C_SendData(I2Cx, registerAddr); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//for ACK // Send data I2C_SendData(I2Cx, data); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//for ACK // Generate STOP condition I2C_GenerateSTOP(I2Cx, ENABLE); } uint8_t I2C_Read_register(I2C_TypeDef *I2Cx, uint8_t deviceAddr, uint8_t registerAddr) { uint8_t data; // Generate START condition I2C_GenerateSTART(I2Cx, ENABLE); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));// for ACK // Send device address for write I2C_Send7bitAddress(I2Cx, deviceAddr << 1, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // for ACK // Send register address I2C_SendData(I2Cx, registerAddr); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));// for ACK // Generate repeated START condition I2C_GenerateSTART(I2Cx, ENABLE); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));// for ACK // Send device address for read I2C_Send7bitAddress(I2Cx, deviceAddr << 1, I2C_Direction_Receiver); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));// for ACK // Receive data while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));// for receive data = I2C_ReceiveData(I2Cx); // Generate STOP condition I2C_GenerateSTOP(I2Cx, ENABLE); return data; } |
تا قسمت بعدی موفق باشید.
نویسنده شو !
سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.