movement: add voltage monitor watch face
This commit is contained in:
		
							parent
							
								
									88f41b12fc
								
							
						
					
					
						commit
						0f03257ee9
					
				| @ -33,6 +33,7 @@ SRCS += \ | |||||||
|   ../watch_faces/thermistor/thermistor_readout_face.c \
 |   ../watch_faces/thermistor/thermistor_readout_face.c \
 | ||||||
|   ../watch_faces/thermistor/thermistor_logging_face.c \
 |   ../watch_faces/thermistor/thermistor_logging_face.c \
 | ||||||
|   ../watch_faces/demos/character_set_face.c \
 |   ../watch_faces/demos/character_set_face.c \
 | ||||||
|  |   ../watch_faces/demos/voltage_face.c \
 | ||||||
|   ../watch_faces/complications/beats_face.c \
 |   ../watch_faces/complications/beats_face.c \
 | ||||||
| 
 | 
 | ||||||
| # Leave this line at the bottom of the file; it has all the targets for making your project.
 | # Leave this line at the bottom of the file; it has all the targets for making your project.
 | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ | |||||||
| #include "thermistor_logging_face.h" | #include "thermistor_logging_face.h" | ||||||
| #include "character_set_face.h" | #include "character_set_face.h" | ||||||
| #include "beats_face.h" | #include "beats_face.h" | ||||||
|  | #include "voltage_face.h" | ||||||
| 
 | 
 | ||||||
