ST Micro HAL with I2C buffer - Page 1
Forum section: electronics / general

author: Gibson486
author_rank: Frequent Contributor
timestamp: March 01, 2017, 09:09:45 pm
content: I am using a STM32F4 micro with the HAL library.For I2C (interrupt version), it seems like you need to have fixed buffer length for the slave to receive info. I am trying to figure out how I can figure out how to tell if something is in the I2C buffer. Unfortunately, the HAL is not very transparent, so I am having a tough time getting this info. I am simply looking to see the buffer size and iterate through it, but I am not sure how to determine the buffer size from the HAL. Any idea?

author: thm_w
author_rank: Super Contributor
timestamp: March 02, 2017, 12:22:07 am
content: Do you have a set message length or can it change?If its fixed set the buffer length to that length, then you shouldn't need to tell if something is in the buffer, just when its completely received.If it can vary you might need to set it to length 1 (at least that is the case for usart, unsure if i2c works the same way). Then implement your own buffer to monitor.Have you looked at the examples in: STM32Cube_FW_L4_V1.6.0\Projects\STM32L432KC-Nucleo\Examples\I2C\I2C_TwoBoards_ComIT\ (change to the F4 equivalent)?Possibly relevant:https://community.st.com/thread/34551-about-i2c-bus

author: Gibson486
author_rank: Frequent Contributor
timestamp: March 02, 2017, 01:34:15 am
content: I need it to be variable in size.Yeah, I saw the demo code. The issue is that the buffer size is predefined as RXBUFFERSIZE....but it is defined somewhere else. To make matters worse, the function call back just toggles a LED. Not what I was expecting. I was expecting to see a nice loop that iterates through an array. The only part of the code that does that is the buffer compare function, but I am still not sure how they filled their buffers in the first place.

author: capt bullshot
author_rank: Super Contributor
timestamp: March 02, 2017, 07:45:25 am
content: There's one thing to tell first place when it comes to STM32 / Cube / HAL:Don't use the provided device drivers. They appear to be coded on a very high level of cross-device abstaction level, leading to a large amount of bloat and inefficiency. They try to cover some use cases, if you have other requirements than what they provide (which isn't uncommon in world of embedded programming), IMHO it's easier to code your own driver.There's one thing to tell first place when it comes to STM32 I2C:Check which version if the I2C your chip has. The older one (inside the STM32F1 devices) has (too) many bugs.

author: Gibson486
author_rank: Frequent Contributor
timestamp: March 02, 2017, 12:57:44 pm
content: Quote from: capt bullshot on March 02, 2017, 07:45:25 amThanks...I noticed.I am starting to think that I made a mistake in picking ST Micro. Everyone was telling me to use it over Freescale. It seems like everything that ST gives you is buggy as hell as well.

author: capt bullshot
author_rank: Super Contributor
timestamp: March 02, 2017, 03:31:53 pm
content: Quote from: Gibson486 on March 02, 2017, 12:57:44 pmNo, it ain't that bad. They all build bugs into their stuff. At least they have big marketing, and you can get many of these discovers / nucleo boards for free (if you ask the right people) or little money (from mouser etc.)Regarding boards and tools, they made real big progress compared to the first STM32F1 (there wasn't even a useable low cost debugger). There's quite large amount of people in this world tinkering at all imaginable levels of skill with STM32F series, so you'll find a lot of support on the net. Just don't rely on their tools only (I'm also one of these "tinkerers":http://wunderkis.de/thestm32files.html)

author: mark03
author_rank: Frequent Contributor
timestamp: March 02, 2017, 05:02:33 pm
content: Quote from: Gibson486 on March 02, 2017, 12:57:44 pmThe ST-provided software is improving; they are making a real effort with the "Cube" stuff. IMO it's still fairly awful, due both to poor-quality work and misguided goals. Even so it can be handy if you need a prototype in a hurry and don't plan to carry the code forward where you'll have to maintain it.If you just code directly from the Reference Manual, STM32 is actually quite good. The writing (clarity and conciseness) is poor, but still better than most of their competitors, and everything you need to be successful is there.

