Weird STM32F103 USB module!!  [SOLVED] - Page 1
Forum section: electronics / general

author: DELTA67
author_rank: Regular Contributor
timestamp: October 16, 2024, 06:55:53 am
content: Hi all,I want to understand, please, how to (using GCC):- initialize the BTABLE and the USB_EPnR registers.- Copy data between RAM and the EPn_RX/TX buffers in PMA.Here's a part from my header:Code:typedef struct {volatile uint32_t EPR;volatile uint32_t RESERVED;volatile uint32_t CNTR;volatile uint32_t ISTR;volatile uint32_t FNR;volatile uint32_t DADDR;volatile uint32_t BTABLE;} _USB;typedef struct {uint16_t ADDR_TX;uint16_t RES1;uint16_t COUNT_TX;uint16_t RES2;uint16_t ADDR_RX;uint16_t RES3;uint16_t COUNT_RX;uint16_t RES4;} BTABLE_ENTRY;#define USB ((_USB*)0x40005C00)// USB base address#define PMA 0x40006000#define EPCOUNT 2// Nr of endpointsvolatile BTABLE_ENTRY* BTable = ((BTABLE_ENTRY*)PMA); // BTable at the start of PMAuint8_t buf;// 64 bytes buffer in RAM- Is __attribute__ ((packed)) mandatory for struct BTABLE_ENTRY?- How to calculate the values for BTable.ADDR_TX, BTable.ADDR_RX and BTable.COUNT_RX given a buffer size (say 64 bytes)? I want that the starting address for EPs buffers to be just after BTABLE.- Is there a relation between EPn and USB->EPR?For example what happens if I set the value of EP field in USB->EPR to 3?- How to copy the received data from EPn RX_BUF to the buffer "buf" in RAM or copy data to send from "buf" to EPn TX_BUF?Thanks

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 16, 2024, 08:09:20 am
content: Someone may have direct answers to your questions.Otherwise, second time I suggest TinyUSB lately. In your case, I don't suggest actually using it (unless you want to), but looking at the source code for STM32 support:https://github.com/hathach/tinyusb/tree/master/src/portable/st/stm32_fsdevMight help.

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 16, 2024, 08:25:29 am
content: Have a look at my bare metal implementation on how USB is done on the F103.https://github.com/pecostm32/STM32F103C8_USB_CH340All your issues are addressed in it.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 16, 2024, 08:53:37 am
content: Thanks SiliconWizard and pcprogrammer for the links I'll try to study the code there.Meanwhile any explanation about the PMA is appreceiated.

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 16, 2024, 11:29:20 am
content: It has been a while since I worked on that USB code and it took some research to get an understanding of it all. Not just the USB interface peripheral in the F103, but also the ins and outs on USB.Thissitewas very helpful.Also usedwiresharkquite a bit.Furthermore I studied the reference manual,RM0008.The PMA is a bit tricky as it uses only 16 bits of every 32 bit address location. You can see how it works in the function "void EP2SendData(void)". A 16 bit variable is written to a pointer pointing to a 32 bit location. Best to study my code in reference to the manual.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 16, 2024, 12:02:25 pm
content: I know roughly how USB works thanks to "usb in a nutshell" tuto from beyondlogic and this site:https://www.usbmadesimple.co.uk/The books "USB complete" by Jan Axleson and "USB design by example" by John Hyde are a MUST have.My problem is with the USB module of the STM32F103 it self!

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 16, 2024, 12:12:13 pm
content: Quote from: DELTA67 on October 16, 2024, 12:02:25 pmI was completely new to USB when I looked into it and at first tired it with the HAL examples generated with the ST Cube crap. Was not able to get things running so decided to attack it myself. The result is the code you now have. The peripheral it self is not that complex. But it would take diving into it again, due to well, aging sucks.For another project I used the USB code in isochronous transfer mode, but that also needed a driver on the host side to make it work. Can post it if you like.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 16, 2024, 12:38:47 pm
content: Thanks again pcprogrammer, for the moment I want to understand the USB module, bulk and isochronous transfers are for later.

