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 ...
Related
I use USB device - STMicroelectronics development board. Use firmware, that support usb hardware. Its works as USB serial port.
On host PC (win10 21H1) i use serial terminal ("Tera Term") for get data from my device. I use standart windows usbserial driver.
My device sending data. If data flow is small (1-2-5 kByte/s) - all work fine. But if i speed up (flow about 100 kByte/s or more) - i see data loss.
I communicated with STMicroelectronics support. We checked issue. We saw USB communication with USB analyzer. We think, than it's windows side problem.
Also, I use a custom port read utility. Data integrity problem persists.
In received data i saw lost 64 or 128... multiple of 64 bytes. 64bytes - endpoint size in my case. See linked data for more information.
I create USB_test project in CubeMx. And add simple code for sending data to PC. Loop data sending if previous CDC transmit complete. Adding delays is unacceptable: firstly, it is not 100% elimination of losses; secondly, it has a bad effect on the bandwidth of the channel.
//in main() function
uint8_t is_transmit = 0;
HAL_Delay(5000);
uint8_t Buf[2048];
uint8_t k = 48;
// fill the array with printable characters
for(uint16_t i=0; i<sizeof(Buf)-2; i++){
if(k > 100) {
k = 48;
}
Buf[i] = k++;
}
// array - is a one string line
Buf[sizeof(Buf)-2] = '\r';
Buf[sizeof(Buf)-1] = '\n';
while (1)
{
if(is_transmit == 0){
is_transmit = 1;
//HAL_Delay(1); // add delay on 1 ms reduces the likelihood of losses by an order of magnitude
CDC_Transmit_FS(Buf, sizeof(Buf));
}
}
In CDC_TransmitCplt_FS() i flash is_transmit.
static int8_t CDC_TransmitCplt_FS(uint8_t *Buf, uint32_t *Len, uint8_t epnum)
{
---
extern uint8_t is_transmit;
is_transmit = 0;
---
return result;
}
Information from ST support communication and USB analyzer log file.
https://drive.google.com/drive/folders/1CvTPfaFGmcFxD4V5zTvsVE6U26DNwG2v?usp=sharing
How i fix this issue? I need data flow from device to host 500 kB/s or more.
Best regards, Andrey.
I'm currently having trouble with talking between the dev board (STM32L476RG) and the GPS module (GP-207U). What my code does now is that, it can print out the very first packet received from GPS to PuTTY and will keep printing the same packet, even if I unplug the Tx wire from the dev board, PuTTY will still keep printing. I suspect that either the buffer that stores the received value is not getting updated(fulshed) or the HAL_UART_Receive() function only run once. (The receive function is in While(1) in main, so I'm confused)
enter image description here
(I unpluged the GPS, Putty still prints, so the receive function isn't doing anything after it received the very first packet from GPS)
/*retrive data from GPS*/
char UARTRxBuffer[1024] = "";
char RxBuffer[1024] = "";
void GetGPS(void) {
HAL_UART_Receive(&huart3, (uint8_t *)UARTRxBuffer, 1024, 1000);
HAL_Delay(100);
sprintf(RxBuffer,"%s\r\n\r\n", UARTRxBuffer);
HAL_UART_Transmit(&huart2, (uint8_t *)RxBuffer, strlen(RxBuffer), 5000);
HAL_Delay(100);
}
GetGPS() is put into while(1) in main().
I tried everything based on my guesses, but none worked.
Thanks ahead for any sort of help!
I suspect the call to HAL_UART_Receive is timing out (1000 ms in your code) during the second/subsequent attempts to read the GPS. if so, the buffer contents wouldn't get cleared or overwritten resulting the same data being printed over and over. It might help to read the GPS datasheet/manual to find out the maximum polling speed (here it appears to be ~200ms, considering the 2x 100 ms delays) and adjust the delay if the GPS device cannot keep up.
try this to confirm
HAL_StatusTypeDef status = HAL_UART_Receive(/*same as above*/);
if(status == HAL_OK){
// got valid data
sprintf(RxBuffer,"%s\r\n\r\n", UARTRxBuffer);
HAL_UART_Transmit(&huart2, (uint8_t *)RxBuffer, strlen(RxBuffer), 5000);
}
else{
sprintf(RxBuffer,"read timeout.\r\n\r\n");
HAL_UART_Transmit(&huart2, (uint8_t *)RxBuffer, strlen(RxBuffer), 5000);
}
API reference docs here page 1037/2232
I am using stm32f4 nucleuo board. I can transmit the audio data through usb to PC without FreeRTOS. Now I want to learn how to integrate the FreeRTOS and usb together. But I have some questions about how fundamentally threads and ISR interact with each other.
Below I have two files.
In main.c, there are two threads created.In usb_thread, I initialize usb dirver and do nothing else.
In vr_thread, it waits state == 1 and process PCM_Buffer.
/* main.c */
extern uint16_t PCM_Buffer[16];
int state = 0;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
osThreadDef(usb_t, usb_thread, osPriorityNormal, 0, configMINIMAL_STACK_SIZE);
osThreadDef(vr_t, vr_thread, osPriorityNormal, 0, configMINIMAL_STACK_SIZE);
usb_thread_handle = osThreadCreate (osThread(usb_t), NULL);
usb_thread_handle = osThreadCreate (osThread(vr_t), NULL);
osKernelStart();
for (;;) {}
}
static void usb_thread(void const *argument)
{
/*Do some initialization here.*/
for (;;) {}
}
static void vr_thread(void const *argument)
{
/*Do some initialization here.*/
for (;;) {
if (state == 1) {
state = 0;
process_buffer(PCM_Buffer);
}
}
}
In app.c, USB_AUDIO_CallBack will be called by usb ISR every 1 millisecond. It transmit PCM_Buffer to PC first because it is really important, then it changes state to 1.
/* app.c */
uint16_t PCM_Buffer[16];
extern int state;
void USB_AUDIO_CallBack(void) //It will be called by usb ISR every 10^-3 second.
{
Send_Audio_to_USB((int16_t *)(PCM_Buffer), NUM_AUDIO_BUF);
state = 1;
}
Here are my questions.
1. How to find out the unit counting tick of FreeRTOS? USB_AUDIO_CallBack will be
called every 1 millisecond, how to know FreeRTOS basic tick is faster or slower
than 1 millisecond. Is FreeRTOS tick equal to systick?
2. Let's assume the process time of process_buffer is less than 1 millisecond. What I want to accomplish here is described below
hardware trigger
|
usb ISR
|
USB_AUDIO_CallBack
|
state=1
|
vr_thread process_buffer
|
state=0, then wait for hardware trigger again.
I really doubt it is the correct way to do it. Or should I use suspend() and resume()?
3. Is using extern to declare global PCM_Buffer the correct way to pass variable between threads or should I use queue in FreeRTOS?
I know these questions are trivial but I really want to understand them. Any helpful document or website is welcome. Thanks.
To convert real time to systick you can use macro pdMS_TO_TICKS(xTimeInMS).
You can define your USB_AUDIO_CallBack also as a thread (or task) or paste the code from the callback to vr_thread (as your application works on only one processor). Then inside the USB ISR you can send a notification using function vTaskNotifyGiveFromISR and receive it inside vr_thread by calling ulTaskNotifyTake. After receiving the notification you can call Send_Audio_to_USB((int16_t *)(PCM_Buffer), NUM_AUDIO_BUF);
and then process_buffer(PCM_Buffer);. It is better to bring out the code from callback to task, because the ISR handler will finish it's job faster as Send_Audio_to_USB function could run long time. You also keep things to be executed in the same order as you needed.
I think that you mean volatile instead of extern. If you want to use this buffer along different threads and ISRs you should define it as volatile, but if you will use the approach with only one task you can declare this buffer as local buffer.
I've been trying to set up some basic usb comunication with a usb mouse using my stm32f4 discvery. there are no usb examples for my board so I went in to look at other boards.
stm's Cube library thingy promissed easy development and all, so I generated a project with cube inlcuding USB_OTG_FS in host only mode and four IO pins for LED's.
I had a look in the files and it seemed like reading mouse buttons would be fairly trivial, but I can't make it work.
if I run
devtype = USBH_HID_GetDeviceType(&hUsbHostFS);
it detects the device correctly.
USBH_HID_MouseInit(&hUsbHostFS)
returns USBH_OK,
but no matter what I try
mouse=USBH_HID_GetMouseInfo(&hUsbHostFS);
is always NULL
"USBH_HID_MouseInit" is never mentioned in the documentation(DM00105256.pdf)
also, only the RTOS examples use them. Reading the usb host standalone example, it would seem that to comunicate with a mouse would be as simple as runing GetDeviceType, and poll GetMouseInfo, but I can't get it to work.
this is all the code I added, it's executed in the main while
if(Appli_state == APPLICATION_READY){
switch(state)
{
case 0:
devtype = USBH_HID_GetDeviceType(&hUsbHostFS);
if(devtype == HID_MOUSE){
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_14,1);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_12,0);
state=1;
}
else if(devtype == HID_KEYBOARD){
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_14,0);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_12,1);
state=2;
}
else{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_14,0);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_12,0);
}
break;
case 1:
if(USBH_HID_MouseInit(&hUsbHostFS)==0){
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_15,1);
state=3;
}
else {
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_15,0);
state=0;
}
break;
case 3:
mouse=USBH_HID_GetMouseInfo(&hUsbHostFS);
if(mouse != NULL)HAL_GPIO_WritePin(GPIOD,GPIO_PIN_13,1);
else HAL_GPIO_WritePin(GPIOD,GPIO_PIN_13,0);
break;
default:
break;
}
}
if(Appli_state != APPLICATION_READY) state=0;
rest of the code is unchanged. like I said before it detects the connection and if it's a mouse, keyboard or unknown device just fine. I just can't get it to communicate
The Cube is buggy, coded in a strange way, and not well documented.
Depending on the actual board you have, you must make sure the clocks are setup correctly (in system_stm32f4.c)
With the stm32f407-based version (http://www.st.com/en/evaluation-tools/stm32f4discovery.html) you should use these settings:
HSE = 8000000
PLL_M = 8
PLL_Q = 7
PLL_N = 336
PLL_P = 4
The USB OTG FS requires a 48MHz clock. Here are the equations:
USB OTG FS, SDIO and RNG Clock = PLL_VCO / PLLQ
PLL_VCO = (HSE_VALUE / PLL_M) * PLL_N
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