app: working clock generator firmware

This commit is contained in:
King Kévin 2022-05-10 10:50:22 +02:00
parent d40972200d
commit 88349d3918
3 changed files with 305 additions and 28 deletions

View File

@ -1,35 +1,28 @@
This firmware template is designed for development boards based around [STM32 F4 series micro-controller](https://www.st.com/en/microcontrollers-microprocessors/stm32f4-series.html).
clock generator (up to 125 MHz) using AD9850.
project
=======
capabilities:
summary
-------
- up to 125 MHz@5V, 100MHz@3.3V
- display shows desired frequency in mHz
- actual output uses AD9850's 0.0291 Hz resolution
- square and sine wave output (and it's inverse)
- 3.3V and 5V output
*describe project purpose*
hardware
========
technology
----------
*described electronic details*
board
=====
The underlying template also supports following board:
the clock generator uses following parts:
- [WeAct MiniF4](https://github.com/WeActTC/MiniF4-STM32F4x1), based on a STM32F401CCU6
**Which board is used is defined in the Makefile**.
This is required to map the user LED and button provided on the board
- Analog Devices AD9850, to generator the clock
- HD44780 LCD display (1x16), to display the set frequency
- rotary encoder, to set the frequency
- switch, to set the output voltage
- coin cell, to save set frequency
connections
===========
Connect the peripherals the following way (STM32F4xx signal; STM32F4xx pin; peripheral pin; peripheral signal; comment):
- *list board to peripheral pin connections*
All pins are configured using `define`s in the corresponding source code.
code

View File

@ -1,8 +1,8 @@
/** STM32F4 application example
/** clock generator, using AD9850
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016-2021
* @date 2016-2022
*/
/* standard libraries */
@ -11,6 +11,7 @@
#include <string.h> // string utilities
#include <time.h> // date/time utilities
#include <ctype.h> // utilities to check chars
#include <math.h> // float utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
@ -24,6 +25,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/exti.h> // external interrupt defines
/* own libraries */
#include "global.h" // board definitions
@ -32,6 +34,7 @@
#include "usb_cdcacm.h" // USB CDC ACM utilities
#include "terminal.h" // handle the terminal interface
#include "menu.h" // menu utilities
#include "lcd_hd44780.h" // LCD display
/** watchdog period in ms */
#define WATCHDOG_PERIOD 10000
@ -49,6 +52,24 @@ static volatile bool second_flag = false; /**< flag set when a second passed */
/** number of seconds since boot */
static uint32_t boot_time = 0;
/** connection to AD9850 */
#define AD9850_DATA PA7
#define AD9850_FQUD PA6
#define AD9850_WCLK PA5
//#define AD9850_D0 PA0
//#define AD9850_D1 PA1
//#define AD9850_D2 PA2
/** AD9850 frequency to set (in mHz) */
static uint64_t ad9850_freq = 0;
/** maximum frequency (in mHz) */
#define AD9850_MAX_FREQ 125000000000ULL
/** connections to rotary encoder (common is ground) */
#define ROTARY_A PA1
#define ROTARY_B PA2
static volatile int8_t rotary_flag = 0; /** flag set when rotary encoder is turned (1 = CW, -1 = CCW) */
size_t putc(char c)
{
size_t length = 0; // number of characters printed
@ -267,6 +288,75 @@ static void command_bootloader(void* argument)
dfu_bootloader(); // start DFU bootloader
}
/** set AD9850 output frequency
* @param[in] frequency frequency to set (in Hz)
* @return actual frequency set (in Hz)
* @note a frequency of 0 disables the output
*/
static double ad9850_set_freq(double frequency)
{
if (frequency > AD9850_MAX_FREQ / 1000) {
frequency = AD9850_MAX_FREQ / 1000;
} else if (frequency < 0) {
frequency = 0;
}
// start with default state
gpio_clear(GPIO_PORT(AD9850_WCLK), GPIO_PIN(AD9850_WCLK));
gpio_clear(GPIO_PORT(AD9850_FQUD), GPIO_PIN(AD9850_FQUD));
// enable serial mode (W0 must we xxxxx011, D0=1, D1=1, D2=0)
gpio_set(GPIO_PORT(AD9850_WCLK), GPIO_PIN(AD9850_WCLK));
sleep_us(1); // tWH = 3.5 ns
gpio_clear(GPIO_PORT(AD9850_WCLK), GPIO_PIN(AD9850_WCLK));
gpio_set(GPIO_PORT(AD9850_FQUD), GPIO_PIN(AD9850_FQUD));
sleep_us(1); // tFH = 7 ns
gpio_clear(GPIO_PORT(AD9850_FQUD), GPIO_PIN(AD9850_FQUD));
// shift out data
const uint32_t freq = round(frequency * 0xffffffff / 125E6); // output 100 kHz
const uint8_t control = 0; // must be 0 for serial data
uint8_t power_down = 0; // power up
if (0 == frequency) {
power_down = 1;
}
const uint8_t phase = 0;
uint64_t shift_out = ((uint64_t)freq << 0) | ((uint64_t)control << 32) | ((uint64_t)power_down << 34) | ((uint64_t)phase << 35); // data to be shifted out
for (uint8_t b = 0; b < 40; b++) { // shift out data, LSb first
if (shift_out & 0x01) {
gpio_set(GPIO_PORT(AD9850_DATA), GPIO_PIN(AD9850_DATA));
} else {
gpio_clear(GPIO_PORT(AD9850_DATA), GPIO_PIN(AD9850_DATA));
}
sleep_us(1); // tDS = 3.5 ns
gpio_set(GPIO_PORT(AD9850_WCLK), GPIO_PIN(AD9850_WCLK));
sleep_us(1); // tWH = 3.5 ns
gpio_clear(GPIO_PORT(AD9850_WCLK), GPIO_PIN(AD9850_WCLK));
sleep_us(1); // tWL = 3.5 ns
// tDH = 3.5ns
shift_out >>= 1; // prepare next bit
}
// latch data
// tFD = 7.0 ns
gpio_set(GPIO_PORT(AD9850_FQUD), GPIO_PIN(AD9850_FQUD));
sleep_us(1); // tFH = 7.0 ns
gpio_clear(GPIO_PORT(AD9850_FQUD), GPIO_PIN(AD9850_FQUD));
sleep_us(1); // tFL = 7.0 ns
return freq * (125E6 / 0xffffffff);
}
/** set AD9850 output frequency */
static void command_freq(void* argument)
{
if (argument) {
ad9850_freq = *(double*)argument * 1000.0; // get user provided frequency
}
const double freq = ad9850_set_freq(ad9850_freq / 1000.0); // set frequency and get the one set
printf("frequency set to %0.3f Hz\n", freq);
}
/** list of all supported commands */
static const struct menu_command_t menu_commands[] = {
{
@ -325,6 +415,14 @@ static const struct menu_command_t menu_commands[] = {
.argument_description = NULL,
.command_handler = &command_bootloader,
},
{
.shortcut = 'f',
.name = "frequency",
.command_description = "set output frequency",
.argument = MENU_ARGUMENT_FLOAT,
.argument_description = "[Hz]",
.command_handler = &command_freq,
},
};
static void command_help(void* argument)
@ -356,6 +454,96 @@ static void process_command(char* str)
}
}
/** create 16 char representation of number
* @number number to represent
* @return 16 char representation
*/
static char* freq2s(uint64_t freq)
{
static char line[16 + 1];
for (uint8_t i = 0; i < LENGTH(line) - 1; i++) {
line[i] = ' '; // clear line
}
line[LENGTH(line) - 1] = '\0'; // terminate string
bool zero_padding = false;
uint64_t divider = 100000000000UL;
uint8_t pos = 1; // position in the line
for (uint8_t d = 0; d < 12; d++) {
if (3 == d || 6 == d) {
if (zero_padding) {
line[pos] = ','; // add separator
}
pos++;
} else if (9 == d) {
if (zero_padding) {
line[pos] = '.'; // add separator
}
pos++;
}
if (8 == d) {
zero_padding = true; // enforce hertz unit display
}
const uint8_t digit = (freq / divider) % 10;
if (digit > 0) {
line[pos] = '0' + digit; // set digit
zero_padding = true; // remember to pad with zeros now
} else if (zero_padding) {
line[pos] = '0';
} else {
line[pos] = ' ';
}
divider /= 10; // go to next digit
pos++; // go to next position
}
return line;
}
static void update_display(uint64_t freq, uint8_t position, bool selected)
{
const uint8_t position2cursor_lut[] = {0x47, 0x46, 0x45, 0x43, 0x42, 0x41, 0x07, 0x06, 0x05, 0x03, 0x02, 0x01};
if (position >= LENGTH(position2cursor_lut)) {
position = LENGTH(position2cursor_lut) - 1;
}
const char* line = freq2s(freq); // get frequency representation
lcd_hd44780_write_line(0, &line[0], 8); // display set frequency
lcd_hd44780_write_line(1, &line[8], 8); // display set frequency
lcd_hd44780_set_ddram_address(position2cursor_lut[position]); // set cursor position
lcd_hd44780_display_control(true, true, selected);
}
/** load settings from SRAM */
static void load_settings(uint8_t* position, uint64_t* frequency)
{
if (position) {
*position = RTC_BKPXR(0);
if (*position >= 12) {
*position = 11;
}
}
if (frequency) {
*frequency = (RTC_BKPXR(1) << 0) + ((uint64_t)RTC_BKPXR(2) << 32);
if (*frequency > AD9850_MAX_FREQ) {
*frequency = AD9850_MAX_FREQ;
}
}
}
/** save settings to SRAM */
static void save_settings(uint8_t position, uint64_t frequency)
{
if (position >= 12) {
position = 11;
}
RTC_BKPXR(0) = position;
if (frequency > AD9850_MAX_FREQ) {
frequency = AD9850_MAX_FREQ;
}
RTC_BKPXR(1) = frequency >> 0;
RTC_BKPXR(2) = frequency >> 32;
}
/** program entry point
* this is the firmware function started by the micro-controller
*/
@ -379,7 +567,7 @@ void main(void)
board_setup(); // setup board
uart_setup(); // setup USART (for printing)
usb_cdcacm_setup(); // setup USB CDC ACM (for printing)
puts("\nwelcome to the CuVoodoo STM32F4 example firmware\n"); // print welcome message
puts("\nwelcome to the CuVoodoo clock generator\n"); // print welcome message
#if DEBUG
// show reset cause
@ -448,6 +636,43 @@ void main(void)
// important: do not re-enable backup_domain_write_protect, since this will prevent clearing flags (but RTC registers do not need to be unlocked)
puts_debug("OK\n");
puts_debug("setup rotary encoder: ");
rcc_periph_clock_enable(GPIO_RCC(ROTARY_B)); // enable clock for button
gpio_mode_setup(GPIO_PORT(ROTARY_B), GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN(ROTARY_B)); // set GPIO to input and pull up
rcc_periph_clock_enable(GPIO_RCC(ROTARY_A)); // enable clock for button
gpio_mode_setup(GPIO_PORT(ROTARY_A), GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN(ROTARY_A)); // set GPIO to input and pull up
exti_select_source(GPIO_EXTI(ROTARY_A), GPIO_PORT(ROTARY_A)); // mask external interrupt of this pin only for this port
exti_set_trigger(GPIO_EXTI(ROTARY_A), EXTI_TRIGGER_FALLING); // trigger when button is pressed
exti_enable_request(GPIO_EXTI(ROTARY_A)); // enable external interrupt
nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(ROTARY_A)); // enable interrupt
uint8_t digit_position = 0; // which digit is selected
load_settings(&digit_position, NULL); // load saved position
bool digit_selected = false; // if a digit is selected
puts_debug("OK\n");
puts_debug("setup AD9850: ");
rcc_periph_clock_enable(GPIO_RCC(AD9850_DATA)); // enable clock for GPIO
gpio_clear(GPIO_PORT(AD9850_DATA), GPIO_PIN(AD9850_DATA)); // don't care about data
gpio_mode_setup(GPIO_PORT(AD9850_DATA), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(AD9850_DATA)); // set pin as output
gpio_set_output_options(GPIO_PORT(AD9850_DATA), GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_PIN(AD9850_DATA)); // set output as push-pull
rcc_periph_clock_enable(GPIO_RCC(AD9850_WCLK)); // enable clock for GPIO
gpio_clear(GPIO_PORT(AD9850_WCLK), GPIO_PIN(AD9850_WCLK)); // idle low
gpio_mode_setup(GPIO_PORT(AD9850_WCLK), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(AD9850_WCLK)); // set pin as output
gpio_set_output_options(GPIO_PORT(AD9850_WCLK), GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_PIN(AD9850_WCLK)); // set output as push-pull
rcc_periph_clock_enable(GPIO_RCC(AD9850_FQUD)); // enable clock for GPIO
gpio_clear(GPIO_PORT(AD9850_FQUD), GPIO_PIN(AD9850_FQUD)); // idle low
gpio_mode_setup(GPIO_PORT(AD9850_FQUD), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(AD9850_FQUD)); // set pin as output
gpio_set_output_options(GPIO_PORT(AD9850_FQUD), GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_PIN(AD9850_FQUD)); // set output as push-pull
load_settings(NULL, &ad9850_freq); // load saved frequency
ad9850_set_freq(ad9850_freq);
puts_debug("OK\n");
puts_debug("setup HD44780 LCD: ");
lcd_hd44780_setup(true, false); // I don't know why the initialisation does not always works the first time (but replugging the power in solveds it)
update_display(ad9850_freq, digit_position, digit_selected);
puts_debug("OK\n");
load_settings(&digit_position, &ad9850_freq);
// setup terminal
terminal_prefix = ""; // set default prefix
terminal_process = &process_command; // set central function to process commands
@ -467,16 +692,62 @@ void main(void)
}
if (button_flag) { // user pressed button
action = true; // action has been performed
puts("button pressed\n");
led_toggle(); // toggle LED
sleep_ms(10); // debounce
if (!gpio_get(GPIO_PORT(BUTTON_PIN), GPIO_PIN(BUTTON_PIN))) { // only allow press (not release)
digit_selected = !digit_selected;
update_display(ad9850_freq, digit_position, digit_selected);
}
sleep_ms(100); // wait a bit to remove noise and double trigger
button_flag = false; // reset flag
}
if (rotary_flag) { // user turned rotary encoder
action = true; // action has been performed
if (rotary_flag > 0) {
if (digit_selected) {
uint64_t unit = 1;
for (uint8_t i = 0; i < digit_position; i++) {
unit *= 10;
}
ad9850_freq += unit;
if (ad9850_freq > AD9850_MAX_FREQ) {
ad9850_freq = AD9850_MAX_FREQ;
}
} else {
if (digit_position > 0) {
digit_position--;
}
}
} else {
if (digit_selected) {
uint64_t unit = 1;
for (uint8_t i = 0; i < digit_position; i++) {
unit *= 10;
}
if (unit > ad9850_freq) {
ad9850_freq = 0;
} else {
ad9850_freq -= unit;
}
} else {
if (digit_position < 11) {
digit_position++;
}
}
}
save_settings(digit_position, ad9850_freq); // save setting to SRAM
ad9850_set_freq(ad9850_freq / 1000.0); // reset frequency (in case the target has been reset)
update_display(ad9850_freq, digit_position, digit_selected);
sleep_ms(100); // wait a bit to remove noise and double trigger
rotary_flag = 0; // reset flag
}
if (wakeup_flag) { // time to do periodic checks
wakeup_flag = false; // clear flag
}
if (second_flag) { // one second passed
second_flag = false; // clear flag
action = true; // action has been performed
ad9850_set_freq(ad9850_freq / 1000.0); // reset frequency (in case the target has been reset)
update_display(ad9850_freq, digit_position, digit_selected);
led_toggle(); // toggle LED to indicate if main function is stuck
}
if (action) { // go to sleep if nothing had to be done, else recheck for activity
@ -500,3 +771,16 @@ void rtc_wkup_isr(void)
tick = WAKEUP_FREQ; // restart count down
}
}
/** interrupt service routine called when rotary encoder is turned */
void GPIO_EXTI_ISR(ROTARY_A)(void)
{
exti_reset_request(GPIO_EXTI(ROTARY_A)); // reset interrupt
if (rotary_flag) { // flag not cleared yet
return;
} else if (gpio_get(GPIO_PORT(ROTARY_B), GPIO_PIN(ROTARY_B))) {
rotary_flag = 1;
} else {
rotary_flag = -1;
}
}

View File

@ -229,7 +229,7 @@ static char usb_serial[] = "0123456789ab";
*/
static const char* usb_strings[] = {
"CuVoodoo", /**< manufacturer string */
"CuVoodoo STM32F4xx firmware", /**< product string */
"CuVoodoo clock generator", /**< product string */
(const char*)usb_serial, /**< device ID used as serial number */
"DFU bootloader (runtime mode)", /**< DFU interface string */
};