author: ataradov
author_rank: Super Contributor
timestamp: October 16, 2024, 01:53:48 pm
content: To answer your questions:There is no need for a packed attribute. The whole BTABLE is accessible in 16-bit quantities only and the entries are 16-bit. You can read it in 8-bit quantities, but writing must happen in 16-bits.ADDR_TX and ADDR_RX are direct increments of the allocated buffer size. When enabling the endpoints simply have a counter that starts after the table and increments by the endpoint size. For the COUNT_RX there is a table in the datasheet that shows how to allocate endpoints of different sizes.Entry in the table (EPn) is the same as "n" in the USB->EPR. EPR contains a field that lets you specify the logical endpoint address, but this field is really pointless, you can just set it equal to EPn/n and be done with it. You will have to use sequential and incrementing endpoint indexes in the USB configuration descriptor. If you for some reason want to use endpoint 12 in the configuration descriptor, then you need to set the field accordingly. But this is never needed and most USB controllers won't let you do it.To copy the data you should use a simple loop that copies in 16-bit chunks. Do not use memcpy(), since it will give you unpredictable results depending on the implementation.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 16, 2024, 02:26:42 pm
content: Thanks ataradov for your help.Quote from: ataradov on October 16, 2024, 01:53:48 pmCan you give some code please?Quote from: ataradov on October 16, 2024, 01:53:48 pmwhat happens if I set the value of EP field in USB->EPR to 3?Quote from: ataradov on October 16, 2024, 01:53:48 pmIn the datasheet they say that you must miltiply the PMA address by 2 . Why?

author: ataradov
author_rank: Super Contributor
timestamp: October 16, 2024, 02:59:01 pm
content: Quote from: DELTA67 on October 16, 2024, 02:26:42 pmThe code would depend a lot on the code structure you use and how you allocate endpoints.For the RX_COUNT, stick to 32-byte allocations (BL_SIZE = 1) and the code would be something like this:Code:int count = (size - 1) / 32;usb_ep_desc.ADDR_RX = usb_pm_ptr;usb_ep_desc.COUNT_RX = COUNT_RX_BL_SIZE | COUNT_RX_NUM_BLOCK(count);usb_pm_ptr += (count + 1) * 32;For TX, the code is trivial:Code:usb_ep_desc.ADDR_TX = usb_pm_ptr;usb_ep_desc.COUNT_TX = 0;usb_pm_ptr += size;After a bus reset, usb_pm_ptr is set to the value after the table.Quote from: DELTA67 on October 16, 2024, 02:26:42 pmPhysical endpoint 0 would respond to a logical address 3. Your USB descriptors should use address 3 in that case. Descriptors don't care how your hardware and code works internally.Note that you would need some other physical endpoint allocated to the logical EP 0, since there always must be 0. And of course don't do this in the real code, it is madness. Just assign them equal and your logical and physical endpoints will be the same and you will have one less thing to think about.Quote from: DELTA67 on October 16, 2024, 02:26:42 pmBecause this memory is weirdly mapped. They mapped 16-bit wide memory into a 32-bit wide address space, so you end up with holes every other 16-bit word. This does not matter in practice, since the memory is special and has to be accessed in 16-bit quantities anyway.They fixed the holes in newer devices, but the memory is still weird, so it does not make much of a difference.

author: wek
author_rank: Frequent Contributor
timestamp: October 16, 2024, 07:20:12 pm
content: > You can read it in 8-bit quantities, but writing must happen in 16-bits.How do you know? Where is this documented?JW

