Lawicel/SLCAN: kaip pasidaryti CAN adapterį

Posted: 2018-06-14 in Darbeliai
Žymos:, , ,

Mano šviesų tinkle prasidėjo keisti gliukai. Iš pradžių galvojau, kad mano nauji firmwarių eksperimentai kalti. Paskui pasirodė, kad nebuvau įdėjęs į firmwares apsaugų, jei CAN žinutės išsiuntimas užvėluoja. Paskui dar pasirodė, kad kai kurie tinklo mazgai iš viso „išsijungia“ (bus-off būsena)Atradau, kad galima liepti valdikliui iš bus-off išlipti pačiam. Bet kodėl tai iš viso prasidėjo? Juk pusę metų buvo viskas gerai. Ir štai, prieš kelias dienas mano šviesų tinklas iš viso dingo iš eterio. Gal susiję su pražaibavusia audra, o gal ir ne.

Pasirodė, kad greičiausiai nusvilo Avietės Canberry skydelis 😦 Pabandžiau ir tą pačią Avietę, ir atsarginę su skydeliu, pabandžiau sujungti ir į kitą tinklą (o gal kiti mazgai durniuoja, ką aš žinau). Visgi kaltė liko Avietės pusėje, įvyko ten turbūt mikroschemoj gedimas.

Skydelį turiu tik vieną — jie ganėtinai brangūs. AliExpress galima nusipirkti paprastesnių ir visai pigių adapterių, tik jie ant Avietės neužsideda, reikia laidukais prijungti, kvarcą pakeisti ir dar ten kitokių hakų padaryti. Nepatogums biškį. Na, bet vis tiek užsisakiau. Biškį liūdesys, aišku, kad negaliu šviesų tam kartui telefonu valdyt.

Kilo mintis: o gal pasidaryt per USART adapterį į CAN? Spausdintų sau žinutes arba priimtų nurodymus, kokias išsiųst? Na, bet reikėtų kodyti ir firmwarę, ir Avietės serviso pusę.

Tuo pačiu truputuką pasibrauzijau aplink, kas gero yra apskritai CAN adapterių pasaulyje. Gal koks CAN-USB yra? Ir netyčia užsiroviau ant Lawicel CAN232 adapterio. Pasidarė juokinga: tas adapteris įgyvendina būtent mano sugalvotą principą: verčia CAN žinutes į apsibrėžtą ASCII HEX tekstą ir siunčia jį per USART (tiksliau, netgi RS232) į kompiuterį. Taip pat per USART priima tekstą ir jį paverčia CAN žinute. Yra ten ir daugiau navarotų, bet šitas pagrindinis. O Linux kernelis turi modulį šitam reikalui ir gali sukurti ant jo virtualų CAN tinklo adapterį. Va čia tai bajeris.

Pasiieškojęs radau ne tik Arduino, bet ir tiesiogiai STM32F103 procesoriukams sukurtos progaminės įrangos, įgyvendinančios Lawicel protokolą (dar vadinamą slcan). Protokolas yra atviras. Patys Lawicel netgi negamina jokių draiverių: štai jums mūsų divaisas, o komandų parsinimą ir gamybą pasidarykit patys, kaip jums patogiau.

Kol laukiu MCP2515 adapterių iš kinų, pagalvojau, kad turėti atsarginį variantą per USART gal ir visai neblogai. Tik tiek, kad internete gulinčių adapterių kodas nelabai patiko. Kai kas lengvai pataisoma (pvz. žiedinio buferio rodyklės postūmis per dalybą — net purto pamačius), kai kas kiek sudėtingiau (uber greitas bitukų vertimas į hex tekstą), tad nusprendžiau remdamasis esamu kodu pasirašyti savo variantą. Be abejo, išnaudoti DMA, net ir žinučių kopijavimui į laikiną žiedinį buferį.

CAN adapterio idėjos:

  • Kodas turi būti labiau greitas, nei kompaktiškas: 1 Mb/s tinkle tai svarbu.
  • 20 kB RAM: maksimaliai išnaudoti lookup lentukes visoms konvertavimo operacijoms.
  • Naudoti DMA spausdinimui į USART, atlaisvinti procesorių.
  • Naudoti DMA žinučių kopijavimui į žiedinį buferį apdorojimui.
  • Automatinis atsistatymas bus-off atveju (ABOM).
  • Jei paveš, naudoti nestandartinį USART baud rate — ~1 Mb/s.
  • Įgyvendinti tik reikalingą Lawicel protokolo dalį.

