FreeRTOS ir CAN: eilės ir kitas multitaskingas

Posted: 2016-04-01 in Darbeliai
Žymos:, ,

FreeRTOS logotipas | Darau, blėTaigis ką gis, po pertraukėlės ir ilgoko terliojimosi pagaliau pasidariau kreivai šleivai veikiantį pirmą pavyzduką su FreeRTOS, CAN2B pranešimukais ir kitom subtilybėm. Kadangi norisi viską padaryti gražiai su FreeRTOS ir visokiais multitaskingais, tai dar ir prisėdau pasinagrinėti tokį reikalą, kaip eilės (queue). Eilės — tai toks reikalas, kuriuo galima tarp užduočių perdavinėti duomenukus. Arba tarp pertraukimo paprogramių ir užduočių — kas ir svarbu.

Galutiniame projekte mano tikslas yra apdoroti pranešimukų pliūpsnius. T.y. didžiąją laiko dalį CAN magistralė bus tuščia. Bet kartais ten pranešimukai stačiai driokstelės. Ir kuris nors įrenginys gali gauti tokį jam adresuotų pranešimukų pliūpsnį. Be abejo, aš nujaučiu, kad procesoriukas sėkmingai su tais pranešimukais susidorotų ir taip, bet norisi jų dorojimą iškelti iš pertraukimo. Ir va čia FreeRTOS tokią galimybę duoda. Tam naudojamos eilės. Na, eilinis gamintojo-vartotojo uždavinukas (producer-consumer) iš elementariojo daugiagijo programavimo.

Paveiksliukas iš FreeRTOS svetainės, iliustruojantis eilių darbą:

FreeRTOS eilių animacija | Darau, blė

Taipgi labai geras kažkokio entužiazto filmukas apie eiles, žiūrėkite, jei noro yr.

Mano mintys sugulė maždaug taip:

  • Yra eilė atkeliaujančių CAN pranešimukų, rx queue. Ji pildoma iš pertraukimo paprogramės
  • Yra eilė išsiunčiamų CAN pranešimukų, tx queue. Ji pildoma iš bet kur, t.y. kažkur iš įrenginio logikos, kai tik yra poreikis kažką išsiųsti
  • Yra rx queue nuskaitanti FreeRTOS užduotis. Ji pagal ID pasižiūri, kas čia per pranešimukas ir perduoda jį doroti kokiai nors tinkamai funkcijai
  • Yra buka tx queue nuskaitanti užduotis, kuri eilėje radus pranešimuką jį tiesiog išsiunčia su atitinkamomis API priemonėmis

Aš jau turiu susilipdęs šiokį tokį CAN freimworkėlį, kuris dar ganėtinai šleivas, bet jau padeda lengviau užkurti CAN magistralę darbui. Tas freimworkėlis gali inicializuoti CAN periferiją darbui su keliolika nustatymų headerio faile. Taip pat gali iškviesti kokią nors filtrus inicializuojančią paprogramę arba, jei tokios nėra, sukurti standartinį, viską praleidžiantį filtrą. Taip pat sukuria pertraukimo apdorojimo paprogramę ir joje iškviečia externą process_rx(CanRxMsg*). Šitą externą galima susikurti bet kur, kur reikia galutiniam projektui. Be abejo, aš šitą externą pasidariau taip, kad jis dėtų pranešimukus į eilę.

Iš anksto užbėgdamas į priekį galiu pasakyti, kad susidūriau su keliomis problemomis ir kaip jas sprendžiau. Mano tokia naivi mintis buvo abi (tiek priėmimo, tiek išsiuntimo) eiles padaryti globaliais kintamaisiais ir jas naudoti tiesiogiai kituose kodo failuose esančiose paprogramėse. Na, išsiuntimo eilę iš viso naudotų bile kas ir bile kur, kaip jau minėjau. Nu ir nesigavo. Kažkodėl iš globalaus statinio kintamojo nuskaitanti paprogramė pakibdavo ir mano visas FreeRTOS reikalas užšaldavo. Taigi teko tų eilių handlus pasidėti viename kodo faile, jame pasidaryti priėmimo ir išsiuntimo užduotis beigi, aišku, susikurti externą iš eilės nuskaitytų pranešimų dorojimui. Tada viskas susitvarkė ir pradėjo veikti. Na, bent jau nestrigti 🙂

Antra problema, su kuria susidūriau, buvo CAN magistralės inicializacija. Viskas kompiliavosi ir pasileidinėjo, jei neįjungdavau CAN. Vos tik įjungiu CAN, tuoj pat FreeRTOS sustoja. O problema pasirodė banali:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

Čia inicializuojamas pertraukimų prioritetų grupavimas. Tingiu apie tai plėstis dabar, bet esmė, kad jis susipjovė su FreeRTOS pertraukimu ir susitvarkė pakeitus į kitą grupę:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

Taigi tiek. O toliau viskas paprasta. Štai esminis CAN apdorojimas su FreeRTOS:

#include "can_queue.h"