author: DELTA67
author_rank: Regular Contributor
timestamp: October 16, 2024, 07:44:24 pm
content: Thanks atradov for your detailed answer.After some cups of cofee (and some beers!) it's seems tha I'm progressing slowly:1- For RX_COUNT:- If the size of the buffer is >= 64 bytes the formula is: RX_COUNT = (1<<15) | ((size/32)<<10) (NOT CORRECT!!)- If the size of the buffer is < 64 bytes the formula is: RX_COUNT = (size/2)<<102- For ADDR_RX/TX:The addresses in the PMA seen by the USB module are relative to 0x40006000. So The size of the BTABLE is EPCOUNT*sizeof(BTABLE_ENTRY)/2.For 64 bytes buffers:Code:// We need the starting address for EPs buffers just after BTABLEuint16_t Addr = EPCOUNT*sizeof(BTABLE_ENTRY)/2;for (int i=0; i<EPCOUNT; i++){BTable.ADDR_TX = Addr;BTable.COUNT_TX = 0;Addr += 64;BTable.ADDR_RX = Addr;BTable.COUNT_RX = (1<<15) | ((64/64) << 10);Addr += 64;}3- The addresses in the PMA seen by the ARM CORE are relative to 0.So we must add 0x40006000 to access them. And since each 16bits in the PMA occupy 32bits we must multiply by 2.Then to copy data from EP0 RX buffer to the "buf" in RAM:Code:uint8_t count = (BTable.COUNT_RX & 0x3FF);// get Nr of bytes receiveduint16_t *src = (uint16_t *)(PMA + BTable.ADDR_RX*2);uint16_t *dest = (uint16_t *)buf;// we copy 2 bytes at a time, +1 to avoid missing the last byte (thanks ataradov)for (int i = 0; i < (count+1)/2; i++) *dest++ = *src++;m'I right?EDIT: due to ataradov notes bellow this post is edited

author: ataradov
author_rank: Super Contributor
timestamp: October 16, 2024, 07:48:00 pm
content: Quote from: wek on October 16, 2024, 07:20:12 pmThat's the neat part - nowhere, you just have to figure it out.

author: wek
author_rank: Frequent Contributor
timestamp: October 16, 2024, 07:55:39 pm
content: Quote from: ataradov on October 16, 2024, 07:48:00 pmOkay.I kindadid; I just hoped you have some better source I've overlooked.JW

author: ataradov
author_rank: Super Contributor
timestamp: October 16, 2024, 07:58:29 pm
content: Quote from: DELTA67 on October 16, 2024, 07:44:24 pmThe high granularity size is 32 bytes. And low granularity only goes up to 62 bytes. Look at the Table 177, it will tell you everything you need to know. And look at my calculation formula. You need to do (size-1)/32, otherwise you will over-commit if the size is 32.Quote from: DELTA67 on October 16, 2024, 07:44:24 pmIt all depends on how those structures are defined.Quote from: DELTA67 on October 16, 2024, 07:44:24 pmThe logic is correct, just double check all the offsets are correct and also check that you are not overflowing the memory. They use different size of that buffer on different devices and not all of them have enough to allocate 64 bytes for all endpoints.Quote from: DELTA67 on October 16, 2024, 07:44:24 pmCount you get here is in bytes, but you are copying in words. So, you need to copy (count+1)/2 entries. The last entry may contain extra byte that you need to discard. Make sure that your target buffer is big enough or manually handle the last transfer.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 16, 2024, 08:40:17 pm
content: OK, I'm almost there, thanks againCode:typedef struct {uint16_t ADDR_TX;uint16_t RES1;uint16_t COUNT_TX;uint16_t RES2;uint16_t ADDR_RX;uint16_t RES3;uint16_t COUNT_RX;uint16_t RES4;} BTABLE_ENTRY;#define PMA 0x40006000#define EPCOUNT 2// Nr of endpointsvolatile BTABLE_ENTRY* BTable = ((BTABLE_ENTRY*)PMA); // BTable at the start of PMAuint8_t buf;// 64 bytes buffer in RAM// We need the starting address for EPs buffers just after BTABLEuint16_t Addr = EPCOUNT*sizeof(BTABLE_ENTRY)/2;for (int i=0; i<EPCOUNT; i++){BTable.ADDR_TX = Addr;BTable.COUNT_TX = 0;Addr += 64;BTable.ADDR_RX = Addr;BTable.COUNT_RX = (1<<15) | ((64/32) << 10); // !! NOT sure !!Addr += 64;}// After receiving some data in EP0 RX buffer we copy it to RAMuint8_t count = (BTable.COUNT_RX & 0x3FF);// get Nr of bytes receiveduint16_t *src = (uint16_t *)(PMA + BTable.ADDR_RX*2);uint16_t *dest = (uint16_t *)buf;for (i = 0; i < (count+1)/2; i++) *dest++ = *src++;

