CAN magistralė: įžanga

Posted: 2015-11-27 in Darbeliai
Žymos:,

CAN logotipas | Darau, blėEilinis neaiškios kilmės projektas šviečiasi ant nosies ir jame reikės sujungti kelis mikrovaldiklius į tinklą. Dar būtų gerai, kad jie tame tinkle galėtų „kalbėtis“ įvairiomis kryptimis, atseit, kad tinklas būtų „multi-master“ tipo, o ne tai kad vienas yra „šeiminykas“, o kiti — „tarnukai“. Dar kitaip: kad visi mikrovaldikliai būtų sąlyginai lygiaverčiai ir bet kuris galėtų pradėti duomenų transliaciją.

Pabandžius ko nors panašaus paieškoti forumuose ar pasišnekėjus su kai kuriais subjektais dažniausiai pasiūloma naudoti RS-485 „tinklą“. Na, nepaisant siūlymų naudoti TCP/IP, kas yra vabšė nesąmonė ir didžiausias pereboras, kai tinkle turi skraidyti vos po kelis baitukus — ne filmams pumpuoti tas tinklas gi.

Su RS-485 kaip ir viskas tvarkoj, bet šitas daiktas yra elektrinė specifikacija. Nurodyta, kaip sujungti laidus ir kaip jais turi skraidyti kokie tai virpesiai. Daugiau nieko. Taigi visas pačių duomenų valdymas, kolizijos, klaidų aptikimas ir panašūs tinklo vargai tenka vargšui programuotojui. O kur daug vargo programuotojui, ten ir daug bugų arba daug laiko sąnaudų testavimui.

Džiugus reikalas tas, kad yra dar tokia CAN magistralė, ypač paplitusi automobiliuose ir kai kuriose kitose įrangose (sakykim, kondicionierių irba klimato kontrolėj). Ji panaši į RS-485 fiziniu požiūriu: taip pat naudojami suvyti (ar nebūtinai) laidai, taip pat reikalingi atspindžio rezistoriai, taip pat reikalingi elektrinių signalų konverteriai (specializuotos mikroschemos). Tačiau yra pora niuansų, kurie man asmeniškai, labai patinka.

Pirmas dalykas: duomenų siuntimas, priėmimas, klaidų ir kolizijų aptikimas yra aparatūriniai. Taip sakant, su šitom problemom dirba silicis, o ne programuotojas. Vadinasi, jeigu aš, programuotojas, liepsiu mikrovaldikliui siųsti pranešimuką CAN magistrale, tai man nereikės rūpintis, kad tas pranešimukas kur tai pasimes, nutrūks ar susidurs su kitu ir suvels visą liniją. Baisiai gerai.

Antras: taip pat yra aparatūrinis pranešimų filtravimas ir nereikia sukti galvos dėl „nedominančių“ pranešimų. Čia šiek tiek „pahakinus“ CAN pranešimų protokolą galima įdiegti tinkle esančių įrenginių adresaciją.

Trečias, ne toks svarbus, bet malonus: CAN tinklo topologija gali būti ir ne linijinė. Ji gali būti ir žvaigždinė irba mišri. Tereikia atspindžio rezistorių skaičiukais pasirūpinti, kas jau nėra labai sudėtinga.

Mano turima krūvelė STM32F103C8T6 mikrovaldiklių turi vidinį CAN magistralės palaikymą. Tik neturi „draiverio“. Yra CAN RX ir TX kojytės, kurias reikia prijungti prie elektrinių signalų konverterio. Gaila, kad nėra dar ir vidinio draiverio, būtų iš viso baisgerai.

Kokie CAN trūkumai? Na, sakykim, ribotas vieno pranešimo duomenų kiekis: nuo 0 iki 8 baitų. Ne kažin kas, tikrai. Bet, pripažinkime, labai daug ir nereikia. Juk čia mikrovaldiklių tinklas, kur jie turi apsikeisti trumpais ir konkrečiais pranešimukais. Pvz. daryk tą ar aną, duok tokį tai duomenuką ar kažką. Be to, niekas nedraudžia pasiųsti pranešimukų seriją, kad ir penkiasdešimt jų.

Dar prie trūkumų yra pradinis bjaurus įsisavinimo laikas, vadinamoji stati įsisavinimo kreivė. Pradžia užknisanti ir varginanti, bet paskui jau galima tais pranešimais mėtytis. Su RS-485 tuo tarpu, kreivė ne tokia stati, bet labai ilga. O ir darbas, kur reikia pačiam išradinėti įvairius tinklo priežiūros valdymus, yra smarkiai per didelis, kad norėtųsi tuo užsiimti. Aišku, jei tinklas yra su vienu „masteriu“, tai dzin. Bet kai reikia lygiaverčių mazgų tinklo, tai RS-485 pagrįstas programinis tinklas — labai sudėtingas daiktas.

Čia įžangėlė. O dabar — trumpai apie CAN pranešimukų struktūrą.

Procesoriukai palaiko CAN2A ir CAN2B. Šios „versijos“ skiriasi papildomu ID laukeliu. CAN2A turi 11 bitų identifikatoriaus lauką, o CAN2B turi papildomą antrą 18 bitų identifikatoriaus lauką. Aš savo „projektui“ žadu naudoti CAN2B. Pirmąjį identifikatorių naudosiu pranešimuko tipui, o antrajame kavosiu „nuo“ ir „kam“ numeriukus. Mano tinkle aparatų bus sąlyginai nedaug, kokie 4-8, tai vieno baito numeriuko kiekvienam iš jų tikrai užteks. Bus dar vienas „stebintis“ mazgas, kuris rankios visus keliaujančius tinkle pranešimukus ir juos registruos bei pats galės ką nors išsiųsti. Jis bus prijungtas prie kompiuterio papildomai veiklai. Tikiuosi, kad jis bus prie kompiuterio prijungtas per USB, labai labai tikiuosi įvaldyti ir USB HID su ARM procesoriukais. Nas, USART visgi atgyvenęs reikalas, nelabai stabilus ir gan lėtas.

CAN magistralė yra suprojektuota, kaip prioritetinė. T.y. „svarbesni“ pranešimai nukeliauja pirmi. Dėl to pranešimų identifikatorius reikia paskirstyti protingai. Loginis nulis yra „pirmaujantis“ arba „dominuojantis“. T.y. pranešimas su identifikatoriumi 0 bus aukštesnio prioriteto, nei pranešimas 1. O pranešimas 1 bus aukštesnio prioriteto, nei pranešimas 2. Pabandykim įsivaizduoti, kaip tai vyksta.

Sakykim, du įrenginiai CAN magistralėje pradeda siųsti pranešimus vienu metu. Išsiunčia abu po nulinį bituką — dominuojantį. Ok. Išsiunčia po antrą — ok. Siunčia trečią — vienas nuliuką, o kitas vienetuką. Tas, kuris siunčia vienetuką, tuo pačiu tikrina liniją — ir ten randa nuliuką, nors siunčia vienetuką. Nes taip elektriškai sugalvota. Taigi aparatas, kuris siuntė vienetuką ir rado nuliuką taip supranta, kad kažkas linijoje irgi siuntinėja pranešimus. Todėl savo siuntimą nutraukia, nes vienetukas yra žemesnio prioriteto. Tada palaukia kažkiek ir vėl bando siųsti. Va taip valdomos kolizijos ir prioritetai CAN magistralėje. Ir, svarbiausia, tai daro aparatūrinė procesoriaus dalis. Programuotojui tereikia „išsiųsti“ ar „priimti“ pranešimą, o visais prioritetais ir kolizijomis rūpinasi magistralė.

Išskersta CAN2B pranešimuko struktūra pagal Vikipediją:

Laukelio pavadinimas Ilgis (bitais) Paskirtis
Pranešimuko pradžia 1 Žymi pranešimuko siuntimo pradžią. Spėju, kad dominuojantis nuliukas.
Identifikatorius A 11 Pirma unikalaus identifikatoriaus dalis, taip pat žyminti ir pranešimo prioritetą.
Užklausos bito pakaitalas (SRR) 1 Recesyvinis (1). Reikalingas dėl CAN2A išplėtimo, daugiau niekam.
Identifikatoriaus išplėtimo bitukas (IDE) 1 Recesyvinis (1) išplėstiniams CAN2B pranešimams su 29 bitų identifikatoriais.
Identifikatorius B 18 Antra unikalaus identifikatoriaus dalis, taip pat žyminti ir pranešimo prioritetą.
Užklausos bitukas (RTR) 1 Dominuojantis (0) duomenų pranešimams ir recesyvinis (1) užklausų pranešimams.
Reservuoti bitukai (r1, r0) 2 Kažkokie tai rezervuoti dominuojantys bitukai, nors priimtini ir recesyviniai…
Duomenų laukelio ilgis (DLC) 4 Duomenų ilgis baitais (0-8)
Duomenų laukelis 0–64 (0-8 baitai) Perduodami duomenų baitai, kurių skaičių nusako DLC.
CRC 15 Kontrolinis skaičiukas duomenų teisingumui patikrinti.
CRC skirtukas 1 Recesyvinis (1)
ACK (patvirtinimo) bitukas 1 Siunčiantysis siunčia recesyvinį (1), o bet kuris gaunantysis gali patvirtinti su dominuojančiu (0)
ACK skirtukas 1 Recesyvinis (1)
Pranešimuko pabaiga (EOF) 7 Recesyviniai (1) bitukai

Jo. Daug visokių pašalinių bitukų lyginant su pačiu duomenų laukeliu. Na, bet, kaip minėjau, CAN magistralė tam ir skirta — visokiems nedideliems pranšimukams perdavinėti. Pats pranešimo identifikatorius jau yra svarbi informacija, juo nusakoma, kas per velnias perduodama. Sakykim, jei sugalvotumėte pahakinti savo automobilį su išmaniu parktroniku, kuris pradeda veikti ir prieky, ir gale automobiliui judant nedideliu (>10 km/h) greičiu, tai iš CAN magistralės reikėtų nusiskaityti spidometro pranešimuką.

Standard Peripherals biblioteka iš STM32 turi krūvelę CAN magistralės pranešimukų pavyzdžių. Aš dar nusipirkau keletą gatavų kaišomų plokštelių su SN65HVD230 mikroschema, kuri CAN RX/TX paverčia CANH ir CANL:

CAN magistralės mikroschema SN65HVD230 | Darau, blė

Tiesa, sunervino mažumėlę šios plokštelės — jose visose įdėti uždarantys 120Ω rezistoriai. Reikės išlituot lauk savo reikmėms… Na, bet pradžiai su dviem devboardukais bus gerai ir toks reikalas. Taigi bandymas pasiųsti žinutę iš vieno devboarduko į kitą:

CAN magistralės bandymas su ARM STM32F103 procesoriais | Darau, blė

Viskas ten paprasta, kaip du pirštu…

O paprasta todėl, kad STM32 Standard Peripherals biblioteka duodama su krūva pavyzdukų. Ten yra ir trys CAN magistralės naudojimo pavyzdžiai. Man tinkamiausias pasirodė „Networking“ pavyzdėlis. Ten siunčiamos žinutės ir keli įrenginiai jas priiminėja. Žinučių priėmimas pakabintas ant pertraukimo, taigi jos niekada nepražiopsosite. Kad tik spėt apdorot 🙂

Šiaip viskas pavyko gan sėkmingai, tik turint vieną JTAG zondą biškį užpypino perjunginėt tarp dviejų devboardukų. Susikonstravau va tokią CAN2B žinutę:

	CanTxMsg tx_msg;
	tx_msg.ExtId = SET_ID_A(1) | 2;
	tx_msg.IDE = CAN_ID_EXT;
	tx_msg.RTR = CAN_RTR_DATA;
	tx_msg.DLC = 4;
	tx_msg.Data[0] = 1;
	tx_msg.Data[1] = 2;
	tx_msg.Data[2] = 3;
	tx_msg.Data[3] = 4;

