STM32-Arduino编程 FreeRTOS移植

  • 环境说明
  • 使用Arduino开发STM32原因
  • 直接开始操作


环境说明

开发软件:vscode+PlatformIO
操作系统:win10
开发板:STM32F103C8T6
UDP通信模块:W5500模块

使用Arduino开发STM32原因

STM32开发比较常用的软件有Keil和STM32Cube,但是用过的同学都应该发现这两款软件对于新手不怎么友好,需要花费很长的时间才能做到入门。特别是在开发一些比较复杂的程序时,比如植入freertos,安排几个任务,能把人给折磨死。与Keil和STM32Cube相同的的是,STM32-Arduino也是采用的库函数开发,并不存在低人一等的说法。当然需要承认的是相比于寄存器开发的程序还是要差一点的。STM32-Arduino在库函数得到基础上进一步的封装,可以大大的降低程序中出现的配置出错的问题,让新手的同学可以更快的开发出高质量的STM32的程序。

直接开始操作

首先打开platformIO,创建工程。

esp32增大freertos大小_UDP


工程名称随便,要设定好对应的开发板。我这里使用的是开发板STM32F103C8T6。如果需要使用其他的板子,在创建工程时设定好对应的板子即可。

esp32增大freertos大小_#define_02

工程创建好之后,在Libraries中搜索FreeRTOS,便可以找到STM32duino FreeRTOS 库,将其加入到STM32UDP_FreeRTOS_TIM的工程中。并且将Ethernet3以及STM32TimerInterrupt库一同加入到工程中。

esp32增大freertos大小_#define_03

在对应的库中找到相应的例程,进行一定的拼接,将UDP通信,串口通信和LED的闪烁分别放到一个任务中执行,拼接后的程序如下。

#include <Arduino.h>
#include <Arduino.h>
/*
  UDPSendReceive.pde:
 This sketch receives UDP message strings, prints them to the serial port
 and sends an "acknowledge" string back to the sender

 A Processing sketch is included at the end of file that can be used to send
 and received messages for testing with a computer.

 created 21 Aug 2010
 by Michael Margolis

 This code is in the public domain.
 */
/*******************
 * 导入库EthEthernet3,使用W5500模块,进行UDP通信,这部分的程序因为引脚不是按照STM32的标准进行配置的,在EthEthernet3中设置RTS和CS引脚,SPI方面要重新配置引脚
 * 导入库STM3Eth2FreeRTOS,STM32配套的FreeRTOS,实现多任务的并行
 * 导入库STM32TimerInterrupt,STM32配套的定时器中断库,调用定时器实现中断
**(*****************/

#include <SPI.h>         // needed for Arduino versions later than 0018
#include <Ethernet3.h>
#include <EthernetUdp3.h>         // UDP library from: bjoern@cs.stanford.edu 12/30/2008
#include <STM32FreeRTOS.h>
#include "STM32TimerInterrupt.h"
#include "STM32_ISR_Timer.h"

/******************FreeRTOS的任务声明**********************/
void TaskBlink( void *pvParameters );
void TaskAnalogRead( void *pvParameters );
void TaskUDP(void *pvParameters);
/*******************UDP通信方面的参数***********************/
// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 1, 177);//IP地址

unsigned int localPort = 8888;      // local port to listen on 端口

// buffers for receiving and sending data
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet,用于存储接收到的数据
char  ReplyBuffer[] = "acknowledged";       // a string to send back

// An EthernetUDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

#if !( defined(STM32F0) || defined(STM32F1) || defined(STM32F2) || defined(STM32F3)  ||defined(STM32F4) || defined(STM32F7) || \
       defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32H7)  ||defined(STM32G0) || defined(STM32G4) || \
       defined(STM32WB) || defined(STM32MP1) )
  #error This code is designed to run on STM32F/L/H/G/WB/MP1 platform! Please check your Tools->Board setting.
#endif

// These define's must be placed at the beginning before #include "STM32TimerInterrupt.h"
// _TIMERINTERRUPT_LOGLEVEL_ from 0 to 4
// Don't define _TIMERINTERRUPT_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system.
// Don't define TIMER_INTERRUPT_DEBUG > 2. Only for special ISR debugging only. Can hang the system.
#define TIMER_INTERRUPT_DEBUG         0
#define _TIMERINTERRUPT_LOGLEVEL_     0

#include "STM32TimerInterrupt.h"

#ifndef LED_BUILTIN
  #define LED_BUILTIN       PB0               // Pin 33/PB0 control on-board LED_GREEN on F767ZI
#endif

#ifndef LED_BLUE
  #define LED_BLUE          PB7               // Pin 73/PB7 control on-board LED_BLUE on F767ZI
#endif

#ifndef LED_RED
  #define LED_RED           PB14              // Pin 74/PB14 control on-board LED_BLUE on F767ZI
#endif
   
HardwareSerial Serial_2()
#define TIMER_INTERVAL_MS         100
#define HW_TIMER_INTERVAL_MS      50

// Depending on the board, you can select STM32 Hardware Timer from TIM1-TIM22
// For example, F767ZI can select Timer from TIM1-TIM14
// If you select a Timer not correctly, you'll get a message from ci[ompiler
// 'TIMxx' was not declared in this scope; did you mean 'TIMyy'? 

// Init STM32 timer TIM1
STM32Timer ITimer(TIM1);

// Init STM32_ISR_Timer
// Each STM32_ISR_Timer can service 16 different ISR-based timers
STM32_ISR_Timer ISR_Timer;

#define TIMER_INTERVAL_0_5S           500L
#define TIMER_INTERVAL_1S             1000L
#define TIMER_INTERVAL_1_5S           1500L

void TimerHandler()
{
  ISR_Timer.run();
}

// In STM32, avoid doing something fancy in ISR, for example complex Serial.print with String() argument
// The pure simple Serial.prints here are just for demonstration and testing. Must be eliminate in working environment
// Or you can get this run-time error / crash
void doingSomething1()
{
  Serial.println("doingSomething1");
 // digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}

void doingSomething2()
{
  Serial.println("doingSomething2");
  //digitalWrite(LED_BLUE, !digitalRead(LED_BLUE));
}
void doingSomething3()
{
  Serial.println("doingSomething3");
  //digitalWrite(LED_RED, !digitalRead(LED_RED));
}
void setup() {
  // start the Ethernet and UDP:
  Ethernet.begin(mac, ip);
  Udp.begin(localPort);

  Serial.begin(9600);
  pinMode(LED_BUILTIN,  OUTPUT);
  pinMode(LED_BLUE,     OUTPUT);
  pinMode(LED_RED,      OUTPUT);

  // Interval in microsecs
  if (ITimer.attachInterruptInterval(HW_TIMER_INTERVAL_MS * 1000, TimerHandler))
  {
    Serial.print(F("Starting ITimer OK, millis() = ")); Serial.println(millis());
  }
  else
    Serial.println(F("Can't set ITimer. Select another freq. or timer"));

  // Just to demonstrate, don't use too many ISR Timers if not absolutely necessary
  // You can use up to 16 timer for each ISR_Timer
  ISR_Timer.setInterval(TIMER_INTERVAL_0_5S,  doingSomething1);
  ISR_Timer.setInterval(TIMER_INTERVAL_1S,    doingSomething2);
  ISR_Timer.setInterval(TIMER_INTERVAL_1_5S,  doingSomething3);
    xTaskCreate(
    TaskUDP
    ,  (const portCHAR *) "UDP"
    ,  128  // Stack size
    ,  NULL
    ,  3  // Priority
    ,  NULL );
    xTaskCreate(
    TaskBlink
    ,  (const portCHAR *)"Blink"   // A name just for humans
    ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  2  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL );

  xTaskCreate(
    TaskAnalogRead
    ,  (const portCHAR *) "AnalogRead"
    ,  128  // Stack size
    ,  NULL
    ,  1  // Priority
    ,  NULL );

  // start scheduler
  vTaskStartScheduler();
  Serial.println("Insufficient RAM");
  while(1);
}

void loop() {
}

void TaskBlink(void *pvParameters)  // This is a task.
{
  (void) pvParameters;

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.

  Most Arduinos have an on-board LED you can control. On the UNO, LEONARDO, MEGA, and ZERO
  it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN takes care
  of use the correct LED pin whatever is the board used.

  The MICRO does not have a LED_BUILTIN available. For the MICRO board please substitute
  the LED_BUILTIN definition with either LED_BUILTIN_RX or LED_BUILTIN_TX.
  e.g. pinMode(LED_BUILTIN_RX, OUTPUT); etc.

  If you want to know what pin the on-board LED is connected to on your Arduino model, check
  the Technical Specs of your board  at https://www.arduino.cc/en/Main/Products

  This example code is in the public domain.

  modified 8 May 2014
  by Scott Fitzgerald

  modified 2 Sep 2016
  by Arturo Guadalupi
*/

  // initialize digital LED_BUILTIN on pin 13 as an output.
  pinMode(LED_BUILTIN, OUTPUT);

  for (;;) // A Task shall never return or exit.
  {
    digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    vTaskDelay( 1000 / portTICK_PERIOD_MS ); // wait for one second
    digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    vTaskDelay( 1000 / portTICK_PERIOD_MS ); // wait for one second
  }
}

void TaskAnalogRead(void *pvParameters)  // This is a task.
{
  (void) pvParameters;

/*
  AnalogReadSerial
  Reads an analog input on pin 0, prints the result to the serial monitor.
  Graphical representation is available using serial plotter (Tools > Serial Plotter menu)
  Attach the center pin of a potentiometer to pin A0, and the outside pins to +5V and ground.

  This example code is in the public domain.
*/

  for (;;)
  {
    // // read the input on analog pin 0:
    // int sensorValue = analogRead(A0);
    // // print out the value you read:
    // Serial.println(sensorValue);
    // vTaskDelay(1);  // one tick delay (15ms) in between reads for stability
  }
}

void TaskUDP(void *pvParameters)
{
  (void) pvParameters;

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.

  Most Arduinos have an on-board LED you can control. On the UNO, LEONARDO, MEGA, and ZERO
  it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN takes care
  of use the correct LED pin whatever is the board used.

  The MICRO does not have a LED_BUILTIN available. For the MICRO board please substitute
  the LED_BUILTIN definition with either LED_BUILTIN_RX or LED_BUILTIN_TX.
  e.g. pinMode(LED_BUILTIN_RX, OUTPUT); etc.

  If you want to know what pin the on-board LED is connected to on your Arduino model, check
  the Technical Specs of your board  at https://www.arduino.cc/en/Main/Products

  This example code is in the public domain.

  modified 8 May 2014
  by Scott Fitzgerald

  modified 2 Sep 2016
  by Arturo Guadalupi
*/

  // initialize digital LED_BUILTIN on pin 13 as an output.
  pinMode(LED_BUILTIN, OUTPUT);

  for (;;) // A Task shall never return or exit.
  {
     // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if (packetSize)
  {
    Serial.print("Received packet of size ");
    Serial.println(packetSize);
    Serial.print("From ");
    IPAddress remote = Udp.remoteIP();
    for (int i = 0; i < 4; i++)
    {
      Serial.print(remote[i], DEC);
      if (i < 3)
      {
        Serial.print(".");
      }
    }
    Serial.print(", port ");
    Serial.println(Udp.remotePort());
    // read the packet into packetBufffer
    Udp.read(packetBuffer, packetSize);
    //Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
    Serial.println("Contents:");
    Serial.println(packetBuffer);

    // send a reply, to the IP address and port that sent us the packet we received
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
    Udp.write(packetBuffer,packetSize);
    //Udp.write(packetBuffer);
    //Udp.write(ReplyBuffer);
    Udp.endPacket();
  }
      vTaskDelay( 10 / portTICK_PERIOD_MS ); // wait for one second
  }
}

需要注意的是,FreeRTOS中的任务数字越高代表着任务的优先级越高,这也就意味着高优先级的任务就有可能会导致低优先级的任务被打断。所以在高优先级的任务中不要使用长时间的延迟函数,最好设法让延迟函数低于500ms,否则低优先级的任务无法运行。