I2C slave receiver on stm32f4 - embedded

I try to implement a i2c slave receiver interrupt service routine on a stm32f4.
Here is my smart piece of code.
void I2C2_EV_IRQHandler()
{
switch (I2C_GetLastEvent(I2C2))
{
//The address sent by the master matches the own address of the peripheral
case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED:
//The slave stretches SCL low until ADDR is
//cleared and DR filled with the data to be sent
I2C_ClearFlag(I2C2,I2C_FLAG_ADDR);
break;
//The application is expecting a data byte to be received
case I2C_EVENT_SLAVE_BYTE_RECEIVED:
I2C_ReceiveData(I2C2);
break;
//The application is expecting the end of the communication
//Make sure that both ADDR and STOPF flags are cleared
//if both are found set.
case I2C_EVENT_SLAVE_STOP_DETECTED:
if(I2C_GetFlagStatus(I2C2,I2C_FLAG_ADDR) == SET)
I2C_ClearFlag(I2C2,I2C_FLAG_ADDR);
if(I2C_GetFlagStatus(I2C2,I2C_FLAG_STOPF) == SET)
I2C_ClearFlag(I2C2,I2C_FLAG_STOPF);
}
}
The interrupt becomes called and I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED case is entered.
The SCL is low now. The reference manual says if I clear the address flag, the clock will continue and data will be sent (Page 579 - Slave receiver). In my opinion the interrupt always becomes called if any data arrives and next state will be I2C_EVENT_SLAVE_BYTE_RECEIVED.
I can not find any example from stm or via google. Can anybody help me or show me an example.

now it works. My problem was that I was not able to reset the ADDR and the STOPF register with the given commands out of reference manual. But if do it in a loop it works fine for me. Here my working Interrupt Routine.
void I2C3_EV_IRQHandler()
{
switch (I2C_GetLastEvent(I2C3))
{
case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED:
STM_EVAL_LEDOn(LED3);
STM_EVAL_LEDOff(LED5);
break;
case I2C_EVENT_SLAVE_BYTE_RECEIVED:
STM_EVAL_LEDToggle(LED4);
STM_EVAL_LEDOff(LED3);
I2C_InputBuffer[I2C_InputBufferIndex++] = I2C_ReceiveData(I2C3);
break;
case I2C_EVENT_SLAVE_STOP_DETECTED:
STM_EVAL_LEDOn(LED6);
STM_EVAL_LEDOff(LED4);
break;
}
I2C_CleanADDRandSTOPF();
if(I2C_InputBufferIndex > MOTOR_PACKAGE_SIZE-1)
{
motorHandleEvent(I2C_InputBuffer);
I2C_InputBufferIndex = 0;
uint8_t resetIndex;
for(resetIndex = 0; resetIndex < MOTOR_PACKAGE_SIZE; resetIndex ++)
I2C_InputBuffer[resetIndex] = 0;
}
}
inline void I2C_CleanADDRandSTOPF()
{
while ((I2C3->SR1 & I2C_SR1_ADDR) == I2C_SR1_ADDR)
{
volatile uint32_t temp;
temp=I2C3->SR1;
temp=I2C3->SR2;
}
while ((I2C3->SR1&I2C_SR1_STOPF) == I2C_SR1_STOPF)
{
volatile uint32_t temp;
temp=I2C3->SR1;
I2C3->CR1 |= 0x1;
}
}

The hardware is doing clock stretching to ensure that the slave is keeping up with the master. The slave first waits to get an address match. Then you get interrupt while SCL is held low. This allows slave to basically provide flow control to the master. The master detects that SCL is being held low by slave and it will wait for it to be released before master sends any more data. So you won't get additional interrupts on data being received because the master won't send any more data until you let SCL go high. You can read about clock stretching here http://en.wikipedia.org/wiki/I%C2%B2C

Related

SPI slaveon DSPIC

