libopencm3, stm32l100rc discovery and SPI issues - Page 1
Forum section: electronics / general

author: krish2487
author_rank: Frequent Contributor
timestamp: September 17, 2019, 08:23:51 am
content: Hello,I seem to have a peculiar issue with the SPI peripheral on stm32L100. For information, I am using a STM32l100RC discovery board. I am trying to interface a SPI Flash module, specifically the W25Q64.I am working off from the beginning STM32, libopencm3 freertos book by warren gay. I am not using the example code per say, just trying to get my hands dirty by using parts of his code to test that the device actually works and I am able to read/write to it. I am just using the barebones spi flash routines he has provided in the repo herehttps://github.com/ve3wwg/stm32f103c8t6I am trying to read the JEDEC ID of the flash module. The flash module is supposed to returnthe manufacturer ID, memory type and capacity in that order, specifically the bytes EF, 40, 17 hex. I did verify that the modules work by trying them out with a arduino uno.I tried to read the ID using the stm32l100rc discovery board and found that I am not able to get to the section where the command for reading the JEDEC ID is sent on the bus.The code just is stuck in the w25_wait section.Here is the screenshot of the sameThe code is just stuck in the wait routine.Here is the relevant codeCode:#include "libopencm3/stm32/rcc.h"#include "libopencm3/stm32/gpio.h"#include "libopencm3/stm32/usart.h"#include "libopencm3/stm32/spi.h"#include "delay.h"#define W25_CMD_MANUF_DEVICE 0x90#define W25_CMD_JEDEC_ID 0x9F#define W25_CMD_WRITE_EN 0x06#define W25_CMD_WRITE_DI 0x04#define W25_CMD_READ_SR1 0x05#define W25_CMD_READ_SR2 0x35#define W25_CMD_CHIP_ERASE 0xC7#define W25_CMD_READ_DATA 0x03#define W25_CMD_FAST_READ 0x0B#define W25_CMD_WRITE_DATA 0x02#define W25_CMD_READ_UID 0x4B#define W25_CMD_PWR_ON 0xAB#define W25_CMD_PWR_OFF 0xB9#define W25_CMD_ERA_SECTOR 0x20#define W25_CMD_ERA_32K 0x52#define W25_CMD_ERA_64K 0xD8#define DUMMY 0x00#define W25_SR1_BUSY 0x01#define W25_SR1_WEL 0x02static const char *cap = {"W25X16", // 14"W25X32", // 15"W25X64" // 16};static uint8_tw25_read_sr1(uint32_t spi) {uint8_t sr1;spi_enable(spi);spi_xfer(spi,W25_CMD_READ_SR1);sr1 = spi_xfer(spi,DUMMY);spi_disable(spi);return sr1;}static uint8_tw25_read_sr2(uint32_t spi) {uint8_t sr1;spi_enable(spi);spi_xfer(spi,W25_CMD_READ_SR2);sr1 = spi_xfer(spi,DUMMY);spi_disable(spi);return sr1;}static voidw25_wait(uint32_t spi) {uint8_t res= w25_read_sr1(spi);while ( (res & W25_SR1_BUSY) ){delay_ms(50);res = w25_read_sr1(spi);}}static uint16_tw25_manuf_device(uint32_t spi) {uint16_t info;w25_wait(spi);spi_enable(spi);spi_xfer(spi,W25_CMD_MANUF_DEVICE); // Byte 1spi_xfer(spi,DUMMY); // Dummy1 (2)spi_xfer(spi,DUMMY); // Dummy2 (3)spi_xfer(spi,0x00); // Byte 4info = spi_xfer(spi,DUMMY) << 8; // Byte 5info |= spi_xfer(spi,DUMMY); // Byte 6spi_disable(spi);return info;}static uint32_tw25_JEDEC_ID(uint32_t spi) {uint32_t info;w25_wait(spi);spi_enable(spi);spi_xfer(spi,W25_CMD_JEDEC_ID);info = spi_xfer(spi,DUMMY); // Manuf.info = (info << | spi_xfer(spi,DUMMY);// Memory Typeinfo = (info << | spi_xfer(spi,DUMMY);// Capacityspi_disable(spi);return info;}static voidw25_read_uid(uint32_t spi,void *buf,uint16_t bytes) {uint8_t *udata = (uint8_t*)buf;if ( bytes > 8 )bytes = 8;else if ( bytes <= 0 )return;w25_wait(spi);spi_enable(spi);spi_xfer(spi,W25_CMD_READ_UID);for ( uint8_t ux=0; ux<4; ++ux )spi_xfer(spi,DUMMY);for ( uint8_t ux=0; ux<bytes; ++ux )udata = spi_xfer(spi,DUMMY);spi_disable(spi);}int main(void) {//Always set the clock for the ports and peripherals first//Select HSICLK with PLL as sysclock source// Sysclock frequency is 16 Mhzrcc_clock_setup_hsi(&rcc_clock_config);//Enable GPIOC Peripheral clockrcc_periph_clock_enable(RCC_GPIOC);//Output mode, no pull ups or pull downgpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO8 | GPIO9);//Set GPIO8 and clear GPIO9 to see togglegpio_set(GPIOC, GPIO8);gpio_clear(GPIOC, GPIO9);//Sequence of steps for SPI transactions//Enable the clocks for the pin ports and peripheral for SPI//Setup pins for the alternate functions//PA4 = SPI1_NSS, PA5 = SPI1_SCK, PA6 = SPI1_MISO, PA7 = SPI1_MOSI//reset the SPI peripheral//initialize the SPI peripheral with the peripheral, clock, clock polarity,//clock phase, data frame format, frame format//set nss management to software otherwise SPI peripheral wont work//enable the SPI peripheral//send or read data using spi_send or spi_read functionsrcc_periph_clock_enable(RCC_GPIOA);rcc_periph_clock_enable(RCC_SPI1);gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO4 | GPIO5 | GPIO7);gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO6);gpio_set_af(GPIOA, GPIO_AF5, GPIO4 | GPIO5 | GPIO7);gpio_set_af(GPIOA, GPIO_AF5, GPIO6);spi_reset(SPI1);spi_init_master(SPI1, SPI_CR1_BAUDRATE_FPCLK_DIV_64, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST);spi_disable_software_slave_management(SPI1);spi_enable_ss_output(SPI1);while (1){gpio_toggle(GPIOC, GPIO8 | GPIO9);uint8_t index = 0;spi_enable(SPI1);w25_wait(SPI1);spi_xfer(SPI1, 0x9F);uint8_t result1 = spi_xfer(SPI1, 0x00);uint8_t result2 = spi_xfer(SPI1, 0x00);uint8_t result3 = spi_xfer(SPI1, 0x00);spi_disable(SPI1);delay_ms(500);}return 0;}I apologize for the length of the code.As I ve said earlier, the code is simply stuck in the w25_wait routine. it never progressess beyond.I tried debugging further and it looks like that the conditionCode:(res & W25_SR1_BUSY)in the w25_wait function always returns true. This is because the previous function invocation ofCode:uint8_t res= w25_read_sr1(spi);in the line above always returns 1.And this is the relevant snipper for the w25_read_sr1Code:w25_read_sr1(uint32_t spi) {uint8_t sr1;spi_enable(spi);spi_xfer(spi,W25_CMD_READ_SR1);sr1 = spi_xfer(spi,DUMMY);spi_disable(spi);return sr1;}But the logic analyzer capture shows that the data being returned is 0x00 not 0x01.I would be extremely grateful if someone more knowledgeable can help me identify the issue.I am more inclined to believe it is because I goofed up something in the initialization and/or the handling. But it is more or less a adapted version of the book's example code from the bluepill to the stm32l100rc.Thank you in advance.!! :-)Tl:DR : The spi wait function for the stm32 is returning 0x01 when it should be returning a 0x00 inspite of a logic analyzer capture showing that 0x00 is being read by the stm32.PS: The 1st image in the attachments is the spi transaction from the arduino uno, the subsequent ones are from the stm32l100rc discovery board