author: ataradov
author_rank: Super Contributor
timestamp: October 16, 2024, 08:48:50 pm
content: How are you not sure? It should be (64-1)/32. This will evaluate into NUM_BLOCK=1, which is what you want for 64 byte buffer.Your copy is also not correct. The starting does not need multiplication, and need to recalculate the address for each iteration or increment src by 2 to get the overall 4 bytes increment per 2 bytes of data.But in practice, start running it and see the values you get. It is much easier to figure it out that way than try to guess what will work.To get the initial SETUP packet you only need to setup one endpoint.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 16, 2024, 09:00:45 pm
content: Ok, I'll get it a try asap.I've computed it this way: 64/32=2 so we get 2 blocks of 32 bytes each.I've to look at table 177 again.Sorry I've not grasped what you've said about the copy algo!

author: ataradov
author_rank: Super Contributor
timestamp: October 16, 2024, 09:17:36 pm
content: But you don't write the number of blocks directly. To get two blocks you need to write NUM_BLOCK=1, as described in the Table 177.I actually don't know what ADDR_RX represents on this device, I only worked with devices where they made the buffer directly addressable by 16-bit words without gaps. If the address represents the number of words, then your allocation is incorrect. But in either case, the way you access and allocate data must match.If you really want to figure this out without looking at any other code, then just to an experiment and see where things are written on the transfer and figure it out from there.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 16, 2024, 09:41:50 pm
content: Quote from: ataradov on October 16, 2024, 09:17:36 pmAAAAH, ok ok got it ! But in this case the formula will be: (size/32)-1, No?Quote from: ataradov on October 16, 2024, 09:17:36 pmIn STM32F103 the buffer is addressable by 32bits with 16bts gaps.I think the correct code is:Code:// Copy data from PMA to RAMfor (i = 0; i < (count+1)/2; i++){*dest++ = *src++;*src++;//skip the dummy 16bits}

author: ataradov
author_rank: Super Contributor
timestamp: October 16, 2024, 09:45:08 pm
content: Yes, but you also need to figure out if the ADDR_xx represents the number of bytes or number of words. Given the way it is described, it looks like it is the "internal" offset, so you need to divide the real offset by two when setting this field and then multiply back when reading. Again, something easy enough to verify by observing where will initial SETUP packet go.

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 17, 2024, 07:08:06 am
content: Quote from: DELTA67 on October 16, 2024, 09:41:50 pmYou can use differently declared pointers and have the compiler take care of the gap for you.Declare the PMA pointer as uint32_t * and the buffer pointer as uint16_t *. This way the extra increment of the PMA pointer is not needed.But when copying from the PMA to the user buffer there can be an odd number of bytes send by the host, and I'm not sure what the USB peripheral does with the last byte in the last short. That is why I used a setup that copies the data from the PMA on a byte basis.Code:uint8_t cnt = EPBTABLE->EPD.RX_COUNT.DATA & 0x7F;uint32_t *dptr = (uint32_t *)EP2RX_BASE;uint16_t data;//Copy the data from the PMA to the receive bufferwhile(cnt--){//Get two bytes from the PMAdata = *dptr++;//Put the first byte in the receive bufferusb_rx[usb_rx_in_idx++] = data & 0xFF;usb_rx_in_idx %= sizeof(usb_rx);//Check if more bytes need to be processedif(cnt){//Put the second byte in the receive bufferusb_rx[usb_rx_in_idx++] = (data >> 8) & 0xFF;usb_rx_in_idx %= sizeof(usb_rx);cnt--;}}Another option is to copy the actual full shorts in a single loop and check if there is a last byte that needs to be copied.One remark, don't try to debug the USB handling with hardware debugging. It won't work due to the host timing out on you. I have tried it when I was developing my USB handler and it fails for sure. That is why I resorted to wireshark.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 17, 2024, 07:35:20 am
content: Quote from: pcprogrammer on October 17, 2024, 07:08:06 am+1, awsome tip!Quote from: pcprogrammer on October 17, 2024, 07:08:06 amThis issue is solved by adding 1 to the count as ataradov has said. We copy (count+1)/2 16bit words.Quote from: pcprogrammer on October 17, 2024, 07:08:06 amYes, I'm planning to test the code using PROTEUS. It has a good debugging capabilities and even a USB transactions analyzer!

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 17, 2024, 08:57:39 am
content: Quote from: DELTA67 on October 17, 2024, 07:35:20 amI did see that, but you missed my point as to why this may be a problem. The last byte copied might, in case of an odd number of bytes received, not be zero. And when using a circular byte buffer as I do in my code there is the risk of crossing the buffer boundary.It depends on how you process the data after copying if it is going to be a problem or not.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 17, 2024, 09:30:18 am
content: Quote from: pcprogrammer on October 17, 2024, 08:57:39 amYou're totaly right and thanks to point this issue. At the moment the goal is to receive requests from host as simply as possible, there's no ciruclar buffers or such complicated things.I've joined the experimental project PLEASE let me know if there's some thing wrong.