Working on DSPIC33FJ128MC804
The problem :
I am completely unable to configura the SPI as slave. SPI Interrupt does never fire. if(SPI2STATbits.SPIRBF || SPI2STATbits.SPIROV) is always evaluated as false too.
I tried
with SSEN enabled and disabled, and many other configurations...
The clock is present and generated by an Arduino Uno as master, the pin mapping has been checked 3 times. This device is not subject to the SPI slave CSn errata of this familly.
Does anyone see what I do wrong ? or have a working example as SLAVE ?
// Setup hardware I/Os configuration
SPI_SLAVE_CSN_TRIS = 1; //input
SPI_SLAVE_CLK_TRIS = 1;
SPI_SLAVE_MOSI_TRIS = 1;
SPI_SLAVE_MISO_TRIS = 0; //output
// Setup remapable pins
SPI_SLAVE_MISO_RPN = _RPOUT_SDO2; // configure RP output
RPINR22bits.SDI2R = SPI_SLAVE_MOSI_RP_NUMBER; //configure inputs
RPINR22bits.SCK2R = SPI_SLAVE_CLK_RP_NUMBER;
RPINR23bits.SS2R = SPI_SLAVE_CSN_RP_NUMBER;
IFS2bits.SPI2IF = 0; // Clear interrupt flag
IEC2bits.SPI2IE = 0; // Disable interrupt
// Baudrate configuration (unused in slave mode )
SPI2CON1bits.PPRE = 0b11; //TODO needed in slave mode ?
SPI2CON1bits.SPRE = 0b110;
SPI2CON1bits.DISSCK = 0; // Internal serial clock
SPI2CON1bits.DISSDO = 0; // SDOx is controlled by the module
SPI2CON1bits.MODE16 = 0; // 8 bit mode //TODO check if 16 bit fit's better
SPI2CON1bits.SMP = 0; // 0 when slave
SPI2CON1bits.CKE = 1; // Emits SDO on SCK falling edge (slave samples on rising)
SPI2CON1bits.CKP = 0; // SCK idle state is LOW level
SPI2CON1bits.SSEN = 1;// CSN pin used for slave mode
SPI2CON1bits.MSTEN = 0; // Slave mode is enabled
//SPI2CON2bits.SPIFSD = 1; //we are not in framed mode
SPI2STATbits.SPIEN = 1; // Enable SPI module
//TODO ISR priority
// Write the SPIx Interrupt Priority Control (SPIxIP) bits in the respective IPCx register to set the interrupt priority
SPI2BUF = 0xf3; // Clear data to be transmitted => for test
IFS2bits.SPI2IF = 0; // Clear interrupt flag
IEC2bits.SPI2IE = 1; // Enable interrupt
SOLVED
The RPN pin fucntions where locked in another part of the code I hadn't written.
I was not aware of this functionnality
I have no experience with SPI in slave mode but in general setting the priority of the interrupt on 16 and 32 bit pic MCUs are not optional, it must be set or the interrupt won't fire, at least that's been my experience. Set the IPCx bits relevant for the peripherals you use.
Try finding a code snippet where an interrupt function is defined and use that as a template. It's generally very similar for every peripheral. UART and maybe also the SPI module has a separate send and receive interrupt vector, both must be defined.
Somewhere in the main function, enable the interrupt and set the priority, at least non zero IIRC:
IEC0bits.AD1IE = 1; // Enable adc interrupt
IPC3bits.AD1IP = 4; // set priority above that of the serial port.
For an ADC I use this to define the interrupt. All you need to change is the _ADC1Interrupt part. See the datasheet and XC16 documentation for the correct function name.
// ADC1 interrupt vector
void __attribute__((__interrupt__(auto_psv))) _ADC1Interrupt(void)
{
if( IFS0bits.AD1IF ) // Check interrupt flag
{
}
IFS0bits.AD1IF = 0; // clear interrupt flag to prevent endless interrupts
}

Atmel SAM D21 DMA stuck in busy state when using USB

