-
Notifications
You must be signed in to change notification settings - Fork 980
API
This part describes the STM32 core functions.
This was introduced core version greater than 1.5.0. It is based on Semantic Versioning 2.0.0 (https://semver.org/).
This ease core dependencies and defined here.
STM32_CORE_VERSION
defines the core version with:
-
STM32_CORE_VERSION_MAJOR
: major version [31:24] -
STM32_CORE_VERSION_MINOR
: minor version [23:16] -
STM32_CORE_VERSION_PATCH
: patch version [15:8] -
STM32_CORE_VERSION_EXTRA
: Extra label [7:0] with:-
0
: official release -
[1-9]
: release candidate -
F[0-9]
: development
-
#if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION <= 0x01050000)
/* Do something for core version lesser than or equal to 1.5.0 */
#else
/* Do something for core version greater than 1.5.0 */
#endif
CoreCallback functions allows to register a callback function called in the loop of the main()
function.
If you need to call as often as possible a function to update your system and you want to be sure this function to be called, you can add it to the callback list. Otherwise, your function should be called inside the loop()
function of
the sketch.
-
void registerCoreCallback(void (*func)(void))
: register a callback function
Paramsfunc
pointer to the callback function -
void unregisterCoreCallback(void (*func)(void))
: unregister a callback function
Paramsfunc
pointer to the callback function
/img/Warning-icon.png By default, the core callback feature is disabled, to enable it CORE_CALLBACK
must be defined.
build_opt.h
can be used to define it by adding -DCORE_CALLBACK
, see build_opt.h wiki.
analogWriteFrequency(freq)
has been added in core version greater than 1.5.0 to set the frequency used by analogWrite()
. Default is PWM_FREQUENCY
(1000) in Hertz.
Note frequency is common to all channels of a specified timer, setting the frequency for one channel will impact all others of the same timer.
// Assuming Ax pins have PWM capabilities and use a different Timer.
analogWrite(A1, 127); // Start PWM on A1, at 1000 Hz with 50% duty cycle
analogWriteFrequency(2000); // Set PMW period to 2000 Hz instead of 1000
analogWrite(A2, 64); // Start PWM on A2, at 2000 Hz with 25% duty cycle
analogWriteFrequency(500); // Set PMW period to 500 Hz
analogWrite(A3, 192); // Start PWM on A3, at 500 Hz with 75% duty cycle
Available in core version greater than 1.5.0
analogRead()
can now be used to read some internal channels with the following definitions:
-
ATEMP
: internal temperature sensor -
AVREF
: VrefInt, internal voltage reference -
AVBAT
: Vbat voltage
A minimum ADC sampling time is required when reading internal channels so default is set it to max possible value. It can be defined more precisely by defining:
-
ADC_SAMPLINGTIME_INTERNAL
to the desired ADC sample time.
ADC_SAMPLINGTIME
and ADC_CLOCK_DIV
could also be redefined by the variant or using build_opt.h
.
Hereafter an usage example which read then convert to proper Unit the 3 internal channels + A0:
#include "stm32yyxx_ll_adc.h"
/* Values available in datasheet */
#define CALX_TEMP 25
#if defined(STM32F1xx)
#define V25 1430
#define AVG_SLOPE 4300
#define VREFINT 1200
#elif defined(STM32F2xx)
#define V25 760
#define AVG_SLOPE 2500
#define VREFINT 1210
#endif
/* Analog read resolution */
#if ADC_RESOLUTION == 10
#define LL_ADC_RESOLUTION LL_ADC_RESOLUTION_10B
#define ADC_RANGE 1024
#else
#define LL_ADC_RESOLUTION LL_ADC_RESOLUTION_12B
#define ADC_RANGE 4096
#endif
// the setup routine runs once when you press reset:
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
analogReadResolution(ADC_RESOLUTION);
}
static int32_t readVref()
{
#ifdef __LL_ADC_CALC_VREFANALOG_VOLTAGE
return (__LL_ADC_CALC_VREFANALOG_VOLTAGE(analogRead(AVREF), LL_ADC_RESOLUTION));
#else
return (VREFINT * ADC_RANGE / analogRead(AVREF)); // ADC sample to mV
#endif
}
#ifdef ATEMP
static int32_t readTempSensor(int32_t VRef)
{
#ifdef __LL_ADC_CALC_TEMPERATURE
return (__LL_ADC_CALC_TEMPERATURE(VRef, analogRead(ATEMP), LL_ADC_RESOLUTION));
#elif defined(__LL_ADC_CALC_TEMPERATURE_TYP_PARAMS)
return (__LL_ADC_CALC_TEMPERATURE_TYP_PARAMS(AVG_SLOPE, V25, CALX_TEMP, VRef, analogRead(ATEMP), LL_ADC_RESOLUTION));
#else
return 0;
#endif
}
#endif
static int32_t readVoltage(int32_t VRef, uint32_t pin)
{
return (__LL_ADC_CALC_DATA_TO_VOLTAGE(VRef, analogRead(pin), LL_ADC_RESOLUTION));
}
// the loop routine runs over and over again forever:
void loop() {
// print out the value you read:
Serial.print("VRef(mv)= ");
int32_t VRef = readVref();
Serial.print(VRef);
#ifdef ATEMP
Serial.print("\tTemp(°C)= ");
Serial.print(readTempSensor(VRef));
#endif
#ifdef AVBAT
Serial.print("\tVbat(mv)= ");
Serial.print(readVoltage(VRef, AVBAT));
#endif
Serial.print("\tA0(mv)= ");
Serial.println(readVoltage(VRef, A0));
delay(200);
}
The STM32 MCU's have several U(S)ART peripherals. By convenience, the U(S)ARTx number is used to define the Serialx
instance:
-
Serial1
forUSART1
-
Serial2
forUSART2
-
Serial3
forUSART3
-
Serial4
forUART4
- ...
For
LPUART1
this isSerialLP1
By default, only one Serialx
instance is available mapped to the generic Serial
name.
To use a second serial port, a HardwareSerial
object should be declared in the sketch before the setup()
function:
// RX TX
HardwareSerial Serial1(PA10, PA9);
void setup() {
Serial1.begin(115200);
}
void loop() {
Serial1.println("Hello World!");
delay(1000);
}
Another solution is to add a build_opt.h file alongside your main .ino
file with: -DENABLE_HWSERIALx
.
This will define the Serialx
instance using the first USARTx
instance found in the PeripheralPins.c
of your variant.
/img/Note-icon.png Note that only the latter solution allows to use the serialEventx()
callback in the sketch.
For Example, if you define in the build_opt.h: -DENABLE_HWSERIAL3
This will instantiate Serial3
with the first Rx and Tx pins found in the PinMap_UART_RX[]
and PinMap_UART_TX[]
arrays in the PeripheralPins.c
of your variant and the serialEvent3()
will be enabled.
To specify which Rx or Tx pins should be used instead of the first one found, you can specified the PIN_SERIALn_RX
or PIN_SERIALn_TX
where n is the number of the Serial instance.
Example for the Serial3
:
- In the
variant.h
:
#define PIN_SERIAL3_RX PB11
#define PIN_SERIAL3_TX PB10
- In the
build_opt.h
:-DPIN_SERIAL3_RX=PB11 -DPIN_SERIAL3_TX=PB10
It is also possible to change the default pins used by the Serial
instance using above API:
void setRx(uint32_t rx)
void setTx(uint32_t tx)
void setRx(PinName rx)
void setTx(PinName tx)
/img/Warning-icon.png Have to be called before begin()
.
Serial.setRx(PG_9); // using pin name PY_n
Serial.setTx(PG14); // using pin number PYn
Serial.begin(9600);
Available in core version greater than 1.7.0
It is now possible to set a HardwareSerial
in half-duplex mode.
The U(S)ART
can be configured to follow a single-wire half-duplex protocol where the Tx and Rx lines are internally connected. In this communication mode, only the Tx pin is used for both transmission and reception.
-
Extended
HardwareSerial
constructors:-
HardwareSerial(uint32_t _rxtx)
: U(S)ART Tx pin number (PYn
) used for half-duplex -
HardwareSerial(PinName _rxtx)
: U(S)ART Tx pin name (PY_n
) used for half-duplex - if
Rx == Tx
then assume half-duplex mode:-
HardwareSerial(uint32_t _rx, uint32_t _tx)
: U(S)ART Tx pin number (PYn
) used for half-duplex -
HardwareSerial(PinName _rx, PinName tx)
: U(S)ART Tx pin name (PY_n
) used for half-duplex
-
-
HardwareSerial(void *peripheral, HalfDuplexMode_t halfDuplex = HALF_DUPLEX_DISABLED)
: ifHALF_DUPLEX_ENABLED
get the first Tx pin for requested peripheral in thePeripheralPins.c
used for half-duplex
-
-
Add
enableHalfDuplexRx()
to enable Serial in Rx mode. Doing aread()
could be used but will avoid to perform a read. Useful beforeavailable()
usage -
void setHalfDuplex()
: enable half-duplex mode of an instance when it not instantiate in half-duplex mode. Must be call beforebegin()
in this case.
Serial4
sends byte to Serila3
, compare values then Serial3
resend it to Serial4
and compare.
Require to connect PA0
and PB10
.
All possible constructor are listed.
HardwareSerial Serial3(PA0);
HardwareSerial Serial4(PB10);
//HardwareSerial Serial3(PA_0);
//HardwareSerial Serial4(PB_10);
//HardwareSerial Serial3(UART4, HALF_DUPLEX_ENABLED);
//HardwareSerial Serial4(USART3, HALF_DUPLEX_ENABLED);
//HardwareSerial Serial3(PA0, PA0);
//HardwareSerial Serial4(PB10, PB10);
//HardwareSerial Serial3(PA_0, PA_0);
//HardwareSerial Serial4(PB_10, PB_10);
//HardwareSerial Serial3(NC, PA_0);
//HardwareSerial Serial4(NC, PB_10);
//HardwareSerial Serial3(NUM_DIGITAL_PINS, PA0);
//HardwareSerial Serial4(NUM_DIGITAL_PINS, PB10);
static uint32_t nbTestOK = 0;
static uint32_t nbTestKO = 0;
void test_uart(int val)
{
int recval = -1;
uint32_t error = 0;
Serial4.write(val);
delay(10);
while (Serial3.available()) {
recval = Serial3.read();
}
/* Enable Serial4 to RX*/
Serial4.enableHalfDuplexRx();
if (val == recval) {
Serial3.write(val);
delay(10);
while (Serial4.available()) {
recval = Serial4.read();
}
/* Enable Serial3 to RX*/
Serial3.enableHalfDuplexRx();
if (val == recval) {
nbTestOK++;
Serial.print("Exchange: 0x");
Serial.println(recval, HEX);
} else {
error = 2;
}
}
else {
error = 1;
}
if (error) {
Serial.print("Send: 0x");
Serial.print(val, HEX);
Serial.print("\tReceived: 0x");
Serial.print(recval, HEX);
Serial.print(" --> KO <--");
Serial.println(error);
nbTestKO++;
}
}
void setup() {
Serial.begin(115200);
Serial4.begin(9600);
Serial3.begin(9600);
}
void loop() {
for (uint32_t i = 0; i <= (0xFF); i++) {
test_uart(i);
}
Serial.println("Serial Half-Duplex test done.\nResults:");
Serial.print("OK: ");
Serial.println(nbTestOK);
Serial.print("KO: ");
Serial.println(nbTestKO);
while (1);
}
Note: Serial Rx/TX buffer size can be changed, see custom definitions
https://github.com/stm32duino/wiki/wiki/HardwareTimer-library
This part describes the STM32 libraries provided with the core.
STM32 SPI library has been modified with the possibility to manage several CS pins without to stop the SPI interface.
We do not describe here the SPI Arduino API but the functionalities added.
We give to the user 3 possibilities about the management of the CS pin:
- the CS pin is managed directly by the user code before to transfer the data (like the Arduino SPI library)
- or the user gives the CS pin number to the library API and the library manages itself the CS pin (see example below)
- or the user uses a hardware CS pin linked to the SPI peripheral
-
SPIClass::SPIClass(uint8_t mosi, uint8_t miso, uint8_t sclk, uint8_t ssel)
: alternative class constructor
Params SPImosi
pin
Params SPImiso
pin
Params SPIsclk
pin
Params (optional) SPIssel
pin. This pin must be an hardware CS pin. If you configure this pin, the chip select will be managed by the SPI peripheral. Do not use API functions with CS pin in parameter. -
void SPIClass::begin(uint8_t _pin)
: initialize the SPI interface and add a CS pin
Params SPI CSpin
to be managed by the SPI library -
void beginTransaction(uint8_t pin, SPISettings settings)
: allows to configure the SPI with other parameter. These new parameter are saved this an associated CS pin.
Params SPI CSpin
to be managed by the SPI library
Params SPIsettings
-
void endTransaction(uint8_t pin)
: removes a CS pin and the SPI settings associated
Params SPI CSpin
managed by the SPI library
Note 1 The following functions must be called after initialization of the SPI instance with begin()
or beginTransaction()
.
If you have several device to manage, you can call beginTransaction()
several time with different CS pin in parameter.
Then you can call the following functions with different CS pin without call again beginTransaction()
(until you call end()
or endTransaction()
).
Note 2 If the mode is set to SPI_CONTINUE
, the CS pin is kept enabled. Be careful in case you use several CS pin.
-
byte transfer(uint8_t pin, uint8_t _data, SPITransferMode _mode = SPI_LAST)
: write/read one byte
Params SPI CSpin
managed by the SPI library
Paramsdata
to write
Params (optional) ifSPI_LAST
mode
the CS pin is reset,SPI_CONTINUE
mode
the CS pin is kept enabled. Return byte received -
uint16_t transfer16(uint8_t pin, uint16_t _data, SPITransferMode _mode = SPI_LAST)
: write/read half-word
Params SPI CSpin
managed by the SPI library
Params 16 bitsdata
to write
Params (optional) ifSPI_LAST
mode
the CS pin is reset,SPI_CONTINUE
mode
the CS pin is kept enabled. Return 16 bits data received -
void transfer(uint8_t pin, void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST)
: write/read several bytes. Only one buffer used to write and read the data
Params SPI CSpin
managed by the SPI library
Params pointer todata
to write. Thedata
will be replaced by the data read.
Paramscount
is number of data to write/read. Params (optional) ifSPI_LAST
mode
the CS pin is reset,SPI_CONTINUE
mode
the CS pin is kept enabled. -
void transfer(byte _pin, void *_bufout, void *_bufin, size_t _count, SPITransferMode _mode = SPI_LAST)
: write/read several bytes. One buffer for the output data and one for the input data
Params SPI CSpin
managed by the SPI library
Params_bufout
is pointer to data to write.
Params_bufin
id pointer where to store the data read.
Paramscount
is number of data to write/read.
Params (optional) ifSPI_LAST
mode
the CS pin is reset,SPI_CONTINUE
mode
the CS pin is kept enabled.
This is an example of the use of the CS pin management:
#include <SPI.h>
// MOSI MISO SCLK
SPIClass SPI3(PC12, PC11, PC10);
void setup() {
SPI3.begin(2); //Enables the SPI3 instance with default settings and attaches the CS pin
SPI3.beginTransaction(1, settings); //Attaches another CS pin and configure the SPI3 instance with other settings
SPI3.transfer(2, 0x52); //Transfers data to the first device
SPI3.transfer(1, 0xA4); //Transfers data to the second device. The SPI3 instance is configured with the right settings
SPI3.end() //SPI3 instance is disabled
}
It is also possible to change the default pins used by the SPI
instance using above API:
void setMISO(uint32_t miso)
void setMOSI(uint32_t mosi)
void setSCLK(uint32_t sclk)
void setSSEL(uint32_t ssel)
void setMISO(PinName miso)
void setMOSI(PinName mosi)
void setSCLK(PinName sclk)
void setSSEL(PinName ssel)
SPI.setMISO(PC_4); // using pin name PY_n
SPI.setMOSI(PC2); // using pin number PYn
SPI.begin(2);
By default, only one Wire
instance is available and it uses the Arduino pins D14(SDA) and D15(SCL).
To use a second I2C port, a TwoWire
object should be declared in the sketch before the setup()
function:
#include <Wire.h>
// SDA SCL
TwoWire Wire2(PB3, PB10);
void setup() {
Wire2.begin();
}
void loop() {
Wire2.beginTransmission(0x71);
Wire2.write('v');
Wire2.endTransmission();
delay(1000);
}
Refers to I2C Timing to customize I2C speed if needed.
It is also possible to change the default pins used by the Wire
instance using above API:
void setSCL(uint32_t scl)
void setSDA(uint32_t sda)
void setSCL(PinName scl)
void setSDA(PinName sda)
/img/Warning-icon.png Have to be called before begin()
.
Wire.setSDA(PC_4); // using pin name PY_n
Wire.setSCL(PC2); // using pin number PYn
Wire.begin();
Available in core version greater than 1.5.0
Adding true
as last parameters of the 3 Wire::begin()
methods will enable the general call mode otherwise false
per default:
void begin(bool generalCall = false)
void begin(uint8_t, bool generalCall = false)
void begin(int, bool generalCall = false)
Wire.begin(true);
or Wire.begin(0x70,true);
By default I2C buffers are all aligned on Arduino API: 32 bytes.
Nevertheless it is possible to transfer up to 255 bytes:
-
In master mode: RX and TX buffers will automatically grow when needed, independently one from each other, and independently from other I2C instances.
Nothing to do from application point of view.
Warning: a bug in STM32 cube HAL (STM32 core v1.8.0) prevents to transfer exactly 255 bytes.(see #853) -
In slave mode: RX and TX buffer size can be statically redefined using hal_conf_extra.h or build_opt.h (at compilation time) thanks to switch
I2C_TXRX_BUFFER_SIZE
(see #853)
All I2C instances are impacted by change of this compilation switch.
Available in core version greater than 1.7.0
CMSIS DSP software library, is a suite of common signal processing functions for use on Cortex-M processor based devices.
The library is divided into a number of functions each covering a specific category:
- Basic math functions
- Fast math functions
- Complex math functions
- Filters
- Matrix functions
- Transform functions
- Motor control functions
- Statistical functions
- Support functions
- Interpolation functions.
The library has separate functions for operating on 8-bit integers, 16-bit integers, 32-bit integer and 32-bit floating-point value.
More info: https://arm-software.github.io/CMSIS_5/DSP/html/index.html
To use it, add:
#include <CMSIS_DSP.h>
arm_math.h
is then automatically include.
EEPROM emulation is based on Arduino API:
https://www.arduino.cc/en/Reference/EEPROM
Emulation is made in Flash, with all constraints related to Flash operation:
- whole sector/page erased and written for each write operation.
Can be very long depending on sector/page size - limited Flash life cycle write operation
In addition to Arduino API, to mitigate Flash constraints, it is possible to use buffered API:
Write operations are made in an intermediate RAM buffer, and only at the end (after writing several parameters for example) the buffer is copied in Flash. Thus only 1 write operation for a whole bunch of data.
Example is available here: https://github.com/stm32duino/STM32Examples/tree/master/examples/NonReg/BufferedEEPROM
void eeprom_buffer_fill(); // This function copies the data from flash into the buffer
void eeprom_buffer_flush(); // This function writes the buffer content into the flash
uint8_t eeprom_buffered_read_byte(const uint32_t pos); // Function reads a byte from the eeprom buffer
void eeprom_buffered_write_byte(uint32_t pos, uint8_t value); // Function writes a byte to the eeprom buffer
By default, EEPROM emulation storage correspond to the last sector/page of Flash,
and its size correspond to the size of the last sector/page.
Nevertheless it is possible to customize address and size used for EEPROM.
In this case, following switches should be defined (in variant.h or build_opt.h)
FLASH_BASE_ADDRESS
-
FLASH_DATA_SECTOR
orFLASH_PAGE_NUMBER
(depending on STM32 family used)
see example of variant implementation: https://github.com/stm32duino/Arduino_Core_STM32/pull/938
/img/Warning-icon.png Single/dual bank configuration:
Default last sector used correspond to default board configuration.
For example, NUCLEO_F767ZI is by default configured in single bank. Last sector correspond to this bank configuration.
If this configuration is changed, it is then mandatory to customize FLASH_BASE_ADDRESS
/FLASH_DATA_SECTOR
,
even to use last sector of Flash.
https://github.com/stm32duino/wiki/wiki/Servo-library
Since core version 1.9.0 (see PR #996), it is possible to mark variables as "noinit", which prevents them from being initialized to a fixed value at startup. This allows using these variables to remember a value across resets (since the reset itself leaves memory unchanged, it is only the startup code that normally resets all variable values, but that is prevented by noinit).
To do this, the variable must be placed in the .noinit
section by adding __attribute__((__section__(".noinit")))
(this is exactly the same as how this works on the original Arduino AVR core). Typically, you would also need to check the startup reason register so you can initialize the variable with a default on the first startup. For example, something like:
unsigned boot_count __attribute__((__section__(".noinit")));
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial port open
// Initialize the variable only on first power-on reset
if (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST))
boot_count = 1;
__HAL_RCC_CLEAR_RESET_FLAGS();
Serial.print("Boot number: ");
Serial.println(boot_count);
++boot_count;
}
void loop() { }
This shows the number of boots since the last POR by incrementing a noinit variable across resets. Note that when you first upload this, it might not start at 1 but at some arbitrary value, because typically the first boot after an upload is not a power-on-reset. To start at 1, disconnect and reconnect power.
-
Advanced usages