Visas Lawicel protokolas aprašytas šioje nuorodoje į PDF failą.

Truputukas teorijos apie žiedinius buferius

Tai yra tiesiog masyvai, kuriuose įrašai įdedami vienas po kito. Kai pasiekiama masyvo pabaiga, jis pradedamas pildyti nuo pradžių. Kodijant paprastuoju būdu reikia patikrinti ar jau pasiekta ta pabaiga ir einamojį indeksą „nunulinti“. Bet yra ir kitų metodų. Jeigu žiedinio buferio narių skaičius yra dvejeto laipsnis (2, 4, 16, 32, 64 etc.), tai indeksą galima nesukant galvos tiesiog didinti vienetu ir imti jo liekaną nuo buferio dydžio. Čia navarotas tame, kad jei indeksas po padidinimo tampa didesnis už buferio dydį, liekana „nusinulina“. Sakykim, 64 elementų buferiui tai bus šitaip:

 i = (i+1) % 64;

Kai i pasieks 64, jis dėl liekanos taps vėl nuliuku.

Bet taip yra baisiai blogai. Nes liekana (mod operacija) yra vis tiek dalyba. O dalyba procesoriuose yra viena iš lėčiausių operacijų, paprastai įgyvendinama programiškai. Mikrovaldikliuose ypač neparanki. Man keista, bet programuotojai dažnai patingi apsirašyti antrą konstantą, kuri yra vienetu mažesnė už žiedinio buferio talpą. Nes tada galima išsisukti be dalybos, su and operacija, kuri yra vieno procesoriaus takto greičio. Ir tada mūsų indeksas bus skaičiuojamas šitaip:

 i = (i+1) & 63;

Nes štai čia yra ta pati mod operacija, tik kiek kitaip. Tik nepamirškite: buferio elementų kiekis turi būti dvejeto laipsnio! Beje, šią matematiką galima pritaikyti gausybei ir kitų atvejų, kad ir laikmačio persivertimui įvertinti, skaičiuoti periodus ir panašiai — jei tik dvejeto laipsnis tinka.

Kitas labai galingas navarotas su dvejeto laipsniu yra neapdorotų elementų skaičiaus radimas. Žiediniame buferyje minimaliai reikalingi du indeksai: paskutinio įrašyto elemento ir paskutinio apdoroto. Kai šie indeksai lygūs, buferis laikomas tuščiu. Kai įrašyto elemento indeksas didesnis už paskutinio apdoroto — buferyje kažkas yra. Deja, šito skirtumo apskaičiavimas pasidaro problema, kai buferio indeksas „persiverčia“ ir buferis vėl pradeda būti pildomas nuo pradžios, o tuo tarpu jo tuštinimas dar „nepersivertė“. Šioje vietoje galima sužaisti su tuo, kaip kompiuteryje atvaizduojami neigiami skaičiai. Svarbu, kad indeksų tipai būtų unsigned ir tada neapdorotų elementų skaičius bus toks:

(write_pos - read_pos) & 63

Vėlgi and operacija, „nuvalanti“ nereikalingus bitukus ir netgi neigiamą rezultatą paverčianti reikalingu skaičiumi.

Na, tai šitą matematiką aš naudosiu. Ji efektyvi ir greita.

Spartus konvertavimas į HEX ir atgal

Lawicel protokolas CAN žinutes verčia šešioliktainiu tekstu. Tai labai gerai, nes šešioliktainė sistema yra visiškai tiesiogiai atvaizduojama į dvejetainę ir atvirkščiai.

Tad, sakykim, naudoti kokį nors sprintf verčiant skaičiukus į šešioliktainius yra visiškai bereikalingas procesoriaus apkrovimas. Visos printf šeimos funkcijos yra „sunkios“ ir net geriausi kompiliatoriaus optimizavimai čia nepagelbės. Kadangi atminties yra daug, tai pats greičiausias tiesioginio atvaizdavimo metodas, be abejo, yra lookup lentukės: masyvai, kurių indeksas yra konvertuojamas skaičius, o reikšmė — atvaizdavimo simbolis. Versti skaičiuką į šešioliktainį galima su šešiolikos narių lentele:

char bin2hex[16] = { '0', '1', '2',... 'E', 'F' };
...
str++ = bin2hex[byte << 4];
str++ = bin2hex[byte &  15];

Tikiuosi, čia aišku, kame reikalas. Imamas baitas, kurį reikia atvaizduoti, kaip HEX eilutę, su nuliuku priekyje, jei skaičius mažesnis už 16. Na, “%02X“ printf analogas.

Tačiau yra įmanomas ir dar greitesnis variantas. Galime naudoti ne 16 baitų lookup lentukę pusbaičiui, o dvi 256 baitų lentukes visam baitui. Aišku, čia jau biškį „pereboras“, bet tokį variantą galima turėti galvoje, jei prireiktų mirtinai optimizuoti ir sutaupyti dar porą procesoriaus taktų. O taupant atmintį galima panaudoti paprastą sudėtį ASCII simbolių generavimui, bet reikia papildomo tikrinimo, nes ‘A’ neina iš karto po ‘9’, yra skylė. Todėl pagal lentukę visgi greičiau. Vertimas atgal kiek problematiškesnis, bet galima pasidaryti tiesiog didesnį masyvą su septyniais tarpiniais tuščiais nariais (nes tiek yra kitų simbolių tarp ‘9’ ir ‘A’):

uint32_t hex2bin[] = {
		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Čia simboliai, esantys tarpe tarp '9' ir 'A'
		0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};

Ir tada šešioliktainis skaičiukas konvertuojamas iš teksto tiesiog atėmus ‘0’ ASCII kodą.

Buferizavimas

Galvojau kurį laiką, kiek ir kokių buferių reikės, bei kaip jie „santykiaus“. Kad tiek CAN, tiek USART magistralės būtų kuo daugiau laiko laisvesnės, mintis gavosi maždaug tokia:

  • CAN žinučių buferis, 64 vienetams. Ateina žinutė ir DMA ją nukopijuoja į einamąjį buferio elementą. Pertraukime duodama komanda, o kai DMA atlieka savo darbą — įvyksta kitas pertraukimas.
  • HEX formato žinučių buferis. Atskira paprogramė žiūri, kada CAN buferyje atsiranda nauja žinutė. Tada ją performatuoja ir padeda į HEX buferį.
  • Kita paprogramė pasižiūri, kada HEX buferyje atsiranda tekstas, tada jį paima ir per USART išspausdina. Tiksliau, duoda komandą kitam DMA kanalui, kad spausdintų ir toliau dinderį muša.

Nuskaitymui panašiai, bet paprasčiau:

  • Vienas baitų buferis nuskaitymui, kas per USART ateina, iki ‘\r’ simbolio. Jei buferis persipildo, tai jį išvalo, cypteli ‘\a’ simboliuku.
  • Radus ‘\r’ užsukamas parseris, kuris analizuoja, kas čia atėjo. Tiesa, ‘\n’ irgi priima, kaip komandos pabaigą, dėl visa ko.
  • Jei parseryje randama žinutė, ji, baigus parsinimą padedama į CAN FIFO išsiuntimui. Tiesa, FIFO rimtai nenaudoju, tik vieną mailbox’ą. Kada nors paspartinsiu iki visų trijų mailbox’ų naudojimo.
  • Parseriui baigus darbą nuskaitymo buferis išvalomas ir vėl laukiama, kas įbyrės į vidų.

USART greitis

Mano CAN magistralė sukasi maksimaliu 1 Mb/s greičiu. USART, tuo tarpu, nėra toks jau spartus, nors sako, kad FT232RL mikroschema iki 4,5 Mb/s pavaro. Nemačiau tokių greičių 🙂

Maksimali sparta, per kurią mano kompiuteris ir STM32F103 procesoriukas per FT232RL mikroschemą sugebėjo susikalbėti, gavosi 460800 bodų. Teoriškai įtraukus CTS, RTS ir CK kojytes, t.y. vietoj paprasto RX/TX UART’o padarius tikrą USARTą sparta tikrai turėtų pakilti, bet kadangi CAN adapteriui naudojau vieną atlikusią plokštę (ir ne visai tam skirtą), tai šių kojyčių neišbandžiau. Lieka ateičiai.

