kputc()
, remove the
recursive call to handle CR LF transmission and avoid the busy wait
loop. USART1 can trigger an interrupt when the Transmit Data Register
(TDR) is empty which is all I need to implement interrupt driven
transmission.
I make a copy of startup.c into startup.txeie.c to do the changes. The name txeie refers to Transmit Data Register Empty Interrupt Enabled.
/* Stubs for System Exception Handler */ void Default_Handler( void) ; #define dflt_hndlr( fun) void fun##_Handler( void) \ __attribute__((weak,alias("Default_Handler"))) dflt_hndlr( NMI) ; dflt_hndlr( HardFault) ; dflt_hndlr( SVCall) ; dflt_hndlr( PendSV) ; dflt_hndlr( SysTick) ; dflt_hndlr( WWDG) ; dflt_hndlr( RTC) ; dflt_hndlr( FLASH) ; dflt_hndlr( RCC) ; dflt_hndlr( EXTI0_1) ; dflt_hndlr( EXTI2_3) ; dflt_hndlr( EXTI4_15) ; dflt_hndlr( DMA_CH1) ; dflt_hndlr( DMA_CH2_3) ; dflt_hndlr( DMA_CH4_5) ; dflt_hndlr( ADC) ; dflt_hndlr( TIM1_BRK_UP_TRG_COM) ; dflt_hndlr( TIM1_CC) ; dflt_hndlr( TIM3) ; dflt_hndlr( TIM6) ; dflt_hndlr( TIM14) ; dflt_hndlr( TIM15) ; dflt_hndlr( TIM16) ; dflt_hndlr( TIM17) ; dflt_hndlr( I2C1) ; dflt_hndlr( I2C2) ; dflt_hndlr( SPI1) ; dflt_hndlr( SPI2) ; dflt_hndlr( USART1) ; dflt_hndlr( USART2) ; dflt_hndlr( USART3_4_5_6) ; dflt_hndlr( USB) ; /* Interrupt vector table: * 1 Stack Pointer reset value * 15 System Exceptions * 32 Device specific Interrupts */ typedef void (*isr_p)( void) ; isr_p const isr_vector[ 16 + 32] __attribute__((section(".isr_vector"))) = { (isr_p) &__StackTop, /* System Exceptions */ Reset_Handler, NMI_Handler, HardFault_Handler, 0, 0, 0, 0, 0, 0, 0, SVCall_Handler, 0, 0, PendSV_Handler, SysTick_Handler, /* STM32F030xx specific Interrupts cf RM0360 */ WWDG_Handler, 0, RTC_Handler, FLASH_Handler, RCC_Handler, EXTI0_1_Handler, EXTI2_3_Handler, EXTI4_15_Handler, 0, DMA_CH1_Handler, DMA_CH2_3_Handler, DMA_CH4_5_Handler, ADC_Handler, TIM1_BRK_UP_TRG_COM_Handler, TIM1_CC_Handler, 0, TIM3_Handler, TIM6_Handler, 0, TIM14_Handler, TIM15_Handler, TIM16_Handler, TIM17_Handler, I2C1_Handler, I2C2_Handler, SPI1_Handler, SPI2_Handler, USART1_Handler, USART2_Handler, USART3_4_5_6_Handler, 0, USB_Handler } ;
I add the description of the TX Empty Interrupt Enable bit in the Configuration Register of USART1:
#define USART_CR1_TXEIE (1 << 7) /* 7: TDR Empty Interrupt Enable */I use a Round Robin buffer to synchronize
kputc()
and
USART1_Handler()
making sure they don’t write to the same location.
static unsigned char txbuf[ 8] ; // best if size is a power of 2 for cortex-M0 #define TXBUF_SIZE (sizeof txbuf / sizeof txbuf[ 0]) static unsigned char txbufin ; static volatile unsigned char txbufout ;
kputc()
write in txbuf[]
while
USART1_Handler()
only read from it.
txbufin
is the index of the position where
kputc()
will insert a character, it’s written by
kputc()
and read by USART1_Handler()
.
txbufout
is the index of the position where
USART1_Handler()
will fetch a character, it’s written by
USART1_Handler()
and read by kputc()
. The value of
txbufout
will change under interrupt, so it is marked as
volatile
to make sure the compiler will not optimize the code in a
conflicting way.
(idx+1) %
size
into (idx+1) & (size-1)
, which is more efficient on chipset
with no support for integer division.
void USART1_Handler( void) { if( txbufout == txbufin) { /* Empty buffer => Disable TXEIE */ USART1[ CR1] &= ~USART_CR1_TXEIE ; } else { static unsigned char lastc ; unsigned char c ; c = txbuf[ txbufout] ; if( c == '\n' && lastc != '\r') c = '\r' ; else txbufout = (txbufout + 1) % TXBUF_SIZE ; USART1[ TDR] = c ; lastc = c ; } } void kputc( unsigned char c) { /* character output */ int nextidx ; /* Wait if buffer full */ nextidx = (txbufin + 1) % TXBUF_SIZE ; while( nextidx == txbufout) yield() ; txbuf[ txbufin] = c ; txbufin = nextidx ; /* Trigger transmission by enabling interrupt */ USART1[ CR1] |= USART_CR1_TXEIE ; }
kputc()
enables the interrupt generation after a new
character is inserted in the buffer.
USART1_Handler()
disables the interrupt generation when the
buffer is empty.
kputc()
now yields when the buffer is full.
The 32 device specific interrupts are enabled through the Nested Vectored Interrupt Controller (NVIC). NVIC is a core peripheral so its description is in the Programming Manual. Enabling is done through the Interrupt Set-Enable Register (ISER).
Set-Enable means writing a 1 enables while writing a 0 does nothing. Reading reports the current settings. There is a corresponding Clear-Enable register, to disable interrupts.
#define NVIC ((volatile long *) 0xE000E100) #define NVIC_ISER NVIC[ 0] #define unmask_irq( idx) NVIC_ISER = 1 << idx #define USART1_IRQ_IDX 27I add a call to the macro unmask_irq() after USART1 initialization.
/* Unmask USART1 irq */ unmask_irq( USART1_IRQ_IDX) ;
SRCS = startup.txeie.c txeie.c uptime.cBuild completes successfully
$ make f030f4.elf from startup.txeie.o txeie.o uptime.o text data bss dec hex filename 2097 0 20 2117 845 f030f4.elf f030f4.hex f030f4.binChecking the map and lst files I can verify that
kputc()
was using 4 bytes of bss
to hold lastchar (1 byte). The new version uses 12 bytes to hold the round
robin buffer (8 bytes), its in and out indexes (2 bytes) and lastchar (1 byte).
% size
to
bit masking & (size – 1)
as the size 8 is a power of 2.
Next, I will use an external sensor to do some measurement.