How can I use hardware NSS (SPI) on STM32F4? - arm

I tried to use the hardware NSS signal with the HAL library, but I can't find any function to make the NSS pin have a low or high level. I also tried to find the answer in the HAL documentation, but there isn't any information there either. All examples on the Internet contain only software NSS.
How is one supposed to use hardware NSS?

Somewhere I read that NSS is driven low as long as the SPI Master is enabled and driven high again if the SPI Master is disabled.
I tried it using the HAL library (Cube/STM32CubeMX) from ST with an STM32L476 and polling SPI1. Initialization before and de-initialization after transmission did not set the NSS pin, but it took a lot of time:
Init structure:
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_HARD_OUTPUT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7;
hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
Transmit sequence:
HAL_SPI_Init( &hspi1 );
HAL_SPI_TransmitReceive( &hspi1, btx, brx, l, 5 ); // timeout 5msec;
while( hspi1.State == HAL_SPI_STATE_BUSY ); // wait for xmission complete
HAL_SPI_DeInit( &hspi1 );
So I decided setting the pin manually using GPIO (using SPI_NSS_SOFT in init):
HAL_GPIO_WritePin( NSS1_GPIO_Port, NSS1_Pin, GPIO_PIN_RESET ); // NSS1 low
HAL_SPI_TransmitReceive( &hspi1, btx, brx, l, 5 ); // timeout 5msec;
while( hspi1.State == HAL_SPI_STATE_BUSY ); // wait xmission complete
HAL_GPIO_WritePin( NSS1_GPIO_Port, NSS1_Pin, GPIO_PIN_SET ); // NSS1 high
I used blocking transmission (no DMA or Interrupt), because it was fast enough and no other tasks waiting. DMA setup proved to take unacceptably longer time to send only 24 bytes at 20 MHz. It would be an acceptable alternative.
As far as I can see in the STM32L4xx manual Chapter 38.4.12/13, the automatic NSS goes high after each byte/word transmission and thus is not well usable for longer streams holding NSS low for the whole transmission.

You can use the NSS pin as a standard GPIO and drive it with an interrupt routine. You should do that part by software. First set the NSS low and then send your frame (HAL_SPI_Transmit).
After the slave gets all the frame, use the HAL_SPI_RxCpltCallback function and set the NSS pin high in that interrupt.
Don’t forget to connect the GPIO pin to the NSS pin on the slave.

The NSS pin may need a pull-up resistor if HiZ (not documented).

Look at the reference manual for your STM32 chip. I don't know if they're all the same but according to the one for mine (STM32WB55xx) and one I was able to find a public web link to (STM32F0)...
See the SPI functional description for NSS pin management (section 28.5.5 for the STM32F0 document I linked to), where it describes three modes:
Software NSS management (SPIx_CR1 register SSM bit = 1). The internal slave select information is driven internally by the SSI bit in register SPIx_CR1. The external NSS pin is free for applications to use.
Hardware NSS management (SSM bit = 0). This has two possible configurations depending on the SSOE bit in register SPIx_CR1:
NSS output enable (SSOE = 1). Only used when the MCU is master. The NSS signal is driven low as soon as SPI is enabled in master mode (SPE=1) and is kept low until SPI is disabled (SPE=0). A pulse can be generated between continuous communications if NSS pulse mode is activated. The SPI can not work in multimaster configuration with this NSS setting
NSS output disable (SSOE = 0). If the MCU is master, this allows multimaster capability. If NSS is pulled low, SPI enters master mode fault state and the device is automatically reconfigured in slave mode. In slave mode, the NSS pin works as a standard "chip select" input and the slave is selected while the NSS line is at low level.
Looking at the headers for my SoC, soft mode is SSM=1 (software NSS management). Hard output mode is SSM=0 and SSOE=1 (NSS output enable). Hard input mode is all zeros (NSS output disable).
If you're using hard output mode, you may also want to look at NSS pulse mode (section 28.5.12 in the STM32F0 document I linked to). It describes (with a timing diagram) how the system will leave NSS low most of the time, pulsing it high between data frames. If your device uses NSS/CS to synchronize the data frames, then this may be useful. Or maybe not, if your device reacts to NSS going high by aborting the current operation, since the text seems to indicate that it will pulse NSS between every word you transfer, not between buffers.
Unfortunately, this doesn't look like the most flexible implementation. Depending on your application, you may find it easier to leave it in soft mode and just toggle the NSS pin via GPIOs.