Įgyvendinimas

Taigi per kelis vakarus susikurpiau firmwarę, kuri įgyvendina dalį Lawicel protokolo:

  • Priima ir siunčia CAN žinutes, CAN2A ir CAN2B
  • Parodo versijos numerį
  • Parodo serijinį numerį
  • Prideda kas 60 s persiverčiantį milisekundinį gautos žinutės timestamp’ą (jei įjungta)

Ko nedaro:

  • Negalima pakeisti nei UART, nei CAN spartos
  • Negalima įjungti POLL režimo
  • Negalima išjungti/įjungti CAN: magistralė įjungta tiesiog visada
  • Negalima įjungti filtrų
  • Negalima nuskaityti klaidų statuso

Nu ir dar ten kelių fyčių nėra, nes man nereikia. Bet šiaip man šis „projektas“ patiko, aš jį vėliau kiek patobulinsiu.

Kodijimas su CMSIS

Aš vis stengiuosi vengti HAL ar Standard Peripherals bibliotekų, kurios turėtų „palengvinti“ programavimą su STM32 mikrovaldikliais. Kiek išeina, naudoju CMSIS. Kaip turbūt jau žinote, ten toks beveik asembleris.

Aš šiaip tikrai neteigiu, kad visada reikia lįsti į registrus ir asemblerius. Gyvenimas trumpas, kodyti reikia greičiau, procesoriai yra spartūs. Bet aš pats mėgstu daugiau pakrapštyti galvą (ir nosį) bei pasigilinti, kaip kas kur veikia. O kai rašau kodą, kuris yra beveik „produkcinis“, norisi jį padaryti kompaktiškesnį.

Turėjau tokį CAN magistralės inicializavimo „utėlyną“ su Standard Peripherals:

void init_can_utils(void)
{
	NVIC_InitTypeDef  NVIC_InitStructure;

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

	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 */
	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_CAN, ENABLE);

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

	/* CAN cell init */
	can_init.CAN_TTCM = DISABLE;
	can_init.CAN_ABOM = ENABLE;
	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 =4;
	#elif CAN_BAUDRATE == 500 /* 500KBps */
		can_init.CAN_Prescaler =8;
	#elif CAN_BAUDRATE == 250 /* 250KBps */
		can_init.CAN_Prescaler =16;
	#elif CAN_BAUDRATE == 125 /* 125KBps */
		can_init.CAN_Prescaler =32;
	#elif  CAN_BAUDRATE == 100 /* 100KBps */
		can_init.CAN_Prescaler =40;
	#elif  CAN_BAUDRATE == 50 /* 50KBps */
		can_init.CAN_Prescaler =80;
	#elif  CAN_BAUDRATE == 20 /* 20KBps */
		can_init.CAN_Prescaler =200;
	#elif  CAN_BAUDRATE == 10 /* 10KBps */
		can_init.CAN_Prescaler =400;
	#else
		 #error "Please select first the CAN Baudrate in Private defines in main.c "
	#endif  /* CAN_BAUDRATE == 1000 */
	CAN_Init(CAN, &can_init);

	/* CAN filter init */
	#ifdef CUSTOM_FILTER
	init_custom_filters();
	#else
	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);
	#endif

	CAN_ITConfig(CAN, CAN_IT_FMP0, ENABLE);
}

Ir užsimaniau jį pilnai perrašyti naudodamasis vien CMSIS. Nelabai žinau, kam to reikėjo, bet norėjosi ir viskas. Persiskaičiau daug tuzinų kartų reikiamas RM0008 dalis. Ir po vieną kabaliuką pradėjau keisti, palaipsniui. Na, kad būtų aišku, kur klaida, jei pradeda neveikti — o tų klaidų būna, kaip kitaip. Ir po vieną dalį keisdamas vis žiūrėjau, kaip mažėja firmwarės apimtys. Žemiau išrašas, kas buvo pakeista ir kiek tai sumažino firmwarę nuo prieš tai buvusios:

