Na stronie głównej projektu NET MF for STM32 nie znajdziemy już plików potrzebnych do uruchomienia STM32F4Discovery opisanych w Przygotowanie STM32F4Discovery do pracy z NETMF. Możemy je za to pobrać z wersji historycznej strony głównej: http://netmf4stm32.codeplex.com/wikipage?version=29
3.02.2014
18.01.2014
.NET Micro Framework na STM32F429
Jestem w szoku. STMicroelectronics, na swojej stronie, udostępnił paczkę z .NET Micro Framework do płytki STM32F429 Discovery, o której wcześniej wspominałem (Discovery ma przyszłość).
W paczce STSW-STM32141 znajdują się pliki hex (tinybooter, CLR i config) do załadowania, sterownik USB (WinUSB) oraz przykłady w C#. Między innymi obsługa LCD, żyroskopu i ekranu dotykowego.Całość została przygotowana dla NETMF SDK 4.3.
13.01.2014
Parametry msbuild
Kto choć raz skompilował solucję Discovery4 (z minimalnymi parametrami np. msbuild /p:flavor=DEBUG;memory=flash Solutions\Discovery4\dotNetMF.proj) nie mógł się zapewne nadziwić, jak coś można z tych szlaczków, co przelatują na ekranie, wyczytać. Ekran przypomina coś takiego...
Aha! Nie do końca. Pięć ekranów wcześniej był żółty, a 2 ekrany wcześniej czerwony. Czy idzie to jakoś ogarnąć? Tak. Potrzebne są dodatkowe parametry. Po kolei.
Właściwości projektu to np. /p:flavor=debug;memory=flash. Zamiast debug możemy użyć następujących wartości: instrumented, release, rtm. Do końcowych wersji CLR powinniśmy użyć release lub rtm.
Elementy docelowe projektu to np: /t:build. Jest to domyślna wartość. Pozwala przyrostowo kompilować solucję. Gdy coś testujemy, dodając elementy do solucji, lub wyskoczy błąd kompilacji to ta opcja najszybciej skompiluje nam ponownie. Do czystej kompilacji powinniśmy użyć wartości /t:clean;build.
Plik projektu do kompilacji to np. Solutions\Discovery4\dotNetMF.proj. W tym przypadku zostanie skompilowany TinyBooter, a następnie TinyCLR. Nic nie stoi jednak na przeszkodzie, aby kompilować tylko pojedyncze projekty. Najczęściej kompiluje się TinyCLR. Czyli powinniśmy użyć takiego projektu Solutions\Discovery4\TinyCLR\TinyCLR.proj. Jak chcemy skompilować tylko TinyBooter to Solutions\Discovery4\TinyBooter\TinyBooter.proj.
Teraz opcje loggera. Parametry logowania konsoli określa się przełącznikiem /clp. Na przykład, aby nie pojawiały się na konsoli żadne informacje można użyć /clp:Verbosity=quiet. Przecież i tak nic z tego co w "czarnym okienku" się pojawia nie jesteśmy w stanie ogarnąć. Wartości jakie możemy użyć w verbosity to: quiet, minimal, normal, detailed i diagnostic. Możemy też skorzystać z dodatkowych opcji np. ErrorsOnly, WarningsOnly i Summary. Pierwsze dwa włączają pokazywanie tylko określonych komunikatów, a trzeci wskazuje czy na końcu ma pokazać podsumowanie. Summary działać będzie tylko wówczas, gdy Verbosity>=normal i nie będzie parametrów ErrorsOnly i WarningsOnly. Według mnie najlepszymi parametrami dla loggera konsoli to /clp:ErrorsOnly;Verbosity=quiet.
Jednak na wypadek błędu pasowałoby mieć jakieś informacje, co poszło nie tak. Możemy włączyć logowanie do pliku. Dzięki przełącznikowi /fl w katalogu, z którego odpalamy msbuild, powstanie plik msbuild.log. Dla tego loggera możemy wskazać takie same parametry, jak dla loggera konsolowego np.: /fl /flp:Summary;Verbosity=normal.
Dodatkowe parametry jakie możemy dodać do polecenia to /nologo - nie pokazuje informacji o wersji msbuild i /m - pozwala użyć do kompilacji wiele procesów. Na koniec otrzymamy więc takie polecenie:
msbuild /t:build /p:flavor=release;memory=flash Solutions\Discovery4\TinyCLR\TinyCLR.proj /clp:ErrorsOnly;Verbosity=quiet /fl /flp:Summary;Verbosity=normal /nologo /m
Najlepiej jednak zrobić sobie plik bat w katalogu PK na przykład z taką zawartością:
rem Discovery4 build script call setenv_gcc 4.6.2 c:\gcc46 msbuild /t:build /p:flavor=release;memory=flash Solutions\Discovery4\TinyCLR\TinyCLR.proj /clp:ErrorsOnly;Verbosity=quiet /fl /flp:Summary;Verbosity=normal /nologo /m pause
11.01.2014
Pliki *.targets
W podkatalogu tools\Targets MicroFramework PK znajdziemy specjalne pliki *.targets. Co to takiego?
Są to pliki z instrukcjami dla msbuild (zapisane w xmlu), w jaki sposób ma wywoływać zewnętrzne narzędzia kompilacji np. z MDK lub GCC. Czyli np. arm-none-eabi-gcc.exe z chyba 'miliardem' przełączników i opcji.
Popatrzmy na przykład do pliku Microsoft.Spot.system.mdk.targets. Jest tam pełno elementów xml z przeróżnymi atrybutami (np. Condition). Połapanie się w tym wszystkim nie jest proste. Najciekawsze jest jednak na początku pliku. Jest tam pełno takich linii:
<CC Condition="'$(COMPILER_TOOL_VERSION)'=='MDK3.1'">"$(MDK_TOOL_PATH)\bin31\armcc.exe"</CC> <CPP Condition="'$(COMPILER_TOOL_VERSION)'=='MDK3.1'">"$(MDK_TOOL_PATH)\bin31\armcc.exe"</CPP> <AS Condition="'$(COMPILER_TOOL_VERSION)'=='MDK3.1'">"$(MDK_TOOL_PATH)\bin31\armasm.exe"</AS> <LINK Condition="'$(COMPILER_TOOL_VERSION)'=='MDK3.1'">"$(MDK_TOOL_PATH)\bin31\armlink.exe"</LINK> <AR Condition="'$(COMPILER_TOOL_VERSION)'=='MDK3.1'">"$(MDK_TOOL_PATH)\bin31\armar.exe"</AR> <FROMELF Condition="'$(COMPILER_TOOL_VERSION)'=='MDK3.1'">"$(MDK_TOOL_PATH)\bin31\fromelf.exe"</FROMELF>
Są to wskazania, gdzie znajdują się poszczególne narzędzia w zależności od wersji MDK. Nazwa elementu to zmienna pod jaką będzie w dalszej części pliku występował dany program. Na przykład wszędzie, gdzie będzie potrzebne wywołanie kompilatora armcc.exe, zostanie wstawione $(CC). Jak łatwo się zorientować możemy użyć tylko niektórych wersji MDK. A są to: 3.1, 3.80a, 4.12, 4.13, 4.54. Dlatego tak ważne jest ustawienie odpowiedniej wersji przy wywołaniu setenv_mdk.cmd.
Ale czy można dodać obsługę nowszej wersji MDK? Na przykład 4.60? Jest jeden problem. W parametrach wywołania konsolidatora (linker), gdzieś około linii 222, jest na stałe wstawiona ścieżka do bibliotek: $(MDK_TOOL_PATH)\RV31\LIB. Wersja 4.60 biblioteki ma w zupełnie innym katalogu: $(MDK_TOOL_PATH)\armcc\lib. Łatwo znaleźć, bo jest to katalog z podkatalogami armlib i cpplib.
Trzeba jakoś to obejść. Najlepiej zdefiniować zmienną dla każdej wersji, która będzie przechowywać tą ścieżkę. Trzeba więc zamienić $(MDK_TOOL_PATH)\RV31\LIB na coś takiego: $(MDKLIB). Trzeba jeszcze do każdej wersji (na początku pliku) dodać taką definicję, umiejętnie zmieniając numerek wersji przy każdej nowej sekcji:
<MDKLIB Condition="'$(COMPILER_TOOL_VERSION)'=='MDK3.1'">"$(MDK_TOOL_PATH)\rv31\lib"</MDKLIB>
Podobnie dokładamy elementy dla wersji 4.60:
<CC Condition="'$(COMPILER_TOOL_VERSION)'=='MDK4.60'">"$(MDK_TOOL_PATH)\armcc\bin\armcc.exe"</CC> <CPP Condition="'$(COMPILER_TOOL_VERSION)'=='MDK4.60'">"$(MDK_TOOL_PATH)\armcc\bin\armcc.exe"</CPP> <AS Condition="'$(COMPILER_TOOL_VERSION)'=='MDK4.60'">"$(MDK_TOOL_PATH)\armcc\bin\armasm.exe"</AS> <LINK Condition="'$(COMPILER_TOOL_VERSION)'=='MDK4.60'">"$(MDK_TOOL_PATH)\armcc\bin\armlink.exe"</LINK> <AR Condition="'$(COMPILER_TOOL_VERSION)'=='MDK4.60'">"$(MDK_TOOL_PATH)\armcc\bin\armar.exe"</AR> <FROMELF Condition="'$(COMPILER_TOOL_VERSION)'=='MDK4.60'">"$(MDK_TOOL_PATH)\armcc\bin\fromelf.exe"</FROMELF> <MDKLIB Condition="'$(COMPILER_TOOL_VERSION)'=='MDK4.60'">"$(MDK_TOOL_PATH)\armcc\lib"</MDKLIB>
I jeszcze jeden element gdzieś koło linii 77:
<CC_CPP_ASM_INTERLEAVE Condition="'$(COMPILER_TOOL_VERSION)'=='MDK4.60'" ></CC_CPP_ASM_INTERLEAVE>
To wszystko. Można kompilować uruchamiając wcześniej setenv_mdk.cmd 4.60
1.11.2013
Ultradzwiękowy czujnik odległości HC-SR04
W ręce trafił mi taki oto czujnik więc opiszę jak się do niego "dobrać". Na pewno się komuś przyda na przykład do konstrukcji robotów.
HC-SR04 to ultradźwiękowy czujnik odległości. Ma jedno wejście (Trig) i jedno wyjście (Echo). Zasilany musi być napięciem +5V. Aby dokonać pomiaru należy na wejściu Trig ustawić +5V na czas co najmniej 10us. Na wyjściu Echo dostaniemy wówczas impuls o szerokości proporcjonalnej do odległości od przeszkody. Piny Trig i Echo możemy podłączyć do dowolnego, wolnego pinu na płytce STM32F4Discovery. Ja akurat Trig podłączyłem do PC2, a Echo do PC1:
HC-SR04 to ultradźwiękowy czujnik odległości. Ma jedno wejście (Trig) i jedno wyjście (Echo). Zasilany musi być napięciem +5V. Aby dokonać pomiaru należy na wejściu Trig ustawić +5V na czas co najmniej 10us. Na wyjściu Echo dostaniemy wówczas impuls o szerokości proporcjonalnej do odległości od przeszkody. Piny Trig i Echo możemy podłączyć do dowolnego, wolnego pinu na płytce STM32F4Discovery. Ja akurat Trig podłączyłem do PC2, a Echo do PC1:
No dobra. Czas napisać jakiegoś kota. Do obsługi Pina Trig będziemy potrzebowali OutputPort, a pomiar szerokości impulsu pinu Echo zrealizujemy na InterrputPort. Zapamiętamy czas wystąpienia narastającego i opadającego zbocza. Szerokość impulsu to różnica tych czasów. Klasa realizująca obsługę czujki wygląda następująco:
public class HCSR04 : IDisposable
{
private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false);
private readonly object _syncRoot = new object();
private bool _disposing;
private static InterruptPort _echo;
private static OutputPort _trigger;
private long _startTime;
private long _stopTime;
public HCSR04(Cpu.Pin triggerPin, Cpu.Pin echoPin)
{
_echo = new InterruptPort(echoPin, false,
Port.ResistorMode.PullDown,
Port.InterruptMode.InterruptEdgeBoth);
_trigger = new OutputPort(triggerPin, false);
_echo.OnInterrupt += (port, state, time) =>
{
if (state == 0)
{
_stopTime = time.Ticks;
_resetEvent.Set();
return;
}
_startTime = time.Ticks;
};
}
public TimeSpan Ping()
{
lock (_syncRoot)
{
if (!_disposing)
{
_trigger.Write(false);
_startTime = _stopTime = 0;
_resetEvent.Reset();
_trigger.Write(true);
Thread.Sleep(1);
_trigger.Write(false);
_resetEvent.WaitOne(60, false);
if (_startTime > 0 && _stopTime > 0)
return TimeSpan.FromTicks(_stopTime - _startTime);
}
}
return TimeSpan.MaxValue;
}
public void Dispose()
{
lock (_syncRoot)
{
_disposing = true;
_trigger.Dispose();
_echo.Dispose();
}
}
}
Procedura Ping zwraca długość trwania impulsu na wyjściu Echo lub TimeSpan.MaxValue w przypadku niepowodzenia pomiaru. Wyjaśnienia wymaga użycie ManualResetEvent i instrukcji lock.
Najpierw lock. Procedura Ping może być wywoływana z różnych wątków jednocześnie. Próba wykonania kolejnego pomiaru (np. przez inny wątek) przed zakończeniem poprzedniego pomiaru zakłóciłaby cały proces. Dlatego instrukcją lock objęto kot, do którego wątki muszą mieć dostęp pojedynczo. Kolejny wątek, który będzie chciał wykonać pomiar, będzie czekał dopóki nie zakończy się wykonywanie tego bloku.
Natomiast użycie ManualResetEvent pozwala, w jak najkrótszym czasie, na zakończenie procedury. Przed wysłaniem impulsu Trig resetEvent jest kasowany, a na opadającym zboczu impulsu ustawiany. W linijce z WaitOne w najgorszym razie program będzie więc czekał 60 milisekund. Jeśli zbocze opadające pojawi się wcześniej, to ustawienie resetEvent spowoduje szybsze przejście programu do następnej instrukcji. Oczywiście zamiast resetEvent można by było użyć Thread.Sleep(60), ale wówczas program czekał by zawsze 60 milisekund.
Do pełni szczęścia brakuje jeszcze dwóch statycznych procedur do zamiany długości impulsu na odległość:
public static float ToCentimeters(TimeSpan pulse)
{
if (pulse.Equals(TimeSpan.MaxValue))
return Single.MaxValue;
float result = pulse.TotalMicroseconds()/58f;
return result;
}
public static float ToInches(TimeSpan pulse)
{
if (pulse.Equals(TimeSpan.MaxValue))
return Single.MaxValue;
float result = pulse.TotalMicroseconds()/148f;
return result;
}
Teraz można już robić pomiary. Najpierw kawałek programu do wyświetlania odległości:
var sensor = new HCSR04(Stm32F4Discovery.FreePins.PC2,
Stm32F4Discovery.FreePins.PC1);
for (;;)
{
TimeSpan pulse = sensor.Ping();
float cm = HCSR04.ToCentimeters(pulse);
string cmStr = cm.Equals(Single.MaxValue) ? "?" : cm.ToString("F1");
float inch = HCSR04.ToInches(pulse);
string inStr = inch.Equals(Single.MaxValue) ? "?" : inch.ToString("F1");
Debug.Print("Distance: " + cmStr + " cm = " + inStr + " in");
Thread.Sleep(1000);
}
Prościzna. Można też zamieniać odległość na częstotliwość i sterować wyjściem PWM tak, aby dioda mrugała szybciej jeśli zbliżamy się do przeszkody, a wolniej jeśli się oddalamy. Trochę przypomina to czujnik parkowania. Procedura zamiany na częstotliwość wygląda tak:
public static float ToFrequency(TimeSpan pulse)
{
//0-30 cm --> 12-1 Hz
const int barier = 30; //cm
float cm = HCSR04.ToCentimeters(pulse);
if (cm > barier)
return 1;
const int maxFreq = 12; //Hz
float result = (cm*(1 - maxFreq))/barier + maxFreq;
return result;
}
A właściwe użycie tak:
var sensor = new HCSR04(Stm32F4Discovery.FreePins.PC2,
Stm32F4Discovery.FreePins.PC1);
var distancePwm = new PWM(Cpu.PWMChannel.PWM_0, 1, 0, false);
distancePwm.Start();
for (;;)
{
TimeSpan pulse = sensor.Ping();
distancePwm.Frequency = ToFrequency(pulse);
distancePwm.DutyCycle = 0.5;
Thread.Sleep(200);
}
Konstruktorom robotów na pewno się przyda detektor kolizji. Niezależna klasa, która poprzez zdarzenie poinformuje o zbliżeniu się do przeszkody. W detektorze użyto timera do cyklicznego odpytywania czujnika HC-SR04. Detektor generuje zdarzenie w przypadku przekroczenia (zbliżenie i oddalenie) zadanej odległości od przeszkody.
public class CollisionDetector
{
public delegate void StateChangedEventHandler(bool crash);
public event StateChangedEventHandler StateChanged;
public float Barier { get; set; }
private readonly HCSR04 _sensor;
private readonly Timer _timer;
private bool _collision;
public CollisionDetector(HCSR04 sensor, TimeSpan scanPeriod)
{
_sensor = sensor;
_timer = new Timer(TimerTick, null, TimeSpan.Zero, scanPeriod);
}
private void TimerTick(object state)
{
TimeSpan pulse = _sensor.Ping();
float distance = HCSR04.ToCentimeters(pulse);
if(distance.Equals(Single.MaxValue))
return;
bool newState = distance <= Barier;
if (_collision ^ newState)
{
_collision = newState;
StateChangedEventHandler handler = StateChanged;
if (handler != null)
StateChanged(_collision);
}
}
}
Teraz czerwoną diodę zapalamy jeśli nadmiernie zbliżymy się do przeszkody, a gasimy jeśli odległość będzie bezpieczna. Nic nie stoi na przeszkodzie, aby zamiast diody sterować np. silniczkiem.
var sensor = new HCSR04(Stm32F4Discovery.FreePins.PC2,
Stm32F4Discovery.FreePins.PC1);
var crashLed = new OutputPort(Stm32F4Discovery.LedPins.Red, false);
var scanPeriod = new TimeSpan(0, 0, 0, 0, 100); //100ms
var detector = new CollisionDetector(sensor, scanPeriod) {Barier = 10}; //10cm
detector.StateChanged += crashLed.Write;
Thread.Sleep(Timeout.Infinite);
28.09.2013
Do czego służy Debug.Print
Czasami warto napisać o rzeczach oczywistych. To co tutaj opiszę odkryłem dopiero po jakimś czasie zabawy z .Net Micro Framework i od czasu do czasu się to przydaje.
Oczywiście do czego służy Debug.Print to chyba każdy wie. W ten sposób wpisujemy sobie w kodzie informacje diagnostyczne, które możemy obserwować w oknie Output w Visual Studio. Ale te komunikaty możemy obserwować nawet bez debuggera i bez Visual Studio. Wystarczy do tego MFDeploy. To ten programik do ładowania firmware (plików hex) do STM32F4Discovery. Wystarczy z menu Target wybrać Connect lub nacisnąć F5.
22.09.2013
Discovery ma przyszłość
Firma STMicroelectronics wprowadza do sprzedaży nową płytkę startową z STM32F4. Niestety nie ma jej jeszcze w sklepach. Nazywa się to 32F429IDISCOVERY (kod produktu STM32F429I-DISCO).
To co się od razu rzuca w oczy to wyświetlacz LCD umieszczony bezpośrednio na płytce.
Najważniejsze parametry płytki:
Najważniejsze parametry płytki:
- mikrokontroler STM32F429ZIT6 (2 MB pamięci flash, 256 KB pamięci RAM)
- na pokładzie programator ST-LINK/V2 ze złączem SWD
- kolorowy touchpanel 2.4" QVGA TFT LCD, 240x320 punktów
- trzyosiowy żyroskop L3GD20
- pamięć SDRAM 64 Mbity
- dwie diody świecące do użycia w programach
- jeden przycisk do użycia w programach
Zestaw w akcji można zobaczyć tutaj: http://www.stm32.eu/node/325, a dokumentację pdf można poczytać na stronie STM: http://www.st.com/web/catalog/tools/FM116/SC959/SS1532/PF259090.
Subskrybuj:
Posty (Atom)




