Compare commits

...

34 Commits

Author SHA1 Message Date
King Kévin ac4731928b Merge branch 'clock_jockey' of https://git.cuvoodoo.info/stm32f1 into clock_jockey 2020-01-12 16:00:32 +01:00
King Kévin e7b5ddcdef ld: limit to 64kB flash to be compatible with most MCU 2020-01-12 15:54:24 +01:00
King Kévin 2bda3be4a0 application: improve motor not spinning protection 2020-01-12 15:53:36 +01:00
King Kévin a5ba9f0aa5 application: fix relay control 2020-01-12 15:53:36 +01:00
King Kévin 3f8158e617 application: reduce default motor speed for safety 2020-01-12 15:53:36 +01:00
King Kévin 600264ce8c README: remove other boord details 2020-01-12 15:53:36 +01:00
King Kévin ee10d9aad3 README: document project 2020-01-12 15:53:36 +01:00
King Kévin ee615e65b1 application: new complete clock jockey application, untested 2020-01-12 15:53:36 +01:00
King Kévin 0ae3087049 USB: set project name as product string 2020-01-12 15:53:36 +01:00
King Kévin 2ba33d58c6 global: define RST as DFU force input 2020-01-12 15:53:36 +01:00
King Kévin 0e1345c18d Rakefile: sepcify board for project 2020-01-12 15:53:36 +01:00
King Kévin d88895ee0c Merge branch 'clock_jockey' of ssh://git.cuvoodoo.info/stm32f1 into clock_jockey 2020-01-10 14:19:02 +01:00
King Kévin 4519bfa2d3 application: improve motor not spinning protection 2020-01-10 14:16:18 +01:00
King Kévin 97c5faf5e5 application: fix relay control 2020-01-10 14:16:18 +01:00
King Kévin f10b43e732 application: reduce default motor speed for safety 2020-01-10 14:16:18 +01:00
King Kévin 7078e37bc5 README: remove other boord details 2020-01-10 14:16:18 +01:00
King Kévin f858253f78 README: document project 2020-01-10 14:16:18 +01:00
King Kévin 925c020c16 application: new complete clock jockey application, untested 2020-01-10 14:16:18 +01:00
King Kévin 079cba9e1a USB: set project name as product string 2020-01-10 14:16:18 +01:00
King Kévin e457f308e5 global: define RST as DFU force input 2020-01-10 14:16:18 +01:00
King Kévin 87db9a239e Rakefile: sepcify board for project 2020-01-10 14:14:34 +01:00
King Kévin 8096ae426b application: improve motor not spinning protection 2020-01-10 11:10:58 +01:00
King Kévin c9e2f9ea93 application: fix relay control 2020-01-10 11:05:47 +01:00
King Kévin 882ea71c4e application: reduce default motor speed for safety 2020-01-10 11:05:27 +01:00
King Kévin 363179e837 flash_internal: fix provided flash size detection 2020-01-10 11:04:20 +01:00
King Kévin ecac618d67 README: remove other boord details 2020-01-10 11:02:49 +01:00
King Kévin 666d1ed983 ld: don't provide flash size 2020-01-10 11:02:22 +01:00
King Kévin 3bf0871f39 ld: don't provide flash size 2020-01-10 11:01:53 +01:00
King Kévin d74fd3aa09 README: document project 2020-01-09 20:52:50 +01:00
King Kévin cdc6eb6896 application: new complete clock jockey application, untested 2020-01-09 20:52:13 +01:00
King Kévin 5810e72096 USB: set project name as product string 2020-01-09 20:51:21 +01:00
King Kévin 140f1126e6 global: define RST as DFU force input 2020-01-09 20:51:21 +01:00
King Kévin 781dd8f44d ld: limit to 64kB flash to be compatible with most MCU 2020-01-09 20:48:59 +01:00
King Kévin fdf800af4f Rakefile: sepcify board for project 2020-01-09 20:47:56 +01:00
6 changed files with 193 additions and 53 deletions

111
README.md
View File