Related

Proper use of SPI functions in RP2040 C/C++ SDK with PGA2310 volume control IC

I've been working on a project where I use a PGA2310 volume control IC to set the volume of an audio signal. The chip's interface is supposedly SPI, but no matter how much I try, I just can't seem to get it right. The chip takes a 16 bit word over SPI that contains the left and right channel volume information.
I'm wondering if I am using the sdk all wrong and my code is whacked.
I guess my question is: Am I setting up and using the SPI functions correctly?
Here is my code
/**
* SPI interface for PGA2310 volume control ic
*/
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "pico/time.h"
const int CS = 0;
const int SDI = 1;
const int SCLK = 2;
const int LED_PIN16 = 16;
const int LED_PIN25 = 25;
uint16_t PGA2310();
uint16_t PGA2310() {
int baud = 1600000;
// SPI inistalization
spi_init(spi0, baud);
spi_set_format(spi0, 16, SPI_CPOL_0 , SPI_CPHA_0, SPI_MSB_FIRST);
// Sets SPI pins
gpio_set_function(CS, GPIO_FUNC_SPI);
gpio_set_function(SDI, GPIO_FUNC_SPI);
gpio_set_function(SCLK, GPIO_FUNC_SPI);
// Sets LED pins
gpio_init(LED_PIN16);
gpio_set_dir(LED_PIN16, GPIO_OUT);
gpio_init(LED_PIN25);
gpio_set_dir(LED_PIN25, GPIO_OUT);
// Data to send to chip. only sending first 8 bits for right channel.
// This is because I am currently just testing the output of the right channel
uint16_t high = 255;
uint16_t low = 100;
// Test by toggling between high and low volume states with LED indicators
while (true) {
gpio_put(LED_PIN25, 1);
spi_write16_blocking(spi0, &high, 1);
sleep_ms(2000);
gpio_put(LED_PIN25, 0);
spi_write16_blocking(spi0, &low, 1);
sleep_ms(2000);
}
}
I've tried a bunch of different methods that I found online for using SPI with the pico SDK. I've tried just sending 8 bits.
Let me know if more info is needed.
As explained in comments, SPI normally consists of 4 signals: /SS, SCLK, MOSI and MISO.
/SS = slave select, also known as chip select. Almost always active low.
SCLK = serial clock.
MOSI = Master Output Slave Input. The main data line. Your MCU seems to call this SDO (serial data out?).
MISO = Master Input Slave Output. Optional signal for duplex (two way) communication SPI. Your MCU seems to call this SDI (serial data input).
In this case the MCU is the master so you should be using MOSI/SDO. A MCU is almost always the master, except when communicating with other MCUs.
Additionally, always double check which CPOL and CPHA settings that the slave expects. Getting these wrong is a classic problem and can lead to subtle "clock skew" problems where everything works fine most of the time, then fail and give corrupt data intermittently.
The names you pick for variables/constants in C code do not have any effect on the hardware and they disappear as soon as the code is compiled. The most important thing is that you understand how the hardware works, read the documentation for the RP2040 SDK functions you are calling, and then pass the correct values to the RP2040 SDK functions.
The biggest problem is that you need to rethink every pin assignment. The RP2040 hardware SPI pin functions are defined in column F1 of the "GPIO Functions" section of the RP2040 datasheet. Here is an excerpt from that table:
This table tells us, for example, you cannot use pin 0 as the SPI0 CS function. If you assign that pin to be an SPI pin, it will be the SPI0 RX (data receiving) pin.
At a minimum, you need to pick one pin to be the SPI0 SCK pin and another to be the SPI0 TX pin, and you must connect those pins from the RP2040 to the equivalent pins on your device. Then you might also need to pick an RP2040 pin to control the CS pin on your device, if it has one. On the RP2040 side, this pin would be configured as a GPIO output pin and you would drive it low or high to enable your device. Refer to your device's datasheet for details about what signals it expects on its inputs and then use an oscilloscope to make sure you are generating compliant signals.
Another problem is that the spi_write16_blocking is probably modifying your high and low variables, so you will probably need to set those to the right values before each time that you use them. (So there is no point in having two different variables like that, just have one.)