I'm using the SAMD21 xPlained pro with a prototype to acquire a signal, using the internal ADC and DMAC which configured to collect 5500 samples.
The samples then transferred to a PC application via the target USB.
The firmware that I wrote for that is working, but I noticed that from time to time the DMA get stuck in busy state.
To debug that I canceled the transfer part to the PC via the USB.
I noticed that DMA works fine and transfers the samples to the memory, but if I connect the USB cable to the PC (without transferring data through the USB), the DMA gets stuck from time to time;
but once I disconnect the (target) USB cable, the DMA works continuously without getting stuck in busy state.
I suspect that it has something with the interrupt and priorities of the USB and the ADC, which are both using the DMA.
I thought I should set the ADC sampling at top priority so the USB won't cause the DMA stuck in busy state, but I couldn't find how to configure that in the code (I'm using ASF).
Any idea why plugging the USB causes the DMA occasionally stuck in busy state?
Should this problem relates to priorities as I suspect, any idea how to reduce the USB interrupt priority?
code:
void GSWatchMainProcess(void)
{
static int i=0;
GSPacket PacketToStream;
switch (KnockKnockStateMachine)
{
case InitKnockKnock:
KnockKnockStateMachine = KnockKnockStandby;
break;
case KnockKnockStandby:
if (StreamADC && !RequestForAcknowledge) KnockKnockStateMachine = WakeupAphrodite;
KnockKnockStateMachine = WakeupAphrodite; //this line was added to skip waiting for a command from the PC
break;
case WakeupAphrodite:
if (dma_is_busy(&example_resource))
{
KnockKnockStateMachine = AbortKnockKnock;
}
else
{
port_pin_set_output_level(PIN_PB09, true);
port_pin_set_output_level(LED_0_PIN, false);
transfer_is_done = false;
if(dma_start_transfer_job(&example_resource))
{
KnockKnockStateMachine = AbortKnockKnock;
}
}
KnockKnockStateMachine = WaitForBurstToEnd;
i=200000; //this counter is used as work-around to reset the DMA when it get stuck after timeout
break;
case WaitForBurstToEnd:
if (!dma_is_busy(&example_resource))
{
KnockKnockStateMachine = ProcessBurst;
}
if(!--i) // work-around to reset DMA when it get stuck
{
KnockKnockStateMachine = AbortKnockKnock;
}
break;
case ProcessBurst:
PacketToStream.Type=Stream;
PacketToStream.ContentLength=0;
for (i = 0; i<(ADC_SAMPLES); i++)
{
PacketToStream.Content[PacketToStream.ContentLength++] = adc_result_buffer[i] / 256;
PacketToStream.Content[PacketToStream.ContentLength++] = adc_result_buffer[i] % 256;
if(PacketToStream.ContentLength>=PACKET_MAX_SIZE)
{
//SendViaGSWatchLink(PacketToStream);
PacketToStream.ContentLength=0;
}
}
//if(PacketToStream.ContentLength>0) SendViaGSWatchLink(PacketToStream);
RequestForAcknowledge = true;
KnockKnockStateMachine = KnockKnockStandby;
break;
case AbortKnockKnock:
dma_abort_job(&example_resource);
dma_free(&example_resource);
port_pin_set_output_level(PIN_PB09, false);
port_pin_set_output_level(LED_0_PIN, true);
transfer_is_done = true;
configure_dma_resource(&example_resource);
setup_transfer_descriptor(&DMA_ADC_descriptor);
dma_add_descriptor(&example_resource, &DMA_ADC_descriptor);
dma_register_callback(&example_resource, transfer_done, DMA_CALLBACK_TRANSFER_DONE);
dma_enable_callback(&example_resource, DMA_CALLBACK_TRANSFER_DONE);
system_interrupt_enable_global();
KnockKnockStateMachine = WakeupAphrodite;
break;
}
}
void transfer_done(struct dma_resource* const resource )
{
transfer_is_done = true;
port_pin_set_output_level(PIN_PB09, false);
port_pin_set_output_level(LED_0_PIN, true);
}
void setup_transfer_descriptor(DmacDescriptor *descriptor)
{
struct dma_descriptor_config descriptor_config;
dma_descriptor_get_config_defaults(&descriptor_config);
descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
descriptor_config.block_transfer_count = ADC_SAMPLES;
descriptor_config.dst_increment_enable = true;
descriptor_config.src_increment_enable = false;
descriptor_config.source_address = (uint32_t)(&adc_instance.hw->RESULT.reg);
//descriptor_config.destination_address = (uint32_t)adc_result_buffer + 2 * ADC_SAMPLES;
descriptor_config.destination_address = (uint32_t)adc_result_buffer + sizeof(adc_result_buffer);
dma_descriptor_create(descriptor, &descriptor_config);
}
void configure_dma_resource(struct dma_resource *resource)
{
struct dma_resource_config config_dma;
dma_get_config_defaults(&config_dma);
config_dma.peripheral_trigger = ADC_DMAC_ID_RESRDY;
config_dma.trigger_action = DMA_TRIGGER_ACTON_BEAT;
config_dma.priority = DMA_PRIORITY_LEVEL_3;
dma_allocate(resource, &config_dma);
}
void configure_adc(void)
{
struct adc_config config_adc;
adc_get_config_defaults(&config_adc);
// config_adc.gain_factor = ADC_GAIN_FACTOR_DIV2; //TODO: check if we need this feature
config_adc.clock_prescaler = ADC_CLOCK_PRESCALER_DIV32; //TODO: check whether it possible to work with 8
config_adc.reference = ADC_REFERENCE_INTVCC0; //ADC_REFERENCE_INT1V; //ADC_REFERENCE_INTVCC1;
config_adc.positive_input = ADC_POSITIVE_INPUT_PIN8;
config_adc.negative_input = ADC_NEGATIVE_INPUT_GND;
config_adc.resolution = ADC_RESOLUTION_12BIT;
config_adc.sample_length = 0; //4
config_adc.differential_mode = false;
config_adc.freerunning = true;
adc_init(&adc_instance, ADC, &config_adc);
adc_enable(&adc_instance);
}
I experienced a very similar issue. My SPI workes fine although I plugged in the USB but started with this Issue when I start it from a specific USB-CDC command.
I am working with asf 3.34 on as 7 and a L21 (very similar).
My workaround which is not clean but works:
After starting the DMA transfer I continiusly(while-loop) check for the transfer done bit(REG_DMAC_CHINTFLAG should be 0x2) and when it is I set the status to ok.
In Code:
transfer_tx_is_done=0;
dma_start_transfer_job(&res_2_Byte);
while (!transfer_tx_is_done) {
/* Wait for transfer done */
if (SPI_done())
{
res_2_Byte.job_status=STATUS_OK;
break;
}
}
where SPI_done() checks the register and transfer_tx_is_done would be set by the interrupt (works sometimes [I said it is dirty])
I am no expert in USB and this is possibly unrelated to you case but I got also issues with USB-CDC and occasional freezing on UC3A3 chips I found out 2 separate reasons:
USB interrupt and tasks must have enough free MCU time
I am setting test bits on entering and exiting of each interrupt I got (USB included) and if they are too close to overlap weird things start to happen like freezing, output signal jitter (way bigger then all ISRs together), sync and acknowledge errors etc even if USB has the highest priority. If I re-time all the stuff I use so the interrupts are firing not at the same times all works good.
beware GPIO toggling is slow operation so you need to take that into account too.
Each version of Host OS (windows) has different timing
I use my MCU for full duplex synchronous bulk transfers and got 3 layers of FIFO (2 on Host and 1 on MCU) to keep up the sync (contnuous 24/7 ~640KByte/s in and ~640KByte/s out). With each new version of Windows (w2k -> Xp --> w7) something changed in either scheduling of threads or driver services and re-timing was necessary changing transfer times and timeouts so the lock less multi threads for RT synchronous USB transfers still work as should.
Latest thing I discover in W7 (they added new "feature") is that some USB controllers (like Intel) on Host side have either freezes on their own or different priorities of data transfers (either based on pipe or direction) and sending/receiving ratio 1:1 no more works on some machines causing freezes on the MCU side from 1ms up to few seconds due to blocked FIFOs. The workaround for that is to fully fill MCU receiving FIFO's (or increase MCU FIFO size which is not possible in my case as it takes almost all memory already) which should be done anyway but in my case I work in RT and do not have many packets ahead so I find out I need to send at least 3 times more packets then receive until the FIFOs full-fill over time (and each time the Host sending freezes which is for some machines all the time) just not to lost sync and all the half full FIFO mechanism on those machines not work anymore.
So in case your USB transfer is synchronized with host or the other way around it is worth to check different OS (like Xp) if the problem is also there. If not there is a high chance you got similar problems I was dealing with so you can try the workarounds ...

Cannot transmit every characters through UART

I am using stm32f0 MCU.
I would like to transmit every single byte received from the uart out of the uart. I am enabling an interrupt on every byte received from uart.
My code is quite simple.
uint8_t Rx_data[5];
//Interrupt callback routine
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) //current UART
{
HAL_UART_Transmit(&huart1, &Rx_data[0], 1, 100);
HAL_UART_Receive_IT(&huart1, Rx_data, 1); //activate UART receive interrupt every time on receiving 1 byte
}
}
My PC transmits ASCII 12345678 to stm32. If things work as expected, the PC should be receiving 12345678 back. However, the PC receives 1357 instead. What is wrong with the code?
Reenabling interrupts may be inefficient. With a couple of modifications it is possible to keep the interrupt active without needing to write the handler all over again. See the example below altered from the stm32cubemx generator.
/**
* #brief This function handles USART3 to USART6 global interrupts.
*/
void USART3_6_IRQHandler(void)
{
InterruptGPS(&huart5);
}
void InterruptGPS(UART_HandleTypeDef *huart) {
uint8_t rbyte;
if (huart->Instance != USART5) {
return;
}
/* UART in mode Receiver ---------------------------------------------------*/
if((__HAL_UART_GET_IT(huart, UART_IT_RXNE) == RESET) || (__HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE) == RESET)) {
return;
}
rbyte = (uint8_t)(huart->Instance->RDR & (uint8_t)0xff);
__HAL_UART_SEND_REQ(huart, UART_RXDATA_FLUSH_REQUEST);
// do your stuff
}
static void init_gps() {
__HAL_UART_ENABLE_IT(&huart5, UART_IT_RXNE);
}
You should make a tx array buffer as well, and use interrupt for writing as well (The first write if not enabled yet, should be sent immediately).
There should be examples of this for STM32 around.
You should probably switch the two lines: Transmit and Receive. The Transmit function waits for a timeout to send the character, in meantime the next received character is missed.