author: krish2487
author_rank: Frequent Contributor
timestamp: September 18, 2019, 07:45:32 am
content: bump!!Does any helpful samaritan have any experience with libopencm3 and can help me with this??Thanks in advance!!!

author: nugglix
author_rank: Regular Contributor
timestamp: September 18, 2019, 08:55:04 am
content: Refer to the function referenced below and start to debug your code w/ this knowledge.And, please, use the STM32 programming and reference manuals.This function contains a lot of hints what *might* have gone wrong:http://libopencm3.org/docs/latest/stm32l1/html/spi__common__all_8c_source.html#l00151

author: krish2487
author_rank: Frequent Contributor
timestamp: September 18, 2019, 09:06:37 am
content: Thanks!! :-)I ll have a look.I can only try this after I get home today.and yes, I do use the reference manuals. It is entirely possible, and mostly probable, I was debugging it wrong. I.e I diagnosed the issue wrong..

author: krish2487
author_rank: Frequent Contributor
timestamp: September 18, 2019, 08:20:16 pm
content: Ok. I have had some time to see if the spi_clean_disable() can fix the issue.Turns out modifying the w25_read_sr1() function to soCode:w25_read_sr1(uint32_t spi) {uint8_t sr1 = 0;spi_enable(spi);spi_xfer(spi,W25_CMD_READ_SR1);sr1 = spi_xfer(spi,DUMMY);// spi_disable(spi);spi_clean_disable(spi);return sr1;}or even toCode:w25_read_sr1(uint32_t spi) {uint8_t sr1 = 0;spi_enable(spi);// spi_xfer(spi,W25_CMD_READ_SR1);// sr1 = spi_xfer(spi,DUMMY);spi_send(spi, 0x05);sr1 = spi_read(spi);spi_send(spi, 0x00);sr1 = spi_read(spi);// spi_disable(spi);spi_clean_disable(spi);return sr1;}conducts the spi transaction only once.. and nothing after..So... Any further suggestions folks!!I am at my wits ends here... I have been staring at the libopencm3 framework / datasheets / errata / reference manual for so long in the past few days nothing is making sense anymore... It could be a very obvious and simple problem.. but for the love of everything holy, I cant see it... :-(

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: September 18, 2019, 10:26:51 pm
content: At this point you should consider writing your own SPI code at the bare metal level. SPI is one of the easiest peripherals to do this with. All of the details are in the reference manual for the STM32 you're using. Give it a try--it's much more satisfying spending a few days writing your own code rather than spending the time debugging someone else's library code.

author: TK
author_rank: Super Contributor
timestamp: September 18, 2019, 10:55:34 pm
content: You can also try with STM32CUBEMX and verify your setup is working. It generates all the code for SPI for you, it even includes sample code to call from your main() function and test.

author: techman-001
author_rank: Frequent Contributor
timestamp: September 19, 2019, 04:07:18 am
content: Quote from: Sal Ammoniac on September 18, 2019, 10:26:51 pmExactly and one earns real understanding from the exercise.Even writing your own bit banging SPI code is easy and there are tons of examples on the web.

author: nugglix
author_rank: Regular Contributor
timestamp: September 19, 2019, 07:07:09 am
content: Or count the disable vs. enable calls in the whole mess.Actually the code doesn't look like something coming from a decent programmer.So I suggest the TO should improve his understanding of the basics.The implementation of SPI only using the reference and programming manuals would help to understand how it works.At the moment it looks like poking around in the dark.

author: krish2487
author_rank: Frequent Contributor
timestamp: September 19, 2019, 07:35:44 am
content: @Sal ammoniac and @ techman-001that is always an option. I am trying to learn libopencm3 and stm32. so I figured I might as well put in the effort to debug any mistakes I might make. I asked if anyone else might have some experience with the specific framework. If all else fails, that is what I suppose I ll do.. :-)@TKI toyed with that idea as well, I might give it a shot over the weekend just for a comparision to see if I can get a barebones SPI code functioning on the cubemx platform.@nugglixperfectly valid observations. The chip select toggle is exactly the number of disable vs enable calls. :-) .If you look at the code, it is not trying to do anything except read the ID. It can be as messy and disorganised as possible. It can be the most barebones, ugly code for all I care if I can understand it and others can understand and help me debug it. refactoring it can happen later.I m, as I ve said before, trying to interface with a SPI flash chip. Till I get my basics right.. it is going to be poking in the dark. Isnt it the case with all of us while learning.PS: I still dont understand what is it that you are trying to hint towards. :-). From your posts I gather you do have some insights into how to debug and identify the issue. It seem like you hinting at the direction I need to take rather than outright state the answer. :-) . I must be exceptionally dumb to miss the hints.. If it not too much trouble. Can you explain what is it that you are expecting me to do..@everyoneI know, the reference manual gives a clearer picture for me to write the driver from scratch. I am trying to get familiar with STM32 as a processor. I would not try and reinvent the wheel by writing the peripheral drivers from scratch. I would prefer to keep one variable fixed, like the framework, and work with the chip.

author: nugglix
author_rank: Regular Contributor
timestamp: September 19, 2019, 07:56:26 am
content: Quote from: krish2487 on September 19, 2019, 07:35:44 ama) CS is not spi_enable/disable. I think I've seen unbalanced/misplaced spi_enable/disable calls. Check what SPI enable/disable does w/ the hardware. In the end, get rid of all these calls. Either use SPI or not. This enable/disable orgy doesn't help.b) There is no excuse for horrible code. Especially when others have to wade through this code.Try to reduce the code to show the problem.This is the most important step in solving a problem: formulate it exactly!Btw, this helps to avoid embarrassing questions.You simply find most of the bugs while shrinking your code.And mind one of my favorite quotes ever:"Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?"-- Brian Kernighan

author: krish2487
author_rank: Frequent Contributor
timestamp: September 19, 2019, 08:13:55 am
content: Thanks.. :-)From your suggestions I am able to take away two pieces of advice .1. Find out exactly what spi_enable / disable does and see if I can manually set the registers myself.2. Reduce the "borrowed" code and try to reduce the number of lines to do the same job..Good points. I will try that tonight!! :-) I admit, I cheated and tried to use the book's code without understanding thorooughly what they do.. I might as well now, take the effort to manually read the registers and see where I go to, even using the libopencm3 framework.'Quote from: nugglix onTodayat 07:56:26 am