arm-none-eabi-size --format=berkeley "ARM_F103_SLCAN.elf"
---- NVIC config ----
   6723	    176	    412	   7311	   1c8f	ARM_F103_SLCAN.elf
---- GPIO ----
   6263	    176	    412	   6851	   1ac3	ARM_F103_SLCAN.elf
---- Remap CAN pins ----
   5763	    176	    412	   6351	   18cf	ARM_F103_SLCAN.elf
---- Init CAN ----
   5095	    176	    412	   5683	   1633	ARM_F103_SLCAN.elf
---- Enable CAN interrupt ----
   4947	    176	    412	   5535	   159f	ARM_F103_SLCAN.elf
---- Init CAN filter ----
   4679	    176	    412	   5267	   1493	ARM_F103_SLCAN.elf
Finished building: ARM_F103_SLCAN.siz

Neišsisaugojau tik pilno dydžio, kur ir NVIC buvo inicializuojamas. Bet kokiu atveju, firmwarė sumažėjo dviem kilobaitais. O kodas gavosi gerokai mažiau skaitomas ir suvokiamas, bet va toks:

int open_can()
{
	// Configure NVIC for CAN
	SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup_1;
	NVIC->IP[CAN1_RX_IRQ_CH] = 0x00;
	NVIC->ISER[CAN1_RX_IRQ_CH >> 0x05] = (uint32_t) 0x01 << (CAN1_RX_IRQ_CH & (uint8_t)0x1F);

	// Configure IOs for CAN
	RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // Enable Alternative IO
	RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; // Enable CAN clock
	RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // Enable GPIOB pins for CAN1

	GPIOB->CRH &= ~(GPIO_CRH_CNF8 | GPIO_CRH_MODE8 | GPIO_CRH_CNF9 | GPIO_CRH_MODE9); // Reset pin8 and 9

	GPIOB->CRH |= GPIO_CRH_CNF8_1; // CNF b10: input pull up/down, mode 00: input
	GPIOB->BSRR |= GPIO_BSRR_BS8; // Pull up

	GPIOB->CRH |= GPIO_CRH_CNF9_1| GPIO_CRH_MODE9; // CNF b10: alternative push-pull, mode 11: 50 MHz output

	AFIO->MAPR &= ~(AFIO_MAPR_CAN_REMAP);
	AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_1; // b10, CAN_RX PB8, CAN_TX PB9.

	// Configure CAN peripheral
	CAN1->MCR = CAN_MCR_RESET;
	CAN1->MCR &= ~CAN_MCR_SLEEP; // Exit from sleep
	CAN1->MCR |= CAN_MCR_INRQ; // Request initialisation

	uint32_t timeout = INAK_TIMEOUT;

	while (((CAN1->MSR & CAN_MSR_INAK) == 0) && (--timeout));
	if (timeout == 0) {
		CAN1->MCR &= ~(uint32_t)CAN_MCR_INRQ;// Leave initialisation
		return -1; // Initialisation failed
	}

	CAN1->MCR |= CAN_CFG_FLAGS;
	CAN1->BTR = cfg_can_mode
			| cfg_can_sjw_quanta
			| cfg_can_ts1_quanta
			| cfg_can_ts2_quanta
			| cfg_can_prescaler;

	CAN1->MCR &= ~(uint32_t)CAN_MCR_INRQ;// Leave initialisation

	timeout = INAK_TIMEOUT;
	while (((CAN1->MSR & CAN_MSR_INAK) == 0) && (--timeout));
	if (timeout == 0) {
		return -2; // Initialisation finalisation failed
	}

	// Enable filter 0 to pass through all messages
	CAN1->FMR |= CAN_FMR_FINIT; // Enter initialisation

	CAN1->FA1R &= ~(uint32_t)CAN_FA1R_FACT0; // Deactivate filter
	CAN1->FS1R |= CAN_FS1R_FSC0; // Scale 32 bit
	CAN1->sFilterRegister[0].FR1 = 0x00; // No filter
	CAN1->sFilterRegister[0].FR2 = 0x00;
	CAN1->FM1R &= ~(uint32_t)CAN_FM1R_FBM0; // Mask mode
	CAN1->FFA1R &= (uint32_t)CAN_FFA1R_FFA0; // FIFO0 assignment
	CAN1->FA1R |= CAN_FA1R_FACT0; // Activate filter

	CAN1->FMR &= ~CAN_FMR_FINIT; // Exit initialisation

	// Enable CAN interrupt
	CAN1->IER |= CAN_IER_FMPIE0;

	// Configure DMA NVIC
	NVIC->IP[CAN_RX_DMA_IRQ] = 0x00;
	NVIC->ISER[CAN_RX_DMA_IRQ >> 0x05] = (uint32_t) 0x01 << (CAN_RX_DMA_IRQ & (uint8_t)0x1F);
	// Configure DMA for Mem2mem
	RCC->AHBENR |= CAN_RX_DMA_CLK;
	CAN_RX_DMA_CH->CCR = 0;
	// Mem2mem, High priority, Increment memory source and destination buffer, transfer complete interrupt enabled
	CAN_RX_DMA_CH->CCR = (DMA_CCR1_MEM2MEM | DMA_CCR1_PL_1 | DMA_CCR1_MINC | DMA_CCR1_PINC | DMA_CCR1_TCIE);

	return 0;
}

