cancel
Showing results for 
Search instead for 
Did you mean: 

What's wrong with my half-duplex (receive only) software UART?

arnold_w
Senior II
Posted on June 09, 2016 at 14:20

I am working with the Discovery Development Board and I would like to receive data over a software UART. In the code below I would expect to receive 8 timer interrupts if only 1 byte is transmitted, but 100 microseconds after the last bit I receive an extra timer interrupt. Does anybody know why? Other than that, the UART seems to works fine.

000 #define TEST_PIN GPIO_PIN_15
001 #define TEST_PORT GPIOC
002 #define TOGGLE_TEST_PIN_AT_SAMPLING() SET_PIN_HIGH(TEST_PORT, TEST_PIN); SET_PIN_LOW(TEST_PORT, TEST_PIN); \
003 SET_PIN_HIGH(TEST_PORT, TEST_PIN); SET_PIN_LOW(TEST_PORT, TEST_PIN)
004 
005 #define Rx_PIN GPIO_PIN_11
006 #define Rx_PORT GPIOC
007 #define Rx_PIN_FLAG 0x0800
008 #define RECEIVE_EVENT_ARG LOW_LEVEL_EVENT__UART2_SW_BYTE_RECEIVED
009 #define EXTERNAL_INT EXTI15_10_IRQn
010 #define EXTERNAL_INT_HANDLER EXTI15_10_IRQHandler
011 #define TIMER_INSTANCE TIM4
012 #define TIMER_INT TIM4_IRQn
013 #define TIMER_INT_HANDLER TIM4_IRQHandler
014 
015 
016 #define CLEAR_EXT_INT_FLAG_ON_Rx() __HAL_GPIO_EXTI_CLEAR_IT(Rx_PIN)
017 #define ENABLE_EXT_INT_ON_Rx() HAL_NVIC_EnableIRQ(EXTERNAL_INT)
018 #define DISABLE_EXT_INT_ON_Rx() HAL_NVIC_DisableIRQ(EXTERNAL_INT)
019 
020 #define START_TIMER() (TIMER_INSTANCE->CR1 |= TIM_CR1_CEN)
021 #define STOP_TIMER() (TIMER_INSTANCE->CR1 &= ~TIM_CR1_CEN)
022 #define TIMER_INT_FLAG (TIMER_INSTANCE->SR & 0x0001)
023 #define ENABLE_TIMER_INT() 
do
{ TIMER_INSTANCE->DIER |= TIM_DIER_UIE; HAL_NVIC_EnableIRQ(TIMER_INT); } 
while
(0)
024 #define DISABLE_TIMER_INT() 
do
{ TIMER_INSTANCE->DIER &= ~TIM_DIER_UIE; HAL_NVIC_DisableIRQ(TIMER_INT); } 
while
(0)
025 #define CLEAR_TIMER_INT_FLAG() (TIMER_INSTANCE->SR &= ~TIM_SR_UIF)
026 #define SET_TIMER_PERIOD(period) (TIMER_INSTANCE->ARR = period)
027 #define SET_COUNTER_VALUE(value) (TIMER_INSTANCE->CNT = value)
028 #define SET_PRE_SCALAR_1() 
do
{ TIMER_INSTANCE->PSC = 0; TIMER_INSTANCE->EGR = TIM_EGR_UG; } 
while
(0)
029 
030 #define DUMMY_INT CAN2_RX1_IRQn
031 #define DUMMY_INT_HANDLER CAN2_RX1_IRQHandler
032 #define ENABLE_DUMMY_INT() (HAL_NVIC_EnableIRQ(DUMMY_INT))
033 #define CLEAR_DUMMY_INT_FLAG() (HAL_NVIC_ClearPendingIRQ(DUMMY_INT))
034 #define ISSUE_DUMMY_INT() NVIC->STIR = DUMMY_INT
035 
036 
037 
static
EventCallback_t ByteReceivedCallback_ = (EventCallback_t)NULL;
038 
static
uint32_t timerPeriod;
039 
static
uint32_t timerPeriodTimesOnePointFive;
040 
static
uint8_t idleState_;
041 
042 
043 
void
UART2_SW_Configuration(uint8_t idleState)
044 {
045 idleState_ = idleState;
046 
047 GPIO_InitTypeDef GPIO_InitStructure;
048 GPIO_InitStructure.Pin = Rx_PIN;
049 
if
(idleState_ == 0)
050 {
051 GPIO_InitStructure.Pull = GPIO_PULLDOWN;
052 GPIO_InitStructure.Mode = GPIO_MODE_IT_RISING;
053 }
054 
else
055 {
056 GPIO_InitStructure.Pull = GPIO_PULLUP;
057 GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING;
058 }
059 GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
060 GPIO_InitStructure.Alternate = 0;
061 HAL_GPIO_Init(Rx_PORT, &GPIO_InitStructure);
062 
063 SET_PRE_SCALAR_1();
064 SET_TIMER_PERIOD(0xFFFFFFFF);
065 
066 HAL_NVIC_SetPriority(DUMMY_INT, 2, 0);
067 HAL_NVIC_SetPriority(EXTERNAL_INT, 1, 0);
068 HAL_NVIC_SetPriority(TIMER_INT, 0, 0);
069 
070 GPIO_InitStructure.Pin = TEST_PIN;
071 GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
072 GPIO_InitStructure.Speed = GPIO_SPEED_FAST;
073 GPIO_InitStructure.Pull = GPIO_NOPULL;
074 GPIO_InitStructure.Alternate = 0;
075 HAL_GPIO_Init(TEST_PORT, &GPIO_InitStructure);
076 SET_PIN_LOW(TEST_PORT, TEST_PIN);
077 
078 ENABLE_DUMMY_INT();
079 CLEAR_EXT_INT_FLAG_ON_Rx();
080 ENABLE_EXT_INT_ON_Rx();
081 }
082 
083 
084 
void
UART2_SW_SetCallback(EventCallback_t ByteReceivedCallback)
085 {
086 
if
(ByteReceivedCallback == (EventCallback_t)NULL)
087 {
088 ByteReceivedCallback_ = (EventCallback_t)NULL;
089 DISABLE_EXT_INT_ON_Rx();
090 }
091 
else
092 {
093 ByteReceivedCallback_ = ByteReceivedCallback;
094 ENABLE_EXT_INT_ON_Rx();
095 }
096 }
097 
098 
099 
void
UART2_SW_SetBaudRate(uint32_t baudRate)
100 {
101 timerPeriod = GET_TIMER_FREQ(TIMER_INSTANCE)/baudRate;
102 timerPeriodTimesOnePointFive = (3 * ((uint32_t)timerPeriod)) >> 1;
103 }
104 
105 
106 
static
volatile
uint8_t bitCount = 0;
107 
static
volatile
uint8_t receivedByte = 0;
108 
static
volatile
uint8_t finishedReceivedByte = 0;
109 
static
uint8_t sampledValue;
110 
111 
void
EXTERNAL_INT_HANDLER(
void
)
112 {
113 SET_COUNTER_VALUE(0);
114 START_TIMER();
115 DISABLE_EXT_INT_ON_Rx();
116 SET_TIMER_PERIOD(timerPeriodTimesOnePointFive);
117 CLEAR_TIMER_INT_FLAG();
118 ENABLE_TIMER_INT();
119 bitCount = 0;
120 }
121 
122 
123 
void
TIMER_INT_HANDLER(
void
)
124 {
125 TOGGLE_TEST_PIN_AT_SAMPLING();
126 sampledValue = READ_PIN(Rx_PORT, Rx_PIN);
127 CLEAR_TIMER_INT_FLAG();
128 
if
(bitCount == 0)
129 {
130 SET_TIMER_PERIOD(timerPeriod);
131 receivedByte = sampledValue;
132 bitCount++;
133 }
134 
else
if
((1 <= bitCount) && (bitCount <= 6))
135 {
136 receivedByte |= (sampledValue << bitCount);
137 bitCount++;
138 }
139 
else
140 {
141 STOP_TIMER();
142 DISABLE_TIMER_INT();
143 SET_TIMER_PERIOD(0xFFFFFFFF);
144 finishedReceivedByte = receivedByte | (sampledValue << 7);
145 
if
(idleState_ == 0)
146 {
147 finishedReceivedByte = ~finishedReceivedByte;
148 }
149 
if
(bitCount == 7)
150 {
151 ISSUE_DUMMY_INT();
152 }
153 
if
(ByteReceivedCallback_ != (EventCallback_t)NULL)
154 {
155 CLEAR_EXT_INT_FLAG_ON_Rx();
156 ENABLE_EXT_INT_ON_Rx();
157 }
158 }
159 }
160 
161 
162 
void
DUMMY_INT_HANDLER(
void
)
163 {
164 
if
(ByteReceivedCallback_ != (EventCallback_t)NULL)
165 {
166 (
void
)ByteReceivedCallback_((uint32_t)RECEIVE_EVENT_ARG, (uint32_t)finishedReceivedByte);
167 }
168 }

10 REPLIES 10
arnold_w
Senior II
Posted on June 09, 2016 at 14:57

It seems I get a ''bonus'' external interrupt after each byte so actually I get another 8 timer interrupts (16 in total) for each received byte. Does anybody know what it is in my code that triggers that extra external interrupt? The last transition on my UART data is the opposite of the start bit and since I only trigger on either falling or rising edge it can't be the last bit that triggers the extra internal interrupt.

arnold_w
Senior II
Posted on June 09, 2016 at 15:21

I can get rid of the 8 extra timer interrupts by changing my external interrupt handler:

void
EXTERNAL_INT_HANDLER(

void

)
{
SET_COUNTER_VALUE(0);
START_TIMER();
if
((EXTI->PR & Rx_PIN_FLAG) == 0)
{
STOP_TIMER();
return
;
}
DISABLE_EXT_INT_ON_Rx();
SET_TIMER_PERIOD(timerPeriodTimesOnePointFive);
CLEAR_TIMER_INT_FLAG();
ENABLE_TIMER_INT();
bitCount = 0;
}

However, when I run this on my own PCB (which runs at 1,85 Volts and needs more flash latency and pre-fetch can't be enabled) then it becomes flaky because it can't cope with the transmitted data rate.
Posted on June 09, 2016 at 15:47

I use the following construction:

  while (EXTI->PR AND (1 SHL FREQ_NOT_GOOD_PIN)) {  // clear pending in EXTI controller

    EXTI->PR = (1 SHL FREQ_NOT_GOOD_PIN);

  }

  do {

    NVIC_ClearPendingIRQ(FREQ_NOT_GOOD_IRQn);

  } while (NVIC_GetPendingIRQ(FREQ_NOT_GOOD_IRQn));

I don't recall the details but certainly resulted from some painful experimentation, which might have left traces on this forum too.

JW

arnold_w
Senior II
Posted on June 10, 2016 at 13:38

Your suggestion solved the problem. Thanks!

arnold_w
Senior II
Posted on June 14, 2016 at 10:34

The problem is back (I might have changed the clock frequency), the code below gives me two external interrupts per received byte;

#define CLEAR_EXT_INT_FLAG_ON_Rx() while (EXTI->PR & Rx_PIN_FLAG) \
{ 
/* clear pending in EXTI controller */
\
EXTI->PR = Rx_PIN_FLAG; \
} \
do
{ \
NVIC_ClearPendingIRQ(EXTI15_10_IRQn); \
} 
while
(NVIC_GetPendingIRQ(EXTI15_10_IRQn))
void
EXTERNAL_INT_HANDLER(
void
)
{
SET_COUNTER_VALUE(0);
START_TIMER();
DISABLE_EXT_INT_ON_Rx();
SET_TIMER_PERIOD(timerPeriodTimesOnePointFive);
CLEAR_TIMER_INT_FLAG();
ENABLE_TIMER_INT();
bitCount = 0;
}
void
TIMER_INT_HANDLER(
void
)
{
TOGGLE_TEST_PIN_AT_SAMPLING();
sampledValue = READ_PIN(Rx_PORT, Rx_PIN);
CLEAR_TIMER_INT_FLAG();
if
(bitCount == 0)
{
SET_TIMER_PERIOD(timerPeriod);
receivedByte = sampledValue;
bitCount++;
}
else
if
((1 <= bitCount) && (bitCount <= 6))
{
receivedByte |= (sampledValue << bitCount);
bitCount++;
}
else
{
STOP_TIMER();
DISABLE_TIMER_INT();
SET_TIMER_PERIOD(0xFFFFFFFF);
finishedReceivedByte = receivedByte | (sampledValue << 7);
if
(idleState_ == 0)
{
finishedReceivedByte = ~finishedReceivedByte;
}
if
(bitCount == 7)
{
ISSUE_DUMMY_INT();
}
if
(ByteReceivedCallback_ != (EventCallback_t)NULL)
{
CLEAR_EXT_INT_FLAG_ON_Rx();
ENABLE_EXT_INT_ON_Rx();
}
}
}

Does anybody know what's wrong? I don't get the extra external interrupt if I instead of using timer interrupts poll a timer:

void
EXTERNAL_INT_HANDLER(
void
)
{
SET_COUNTER_VALUE(0);
START_TIMER();
DISABLE_ALL_INTS_IF_NECESSARY();
DISABLE_EXT_INT_ON_Rx();
SET_TIMER_PERIOD(timerPeriodTimesOnePointFive);
CLEAR_TIMER_INT_FLAG();
while
(TIMER_INT_FLAG == 0) {};
// 1st data bit
sampledValue = READ_PIN(Rx_PORT, Rx_PIN);
CLEAR_TIMER_INT_FLAG();
SET_TIMER_PERIOD(timerPeriod);
receivedByte = sampledValue;
while
(TIMER_INT_FLAG == 0) {};
// 2nd data bit
sampledValue = READ_PIN(Rx_PORT, Rx_PIN);
CLEAR_TIMER_INT_FLAG();
receivedByte |= (sampledValue << 1);
while
(TIMER_INT_FLAG == 0) {};
// 3rd data bit
sampledValue = READ_PIN(Rx_PORT, Rx_PIN);
CLEAR_TIMER_INT_FLAG();
receivedByte |= (sampledValue << 2);
while
(TIMER_INT_FLAG == 0) {};
// 4th data bit
sampledValue = READ_PIN(Rx_PORT, Rx_PIN);
CLEAR_TIMER_INT_FLAG();
receivedByte |= (sampledValue << 3);
while
(TIMER_INT_FLAG == 0) {};
// 5th data bit
sampledValue = READ_PIN(Rx_PORT, Rx_PIN);
CLEAR_TIMER_INT_FLAG();
receivedByte |= (sampledValue << 4);
while
(TIMER_INT_FLAG == 0) {};
// 6th data bit
sampledValue = READ_PIN(Rx_PORT, Rx_PIN);
CLEAR_TIMER_INT_FLAG();
receivedByte |= (sampledValue << 5);
while
(TIMER_INT_FLAG == 0) {};
// 7th data bit
sampledValue = READ_PIN(Rx_PORT, Rx_PIN);
CLEAR_TIMER_INT_FLAG();
receivedByte |= (sampledValue << 6);
while
(TIMER_INT_FLAG == 0) {};
// 8th data bit
sampledValue = READ_PIN(Rx_PORT, Rx_PIN);
STOP_TIMER();
SET_TIMER_PERIOD(0xFFFFFFFF);
if
(idleState_ == 0)
{
finishedReceivedByte = ~(receivedByte | (sampledValue << 7));
}
else
{
finishedReceivedByte = receivedByte | (sampledValue << 7);
}
CLEAR_EXT_INT_FLAG_ON_Rx();
ENABLE_ALL_INTS_IF_THEY_WERE_ENABLED();
ISSUE_DUMMY_INT();
if
(ByteReceivedCallback_ == (EventCallback_t)NULL)
{
DISABLE_EXT_INT_ON_Rx();
CLEAR_EXT_INT_FLAG_ON_Rx();
}
else
{
CLEAR_EXT_INT_FLAG_ON_Rx();
ENABLE_EXT_INT_ON_Rx();
}
}

However, I would prefer to use the interrupt based code. In addition, I don't like it when mysterious things are happening in my microcontroller...
Posted on June 14, 2016 at 11:00

> TOGGLE_TEST_PIN_AT_SAMPLING();

Move this from Timer's interrupt to the external interrupt handler, then show pulses of this test pin, together with the input waveform. JW
Posted on June 14, 2016 at 11:38

One more thing

> I might have changed the clock frequency

How exactly? Did it involve change of the APB prescalers? What mcu are we talking about?

JW
arnold_w
Senior II
Posted on June 14, 2016 at 12:08

I have attached two photos, one with the received data and the external interrupts and one with the received data and the timer interrupts. In both cases the character A (hex 41) is transmitted at 115200 Baud.

I am running on the Discovery Development Board with an STM32F407 microcontroller. My UART is capable of sending characters just fine and when sending it's using the same timer periods as when receiving. Therefore, I'm not suspecting clock issues.

________________

Attachments :

ExternalInt.jpg : https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006I0ZN&d=%2Fa%2F0X0000000baY%2F12UMtTZbUYtYGpHaniPIOshsAZhkgcU32067U4F83gc&asPdf=false

TimerInt.jpg : https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006I0ZI&d=%2Fa%2F0X0000000baX%2FYyz2SHUMp2xa6aHiEr8m1ZJfiZSdWYXGVhdk9lMKnIs&asPdf=false
arnold_w
Senior II
Posted on June 14, 2016 at 13:04

I found the problem, it was a copy-and-paste-error. The following:

#define CLEAR_EXT_INT_FLAG_ON_Rx() while (EXTI->PR & Rx_PIN_FLAG) \
{ 
/* clear pending in EXTI controller */
\
EXTI->PR = Rx_PIN_FLAG; \
} \
do
{ \
NVIC_ClearPendingIRQ(EXTI15_10_IRQn); \
} 
while
(NVIC_GetPendingIRQ(EXTI15_10_IRQn))

should instead be:

#define CLEAR_EXT_INT_FLAG_ON_Rx() while (EXTI->PR & Rx_PIN_FLAG) \
{ 
/* clear pending in EXTI controller */
\
EXTI->PR = Rx_PIN_FLAG; \
} \
do
{ \
NVIC_ClearPendingIRQ(EXTERNAL_INT); \
} 
while
(NVIC_GetPendingIRQ(EXTERNAL_INT))