| const watch_face_t watch_faces[] = { | const watch_face_t watch_faces[] = { | ||||||
|     simple_clock_face, |     simple_clock_face, | ||||||
|  | |||||||
							
								
								
									
										66
									
								
								movement/watch_faces/demos/voltage_face.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								movement/watch_faces/demos/voltage_face.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include "voltage_face.h" | ||||||
|  | #include "watch.h" | ||||||
|  | 
 | ||||||
|  | void _voltage_face_update_display() { | ||||||
|  |     char buf[14]; | ||||||
|  |     float voltage = (float)watch_get_vcc_voltage() / 1000.0; | ||||||
|  |     sprintf(buf, "BA  %4.2f V", voltage); | ||||||
|  |     // printf("%s\n", buf);
 | ||||||
|  |     watch_display_string(buf, 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void voltage_face_setup(movement_settings_t *settings, void ** context_ptr) { | ||||||
|  |     (void) settings; | ||||||
|  |     (void) context_ptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void voltage_face_activate(movement_settings_t *settings, void *context) { | ||||||
|  |     (void) settings; | ||||||
|  |     (void) context; | ||||||
|  |     watch_enable_adc(); | ||||||
|  |     // if we set the reference voltage here, watch_get_vcc_voltage won't do it over and over
 | ||||||
|  |     watch_set_analog_reference_voltage(ADC_REFERENCE_INTREF); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool voltage_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { | ||||||
|  |     (void) settings; | ||||||
|  |     (void) context; | ||||||
|  |     watch_date_time date_time; | ||||||
|  |     switch (event.event_type) { | ||||||
|  |         case EVENT_MODE_BUTTON_UP: | ||||||
|  |             movement_move_to_next_face(); | ||||||
|  |             break; | ||||||
|  |         case EVENT_LIGHT_BUTTON_DOWN: | ||||||
|  |             movement_illuminate_led(); | ||||||
|  |             break; | ||||||
|  |         case EVENT_ACTIVATE: | ||||||
|  |             _voltage_face_update_display(); | ||||||
|  |             break; | ||||||
|  |         case EVENT_TICK: | ||||||
|  |             date_time = watch_rtc_get_date_time(); | ||||||
|  |             if (date_time.unit.second % 5 == 4) { | ||||||
|  |                 watch_set_indicator(WATCH_INDICATOR_SIGNAL); | ||||||
|  |             } else if (date_time.unit.second % 5 == 0) { | ||||||
|  |                 _voltage_face_update_display(); | ||||||
|  |                 watch_clear_indicator(WATCH_INDICATOR_SIGNAL); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         case EVENT_TIMEOUT: | ||||||
|  |             movement_move_to_face(0); | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void voltage_face_resign(movement_settings_t *settings, void *context) { | ||||||
|  |     (void) settings; | ||||||
|  |     (void) context; | ||||||
|  |     // make sure to restore the default in the end.
 | ||||||
|  |     watch_set_analog_reference_voltage(ADC_REFCTRL_REFSEL_INTVCC2_Val); | ||||||
|  |     watch_disable_adc(); | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								movement/watch_faces/demos/voltage_face.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								movement/watch_faces/demos/voltage_face.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | #ifndef VOLTAGE_FACE_H_ | ||||||
|  | #define VOLTAGE_FACE_H_ | ||||||
|  | 
 | ||||||
|  | #include "movement.h" | ||||||
|  | 
 | ||||||
|  | void voltage_face_setup(movement_settings_t *settings, void ** context_ptr); | ||||||
|  | void voltage_face_activate(movement_settings_t *settings, void *context); | ||||||
|  | bool voltage_face_loop(movement_event_t event, movement_settings_t *settings, void *context); | ||||||
|  | void voltage_face_resign(movement_settings_t *settings, void *context); | ||||||
|  | 
 | ||||||
|  | static const watch_face_t voltage_face = { | ||||||
|  |     voltage_face_setup, | ||||||
|  |     voltage_face_activate, | ||||||
|  |     voltage_face_loop, | ||||||
|  |     voltage_face_resign, | ||||||
|  |     NULL | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #endif // VOLTAGE_FACE_H_
 | ||||||
| @ -138,6 +138,35 @@ void watch_set_analog_sampling_length(uint8_t cycles) { | |||||||
|     _watch_sync_adc(); |     _watch_sync_adc(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void watch_set_analog_reference_voltage(watch_adc_reference_voltage reference) { | ||||||
|  |     ADC->CTRLA.bit.ENABLE = 0; | ||||||
|  | 
 | ||||||
|  |     if (reference == ADC_REFERENCE_INTREF) SUPC->VREF.bit.VREFOE = 1; | ||||||
|  |     else SUPC->VREF.bit.VREFOE = 0; | ||||||
|  | 
 | ||||||
|  |     ADC->REFCTRL.bit.REFSEL = reference; | ||||||
|  |     ADC->CTRLA.bit.ENABLE = 1; | ||||||
|  |     _watch_sync_adc(); | ||||||
|  |     // throw away one measurement after reference change (the channel doesn't matter).
 | ||||||
|  |     _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_SCALEDCOREVCC); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint16_t watch_get_vcc_voltage() { | ||||||
|  |     // stash the previous reference so we can restore it when we're done.
 | ||||||
|  |     uint8_t oldref = ADC->REFCTRL.bit.REFSEL; | ||||||
|  | 
 | ||||||
|  |     // if we weren't already using the internal reference voltage, select it now.
 | ||||||
|  |     if (oldref != ADC_REFERENCE_INTREF) watch_set_analog_reference_voltage(ADC_REFERENCE_INTREF); | ||||||
|  | 
 | ||||||
|  |     // get the data
 | ||||||
|  |     uint32_t raw_val = _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_SCALEDIOVCC_Val); | ||||||
|  | 
 | ||||||
|  |     // restore the old reference, if needed.
 | ||||||
|  |     if (oldref != ADC_REFERENCE_INTREF) watch_set_analog_reference_voltage(oldref); | ||||||
|  | 
 | ||||||
|  |     return (uint16_t)((raw_val * 1000) / (1024 * 1 << ADC->AVGCTRL.bit.SAMPLENUM)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| inline void watch_disable_analog_input(const uint8_t pin) { | inline void watch_disable_analog_input(const uint8_t pin) { | ||||||
|     gpio_set_pin_function(pin, GPIO_PIN_FUNCTION_OFF); |     gpio_set_pin_function(pin, GPIO_PIN_FUNCTION_OFF); | ||||||
| } | } | ||||||
|  | |||||||
| @ -94,6 +94,53 @@ void watch_set_analog_num_samples(uint16_t samples); | |||||||
|   **/ |   **/ | ||||||
| void watch_set_analog_sampling_length(uint8_t cycles); | void watch_set_analog_sampling_length(uint8_t cycles); | ||||||
| 
 | 
 | ||||||
|  | typedef enum { | ||||||
|  |     ADC_REFERENCE_INTREF = ADC_REFCTRL_REFSEL_INTREF_Val, | ||||||
|  |     ADC_REFERENCE_VCC_DIV1POINT6 = ADC_REFCTRL_REFSEL_INTVCC0_Val, | ||||||
|  |     ADC_REFERENCE_VCC_DIV2 = ADC_REFCTRL_REFSEL_INTVCC1_Val, | ||||||
|  |     ADC_REFERENCE_VCC = ADC_REFCTRL_REFSEL_INTVCC2_Val, | ||||||
|  | } watch_adc_reference_voltage; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /** @brief Selects the reference voltage to use for analog readings. Default is ADC_REFERENCE_VCC.
 | ||||||
|  |   * @param reference One of ADC_REFERENCE_VCC, ADC_REFERENCE_VCC_DIV1POINT6, ADC_REFERENCE_VCC_DIV2 | ||||||
|  |   *                  or ADC_REFERENCE_INTREF. | ||||||
|  |   * @details In order to turn an analog voltage into a 16-bit integer, the ADC needs to compare the | ||||||
|  |   *          measured voltage to a reference point. For example, if you were powering the watch with | ||||||
|  |   *          VCC == 3.0V and you had two 10K resistors connected in series from 3V to GND, you could | ||||||
|  |   *          expect to get 3 volts when you measure the top of the voltage divider, 0 volts at the | ||||||
|  |   *          bottom, and 1.5 volts in the middle. If you read these values uising a reference voltage | ||||||
|  |   *          of ADC_REFERENCE_VCC, the top value would be about 65535, the bottom about 0, and the | ||||||
|  |   *          middle about 32768. However! If we used ADC_REFERENCE_VCC_DIV2 as our reference, we would | ||||||
|  |   *          expect to get 65535 both at the top and the middle, because the largest value the ADC can | ||||||
|  |   *          measure in this configutation is 1.5V (VCC / 2). | ||||||
|  |   * | ||||||
|  |   *          By changing the reference voltage from ADC_REFERENCE_VCC to ADC_REFERENCE_VCC_DIV1POINT6 | ||||||
|  |   *          or ADC_REFERENCE_VCC_DIV2, you can get more resolution when measuring small voltages (i.e. | ||||||
|  |   *          a phototransistor circuit in low light). | ||||||
|  |   * | ||||||
|  |   *          There is also a special reference voltage called ADC_REFERENCE_INTREF. The SAM L22's | ||||||
|  |   *          Supply Controller provides a selectable voltage reference (by default, 1.024 V) that you | ||||||
|  |   *          can select as a reference voltage for ADC conversions. Unlike the three references we | ||||||
|  |   *          talked about in the last paragraph, this reference voltage does not depend on VCC, which | ||||||
|  |   *          makes it very useful for measuring the battery voltage (since you can't really compare | ||||||
|  |   *          VCC to itself). You can change the INTREF voltage to 2.048 or 4.096 V by poking at the | ||||||
|  |   *          supply controller's VREF register, but the watch library does not support this use case. | ||||||
|  |   **/ | ||||||
|  | void watch_set_analog_reference_voltage(watch_adc_reference_voltage reference); | ||||||
|  | 
 | ||||||
|  | /** @brief Returns the voltage of the VCC supply in millivolts (i.e. 3000 mV == 3.0 V). If running on
 | ||||||
|  |   *        a coin cell, this will be the battery voltage. | ||||||
|  |   * @details Unlike other ADC functions, this function does not return a raw value from the ADC, but | ||||||
|  |   *          rather scales it to an actual number of millivolts. This is because the ADC doesn't let | ||||||
|  |   *          us measure VCC per se; it instead lets us measure VCC / 4, and we choose to measure it | ||||||
|  |   *          against the internal reference voltage of 1.024 V. In short, the ADC gives us a number | ||||||
|  |   *          that's complicated to deal with, so we just turn it into a useful number for you :) | ||||||
|  |   * @note This function depends on INTREF being 1.024V. If you have changed it by poking at the supply | ||||||
|  |   *       controller's VREF.SEL bits, this function will return inaccurate values. | ||||||
|  |   */ | ||||||
|  | uint16_t watch_get_vcc_voltage(); | ||||||
|  | 
 | ||||||
| /** @brief Disables the analog circuitry on the selected pin.
 | /** @brief Disables the analog circuitry on the selected pin.
 | ||||||
|   * @param pin One of pins A0-A4. |   * @param pin One of pins A0-A4. | ||||||
|   */ |   */ | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user