@ -1,54 +1,97 @@
This firmware template is designed for development boards based around [STM32 F1 series micro-controller](http://www.st.com/web/en/catalog/mmc/FM141/SC1169/SS1031).
project
=======
summary
-------
*describe project purpose*
this clock jockey monitors the tachometer of a motor turning a clock, and regulates its speed by switching the power to it.
technology
----------
*described electronic details*
the goal was to make a round clock quadrant turn.
the quadrant is made out of wood, around 1 m heigh, and weights around 5 kg.
it is mounted on a disco ball base.
the bearing is strong enough to hold it and allow the clock to turn around its center.
originally the disco ball base included a motor, but it was too fragile to withhold the forces.
two plastic gears in the gear box broke.
this was the state of the clock when I joined the team.
I decided to fix the turning clock.
the small DC or stepper motor I tried were too weak to turn the clock.
I decided to use a universal motor salvaged from a washing machine.
the pinout of the motor was undocumented, but not hard to figure out.
two wires went to a small module at the end of the rotor shaft.
this is the tachometer to measure the rotation speed.
it is also easy to trace the two wires going to the brushes feeding the power to the rotor coils.
next, measure the resistance of the remaining pin pairs.
one will have 0 Ohms.
this is the fuse to protect against over-heating.
there will be two pairs of 0.6 Ohms, and one of 1.2 Ohms.
these are the two coils for the stator, with a center tap.
wire the motor with 220V AC the following way:
AC L - fuse - rotor - stator (2 coils, not center tap) - AC N
to limit the speed I used a 4000 W capable SCR.
the SCR was not enough to regulate the motor speed.
setting the potentiometer to a fixed point would results in the motor to stop turning after some time, of to speed up to fast.
the user would have to adjust the potentiometer using then knob periodically to have a somewhat constant speed.
to overcome this limitation I developed the clock jockey.
this device will monitor the speed of the motor using the tachometer, and switch the power to the motor using a Solid State Relay (SSR), so to reach and stay at the predefined speed.
the motor tachometer provides an AC signal.
basically it's a generating motor linked to the shaft of the actual motor.
the AC frequency (and voltage) indicates the speed of the motor.
the AC signal is rectified using a full bridge rectifier.
the rectified signal is input to an PC817 optocoupler (with ~ 200 Ohm inline resistor).
the full bridge rectifier protects to optocoupler, which has a reverse breakdown voltage of 6 V, lower than the seen -10 V peaks of the AC signal.
it also allows to get two pulses per AC period, for a more accurate speed measurement.
the optocoupler is directly connected to the clock jockey.
an omron G3MB-202P (5V) is also connected to the clock jockey to switch to power going to the motor.
the SCR is still in line to limit the maximum delivered power.
board
=====
The current implementation uses a [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board).
the device reuses an [ST-LINK/V2 clone](https://wiki.cuvoodoo.info/doku.php?id=jtag#mini_st-link_v2).
this board uses a STM32F103C8T6 micro-controller.
The underlying template also supports following board:
- [Maple Mini](http://leaflabs.com/docs/hardware/maple-mini.html), based on a STM32F103CBT6
- [System Board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#system_board), based on a STM32F103C8T6
- [blue pill](ihttps://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#blue_pill), based on a STM32F103C8T6
- [core board](https://wiki.cuvoodoo.info/doku.php?id=stm32f1xx#core_board), based on a STM32F103C8T6
- [ST-LINK V2 mini](https://wiki.cuvoodoo.info/doku.php?id=jtag#mini_st-link_v2), a ST-LINK/V2 clone based on a STM32F101C8T6
- [USB-Blaster](https://wiki.cuvoodoo.info/doku.php?id=jtag#armjishu_usb-blaster), an Altera USB-Blaster clone based on a STM32F101C8T6
**Which board is used is defined in the Makefile**.
This is required to map the user LED and button provided on the board
The ST-LINK V2 mini clone has SWD test points on the board.
Because read protection is enabled, you will first need to remove the protection to be able to flash the firmware.
To remove the read protection (and erase flash), run `rake remove_protection` while a SWD adapter is connected.
The Altera USB-Blaster clone has a pin header for SWD and UART1 on the board.
SWD is disabled in the main firmware, and it has read protection.
To be able to flash using SWD (or the serial port), the BOOT0 pin must be set to 1 to boot the system memory install of the flash memory.
To set BOOT0 to 1, apply 3.3 V on R11, between the resistor and the reference designator, when powering the device.
The red LED should stay off while the green LED is on.
Now you can remove the read protection (and erase flash), run `rake remove_protection` while a SWD adapter is connected.
since the device is firmware protected (against read-out), you will first need to remove this protection using an SWD adapter and running `rake remove_protection`.
you can then flash the bootloader using SWD and application using DFU a documented in the section below.
connections
===========
Connect the peripherals the following way (STM32F10X signal; STM32F10X pin; peripheral pin; peripheral signal; comment):
connect the peripherals the following way:
- *list board to preipheral pin connections*
- PC817X emitter, SWIM pin (PB11, pulled up to 3V3 on board)
- PC817X collector, GND
- G3MB-202P SSR 3+, 5V (5V required by SSR, with embedded resistor)
- G3MB-202P SSR 4-, SWDIO (SWCLK pin in not 5V tolerant)
All pins are configured using `define`s in the corresponding source code.
all pins are configured using `define`s in the corresponding source code.
usage
=====
the firmware will simply count the number of optocoupler falling edges (2 per revolution).
it will periodically compare this count to the target value.
if this is below, the SSR will be switched on.
if it is above, the SSR will be switched off.
to set this target, connect the clock jokey to a computer.
it will appear as a serial device (using the CDC ACM USB profile).
use a serial terminal program and connect to it (the baud rate does not matter).
enter `tacho xx`, with xx being the target tachometer count.
to view the set and current tachometer values, enter `tacho`.
the target tachometer count is somewhat relative.
it is proportional to the motor speed and tied to the periodic check (currently set to 0.1 s).
if the tacho count it at 0 for too many seconds after boot, the clock jockey will with off the power.
this is a safety feature since the tachometer is either not reading the speed, or the motor is stuck.
code
====
@ -86,7 +129,7 @@ The `Makefile` uses a Black Magic Probe (per default), or a ST-Link V2 along Ope
To flash the `booltoader` using SWD run `rake flash_booloader`.
Once the `bootloader` is flashed it is possible to flash the `application` over USB using the DFU protocol by running `rake flash`.
To force the bootloader to start the DFU mode press the user button or short a pin, depending on the board.
To force the bootloader to start the DFU mode, short the RST pin to the nearby ground pin.
It is also possible to flash the `application` image using SWD by running `rake flash_application`.
debug
@ -98,8 +141,4 @@ To start the debugging session run `rake debug`.
USB
---
The firmware offers serial communication over USART1 and USB (using the CDC ACM device class).
You can also reset the board by setting the serial width to 5 bits over USB.
To reset the board run `rake reset`.
This only works if provided USB CDC ACM is running correctly and the micro-controller isn't stuck.
The firmware offers serial communication over USB (using the CDC ACM device class).

View File

@ -15,7 +15,7 @@ FIRMWARES = [BOOTLOADER, APPLICATION]
# which development board is used
# supported are: SYSTEM_BOARD, MAPLE_MINI, BLUE_PILL, CORE_BOARD, STLINKV2, BLASTER, BUSVOODOO
BOARD = ENV["BOARD"] || "CORE_BOARD"
BOARD = ENV["BOARD"] || "STLINKV2"
# libopencm3 definitions
LIBOPENCM3_DIR = "libopencm3"

View File

@ -15,7 +15,7 @@
/** STM32F1 application example
* @file application.c
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2019
* @date 2016-2020
*/
/* standard libraries */
@ -37,6 +37,7 @@
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
#include <libopencm3/stm32/desig.h> // design utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/timer.h> // timer utilities
/* own libraries */
#include "global.h" // board definitions
@ -47,12 +48,16 @@
#include "usb_cdcacm.h" // USB CDC ACM utilities
#include "terminal.h" // handle the terminal interface
#include "menu.h" // menu utilities
#include "flash_internal.h" // menu utilities
#define WATCHDOG_PERIOD 10000 /**< watchdog period in ms */
#define WATCHDOG_PERIOD 3000 /**< watchdog period in ms */
/** set to 0 if the RTC is reset when the board is powered on, only indicates the uptime
* set to 1 if VBAT can keep the RTC running when the board is unpowered, indicating the date and time
*/
#define RTC_DATE_TIME 1
#define RTC_DATE_TIME 0
/** number of RTC ticks per second */
#define RTC_TICKS 10
/** RTC time when device is started */
static time_t time_start = 0;
@ -63,6 +68,13 @@ static time_t time_start = 0;
volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */
/** @} */
volatile uint32_t tacho_count = 0; /**< tachometer edge count */
bool tacho_display = false; /**< if the current tachometer count should be displayed */
#define TACHO_PIN PB11 /**< tachometer input on SWIM pin, pulled up to 3V3 by 680R */
#define TACHO_TARGET_DEFAULT 20 /**< the default target tachometer count (slow for safety) */
uint32_t tacho_target = TACHO_TARGET_DEFAULT; /**< the target tachometer count (switch SSR on when below, off when above) */
#define SSR_PIN PB14 /**< pin to control the SSR (SWDIO, active low, open drain to 5V) */
size_t putc(char c)
{
size_t length = 0; // number of characters printed
@ -117,6 +129,30 @@ static void command_reset(void* argument);
*/
static void command_bootloader(void* argument);
static void command_tacho(void* argument)
{
(void)argument; // we won't use the argument
if (argument) { // tachometer value has been provided
uint32_t target = *(uint32_t*)argument; // get target tachometer value
printf("setting target tachometer value to %u\n", target);
tacho_target = target;
// save to EEPROM
const uint32_t eeprom_tacho[2] = {tacho_target, tacho_target ^ 0xffffffff};
const int32_t rc = flash_internal_eeprom_write((uint8_t*)eeprom_tacho, sizeof(eeprom_tacho));
if (rc != sizeof(eeprom_tacho)) {
printf("could not save value to EEPROM: %d\n", rc);
}
} else {
if (tacho_display) {
tacho_display = false;
} else {
printf("target tachometer value set to %u\n", tacho_target);
tacho_display = true;
}
}
}
/** list of all supported commands */
static const struct menu_command_t menu_commands[] = {
{
@ -169,6 +205,14 @@ static const struct menu_command_t menu_commands[] = {
.argument_description = NULL,
.command_handler = &command_bootloader,
},
{
.shortcut = 't',
.name = "tacho",
.command_description = "set/show/hide tachometer target and current value",
.argument = MENU_ARGUMENT_UNSIGNED,
.argument_description = "[target]",
.command_handler = &command_tacho,
},
};
static void command_help(void* argument)
@ -330,7 +374,7 @@ void main(void)
uart_setup(); // setup USART (for printing)
#endif
usb_cdcacm_setup(); // setup USB CDC ACM (for printing)
puts("\nwelcome to the CuVoodoo STM32F1 example application\n"); // print welcome message
puts("\nwelcome to the CuVoodoo dachboden clock turner\n"); // print welcome message
#if DEBUG
// show reset cause
@ -374,7 +418,7 @@ void main(void)
printf("setup internal RTC: ");
#if defined(BLUE_PILL) || defined(STLINKV2) || defined(BLASTER) // for boards without a Low Speed External oscillator
// note: the blue pill LSE oscillator is affected when toggling the onboard LED, thus prefer the HSE
rtc_auto_awake(RCC_HSE, 8000000 / 128 - 1); // use High Speed External oscillator (8 MHz / 128) as RTC clock (VBAT can't be used to keep the RTC running)
rtc_auto_awake(RCC_HSE, 8000000 / 128 / RTC_TICKS - 1); // use High Speed External oscillator (8 MHz / 128) as RTC clock (VBAT can't be used to keep the RTC running)
#else // for boards with an precise Low Speed External oscillator
rtc_auto_awake(RCC_LSE, 32768 - 1); // ensure internal RTC is on, uses the 32.678 kHz LSE, and the prescale is set to our tick speed, else update backup registers accordingly (power off the micro-controller for the change to take effect)
#endif
@ -383,6 +427,40 @@ void main(void)
time_start = rtc_get_counter_val(); // get start time from internal RTC
printf("OK\n");
printf("setup SSR: ");
rcc_periph_clock_enable(GPIO_RCC(SSR_PIN)); // enable clock for GPIO peripheral
gpio_set(GPIO_PORT(SSR_PIN), GPIO_PIN(SSR_PIN)); // set high to switch off
gpio_set_mode(GPIO_PORT(SSR_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(SSR_PIN)); // set SSR - control pin to open drain. active low, connected to 5V on the + pin
printf("OK\n");
// setup timer to measure motor tachometer
printf("setup tachometer measurer: ");
rcc_periph_clock_enable(GPIO_RCC(TACHO_PIN)); // enable clock for GPIO peripheral
gpio_set_mode(GPIO_PORT(TACHO_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN(TACHO_PIN)); // set tachometer pin to input
rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt
exti_select_source(GPIO_EXTI(TACHO_PIN), GPIO_PORT(TACHO_PIN)); // mask external interrupt of this pin only for this port
gpio_set(GPIO_PORT(TACHO_PIN), GPIO_PIN(TACHO_PIN)); // pull up to eliminate noise
exti_set_trigger(GPIO_EXTI(TACHO_PIN), EXTI_TRIGGER_FALLING); // trigger when opto-coupler triggers
exti_enable_request(GPIO_EXTI(TACHO_PIN)); // enable external interrupt
nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(TACHO_PIN)); // enable interrupt
printf("OK\n");
// load target tachometer value from EEPROM
uint32_t eeprom_tacho[2];
flash_internal_eeprom_setup(1); // use 1 page for EEPROM emulation
bool eeprom_read = flash_internal_eeprom_read((uint8_t*)eeprom_tacho, sizeof(eeprom_tacho));
printf("target tachometer count (");
if (eeprom_read && eeprom_tacho[0] == (eeprom_tacho[1] ^ 0xffffffff)) {
tacho_target = eeprom_tacho[0];
printf("set");
} else {
tacho_target = TACHO_TARGET_DEFAULT;
printf("default");
}
bool tacho_safety = false; // if the motor is switched of for safety reasons
uint32_t tacho_activity = rtc_get_counter_val(); // when was the last time we saw the motor spinning
printf("): %u\n", tacho_target);
// setup terminal
terminal_prefix = ""; // set default prefix
terminal_process = &process_command; // set central function to process commands
@ -390,7 +468,6 @@ void main(void)
// start main loop
bool action = false; // if an action has been performed don't go to sleep
button_flag = false; // reset button flag
while (true) { // infinite loop
iwdg_reset(); // kick the dog
if (user_input_available) { // user input is available
@ -399,19 +476,32 @@ void main(void)
char c = user_input_get(); // store receive character
terminal_send(c); // send received character to terminal
}
if (button_flag) { // user pressed button
action = true; // action has been performed
printf("button pressed\n");
led_toggle(); // toggle LED
for (uint32_t i = 0; i < 1000000; i++) { // wait a bit to remove noise and double trigger
__asm__("nop");
}
button_flag = false; // reset flag
}
if (rtc_internal_tick_flag) { // the internal RTC ticked
rtc_internal_tick_flag = false; // reset flag
action = true; // action has been performed
uint32_t tacho = tacho_count; // backup before clearing
tacho_count = 0; // restart count
led_toggle(); // toggle LED (good to indicate if main function is stuck)
if (!tacho_safety) {
if (tacho < tacho_target) {
gpio_clear(GPIO_PORT(SSR_PIN), GPIO_PIN(SSR_PIN)); // switch SSR on to provide power
} else {
gpio_set(GPIO_PORT(SSR_PIN), GPIO_PIN(SSR_PIN)); // switch SSR off to cut power
}
if (tacho) { // we tachometer indicates the motor is spinning
tacho_activity = rtc_get_counter_val(); // updated the last time we saw it spinning
} else if (rtc_get_counter_val() > tacho_activity + 5 * RTC_TICKS) { // the motor does not seem to turn, after 5 s
tacho_safety = true; // turn safety on
gpio_set(GPIO_PORT(SSR_PIN), GPIO_PIN(SSR_PIN)); // switch SSR off to cut power
printf("\ntachometer does not indicate the motor is turning\n");
printf("either the tachometer is defective, or the motor is stuck\n");
printf("switching the motor off for safety\n");
printf("reboot to retry\n");
}
}
if (tacho_display) {
printf("%u\n", tacho); // display tachometer frequency
}
}
if (action) { // go to sleep if nothing had to be done, else recheck for activity
action = false;
@ -427,3 +517,10 @@ void rtc_isr(void)
rtc_clear_flag(RTC_SEC); // clear flag
rtc_internal_tick_flag = true; // notify to show new time
}
/** interrupt service routine called when tachometer edge is detected is pressed */
void GPIO_EXTI_ISR(TACHO_PIN)(void)
{
exti_reset_request(GPIO_EXTI(TACHO_PIN)); // reset interrupt
tacho_count++; // increment edge count
}

View File

@ -23,6 +23,7 @@ PROVIDE(__flash_end = ORIGIN(rom) + LENGTH(rom));
*/
PROVIDE(__application_end = 0);
PROVIDE(__flash_end = 0);
/* RAM location reserved so application can talk to bootloader and tell to start DFU */
PROVIDE(__dfu_magic = ORIGIN(ram) - 4);

View File

@ -665,6 +665,9 @@
#elif defined (BLASTER)
#define DFU_FORCE_PIN PA8 /**< GPIO pin (pin PA8, not SWD and UART pin on debug port) */
#define DFU_FORCE_VALUE 0 /**< short to nearby ground connection to force DFU */
#elif defined(STLINKV2)
#define DFU_FORCE_PIN PB6 /**< RST pin pin */
#define DFU_FORCE_VALUE 0 /**< short to nearby GND pin to force DFU */
#elif defined(BUSVOODOO)
#if BUSVOODOO_HARDWARE_VERSION==0
/* on BusVoodoo v0 DFU input is on PC7 */

View File

@ -242,7 +242,7 @@ static char usb_serial[] = "00112233445566778899aabb";
*/
static const char* usb_strings[] = {
"CuVoodoo", /**< manufacturer string */
"CuVoodoo STM32F1xx firmware", /**< product string */
"CuVoodoo dachboden clock jockey", /**< product string */
(const char*)usb_serial, /**< device ID used as serial number */
"DFU bootloader (runtime mode)", /**< DFU interface string */
};