2012-08-29 08:11 AM
I am trying to have a simple millisecond delay function and experiencing some very strange results. I am using Keil uVision V4.00.0. Here is my code:
#include ''stm32f0_discovery.h''
//prototypes
void Delay_ms(long ms);
//configure clocks
void RCC_Configuration(void)
{
/* --------------------------- System Clocks Configuration -----------------*/
/* GPIOC clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
}
//configure GPIO
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*-------------------------- GPIO Configuration ----------------------------*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
int main(void) {
RCC_Configuration();
GPIO_Configuration();
while(1) {
GPIO_ResetBits(GPIOC, GPIO_Pin_9);
GPIO_SetBits(GPIOC, GPIO_Pin_8);
Delay_ms(5);
GPIO_ResetBits(GPIOC, GPIO_Pin_8);
GPIO_SetBits(GPIOC, GPIO_Pin_9);
Delay_ms(5);
}
}
void Delay_ms(long ms) {
//ms = ms * 48000 / 4;
ms = ms * 38400 / 4;
//ms = 48000;
while (ms) {
ms--;
}
}
This creates an 33Hz squarewave on pin PC8 (measured using scope). I had expected to use a value of 48000 rather than 38400 but that gave an 80Hz squarewave. Iam trying to create 100Hz, which is why I changed it to 38400 expecting to increase the frequency by 25%. I have tried some different lines and get very stange behaviour:
| Multiplier |
Frequency(Hz) |
debug starting Hex |
debug starting Dec|
Clk per loop|
48000
00
5
24000
00
5
12000
00
5
20000
00
5
38400
33
BB80
48000
6
38402
00
BB82
48002
5
38401
00
BB81
48001
5
38399
00
BB7E
47998
5
What's so special about ''ms = ms * 38400 / 4''? I had expected the loop to take 4 clock cycles (hence starting with ''ms = ms * 48000 / 4''). Next I tried hardwiring the starting values, like using ''ms = 48000''. 48,000 created 125Hz and 60,000 created 100Hz. So that's 4 clocks per cycle. looking at the debug the starting Hex in the first case is BB80! I'm using Level 3 (O3) optimization under Keil. Any ideas why I am seeing such strange behaviour? #loop-timing #keil-uvision #stm32f0512012-08-30 10:18 AM
For a purely software loop, I'd attack it like this.
ALIGN
Spin_Delay PROC
EXPORT Spin_Delay
SUBS R0,R0,#1
BNE Spin_Delay
BX LR
ENDP
extern void Spin_Delay(long);
#define DELAY_MS(x) Spin_Delay(((x) * (48000 / 4)) - 3)
...
while(1)
{
GPIOC->BSRR = (1 <<
8
) | ((1 << 9) << 16);
DELAY_MS(5);
GPIOC->BSRR = (1 << 9) | ((1 << 8) << 16);
DELAY_MS(5);
}
2012-08-31 02:57 AM
Thanks for this. It does assume that the correct delay figure is in R0, is that a fair assumption?
I have been trying to acheive the desired result in the C file without much success. I tried adding the line__asm(''ALIGN'');
but Keil complains that:
''main.c(54): error: #1113: Inline assembler not permitted when generating Thumb code''. I also tried:
#pragma arm
__asm(''ALIGN'');
#pragma thumb
But Keil complains
''main.c(53): error: #1114-D: this feature not supported on target architecture/processor''. The Cortex M0 is thumb only I think, which means no ALIGN:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0497a/CIHJJEIH.html
I suspect the key for consistent operation is to avoid using division in the sub-routine as that is of indetermate length, whereas MULS is of fixed length. I was using the division to make it clear how it used the clock frequency but think it's better to use defines:#define CLOCK_FREQUENCY 48000000
#define DELAY_MS_MULTIPLIER (CLOCK_FREQUENCY / 5000)
That way the sub-routine becomes:
void Delay_ms(long ms) {
ms = ms * DELAY_MS_MULTIPLIER;
__nop();
while (ms) {
ms--;
}
}
notice the __nop(); line is still required.
I think this approach should be pretty safe as the function call should be aligned shouldn't it?? If wanting to be extra safe, I could have two loops, one of which will always be aligned and one of which will always be unaligned. This seems excessive though:
#define CLOCK_FREQUENCY 48000000
#define DELAY_MS_MULTIPLIER (CLOCK_FREQUENCY / 11000)
void Delay_ms(long ms) {
long milliseconds;
milliseconds = ms * DELAY_MS_MULTIPLIER;//48000 / 11;
//__nop();
while (milliseconds) {
milliseconds--;
}
milliseconds = ms * DELAY_MS_MULTIPLIER;//48000 / 11;
__nop();
while (milliseconds) {
milliseconds--;
}
}
2012-08-31 03:22 AM
Perhaps a bit obsessive, but I have changed the 11 to 12 so that the division of 48000 is exact. With the ms function it may only result in a 0.014% error, but it's considerable when using the us sub-routine I've just added:
#define CLOCK_FREQUENCY 48000000
#define DELAY_MS_MULTIPLIER (CLOCK_FREQUENCY / 12000)
#define DELAY_US_MULTIPLIER (CLOCK_FREQUENCY / 12000000) //adapt sub-routine if clock frequency is not divisible by 12
void Delay_ms(long ms) {
long milliseconds;
milliseconds = ms * DELAY_MS_MULTIPLIER;//48000 / 12;
while (milliseconds) {
milliseconds--;
__nop();
}
milliseconds = ms * DELAY_MS_MULTIPLIER;//48000 / 12;
__nop();
while (milliseconds) {
milliseconds--;
}
}
void Delay_us(long us) { //adapt sub-routine if clock frequency is not divisible by 12
//Only acceptable accuracy (<1%) for durations of 50us or more due to sub-routine call time!!
long microseconds;
microseconds = us * DELAY_US_MULTIPLIER;//48 / 12;
while (microseconds) {
microseconds--;
__nop();
}
microseconds = us * DELAY_US_MULTIPLIER;//48 / 12;
__nop();
while (microseconds) {
microseconds--;
}
}
Of course calling the sub-routine will result in an error for very low numbers of us, something that could be corrected in time if needed.
2012-08-31 04:36 AM
Here is a more accurate sub-routine but you must never call it with an argument of 0 or it will make very long delays!
void Risky_Delay_us(long us) { //adapt sub-routine if clock frequency is not divisible by 12
//Only acceptable accuracy (<1%) for durations of 5us or more due to sub-routine call time!!
//********************* CAUTION NEVER CALL WITH us = 0 !!!!!!! *************************************
long microseconds;
microseconds = us * DELAY_US_MULTIPLIER;//48 / 12;
while (microseconds) {
microseconds--;
__nop();
}
us--; //attempt to compensate for sub-routine call time (~0.5us @48MHZ clock)
microseconds = us * DELAY_US_MULTIPLIER;//48 / 12;
__nop();
while (microseconds) {
microseconds--;
}
}
Heed the warning! AnIF to check slows the sub-routine down more but is safer so:
void Delay_us(long us) { //adapt sub-routine if clock frequency is not divisible by 12
//Only acceptable accuracy (<
1
%) for durations of 10us or more due to sub-routine call time!!
long microseconds;
if(us >= 1) {
microseconds = us * DELAY_US_MULTIPLIER;//48 / 12;
while (microseconds) {
microseconds--;
__nop();
}
us--; //attempt to compensate for sub-routine call time (~0.5us @48MHZ clock)
microseconds = us * DELAY_US_MULTIPLIER;//48 / 12;
__nop();
while (microseconds) {
microseconds--;
}
}
}
This sort of compensation is really easy when working in assembler of course.
2012-08-31 05:28 AM
I put my assembler in startup.s
2012-08-31 06:31 AM
That's great thanks Clive1, works a treat. I have made one simple change which is to change the BNE to a BGT for the sake of safety (in case of accidentally sending a negative number to Spin_Delay).
Sits nicely at the end of the startup_stm32f0xx.s file above the ;*******; User Stack... where there was already an ALIGN so I didn't duplicate that. At 48MHz it should work for delays of up to 357 seconds (2^32 /12e6) which gives good flexibility.