Ne visai ta vieta, kur būtina baisiai taupyti, bet vis tiek smagu. Išmetus (arba, tiksliau, atjungus) visą debuginimą firmwarė sulindo į nepilnus 4 kB. printf tipo debugai yra labai labai ėdrūs atminčiai.

Iš Standard Peripherals pasilikau CAN_Receive ir CAN_Transmit paprogrames, jos parašytos patogiai ir nėra per daug apkrautos, o visokie assert per kompiliatorių išsijungia.

Surinkimas ir paleidimas

Aš panaudojau vieną iš savo „išmanaus“ apšvietimo plokščių, kurios daugiau niekam nenaudojamos. Bet jei norėtumėte surinkti šitą daiktą patys, jums reikės:

  • STM32F103C6 procesoriuko devboardo. Mano mėgstamiausias — Blue Pill.
  • SN65HVD230 CAN transiverio, yra papigiai AliExpresse break-out-boardų.
  • FT232RL mikroschema pagrįsto USB-UART adapterio. Jungiamas prie USART1.
  • Laidukų, be abejo, breadboardo ar panašiai.

Pastaba: dėl mano poreikių CAN-RX ir CAN-TX yra perkelti (remapinti) ant PB8 ir PB9.

Surinkus, įrašius firmwarę ir prijungus prie kompiuterio su Linux reikia sukurti tinklo adapterį. Tam paleidžiamas slcand demonas, o paskui pakuriamas ir pats adapteris (interfeisas):

sudo slcand -S 460800 -t sw /dev/ttyUSB0 can0
sudo ip link set up can0

Nu ir viskas, galima su candump can0 stebėti, kas CAN magistralėje vyksta, o su cansend patiems siuntinėti žinutes.

Kadangi Canberry nuduso, tai kol kas prijungiau šitą reikalą bile kaip:

Savadarbis CAN magistralės adapteris su ARM STM32F103 procesoriumi | Darau, blė

Bet veikia. Tiesa, jeigu pabyra į tinklą labai daug žinučių, nespėja jų išspausdinti į USART1: persipildo buferis. Bet čia retas atvejis, pasireiškiantis, kai aš naudoju tokį durną savo išmislą: CAN terminalą. To nepaveža. Pradžiai bandysiu didinti buferį, o ir gal terminalą paoptimizuoti, nes ans dabar vieną simbolį viena žinute ir siunčia, o didelius gabalus galėtų ir visais 8 baitais išsiųst.

Pabaigai šito daikto kodas Githube: https://github.com/darauble/lawicel-slcan

Reklama
Komentarai
  1. Linas parašė:

    RingBuffer vertimas į LT ganėtinai keistai skamba 🙂 kaip koks žiedinis raumuo 😀

  2. Aidas parašė:

    Mano supratimu, trūksta dviejų žvaigždučių pirmame kodo gabaliuke skyrelyje „Spartus konvertavimas į HEX ir atgal“ — abu kartus prieš str.

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 )

Google+ photo

Jūs komentuojate naudodamiesi savo Google+ 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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.