Branch data Line data Source code
1 : : /**
2 : : * @file app.c
3 : : * @brief Shared application layer implementation (UART TX, system clock, LED)
4 : : */
5 : :
6 : : #include "app.h"
7 : : #if defined(OW_PORT_FAMILY_F0)
8 : : #include "stm32f0xx.h"
9 : : #elif defined(OW_PORT_FAMILY_G0)
10 : : #include "stm32g0xx.h"
11 : : #else
12 : : #include "stm32f1xx.h"
13 : : #endif
14 : :
15 : : // ======== USART1 TX ring buffer ========
16 : : static uint32_t uart_tx_head = 0; // write index - points to next free slot
17 : : static uint32_t uart_tx_tail = 0; // read index - points to oldest data
18 : : static uint8_t uart_tx_buf[UART_TX_BUF_SIZE]; // circular buffer for UART transmission
19 : :
20 : : /**
21 : : * @brief Advance USART1 transmission by at most one byte (non-blocking)
22 : : * @note Must be called periodically to feed the UART from the ring buffer
23 : : */
24 : 1410 : void uart_poll_tx(void) {
25 : : #if defined(OW_PORT_FAMILY_G0)
26 : : // G0 uses the modern USART naming: TXE/TXFNF lives in ISR, data in TDR
27 [ + - + - ]: 470 : if ((USART1->ISR & USART_ISR_TXE_TXFNF) && (uart_tx_tail != uart_tx_head)) {
28 : 470 : uint8_t b = uart_tx_buf[uart_tx_tail];
29 : 470 : uart_tx_tail = (uart_tx_tail + 1u) & UART_TX_IDX_MASK;
30 : 470 : USART1->TDR = b;
31 : : }
32 : : #elif defined(OW_PORT_FAMILY_F0)
33 : : // F0 unified USART naming: TXE lives in ISR, data goes to TDR
34 [ + - + - ]: 470 : if ((USART1->ISR & USART_ISR_TXE) && (uart_tx_tail != uart_tx_head)) {
35 : : // Get byte from buffer at tail position
36 : 470 : uint8_t b = uart_tx_buf[uart_tx_tail];
37 : : // Advance tail pointer with wrap-around
38 : 470 : uart_tx_tail = (uart_tx_tail + 1u) & UART_TX_IDX_MASK;
39 : : // Write byte to UART data register for transmission
40 : 470 : USART1->TDR = b;
41 : : }
42 : : #else
43 : : // Check if UART is ready to transmit (TXE flag set) and buffer not empty
44 [ + - + - ]: 470 : if ((USART1->SR & USART_SR_TXE) && (uart_tx_tail != uart_tx_head)) {
45 : : // Get byte from buffer at tail position
46 : 470 : uint8_t b = uart_tx_buf[uart_tx_tail];
47 : : // Advance tail pointer with wrap-around
48 : 470 : uart_tx_tail = (uart_tx_tail + 1u) & UART_TX_IDX_MASK;
49 : : // Write byte to UART data register for transmission
50 : 470 : USART1->DR = b;
51 : : }
52 : : #endif
53 : 1410 : }
54 : :
55 : : /**
56 : : * @brief Block until every enqueued byte has been transmitted
57 : : * @note Blocking, intended for diagnostic/blocking code paths only; the
58 : : * demos keep the non-blocking uart_poll_tx() discipline.
59 : : */
60 : 54 : void uart_flush(void) {
61 [ + + ]: 1266 : while (uart_tx_tail != uart_tx_head) {
62 : 1212 : uart_poll_tx();
63 : : }
64 : 54 : }
65 : :
66 : : /**
67 : : * @brief Enqueue a single byte into the UART transmit buffer (non-blocking)
68 : : * @param[in] b Byte to enqueue
69 : : * @return 1 if enqueued, 0 if the buffer is full (byte dropped)
70 : : * @note Never blocks: when the buffer is full the byte is dropped so the
71 : : * caller's code path stays non-blocking.
72 : : */
73 : 1428 : int uart_tx_enqueue_byte(int b) {
74 : 1428 : uint32_t head = uart_tx_head;
75 : : // Calculate next head position with wrap-around using power-of-two mask
76 : 1428 : uint32_t next = (head + 1u) & UART_TX_IDX_MASK;
77 [ + + ]: 1428 : if (next != uart_tx_tail) { // Room is available
78 : 1410 : uart_tx_buf[head] = (uint8_t)b; // Store byte at current head position
79 : 1410 : uart_tx_head = next; // Update head pointer
80 : 1410 : return 1;
81 : : }
82 : 18 : return 0; // Buffer full - drop the byte to stay non-blocking
83 : : }
84 : :
85 : : /**
86 : : * @brief Enqueue an entire null-terminated string (non-blocking)
87 : : * @param[in] s Null-terminated string to enqueue
88 : : * @return Number of characters actually enqueued (may be less than strlen)
89 : : */
90 : 144 : int uart_write_str(const char* s) {
91 : 144 : const char* start = s;
92 [ + + ]: 594 : while (*s) {
93 [ + + ]: 456 : if (uart_tx_enqueue_byte(*s)) {
94 : 450 : s++;
95 : : } else {
96 : 6 : break; // Buffer full - stop to stay non-blocking
97 : : }
98 : : }
99 : 144 : return (int)(s - start);
100 : : }
101 : :
102 : : /**
103 : : * @brief Convert integer to string and enqueue for UART transmission
104 : : * @param[in] value Integer value to convert and transmit
105 : : * @return Number of characters enqueued
106 : : * @note Buffer holds 12 chars, enough for the full int32 range (-2147483648)
107 : : */
108 : 84 : int uart_write_int(int value) {
109 : : char buf[12]; // enough for -2147483648 and '\0'
110 : 84 : char* p = buf + sizeof(buf) - 1;
111 : 84 : *p = '\0';
112 : :
113 [ + + ]: 84 : if (value == 0) { // Special case for zero
114 : 6 : *(--p) = '0';
115 : : } else {
116 : 78 : int is_negative = 0;
117 : : unsigned int uvalue;
118 : :
119 [ + + ]: 78 : if (value < 0) { // Handle negative numbers
120 : 12 : is_negative = 1;
121 : 12 : uvalue = (unsigned int)-(value + 1) + 1;
122 : : } else {
123 : 66 : uvalue = (unsigned int)value;
124 : : }
125 : :
126 : : do { // Convert digits from least significant to most significant
127 : 216 : *(--p) = '0' + (uvalue % 10);
128 : 216 : uvalue /= 10;
129 [ + + ]: 216 : } while (uvalue);
130 : :
131 [ + + ]: 78 : if (is_negative) *(--p) = '-'; // Add negative sign if needed
132 : : }
133 : 84 : return uart_write_str(p);
134 : : }
135 : :
136 : : /**
137 : : * @brief Enqueue one byte as two uppercase hexadecimal digits (non-blocking)
138 : : * @param[in] b Byte to convert and transmit
139 : : * @return Number of characters actually enqueued (0, 1 or 2)
140 : : */
141 : 54 : int uart_write_hex(uint8_t b) {
142 : : static const char hex[] = "0123456789ABCDEF";
143 : 54 : int n = 0;
144 : 54 : n += uart_tx_enqueue_byte(hex[(b >> 4) & 0x0F]);
145 : 54 : n += uart_tx_enqueue_byte(hex[b & 0x0F]);
146 : 54 : return n;
147 : : }
148 : :
149 : : #if !defined(DS18B20_TEST_HARNESS)
150 : : /* Hardware bring-up (system clock, USART1 TX, LED GPIO) with full register
151 : : *-level access. Excluded from the host test build, which only exercises the
152 : : * non-blocking UART ring buffer above. */
153 : :
154 : : /**
155 : : * @brief Configure system clock
156 : : * @note The source is derived from OW_PORT_SYSCLK_MHZ (see onewire.h).
157 : : * F1: 72MHz via HSE+PLL x9, or raw HSI at 8MHz. F030x6 has no HSE:
158 : : * 48MHz via HSI/2+PLL x12, or raw HSI at 8MHz. G031x6 has no HSE:
159 : : * 64MHz via HSI16+PLL (M=1, N=8, R=2), or raw HSI16 at 16MHz.
160 : : */
161 : : __STATIC_FORCEINLINE void configure_system_clock(void) {
162 : : #if defined(OW_PORT_FAMILY_G0)
163 : : #if (OW_PORT_SYSCLK_MHZ) == 64
164 : : // HSI16 is on and stable right after reset. PLL source must be selected
165 : : // explicitly: on this family PLLSRC=00 means "no clock sent to the PLL",
166 : : // HSI16 encodes as 10 (RCC_PLLCFGR_PLLSRC_HSI). M(=1) xN(=8) R(=2) then
167 : : // gives 64MHz; PLLREN enables the PLLR output the SYSCLK mux uses.
168 : : RCC->PLLCFGR = RCC_PLLCFGR_PLLSRC_HSI | RCC_PLLCFGR_PLLN_3 |
169 : : RCC_PLLCFGR_PLLR_0 | RCC_PLLCFGR_PLLREN;
170 : : RCC->CR |= RCC_CR_PLLON;
171 : : while (!(RCC->CR & RCC_CR_PLLRDY))
172 : : ;
173 : : // Flash latency: 2 wait states above 48MHz (RM0444)
174 : : FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_LATENCY_1;
175 : : // Switch system clock to PLLRCLK
176 : : RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_SW) | RCC_CFGR_SW_PLLRCLK;
177 : : while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLLRCLK)
178 : : ;
179 : : #elif (OW_PORT_SYSCLK_MHZ) == 16
180 : : // Raw HSI16: the MCU already runs on the internal 16MHz RC after reset —
181 : : // nothing to configure
182 : : #else
183 : : #error "Unsupported OW_PORT_SYSCLK_MHZ for G0: use 64 (HSI16+PLL) or 16 (raw HSI16)"
184 : : #endif
185 : : #elif defined(OW_PORT_FAMILY_F0)
186 : : #if (OW_PORT_SYSCLK_MHZ) == 48
187 : : // PLL input is HSI/2 = 4MHz; x12 gives 48MHz. Configure the multiplier
188 : : // before enabling the PLL so it locks on a valid clock (per RM0360).
189 : : RCC->CFGR = RCC_CFGR_PLLMUL12;
190 : : RCC->CR |= RCC_CR_PLLON;
191 : : while (!(RCC->CR & RCC_CR_PLLRDY))
192 : : ;
193 : : // Flash latency for 48MHz operation (1 wait state)
194 : : FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
195 : : // Switch system clock to PLL
196 : : RCC->CFGR |= RCC_CFGR_SW_PLL;
197 : : while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
198 : : ;
199 : : #elif (OW_PORT_SYSCLK_MHZ) == 8
200 : : // Raw HSI: the MCU already runs on the internal 8MHz RC after reset —
201 : : // nothing to configure
202 : : #else
203 : : #error "Unsupported OW_PORT_SYSCLK_MHZ for F0: use 48 (HSI+PLL) or 8 (raw HSI)"
204 : : #endif
205 : : #else /* F1 */
206 : : #if (OW_PORT_SYSCLK_MHZ) == 72
207 : : // Enable HSI and HSE oscillators
208 : : RCC->CR = RCC_CR_HSION | RCC_CR_HSEON;
209 : : // Wait for HSE to stabilize - HSERDY is the hardware stabilization
210 : : // indicator, so no fixed delay is required
211 : : while (!(RCC->CR & RCC_CR_HSERDY))
212 : : ;
213 : : // Configure PLL: HSE source, multiply by 9, APB1 prescaler /2
214 : : RCC->CFGR = RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9 | RCC_CFGR_PPRE1_DIV2;
215 : : // Enable PLL only after HSE is confirmed stable, so the PLL locks on a
216 : : // valid clock (per RM0008: HSE must be ready before enabling the PLL)
217 : : RCC->CR |= RCC_CR_PLLON;
218 : : // Wait for the PLL to lock
219 : : while (!(RCC->CR & RCC_CR_PLLRDY))
220 : : ;
221 : : // Configure flash latency for 72MHz operation
222 : : FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_2;
223 : : // Switch system clock to PLL
224 : : RCC->CFGR = RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9 | RCC_CFGR_PPRE1_DIV2 | RCC_CFGR_SW_PLL;
225 : : // Wait for system clock switch to PLL
226 : : while ((RCC->CFGR & RCC_CFGR_SWS_PLL) != RCC_CFGR_SWS_PLL)
227 : : ;
228 : : // Disable HSI oscillator
229 : : RCC->CR &= ~RCC_CR_HSION;
230 : : #elif (OW_PORT_SYSCLK_MHZ) == 8
231 : : // Raw HSI: the MCU already runs on the internal 8MHz RC after reset —
232 : : // nothing to configure
233 : : #else
234 : : #error "Unsupported OW_PORT_SYSCLK_MHZ for F1: use 72 (HSE+PLL) or 8 (raw HSI)"
235 : : #endif
236 : : #endif
237 : : }
238 : :
239 : : /**
240 : : * @brief Initialize microcontroller peripherals for UART communication and LED control
241 : : * @note F1: USART1 TX on PA9 (AF push-pull), LED on PC13. F0: same PA9 UART
242 : : * via MODER/AFR, LED on PA4 (no GPIOC on F030x6). G0: UART TX on
243 : : * logical PA9 (PA11 pad after the SYSCFG remap, see ow_port_g0.h),
244 : : * LED on PA4 (no PC13 bonded out on TSSOP20).
245 : : */
246 : : __STATIC_FORCEINLINE void hardware_init(void) {
247 : : #if defined(OW_PORT_FAMILY_F0) || defined(OW_PORT_FAMILY_G0)
248 : : // Enable clock for GPIOA and USART1 (G0: GPIO on IOPENR, USART1 on APBENR2)
249 : : #if defined(OW_PORT_FAMILY_G0)
250 : : RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
251 : : RCC->APBENR2 |= RCC_APBENR2_USART1EN;
252 : : #else
253 : : RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
254 : : RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
255 : : #endif
256 : :
257 : : // Configure PA9 as alternate function push-pull output (F0: AF1, G0: AF1
258 : : // = USART1_TX; on G0 the signal lands on the PA11 pad via SYSCFG remap)
259 : : #if defined(OW_PORT_FAMILY_G0)
260 : : GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODE9) | GPIO_MODER_MODE9_1;
261 : : #else
262 : : GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER9) | GPIO_MODER_MODER9_1;
263 : : #endif
264 : : GPIOA->AFR[1] = (GPIOA->AFR[1] & ~GPIO_AFRH_AFSEL9) | (1u << GPIO_AFRH_AFSEL9_Pos);
265 : :
266 : : // Configure PA4 as general purpose output for LED control
267 : : #if defined(OW_PORT_FAMILY_G0)
268 : : GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODE4) | GPIO_MODER_MODE4_0;
269 : : #else
270 : : GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER4) | GPIO_MODER_MODER4_0;
271 : : #endif
272 : :
273 : : // Configure USART1: 115200 baud, 8 data bits, no parity, 1 stop bit, TX only
274 : : USART1->BRR = USART_BRR_CALC((OW_PORT_SYSCLK_MHZ) * 1000000u, 115200); // PCLK = SYSCLK
275 : : USART1->CR1 = USART_CR1_TE | USART_CR1_UE; // Enable USART1; TX enable only
276 : : #else
277 : : // Enable clock for GPIOA, USART1, and GPIOC peripherals
278 : : RCC->APB2ENR |= (RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPCEN);
279 : :
280 : : // Configure PA9 as alternate function push-pull output, 2MHz speed
281 : : // Clear existing configuration bits
282 : : GPIOA->CRH &= ~(GPIO_CRH_MODE9 | GPIO_CRH_CNF9);
283 : : // Set alternate function push-pull output mode, 2MHz speed
284 : : GPIOA->CRH |= (GPIO_CRH_MODE9_1 | GPIO_CRH_CNF9_1);
285 : :
286 : : // Configure PC13 as general purpose output, 2MHz speed for LED control
287 : : GPIOC->CRH &= ~(GPIO_CRH_MODE13 | GPIO_CRH_CNF13);
288 : : GPIOC->CRH |= GPIO_CRH_MODE13_1;
289 : :
290 : : // Configure USART1: 115200 baud, 8 data bits, no parity, 1 stop bit, TX only
291 : : USART1->BRR = USART_BRR_CALC((OW_PORT_SYSCLK_MHZ) * 1000000u, 115200); // PCLK2 = SYSCLK
292 : : USART1->CR1 = USART_CR1_TE | USART_CR1_UE; // Enable USART1; TX enable only
293 : : #endif
294 : : }
295 : :
296 : : /**
297 : : * @brief Initialize system clock, USART1 TX and the busy LED GPIO
298 : : */
299 : : void app_init(void) {
300 : : configure_system_clock();
301 : : hardware_init();
302 : : }
303 : :
304 : : /**
305 : : * @brief Busy indicator - toggles LED during measurement
306 : : * @param[in] action 0 = idle, non-zero = busy
307 : : * @note Non-blocking LED control using atomic BSRR register operations.
308 : : * Strong definition overrides the weak one in the DS18B20 driver.
309 : : * F1: LED on PC13 (active low). F0: LED on PA4 (active low assumed).
310 : : */
311 : : void ds18b20_busy(unsigned action) {
312 : : #if defined(OW_PORT_FAMILY_F0) || defined(OW_PORT_FAMILY_G0)
313 : : if (action) {
314 : : // Turn LED on (PA4 low)
315 : : #if defined(OW_PORT_FAMILY_G0)
316 : : GPIOA->BSRR = GPIO_BSRR_BR4;
317 : : #else
318 : : GPIOA->BSRR = GPIO_BSRR_BR_4;
319 : : #endif
320 : : } else {
321 : : // Turn LED off (PA4 high)
322 : : #if defined(OW_PORT_FAMILY_G0)
323 : : GPIOA->BSRR = GPIO_BSRR_BS4;
324 : : #else
325 : : GPIOA->BSRR = GPIO_BSRR_BS_4;
326 : : #endif
327 : : }
328 : : #else
329 : : if (action) {
330 : : // Turn LED on (PC13 low due to pull-up LED configuration)
331 : : // BSRR BR register: atomic bit reset operation
332 : : GPIOC->BSRR = GPIO_BSRR_BR13;
333 : : } else {
334 : : // Turn LED off (PC13 high)
335 : : // BSRR BS register: atomic bit set operation
336 : : GPIOC->BSRR = GPIO_BSRR_BS13;
337 : : }
338 : : #endif
339 : : }
340 : : #endif /* !DS18B20_TEST_HARNESS */
|