author: Gibson486
author_rank: Frequent Contributor
timestamp: March 02, 2017, 06:40:31 pm
content: When you say reference manual, do you mean this?http://www.st.com/content/ccc/resource/technical/document/user_manual/2f/71/ba/b8/75/54/47/cf/DM00105879.pdf/files/DM00105879.pdf/jcr:content/translations/en.DM00105879.pdf

author: racemaniac
author_rank: Contributor
timestamp: March 02, 2017, 07:11:08 pm
content: Quote from: Gibson486 on March 02, 2017, 06:40:31 pmNope, if it's the reference manual, it really has "reference manual" as title on topfor example for the stm32f411:http://www.st.com/content/ccc/resource/technical/document/reference_manual/9b/53/39/1c/f7/01/4a/79/DM00119316.pdf/files/DM00119316.pdf/jcr:content/translations/en.DM00119316.pdfAnd concerning if stm32 is a good choice. They all have there good and bad sides i guess. I started with arduino & ended up developing on STM32. I recently also started with cubemx (after first coding my own spi & i2c drivers from scratch for it.). And it's indeed far from perfect, but it does give you a decent basis to start from. You can quickly do some tests on the peripherals you want to use, and gradually replace the HAL code to your own. At first you can still use the HAL code to initialize the peripheral. Once everything is initialized, creating your own driver to use it is not too hard if you read the reference manual on how it works.Haven't got much experience with other mcu's yet, but i'm like stm32 so far, affordable, powerful, and well enough documented (and with a code generator that can get you started). good enough for me for now

author: thm_w
author_rank: Super Contributor
timestamp: March 03, 2017, 12:09:14 am
content: Quote from: Gibson486 on March 02, 2017, 01:34:15 amTry a length of one and make your own buffering function.They filled the buffer by receiving number of bytes defined by RXBUFFERSIZE (in hal_i2c.c maybe the interrupt is function I2C_ITListenCplt?). Its a shortcoming of the HAL but also makes it simpler to use in basic cases.Code:if(HAL_I2C_Slave_Receive_IT(&I2cHandle, (uint8_t *)aRxBuffer, 1) != HAL_OK)In hal_i2c.c:Code:(+) Receive in slave mode an amount of data in non-blocking mode using HAL_I2C_Slave_Receive_IT()(+) At reception end of transfer, HAL_I2C_SlaveRxCpltCallback() is executed and user canadd his own code by customization of function pointer HAL_I2C_SlaveRxCpltCallback()So you could use the callback to fill a buffer maybe:Code:void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *I2cHandle){static int i = 0;buffer = *I2cHandle.pBuffPtr;if(i++ > max_length)i = 0;}

author: Gibson486
author_rank: Frequent Contributor
timestamp: March 03, 2017, 09:40:35 pm
content: Are you saying that RXBUFFERSIZE is not something you send, but it is determined by the actual mount of info in the buffer? I was thinking about making the buffer 1 byte, but I was unsure how it was determined. I thought RXBUFFERSIZE was something you had to define.

author: thm_w
author_rank: Super Contributor
timestamp: March 03, 2017, 10:20:21 pm
content: Quote from: Gibson486 on March 03, 2017, 09:40:35 pmRXBUFFERSIZE is something you define yes, in their example they define it as ~147 because they know how much will be received:Code:uint8_t aRxBuffer;#define RXBUFFERSIZE TXBUFFERSIZE#define TXBUFFERSIZE (COUNTOF(aTxBuffer) - 1)uint8_t aTxBuffer[] = " ****I2C_TwoBoards communication based on IT**** ****I2C_TwoBoards communication based on IT**** ****I2C_TwoBoards communication based on IT**** ";If you don't know how much you are receiving, AFAIK it should be possible to set it as 1, so you can figure out what message it is and how many bytes it is.