SET_ID_A — mano paties makrosas, kuris tiesiog stumteli ID A skaičiuką per 18 punktų kairėn.

Čia tokia subtilybė su STM32 konkrečiai. Jei IDE nustatyčiau CAN_ID_STD, t.y. CAN2A, tik su ID A, be B dalies, tai turėčiau nustatyti tx_msg.StdId = 1. T.y. aš galvojau, kad jei naudoji ID A ir ID B, tai jie „nusėda“ skirtinguose struktūros laukeliuose. Pasirodo, ne. CAN2A naudojamas StdId laukelis, o CAN2B — ExtId, ilgesnis, laukelis. Jeigu svarbu atskirti ID A nuo ID B, tai juos reikia programiškai išsikrapštyti iš ExtId laukelio.

Žinutei išsiųsti reikalingas kodas:

uint8_t mbox;
mbox = CAN_Transmit(CAN1, &tx_msg);
while(CAN_TransmitStatus(CAN1, mbox) == CAN_TxStatus_Pending);

Cikliukas čia įdėtas specialiai palaukti, kol aparatūra žinutę išsiųs (ar nufeilins). Galima ir nenaudoti, jei žinutės išsiuntimas nėra svarbus: išeis — gerai, neišeis — irgi dzin. STM32 turi tris mailboxus, per kuriuos išsiunčiamos žinutės — kas ten tiksliai yr ir kam to reikia, dar nežinau, neįsigilinau. Įtariu, kad tai toks kaip ir vidinis žinučių paskirstymo mechanizmas. Jei aparatas siunčia žinutes labai intensyviai, tai pirmas mailboxas gali būti užsiėmęs, tada ji dedama į antrą ir taip sukasi roundrobinu. Negarantuoju, kad teisingai tai suprantu, bet atrodo, kad yra būtent taip.

Imtuvo pusėje įsidėjau paprastą funkciją, kuri iškviečiama įvykus CAN priėmimo pertraukimui. Ji tiesiog išspausdina žinutės turinį. Tai aukščiau parodyta žinutė išspausdinama į USART1 va taip:

Received CAN2B message:
        ID: 00000000
        ExtID: 00040002
        ID A: 00000001
        ID B: 00000002
        IDE: 04
        RTR: 00
        DLC: 04
        Data: 01 02 03 04 
        FMI: 00

FMI laukelis nurodo, kelintas filtras buvo panaudotas „įleidžiant“ žinutę. Bet apie CAN filtravimą parašysiu kitą kartą, ten labai įdomus žvėris.

Kaip matote, ID yra nuliukas — ten StdId laukelis. Jo CAN2B atveju nėra. Tačiau galima ID A ir ID B išsikrapštyti iš ExtId, ką aš ten ir padariau.

Įeinančios žinutės pertraukimą apdorojanti funkcija STM32F103C8 procesoriukams vadinasi kažkaip durnai: void USB_LP_CAN1_RX0_IRQHandler(void). Kitiems aparatams su dviem CAN yra lyg ir kitokie pavadinimai. Kad galėtumėte priimti CAN žinutę, reikia minimaliai va tokio kodo:

CanRxMsg rx_msg;
void USB_LP_CAN1_RX0_IRQHandler(void)
{
	CAN_Receive(CAN1, CAN_FIFO0, &rx_msg);
	// Čia kažką galima daryti su rx_msg...
}

Visu kodu čia nesidalinsiu, nemanau, kad yra reikalas. Kaip minėjau, nusisriegiau viską iš STM32 pavyzduko, tik pramečiau Discovery plokščių makrosus lauk. Be to, kadangi šviečiasi kopyraitinis projektas su CAN, tai jau pradėjau jį krapštyti ir kodas, deja, proprietary. Bandysiu šiaip nuotrupomis pasidalint bendram išsilavinimui.

Įdėsiu pabaigai dar didelį kodo kurkulą, kuris inicializuoja CAN magistralę ir pertraukimus:

#define CAN_BAUDRATE  10   /* 10kBps  */

