app: working clock generator firmware
This commit is contained in:
parent
d40972200d
commit
88349d3918
37
README.md
37
README.md
|
@ -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
|
||||
|
|
294
application.c
294
application.c
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue