{"id":4754,"date":"2026-09-24T13:16:01","date_gmt":"2026-09-24T13:16:01","guid":{"rendered":"https:\/\/blog.embeddedexpert.io\/?p=4754"},"modified":"2026-09-24T13:16:04","modified_gmt":"2026-09-24T13:16:04","slug":"getting-started-with-stm32-low-layer-ll-spi-transmit-in-polling-mod","status":"publish","type":"post","link":"https:\/\/blog.embeddedexpert.io\/?p=4754","title":{"rendered":"Getting Started with STM32 Low Layer (LL): SPI Transmit in Polling Mod"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"559\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_lyx4c2lyx4c2lyx4-1024x559.jpg\" alt=\"\" class=\"wp-image-4755\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_lyx4c2lyx4c2lyx4-1024x559.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_lyx4c2lyx4c2lyx4-300x164.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_lyx4c2lyx4c2lyx4-768x419.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_lyx4c2lyx4c2lyx4-1150x627.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_lyx4c2lyx4c2lyx4-750x409.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_lyx4c2lyx4c2lyx4-400x218.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_lyx4c2lyx4c2lyx4-250x136.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_lyx4c2lyx4c2lyx4.jpg 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The Serial Peripheral Interface (SPI) is one of the most common protocols for connecting microcontrollers to external devices such as sensors, memory chips, and displays, making it an essential tool in any embedded developer&#8217;s toolkit. In this guide, we will take a hands-on approach to the SPI peripherals of the STM32F4 family using the Low-Layer (LL) drivers, giving you direct, register-level control over the configuration and data transfer process.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>In this guide, we shall cover the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Introduction.<\/li>\n\n\n\n<li>STM32CubeMX setup.<\/li>\n\n\n\n<li>Importing the project to STM32CubeIDE.<\/li>\n\n\n\n<li>Firmware development.<\/li>\n\n\n\n<li>Results.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Introduction:<\/h2>\n\n\n\n<p>The Serial Peripheral Interface (SPI) is one of the most common protocols for connecting microcontrollers to external devices such as sensors, memory chips, and displays, making it an essential tool in any embedded developer&#8217;s toolkit. Originally introduced by Motorola in the mid-1980s, it has since become a de facto standard supported by virtually every semiconductor manufacturer. If you have ever read data from an accelerometer or flash chip, driven a TFT display, or communicated with an nRF24L01 or LoRa radio module, you have used SPI.<\/p>\n\n\n\n<p>In this guide, we will take a hands-on approach to the SPI peripherals of the STM32F4 family using the Low-Layer (LL) drivers, giving you direct, register-level control over the configuration and data transfer process \u2014 while still letting STM32CubeMX generate the initialization code for us.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How SPI works<\/h3>\n\n\n\n<p>SPI is a <strong>synchronous, full-duplex, master\u2013slave<\/strong> protocol built around four signals:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Signal<\/th><th>Direction<\/th><th>Purpose<\/th><\/tr><\/thead><tbody><tr><td>SCLK<\/td><td>Master \u2192 Slave<\/td><td>Serial clock, generated by the master<\/td><\/tr><tr><td>MOSI<\/td><td>Master \u2192 Slave<\/td><td>Master Out, Slave In (data to the slave)<\/td><\/tr><tr><td>MISO<\/td><td>Slave \u2192 Master<\/td><td>Master In, Slave Out (data from the slave)<\/td><\/tr><tr><td>CS (NSS)<\/td><td>Master \u2192 Slave<\/td><td>Chip Select, usually active low<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>The master generates the clock and initiates every transfer. Internally, master and slave each hold a shift register, and with every clock pulse one bit is exchanged in <em>both<\/em> directions simultaneously. This has a practical consequence: <strong>every SPI transaction is an exchange<\/strong>, so reading a register from a slave still requires you to transmit (dummy) bytes \u2014 that&#8217;s what clocks the data back to you.<\/p>\n\n\n\n<p>The <strong>Chip Select<\/strong> line selects which slave to talk to. Multiple slaves can share one SCLK\/MOSI\/MISO bus, each with its own CS line. Although the CS function can be handled in hardware, in practice it is usually a plain GPIO pin \u2014 which is why you will typically see it handled alongside the SPI driver, as we do in this guide.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Clocking: CPOL, CPHA, and speed<\/h3>\n\n\n\n<p>Two configuration bits \u2014 <strong>clock polarity (CPOL)<\/strong> and <strong>clock phase (CPHA)<\/strong> \u2014 define the four standard SPI modes:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Mode<\/th><th>CPOL<\/th><th>CPHA<\/th><th>Description<\/th><\/tr><\/thead><tbody><tr><td>0<\/td><td>0<\/td><td>0<\/td><td>Clock idle low, data sampled on rising edge<\/td><\/tr><tr><td>1<\/td><td>0<\/td><td>1<\/td><td>Clock idle low, data sampled on falling edge<\/td><\/tr><tr><td>2<\/td><td>1<\/td><td>0<\/td><td>Clock idle high, data sampled on falling edge<\/td><\/tr><tr><td>3<\/td><td>1<\/td><td>1<\/td><td>Clock idle high, data sampled on rising edge<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>The mode and bit order (MSB- or LSB-first) must match what your slave expects \u2014 check its datasheet. Most devices use mode 0 or mode 3, MSB first.<\/p>\n\n\n\n<p>SPI owes its speed to its simplicity: no addressing, no acknowledgments, no protocol overhead \u2014 just raw clocked bits. On the STM32F4, the clock is derived from the APB bus via a prescaler (fPCLK\/2 up to fPCLK\/256), reaching <strong>42 Mbit\/s<\/strong> on SPI1 and <strong>21 Mbit\/s<\/strong> on SPI2\/SPI3 of a 168 MHz STM32F407. The trade-off is that correctness is entirely your responsibility: there is no built-in mechanism telling you whether the slave actually received the data.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The STM32F4 SPI peripheral<\/h3>\n\n\n\n<p>Depending on the specific device, the STM32F4 family provides up to <strong>six SPI peripherals (SPI1\u2013SPI6)<\/strong>, spread across the two APB buses \u2014 which matters, because the bus clock limits your maximum SPI clock. Each peripheral supports:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Master and slave<\/strong> operation<\/li>\n\n\n\n<li><strong>Full-duplex<\/strong>, <strong>half-duplex<\/strong> (single bidirectional wire), and <strong>simplex<\/strong> (TX-only or RX-only) modes<\/li>\n\n\n\n<li><strong>8-bit and 16-bit data frames<\/strong>, MSB- or LSB-first<\/li>\n\n\n\n<li>All four CPOL\/CPHA combinations<\/li>\n\n\n\n<li><strong>Motorola and TI frame formats<\/strong><\/li>\n\n\n\n<li>Baud-rate prescaler from fPCLK\/2 to fPCLK\/256<\/li>\n\n\n\n<li>Flexible <strong>NSS management<\/strong>: software control, hardware output, or NSS pulse generation between frames<\/li>\n\n\n\n<li>Hardware <strong>CRC calculation and checking<\/strong> with a programmable polynomial<\/li>\n\n\n\n<li><strong>DMA<\/strong> requests for both transmit and receive<\/li>\n\n\n\n<li><strong>Interrupts<\/strong> on TXE, RXNE, and error events (overrun, mode fault, CRC error, frame error)<\/li>\n\n\n\n<li>The <strong>BSY flag<\/strong> for detecting the true end of a transfer \u2014 critical for knowing when it is safe to release CS, as we will see later<\/li>\n<\/ul>\n\n\n\n<p>Note that the F4&#8217;s SPI has no internal FIFO and only fixed 8- or 16-bit frame sizes \u2014 newer families such as the F7, L4, and H7 added both, so keep this in mind when porting code.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why LL drivers?<\/h3>\n\n\n\n<p>ST provides two layers: the <strong>HAL<\/strong>, which offers portability and convenience at the cost of significant abstraction, and the <strong>LL drivers<\/strong>, which are thin inline functions mapped directly onto the peripheral&#8217;s registers. With LL there is no hidden state machine, no runtime overhead, and no surprise behavior \u2014 every flag you poll and every bit you set is visible in your code. That makes LL the ideal choice both for performance-critical applications and for anyone who genuinely wants to understand how the peripheral works. Best of all, you don&#8217;t have to choose between convenience and control: CubeMX can generate the LL initialization code for you.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. STM32CubeMX Setup:<\/h2>\n\n\n\n<p>Open STM32CubeMX as start a new project as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-1024x662.jpg\" alt=\"\" class=\"wp-image-3990\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-04-37-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Search for your STM32 MCU, select the MCU and click on Start New Project as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"724\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-1024x724.jpg\" alt=\"\" class=\"wp-image-3991\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-1024x724.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-300x212.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-768x543.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-1536x1087.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-2048x1449.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-1150x814.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-750x531.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-400x283.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_11-06-08-250x177.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, from Connectivity, select SPI1 and enable in Full Duplex Master as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-51-42-1-1024x662.jpg\" alt=\"\" class=\"wp-image-4757\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-51-42-1-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-51-42-1-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-51-42-1-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-51-42-1-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-51-42-1-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-51-42-1-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-51-42-1-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-51-42-1-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-51-42-1-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>This will set PA5, PA6 and PA7 as SCK, MISO and MOSI respectively.<\/p>\n\n\n\n<p>Next, we need CS pin, we shall use PA0 as CS pin, set PA0 as GPIO Output.<\/p>\n\n\n\n<p>Next, from project manager, Advanced Settings, set both SPI and GPIO as LL as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-54-50-1024x662.jpg\" alt=\"\" class=\"wp-image-4758\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-54-50-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-54-50-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-54-50-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-54-50-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-54-50-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-54-50-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-54-50-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-54-50-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-54-50-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, from Code Generator, enable Create peripheral initialization as pair of .c\/.h files per peripheral as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-1024x662.jpg\" alt=\"\" class=\"wp-image-4759\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-56-28-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, from Project, give the project a name and set toolchain\/IDE to STM32CubeIDE and click on Generate Code as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-57-59-1024x662.jpg\" alt=\"\" class=\"wp-image-4760\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-57-59-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-57-59-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-57-59-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-57-59-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-57-59-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-57-59-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-57-59-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-57-59-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/2026-09-24_15-57-59-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Thats all for the STM32CubeMX setup.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Importing the Project to STM32CubeIDE:<\/h2>\n\n\n\n<p>Open STM32CubeIDE, select your workspace and click on Launch.<\/p>\n\n\n\n<p>From the IDE, click File and select STM32 Project Create\/Import as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"873\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-1024x873.jpg\" alt=\"\" class=\"wp-image-3997\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-1024x873.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-300x256.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-768x655.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-1150x981.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-750x640.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-400x341.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21-250x213.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_16-57-21.jpg 1524w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, from Import STM32 Project, select STM32CubeMX\/STM32CubeIDE Project and click on Next as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"769\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-1024x769.jpg\" alt=\"\" class=\"wp-image-3998\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-1024x769.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-300x225.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-768x577.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-1150x863.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-750x563.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-400x300.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37-250x188.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/11\/2025-11-26_17-00-37.jpg 1172w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, select the folder that contains the .ioc file and click on Finish as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"652\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-1024x652.jpg\" alt=\"\" class=\"wp-image-4360\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-1024x652.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-300x191.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-768x489.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-1536x977.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-1150x732.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-750x477.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-400x255.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41-250x159.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/2026-04-11_11-31-41.jpg 1760w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Note: Project name is for reference only.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Firmware Development:<\/h2>\n\n\n\n<p>Open spi.h header file.<\/p>\n\n\n\n<p>In user begin Prototypes, declare the following function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void SPI_TransmitBytes(SPI_TypeDef *SPIx, const uint8_t *pData, uint16_t Size);<\/pre><\/div>\n\n\n\n<p>This function shall transmit N bytes in polling mode and accepts the following parameters:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>SPI instant, which SPI to user such SPI1, SPI2 etc. This guide uses SPI1.<\/li>\n\n\n\n<li>Pointer to the data to be transmitted.<\/li>\n\n\n\n<li>Size of the data to be transmitted.<\/li>\n<\/ul>\n\n\n\n<p>Next, in spi.c source file.<\/p>\n\n\n\n<p>In user code begin 1, declare the following function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void SPI_TransmitBytes(SPI_TypeDef *SPIx, const uint8_t *pData, uint16_t Size)<\/pre><\/div>\n\n\n\n<p>Within the function:<\/p>\n\n\n\n<p>First, enable the SPI peripheral if it is not enabled:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">LL_SPI_Enable(SPIx);<\/pre><\/div>\n\n\n\n<p>Next, transmit the data as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Wait for TX to be empty.<\/li>\n\n\n\n<li>Send the data.<\/li>\n\n\n\n<li>Loop for the number of data to be transmitted.<\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\tfor (uint16_t i = 0U; i &lt; Size; i++)\n\t{\n\t\/* Wait until TX buffer is empty *\/\n\t\twhile (!LL_SPI_IsActiveFlag_TXE(SPIx))\n\t\t{\n\t\t\t;\n\t\t}\n\n\t\tLL_SPI_TransmitData8(SPIx, pData[i]);\n\t}<\/pre><\/div>\n\n\n\n<p>Finally, wait until the busy flag is cleared:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">while (LL_SPI_IsActiveFlag_BSY(SPIx)) {}<\/pre><\/div>\n\n\n\n<p>Hence, the entire function as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void SPI_TransmitBytes(SPI_TypeDef *SPIx, const uint8_t *pData, uint16_t Size)\n{\n\n\tLL_SPI_Enable(SPIx);\n\n\tfor (uint16_t i = 0U; i &lt; Size; i++)\n\t{\n\t\/* Wait until TX buffer is empty *\/\n\t\twhile (!LL_SPI_IsActiveFlag_TXE(SPIx))\n\t\t{\n\t\t\t;\n\t\t}\n\n\t\tLL_SPI_TransmitData8(SPIx, pData[i]);\n\t}\n\n\t\/* Wait until the last byte is fully shifted out of the shift register *\/\n\twhile (LL_SPI_IsActiveFlag_BSY(SPIx)) {}\n\n}<\/pre><\/div>\n\n\n\n<p>Next, open main.c<\/p>\n\n\n\n<p>In main.c in user code begin PD, declare the following macros:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">#define CS_LOW()    LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_0)\n#define CS_HIGH()   LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_0)<\/pre><\/div>\n\n\n\n<p>These micros will handle the CS pin state.<\/p>\n\n\n\n<p>Next, in user code begin PV, declare the following variables:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">#define DataSize 5\nuint8_t dataBuffer[DataSize];<\/pre><\/div>\n\n\n\n<p>The databuffer will hold the data to be transferred over SPI with a size determined by DataSize.<\/p>\n\n\n\n<p>Next, in user code begin 2 in main function, we shall fill the array as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">for (int i=0;i&lt; DataSize; i++)\n{\n  dataBuffer[i]=i+1;\n}<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Finally, in user code begin 3 in while 1 loop:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">CS_LOW();\nSPI_TransmitBytes(SPI1,dataBuffer,DataSize);\nCS_HIGH();\nHAL_Delay(1);<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Set the CS pin to low.<\/li>\n\n\n\n<li>Transmit the data over SPI.<\/li>\n\n\n\n<li>Set CS pin to high.<\/li>\n\n\n\n<li>Wait for 1 ms.<\/li>\n<\/ul>\n\n\n\n<p>Thats all for the firmware.<\/p>\n\n\n\n<p>Save, build and run the project on your board.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"34\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1024x34.png\" alt=\"\" class=\"wp-image-4349\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1024x34.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-300x10.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-768x26.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1536x51.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1150x38.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-750x25.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-400x13.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-250x8.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1.png 1986w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. Results:<\/h2>\n\n\n\n<p>By probing PA5, PA7 and PA0 and using either oscilloscope or logic analyzer,. you should get the following:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"630\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/RigolDS22.png\" alt=\"\" class=\"wp-image-4761\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/RigolDS22.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/RigolDS22-300x185.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/RigolDS22-768x473.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/RigolDS22-750x461.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/RigolDS22-400x246.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/RigolDS22-250x154.png 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>We have successfully transmitted the 5 bytes.<\/p>\n\n\n\n<p>Next, we shall develop a reception code to receive data from a slave device.<\/p>\n\n\n\n<p>Stay tuned. <\/p>\n\n\n\n<p>Happy coding \ud83d\ude09<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The Serial Peripheral Interface (SPI) is one of the most common protocols for connecting microcontrollers to external devices such as sensors, memory chips, and displays, making it an essential tool in any embedded developer&#8217;s toolkit. In this guide, we will take a hands-on approach to the SPI peripherals of the STM32F4 family using the Low-Layer [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,11,12],"tags":[],"class_list":["post-4754","post","type-post","status-publish","format-standard","hentry","category-embedded-systems","category-peripheral-drivers","category-stm32"],"_links":{"self":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4754"}],"collection":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4754"}],"version-history":[{"count":1,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4754\/revisions"}],"predecessor-version":[{"id":4762,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4754\/revisions\/4762"}],"wp:attachment":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}