author: Gibson486
author_rank: Frequent Contributor
timestamp: March 06, 2017, 09:06:42 pm
content: Let me ask this. If we read 8 bits at a time (turn the buffer size into 1), does reading a byte clear out the byte you just read? If so, I imagine there is a register that tells me when the I2C buffer is empty.

author: thm_w
author_rank: Super Contributor
timestamp: March 06, 2017, 10:47:07 pm
content: Quote from: Gibson486 on March 06, 2017, 09:06:42 pmIf we are talking about the high level software, reading does nothing. If you start a new HAL receipt of a byte then it will probably "clear" the previous byte. This is why you copy it to your own buffer.At the low level there is a single byte data buffer (I2C_DR::DR pg 488)QuoteIf you had more than one byte come in without reading the register, you will get an overrun flag, and the new data will be lost.So either you read that byte manually upon interrupt, get HAL to do it, or get DMA to dump it somewhere. End result is you are always doing "something" after every byte is received.

author: Gibson486
author_rank: Frequent Contributor
timestamp: March 07, 2017, 05:45:26 pm
content: So, does HAL_I2C_Slave_Receive_IT() need to be called for every byte? I am a little confused because if you set the buffer size to one, wouldn't it just transfer one byte? Also, if this in an interrupt, why do we need to call HAL_I2C_Slave_Receive_IT() in the main loop? Don't we go directly to the function call back?

author: thm_w
author_rank: Super Contributor
timestamp: March 07, 2017, 09:54:01 pm
content: Quote from: Gibson486 on March 07, 2017, 05:45:26 pmYou would have to call it for every byte, yes, because you don't know the length of the i2c packet. Once you receive the one byte, then you decide if you need to receive more or not.May also be possible to call it once set to the length of your shortest length packet, and then receive more if needed.HAL_I2C_Slave_Receive_IT() is called to:- Check i2c peripheral has been setup- Set up HAL internal locks and mode tracking, etc.- Enable i2c interruptThere is a lot of housekeeping its doing. Sure some of that stuff is not useful to you, but thats the cost of ease of use.If that doesn't work for you, then you will need to write your own interrupt functions to handle incoming data.

author: Gibson486
author_rank: Frequent Contributor
timestamp: March 09, 2017, 07:53:32 pm
content: I think I may need to write my own driver. I tried doing it with one byte, but the problem is that it hangs. I suspect the clock is being pulled low after the first byte. The issue, as I dug further, is that the HAL for the interrupt receive only allows access to what you request in the function. Beyond on that, it seems that there is no way to have it cycle through multiple bytes by accessing the DR register. Correct me if I am wrong....

