Nachdem ich mich jetzt zwei Tage lang mit dem CAN Bus am o.g. Discovery
Board herumgeschlagen habe, möchte ich meine Erkenntnisse gerne mit euch
teilen... gleich vorweg: es funktioniert... wenn auch mit einem faden
Beigeschmack.
Das Setting
===========
Das STM32F429I Discovery Board ist ein etwas kräftigeres Board im
Vergleich zum STM32F4-Discovery. Der Prozessor selbst ist mit 180 MHz
etwas flotter (das STM32F4-Disco läuft mit 168 MHz), die Peripherie ist
mit dem LCD-Touch Display etwas umfangreicher bestückt.
Von mir eingesetzt wird das Board zu Forschungszwecken in der
Optimalsteuerung, wobei am PC eine Regelstrecke simuliert wird, der
Regler sitzt auf dem Cortex-M4. Die Kommunikation erfolgt über CAN,
wobei am PC der Low-Budget OpenSource Adapter "USBtin" als CAN-zu-USB
Interface unter Linux betrieben wird (Super Teil! Danke, Thomas Fischl!)
Der STM32F429 des Boards verfügt über zwei native CAN Controller.
Verdrahtet wird das RX/TX des Prozessors mit einem MCP2551 Transciever,
der (das gibt manchmal Grund zur Diskussion) einfach mit RS ohne
Widerstand auf Masse geschaltet ist.
Das Problem
===========
Die Umfangreiche Peripherie ist hier leider recht hinderlich. Beide CAN
Ports werden mit alternativen Beschaltungen entweder für das Display
(CAN1) oder für den USB to go Anschluss (CAN2) verwendet. Der naive
Ansatz: wir brauchen den USB Port nicht, also klemmen wir den MCP2551
einfach auf die GPIOB Pins 12/13 und legen los.
Was bei einigen von Euch wahrscheinlich gleich ein Augenrollen
verursacht, erschien mir als fachfremde Person (Technomathematik)
vorerst logisch. Die Quittung kam aber prompt. Das serielle Terminal am
PC bleibt schwarz. Wie soll es auch anders, wenn das Signal am TX-Pin so
genuschelt aussieht, wie auf dem ersten Bild? Ganz offensichtlich hängen
da in der USB-Peripherie ein paar Widerstände und Kondensatoren herum,
die aus den schönen digitalen Flanken Matschebrei machen.
Die Notlösung
=============
Schweren Herzens trennen wir uns also von der Funktion des LCD-Displays
und versuchen das gleiche mit dem CAN1. Da die beiden Pins (GPIOA 11/12)
auf dem Board als Signalleitungen für rote Bildinformationen verwendet
werden kann (das Board wirbt mit mehreren Mio. Pixel pro Sekunde),
erwarten wir ein knackiges Signal. Und tatsächlich: Das Signal passt
(zweites Bild)... für den Preis, dass das LCD-Display nicht verwendet
werden kann, was den Sinn des teureren STM32F429I Boards irgendwie in
Frage stellt.
edit: In Kommentaren wurde angeregt, die CAN1-Alternative auf D0/1 zu
verwenden. Die Ports sind mit dem SDRAM verbunden, was man ja nicht
unbedingt brauche. Theoretisch ließe sich es sicherlich irgendwie
hintricksen, wenn man sich die Zeit nehmen will, einen eigenen Display
Treiber zu bauen. Wenn man aber mit möglichst wenig Aufwand die
Peripherie-Bibliotheken des Boards verwenden möchte, klappt diese Lösung
leider nicht, da diese das SDRAM als Frame Buffer verwenden. Weitere
Portalternativen existieren im LQFP144 Format leider nicht.
Feintuning
==========
Wie auch immer. Was noch zu erwähnen ist: Das Board ist schneller
getaktet, als das STM32F4-Disco. Viele Beispiele im Netz basieren jedoch
auf diesem 168 MHz langsameren Board, weshalb wir das Timing des CAN
Busses neu berechnen müssen... Der ABP1 Bus ist auf diesem Board mit
einem Prescaler von vier auf 45 MHz getaktet. Die Examples sind für 42
MHz geschrieben.
Wir berechnen also das Timing für das Board neu. Nehmen wir gleich
1MBit/s auf dem CAN-Bus (wenn schon, denn schon...). Ein Bit braucht
also 45*10^6 / 1*10^6 = 45 Takte. Es gilt also
45 Takte = CAN_Prescaler * (1 + CAN_BS1 + CAN_BS2)
Ein CAN_Prescaler von 5 macht sich hier ganz gut. Bleiben noch
CAN_BS1 + CAN_BS2 = 8
übrig. Damit das Sampling in der hinteren Hälfte stattfindet teilen wir
die acht auf
CAN_BS1 = 5
CAN_BS2 = 3
auf. Und voila... USBtin und Board tauschen munter CAN-Frames aus.
Leider ohne Display, weshalb der abschließende Erfolg mithilfe der
beiden User-LEDs gekennzeichnet werden musste.
edit: In Kommentaren wurde angeregt, den Takt des Prozessors einfach in
der SystemInit anzupassen. Das geht natürlich auch. Ich war so bequem,
einfach die SystemInit des Touch-Panel Beispiels zu verwenden welche den
Prozessor mit maximalen 180 MHz taktet. Die selektive Anpassung des
CAN-Bus Takts ist hier sinnvoll, da andere Systemkomponenten davon nicht
betroffen sind (im Gegensatz zum Ändern des Systemtakts).
Etwas Quellcode für Copy-Paster
===============================
Konfiguration der Ports:
1 | GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_CAN1);
|
2 | GPIO_PinAFConfig(GPIOA, GPIO_PinSource12, GPIO_AF_CAN1);
|
3 |
|
4 | GPIO_InitTypeDef GPIO_InitStructure;
|
5 |
|
6 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
|
7 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
|
8 | GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
9 | GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
|
10 | GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
|
11 | GPIO_Init(GPIOA, &GPIO_InitStructure);
|
CAN Controller initialisieren
1 | CAN_InitTypeDef CAN_InitStructure;
|
2 |
|
3 | CAN_DeInit(CAN1);
|
4 |
|
5 | CAN_InitStructure.CAN_TTCM = DISABLE;
|
6 | CAN_InitStructure.CAN_ABOM = DISABLE;
|
7 | CAN_InitStructure.CAN_AWUM = DISABLE;
|
8 | CAN_InitStructure.CAN_NART = DISABLE;
|
9 | CAN_InitStructure.CAN_RFLM = DISABLE;
|
10 | CAN_InitStructure.CAN_TXFP = DISABLE;
|
11 | CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
|
12 | CAN_InitStructure.CAN_SJW = CAN_SJW_1tq
|
13 | CAN_InitStructure.CAN_BS1 = CAN_BS1_5tq;
|
14 | CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
|
15 | CAN_InitStructure.CAN_Prescaler = 5; // Baudrate 1Mbaud
|
16 | if (CAN_Init(CAN1, &CAN_InitStructure)) {
|
17 | GPIOG->BSRRL = GPIO_Pin_13; // Lichtle an, wenn's geklappt hat
|
18 | }
|
CAN Filter soll alles rein lassen
1 | CAN_FilterInitTypeDef CAN_FilterInitStructure;
|
2 |
|
3 | CAN_FilterInitStructure.CAN_FilterNumber = 0;
|
4 | CAN_FilterInitStructure.CAN_FilterFIFOAssignment = 0;
|
5 | CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
|
6 | CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
|
7 | CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
|
8 | CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
|
9 | CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
|
10 | CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
|
11 | CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
|
12 | CAN_FilterInit(&CAN_FilterInitStructure);
|
Bequemen Frame-Empfang per Interrupt initialisieren
1 | NVIC_InitTypeDef NVIC_InitStructure;
|
2 |
|
3 | NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX0_IRQn;
|
4 | NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0;
|
5 | NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0;
|
6 | NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
|
7 | NVIC_Init(&NVIC_InitStructure);
|
8 |
|
9 | CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
|
... und zu guter Letzt: Interrupt implementieren
1 | void CAN1_RX0_IRQHandler(void)
|
2 | {
|
3 | GPIOG->BSRRL = GPIO_Pin_14; // rotes LED an
|
4 |
|
5 | CanRxMsg RxMessage;
|
6 | CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
|
7 | /* .... Nachricht behandeln .... */
|
8 | }
|
Fazit: Es geht, aber es bleibt fraglich, ob man nicht lieber das etwas
kleinere STM32F4-Discovery Board verwenden sollte, wenn man nicht ohne
CAN kann. Es kostet weniger und ist etwas leichter...
Trotzdem... viel Erfolg damit!