static xQueueHandle canRxQueue;
static xQueueHandle canTxQueue;

void vCanRx(void *p)
{
	CanRxMsg rx;

	while(1) {
		if (xQueueReceive(canRxQueue, &rx, 0)) {
			process_received_from_queue(&rx);
		}
	}
}

void vCanTx(void *p)
{
	CanTxMsg tx;
	uint8_t mbox;

	while(1) {
		if (xQueueReceive(canTxQueue, &tx, 0)) {
			mbox = CAN_Transmit(CAN, &tx);
			while(CAN_TransmitStatus(CAN, mbox) == CAN_TxStatus_Pending);
		}
	}
}

void init_can_queue()
{
	init_can_utils();
	canRxQueue = xQueueCreate(5, sizeof(CanRxMsg));
	canTxQueue = xQueueCreate(5, sizeof(CanTxMsg));
	xTaskCreate(vCanTx, ( const char * ) "CTX", configMINIMAL_STACK_SIZE*2, NULL, tskIDLE_PRIORITY+1, NULL);
	xTaskCreate(vCanRx, ( const char * ) "CRX", configMINIMAL_STACK_SIZE*4, NULL, tskIDLE_PRIORITY+1, NULL);
}

void queue_tx(CanTxMsg *tx)
{
	xQueueSend(canTxQueue, tx, 50);
}

void process_rx(CanRxMsg *rx)
{
	xQueueSendFromISR(canRxQueue, rx, NULL);
}

init_can_utils — mano jau minėta paprogramė, užkurianti CAN magistralę ir pertraukimo apdorojimą. process_rx — būtent šitame kode deklaruotas externas, kuris naudojamas pertraukime. Jis tik padeda pranešimuką į eilę. queue_tx — API visai „išorei“, kad galėtų padėti išsiunčiamus pranešimus į eilę išsiuntimui. Na, ir dvi FreeRTOS užduočių paprogramės: viena atkeliaujančių pranešimukų dorojimui, kita — išeinančių. process_received_from_queue — vėlgi externas, kurį galima deklaruoti kažkur kitur. Headeris irgi paprastutis:

#ifndef CAN_QUEUE_H_
#define CAN_QUEUE_H_

#include "stm32f10x.h"
#include "can_utils.h"

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#ifdef __cplusplus
extern "C" {
#endif

void init_can_queue(void);

void queue_tx(CanTxMsg *);

extern void process_received_from_queue(CanRxMsg *);

#ifdef __cplusplus
} // extern "C"
#endif

#endif /* CAN_QUEUE_H_ */

O paskui ant viso šito smagumo galima kabinti visokią logiką, kuri gauna pranešimus iš eilės ir gali juos dėliot į eilę išsiuntimui.

Dabar šiek tiek apie eilių specifiką FreeRTOS. Kiekviena eilė užima 76 baitus RAM ir plius tiek, kiek reikia joje saugomiems elementams saugoti. Tiek CanRxMsg, tiek CanTxMsg struktūros užima po 20 baitų, o aš jų dedu į eilę po 5 vienetus. Taigi dvi eilės po 176 baitus. Galima būtų šiek tiek paoptimizuoti, nes tos struktūros turi perteklinės ir man nereikalingos informacijos, bet bala nematė. Kol kas poreikio optimizavimui nematau, o jei prireiks — tą padaryti visada bus galima.

Elementus į eilę galima padėti su viena iš dviejų funkcijų: xQueueSendFromISR ir xQueueSend. Apskritai, FreeRTOS daugybė funkcijų turi „paprastą“ variantą, kuris naudojamas užduotyse ir specialų, kurio pavadinimas baigiasi FromISR, naudojamą pertraukimų kode. Mano aukščiau pateiktame kode tas irgi matyti. Konkrečiai padėjimo į eilę funkcijos skiriasi tik tuo, jog paprasta funkcija turi laiko išsibaigimo parametrą. T.y. nurodoma, kiek laiko funkcija gali bandyti padėti elementą į eilę. Laikas nurodomas taktais, o ne kokiomis nors milisekundėmis. Jei tą laiką būtina nurodyti realiu laiku, tai taktus reikia suderinti su portTICK_PERIOD_MS konstanta.

Jei per tą laiką elemento padėti į eilę nepavyksta, jis „numetamas“ ir į eilę nepatenka. Na, čia tuo atveju, jeigu eilė pilna ir funkcija gali kažkiek laukti, kol anoji aptuštės. Tuo tarpu pertraukimo paprogramė turi gudrų trečią parametrą, kur padeda pdTrue arba pdFalse reikšmę pagal tai, kokią įtaką kažkokiai užduočiai turėjo elemento padėjimas į eilę. Jei ten kažkokia aukštesnio prioriteto, nei einamoji, užduotis buvo pažadinta, tai bus padėta pdTrue. Bet sunkiai įsivaizduoju, kaip čia gali būti, kai „einamoji užduotis“ yra pertraukimo apdorojimas. Nelabai aiškus reikalas man. Galima vietoj to parametro rašyti NULL ir nesukti galvos.

Nuskaitymo iš eilės paprogramės irgi yra dvi. Naudoju tiktai xQueueReceive, nes kodas yra paprastoje užduotyje. Šioji paprogramė irgi turi trečią laiko išsekimo parametrą. Jei jis yra ne nulinis, tai užduotis blokuoja visą veiksmą ir laukia, kol eilėje kažkas atsiras. Jei per iškapsėjusį laiką neatsiranda, funkcija grąžina pdFalse ir baigia darbą. Na, o jei šis parametras yra nuliukas, tai funkcija tiesiog neradusi nieko eilėje iš karto baigia darbą. Aš palikau nuliuką, kad man kitų užduočių neblokuotų.

Galimas dar toks variantas. Jei konfigūracijoj yra INCLUDE_vTaskSuspend yra vienetukas, tai trečiajam parametrui nustačius išsekimo laiką portMAX_DELAY užduotis kabos „amžinai“, kol kažkas atsiras eilėje. Kam tokio nustatymo gali reikėti, nelabai įsivaizduoju. Juk mūsų tiklas su FreeRTOS — lygiagrečiai dirbančios užduotys. Aišku, gali būti toks atvejis, kai į eilę įkrentantys elementai tiesiog „varinėja“ visą sistemą, kaip kokie įvykiai. Tuomet tokie nustatymai turi prasmės. Bet mano tikslas ne toks. Aišku, gali būti, kad ir nuliukas man netiks. Pradiniam testavimui tinka ir toks reikalas, bet galbūt vėliau reikės patiuninti nuostatas.

Pabaigai mano can_utils kodas. Headeris:

#ifndef CAN_UTILS_H_
#define CAN_UTILS_H_

// Defaininam, jei parašėm procedūrikę reikalingiems filtrams.
//#define CUSTOM_FILTER

// Pagalbiniai makrosai ID A ir ID B ištraukimui iš ExtId
#define ID_A(id) ((id >> 18) & 0x7FF)
#define ID_B(id) (id & 0x3FFFF)
#define ID_FROM(id) ((id >> 7) & 0x7F)
#define SET_ID_A(id) (id << 18)
#define SET_EXT_ID(msg_type, b_type, from, to) ((msg_type << 18) | ((b_type << 14) & 0x3C000) | ((from << 7) & 0x3F80) | (to & 0x7F))

#ifdef __cplusplus
extern "C" {
#endif

#include "makrosai.h"
#include "stm32f10x.h"

//#define CAN_BAUDRATE  1000      /* 1MBps   */
// #define CAN_BAUDRATE  500  /* 500kBps */
// #define CAN_BAUDRATE  250  /* 250kBps */
// #define CAN_BAUDRATE  125  /* 125kBps */
// #define CAN_BAUDRATE  100  /* 100kBps */
// #define CAN_BAUDRATE  50   /* 50kBps  */
// #define CAN_BAUDRATE  20   /* 20kBps  */
#define CAN_BAUDRATE  10   /* 10kBps  */

// STM32F103C8
#define CAN                          CAN1
#define RCC_APB2Periph_GPIO_CAN    RCC_APB2Periph_GPIOB
#define RCC_APB1Periph_CAN         RCC_APB1Periph_CAN1
#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

void init_can_utils(void);

void disable_all_filters(void);

extern void process_rx(CanRxMsg *rx_msg);

#ifdef __cplusplus
}
#endif

#endif /* CAN_UTILS_H_ */</pre>
Ir implementacija:
<pre>#include "can_utils.h"

#define FMR_FINIT    ((uint32_t)0x00000001) /* Filter init mode */

#ifdef CUSTOM_FILTER
extern void init_custom_filters(void);
#endif

CanRxMsg rx_msg;
CAN_InitTypeDef        can_init;
CAN_FilterInitTypeDef  can_filter_init;

#ifndef STM32F10X_CL
void USB_LP_CAN1_RX0_IRQHandler(void)
#else
void CAN1_RX0_IRQHandler(void)
#endif
{
	CAN_Receive(CAN, CAN_FIFO0, &rx_msg);
	process_rx(&rx_msg); // Externas atėjusių mesedžiukų apdorojimui
}

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 = 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(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);
}

void disable_all_filters()
{
	uint32_t filter = 0x0FFFFF;

	CAN->FMR  |= FMR_FINIT;
	CAN->FA1R &= ~filter;
	CAN->FMR  &= ~FMR_FINIT;
}

Kodas tik pusiau universalus. Kai kurios dalys su defainais pritaikytos ir platesniam procesorių ratui, bet tik kai kurios. Kitas reikėtų tiuninti.

Tai tiek šiandienai.

Reklama

Parašykite komentarą

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

WordPress.com Logo

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

Google photo

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

Twitter picture

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

Facebook photo

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

Connecting to %s

Brukalų kiekiui sumažinti šis tinklalapis naudoja Akismet. Sužinokite, kaip apdorojami Jūsų komentarų duomenys.