PIC16F1946 - Hardware SPI timing issues? - Page 1
Forum section: electronics / general

author: nexus
author_rank: Regular Contributor
timestamp: November 06, 2022, 09:35:42 pm
content: Hi all,I am working on a board I designed using a PIC16F1946 in the QFN-64 package as my main mcu. This is not my first PIC16 project, but it is the first where I am implementing SPI with PIC.I have been reading and studying the datasheet and online examples regarding hardware SPI using the MSSP peripheral.I would like to use typical 4-wire SPI to interface with a MAX 31865 RTD temp sensor interface IC. Please see attached schematic for pinout and connection info.I began with writing some code to initialize and read/write using hardware SPI on MSSP1. I am developing in MPLAB IDE X v6.05 using the free XC8 v2.40 compiler.As far as I can tell, the setup code I wrote follows the SPI configuration as per the datasheet. However, I am seeing this strange behavior on my hardware: the clock (CLK) and serial data out (SDI) are transitioning at the same time, on the same edge. This is not how SPI should operate, as data is shifted in on the clock transition... i.e. the clock should transition while the data remains constant. For some reason, they are transitioning on the same edge which will not allow SPI to work.My main.c is as follows:Code:/** File: max_test_1.c* Author: kev** Created on November 6, 2022, 1:05 PM*/#include <xc.h>#include <stdint.h>#include <stdio.h>#include "configs.h"void spi_init(){SSP1CON1 = (0<<5); //clear bit 5, disable serial portTRISC = (0<<3) | (1<<4) | (0<<5) | (0<<6); //clear RC3, RC5, RC6 (output: SCK1, SDO, CS) and set RC4 (input: SDI)SSP1CON1 = (0<<5); //clear bit 5, disable serial portSSP1CON1 = (0<<4) | (0<<3) | (0<<2) | (0<<1) | (0<<0); // 4: (CKP), 3-0:(SSPM = 0000; Master mode , Fosc/4)SSP1STAT = (0<<7) | (0<<6); //6: (CKE) transmit on clock transition to activeSSP1ADD = 0;//LATCbits.LATC6 = 1; //CS high//LATCbits.LATC6 = 0; //CS lowSSP1CON1 = (1<<5); //set bit 5, enable serial port}uint8_t spi_read(uint8_t buf){SSP1BUF = buf;while(!PIR1bits.SSP1IF);PIR1bits.SSP1IF = 0;return SSP1BUF;}uint8_t spi_read2(uint8_t buf){SSP1BUF = 0xAA;// Writes dummy bytewhile(!PIR1bits.SSP1IF); // Wait for transfer to completereturn SSP1BUF;// Returns received byte}void spi_write(uint8_t *buf){SSP1BUF = *buf;while(!PIR1bits.SSP1IF);PIR1bits.SSP1IF = 0;}void spi_write2(uint8_t buf){uint8_t rxtmp;SSP1BUF = buf;// Write to SPIwhile(!PIR1bits.SSP1IF); // Wait for transfer to completerxtmp = SSP1BUF;// Dummy read}void main(void) {spi_init();unsigned char i;while(1){for( i = 0; i < 255; i++ ) {spi_write2(i);}}}Any idea or explanations as to if I am doing something incorrectly in software to initialize and write to the SPI buffer?Attached a screenshot from my scope showing what happens when this code runs. The labels (CS, CLK, SDI, SDO) are as viewed from the MAX chip, so SDI here would be the same net as SDO on the PIC.

author: nexus
author_rank: Regular Contributor
timestamp: November 06, 2022, 09:38:48 pm
content: My code is a bit sloppy as of now as I have been scatterbrained trying to figure this out as of now, and I am by no means a pro software engineer.Attaching datasheet for PIC16F1946 for convenience.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 06, 2022, 09:55:10 pm
content: Your problem is CKE. You must set it to 1.But your code isn't doing that:Code:SSP1STAT = (0<<7) | (0<<6); //6: (CKE) transmit on clock transition to activeFix:Code:SSP1STAT = (0<<7) | (1<<6); //6: (CKE) transmit on clock transition to activeNo need to upload the datasheet (Wasting forum storage), just link it from microchip:datasheetEdit:For dummy read:Code:void spi_write2(uint8_t buf){uint8_t rxtmp;SSP1BUF = buf; // Write to SPIwhile(!PIR1bits.SSP1IF); // Wait for transfer to completerxtmp = SSP1BUF; // Dummy read}This is enough, and faster:Code:void spi_write2(uint8_t buf){SSP1BUF = buf; // Write to SPIwhile(!PIR1bits.SSP1IF); // Wait for transfer to completeSSP1BUF;// Dummy read}

author: jpanhalt
author_rank: Super Contributor
timestamp: November 06, 2022, 09:57:02 pm
content: I use similar chips (e.g., 16F18xx and 16F17xx). I also code in Assembly, so my code probably won't help you. The two variables I worry about are mode and system oscillator frequency. Usually mode is pretty clear, but I wrote a routine for all four modes. If I have a problem, I drop the fosc to something very reasonable, like 4 MHz or 8Mhz, then try each mode. Once that is working, I go to 16 MHz or 32 MHz to be sure they also work. In my present project, for example, with two peripherals on the same SPI, only one mode worked with both, and I needed to keep fosc at 16 MHz or below.Have you tried different modes at a low frequency?

author: Benta
author_rank: Super Contributor
timestamp: November 06, 2022, 10:10:27 pm
content: There are four modes of SPI transfers depending on clock phase and clock polarity.It looks to me like you have your clock polarity wrong.