Issue in interfacing E-Ink display with STM8S103F3P6 microcontroller

I am using Waveshare 1.54" ePaper Module. Using SPI peripheral:
CPU freq is 16Mhz
SPI Prescaler DIV by 8
MSB FIRST
CPOL=0, CPHA=1
The Display does not response but it respond with TI CC1310 properly.
The problem with SPI is after transmitting byte it does not go to ideal high state.
I have checked with logic analyser.
The SPI is initialised thus:
/****************** Initializing The SPI Peripheral ******************/
void SPI_setup(void)
{
CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, ENABLE); //Enable SPI Peripheral Clock
//Set the MOSI, MISO and SCk at high Level.
//GPIO_ExternalPullUpConfig(GPIOC, (GPIO_Pin_TypeDef)(GPIO_PIN_6),ENABLE);
SPI_DeInit();
SPI_Init(SPI_FIRSTBIT_MSB, //Send MSB First
SPI_BAUDRATEPRESCALER_8, //Fosc/16 = 1MHz
SPI_MODE_MASTER,
SPI_CLOCKPOLARITY_LOW, //IDEAL Clock Polarity is LOW
SPI_CLOCKPHASE_2EDGE, //The first clock transition is the first data capture edge
SPI_DATADIRECTION_2LINES_FULLDUPLEX, //Only TX is Enable
SPI_NSS_SOFT,
0x00);
SPI_Cmd(ENABLE);
}
This is pretty much the same problem you had at Issue in interfacing SPI e-ink display with PIC 18F46K22 only on a different processor. Worth noting that CPHA on STM8 has the opposite sense to CPE on PIC18 which may be the cause of your error. That is to say that CPHA=1 on the STM8 has the same effect as CKE=0 on the PIC18. You really have to look at the timing diagrams for each part carefully.
From https://www.waveshare.com/wiki/1.54inch_e-Paper_Module:
Compare with the STM8 reference manual:
Clearly you need one of:
CPHA=1 / CPOL=1 (SPI_CLOCKPOLARITY_HIGH / SPI_CLOCKPHASE_2EDGE) or
CPHA=0 / CPOL=0 (SPI_CLOCKPOLARITY_LOW / SPI_CLOCKPHASE_1EDGE)
If it is the SCLK that you want to be normally-high, then you need the first option - although I fail to see why that is "ideal", the Waveshare diagram clearly indicates that either is acceptable.

STM32 I2C set SDA to low

Is there any way to set the SDA and SCL pin of the I2C1 connection of the STM32 to low or high signal?
I use a security chip and I have to send a wake condition, with the following condition:
if SDA is held low for a period of greater than 60us, the device will exit low power mode and
after a delay of 1500us, it will be ready to receive I2C commands.
I've already tried to toggle the actual pin with HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);, but this isn't working.
I've configured my project with STM32CubeMX.
Thanks for your help.
In I2C, the START condition requires a High to Low transition, if you then send a dummy address 0, a NACK will be generated (or rather the lack of any response will be interpreted as a NACK). In a normal transaction, the software would respond to the NACK by generating a repeated START or a STOP condition, however this must be done in software, so all you have to do is nothing for 1.5ms. Thereafter you can generate the START with the device's actual address, and if the device is running it will generate an ACK.
I am not familiar with the HAL library driver, and frankly the documentation is abysmal, but it is possible that it does not give you the necessary control, and you will have to access the I2C peripheral at the register level for at least this procedure. You might try a zero-length I2C_MasterRequestWrite() call to address zero followed by a delay. An oscilloscope would be useful here to ensure the expected signal timing is being generated.
When you initialize I2C, GPIO pins mode is set to ALTERNATE MODE,so writing HAL commands won't work on it.
Using normal HAL libraries won't help you in this. You have to configure I2C protocol on your own using stm32 registers.
I recommend that the ownaddress of the slave address using the device of the using I2C channel sets like the below code.
I2C_InitStructure.I2C_OwnAddress1 = 0x30; // the unique slave address of the deviecs
because the master could be send the broadcast operation not the unique operation.