Communicating dsPIC with PC application through UART. Receiver interrupt handling

I'm communicating my C# applications with dsPIC x16 microcontroller using UART. I want to send/receive fixed size frames and I tried to manage it in a following way:
if(readFrame)
{ IEC0bits.U1RXIE=0; //turn off the U1RX interrupts
readFrame = false;
while(indexer < 8 )
{
while(!U1STAbits.URXDA);
modbusBuffer[indexer]=U1RXREG;
indexer++;
}
if(indexer == 8)
{
modbusRecvTask(modbusBuffer);
indexer=0;
}
IEC0bits.U1RXIE=1; //turn on U1RX interrupts
}
void _ISR_NAP _U1RXInterrupt()
{
if(IFS0bits.U1RXIF)
{
IFS0bits.U1RXIF = 0; //set the interrupt flag to false
if(U1STAbits.OERR==1) //check overload error
{
U1STAbits.OERR=0; //clear error flag
}
else
{
readFrame = true;
}
}
}
The thing is that it works fine only for the first received frame. After that the program goes into the receiver interrupt again and sets the flag readFrame to true even though no bytes were send and is getting stuck in line:
while(!U1STAbits.URXDA);
I've read some advices to clear the read buffer of the UART in order to prevent the program to go into the ISR again but I couldn't find a way to do it.