author: techman-001
author_rank: Frequent Contributor
timestamp: September 19, 2019, 11:13:19 am
content: Quote from: krish2487 on September 19, 2019, 07:35:44 amApologies I know you are using CM3 with C, but I can't resist showing how easy it is (in Forth) to do a bit banged SPI. No libraries needed. The code below is from my old working project and shows the most important SPI parts, CLOCK and DATA.The bit banged example will give a useful understanding of SPI when you try to configure the STM32 SPI peripheral which doesn't look trivial to me. Just a Cortex-m0 SPI-1 contains 9 registers with a total of 52 bitfields.I wrote my first SPI example application, driving a Nokia 5110 LCD display and after I had finished I wondered why I'd ever bother to use the STM32F0 SPI peripheral (in this case) as it's so incredibly easy to bit bang.Serialize the SPI DATA and create a CLOCK and you're 90% done. All that's left is Chip Enable, Reset and command/data signals for the individual SPI device.: clockdata ( Creates clock and data bits from a data byte )ifdata-highelsedata-lowthenclk-highclk-low;: >serialize ( Serializes a byte, Most Significant Bit First )dup 128 and clockdatadup 64 and clockdatadup 32 and clockdatadup 16 and clockdatadup 8 and clockdatadup 4 and clockdatadup 2 and clockdata1 and clockdata;: >CMD ( Adds preamble and postamble signals to a command byte )CE->LOWDC->LOW>serializeCE->HIGH;">CMD" takes a data byte as the sole parameter and then generates the CE, DC and CE signals for the LCD. It then calls ">serialize".">serialize" takes the same data byte as the sole parameter and deconstructs it in chunks which are handed to "clockdata" as they're computed."Clockdata" then constructs the DATA and ClOCK signals for the LCD SPI.

author: krish2487
author_rank: Frequent Contributor
timestamp: September 19, 2019, 11:24:03 am
content: No apologies needed! :-)I am glad and thankful that many fine folks are offering advice and suggestions. :-)I am comfortable with C and it seemed natural to learn a framework within the same language. It was either cubemx or libopencm3.Libopencm3 was more intuitive and easy to understand for a noob, like me. The example given does seem very very easy to implement in forth.It would also mean I would need to learn forth. :-POf course, translating the same concepts to C and bitbanging seems equally easy. As ive said, I am just trying to learn the processor and its peripherals..Of course, the final goal is to use the SPI peripheral and the flash chip, use a filesystem like littleFS or SPIFFS for storing some text data.So I just started out.. one step at a time.. trying to read and control the flash memory.. and quite a journey it has been so far.. :-DQuote from: techman-001 onTodayat 11:13:19 am

