ASCII Serial Com
Serial communication library between computers, microcontrollers, FPGAs, etc. Uses only ASCII. Not the most efficient protocol, but meant to be easy to read
Loading...
Searching...
No Matches
stm32f091nucleo64_adc_streaming.c
Go to the documentation of this file.
1/** \file
2 *
3 * \brief Streams ADC data from pin A0 (happens to be labelled A0 on the Arduino
4 * connector of nucleo-f091rc board)
5 *
6 * The time between ADC conversions is set in register 6, in milliseconds.
7 *
8 * Through changing option flag, can instead stream a 32 bit counter
9 *
10 * DAC channel 1 (A4 and Arduino A2 on board) is also enabled
11 * and may be changed by writing to register 3. Period between
12 * DAC conversions is configurable as is trinagle/noise generation.
13 *
14 * A register write can also turn on/off the LED
15 *
16 * Register map is documented at \ref register_map
17 *
18 */
19
20#include <libopencm3/cm3/cortex.h>
21#include <libopencm3/cm3/nvic.h>
22#include <libopencm3/stm32/adc.h>
23#include <libopencm3/stm32/dac.h>
24#include <libopencm3/stm32/gpio.h>
25#include <libopencm3/stm32/rcc.h>
26#include <libopencm3/stm32/timer.h>
27#include <libopencm3/stm32/usart.h>
28
29#include "arm/stm_usart.h"
30#include "asc_exception.h"
31#include "asc_helpers.h"
32#include "ascii_serial_com.h"
35#include "circular_buffer.h"
36#include "millisec_timer.h"
37
38// ASC_USART should be connected through USB
39#define ASC_USART_NO 2
40#define ASC_USART USART2
41
42#define PORT_LED GPIOA
43#define PIN_LED GPIO5
44#define RCC_GPIO_LED RCC_GPIOA
45
46/////////////////////////////////
47
48CEXCEPTION_T e;
49uint16_t nExceptions;
50
52millisec_timer adc_timer;
53uint32_t adc_sample_period_ms = 1000;
54uint32_t adc_n_overruns = 0;
55millisec_timer dac_reset_timer;
56#define DAC_RESET_TIME_MS 500
57
58/////////////////////////////////
59
60uint32_t optionFlags = 0;
61
62#define nRegs 10
63
64/** \brief Register Map
65 *
66 * ## Register Map
67 *
68 * |Register Number | Description | r/w |
69 * | -------------- |------------ | --- |
70 * | 0 | PORTA input data register, bit 5 is LED | r |
71 * | 1 | PORTA output data register, bit 5 is LED | r, bit 5 is w |
72 * | 2 | optionFlags: see below | r/w |
73 * | 3 | DAC Channel 1 output val | r, bottom 12 bits w |
74 * | 4 | Reserved for DAC Channel 2, but not implemented | r |
75 * | 5 | Current millisecond_timer value | r |
76 * | 6 | Period between ADC samples in ms * | r/w |
77 * | 7 | Number of times ADC overrun flag has been set | r |
78 * | 8 | Time between DAC conversion in 100 us * | r/w 16 bits |
79 * | 9 | DAC waveform & waveform amplitude sel. * | r, w bits 6-10 |
80 *
81 * ### Option flags
82 *
83 * |Big Number | Description |
84 * | -------------- |------------ |
85 * | 0 | if 0: stream ADC, if 1: stream counter |
86 * | 1 | if 1: disable & re-enable DAC, then reset bit to 0, if 0: nothing |
87 *
88 * Register 6: ADC sample period only written to ADC register when 'n' command
89 * received
90 *
91 * Register 8: default 100, so update every 10 ms
92 *
93 * ### Waveform generation with register 9
94 *
95 * Register 9: default 0. Bits 6-7 (start from 0) control waveform generation.
96 * 00: disabled, 01: noise, 1x: triangle. Bits 8-11 are amplitude: where the
97 * max-min is 2^(n+1)-1, where n is the value (at least for triangle), maxing
98 * out at 0b1011=0xB. The triangle starts from reg 3 and goes up to the max-min
99 * value programmed, then comes back down. The noise is centered around
100 * register 3.
101 *
102 * So, write to the register: 0xYZ0, where Y is the amplitude: 0-B, and Z is
103 * the waveform type: 0 for none, 1 for nosie, 8 for triangle.
104 *
105 * I'd say the noise has an RMS in the region of 10 counts. The mask doesn't
106 * change the RMS that much.
107 *
108 * **It may be necessary to reset the DAC (see option flags) to change the
109 * amplitude and/or disable waveform generation**
110 *
111 * Register 9 examples:
112 *
113 * - triangle with max-min = 127: 0b10110000000 = 0x680
114 * - full scale triangle: write 9 0xB80, write 3 0, write 8 3
115 * - noise with large amplitude: 0xB10
116 * - flat, waveform disabled: 0
117 *
118 * @see register_write_masks
119 *
120 */
121volatile REGTYPE *register_map[nRegs] = {
122 &GPIOA_IDR, // input data reg
123 &GPIOA_ODR, // output data reg
124 &optionFlags,
125 &DAC_DHR12R1(DAC1), // DAC Channel 1 Data holding register
126 &DAC_DHR12R2(DAC1), // DAC Channel 2 Data holding register (not active)
127 &MILLISEC_TIMER_NOW, // millisec timer value
128 &adc_sample_period_ms, // Time between ADC samples in ms
129 &adc_n_overruns, // number of times overrun bit has been set
130 &TIM_ARR(TIM6), // time between DAC conversions in 100 us
131 &DAC_CR(DAC1), // waveform and waveform amplitude selection only want bits
132 // 6-11 (start from 0)
133};
134
135/** \brief Write masks for \ref register_map
136 *
137 * These define whether the given register in register_map is writable or not
138 *
139 */
140REGTYPE register_write_masks[nRegs] = {
141 0, // input data reg
142 1 << 5, // output data reg
143 0xFFFFFFFF, // option flags
144 0xFFF, // DAC_DHR12R1
145 0, // DAC_DHR12R2
146 0, // MILLISEC_TIMER_NOW
147 0xFFFFFFFF, // adc_sample_period_ms
148 0, // adc_n_overruns
149 0xFFFF, // TIM_ARR(TIM6)
150 0xFC0, // DAC_CR(DAC), w bits 6-11
151};
152
153uint32_t counter = 0;
154
156
157///////////////////////////////////
158
159static void gpio_setup(void) {
160 rcc_periph_clock_enable(RCC_GPIO_LED);
161
162 gpio_mode_setup(PORT_LED, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, PIN_LED);
163}
164
165static void adc_setup(void) {
166 // With nChans == 1 and ADC_MODE_SCAN, there is only one conversion per
167 // "trigger" You can manually trigger by setting ADSTART in ADC_CR
168 // ADSTART will stay set and only be cleared when EOSEQ is set
169 // After the conversion is complete EOC and EOSEQ are both set (in ADC_ISR)
170 // You can read the data from ADC_DR
171 rcc_periph_clock_enable(RCC_ADC);
172 rcc_periph_clock_enable(RCC_GPIOA);
173
174 // Only pins A0=ADC_IN0 happens to also be Arduino A0
175 gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO0);
176#define nChans 1
177 uint8_t adc_channel_sequence[nChans] = {0};
178
179 adc_power_off(ADC);
180 adc_set_clk_source(ADC, ADC_CLKSOURCE_ADC);
181 adc_calibrate(ADC);
182 adc_set_operation_mode(
183 ADC, ADC_MODE_SCAN); // ADC_MODE_SCAN is ~cont and ~discon;
184 // ADC_MODE_SCAN_INFINITE is cont and ~dicon
185 adc_disable_external_trigger_regular(ADC);
186 adc_set_right_aligned(ADC);
187 adc_enable_temperature_sensor();
188 adc_set_sample_time_on_all_channels(ADC, ADC_SMPTIME_071DOT5);
189 adc_set_regular_sequence(ADC, nChans, adc_channel_sequence);
190 adc_set_resolution(ADC, ADC_RESOLUTION_12BIT);
191 adc_disable_analog_watchdog(ADC);
192 adc_power_on(ADC); // this sync waits until ADRDY is set
193}
194
195static void dac_setup(void) {
196 // Setup TIM6
197 rcc_periph_clock_enable(RCC_TIM6);
198 TIM_PSC(TIM6) = rcc_ahb_frequency / 100 - 1; // tick every 100 us
199 TIM_ARR(TIM6) = 100; // by default, update every 10 ms
200 TIM_CR2(TIM6) |= TIM_CR2_MMS_UPDATE; // trigger output on update
201 TIM_CR1(TIM6) |= TIM_CR1_ARPE; // wait for update to update the value of
202 // auto-reload shadow reg
203 TIM_CR1(TIM6) |= TIM_CR1_CEN; // enable
204
205 // Setting this up to trigger using timer 6, so setting that up here too.
206 rcc_periph_clock_enable(RCC_DAC);
207 rcc_periph_clock_enable(RCC_GPIOA);
208
209 // Pin A4 is DAC1 output and Arduino A2 on board
210 // Pin A5 is DAC2 output and Arduino D13 on board
211 // Only enable DAC1 for now
212 gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO4);
213
214 dac_disable(DAC1, DAC_CHANNEL1);
215 dac_buffer_enable(DAC1, DAC_CHANNEL1);
216 dac_disable_waveform_generation(DAC1, DAC_CHANNEL1);
217 dac_set_trigger_source(DAC1, DAC_CR_TSEL1_T6);
218 dac_trigger_enable(DAC1, DAC_CHANNEL1);
219 dac_enable(DAC1, DAC_CHANNEL1);
220}
221
222uint8_t tmp_byte = 0;
223int main(void) {
224
225 Try {
227 nRegs);
228
229 millisec_timer_systick_setup(rcc_ahb_frequency);
230 gpio_setup();
231 adc_setup();
232 dac_setup();
233
234 // USART2 TX: PA2 AF1
235 // USART2 RX: PA3 AF1
236 rcc_periph_clock_enable(RCC_GPIOA);
237 rcc_periph_clock_enable(RCC_USART2);
238 setup_usart(ASC_USART, 9600, GPIOA, GPIO2, GPIO_AF1, GPIOA, GPIO3,
239 GPIO_AF1);
240 nvic_enable_irq(NVIC_USART2_IRQ);
241 usart_enable_rx_interrupt(ASC_USART);
242 usart_enable(ASC_USART);
243 }
244 Catch(e) { return e; }
245
246 while (1) {
247 Try {
248 const bool stream_state_before_receiving = streaming_is_on;
250 // just started streaming so start this timer
251 if (streaming_is_on && !stream_state_before_receiving) {
253 adc_sample_period_ms);
254 }
255
256 if ((optionFlags >> 1) &
257 1) { // reset DAC, needed b/c it's hard to disable noise generation
258 dac_disable(DAC1, DAC_CHANNEL1);
259 optionFlags &= ~(1 << 1);
261 DAC_RESET_TIME_MS);
262 }
263 if (millisec_timer_is_expired(&dac_reset_timer, MILLISEC_TIMER_NOW)) {
264 dac_enable(DAC1, DAC_CHANNEL1);
265 }
266
267 if (streaming_is_on && !(optionFlags & 1) &&
269 adc_start_conversion_regular(ADC);
270 }
271
273 if (optionFlags & 1) { // counter stream mode
274 char counter_buffer[8];
275 convert_uint32_to_hex(counter, counter_buffer, true);
277 counter++;
278 } else { // ADC stream mode
279 if (adc_eos(ADC)) {
280 if (adc_get_overrun_flag(ADC)) {
281 adc_n_overruns++;
282 adc_clear_overrun_flag(ADC);
283 }
284 static uint16_t adc_val;
285 static char adc_val_buffer[4];
286 adc_val = adc_read_regular(ADC);
287 ADC_ISR(ADC) |= (1 << ADC_ISR_EOSEQ) | (1 << ADC_ISR_EOC);
288 convert_uint16_to_hex(adc_val, adc_val_buffer, true);
290 3);
291 }
292 }
293 }
294 }
295 Catch(e) { nExceptions++; }
296 }
297
298 return 0;
299}
300
301def_usart_isr_push_rx_to_circ_buf(usart2_isr, ASC_USART, &extraInputBuffer)
#define HANDLE_ASC_COMM_IN_POLLING_LOOP(uart_no)
Polling for ascii_serial_com_device and ascii_serial_com_register_pointers.
#define STREAM_TO_HOST_ASC_DEVICE_W_REGISTER_POINTERS(data, data_size)
Stream data to the host.
#define READY_TO_STREAM_ASC_DEVICE_W_REGISTER_POINTERS
Check if you should stream a message to the host.
#define DECLARE_ASC_DEVICE_W_REGISTER_POINTERS()
Declarations for ascii_serial_com_device and ascii_serial_com_register_pointers.
#define SETUP_ASC_DEVICE_W_REGISTER_POINTERS(register_map, register_write_masks, nRegs)
Setup for ascii_serial_com_device and ascii_serial_com_register_pointers.
void convert_uint16_to_hex(uint16_t num, char *outstr, bool caps)
convert uint16 to hex string
void convert_uint32_to_hex(uint32_t num, char *outstr, bool caps)
convert uint32 to hex string
ASCII Serial Com Device.
ASCII Serial Com Register Pointers.
void millisec_timer_systick_setup(uint32_t ahb_frequency)
Setup the systick timer.
void millisec_timer_set_rel(millisec_timer *timer, const millisec_timer_unit_t now, const millisec_timer_unit_t rel)
Set timer to expire in the future.
bool millisec_timer_is_expired_repeat(millisec_timer *timer, const millisec_timer_unit_t now)
Check if timer has expired & if so, re-enable for the same interval.
bool millisec_timer_is_expired(millisec_timer *timer, const millisec_timer_unit_t now)
Check if timer has expired & if so, disable it.
Portable millisecond timer.
static uint32_t MILLISEC_TIMER_NOW
A counter that increments every millisecond.
#define MILLISEC_TIMER_SYSTICK_IT
Millisecond timer SysTick interrupt.
REGTYPE register_write_masks[nRegs]
Write masks for register_map.
volatile REGTYPE * register_map[nRegs]
Register Map.
To be used with the USART peripherals on STM32 microcontrollers.
#define setup_usart(usart, baud, tx_port, tx_pin, tx_af, rx_port, rx_pin, rx_af)
Setup a USART.
Definition stm_usart.h:52
#define def_usart_isr_push_rx_to_circ_buf(isr_name, usart, circular_buffer)
Define the ISR for a USART to push rx bytes to a circular buffer.
Definition stm_usart.h:95
portable millisecond timer