// STM32F103C8
#define CAN                        CAN1
#define RCC_APB2Periph_GPIO_CAN    RCC_APB2Periph_GPIOB
#define GPIO_Remapping_CAN         GPIO_Remap1_CAN1
#define GPIO_CAN                   GPIOB
#define GPIO_Pin_CAN_RX            GPIO_Pin_8
#define GPIO_Pin_CAN_TX            GPIO_Pin_9

CAN_InitTypeDef        can_init;

void init_can_utils(void)
{
	NVIC_InitTypeDef  NVIC_InitStructure;

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

	NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	GPIO_InitTypeDef gpio_init;

	/* GPIO clock enable */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_CAN, ENABLE);

	gpio_init.GPIO_Pin = GPIO_Pin_CAN_RX;
	gpio_init.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIO_CAN, &gpio_init);

	/* Configure CAN pin: TX */
	gpio_init.GPIO_Pin = GPIO_Pin_CAN_TX;
	gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;
	gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIO_CAN, &gpio_init);

	GPIO_PinRemapConfig(GPIO_Remapping_CAN , ENABLE);

	/* CANx Periph clock enable */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);


	/* CAN register init */
	CAN_DeInit(CAN);
	CAN_StructInit(&can_init);

	/* CAN cell init */
	can_init.CAN_TTCM = DISABLE;
	can_init.CAN_ABOM = DISABLE;
	can_init.CAN_AWUM = DISABLE;
	can_init.CAN_NART = DISABLE;
	can_init.CAN_RFLM = DISABLE;
	can_init.CAN_TXFP = DISABLE;
	can_init.CAN_Mode = CAN_Mode_Normal;

	/* CAN Baudrate = 1MBps*/
	can_init.CAN_SJW = CAN_SJW_1tq;
	can_init.CAN_BS1 = CAN_BS1_3tq;
	can_init.CAN_BS2 = CAN_BS2_5tq;

	#if CAN_BAUDRATE == 1000 /* 1MBps */
		can_init.CAN_Prescaler =6;
	#elif CAN_BAUDRATE == 500 /* 500KBps */
		can_init.CAN_Prescaler =12;
	#elif CAN_BAUDRATE == 250 /* 250KBps */
		can_init.CAN_Prescaler =24;
	#elif CAN_BAUDRATE == 125 /* 125KBps */
		can_init.CAN_Prescaler =48;
	#elif  CAN_BAUDRATE == 100 /* 100KBps */
		can_init.CAN_Prescaler =60;
	#elif  CAN_BAUDRATE == 50 /* 50KBps */
		can_init.CAN_Prescaler =120;
	#elif  CAN_BAUDRATE == 20 /* 20KBps */
		can_init.CAN_Prescaler =300;
	#elif  CAN_BAUDRATE == 10 /* 10KBps */
		can_init.CAN_Prescaler =600;
	#else
		 #error "Please select first the CAN Baudrate in Private defines in main.c "
	#endif  /* CAN_BAUDRATE == 1000 */
	CAN_Init(CAN1, &can_init);

	/* CAN filter init */
	can_filter_init.CAN_FilterNumber = 0;
	can_filter_init.CAN_FilterMode = CAN_FilterMode_IdMask;
	can_filter_init.CAN_FilterScale = CAN_FilterScale_32bit;
	can_filter_init.CAN_FilterIdHigh = 0x0000;
	can_filter_init.CAN_FilterIdLow = 0x0000;
	can_filter_init.CAN_FilterMaskIdHigh = 0x0000;
	can_filter_init.CAN_FilterMaskIdLow = 0x0000;
	can_filter_init.CAN_FilterFIFOAssignment = 0;
	can_filter_init.CAN_FilterActivation = ENABLE;
	CAN_FilterInit(&can_filter_init);

	CAN_ITConfig(CAN, CAN_IT_FMP0, ENABLE);
}

Va kaip ir viskas. Filtras čia yra įleidžiantis absoliučiai visas žinutes. Jo būtinai reikia, kitaip imtuvas neveikia. Greitį aš naudosiu patį lėčiausią, nes tinklo topologija bus labai cherova, greičiausiai linijinė persipinanti su žvaigždine. Na, bet ir to užteks per akis.

Advertisements
Komentarai
  1. Julius parašė:

    O ką jūs tokio mandro konstruosite?

    • Darau, Blė parašė:

      Kas susiję su darbiniu poreikiu — tai laidinį jutiklių tinklą. Temperatūra, drėgmė, slėgis, šviesa… gal ir CO₂. Na, kam yra jutikliai, tą ir matuos.

      Asmeniškai, tai jei bus noro ir entuziazmo, galbūt pasidarysiu CAN magistrale valdomą apšvietimą.

      • robchizprojects parašė:

        Aciu uz labai gerai sukramtyta ir pateikta CAN paaiskinima. Beje nelabai supratau kur cia multimaster didziulis poreikis (apsvietimo jungikliai kadanors:). Ir jungiklius galima pulinti pvz. Per modbusa (aisku gerokai padidinus apklausimus). Taip pat standartines irangos pasirinkimas su modbus rtu didesnis (skaitikliai, davikliai, I/O ir t.t.) nebent paciam norisi kru…s. Del TCP (modbus) tai juk visiskas geris kokiam cloudui 🙂 realizuoti pvz ant avietes easy.

        • Darau, Blė parašė:

          Na, pushinimas visada efektyviau už pollinimą 🙂 Be to, pollinimui turi būti centralė kažkokia, kuri diriguotų visam tinklui. Numirs centralė — neliks tinklo. O kai aparatai gali šnekėtis nepriklausomai nuo centralės, tai yra visiškas gėris.

          Kai reikia persiųsti kelis bitukus, tai TCP overheadas yra elementariai per didelis. Kaina irgi — įskaitant kabelius, TCP modulius ir programavimą su jais. Reikia įnagius rinktis pagal konkrečius poreikius.

          • robchiz parašė:

            Taip, del pushinimo sutinku kai tai gyvybishkai svarbu: automobilis, dirbtinio kvepavimo aparatas ir t.t. 🙂 . Be centrales gerai, bet tada kiekvienas atskiras daiktas turi buti pakankamai protingas. Del TCP tai turiu galvoje kad kiekvienas tinkle slave’as pigus modbus rtu bet tinkle 1 modbus/TCP gateway’us kad ir ant ruterio: https://hackaday.io/project/706-wireless-modbus-gateway-for-under-25 . Dar paprasciau: duodi avietei rs-485 adapteri uz 3 baksus ir darai ka nori numirs aviete kita pakeisi 🙂

  2. mechadrake parašė:

    mačiau mikeselectricstuff youtubes kanale vienam video rode kaip floodina duomenim jis rs-485, bet ten naudoja sviesu visokiu valdymui ta 485, daugybei ju pc usb i rs-485 ir i programine dali nesileido.

  3. Dalius parašė:

    Kodėl iš karto TCP/IP? Paprastesniems dalykams gal užtektų žemesnių stack’o lygmenų? Yra tekę naudoti Ethernet’ines aplikacijas, kurios neturi TCP/IP overhead’o..

    • Darau, Blė parašė:

      Nu čia tiesiog, kaip blogas pavyzdys pateikta 🙂 Jo, galima Ethernetą naudot ir žemesniu lygiu, bet vis tiek nematau prasmės, kai yra aparatūrinis CAN…

Parašykite komentarą

Įveskite savo duomenis žemiau arba prisijunkite per socialinį tinklą:

WordPress.com Logo

Jūs komentuojate naudodamiesi savo WordPress.com paskyra. Atsijungti / Keisti )

Twitter picture

Jūs komentuojate naudodamiesi savo Twitter paskyra. Atsijungti / Keisti )

Facebook photo

Jūs komentuojate naudodamiesi savo Facebook paskyra. Atsijungti / Keisti )

Google+ photo

Jūs komentuojate naudodamiesi savo Google+ paskyra. Atsijungti / Keisti )

Connecting to %s