author: techman-001
author_rank: Frequent Contributor
timestamp: September 19, 2019, 12:18:05 pm
content: Quote from: krish2487 on September 19, 2019, 11:24:03 amOpps, I should have mentioned I only gave the Forth example, as that's all I have. I've never written a SPI bitbanger in C or else I'd have given that code instead.My intent was just to demonstrate the ease of SPI bit banging, not try and convert you to Forth.Personally I like libopencm3 and Gcc for C on STM32.

author: nugglix
author_rank: Regular Contributor
timestamp: September 20, 2019, 05:33:13 pm
content: I hope the TO shares the findings of the debugging.Would be a novel approach to this kind of threads -- actually see if the TO had success.

author: krish2487
author_rank: Frequent Contributor
timestamp: September 21, 2019, 07:40:36 am
content: Well,I apologize for the delay in posting back here. I ve had quite a busy day in trying to understand what I was doing wrong.And it turns out, something absurdly simple.I have had the opportunity to read the book I mentioned earlier, and in the end, in the troubleshooting section, the author specifically mentions about gpio and alternate functions.It turns out I was configuring the SPI peripheral incompletely.I had forgotten to set the output mode options in the setup which threw the spanner in the works.This is the code with the changes highlightedCode:rcc_periph_clock_enable(RCC_GPIOA); rcc_periph_clock_enable(RCC_SPI1);gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO4 | GPIO5 | GPIO7);gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_40MHZ, GPIO4 | GPIO5 | GPIO7);gpio_set_af(GPIOA, GPIO_AF5, GPIO4 | GPIO5 | GPIO7);gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO6);gpio_set_af(GPIOA, GPIO_AF5, GPIO6);spi_reset(SPI1);spi_init_master(SPI1, SPI_CR1_BAUDRATE_FPCLK_DIV_128, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST);And consequently, the test code works fine, I am able to read the JEDEC ID, manufacturer ID and the unique ID just fine.I should have read the manuals more thoroughly and saved myself quite some hair pulling.:-)Though, from what I read in the errata, it could have very well been a silicon bug workaround that is coming into play here. from section 2.4.6 in the errata sheetCode:Corrupted last bit of data and/or CRC, received in Master mode withdelayed SCK feedbackDescriptionIn receive transaction, in both I2S and SPI Master modes, the last bit of the transacted frame is not captured when the signal provided by internal feedback loop from the SCK pin exceeds a critical delay. The lastly transacted bit of the stored data then keeps the value from the pattern received previously. As a consequence, the last receive data bit may be wrong and/or the CRCERR flag can be unduly asserted in the SPI mode if any data under check sum and/or just the CRC pattern is wrongly captured.In SPI mode, data are synchronous with the APB clock. A delay of up to two APB clock periods can thus be tolerated for the internal feedback delay. The I2S mode is more sensitive than the SPI mode since the SCK clock is not synchronized with the APB clock. In this case, the margin of the internal feedback delay is lower than one APB clock period.The main factors contributing to the delay increase are low VDD level, high temperature, high SCK pin capacitive load and low SCK I/O output speed. The SPI communication speed has no impact.WorkaroundThe following workaround can be adopted, jointly or individually:•Decrease the APB clock speed.•Configure the IO pad of the SCK pin to be faster.I have tried experimenting with the peripheral clock prescaler and the output speed and found that in some cases, it outright does not work, and in some combinations it just returns garbage data.Where the SPI clock tends to be higher-ish the data being returned is garbage.. as I ve mentioned in the original post... the transactions take place.. but garbage data is read..Anyway, thank you for the help folks!! :-) I hope this can help some one else later from making the same mistake.. You have been extremely helpful and patient with me and my dumbness.. :-P

author: nugglix
author_rank: Regular Contributor
timestamp: September 21, 2019, 07:49:16 am
content: Congrats!Nice to see that it worked out in the end.So one to remember:alwayscheck device initialization.Good luck for the remainder of the project!

author: krish2487
author_rank: Frequent Contributor
timestamp: September 21, 2019, 07:52:50 am
content: Thanks.. :-)As i ve said, you guys have been extremely help and patient with me. I cannot stress this enough.And yes, I shall double check my initialization code henceforth!!Oops, formatting seems to be messed up in the earlier post.This is the line that made the difference.Code:gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_40MHZ, GPIO4 | GPIO5 | GPIO7);