STM32F411: is clearing an external interrupt flag really necessary?

I've bought an STM32F411 nucleo board and now I'm trying to understand various bits and pieces of the HAL. Starting with external interrupts seemed to be a good idea, because the board has a push button which is connected to PC13. So I've set up a simple toggle-the-frequency blinky. The code below is a bit simplified:
#define LED_PIN GPIO_PIN_5
#define BTN_PIN GPIO_PIN_13
static uint32_t blink_period = 250;
int main(void)
{
HAL_Init();
SystemClock_Config();
__GPIOA_CLK_ENABLE();
GPIO_InitTypeDef pinConfig;
pinConfig.Pin = (LED_PIN);
pinConfig.Pull = GPIO_NOPULL;
pinConfig.Mode = GPIO_MODE_OUTPUT_PP;
pinConfig.Speed = GPIO_SPEED_FAST;
HAL_GPIO_Init(GPIOA, &pinConfig);
__GPIOC_CLK_ENABLE();
pinConfig.Pin = (BTN_PIN);
pinConfig.Pull = GPIO_NOPULL;
pinConfig.Mode = GPIO_MODE_IT_FALLING;
pinConfig.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(GPIOC, &pinConfig);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0x0F, 0x00);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
while (1)
{
HAL_GPIO_TogglePin(GPIOA, LED_PIN);
HAL_Delay(blink_period);
}
}
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(BTN_PIN);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == BTN_PIN)
{
if (blink_period == 500)
{
blink_period = 250;
}
else
{
blink_period = 500;
}
}
}
When I push the button, an interrupt is generated and the blinky frequency changes from 1 to 2 Hz (or vice-versa). This works as intended, but why? I forgot to clear the pending interrupt flag, so the ISR should be called over and over. The datasheet clearly states that
When the selected edge occurs on the external interrupt line, an interrupt request is generated. The pending bit corresponding to the interrupt line is also set. This request is
reset by writing a ‘1’ in the pending register.
Reading a bit further reveals that this is a bit different for events:
When the selected edge occurs on the event line, an event pulse is generated. The pending bit corresponding to the event line is not set.
However, I'm not setting the button pin mode to any of the GPIO_MODE_EVT_... modes so I'm not using the event mechanism (to be honest I don't yet know what that even is - I just think that I'm not using it. Any hints are welcome).
So somewhere I should have to call void HAL_NVIC_ClearPendingIRQ (IRQn_Type IRQn), shouldn't I? It seems that clearing the flag by software is not necessary, because the ISR is not called more than once per falling edge. I've added a breakpoint in HAL_GPIO_EXTI_Callback to verify this.
Edit
As mentioned in the comments, the flag clearing code is in ST's implementation of the GPIO interrupt handler:
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
This handler needs to be called by the actual ISR (which is done in my code) and it clears the pending flag corresponding to the GPIO_Pin argument. So I have to write an ISR which sorts out which flags are set, and call HAL_GPIO_EXTI_IRQHandler for each, which in turn calls my HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin), again with the pin as an argument. For each external interrupt, the pin number would get checked some ~3 times (in the ISR, in the handler and in the callback)!
If that is the solution, I want my problem back.
You don't have to call HAL_NVIC_ClearPendingIRQ (IRQn_Type IRQn) because the pending bit in the NVIC will be cleared automatically upon entering HAL_GPIO_EXTI_IRQHandler.
The HAL_GPIO_EXTI_IRQHandler() implementation clears the pending bit in the peripheral, not in the NVIC. If it didn't clear the pending bit by calling __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin), then the handler would be called again and again. The point is that you must distinguish between the interrupt pending bit in the peripheral and the pending bit in the NVIC.