author: WillHuang
author_rank: Contributor
timestamp: March 13, 2017, 12:21:22 am
content: If it helps, here's a small I2C driver I wrote for the STM32F303. It uses CMSIS:Code:/*!********************************************************************************* \brief Initializes the I2C1 module* 100 KHz Mode, 140ns rise time, 40ns fall time* PB6 - SCL (AF4)* PB7 - SDA (AF4)* \param 'interrupt' ENABLE OR DISABLE interrupt* \returns none*********************************************************************************/void I2C1_Init(void){if(!IsInit){GPIOBClock(1); // Enable GPIOB ClockGPIOB->MODER &= ~(GPIO_MODER_MODER6 | GPIO_MODER_MODER7); // Reset PB6 and PB7 Mode typeGPIOB->MODER |= GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1; // Set PB6 and PB7 to AF modeGPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7; // Set output type to be open drainGPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6 | GPIO_OSPEEDER_OSPEEDR7; // Output speed to be the highestGPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR6 | GPIO_PUPDR_PUPDR7); // No pull-up/pull-downGPIOB->AFR &= ~(GPIO_AFRL_AFRL6 | GPIO_AFRL_AFRL7); // Reset AF selection on PB6 and PB7GPIOB->AFR |= (4U << 24) | (4U << 28); // AF4 (I2C1) for PB6 and PB7RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // Enable I2C1 ClockI2C1->CR1 &= ~I2C_CR1_PE; // Clear PE BitI2C1->TIMINGR = 0x00301D2B; // Timing for 100 kHz, 140 ns rise time, 40 ns fall timeI2C1->CR1 |= I2C_CR1_PE; // Enable I2C by setting PE bit in I2C_CR1 registerIsInit = 1;}}/*!********************************************************************************* \brief Initiates a master communication* \param 'sAddress' 8-bit address of the slave device* \param 'rd_wrn' READS from or WRITE to the slave device* \param 'numBytes' The number of bytes to read or write* \returns none*********************************************************************************/void I2C1_BeginTransmission(uint8_t sAddress, uint8_t rd_wrn, uint8_t nBytes){while(I2C1->ISR & I2C_ISR_BUSY); // Wait until line is not busyI2C1->CR2 = 0x00; // Reset settingsI2C1->CR2 = (sAddress << 1); // Set slave addressI2C1->CR2 |= (nBytes << 16); // 2 Byte to be transmittedif(rd_wrn == 0) // Write request{I2C1->CR2 &= ~I2C_CR2_RD_WRN;}else // Read request{I2C1->CR2 |= I2C_CR2_RD_WRN;}I2C1->CR2 |= I2C_CR2_START; // Set START bit}/*!********************************************************************************* \brief Ends I2C communication* \param none* \returns none*********************************************************************************/void I2C1_EndTransmission(void){while(!(I2C1->ISR & I2C_ISR_TC)); // Wait for transfer to be completedI2C1->CR2 |= I2C_CR2_STOP;I2C1->ICR |= I2C_ICR_STOPCF; // Clear stop detection flag}/*!********************************************************************************* \brief Writes a single byte to a slave with a 7-bit addressing mode* \param 'data' Data to write to the bus* \returns none*********************************************************************************/void I2C1_Write(uint8_t data){while(!(I2C1->ISR & I2C_ISR_TXIS)); // Wait until TXDR is emptyI2C1->TXDR = data; // 8-bit data to be transmitted}/*!********************************************************************************* \brief Determines if the I2C bus is busy or not* \param none* \returns returns (1) if BUSY or (0) if not BUSY*********************************************************************************/uint8_t I2C1_IsBusy(void){static uint8_t status;if(I2C1->ISR & I2C_ISR_BUSY) { status = 1; }else { status = 0; }return status;}

author: rstofer
author_rank: Super Contributor
timestamp: March 13, 2017, 01:59:46 am
content: On NXP devices, you ALWAYS implement I2C as an interrupt driven state machine. if you are implementing a slave, the end of the message occurs when the I2C gadget interrupts with a STOP condition. Every conceivable interrupting condition returns a unique status byte. There is a giant case statement that is used to branch to the proper event handler. The same state machine also handles the situation of being a master.Given that a STOP condition is detected, you should know when the entire packet has been received.I know absolutely nothing about the STM32 board except to say that HAL and Cube are not impressing me (I have several boards). At some point, I would cut my losses and write my own drivers.I2C is probably the most difficult protocol to implement. I almost always prefer SPI and will buy the appropriate peripheral devices such that I avoid I2C whenever possible.

author: Gibson486
author_rank: Frequent Contributor
timestamp: March 13, 2017, 04:37:49 pm
content: That sounds very useful. That is what we are trying to do now. Unfortunately, at least from what I have seen, everything just goes to a generic handler routine and that is it.

author: Gibson486
author_rank: Frequent Contributor
timestamp: March 14, 2017, 08:56:41 pm
content: Got it working.I edited the handler to mimic blocking based on the state of registers. After I got it working, I just realized it was basically the blocking code (the non IT HAL code) with two more register checks. Took us a while, but the I2C protocol has never seemed more clear!Thanks!