Setting nss_soft in Master (SPI)

I want to set the NSS pin to software mode in master using Nucleo STM32F103RB.
In the reference manual, they say,
In NSS Software mode, set the SSM and SSI bits in the SPI_CR1 register. If the NSS pin is required in output mode, the SSOE bit only should be set.
Why do we need to set the SSI bit with SSM?
What is the purpose of the SSOE bit?
It's related to the rarely used multi-master communication.
In a multi-master setup, the NSS signal controls access to the SPI bus. The ST documentation is unfortunately a bit vague there, but my understanding is that
NSS high input means the bus is free, and you are allowed to transmit
NSS low input means someone else is transmitting, and you become a slave.
Why do we need to set SSI bit with SSM?
If the SSM (Software Slave Management) bit is set in master mode, then the SSI (Slave Select Internal) bit becomes the source of the NSS signal instead of the pin. Setting SSI to 1 allows the master to transmit, setting it to 0 makes it a slave (clears the MSTR bit in CR1).
If you have a single master, just set
SPI->CR1 = SPI_CR1_MSTR | SPI_CR1_SPE | SPI_CR1_SSM | SPI_CR1_SSI
and don't worry about the rest. It's the most flexible way, and you can control as many slaves as you like with GPIO outputs connected to the CS lines separately. You can use the NSS pin as GPIO as well.
What is the purpose of SSOE bit?
It changes the NSS pin to an output. Initially set to high, it becomes low when the controller starts transmitting (when the DR register is written to). Note that it won't automatically become high again when the transfer is finished, but by setting SPI_CR1_SPE to 0.
Using SSOE can be useful when a single master is talking to a single slave, because CS is controlled by the SPI registers. Not having to talk to a GPIO peripheral at all, there isn't any need to load its base address to a register and holding it there, saving some cycles and a couple of bytes in flash, making it possible to use a register for something else by an optimizing compiler.

STM32F1 - Using master SPI on bare metal

I've been trying to port some of my AVR code to drive a simple SPI LCD to ARM as a learning exercise (I'm very new to ARM in general). For this I just need to use SPI in master mode.
I looked in the datasheet for my device (STM32F103C8) and found that the SPI1 pins I need, SCK and MOSI are mapped as alternative functions of PA5 and PA7, respectively, along with other peripherals (pg.29). My understanding is that in order to use the SPI function on these pins, I need to make sure that anything else mapped to the same pin is disabled. When looking at the defaults for the peripheral clock control register, however, it looks like the other features are already disabled.
I looked at the SPI section in the reference manual, including section 25.3.3 - Configuring the SPI in master mode. First I enabled the SPI1 master clock in APB2ENR and followed the steps in this section to configure SPI1 to my needs. I also changed the settings for PA5/7 to set their mode to "Alternate Function Output push-pull" (9.1.4). Finally, I enabled SPI1 by setting CR1_SPE.
From my reading, I had thought that by loading a value into the SPI1 data register after configuring SPI as above, the data would be shifted out. However, after writing the data, the TXE flag in the SPI status register never becomes set, indicating that the data I wrote into it is just sat there.
At this point, I'm assuming that there is something else I've failed to configure correctly. For example, I'm not 100% sure about what to do with the PA5/7 pins. I've tried to understand what I can from the datasheets, but I'm not getting anywhere. Is there anything else that needs to be done before it'll work?
I'm almost certain that you did not set SSM and SSI bits in SPIx->CR1 register. SPI in these chips is pretty simple, for the polled transfers you need to set SSM, SSI, SPE, MSTR, correct format (LSBFIRST, CPOL, CPHA) and proper baudrate (BR) in SPIx->CR1 and you're good to go.

Resources