author: nexus
author_rank: Regular Contributor
timestamp: November 06, 2022, 10:49:32 pm
content: So I set the CKE bit as suggested:Code:void spi_init(){SSP1CON1 = (0<<5); //clear bit 5, disable serial portTRISC = (0<<3) | (1<<4) | (0<<5) | (0<<6); //clear RC3, RC5, RC6 (output: SCK1, SDO, CS) and set RC4 (input: SDI)SSP1CON1 = (0<<4) | (0<<3) | (0<<2) | (0<<1) | (0<<0); // 4: (CKP), 3-0:(SSPM = 0000; Master mode , Fosc/4)SSP1STAT = (0<<7) | (1<<6); //6: (CKE) transmit on clock transition to activeSSP1ADD = 0;//LATCbits.LATC6 = 1; //CS high//LATCbits.LATC6 = 0; //CS lowSSP1CON1 = (1<<5); //set bit 5, enable serial port}It still looks like the data and clock transition are falling on the same edge. I took a few more screenshots from the scope showing what the lines look like; the data being generated for SPI is random as per the for loop, but all captures appear to have this property.Indeed, the timing diagram picture you shared does show clock transition occurring at half the data bit "length" in terms of time. This doesn't appear to be the case on my waveforms; I can't imagine that the hardware SPI perihperal has such as issue though? There is nothing in the errata for F1946 regarding this.Quote from: DavidAlfa on November 06, 2022, 09:55:10 pm

author: nexus
author_rank: Regular Contributor
timestamp: November 06, 2022, 10:51:35 pm
content: Quote from: jpanhalt on November 06, 2022, 09:57:02 pmI am running at a pretty low speed; I have the interal system clock set for 1 MHz and I'm using Fosc/4 for SPI timing, so the clock is running at 250 KHz.I believe the SPI mode is also set with the CKP/CKE bits that I have also set in the SSP1STAT register.

author: jpanhalt
author_rank: Super Contributor
timestamp: November 06, 2022, 11:09:58 pm
content: Microchip's definition of the 4 modes uses different terms than most peripheral producers use. (See: Wikipedia) Sure, it is usually obvious what will work, but it takes very little simulator time to check the other modes when my first choice doesn't work. Your choice of Fosc should not be a problem. SPI is spec'd to almost DC, as it is simply an exchange of bits.EDIT: I checked the errata for that chip and found this:QuoteI don't know if that is relevant to your situation, as I cannot read C reliably. But it would be a problem if one was polling the flag.

author: Benta
author_rank: Super Contributor
timestamp: November 06, 2022, 11:19:26 pm
content: I looked at the Maxim datasheet.They might call it "SPI", but it's nothing of the sort. Just the signal names prove that.Your MCU side has to provide 16 consecutive positive-edge clock cycles to the socalled "SPI" Maxim device.The first eight send an adress, the last eight read or write data.A standard SPI peripheral interface willnotsupport this.I think you may need to write your own "bit-banger". Perhaps using the SPI peripheral interface, but I can't say.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 06, 2022, 11:34:12 pm
content: Quote from: nexus on November 06, 2022, 10:49:32 pmAnd? Where's the problem?All you need to care of is the sampling (active) edge, your problem was that both data and clock were changing in the active edge, not the case anymore.The MAX31865 samples the data on the rising edges of the clock, when it goes high!In that very specific moment the data is sampled, and nothing else happens until the next edge.What happens to the data between the rising edges simply doesn't matter.This is reflected on the clock waveforms, data changing inmediately after the clock goes idle is a great feature, leaves more time for the signal to settle down before the next clock pulse happens.Edit: However I didn't notice Maxim idle clock level is high, so you need a diffferent clock polarity:Code:SSP1CON1 = (1<<4) | (0<<3) | (0<<2) | (0<<1) | (0<<0); // 4: (CKP=1)SSP1STAT = (0<<7) | (0<<6); //6: (CKE=0)

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 07, 2022, 12:00:10 am
content: And should look like this:

author: NorthGuy
author_rank: Super Contributor
timestamp: November 07, 2022, 01:03:46 am
content: Quote from: nexus on November 06, 2022, 10:49:32 pmClock has two edges - rising and falling. There are two possibilities:- The transmitter changes the data on the rising edge of the clock, the receiver samples the data on the falling edge of the clock (because the data is stable at this point)or- The transmitter changes the data on the falling edge of the clock, the receiver samples the data on the rising edge of the clockIf you're a transmitter you choose one or the other based on what the receiver does.With higher speeds (or with longer transmission delays), it gets a little bit more complicated.

author: nexus
author_rank: Regular Contributor
timestamp: November 07, 2022, 01:41:58 am
content: Quote from: Benta on November 06, 2022, 11:19:26 pmI see... i think this is what im primarily missing.Looking at the timing diagrams in the maxim datasheet, it shows that no serial data is sent from the Maxim during the first 8 clock cycles during a read or write. My SPI is currently only sending a byte at a time (8 clock cycles); no wonder the Maxim chip is not sending any data on SDO.I will have to modify the SPI write function to run for two bytes consecutively for both read and write. I will try to do this with still using the MSSP hardware with SPI. Worst case I will have to abandon the hardware peripheral and write a bitbang function.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 07, 2022, 01:48:50 am
content: Ifor a moment, one section showed one polarity type, other the opposite.Well, it can take any of those polarities automatically:QuoteSo any of these will work:CKP=1, CKE=0,CKP=0, CKE=1But NOT other combinations!

author: NorthGuy
author_rank: Super Contributor
timestamp: November 07, 2022, 02:21:59 am
content: Quote from: nexus on November 07, 2022, 01:41:58 amYou don't need to abandon anything.1. drive cs low2. send the byte with the address3. wait for the byte to be transmitted4. read and discard the returned byte5. send a zero byte6. wait for the byte to be transmitted7. read the returned data8. drive cs highIf you need to read more data, you most likely can repeat steps 5 to 7 several times before step 8.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 07, 2022, 03:38:08 am
content: Simple as this. Nothing else!Code:uint8_t spi_read(uint8_t addr){LATCbits.LATC6 = 0; // CS lowNop(); // Small delaySSP1BUF = addr; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = 0; // Send dummy bytewhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS highreturn SSP1BUF; // Return received data}Code:void spi_write(uint8_t addr, uint8_t data){LATCbits.LATC6 = 0; // CS lowNop(); // Small delaySSP1BUF = addr; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = data; // Send datawhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS high}Or combined:Code:uint8_t max_rw(uint8_t addr, uint8_t data){LATCbits.LATC6 = 0; // CS lowNop(); // Small delaySSP1BUF = addr; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = data; // Send dummy byte / datawhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS highreturn SSP1BUF; // Return received data}Code:max_rw(addr, data); // writedata = max_rw(addr, 0); // read

author: jpanhalt
author_rank: Super Contributor
timestamp: November 07, 2022, 06:23:46 am
content: I must have been asleep yesterday. I use the MAX31856 regularly with a PIC16F1789. The MAX31865 has the same interface. I need to use mode 3 as the GLCD display doesn't work with mode 1. If DavidAlfa's code doesn't work, you may find these settings helpful.Here is the setup for a PIC16F1789. You may need to change the register names as the 16F1946 has two MSSP modules; whereas, the 16F1789 only has one.Code:;*******************************************************************************;Setup SPI serial port;*******************************************************************************;;Mode 0 ;CKP=0, CKE=1; banksel SSPSTAT ;16F1783 DS refers to these registers as shown |B4; clrf SSPSTAT ;but the text omits the "1's", works either way |b4; bsf SSPSTAT,6 ;CKE=1 =active to idle |B4; movlw b'00100001' ;SPI clk=Fosc/4,bit<0>=0,clk=Fosc/16,bit<0>=1 |B4; movwf SSPCON1 ;bit<4>,CKP=0 = idle low, bit<5> enables SPI |B4;;Mode 1 ;CKP=0,CKE=0; banksel SSPSTAT ;all SPI controls in Bank4 |B4; clrf SSPSTAT ;CKE=0, SMP=0 (sample at middle) |B4; movlw b'00100001' ;enable SPEN, set SPI CLK to Fosc/16 |B4; movwf SSPCON1 ;CKP=0,master clk =Fosc/x (see below) |B4;;Mode 2 ;CKP=1,CKE=1; banksel SSPSTAT ;all SPI controls in Bank4 |B4; clrf SSPSTAT ;CKE=1, SMP=1 (sample at middle) |B4; bsf SSPSTAT,6 ; |B4; movlw b'00110001' ;enable SPEN, set SPI CLK to Fosc/16 |B4; movwf SSPCON1 ;CKP=1,master clk =Fosc/x (see below) |B4;;Mode3 ;CKP=1,CKE=0banksel SSPSTAT ; |B4clrf SSPSTAT ; |B4movlw b'00110001' ;enable serial port, clk = Fosc/16 |B4movwf SSPCON1 ; |B4;*******************************************************************************bsf SSPSTAT,7 ;set SMP, doess not affect mode |B4;NB: Modes 0,2,3 work with display. Mode 1 does not. Only Modes 1&3 work;with MAX31856. Tested at Fosc/16 = 2 MHz. Modes defined as per Wikipedia.;*******************************************************************************And here are the 3 routines I use to communicate to the MAX31856Code:;*******************************************************************************PutAddr ;call from any bank, returns |B0movlb 2 ; |B2bcf TC_CS ; |B2movlb 4 ; |B4movwf SSPBUF ; |B4btfss SSPSTAT,BF ; |B4bra $-1 ; |B4return;NB: Can combine Putreg and GetReg as what is written to SSPBUF doesn't matter;when reading. Factor off "ExitR_W" as a separate call. Saves about 7 LOCPutReg ;enter w/ value to be written in WREGmovwf SSPBUF ; |B4btfss SSPSTAT,BF ; |B4bra $-1 ; |B4movlb 2 ; |B2bsf TC_CS ; |B2movlb 0 ; |B0returnGetRegclrf SSPBUF ;if read not necessary, then just clrf |B4btfss SSPSTAT,BF ; |B4bra $-1 ; |B4movf SSPBUF,w ;move first read byte to WREG |B4return ;NB:returns in|B4 |B4EDIT: Since this uses polling, you may need to add a NOP or two before returning to take care of the errata issue. The SSPIF flag as in DavidAlfa's routine has the same problem. It might be worth checking the C-code disassembly to see if there is a delay.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 07, 2022, 12:49:17 pm
content: Just use CKE=1, CKP=0 and errata will be gone.

author: jpanhalt
author_rank: Super Contributor
timestamp: November 07, 2022, 02:15:34 pm
content: The Maxim MAX31865 only supports modes 1 & 3

author: jpanhalt
author_rank: Super Contributor
timestamp: November 10, 2022, 09:03:18 pm
content: @NexusDid you get it to work?

author: nexus
author_rank: Regular Contributor
timestamp: November 11, 2022, 06:31:51 pm
content: Sorry for the delay, busy week.I implemented DavidAlfa's code, which makes perfect sense to me when I read it line by line. When I run my test routine:Code:/** File: max_test_1.c* Author: kev** Created on November 6, 2022, 1:05 PM*/#include <xc.h>#include <stdint.h>#include <stdio.h>#include "configs.h"void spi_init(){SSP1CON1 = (0<<5); //clear bit 5, disable serial portTRISC = (0<<3) | (1<<4) | (0<<5) | (0<<6); //clear RC3, RC5, RC6 (output: SCK1, SDO, CS) and set RC4 (input: SDI)SSP1CON1 = (1<<4) | (0<<3) | (0<<2) | (0<<1) | (0<<0); // 4: (CKP), 3-0:(SSPM = 0000; Master mode , Fosc/4)SSP1STAT = (0<<7) | (1<<6); //6: (CKE) transmit on clock transition to activeSSP1ADD = 0;SSP1CON1 = (1<<5); //set bit 5, enable serial port//LATCbits.LATC6 = 1; //CS high//LATCbits.LATC6 = 0; //CS low}uint8_t spi_read(uint8_t addr){LATCbits.LATC6 = 0; // CS lowNOP(); // Small delaySSP1BUF = addr; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = 0; // Send dummy bytewhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS highreturn SSP1BUF; // Return received data}void spi_write(uint8_t addr, uint8_t data){LATCbits.LATC6 = 0; // CS lowNOP(); // Small delaySSP1BUF = addr; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = data; // Send datawhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS high}void main(void) {spi_init();unsigned char i;while(1){for( i = 0; i < 255; i++ ) {spi_write(0x80,i);}}}It seems to be working; I get 16 clock cycles, CS goes low which enables comm on the max device. There is data being written on SDI.I don't expect any data on SDO from the max since this is just writing random data.I tried moving on to using the library I had selected for this project. It is a C library for the MAX 31865 which seems pretty well structured and comprehensive.https://github.com/hepingood/max31865I added the library to my project and I am trying to implement the "shot" reading mode.I imported the code into the "driver_max31865_interface.c" file which is the blank interface template provided by the library to be tailored to the hardware.This is my "driver_max31865_interface.c" code:Code:#include "driver_max31865_interface.h"#include <xc.h>#include <stdint.h>#include <stdio.h>#define _XTAL_FREQ 1000000/*** @brief interface spi bus init* @return status code* - 0 success* - 1 spi init failed* @note none*/uint8_t max31865_interface_spi_init(void){SSP1CON1 = (0<<5); //clear bit 5, disable serial portTRISC = (0<<3) | (1<<4) | (0<<5) | (0<<6); //clear RC3, RC5, RC6 (output: SCK1, SDO, CS) and set RC4 (input: SDI)SSP1CON1 = (0<<4) | (0<<3) | (0<<2) | (0<<1) | (0<<0); // 4: (CKP), 3-0:(SSPM = 0000; Master mode , Fosc/4)SSP1STAT = (0<<7) | (1<<6); //7: (SMP) 1-end, 0-middle, 6: (CKE) transmit on clock transition to activeSSP1ADD = 0;SSP1CON1 = (1<<5); //set bit 5, enable serial portreturn 0;}/*** @brief interface spi bus deinit* @return status code* - 0 success* - 1 spi deinit failed* @note none*/uint8_t max31865_interface_spi_deinit(void){SSP1CON1 = (0<<5); //clear bit 5 of SSP1CON1 to disable serial portreturn 0;}/*** @brief interface spi bus read* @param reg is the register address* @param *buf points to a data buffer* @param len is the length of data buffer* @return status code* - 0 success* - 1 read failed* @note none*/uint8_t max31865_interface_spi_read(uint8_t reg, uint8_t *buf, uint16_t len){LATCbits.LATC6 = 0; // CS lowNOP(); // Small delaySSP1BUF = reg; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = 0; // Send dummy bytewhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS highreturn SSP1BUF; // Return received datareturn 0;}/*** @brief interface spi bus write* @param reg is the register address* @param *buf points to a data buffer* @param len is the length of data buffer* @return status code* - 0 success* - 1 write failed* @note none*/uint8_t max31865_interface_spi_write(uint8_t reg, uint8_t *buf, uint16_t len){LATCbits.LATC6 = 0; // CS lowNOP(); // Small delaySSP1BUF = reg; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = *buf; // Send datawhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS highreturn 0;}/*** @brief interface delay ms* @param ms* @note none*/int delay_ms(unsigned int ms){while(ms > 0){__delay_ms(1);ms--;}}void max31865_interface_delay_ms(uint32_t ms){delay_ms(ms);}/*** @brief interface print format data* @param fmt is the format data* @note none*/void max31865_interface_debug_print(const char *const fmt, ...){}And my main.c:Code:#include "configs.h" // configuration bits#include <xc.h>#include <stdint.h>#include <stdio.h>#include "driver_max31865_shot.h"uint8_t res;uint8_t i;float temp = 0;#define _XTAL_FREQ 1000000 // needed for delay functionsvoid main(void) {OSCCONbits.IRCF = 0x0B; //set internal osc to 1mhzOSCCONbits.SCS = 0x00; //system clock setupmax31865_shot_init(MAX31865_WIRE_2, MAX31865_RESISTOR_100PT, 400.f);while(1) {max31865_shot_read((float *)&temp);;max31865_interface_delay_ms(1000);}return;}I do get the correct waveforms on the CS and CLK lines when "max31865_interface_spi_read" is being polled every second. However, no data being written on SDI and subsequently nothing on SDO.Perhaps I'm not understanding how the library works completely?For example, the "max31865_interface_spi_read" function has 3 arguments: (uint8_t reg, uint8_t *buf, uint16_t len)"reg" should pass the correct register for the MAX31865 to the SPI routine, correct? (as defined in library file "driver_MAX31856.c")Code:/*** @brief chip register definition*/#define MAX31856_REG_CONFIG 0x00 /**< config register */#define MAX31856_REG_RTD_MSB 0x01 /**< rtd msb register */#define MAX31856_REG_RTD_LSB 0x02 /**< rtd lsb register */#define MAX31856_REG_HIGH_FAULT_MSB 0x03 /**< high fault msb register */#define MAX31856_REG_HIGH_FAULT_LSB 0x04 /**< high fault lsb register */#define MAX31856_REG_LOW_FAULT_MSB 0x05 /**< low fault msb register */#define MAX31856_REG_LOW_FAULT_LSB 0x06 /**< low fault lsb register */#define MAX31856_REG_FAULT_STATUS 0x07 /**< fault status register */This is where I begin scratching me head as a self-taught programmer. I have no formal computer science education, so I am just picking up lessons as I attempt to do things and they do not work.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 11, 2022, 08:14:25 pm
content: Quote from: nexus on November 11, 2022, 06:31:51 pmWhich of the registers are you trying to read?

author: nexus
author_rank: Regular Contributor
timestamp: November 11, 2022, 10:04:30 pm
content: Quote from: NorthGuy on November 11, 2022, 08:14:25 pmFrom the library (driver_max31865.c), the "max31865_single_read" function first reads and writes the config register (which is specified as 0x00) and then it reads the rtd msb register (0x01). So those would be the two registers I am trying to access during a call to "max31865_shot_read". I don't see any problems between the specified registers in the lib and the datasheet. Not sure why this isn't working assuming my SPI functions are also fine.

author: Benta
author_rank: Super Contributor
timestamp: November 11, 2022, 10:53:25 pm
content: From the first 'scope plot, your clock has the wrong polarity/phase. You should have positive going pulse when transferring data.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 12, 2022, 12:23:17 am
content: Quote from: nexus on November 11, 2022, 10:04:30 pmIf it reads the config register and the value of the config register is zero, then what we see on your scope picture is what you would expect - both SDI and SDO low all the time. To observe non-trivial SDO you need to read a register which has an address other than zero. Similarly, to see something on SDI, the register you read must contain a value which is not zero.

author: nexus
author_rank: Regular Contributor
timestamp: November 12, 2022, 05:45:11 am
content: Quote from: Benta on November 11, 2022, 10:53:25 pmI see. I reversed the clock polarity by setting bit 4 in SSP1CON1. This is my initialization code now:Code:uint8_t max31865_interface_spi_init(void){SSP1CON1 = (0<<5); //clear bit 5, disable serial portTRISC = (0<<3) | (1<<4) | (0<<5) | (0<<6); //clear RC3, RC5, RC6 (output: SCK1, SDO, CS) and set RC4 (input: SDI)SSP1CON1 = (1<<4) | (0<<3) | (0<<2) | (0<<1) | (0<<0); // 4: (CKP), 3-0:(SSPM = 0000; Master mode , Fosc/4)SSP1STAT = (0<<7) | (0<<6); //7: (SMP) 1-end, 0-middle, 6: (CKE) transmit on clock transition to activeSSP1ADD = 0;SSP1CON1 = (1<<5); //set bit 5, enable serial portreturn 0;}CKE (SSP1STAT bit 6) is still cleared (0 - Transmit occurs on transition from Idle to active clock state)This is what happens when the code first initializes and runs the "max31865_shot_init" function.Interestingly, SDO is appears to go high after the last clock cycle and then decays, almost capacatively.The polling "max31865_shot_read" function in the while loops looks like this:The same decay happens on SDO after the 16th clock cycle. It actually goes high when the last CLK pulse occurs, the decays.Perhaps the "gap" between the first 8 and last 8 clock cycles is too large, and the max is beginning to write serial data after the last 8 clock cycles?

author: NorthGuy
author_rank: Super Contributor
timestamp: November 12, 2022, 06:10:38 am
content: SDO (also called MOSI) is driven by the PIC. SDI (MISO) is driven by Max. Check your connections. Do you have anything else connected to these pins.Also, whewn you write this:Code:SSP1CON1 = (1<<5); //set bit 5, enable serial portyou overwrite all the bits of the register. It's the same as if you wroteCode:SSP1CON1 = 0x20; //set bit 5, enable serial portIt does set the 5-th bit to 1, but alse reset all the other bits to 0. What you need is this:Code:SSP1CON1 |= (1<<5); //set bit 5, enable serial portor this:Code:SSP1CON1bits.SSPEN = 1; //set bit 5, enable serial port

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 12, 2022, 07:27:15 am
content: Your code is overcomplicated.The MAX31865 only reads/write one byte for each register, so there's little sense on using a pointer to the buffer and data length.Also the two sepparate read/write functions are 99% redundant, that's why I showed how to unify them, the result will be the same.Why are you setting CKE low? You've been warned about the errata, also CKP=0,CKE=1 is the most common SPI mode.Why so many functions always returning 0 when there're no checks of any kind, or anything else to return? Just make them void.Thedelay_msfunction is redundant, simply use__delay_ms();Remember for the delay to work correctly,_XTAL_FREQmust be defined somewhere, matching the crystal frequency in Hz.You're defining it as 1MHz, is that correct? Seems too low?Writing SSPBUF before clearing SSP1IF will make it a bit faster, as the transmission starts earlier.Code:void max31865_interface_spi_init(void){TRISC = 1<<4; // Clear RC3, RC5, RC6 (output: SCK1, SDO, CS) and set RC4 (input: SDI)SSP1CON1 = 0;SSP1STAT = 0;SSP1ADD = 0;CKE1 = 1; // Transmit from idle to active transitionSSPEN1 = 1; // Enable serial port}void max31865_interface_spi_deinit(void){SSPEN1=0; // Disable serial port}uint8_t max31865_interface_spi_readwrite(uint8_t reg, uint8_t data){LATC6 = 0; // CS lowNOP(); // Small delaySSP1BUF = reg; // Send addresswhile(!SSP1IF); // WaitSSP1BUF = data; // Send data (Or dummy byte in read mode)SSP1IF = 0; // Clear flagwhile(!SSP1IF);SSP1IF=0; // Wait, clear flagLATC6 = 1; // CS highreturn SSP1BUF; // Return received data if any (Ignore in write mode)}Image test:Code:max31865_interface_spi_readwrite(0x99,0xAA);

author: nexus
author_rank: Regular Contributor
timestamp: November 20, 2022, 02:58:41 am
content: Coming back to this after another exhausting week.Quote from: NorthGuy on November 12, 2022, 06:10:38 amI see, thank you for pointing this out. I changed the code such that I am not clearing any bits that were previously set accidently.Quote from: DavidAlfa on November 12, 2022, 07:27:15 amKeeping things as simple as possible, here is what I am trying:Code:/** File: max_test_1.c* Author: kev** Created on November 6, 2022, 1:05 PM*/#include <xc.h>#include <stdint.h>#include <stdio.h>#include "configs.h"#define _XTAL_FREQ 1000000void spi_init(){SSP1CON1 = (0<<5); //clear bit 5, disable serial portTRISC = (0<<3) | (1<<4) | (0<<5) | (0<<6); //clear RC3, RC5, RC6 (output: SCK1, SDO, CS) and set RC4 (input: SDI)SSP1CON1 = (0<<4) | (0<<3) | (0<<2) | (0<<1) | (0<<0); // 4: (CKP), 3-0:(SSPM = 0000; Master mode , Fosc/4)SSP1STAT = (0<<7) | (1<<6); //6: (CKE) transmit on clock transition to activeSSP1ADD = 0;SSP1CON1bits.SSPEN = 1; //set bit 5, enable serial port}uint8_t max31865_interface_spi_readwrite(uint8_t reg, uint8_t data){LATCbits.LATC6 = 0; // CS lowNOP(); // Small delaySSP1BUF = reg; // Send addresswhile(!SSP1IF); // WaitSSP1BUF = data; // Send data (Or dummy byte in read mode)PIR1bits.SSP1IF = 0; // Clear flagwhile(!SSP1IF);SSP1IF=0; // Wait, clear flagLATCbits.LATC6 = 1; // CS lowreturn SSP1BUF; // Return received data if any (Ignore in write mode)}void main(void) {spi_init();OSCCONbits.IRCF = 0x0B; //set internal osc to 1mhzOSCCONbits.SCS = 0x00; //system clock setupmax31865_interface_spi_readwrite(0x00,0x62);__delay_ms(100);while (1){max31865_interface_spi_readwrite(0x01,0x00); //read RTD MSBsmax31865_interface_spi_readwrite(0x02,0x00); //read RTD LSBs// max31865_interface_spi_readwrite(0x03,0x00); //read high fault threshold status// max31865_interface_spi_readwrite(0x04,0x00); //read low fault threshold status// max31865_interface_spi_readwrite(0x05,0x00); //read low fault threshold MSB// max31865_interface_spi_readwrite(0x06,0x00); //read low fault threshold LSB// max31865_interface_spi_readwrite(0x07,0x00); //read fault status__delay_ms(1000);}}The first command is to set the configuration register: max31865_interface_spi_readwrite(0x00,0x62). Here's what that looks like on the scope:The second two commands which run once a second in the while loop are to read the RTD MSB and LSB registers. Here's what that looks like on scope:I have a tested PT100 hooked up across the RTD. My board is using a 400 Ohm reference resistor. Also attached full schematic project in case something here is obviously wrong, which I don't believe it is since I am using the MAX31865 reference design and I'm sure I have the SPI pins mapped correctly between the PIC and this chip.What I'm not sure of is the data that I'm reading from the MAX31865 on SDO. The RTD MSB register reads empty. The RTD LSB register is all high, and there is this strange decay during the end of each transmission. It looks to be capacitive, which I don't understand.Also, when I plug and unplug my RTD, nothing changes in the RTD MSB/LSB data from what I can see.Room temperature (20C) for the PT100 with a 400 Ohm Rreference should yield the following data from the RTD registers:Do I need to set the fault threshold registers as well? I don't see anything else that could be wrong here.I also replaced the MAX31865 chip. Digikey was out of stock when I first made the board, so I bought a breakout on eBay to get the part. Now that the QFN20 package is in stock again I ordered 10 brand new ones, so I'm sure the chip itself is genuine and functional.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 20, 2022, 05:37:25 am
content: The decay is normal, SDO becomes hi-z when chip is not active, PIC input has high impedance too, so it takes a bit to discharge.If you really need to get rid of it, add a 10K pulldown resistor to SDO.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 20, 2022, 02:50:38 pm
content: Quote from: nexus on November 20, 2022, 02:58:41 amI looked at the datasheet.In your communications, everything appears to be shifted by 1 bit. You're reading register 0x04 instead of 0x02, hence you get all ones. The response is shifted right by 1 bit too - 0x7f instead of 0xff. So, your SPI mode is wrong. Looking at their pictures in the datasheet, you need CKE = 0. The datasheet claims CKP doesn't matter (see Fig. 5). You have CKP = 0, CKE = 1.Also, it will not do any conversions on its own. You can do either automatic conversions, or one shot conversion. Either way, you need to write to the configuration register to get anything out of the chip. Read the datasheet for details.

author: jpanhalt
author_rank: Super Contributor
timestamp: November 20, 2022, 04:27:16 pm
content: Quote from: NorthGuy on November 20, 2022, 02:50:38 pmAh, deja vu post #17 & 19.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 20, 2022, 06:49:52 pm
content: Doesn't look shifted to me, I clearly see how SDO changes on the previous falling edge, sending 1 & 2.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 21, 2022, 05:16:42 am
content: Quote from: DavidAlfa on November 20, 2022, 06:49:52 pmSure. But the MAX device expects CKE = 0, so it sees it shifted.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 21, 2022, 05:54:57 am
content: Nope:Quote from: DavidAlfa on November 07, 2022, 01:48:50 am

author: jpanhalt
author_rank: Super Contributor
timestamp: November 21, 2022, 11:29:59 am
content: The MAX31865 (and its related chips for thermocouples) only works SPI modes 1 and 3. I documented that fact experimentally with the MAX31856.From MAX31865 datasheet page 16 (attachment):

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 21, 2022, 12:07:52 pm
content: Damn, I got the spi modes backwards, you're rightSo yeah:CKE=0,CKP=0 (Mode 1) or CKP=1(Mode 3)

author: jpanhalt
author_rank: Super Contributor
timestamp: November 21, 2022, 12:20:20 pm
content: Remember (post #8) that when CKE=0, the errata for the PIC16F1946 BF comes into play. That errata does not apply to the 16F18xx or 16F17xx chips so far as I know. That is based on my experience with a few examples in those latter series. I have not checked every chip in those latter series, however.

author: nexus
author_rank: Regular Contributor
timestamp: November 24, 2022, 04:19:07 am
content: Good news, I believe the SPI is somewhat working now.One mistake I found was that I kept trying to write to the config read register (0x00) instead of the actual write address (0x80). Doh!Another thing I realized is that I need to have 24 clock cycles to do a complete read of the RTD registers; 8 to write the register address, and 16 for the full 16 bits of data in the MSB temperature measurement register.Here is the code that works as far as setting the config and doing a 1 shot measurement by setting and clearing the 1-shot bit (D5):Code:#include <xc.h>#include <stdint.h>#include <stdio.h>#include "configs.h"#define _XTAL_FREQ 1000000void spi_init(){SSP1CON1 = (0<<5); //clear bit 5, disable serial portTRISC = (0<<3) | (1<<4) | (0<<5) | (0<<6)| (0<<6); //clear RC3, RC5, RC6 (output: SCK1, SDO, CS) and set RC4, RC7 (input: SDI, MAX RDY)SSP1CON1 = (1<<4) | (0<<3) | (0<<2) | (0<<1) | (0<<0); // 4: (CKP), 3-0:(SSPM = 0000; Master mode , Fosc/4)SSP1STAT = (0<<7) | (0<<6); //7 (SPI Master mode), 0 = Input data sampled at middle of data output time, 6: (CKE) transmit on clock transition to activeSSP1ADD = 0;SSP1CON1bits.SSPEN = 1; //set bit 5, enable serial port}uint8_t max31865_interface_spi_readwrite(uint8_t reg, uint8_t data){LATCbits.LATC6 = 0; // CS lowNOP(); // Small delaySSP1BUF = reg; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = data; // Send dummy byte / datawhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = data; // Send dummy byte / datawhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS highreturn SSP1BUF; // Return received data}void main(void) {spi_init();OSCCONbits.IRCF = 0x0B; //set internal osc to 1mhzOSCCONbits.SCS = 0x00; //system clock setupmax31865_interface_spi_readwrite(0x80,0xA0); //config register write addr: 0x80. set config register bits 1000000 = 0x80__delay_ms(100);while (1){max31865_interface_spi_readwrite(0x80,0xA8); //set 1-shot bit to trigger conversion (D5)max31865_interface_spi_readwrite(0x01,0x00); //read RTD MSBs// max31865_interface_spi_readwrite(0x02,0x00); //read RTD LSBsmax31865_interface_spi_readwrite(0x80,0xA0); //clear 1-shot bit (D5)// max31865_interface_spi_readwrite(0x03,0x00); //read high fault threshold status// max31865_interface_spi_readwrite(0x04,0x00); //read low fault threshold status// max31865_interface_spi_readwrite(0x05,0x00); //read low fault threshold MSB// max31865_interface_spi_readwrite(0x06,0x00); //read low fault threshold LSB// max31865_interface_spi_readwrite(0x07,0x00); //read fault status__delay_ms(1000);}}I have serial decode set up on the scope so you can clearly see what data is being transacted between the PIC and the MAX.Now one challenge I am having to read the data is to create a buffer for SSP1BUF. Since I need to read all 16 bits, and SSP1BUF is only 8 bits and cleared in the middle of the read, I need to capture all 16 bits of the two SSP1BUF reads into a single variable as the code executes..

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 24, 2022, 04:54:01 am
content: Why sending/receiving 2 data bytes?Code:uint8_t max31865_interface_spi_readwrite(uint8_t reg, uint8_t data){LATCbits.LATC6 = 0; // CS lowNOP(); // Small delaySSP1BUF = reg; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = data; // Send dummy byte / datawhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = data; // Send dummy byte / data <<--------- ?????while(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS highreturn SSP1BUF; // Return received data}Just make two readings and pack the data into a 16-bit variable:Code:uint8_t max31865_interface_spi_readwrite(uint8_t reg, uint8_t data){LATCbits.LATC6 = 0; // CS lowNOP(); // Small delaySSP1BUF = reg; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = data; // Send dummy byte / datawhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS highreturn SSP1BUF; // Return received data}uint16_t read_rtd(void){uint16_t rtd = max31865_interface_spi_readwrite(0x01,0x00); //read RTD MSBsrtd = rtd<<8 | max31865_interface_spi_readwrite(0x02,0x00); //read RTD LSBsreturn rtd;}

author: NorthGuy
author_rank: Super Contributor
timestamp: November 24, 2022, 04:28:31 pm
content: Quote from: nexus on November 24, 2022, 04:19:07 amNot necessarily, you can read each half separately.But you certainly can read two bytes at once, a la:Code:uint16_t max31865_interface_spi_read16(uint8_t reg){uint8_t temp;LATCbits.LATC6 = 0; // CS lowNOP(); // Small delaySSP1BUF = reg; // Send addresswhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;SSP1BUF = 0; // Send dummy bytewhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;temp = SSP1BUF;SSP1BUF = 0; // Send dummy bytewhile(!PIR1bits.SSP1IF); // WaitPIR1bits.SSP1IF = 0;LATCbits.LATC6 = 1; // CS highreturn (SSP1BUF << 8) | temp; // Return received data}

author: nexus
author_rank: Regular Contributor
timestamp: November 27, 2022, 05:19:49 am
content: Finally, I think I've reached the finish line on this project. Thank you for all the tips!The SPI was the biggest software hurdle for me since I had never used the hardware peripheral on a PIC, never used the MAX 31865 before, and in general am still a beginner.The program is right up against the memory limit for the PIC16F1946 when compiled using XC8 free v2.40. Attached main.c. It's not the prettiest code (I'm still a mechanical engineer on paper), but it works.Code:Memory Summary:Program space used 1FC4h ( 8132) of 2000h words ( 99.3%)Data space used 1D3h ( 467) of 200h bytes ( 91.2%)EEPROM space used 0h ( 0) of 100h bytes ( 0.0%)Configuration bits used 2h ( 2) of 2h words (100.0%)ID Location space used 4h ( 4) of 4h bytes (100.0%)I would have used the 16F1947 with twice the internal storage and RAM but none were in stock on digikey and mouser. I could always upgrade with a simple swap as I believe they are identical outside of the memory size difference.I will do a proper writeup on my project sitehttps://neonkev.com/.The metalwork was also completely custom. I designed the box in CAD and had it made by PCBway along with the boards. 316 stainless sheet with press fit PEM inserts. All M3 fasteners.For now, a quick demo of what this whole thing was about: a power supply for the Vision Engineering C073 14 point LED illuminator. It drives two separate strings of 7 high power LEDs, the 12V cooling fan, and reads the internal PT100 RTD in the LED heatsink to monitor temp. Brightness is controlled by PWM through the PIC and the rotary encoder. Push button for the encoder turns the main power on and off via relay to the LED driver ICs (MPM6010).I upgraded the fans in the light head and power supply to PWM controlled ones as well.They are autonomous by use of independent temperature controlled drivers:https://www.ebay.com/itm/284526780525LEDs on the original C073 ring light were also upgraded to the latest Luxeon star rebel LEDs, 235lm @ 700mA:https://www.luxeonstar.com/cool-white-5650K-sinkpad-ii-20mm-star-led-module-235lmQuick demo vid:

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 27, 2022, 05:50:13 am
content: Ensure to enable optimizations, O2 is free and will reduce the memory usage a lot.So you're "only” a mechanical enginneer but did that? That's pretty good!

author: nexus
author_rank: Regular Contributor
timestamp: November 27, 2022, 06:08:20 am
content: Quote from: DavidAlfa on November 27, 2022, 05:50:13 amAppreciate this tip! I had no idea that there were different levels of compiler optimization with the free version of XC8.I see that there are different options that have different tradeoffs in size vs speed.Can optimization actually increase speed/efficiency of code? Also, is it possible to "break" code with optimization that might otherwise work without it?Thank you! Yup, I am doing my masters in electrical now, but practically all of my electrical/computer science knowledge is self taught.It all started with nixie tubes; from there I dabbled with embedded design, power electronics, and down the rabbit hole I went...

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 27, 2022, 07:08:25 am
content: It could break some things if not properly programmed, like interrupts writing variables not declared as volatile or for/while loops doing nothing aka simple delays (Inserting a nop in the loop would fix it, but also need to calibrate the delay).Overall optimizations are great, both for speed and memory usage.Try it, if you have any issues, I could review your code searching for non optimization-friendly stuff, pics are really simple and It would be easy to spot.

author: nexus
author_rank: Regular Contributor
timestamp: November 27, 2022, 07:32:00 am
content: Understood. I did select optimization level 3, which is described in MPLAB as "Optimize yet more favoring speed (superset of O2)." This one appealed to me the most especially since I have a big timer interrupt that needs to run quickly to handle the rotary encoder and display.Code:Memory Summary:Program space used 1D7Ch ( 7548) of 2000h words ( 92.1%)Data space used 1D3h ( 467) of 200h bytes ( 91.2%)EEPROM space used 0h ( 0) of 100h bytes ( 0.0%)Configuration bits used 2h ( 2) of 2h words (100.0%)ID Location space used 4h ( 4) of 4h bytes (100.0%)Shrunk the program and data space by around 8% and seems to run on the hardware just fine.Think I might have put more effort into this unit than the OEM did in their production power supply.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 27, 2022, 04:31:17 pm
content: Quote from: nexus on November 27, 2022, 05:19:49 amCongratulations! Great project!

author: nexus
author_rank: Regular Contributor
timestamp: November 27, 2022, 06:03:29 pm
content: Thank you!It will be open source soon, so it could be a useful point of reference for a generic LED driver module for others.

author: Proto
author_rank: Contributor
timestamp: November 29, 2022, 10:11:43 pm
content: I did not see anyone mention Microchip Code Configurator (MCC). I started a PIC project a few years ago after a long hiatus and grumbled that Microchip did not appear to have many code examples compared to the TI MSP430 I had used more recently. This all changed when I fired up MCC. The PIC16F15324 part I used allows on chip peripherals to be mapped to almost any port pin through a well-designed GUI. The GUI extends to picking peripherals and lets you set them up without necessarily looking at the datasheet. MCC also reminds you to set up dependent peripherals like the TIMER1 for the Compar/Cpture subsystem. Now you press generate and you get initialization, general usage API's and interrupt functions created for you in C code, all neatly organized by peripheral name with source and hex file extensions. Its like having another programmer on your team writing your driver API's for you. Then you can modify them for your task while you also expand main.c for program flow. Is anybody using MCC? A mix of code examples can have conflicting setup requirements when put together while MCC does a very good job of keeping all the peripherals happy.