Why can you not manually control SPI CSS pins? - Page 1
Forum section: electronics / general

author: calvingloster
author_rank: Regular Contributor
timestamp: July 27, 2025, 05:08:37 am
content: I have a SAM4SD32C micro and I have the SPI NPCS0 (PA11) and NPCS1 (PA31) connected to two slave chip select pins. The one slave requires 3Byte communication words but the SPI peripheral of this microcontroller only allows 8bit and 16bit. As a result when I transfer data it automatically asserts (low to high) the NPCS0 pin when two bytes are transferred. Why can I not take control of the NPCS0 (PA11) pin and just toggle it via GPIO? I've already run the PCB so I cant change the lines now.On a second note, why cant I control any of the NPCSx lines as GPIO's? even if i have not selected PA31 as the chip select line? i cant even control PA31 even though I've set it as a GPIO

author: ataradov
author_rank: Super Contributor
timestamp: July 27, 2025, 06:16:30 am
content: You should be able to control them as regular GPIOs, this is a pretty common use case. I would double check your code. SPI controller should not override GPIO that are not configured for SPI.Can you control GPIO using the same code when SPI is not enabled at all?You won't be able to control them when SPI function is selected for the I/O pin. There can be only one controller, it is either PIO or SPI.

author: calvingloster
author_rank: Regular Contributor
timestamp: July 27, 2025, 07:33:20 am
content: See the code below. I have selected the PIO to control the NPCSx lines and the peripheral to control the MOSI, MISO and SCK lines. When i try just toggle the NPCSx lines then they do nothing:Code:void spiMasterSetupSW(void){//1.Enable the peripheral OF SPI clock in PMCPMC->PMC_PCER0 = PMC_PCER0_PID21;//2.Disable Parallel port (enables the peripheral)PIOA->PIO_PDR = (MISO | MOSI | SCK);//enable peripheral clock for and PIOAPMC->PMC_PCER0 = PMC_PCDR0_PID11;//enable the PIO controller for CSS0 and CSSxPIOA->PIO_PER = (CSS0 | CSS1);//set as outputPIOA->PIO_OER = (CSS0 | CSS1);//set initial state as highPIOA->PIO_SODR = (CSS0 | CSS1);//3.Disable SPI before configurationSPI->SPI_CR = SPI_CR_SPIDIS;//4.Reset SPI if any previous stateSPI->SPI_CR = SPI_CR_SWRST;//5.set master modeSPI->SPI_MR |= SPI_MR_MSTR;//6.disable fault detectionSPI->SPI_MR &= ~SPI_MR_MODFDIS;//7.select sample edge of clock, leading or trailingSPI->SPI_CSR |= SPI_CSR_NCPHA;SPI->SPI_CSR |= SPI_CSR_NCPHA;//8.setup clock and settings for all NPCSRs//SCBR = peripheral clock / SPCK Bit RateSPI->SPI_CSR |= (2 << SPI_CSR_SCBR_Pos);SPI->SPI_CSR |= (2 << SPI_CSR_SCBR_Pos);//11.The chip selects are directly connected to a peripheral device.SPI->SPI_MR &= ~SPI_MR_PCSDEC;//12.enable the SPISPI->SPI_CR = SPI_CR_SPIEN;// | SPI_CR_LASTXFER;} void spiWriteByteSW(uint8_t data){REG_PIOA_CODR = PIO_PA11;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = data;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = data;//wait for data to be transfered to the registerwhile (!(REG_SPI_SR & spiRDRF));(void)REG_SPI_RDR;REG_PIOA_SODR = PIO_PA11;}int main(void){/* Initialize the SAM system */SystemInit();setupMainClock();setupPins();spiMasterSetupSW();spiWriteByteSW(0x00);while (1){REG_PIOA_SODR = PIO_PA11;delay65();REG_PIOA_CODR = PIO_PA11;delay65();}}

author: mtwieg
author_rank: Frequent Contributor
timestamp: July 27, 2025, 12:53:21 pm
content: I would check the actual state of the PIO registers (PIO_OSR, PIO_PSR, and PIO_ODSR), it could be that something in the atmel/microchip driver is overruling your settings.

author: ataradov
author_rank: Super Contributor
timestamp: July 27, 2025, 02:59:22 pm
content: So, does the pin toggle when SPI is not initialized at all? I don't see anything immediately wrong with the code, but I also don't think SPI takes over all the pins. Something else is going on.At the same time, you can use 8 bit transfers and then you can do 2 or 3 byte transfers manually. And with an option to keep the CS asserted and LASTXFER bit, you have full control over the CS line. It is probably a better way of using it anyway, since it will maintain all the delays before and after data transfer.

author: calvingloster
author_rank: Regular Contributor
timestamp: July 28, 2025, 01:04:02 pm
content: It doesn't even toggle the pin. If i don't setup The SPI then the pin toggles. Its almost like you cant select any of the default NPCSx pins if you want to control it manually. makes no sense.If I use 8 bit mode or any other word size there is no logical way to control the NPCSx pin. I've spent so many hours trying to understand it but I can never get it working. Then when I think I have it figured out, the next sensor requires a different read length and I realize my previous code wasn't actually doing what it was suppose to, it just happen to work by fluke.The LASTXFER never works either. what is this bit supposed to do? Does it signify that this is the last bit being written so it must de-assert NPCSx line after its sent? I've tried using it but it has no effect at all. If i need to do a burst read of lets say 4 bytes. I can do it in 8 bit mode but then I cant read the return values. As soon as I read the return data register the bus thinks "oh looks like no one is doing anything, lets de-assert the NPCSx line".This code allows me to burst transfer 4 bytes and the NPCSx pin stays low but the MISO line is not read and I lose bytes because the Receive data register is only 8 bytes:Code:while (1){while (!(REG_SPI_SR & spiTDRE));SPI->SPI_TDR = (0x01);while (!(REG_SPI_SR & spiTDRE));SPI->SPI_TDR = (0x02);while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = 0x03;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = 0x04;while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while(1);}This code de-asserts the NPCSx line everytime i read the MISO data:Code:while (1){while (!(REG_SPI_SR & spiTDRE));SPI->SPI_TDR = (0x01);while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while (!(REG_SPI_SR & spiTDRE));SPI->SPI_TDR = (0x02);while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = 0x03;while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = 0x04;while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while(1);}If I change to 16 bit mode then problem is the same. It almost seems like I need to setup the SPI receive part with interrupts? is polling just not good enough?The only way i can manage to prevent NPCSx from se-asserting to add a delay between transfers by setting the DLYBCT fields. But this severely limits the data rate by half and does not seem efficient. The master mode flow diagram shows that the NPCSx line is not de-asserted when "new write to SPI_TDR occured while data transfer or delay DLYBCT". For this to be true the data needs to be written to the TDR very fast, if you wait to read the MISO data then its too late and it gets de-asserted. Ive attached and highlighted this part of the flow diagram."• DLYBCT: Delay Between Consecutive TransfersThis field defines the delay between two consecutive transfers with the same peripheral without removing the chip select.The delay is always inserted after each transfer and before removing the chip select if needed."

author: ataradov
author_rank: Super Contributor
timestamp: July 28, 2025, 02:21:07 pm
content: I would need to see more complete code to tell anything useful.

author: calvingloster
author_rank: Regular Contributor
timestamp: July 28, 2025, 03:14:30 pm
content: Code:#include "sam.h"#include "SPI_SAM4SD32C.h"#include "BMA530.h"//delay functionvoid delay650(void){for (volatile uint16_t x=0; x<650;x++){asm ("nop");}}void delay (void){for (volatile uint16_t x=0; x<65550;x++){asm ("nop");}}void setupMainClock(void){//remember you must declare F_CPU in compiler symbols and it must be equal to the frequency set here.PMC->CKGR_MOR |= (CKGR_MOR_KEY_PASSWD) | CKGR_MOR_MOSCRCEN;//Enabling the fast RC oscillator (MOSCRCEN = 1) and//changing the fast RC frequency (MOSCCRF) at the same time is not allowed!!//The fast RC must be enabled first and its frequency changed in a second stepPMC->CKGR_MOR |= (CKGR_MOR_KEY_PASSWD) | (CKGR_MOR_MOSCRCF_8_MHz);//wait until main clock readywhile (!(PMC->PMC_SR & PMC_SR_MCKRDY));//set the multiplier and divider//The PLLB Clock frequency is the PLLB input frequency multiplied by MULB + 1.PMC->CKGR_PLLBR |= CKGR_PLLBR_MULB(14) | CKGR_PLLBR_DIVB(1);//wait until main clock readywhile (!(PMC->PMC_SR & PMC_SR_MCKRDY));//set wait statesREG_EFC0_FMR |= EEFC_FMR_FWS(6);//select PLLB as the master clockPMC->PMC_MCKR |= PMC_MCKR_CSS_PLLB_CLK;delay650();}int main(void){/* Initialize the SAM system */SystemInit();setupMainClock();uart1Setup();spiMasterSetup();delay650();//bma530Setup();while (1){while (!(REG_SPI_SR & spiTDRE));SPI->SPI_TDR = (0x01);while (!(REG_SPI_SR & spiTDRE));SPI->SPI_TDR = (0x02);while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = 0x03;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = 0x04;while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while (!(SPI->SPI_SR & spiRDRF));(void)REG_SPI_RDR;while(1);}}Code:/** SPI_SAM4SD32C.c** Created: 04-12-2024 3:55:13 PM* Author: Atticus*/#include "SPI_SAM4SD32C.h"//Slave mode if the MSTR bit in the SPI_MR is written to 0:volatile uint8_t rxCounter = 0;volatile uint8_t newPacket = 0;volatile uint8_t rxBuffer;volatile uint8_t rxBufferTemp;volatile uint8_t rxCounterTemp = 0;//volatile uint8_t rxBuffer;volatile uint32_t status = 0;uint16_t time1Buffer;//this function is user defined and gets called by the PIO handler depending if the SPI allocated PIOA pins caused the interrupt//The dispatch functions lives in PIO_HANDLER.cstatic void spi_pio_interrupt_handler(void) {// Handle pio interrupts that belongs to the spi peripheralrxCounterTemp = rxCounter;//read if pin is high? why because this interrupt is setup only for when there is a rising edge?//yes BUT because of bouncing and ringing of the line we might get a rising edge if there is a falling edge//check if the pin is high?//if (PIOA->PIO_PDSR & PIO_PA11)//{//pin is highif (rxCounterTemp ==14){memcpy(rxBufferTemp,rxBuffer,rxCounterTemp);newPacket = 1;rxCounterTemp = 0;}rxCounter = 0;//uartTransmitChar('U');//rxCounterTemp = rxCounter;//memcpy(rxBufferTemp,rxBuffer,rxCounterTemp);//newPacket = 1;//rxCounter = 0;//}//else//{//pin is low//REG_PIOC_SODR = PIO_PER_P22;//REG_PIOC_CODR = PIO_PER_P22;//}//clear all pending interrupts before we exitPIOA->PIO_ISR;}void spiMasterSetup(void){//1.Enable the peripheral OF SPI clock in PMCPMC->PMC_PCER0 = PMC_PCER0_PID21;//2.Disable Parallel port (enables the peripheral)PIOA->PIO_PDR = (PIO_PA31 | PIO_PA11 | PIO_PA12 | PIO_PA13 | PIO_PA14);//try set css manually//PIOA->PIO_PER = (PIO_PA31 | PIO_PA11);//PIOA->PIO_OER = (PIO_PA31 | PIO_PA11);//PIOA->PIO_SODR = (PIO_PA31 | PIO_PA11);//setup NPCS1. its on peripheral B so need to mux itPIOA->PIO_ABCDSR |= (PIO_ABCDSR_P9);PIOA->PIO_ABCDSR &= ~(PIO_ABCDSR_P9);//3.Disable SPI before configurationSPI->SPI_CR = SPI_CR_SPIDIS;//4.Reset SPI if any previous stateSPI->SPI_CR = SPI_CR_SWRST;//5.set master modeSPI->SPI_MR |= SPI_MR_MSTR;//6.disable fault detectionSPI->SPI_MR &= ~SPI_MR_MODFDIS;//7.select sample edge of clock, leading or trailingSPI->SPI_CSR |= SPI_CSR_NCPHA;SPI->SPI_CSR |= SPI_CSR_NCPHA;//7.select different css pinSPI->SPI_MR |= SPI_MR_PCS(0b0000);//8.setup clock and settings for all NPCSRs//SCBR = peripheral clock / SPCK Bit RateSPI->SPI_CSR |= (12 << SPI_CSR_SCBR_Pos);SPI->SPI_CSR |= (12 << SPI_CSR_SCBR_Pos); // Same clock divider as NPCS0, adjust as needed//9.set 16 bytesSPI->SPI_CSR |= SPI_CSR_BITS_8_BIT;SPI->SPI_CSR |= SPI_CSR_BITS_8_BIT;//10.fixed peripheral selectedSPI->SPI_MR &= ~SPI_MR_PS;//11.The chip selects are directly connected to a peripheral device.SPI->SPI_MR &= ~SPI_MR_PCSDEC;//12.enable the SPISPI->SPI_CR = SPI_CR_SPIEN;// | SPI_CR_LASTXFER;}void spiSlaveSetup(void){//new//enable P11 for PIO controlPIOA->PIO_PDR = (PIO_PA12 | PIO_PA13 | PIO_PA14);//new//disable output line of P11PIOA->PIO_ODR = PIO_PA11;//new//setup PIO interrupts for the NSS pin first//enable PIO line interruptuint32_t interruptState = PIOA->PIO_ISR;uint32_t newState = PIO_PA11;PIOA->PIO_IER = (interruptState | newState);//enable glitch filter//PIOA->PIO_IFER = PIO_PDR_P11;//enable the slow clock filter to program the length of glitch we reject//PIOA->PIO_IFSCER = PIO_PDR_P11;//set the duration//PIOA->PIO_SCDR = 0;//enable additional interrupt modesinterruptState = PIOA->PIO_AIMMR;newState = PIO_PA11;PIOA->PIO_AIMER = (interruptState | newState);//set edge detectioninterruptState = PIOA->PIO_ELSR;newState = PIO_PA11;PIOA->PIO_ESR = (interruptState | newState);//set rising edge detection registerinterruptState = PIOA->PIO_FRLHSR;newState = PIO_PA11;PIOA->PIO_REHLSR = (interruptState | newState);//enable PIOA interruptsNVIC_EnableIRQ(PIOA_IRQn);//set priority?NVIC_SetPriority(PIOA_IRQn, 1);//by default the peripheral is on peripheral A so i dont really need to set it but be explicitPIOA->PIO_ABCDSR &= ~(PIO_ABCDSR_P11);PIOA->PIO_ABCDSR &= ~(PIO_ABCDSR_P11);PIOA->PIO_ABCDSR &= ~(PIO_ABCDSR_P12);PIOA->PIO_ABCDSR &= ~(PIO_ABCDSR_P12);PIOA->PIO_ABCDSR &= ~(PIO_ABCDSR_P13);PIOA->PIO_ABCDSR &= ~(PIO_ABCDSR_P13);PIOA->PIO_ABCDSR &= ~(PIO_ABCDSR_P14);PIOA->PIO_ABCDSR &= ~(PIO_ABCDSR_P14);NVIC_ClearPendingIRQ(SPI_IRQn);NVIC_EnableIRQ(SPI_IRQn);//setup interrupts firstuint32_t currentInterrupts = REG_SPI_IMR;//we want an interrupts for NSSR pin and Receive Data Register Fulluint32_t newInterrupts = (SPI_IER_RDRF | SPI_IER_NSSR);SPI->SPI_IER = (currentInterrupts|newInterrupts);//read data on leading edge of clock and setup on falling edgeSPI->SPI_CSR |= SPI_CSR_NCPHA;//set slave modeSPI->SPI_MR &= ~SPI_MR_MSTR;//1.Enable the peripheral OF SPI clock in PMCuint32_t currentPeripherals = REG_PMC_PCSR0;uint32_t newlySetPeripherals = PMC_PCER0_PID21;REG_PMC_PCER0 = (currentPeripherals|newlySetPeripherals);//this sets the address of the handler function "spi_pio_interrupt_handler" to spi_callback() function.//so that when you run spi_callback(), it actually runs this function -> spi_pio_interrupt_handler()//register_spi_interrupt(spi_pio_interrupt_handler);//enable interrupts__asm("CPSIE i");//finally enable the SPI peripheralSPI->SPI_CR = SPI_CR_SPIEN;}void spiWriteByte(uint8_t data){//REG_PIOA_CODR = PIO_PER_P11;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = data;//wait till data transfered to shift register//wait for data to be transfered to the registerwhile (!(REG_SPI_SR & spiRDRF));//return REG_SPI_RDR;//REG_PIOA_SODR = PIO_PER_P11;}uint16_t spiWrite2Byte(uint8_t data1,uint8_t data2){//REG_PIOA_CODR = PIO_PER_P11;uint16_t data = (((uint16_t)data1<<8) | data2);while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = data;//wait till data transfered to shift register//wait for data to be transfered to the registerwhile (!(REG_SPI_SR & spiRDRF));return REG_SPI_RDR;//REG_PIOA_SODR = PIO_PER_P11;}uint32_t spiWrite4Byte(uint8_t data1,uint8_t data2,uint8_t data3,uint8_t data4){uint16_t highByte = 0;uint16_t lowByte = 0;//The first 8 bits are junk. The MSB of the data is a parity bituint16_t firstByteJunkMask = 0b0000000011111111;uint16_t dataMsB = (((uint16_t)data1<<8) | data2);uint16_t dataLsB = (((uint16_t)data3<<8) | data4);//both writes occur after each other so CSS does not de-assertwhile (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = dataMsB;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = dataLsB;while (!(SPI->SPI_SR & spiRDRF));highByte = (REG_SPI_RDR & firstByteJunkMask);while (!(REG_SPI_SR & spiRDRF));lowByte = REG_SPI_RDR;//uartTransmitUint32_t (((uint32_t)highByte)<<16 | lowByte);//uartTransmitString("\n");return (((uint32_t)highByte)<<16 | lowByte);}//used for burst read/write starting at address and then autoincrimentingvoid spiWrite6Byte(uint8_t data1, int16_t *arrayPointer){uint16_t highByte = 0;uint16_t lowByte = 0;//The first 8 bits are junk. The MSB of the data is a parity bituint16_t firstByteJunkMask = 0b0000000011111111;uint16_t dataMsB = (((uint16_t)data1<<8) | 0x00);uint16_t dataLsB = 0x0000;//both writes occur after each other so CSS does not de-assertwhile (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = dataMsB;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = dataLsB;while (!(SPI->SPI_SR & spiRDRF));arrayPointer = (REG_SPI_RDR & firstByteJunkMask);while (!(REG_SPI_SR & spiRDRF));arrayPointer = REG_SPI_RDR;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = dataLsB;while (!(REG_SPI_SR & spiTDRE));REG_SPI_TDR = dataLsB;while (!(REG_SPI_SR & spiRDRF));arrayPointer = REG_SPI_RDR;while (!(REG_SPI_SR & spiRDRF));arrayPointer = REG_SPI_RDR;//uartTransmitUint32_t (((uint32_t)highByte)<<16 | lowByte);//uartTransmitString("\n");}Code:/** BMA530.c** Created: 26-07-2025 9:21:36 PM* Author: Atticus*/#include "BMA530.h"#include "SPI_SAM4SD32C.h"#include "UART_SAM4SD32C.h"//initialise the sensor//array to stor the accelerometer data in 2's complimentint16_t accData;uint8_t bma530ReadID(uint8_t addr){uint32_t chip_id = 0;//here we return everything but we only interested in the 3rd byte that gets returned for the IDchip_id = (spiWrite4Byte(((READ<<7) | addr),0x80,0x80,0x80));return (uint8_t)(chip_id>>8);}uint8_t bma530ReadHealth(uint8_t addr){uint32_t chip_health = 0;//here we return everything but we only interested in the 3rd byte that gets returned for the IDchip_health = (spiWrite4Byte(((READ<<7) | addr),0x80,0x80,0x80));return (((uint8_t)(chip_health>>8)) & HEALTH_MASK);}//0=System Health failed//1=Success//2=ChipID unknownuint8_t bma530Setup(void){//to initialize the accelerometer you must read its ID//It is this read that makes the sensor to realise what protocol to use, i2C of SPI//The sensor does not return anything// |// |// v//read the chip id again. now it will returnuint32_t chipID = 0;uint32_t chipHealth = 0;//no respond counteruint32_t noRspCnt = 0;(void)bma530ReadID(CHIP_ID);chipID = bma530ReadID(CHIP_ID);if (!(chipID == 0xC2)){return 2;}uart1TransmitString("CHIP_ID = ");uart1TransmitUint32_t(chipID);uart1TransmitString("\n");while (((chipHealth & 0x0F) != 0x0F)){chipHealth = bma530ReadHealth(CHIP_HEALTH);noRspCnt++;uart1TransmitString("CHIP_HEALTH = ");uart1TransmitUint32_t(chipHealth);uart1TransmitString("\n");if (noRspCnt == 50){chipHealth = 0x0F;}}if (noRspCnt == 50){return 0;}else{return 1;}//delay();//chipID = bma530ReadAdd(0x02);}//writes the data to the global accData Arrayvoid bma530ReadAcc(uint8_t addr){uint32_t chip_health = 0;//here we return everything but we only interested in the 3rd byte that gets returned for the IDspiWrite6Byte(((READ<<7) | addr),accData);}

author: ataradov
author_rank: Super Contributor
timestamp: July 29, 2025, 12:33:22 am
content: I don't know what is exactly wrong with your code. Here is a code that works. This is the most basic SPI master driver that uses software control over CS lines.The code just does 5 and7 byte write/read operation asserting two CS lines.This was tested on SAM4S Xplained Pro using this project as a templatehttps://github.com/ataradov/mcu-starter-projects/tree/master/sam4s. Replace main.c with attached here if you want to see it run as is.

author: calvingloster
author_rank: Regular Contributor
timestamp: July 29, 2025, 11:29:28 am
content: Thanks @ataradov you once again solved this for me. I firstly would like to say thank you for the effort of actually writing that code and posting it, you are an exceptional human being I really appreciate youSo the problem was i was ENABLING fault detection instead of disabling it as my commented code LIED!!!!Code://6.disable fault detectionSPI->SPI_MR &= ~SPI_MR_MODFDIS;This one register single handedly destroyed everything! I built two projects and slowly started replacing your code with mine till it stopped working and it ended up being the fault detection. What lesson did I learn here? Comments LIE!!!! I've read this in some programming books and today 29/07/2025 it finally bit me. From now on when I have a bug I cant figure out I will delete all my comments and start againOn another note is it correct to think that a fault is occurring because the SPI interface doesn't detect the NPCSx line working? as in it has no control over the line so it asserts an error?

author: Siwastaja
author_rank: Super Contributor
timestamp: July 29, 2025, 11:41:10 am
content: Quote from: calvingloster on July 29, 2025, 11:29:28 amThat kind of comment is indeed hard to keep in sync when experimenting and changing the code. One way to deal with this, instead of deleting all comments, is to comment it like this:SPI->SPI_MR &= ~SPI_MR_MODFDIS; // Fault detection bit: 0 = disabled, 1 = enabledNow this comment is equally usable whether you change the code to disable or enable it; the comment still explains the cryptic MR_MODFDIS name, and the polarity of the bit; the part you are much more unlikely to change (bonus points for explaining what "Fault detection" is or does in 4-5 words). Yet it does not tell you which value it actually writes, so you mentally refer back to the actual code, and therefore it can't get out of sync (avoid repetitionprinciple also applies to comments!).

author: ataradov
author_rank: Super Contributor
timestamp: July 29, 2025, 01:51:34 pm
content: Quote from: calvingloster on July 29, 2025, 11:29:28 amThis is why always remove comments from any code I review.Quote from: calvingloster on July 29, 2025, 11:29:28 amI would assume so.I saw it block too without this bit, so I was just looking at all the bits that may relevant and setting them one by one until things unblocked. The difference in my approach is that I know that this functionality (manual control of GPIOs) must be there, or this SPI controller is simply defective.Also, I would never rely on the default values of the registers. I always format and write the whole value at once, it makes it way easier to review the code, as it makes no assumptions about what is cleared and what is set.

author: calvingloster
author_rank: Regular Contributor
timestamp: July 29, 2025, 09:43:12 pm
content: Ahh ok that's a good point thanks, I'll set the whole register with a write operation instead of just read modify write the bits I think I need that way it's explicit. Thanks again👍🏻

author: Siwastaja
author_rank: Super Contributor
timestamp: July 31, 2025, 07:05:15 am
content: I also advice doing whole writes to peripheral registers. Benefits:* Smaller code (less flash used)* Faster code* Write is atomic (RMW is not atomic), sometimes this matters* You get well-known state in other bits. Idotrust the manufacturer reset values, but sometimes you have some other code running (that you probably wrote, but cannot fully manage in your head) and messing up with the registers.I consider RMW on peripheral register as a "special case" which is sometimes needed.

author: crm
author_rank: Newbie
timestamp: July 31, 2025, 04:45:25 pm
content: "I also advice doing whole writes to peripheral registers" - I have been mostly an Atmel person, but recently started working with STM32 (STM32C0 for now). They have "Reserved, must be kept at reset value" everywhere. Not sure what that means. They also have reset values documented. The reset values are usually zeroes for reserved bits. So, can you trust that they are going to remain zeroes and write whole values? Or should you assume they can change and therefore do RMW to obey the "must be kept at reset value" clause?

author: ataradov
author_rank: Super Contributor
timestamp: July 31, 2025, 04:48:12 pm
content: Must be kept at reset value happens with all vendors, but it is not that common overall. This is annoying, and I usually try to keep them at reset value, but I make a mask of such bits and still do a single write, just preserving the reserved values.But I only do that what I expect that those bits may actually have some undocumented meaning. Like if a UART says that, I ignore it. If some low level system component (like a calibration register) says that, I honor that.I generally don't care if the meaning of those bits may change when moving to a new part number, I don't make universal drivers and when moving to a new part, I always review the differences and check how they can affect my code.

author: Siwastaja
author_rank: Super Contributor
timestamp: August 01, 2025, 07:52:14 am
content: Quote from: crm on July 31, 2025, 04:45:25 pmIt means that when you write, you write the specified reset value to these reserved bits, which, as you say, is usually 0. No need to RMW due to this remark (it would be a very special case and hopefully documented).