STM32 - 4.4us gaps in my signal every 1ms - Page 1
Forum section: electronics / general

author: dentaku
author_rank: Frequent Contributor
timestamp: October 31, 2020, 01:00:54 am
content: I just wrote a very simple Arduino sketch to toggle PA0 on a Blue Pill.Code:void setup() {pinMode(PA0, OUTPUT);}void loop() {GPIOA->ODR |= 0b0000000000000001; //set 1rst bit in PORTA (GPIOA) to 1 using Output Data RegisterGPIOA->ODR &= ~(0b0000000000000001); //low}I just wanted to see how fast the pulses would be.QUESTION:There are gaps in my signal where it stays high for 4.4us and this happens exactly every 1ms.There also seems to be a point where it stays low for around 5us every 1ms.Is there something in the STM32F103C8T6 that ticks every 1ms that interrupts with my pin toggling?

author: drvtech
author_rank: Regular Contributor
timestamp: October 31, 2020, 01:07:46 am
content: Yes, the timer interrupts which allow you to use the millis() function. Whether you are using it or not the timer is still running and causing interrupts

author: dentaku
author_rank: Frequent Contributor
timestamp: October 31, 2020, 01:26:28 am
content: Quote from: drvtech on October 31, 2020, 01:07:46 amOK. so all the pins just freeze for 4.4us in whatever state they're in every 1ms.I suppose if you were using SPI at unusually high speeds the clock would look strange once in a while but I guess it's not a problem.

author: Dabbot
author_rank: Regular Contributor
timestamp: October 31, 2020, 02:52:12 am
content: Quote from: dentaku on October 31, 2020, 01:26:28 amNo. Your code is suspended while the interrupt is handled. And if you're bit-banging pins as you are here, you observe the results.Usually, and especially for SPI, the pins are going to be driven by an onboard peripheral which doesn't care what your code is up to except when its buffers are empty / full.

author: cv007
author_rank: Frequent Contributor
timestamp: October 31, 2020, 04:14:28 am
content: You have BSSR and BRR registers to get you atomic setting/clearing of pins-https://godbolt.org/z/qorhP9and will also better your chances at the 'how fast can I toggle a pin' contest. The atomic part is the important part, as the pin toggle contest is won by those that use peripherals.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 31, 2020, 08:48:12 am
content: You are using arduino outside the intended usage.You are seeing the result of their interrupt handler running. Ditch the Arduino code and you can decide what interrupt handlers you write, if any.I also suggest using BSRR registers to avoid read-modify-write operation of the IO registers: with a simple write operation, you can set any bits to 0 and 1. I use these macros:Code:#define HI(port, idx) do{(port)->BSRR = 1UL<<(idx);}while(0)#define LO(port, idx) do{(port)->BSRR = 1UL<<(16+idx);}while(0)Usage is obvious, isn't it?

author: drvtech
author_rank: Regular Contributor
timestamp: October 31, 2020, 07:18:39 pm
content: Why do you need to wrap the macro definition in a do/while?

author: krish2487
author_rank: Frequent Contributor
timestamp: October 31, 2020, 09:59:56 pm
content: For this reasonhttps://stackoverflow.com/questions/1067226/c-multi-line-macro-do-while0-vs-scope-blockTl;dr - the do while(0) block allows for correct expansion of multiline statements in a macro.And do it enough times and it becomes a muscle memory to implement every macro the correct way - even for a single line macro.Quote from: drvtech on October 31, 2020, 07:18:39 pm

author: dentaku
author_rank: Frequent Contributor
timestamp: October 31, 2020, 10:25:32 pm
content: Quote from: Dabbot on October 31, 2020, 02:52:12 amI'm glad you mentioned that because after posting my reply I wondered about things like SPI that already have hardware built into the microcontroller and how they shouldn't be interfered by the timer interrupt.I just did an experiment using SPI.beginTransaction(SPISettings(36000000, MSBFIRST, SPI_MODE0)); and my scope measures the clock at 36MHz. I'm assuming it won't go any faster because that's half the 72MHz clock speed of the STM32F103.I have absolutely no need to toggle pins this fast, I'm just curious.

author: drvtech
author_rank: Regular Contributor
timestamp: October 31, 2020, 11:49:48 pm
content: krish2487 - Thanks for the do/while explanation. I'll stop hijacking the thread now :-)Drvtech

author: cv007
author_rank: Frequent Contributor
timestamp: November 01, 2020, 12:55:09 am
content: One can also create static functions for these things, where you are then in the compiler's world instead of having a word processor transforming your code into something you cannot see directly. Since cannot see the code directly, you have to employ things like do/while0 for multi statement macros, which is easy enough to forget unless you do it all the time, even when not needed- and makes it look like you are in a code obfuscation contest. Can't easily get away from the pre-processor in C, but moving as much as possible to the compiler is a better option in my opinion.https://godbolt.org/z/PePWW9While you are playing around, you could find out what happens with non-atomic pin manipulation- find that 1ms isr, and in that 1ms isr add some code to toggle a different pin on the same port, your main code pin toggle still using the ODR register as before. What you should see from the isr pin is 1ms toggle, but with the non-atomic work being done on the same port in the main loop, that 1ms toggle should see some 'extra' toggles because of the main code 'undoing' what the isr just did. Maybe a few minutes spent seeing this in action will set in your mind why some of these registers for atomic manipulation exist.

author: dentaku
author_rank: Frequent Contributor
timestamp: November 01, 2020, 02:44:45 am
content: Quote from: cv007 on October 31, 2020, 04:14:28 amConsidering this is just a useless exercise I tried because I wanted to use something other than an AVR I've been reading about a lot about stuff I've never needed to use before.May I ask what the values in this line of code are for?static volatile uint32_t* GpioA_ODR = (volatile uint32_t*)(0x40010800+0x0C);To me that's (0b1000000000000010000100000000000+0b1100) which = 0100 0000 0000 0001 0000 1000 0000 1100having read this."IDR registers are responsible for digital inputs while ODR registers are responsible for digital outputs. In both registers the upper 16 bits are always kept 0 because GPIOs are 16 bits wide while their representative registers are 32 bit wide."Why does your code have two ones in the upper 16bits which are supposed to be reserved?By the way, your code...GPIOA->BSRR = 1; //setGPIOA->BSRR = 1 << 16; //resetIS slightly faster than my original code.

author: cv007
author_rank: Frequent Contributor
timestamp: November 01, 2020, 03:55:29 am
content: >May I ask what the values in this line of code are for?static volatile uint32_t* GpioA_ODR = (volatile uint32_t*)(0x40010800+0x0C);GpioA_ODR is a pointer to a volatile uint32_t and its address is 0x40010800C which was derived from the datasheet (the addition is done in parens so the byte offset address listed in the datasheet can be used, as without parens the addition is done to the first value treated as pointer address, and since the pointer is pointing to a 4byte value/uint32_t, the addition will be 0x0C*4 which is not what is wanted).The address 'stored' in the var GpioA_ODR has nothing to do with what it is pointing to, so the 0x4001 has nothing to do with the upper bits of ODR. This would also be correct-static volatile uint16_t* GpioA_ODR = (volatile uint16_t*)(0x40010800+0x0C);where GpioA_ODR still stores the same address, but now its pointing to a uint16_t instead. Probably a better idea as you will then get a warning of you try to write anything other than a 16bit value. Was a simple example, so did not worry about it.You may want to figure out what the code you wrote does. Decode it using the headers, and you will not be too far from what I wrote. The online compiler does not have headers for an mcu (with some exceptions), so I created my own little 'header' to create an example. All headers, drivers, and code ultimately ends up as standard C code for the compiler to digest, so if you take the time to figure out what a header/driver may be doing you will also discover what the C language can do, as it all ends up there. Once you know how the sausage is made, you can then make your own in whatever manner you prefer.

author: westfw
author_rank: Super Contributor
timestamp: November 01, 2020, 08:32:07 am
content: QuoteNormally you'd use predefined names for the ODR included from some massive .h file provided by ST.The godbolt code analysis tool doesn't have access to those files, so you have to include a "cut down" replacement in your source code.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 01, 2020, 10:10:41 am
content: Quote from: cv007 on November 01, 2020, 03:55:29 amDo note these two do not perform the exact same operation. Dereferencing the first one does a 32-bit bus operation, dereferencing the second one does a 16-bit bus operation. (Bus meaning the APB/AHB bus between the core and the GPIO peripheral). In this particular case (the ODR register), I don't believe there is any difference in the result, performance or functionality. But some peripheral registers, notably SPI data registers in those STM32 chips that implement FIFOs in SPI peripherals, the width of the read/write bus operations define how many elements are pushed to / popped from the FIFOs. For example, if SPI1 on STM32F7/H7 is configured for 8-bit SPI word,*((volatile uint16_t*)&SPI1->TXDR) = 0;sends TWO consecutive words on the SPI bus, while*((volatile uint8_t*)&SPI1->TXDR) = 0;sends only one. Of course, the stupid ST header writers haven't defined such type aliases for accessing the the regs properly at all, so you need to create your own and cast the one given in header to another type. As a result, every young player accidentally sends 4 bytes each time they want to just write a byte on SPI.

author: cv007
author_rank: Frequent Contributor
timestamp: November 01, 2020, 07:06:18 pm
content: > This would also be correct->static volatile uint16_t* GpioA_ODR = (volatile uint16_t*)(0x40010800+0x0C);Actually, I was wrong. If I would have read the datasheet closer, I would have seen that the GPIO registers have to be accessed as 32bit words (at least that is what is says). So, that is another piece of info you have to seek out when putting together your own header or any other of your own register access code. The access requirements will vary, and there is no way to know what they are for any mcu/peripheral/register until you find what the datasheet has to say about it.More header creation, with some 'driver' code-Chttps://godbolt.org/z/v5djhYC++https://godbolt.org/z/Yn6Ees(could be mistakes in both, as I just wrote them without looking too close at the asm output)lots of ways to use these languages.

author: dentaku
author_rank: Frequent Contributor
timestamp: November 01, 2020, 10:31:40 pm
content: That's all interesting stuff I might learn more about some day. I've never had to do any type of coding like this before but I DO like knowing who stuff works so diving deeper into what my code is doing behind the scenes is sometimes worth doing.When I saw the * in the godbolt code I wondered if it had something to do with pointers, which I've never understood considering I've never gone much beyond typical Arduino programming.This all came about because I wanted to find how to do what I normally do with PORTx = on AVRs on a BluePill and this thread went out on a tangent

author: westfw
author_rank: Super Contributor
timestamp: November 02, 2020, 03:11:48 am
content: QuoteSee also "bit banding." If you don't have enough tangents yet.On chips that support bit-banding (some Cortex-M3s, mostly. Including the STM32f103) you can define an address that lets you access a single bit of the peripheral IO space. Something like:Code:static volatile uint32_t* GpioA12 = (volatile uint16_t*)(0x42000000 +(10800+0x0C)*32 + 12*4);GpioA12 = 0; //set A12 to 0GpioA12 = 1; // set A12 to 1(I don't really recommend looking at this too much; bit banding seems to have "not caught on.")

author: S. Petrukhin
author_rank: Super Contributor
timestamp: November 02, 2020, 04:40:48 am
content: Don't expect Arduino to be completely transparent and in control of the situation. This is a child's toy, very stupid in relation to the MCU.In addition to using the registers to control bits, the Cortex has a bit alias area. For each bit (each!) of RAM (including peripheral registers), 32 bits are allocated in a separate memory area, in which the lowest bit controls. That is, to control any bit, you can define a boolean variable in the alias area.