author: ataradov
author_rank: Super Contributor
timestamp: October 17, 2024, 01:47:25 pm
content: I did think about using uint32_t pointers, but did not propose it, since on newer device where locations have no gaps, 32-bit access does not work and violates the requirement for accessing the buffer. Recalculating the correct address for each access seems more universal.You need to start responding to those requests now.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 17, 2024, 01:59:35 pm
content: Quote from: ataradov on October 17, 2024, 01:47:25 pmYah, the next step is sending the device descriptor and get the assigned address and display it.1- It seems that proteus display only 512bytes from the 1024 the PMA have!2- I don't know how to let wireshark capture the trafic between PROTEUS and the host.When I launch wireshark it only detects my 2 hardware root hubs.

author: ataradov
author_rank: Super Contributor
timestamp: October 17, 2024, 02:00:57 pm
content: No idea how proteus works, can't help you here.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 17, 2024, 02:28:04 pm
content: Quote from: ataradov on October 17, 2024, 02:00:57 pmNever mind.

author: pcprogrammer
author_rank: Super Contributor
timestamp: October 17, 2024, 05:35:43 pm
content: Quote from: DELTA67 on October 17, 2024, 01:59:35 pmThe F103 only has 512 bytes in the PMA. See page 625/1134 of RM0008 Rev 20. Attached is a capture of the bit showing it is only 512 bytes.There is also a mention about this dedicated 512 byte SRAM being shared with the CAN peripheral on page 622.Also added where it shows that both the registers and PMA are 16 bits wide but 32 bits aligned. Access can be done based on both 16 bits and 32 bits wide. As for portability it probably has to be re written to work with newer devices anyway. Have not played with USB on such devices yet, but what I wrote for the F103 does work on the F303.Quote from: DELTA67 on October 17, 2024, 01:59:35 pmIt PROTEUS only mimics a USB interface to be seen by the target device it won't show up under the host itself and wireshark won't be able to use it. But I don't have any knowledge about PROTEUS.

author: DELTA67
author_rank: Regular Contributor
timestamp: October 17, 2024, 08:00:45 pm
content: Quote from: pcprogrammer on October 17, 2024, 05:35:43 pmYes, but those 512bytes occupy 1024 in the memory map due to the 16bits gaps!PROTEUS 8.13 shows only the first 512 bytes from the memory map starting at 0x40006000.Quote from: pcprogrammer on October 17, 2024, 05:35:43 pmOk, I think so.