Compare commits
10 Commits
aa52d57149
...
6a2e8164d9
Author | SHA1 | Date |
---|---|---|
true | 6a2e8164d9 | |
true | b3c799d7e7 | |
true | 6394c3aaff | |
true | ad383a76ac | |
true | 418fdafe56 | |
true | 36a55a865b | |
true | f2d4afce1c | |
true | cfd9715023 | |
true | f504e921d6 | |
true | 7fb2dee523 |
|
@ -0,0 +1,86 @@
|
|||
## TESO-O Fun Robot at Hackaday Supercon 2023
|
||||
|
||||
Firmware for TEST-O: The Continuity and Diode Test Funbot
|
||||
|
||||
This is a PlatformIO-compatible project.
|
||||
|
||||
|
||||
|
||||
**BE AWARE:**
|
||||
On REV1 boards, the right potentiometer only works for the first half of its rotation. Turning the right pot beyond half way _will not cause damage_, but the addon will consume extra current (up to ~3-5mA). If the knob is within the affected second half of rotation, the eye LEDs will flash red as an indicator while in RGB fun mode.
|
||||
|
||||
To work around this, use the right pot only within the first half of its rotation.
|
||||
|
||||
|
||||
|
||||
|
||||
## What can you do with it?
|
||||
|
||||
The TEST-O Robot is:
|
||||
|
||||
- A continuity meter
|
||||
- Set the switch to the left position.
|
||||
- Use the provided probes to check continuity.
|
||||
- The left eye will show continuity status.
|
||||
- Continuity is indicated if a resistance of ~100ohms or less is detected.
|
||||
- Turn the buzzer on or off using the button while in this mode.
|
||||
- Testing voltage is ~1.2V and limited to ~2mA.
|
||||
|
||||
- A diode test meter
|
||||
- Set the switch to the right position.
|
||||
- Use the provided probes on the diode.
|
||||
- The right eye will show diode test status.
|
||||
- Status is updated if the diode conducts (positive on anode, negative on cathode).
|
||||
- Shorts are shown with a different eye LED color.
|
||||
- LEDs can be tested, though not all will light or indicate on the right eye.
|
||||
- Testing voltage is ~3.1V and limited to ~2mA.
|
||||
|
||||
- An RGB light show with long dangly probe arms
|
||||
- Up to you if you want to short things out with dangly probes
|
||||
- 7 configurable RGB programs available
|
||||
- Turn the potentiometers to set program parameters
|
||||
- There is an empty RGB LED program slot available for hackers to add one
|
||||
|
||||
|
||||
## Hacking
|
||||
|
||||
Use a DAPLink probe to debug. If you don't have one, there are many projects to convert existing devboards to DAPLink.
|
||||
|
||||
If you want to hack on this without a debug probe, an XMODEM bootloader is available. Use a TTL UART to connect to the UART pins on the addon header. To activate the bootloader, ensure the left knob is between halfway to full clockwise. Press and hold the button while powering on. The bootloader will now indicate it is working with flashing LEDs, and will communicate at 115200-8-N-1. Send your firmware.bin file using XMODEM using a terminal emulator.
|
||||
|
||||
|
||||
## Specifications
|
||||
|
||||
- PUYA PY32F003W16S6 MCU
|
||||
- ARM Cortex-M0+ core running at 8MHz
|
||||
- 32K Flash / 4K RAM
|
||||
- 2x RGBLED for indicators or fun mode
|
||||
- Low-power piezo buzzer for continuity test
|
||||
- Built-in continuity cheeks and LED test antenna
|
||||
- Pots to set things just how you want them
|
||||
- Works as an addon, or standalone with a CR2032
|
||||
|
||||
|
||||
## Misc Notes:
|
||||
|
||||
- This device is using PUYA PY32F003 MCU, an MCU intended for
|
||||
the China domestic market. This MCU, as well as other
|
||||
PY32F002A/003/030 cores with at least 32K flash, internally
|
||||
uses the same core (and has the same features) as PY32F030F6P6.
|
||||
|
||||
- The normal firwmare does not use UART and leaves the pins configured
|
||||
as SWD for the user.
|
||||
|
||||
- There is no official OpenOCD support for this MCU. This project uses
|
||||
pyOCD as adding PUYA MCU to pyOCD is trivial. Using OpenOCD, J-Link,
|
||||
or anything other than the DAPLink compatible debugger with pyOCD
|
||||
is untested and left as an exercise to the programmer.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
Original idea by rCON. Idea tweaked and implemented by true.
|
||||
|
||||
Art modified by rCON and further modified by true.
|
||||
|
||||
Schematic, board layout, code, and assembly by true.
|
|
@ -14,11 +14,11 @@
|
|||
|
||||
|
||||
#define ADC_CHANNELS 5
|
||||
#define ADC_HISTLEN 16
|
||||
#define ADC_HISTLEN 32
|
||||
|
||||
|
||||
|
||||
extern uint16_t adc_avg[ADC_CHANNELS];
|
||||
extern uint32_t adc_avg[ADC_CHANNELS];
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -16,9 +16,39 @@
|
|||
#define FLASH_HSI_24MHz (uint32_t *)0x1FFF0F6C
|
||||
|
||||
|
||||
#define UCONF_FLASH_SIZE 512
|
||||
#define UCONF_FLASH_START (FLASH_END + 1) - UCONF_FLASH_SIZE
|
||||
|
||||
#define UCONF_SIZE 64 // size in bytes
|
||||
#define UCONF_COUNT (UCONF_FLASH_SIZE / UCONF_SIZE)
|
||||
|
||||
#define UCONF_KEY 0x3713
|
||||
|
||||
|
||||
|
||||
typedef struct UConf {
|
||||
uint16_t conf_key; // should read 0x3713
|
||||
uint8_t cont_buzzer; // continuity buzzer on/off
|
||||
uint8_t cont_sensitivity; // continuity sensitivity high/low
|
||||
uint16_t cont_shorted; // ADC counts when probes shorted in continuity mode
|
||||
uint16_t diode_shorted; // unused, unneeded
|
||||
uint32_t rsvd_1[4];
|
||||
uint32_t rsvd_2[8];
|
||||
uint32_t update_cntr; // increment every update of config
|
||||
uint32_t crc32; // CRC of preceding 60 bytes
|
||||
} UConf; // 64 bytes
|
||||
|
||||
|
||||
|
||||
extern struct UConf conf;
|
||||
|
||||
|
||||
|
||||
void flash_init();
|
||||
|
||||
void flash_load_conf();
|
||||
void flash_commit_conf();
|
||||
|
||||
|
||||
|
||||
#endif /* _INC_FLASH_H */
|
|
@ -8,9 +8,14 @@
|
|||
#define _INC_RGBPROG_H
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
|
||||
void rgbprog_run();
|
||||
|
||||
void rgbprog_error_flasher(uint8_t scale, uint8_t r, uint8_t g, uint8_t b);
|
||||
|
||||
|
||||
|
||||
#endif /* _INC_RGBPROG_H */
|
|
@ -32,6 +32,11 @@ void userio_parse();
|
|||
uint8_t userio_get_mode();
|
||||
int16_t userio_get_btn();
|
||||
|
||||
uint8_t userio_get_set1_limit();
|
||||
|
||||
int16_t userio_get_btn_held();
|
||||
void userio_set_btn_override();
|
||||
|
||||
|
||||
|
||||
#endif /* _INC_USERIO_H */
|
|
@ -15,6 +15,7 @@ platform = nxplpc ; not actually this and we are not using
|
|||
board = generic_py32f030x6
|
||||
board_build.ldscript = py32f030x6.ld
|
||||
|
||||
|
||||
debug_tool = custom ; built-in pyocd support does not allow config file override
|
||||
debug_server = $PYTHONEXE
|
||||
${platformio.packages_dir}/tool-pyocd/pyocd-gdbserver.py
|
||||
|
@ -22,7 +23,6 @@ debug_server = $PYTHONEXE
|
|||
$PROJECT_DIR/pyocd.yaml
|
||||
-t
|
||||
PY32F030x6
|
||||
#debug_port = localhost:3333
|
||||
debug_extra_cmds =
|
||||
set mem inaccessible-by-default off
|
||||
|
||||
|
@ -56,14 +56,14 @@ debug_build_flags =
|
|||
|
||||
[env:sc7-testobot_REV1-py32f-dbg_pyocd]
|
||||
build_type = debug
|
||||
|
||||
board_upload.maximum_size = 32256 ; 512 bytes for config
|
||||
debug_build_flags = ${common.debug_build_flags}
|
||||
-DTESTO_REV1
|
||||
|
||||
|
||||
[env:sc7-testobot_REV2-py32f-dbg_pyocd]
|
||||
build_type = debug
|
||||
|
||||
board_upload.maximum_size = 32256 ; 512 bytes for config
|
||||
debug_build_flags = ${common.debug_build_flags}
|
||||
-DTESTO_REV2
|
||||
|
||||
|
@ -71,16 +71,16 @@ debug_build_flags = ${common.debug_build_flags}
|
|||
[env:sc7-testobot_REV1-py32f-rel_bl]
|
||||
build_type = release
|
||||
board_build.ldscript = py32f030x6_bl.ld
|
||||
board_upload.maximum_size = 30720 ; 2K reserved for bootloader
|
||||
|
||||
board_upload.maximum_size = 30208 ; 2K reserved for bootloader, 512 bytes for config
|
||||
build_flags = ${common.build_flags}
|
||||
-DBOOTLOADED
|
||||
-DTESTO_REV1
|
||||
|
||||
|
||||
[env:sc7-testobot_REV2-py32f-rel_bl]
|
||||
build_type = release
|
||||
board_build.ldscript = py32f030x6_bl.ld
|
||||
board_upload.maximum_size = 30720 ; 2K reserved for bootloader
|
||||
|
||||
board_upload.maximum_size = 30208 ; 2K reserved for bootloader, 512 bytes for config
|
||||
build_flags = ${common.build_flags}
|
||||
-DBOOTLOADED
|
||||
-DTESTO_REV2
|
|
@ -30,7 +30,7 @@ _Min_Stack_Size = 0x100; /* required amount of stack: 256 bytes */
|
|||
MEMORY
|
||||
{
|
||||
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
|
||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K
|
||||
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K - 512
|
||||
}
|
||||
|
||||
/* Define output sections */
|
||||
|
|
|
@ -30,7 +30,7 @@ _Min_Stack_Size = 0x100; /* required amount of stack: 256 bytes */
|
|||
MEMORY
|
||||
{
|
||||
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
|
||||
FLASH (rx) : ORIGIN = 0x08002000, LENGTH = 30K
|
||||
FLASH (rx) : ORIGIN = 0x08000800, LENGTH = 32K - 2K - 512
|
||||
}
|
||||
|
||||
/* Define output sections */
|
||||
|
|
18
src/adc.c
18
src/adc.c
|
@ -9,12 +9,12 @@
|
|||
* voltage - only the analog supply can be the reference.
|
||||
*
|
||||
* because most inputs are high impedance, which is not ideal,
|
||||
* sampling time is increased. this results in a final ADC sample rate
|
||||
* of about 33KHz, still well beyond what is needed in this application.
|
||||
* this value may change in the future.
|
||||
* sampling time is increased. this value may change in the future.
|
||||
*
|
||||
* note: attempted to use DMA for this and scanning all channels
|
||||
* at once, but that didn't work. would not reliably read channels.
|
||||
* the example does 32bit transfers and I was doing 16bit;
|
||||
* maybe there's some issue with that.
|
||||
* tried many different variations and it just wasn't working.
|
||||
* because of this I moved to using interrupt and the built-in
|
||||
* wait mode just in case things went slow.
|
||||
|
@ -30,7 +30,7 @@
|
|||
|
||||
|
||||
|
||||
#define SAMPLE_TIME LL_ADC_SAMPLINGTIME_239CYCLES_5
|
||||
#define SAMPLE_TIME LL_ADC_SAMPLINGTIME_41CYCLES_5
|
||||
|
||||
#define ADC_SEQ_RDY 0xff
|
||||
#define ADC_SEQ_STARTUP 0xfe
|
||||
|
@ -44,7 +44,7 @@
|
|||
static uint16_t adc_read[ADC_CHANNELS];
|
||||
static uint16_t adc_hist[ADC_CHANNELS][ADC_HISTLEN];
|
||||
|
||||
uint16_t adc_avg[ADC_CHANNELS];
|
||||
uint32_t adc_avg[ADC_CHANNELS];
|
||||
|
||||
static uint8_t adc_seq = 0;
|
||||
static uint8_t adc_idx = ADC_HISTLEN + 1;
|
||||
|
@ -70,6 +70,9 @@ void adc_init()
|
|||
// with wait mode (anti-overrun) active
|
||||
ADC1->CFGR1 = ADC_CFGR1_OVRMOD | ADC_CFGR1_WAIT;
|
||||
|
||||
// default clock
|
||||
ADC1->CFGR2 = 0;
|
||||
|
||||
// configure scan channels, sampling time (11 = temp, 12 = vrefint)
|
||||
ADC1->SMPR = SAMPLE_TIME;
|
||||
ADC1->CHSELR = CONF_SET1_AN | PROBE_AN | // note: SET1 and VREFEXT are
|
||||
|
@ -160,6 +163,9 @@ uint8_t adc_next()
|
|||
// wait for calibration to complete, if started
|
||||
while (ADC1->CR & ADC_CR_ADCAL) {};
|
||||
|
||||
// set ADC clock to 4MHz
|
||||
ADC1->CFGR2 = LL_ADC_CLOCK_ASYNC_HSI_DIV2;
|
||||
|
||||
// do our first round of conversions
|
||||
adc_go();
|
||||
|
||||
|
@ -185,7 +191,7 @@ uint8_t adc_next()
|
|||
for (j = 0; j < ADC_HISTLEN; j++) {
|
||||
adc_avg[i] += adc_hist[i][j];
|
||||
}
|
||||
adc_avg[i] >>= 4;
|
||||
adc_avg[i] /= ADC_HISTLEN;
|
||||
}
|
||||
|
||||
// check vref to determine if we need to recalibrate
|
||||
|
|
247
src/flash.c
247
src/flash.c
|
@ -15,6 +15,11 @@
|
|||
#include "flash.h"
|
||||
|
||||
|
||||
|
||||
struct UConf conf = {0};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* loads flash timing values.
|
||||
* ensure flash is unlocked before calling this function,
|
||||
|
@ -34,6 +39,7 @@ void flash_set_timing(uint32_t *timing_table)
|
|||
// the values listed in the reference manual. this is true of
|
||||
// all versions I have access to - English v1.0 and Chinese v1.2.
|
||||
// for example, @8MHz, RM says 0x5dc0 for PERTPE but ROM holds 0x6b60
|
||||
// in testing, either ROM or RM value seems to work.
|
||||
|
||||
// note: the datasheet doesn't say it anywhere, but if flash is not unlocked,
|
||||
// then flash timing values aren't writeable
|
||||
|
@ -84,8 +90,8 @@ __attribute__ ((long_call, section(".ramfunc"))) void flash_init()
|
|||
|
||||
// enable EOPIE (per DS, EOP isn't set unless EOPIE is set)
|
||||
// and signal that we want to write option bits,
|
||||
// then trigger the write by writing to a random address shown in the DS
|
||||
FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_OPTSTRT;
|
||||
// then trigger the write by writing to a random address shown in the DS
|
||||
*((__IO uint32_t *)(0x40022080)) = 0x55aa55aa;
|
||||
|
||||
// wait for BSY to go low, then EOP to go high
|
||||
|
@ -103,3 +109,242 @@ __attribute__ ((long_call, section(".ramfunc"))) void flash_init()
|
|||
// re-lock flash when done
|
||||
FLASH->CR = FLASH_CR_LOCK;
|
||||
}
|
||||
|
||||
/*
|
||||
* loads latest configuration from flash to conf variable in RAM.
|
||||
*/
|
||||
void flash_load_conf()
|
||||
{
|
||||
int8_t i, j;
|
||||
uint8_t loaded = 0;
|
||||
|
||||
struct UConf *fconf;
|
||||
uint32_t *f32;
|
||||
uint32_t *u32;
|
||||
|
||||
for (i = (UCONF_COUNT - 1); i >= 0; i--) {
|
||||
fconf = (struct UConf *)(UCONF_FLASH_START + (UCONF_SIZE * i));
|
||||
f32 = (uint32_t *)fconf;
|
||||
|
||||
// empty flash = nothing here
|
||||
if (fconf->crc32 == 0xffffffff) continue;
|
||||
|
||||
// invalid header = skip
|
||||
if (fconf->conf_key != UCONF_KEY) continue;
|
||||
|
||||
// calculate crc32
|
||||
CRC->CR = CRC_CR_RESET;
|
||||
while (CRC->CR & CRC_CR_RESET);
|
||||
|
||||
for (j = 0; j < ((UCONF_SIZE / 4) - 1); j++) {
|
||||
CRC->DR = f32[j];
|
||||
}
|
||||
|
||||
// check crc32
|
||||
if (CRC->DR == f32[(UCONF_SIZE / 4) - 1]) {
|
||||
// it passes. copy to RAM and break out
|
||||
u32 = (uint32_t *)&conf;
|
||||
for (j = 0; j < (UCONF_SIZE / 4); j++) {
|
||||
*u32++ = *f32++;
|
||||
}
|
||||
loaded = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
// default config of zeroes works
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* erases a flash page at the specified address.
|
||||
*/
|
||||
__attribute__ ((long_call, section(".ramfunc"))) void flash_erase_page(uint32_t addr)
|
||||
{
|
||||
uint32_t primask;
|
||||
|
||||
// ensure flash is not busy
|
||||
while (FLASH->SR & FLASH_SR_BSY);
|
||||
|
||||
// unlock flash
|
||||
FLASH->KEYR = FLASH_KEY1;
|
||||
FLASH->KEYR = FLASH_KEY2;
|
||||
while (FLASH->CR & FLASH_CR_LOCK); // still locked? then stall forever
|
||||
|
||||
// enable EOPIE (per DS, EOP isn't set unless EOPIE is set)
|
||||
// and signal that we want to erase a page
|
||||
FLASH->SR = FLASH_SR_EOP;
|
||||
FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_PER;
|
||||
|
||||
// disable interrupts
|
||||
primask = __get_PRIMASK();
|
||||
__disable_irq();
|
||||
|
||||
// trigger the erase by writing to a random address shown in the DS
|
||||
*((__IO uint32_t *)(addr)) = 0x55aa55aa;
|
||||
|
||||
// ensure flash is not busy, then wait for end of programming
|
||||
while (FLASH->SR & FLASH_SR_BSY);
|
||||
while (!(FLASH->SR & FLASH_SR_EOP));
|
||||
FLASH->SR |= FLASH_SR_EOP;
|
||||
|
||||
// re-lock flash
|
||||
FLASH->CR = FLASH_CR_LOCK;
|
||||
|
||||
// re-enable interrupts
|
||||
__set_PRIMASK(primask);
|
||||
}
|
||||
|
||||
/*
|
||||
* erases a flash page at the specified address.
|
||||
* data must be a pointer to a full page and
|
||||
* must contain 128 bytes (32 words) of data to write.
|
||||
*/
|
||||
__attribute__ ((long_call, section(".ramfunc"))) void flash_write_page(uint32_t addr, uint32_t *data)
|
||||
{
|
||||
uint32_t primask;
|
||||
|
||||
uint8_t i;
|
||||
|
||||
uint32_t *src;
|
||||
uint32_t *dst;
|
||||
|
||||
// ensure flash is not busy
|
||||
while (FLASH->SR & FLASH_SR_BSY);
|
||||
|
||||
// unlock flash
|
||||
FLASH->KEYR = FLASH_KEY1;
|
||||
FLASH->KEYR = FLASH_KEY2;
|
||||
while (FLASH->CR & FLASH_CR_LOCK); // still locked? then stall forever
|
||||
|
||||
// enable EOPIE (per DS, EOP isn't set unless EOPIE is set)
|
||||
// and signal that we want to write a page
|
||||
FLASH->SR = FLASH_SR_EOP;
|
||||
FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_PG;
|
||||
|
||||
// disable interrupts
|
||||
primask = __get_PRIMASK();
|
||||
__disable_irq();
|
||||
|
||||
// write data to flash
|
||||
src = data;
|
||||
dst = (uint32_t *)addr;
|
||||
for (i = 0; i < (FLASH_PAGE_SIZE / 4) - 1; i++) {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
|
||||
// for the last word, enable the PGSTRT bit then write the word to commit to flash
|
||||
FLASH->CR |= FLASH_CR_PGSTRT;
|
||||
*dst = *src;
|
||||
|
||||
// ensure flash is not busy, then wait for end of programming
|
||||
while (FLASH->SR & FLASH_SR_BSY);
|
||||
while (!(FLASH->SR & FLASH_SR_EOP));
|
||||
FLASH->SR |= FLASH_SR_EOP;
|
||||
|
||||
// re-lock flash
|
||||
FLASH->CR = FLASH_CR_LOCK;
|
||||
|
||||
// re-enable interrupts
|
||||
__set_PRIMASK(primask);
|
||||
}
|
||||
|
||||
/*
|
||||
* stores configuration from conf variable in RAM to flash.
|
||||
* performs very basic wear leveling.
|
||||
*/
|
||||
void flash_commit_conf()
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
uint8_t flash_idx = 0;
|
||||
uint8_t flash_full = 0;
|
||||
|
||||
struct UConf *fconf;
|
||||
uint32_t *f32, *u32;
|
||||
uint32_t write[(FLASH_PAGE_SIZE / 4)];
|
||||
|
||||
// find first free page index
|
||||
for (i = 0; i < UCONF_COUNT; i++) {
|
||||
fconf = (struct UConf *)(UCONF_FLASH_START + (UCONF_SIZE * i));
|
||||
if ((fconf->crc32 == 0xffffffff) || (fconf->conf_key != UCONF_KEY)) {
|
||||
flash_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if all pages are occupied, we'll erase all the pages
|
||||
if (i == UCONF_COUNT) {
|
||||
flash_full = 1;
|
||||
}
|
||||
|
||||
// ensure live config header is correct, and calculate CRC
|
||||
conf.conf_key = UCONF_KEY;
|
||||
conf.update_cntr++;
|
||||
u32 = (uint32_t *)&conf;
|
||||
|
||||
CRC->CR = CRC_CR_RESET;
|
||||
while (CRC->CR & CRC_CR_RESET);
|
||||
|
||||
for (i = 0; i < (UCONF_SIZE / 4) - 1; i++) {
|
||||
CRC->DR = u32[i];
|
||||
}
|
||||
u32[(UCONF_SIZE / 4) - 1] = CRC->DR;
|
||||
|
||||
// do we need to actually update data?
|
||||
// check checksum of our data against checksum of prior entry
|
||||
if (flash_idx) {
|
||||
fconf = (struct UConf *)(UCONF_FLASH_START + (UCONF_SIZE * (flash_idx - 1)));
|
||||
if (fconf->crc32 == conf.crc32) {
|
||||
// nothing to update; we can break out
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// set empty write buffer to 0xff
|
||||
for (i = 0; i < 32; i++) {
|
||||
write[i] = 0xffffffff;
|
||||
}
|
||||
|
||||
// set write buffer
|
||||
u32 = (uint32_t *)write;
|
||||
|
||||
// if this is the second entry in a flash page,
|
||||
// copy first half of flash page from ROM to the write buffer,
|
||||
// as our config size (64 bytes) is smaller than the page write size (128 bytes)
|
||||
if (flash_idx & 1) {
|
||||
f32 = (uint32_t *)fconf;
|
||||
for (i = 0; i < (UCONF_SIZE / 4); i++) {
|
||||
*u32++ = *f32++;
|
||||
}
|
||||
}
|
||||
|
||||
// correct flash_idx if it is out of bounds
|
||||
// (we will usually have 1-8, only getting 0 on the very first write)
|
||||
if (flash_idx == UCONF_COUNT) {
|
||||
flash_idx = 0;
|
||||
}
|
||||
|
||||
// copy user config from RAM to write buffer at the appropriate offset
|
||||
f32 = (uint32_t *)&conf;
|
||||
for (i = 0; i < (UCONF_SIZE / 4); i++) {
|
||||
*u32++ = *f32++;
|
||||
}
|
||||
|
||||
// we now need the flash page boundary, so remove lowest bit from flash_idx
|
||||
flash_idx &= ~1;
|
||||
|
||||
// erase this page
|
||||
flash_erase_page(UCONF_FLASH_START + (UCONF_SIZE * flash_idx));
|
||||
|
||||
// commit to flash
|
||||
flash_write_page(UCONF_FLASH_START + (UCONF_SIZE * flash_idx), write);
|
||||
|
||||
// if flash was full, erase other pages
|
||||
if (flash_full) {
|
||||
for (i = (UCONF_FLASH_START + FLASH_PAGE_SIZE); i < FLASH_END; i += FLASH_PAGE_SIZE) {
|
||||
flash_erase_page(i);
|
||||
}
|
||||
}
|
||||
}
|
20
src/main.c
20
src/main.c
|
@ -3,6 +3,8 @@
|
|||
*
|
||||
* your robot addon badge buddy for testing all the things
|
||||
*
|
||||
* version 0.0.1
|
||||
*
|
||||
* file creation: 20231015 0021
|
||||
*/
|
||||
|
||||
|
@ -104,6 +106,9 @@ int main()
|
|||
flash_init(); // also configures option bytes to enable PF2, if necessary
|
||||
gpio_init();
|
||||
|
||||
// load configuration from flash
|
||||
flash_load_conf();
|
||||
|
||||
// peripheral initialization
|
||||
led_init();
|
||||
adc_init();
|
||||
|
@ -116,7 +121,7 @@ int main()
|
|||
|
||||
while (1) {
|
||||
// run LED programs out of interrupt context at 256Hz
|
||||
if ((ctr & 0xf) == 0) {
|
||||
if (!(ctr & 0xf)) {
|
||||
if (userio_get_mode() == MODE_FUN) {
|
||||
rgbprog_run();
|
||||
}
|
||||
|
@ -130,6 +135,8 @@ int main()
|
|||
/*
|
||||
* main application interrupt
|
||||
*/
|
||||
uint16_t adc_ctr = 0; // count ADC cycles
|
||||
volatile uint16_t adc_sec; // ADC cycles per second (used w/debugger)
|
||||
|
||||
void SysTick_Handler(void)
|
||||
{
|
||||
|
@ -144,16 +151,21 @@ void SysTick_Handler(void)
|
|||
ctr &= 0xfff;
|
||||
if (!ctr) {
|
||||
uptime++;
|
||||
adc_sec = adc_ctr;
|
||||
adc_ctr = 0;
|
||||
}
|
||||
|
||||
// run main logic at 1024Hz
|
||||
if (!(ctr & 0x3)) {
|
||||
// run main logic at 2048Hz
|
||||
if (!(ctr & 0x1)) {
|
||||
// shifted counter for use in the program
|
||||
cs = ctr >> 2;
|
||||
cs = ctr >> 1;
|
||||
|
||||
// adc tested to result in about 61 reads/second
|
||||
if (!adc_next()) {
|
||||
// adc has new computed results
|
||||
adc_ctr++;
|
||||
|
||||
// start using ADC results only after the first cycle
|
||||
if (uptime || cs) {
|
||||
// figure out knobs, buttons, switches
|
||||
userio_parse();
|
||||
|
|
194
src/probe.c
194
src/probe.c
|
@ -4,6 +4,9 @@
|
|||
* this file is responsible for figuring out the
|
||||
* continuity and diode tests from the raw analog data.
|
||||
*
|
||||
* todo: confirm mathematically that continuity values
|
||||
* match the measured values
|
||||
*
|
||||
* file creation: 20231016 0255
|
||||
*/
|
||||
|
||||
|
@ -13,77 +16,157 @@
|
|||
|
||||
#include "adc.h"
|
||||
#include "led.h"
|
||||
#include "rgbprog.h"
|
||||
#include "userio.h"
|
||||
|
||||
|
||||
|
||||
#define CONT_OPEN 1240
|
||||
#include "flash.h"
|
||||
|
||||
|
||||
|
||||
// diode measurements at 3V3
|
||||
#define DIODE_OPEN 4000 // test unit measures ~4060
|
||||
#define DIODE_STANDARD 2330 // fairchild 5V2 zener, forward biased
|
||||
#define DIODE_OPEN 3860 // test unit measures ~4060 no D1, ~3960 with D1
|
||||
#define DIODE_SCHOTTKY 1960 // vishay 40V 1A SB140
|
||||
#define DIODE_STANDARD 2330 // fairchild 5V2 zener, forward biased
|
||||
#define DIODE_LED_BLUE 3190
|
||||
#define DIODE_LED_GRN 3040 // very dim
|
||||
#define DIODE_LED_RED 3020
|
||||
#define DIODE_SHORT 1900 // test unit measures ~1820
|
||||
|
||||
#define SHORT_THRESHOLD 2
|
||||
|
||||
// continuity offsets from calibrated short value
|
||||
#define CONT_47OHM 16
|
||||
#define CONT_100OHM 34
|
||||
|
||||
#define CONT_EXTRA_COUNTS 3 // extra raw counts as a fudge factor
|
||||
|
||||
#define CONT_CAL_THRESHOLD 890 // typical short is ~845-860 region
|
||||
|
||||
#define CONT_LATCH_THRESHOLD 5 // extra raw counts for latching indicator
|
||||
#define CONT_LATCH_TIMEOUT 4 // probe measurement passes to hold latched indicator
|
||||
|
||||
|
||||
|
||||
static uint16_t vref;
|
||||
|
||||
static uint8_t cont_cal_timer;
|
||||
static uint16_t cont_cal_low;
|
||||
static uint16_t cont_cal_avg;
|
||||
static const uint16_t cont_thresh_tbl[] = {
|
||||
CONT_100OHM, CONT_47OHM
|
||||
};
|
||||
|
||||
static uint8_t latch = 0;
|
||||
static uint8_t buzzer = 0;
|
||||
|
||||
|
||||
|
||||
static inline void probe_measure_cont()
|
||||
{
|
||||
uint16_t probe, v_ext;
|
||||
uint32_t x;
|
||||
uint32_t x, c;
|
||||
uint8_t g, b;
|
||||
|
||||
probe = adc_avg[ADC_PROBE];
|
||||
v_ext = adc_avg[ADC_VREF_EXT];
|
||||
|
||||
// if the button has been pushed, toggle the buzzer
|
||||
if (userio_get_btn() > 0) {
|
||||
buzzer ^= 1;
|
||||
}
|
||||
|
||||
// this LED will not set anything,
|
||||
// but zero it out anyway
|
||||
led_setrgb(1, 0, 0, 0);
|
||||
|
||||
if (vref) {
|
||||
x = probe << 12; // maximum possible level
|
||||
x /= vref; // normalize to 4096max
|
||||
if (x > 4095) x = 4095;
|
||||
|
||||
if (x < 2405) { // roughly 100ohm or lower
|
||||
latch = 4; // latch any continuity for a while
|
||||
}
|
||||
|
||||
if (latch) {
|
||||
// indicate continuity
|
||||
if (x >= 2420) latch--; // hysteresis
|
||||
|
||||
led_setrgb(0, 0, 250, ((buzzer) ? 100 : 0));
|
||||
led_buzz(0);
|
||||
if (buzzer) led_buzz(1);
|
||||
} else {
|
||||
// idle
|
||||
led_setrgb(0, 120, 0, ((buzzer) ? 8 : 0));
|
||||
led_buzz(0);
|
||||
}
|
||||
}
|
||||
|
||||
// update vref
|
||||
if (!vref) vref = v_ext;
|
||||
vref += v_ext;
|
||||
vref >>= 1;
|
||||
|
||||
// if the button has been held, change the threshold
|
||||
if (userio_get_btn_held() == 60) {
|
||||
userio_set_btn_override();
|
||||
conf.cont_sensitivity ^= 1;
|
||||
}
|
||||
|
||||
// if the button has _really_ been held, clear the calibration
|
||||
if (userio_get_btn_held() == 600) {
|
||||
conf.cont_shorted = 0;
|
||||
}
|
||||
|
||||
// if there is no calibration, try to perform it
|
||||
if (!conf.cont_shorted) {
|
||||
// until this is performed, show error LEDs
|
||||
rgbprog_error_flasher(5, 0, 0, 100);
|
||||
|
||||
// initialize
|
||||
if (!cont_cal_timer) {
|
||||
cont_cal_low = 0xfff;
|
||||
cont_cal_avg = 0;
|
||||
}
|
||||
|
||||
// if probe appears to be shorted, calibrate to lowest value read
|
||||
if (adc_avg[ADC_PROBE] < CONT_CAL_THRESHOLD) {
|
||||
// low value
|
||||
if (adc_avg[ADC_PROBE] < cont_cal_low) {
|
||||
cont_cal_low = adc_avg[ADC_PROBE];
|
||||
}
|
||||
// average value
|
||||
if (!cont_cal_avg) {
|
||||
cont_cal_avg = adc_avg[ADC_PROBE];
|
||||
} else {
|
||||
cont_cal_avg += adc_avg[ADC_PROBE];
|
||||
cont_cal_avg >>= 1;
|
||||
}
|
||||
cont_cal_timer++;
|
||||
} else {
|
||||
cont_cal_timer = 0;
|
||||
}
|
||||
|
||||
// after held long enough, commit
|
||||
if (cont_cal_timer == 180) {
|
||||
cont_cal_timer = 0;
|
||||
conf.cont_shorted = (cont_cal_avg + cont_cal_low) >> 1;
|
||||
flash_commit_conf();
|
||||
}
|
||||
}
|
||||
|
||||
// right LED remains unused while buzzer is being used,
|
||||
// but zero it out anyway
|
||||
led_setrgb(1, 0, 0, 0);
|
||||
|
||||
// if no calibration yet, bail here
|
||||
if (!conf.cont_shorted) return;
|
||||
|
||||
// if the button has been pushed, toggle buzzer on/off
|
||||
if (userio_get_btn() > 0) {
|
||||
conf.cont_buzzer ^= 1;
|
||||
}
|
||||
|
||||
if (vref) {
|
||||
// note: we don't use VREF anymore...
|
||||
// it's too noisy :/
|
||||
|
||||
// probe level
|
||||
x = probe - conf.cont_shorted;
|
||||
if (x > 0xfff) x = 0;
|
||||
|
||||
// threshold level
|
||||
c = cont_thresh_tbl[conf.cont_sensitivity];
|
||||
|
||||
// are we measuring a lower ohm value on the probes than our threshold?
|
||||
if (x < (c + CONT_EXTRA_COUNTS)) { // roughly 100ohm or lower
|
||||
latch = CONT_LATCH_TIMEOUT; // latch any continuity for a while
|
||||
}
|
||||
|
||||
// blue led to indicate buzzer is enabled
|
||||
b = conf.cont_buzzer ? 20 : 0;
|
||||
|
||||
if (latch) {
|
||||
// hysteresis
|
||||
if (x >= (c + CONT_LATCH_THRESHOLD)) latch--;
|
||||
|
||||
// indicate continuity
|
||||
led_setrgb(0, 0, 250, b);
|
||||
led_buzz(0);
|
||||
if (conf.cont_buzzer) led_buzz(1);
|
||||
} else {
|
||||
// idle
|
||||
g = conf.cont_sensitivity ? 40 : 0;
|
||||
led_setrgb(0, 120, g, b);
|
||||
led_buzz(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void probe_measure_diode()
|
||||
|
@ -97,23 +180,23 @@ static inline void probe_measure_diode()
|
|||
if (p < DIODE_SHORT) {
|
||||
// show off on short
|
||||
led_setrgb(1, 0, 0, 0);
|
||||
}
|
||||
|
||||
} else
|
||||
if (p >= DIODE_OPEN) {
|
||||
// show red on open
|
||||
led_setrgb(1, 120, 0, 0);
|
||||
}
|
||||
|
||||
} else
|
||||
if (p >= 1940 && p < 2650) {
|
||||
// show green on regular or schottky diodes
|
||||
led_setrgb(1, 0, 200, 0);
|
||||
}
|
||||
if (p >= 2650 && p < 3800) {
|
||||
} else
|
||||
if (p >= 2650 && p < 3700) {
|
||||
// show blue on LEDs
|
||||
led_setrgb(1, 0, 0, 500);
|
||||
} else
|
||||
if (p >= 3701) {
|
||||
// show red+blue if between open and LED
|
||||
led_setrgb(1, 120, 0, 120);
|
||||
}
|
||||
|
||||
// if we haven't set a value, flash red to indicate error state
|
||||
}
|
||||
|
||||
void probe_measure()
|
||||
|
@ -124,7 +207,7 @@ void probe_measure()
|
|||
|
||||
switch (mode) {
|
||||
case MODE_CONT: probe_measure_cont(); break;
|
||||
case MODE_FUN: break; // todo: run RGBLED program
|
||||
case MODE_FUN: break; // LED program is run elsewhere
|
||||
case MODE_DIODE: probe_measure_diode(); break;
|
||||
}
|
||||
}
|
||||
|
@ -135,13 +218,22 @@ void probe_measure()
|
|||
*/
|
||||
void probe_mode_switch(uint8_t mode)
|
||||
{
|
||||
if (mode == MODE_CONT) {
|
||||
switch (mode) {
|
||||
case MODE_CONT: {
|
||||
// set LED to buzzer mode, set PROBESEL low
|
||||
led_mode_buzzer();
|
||||
PROBESEL_PORT->BRR = (1 << PROBESEL_PIN);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// set LED to dual RGB mode, set PROBESEL high
|
||||
led_mode_rgb();
|
||||
PROBESEL_PORT->BSRR = (1 << PROBESEL_PIN);
|
||||
}
|
||||
}
|
||||
|
||||
// save config if moving into LED mode
|
||||
if (mode == MODE_FUN) {
|
||||
flash_commit_conf();
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@
|
|||
*/
|
||||
|
||||
|
||||
#include "rgbprog.h"
|
||||
|
||||
#include "led.h"
|
||||
#include "hsv2rgb.h"
|
||||
#include "rand.h"
|
||||
|
@ -112,6 +114,13 @@ void rgbprog_run()
|
|||
brite &= 0x3;
|
||||
}
|
||||
|
||||
// if SET1 is out of range, indicate that instead of
|
||||
// running the normal program
|
||||
if (userio_get_set1_limit()) {
|
||||
rgbprog_error_flasher(0, 120, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// which program to run?
|
||||
j = 0;
|
||||
for (i = 32; i <= 256; i += 32) {
|
||||
|
@ -390,3 +399,26 @@ void rgbprog_prog7(uint8_t k0)
|
|||
rgb_setled(0, &rgb[0]);
|
||||
rgb_setled(1, &rgb[0]);
|
||||
}
|
||||
|
||||
/*
|
||||
* internal program:
|
||||
*
|
||||
* flashes LEDs, indicating a SET1 knob error.
|
||||
*/
|
||||
static uint8_t err_ctr = 0;
|
||||
static uint8_t err_tog = 0;
|
||||
void rgbprog_error_flasher(uint8_t scale, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
uint8_t x;
|
||||
|
||||
x = (1 << scale);
|
||||
err_ctr += x;
|
||||
err_ctr &= ~(x - 1);
|
||||
|
||||
if (!err_ctr) {
|
||||
err_tog ^= 1;
|
||||
}
|
||||
|
||||
led_setrgb(err_tog, r, g, b);
|
||||
led_setrgb(err_tog ^ 1, 0, 0, 0);
|
||||
}
|
49
src/userio.c
49
src/userio.c
|
@ -41,8 +41,13 @@
|
|||
#define MODE_ANALOG_MIN 20
|
||||
|
||||
|
||||
#define BTN_OPEN 0
|
||||
#define BTN_PUSHED 1
|
||||
#define BTN_OVERRIDE 2
|
||||
|
||||
|
||||
#ifdef TESTO_REV1
|
||||
#define SET1_MAX 2047 // 3V net feeding into 1V24 via body diode above this
|
||||
#define SET1_MAX 2199 // 3V net feeding into 1V24 via body diode above this
|
||||
#else
|
||||
#define SET1_MAX 1539 // 1.24Vref
|
||||
#endif
|
||||
|
@ -76,9 +81,12 @@ void userio_parse()
|
|||
m = adc_avg[ADC_SET_MODE];
|
||||
if (m < MODE_ANALOG_MIN) {
|
||||
// button is pushed
|
||||
btn = 1;
|
||||
if (btn == BTN_OPEN) btn = 1;
|
||||
if (btn_held != 0xffff) btn_held++;
|
||||
} else if (btn == 1) {
|
||||
} else if (btn) {
|
||||
// release held count if overridden
|
||||
if (btn == BTN_OVERRIDE) btn_held = 0;
|
||||
|
||||
// button is released
|
||||
btn = 0;
|
||||
}
|
||||
|
@ -165,8 +173,8 @@ int16_t userio_get_btn()
|
|||
{
|
||||
int16_t ret;
|
||||
|
||||
if (btn) return -1;
|
||||
if (!btn) {
|
||||
if (btn == BTN_PUSHED) return -1;
|
||||
if (btn == BTN_OPEN) {
|
||||
if (btn_held) {
|
||||
ret = btn_held;
|
||||
btn_held = 0;
|
||||
|
@ -176,3 +184,34 @@ int16_t userio_get_btn()
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* returns the button state.
|
||||
* 0 = not held
|
||||
* >0 = held for X ticks
|
||||
*/
|
||||
int16_t userio_get_btn_held()
|
||||
{
|
||||
return btn_held;
|
||||
}
|
||||
|
||||
/*
|
||||
* overrides and disables the next button release event.
|
||||
*/
|
||||
void userio_set_btn_override()
|
||||
{
|
||||
btn = BTN_OVERRIDE;
|
||||
}
|
||||
|
||||
/*
|
||||
* returns true if the right knob is beyond maximum range.
|
||||
* always returns false on REV2 or later boards.
|
||||
*/
|
||||
uint8_t userio_get_set1_limit()
|
||||
{
|
||||
#ifdef TESTO_REV1
|
||||
if (adc_avg[ADC_SET1] > SET1_MAX) return 1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue