-
Notifications
You must be signed in to change notification settings - Fork 155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable qemu target for STM32 emulation #562
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
Note: The following procedure has only been tested using a x86-64 Linux host. Your luck may vary with any other setup | ||
|
||
It is possible to do debugging and profiling using the STM32 QEMU fork here: | ||
https://github.com/DeviationTX/deviation/files/2743535/errors_master_gcc8.2.txt | ||
|
||
Build from scratch: | ||
git submodule update --init dtc | ||
./configure --enable-debug --target-list="arm-softmmu" --disable-werror | ||
make | ||
Using docker may be easier (but it is untested): | ||
docker build . -t qemu_stm32 | ||
|
||
You will need arm-none-eabi-gdb-py for profiling. This will require a more recent Embedded GCC (recommended GCC 8): | ||
https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads | ||
|
||
The image is built using: | ||
cd src | ||
make qemu | ||
The target is an STMF103RB (same as in devo7e). running on an Olimex P103 board. this is mostly irrelevant unless | ||
peripheral support is added in the future | ||
|
||
Once built, it can be started in qemu using: | ||
qemu-system-arm -S -s -M stm32-p103 -kernel qemu.bin | ||
or if you use the docker image (you're responsible for mounting your devo build area): | ||
docker run --rm qemu_stm32 /usr/local/bin/qemu-system-arm -M stm32-p103 -kernel qemu | ||
|
||
And finally the profiler can be run via gdb: | ||
arm-none-eabi-gdb-py | ||
source ../utils/qemu.py | ||
|
||
The code to profile can be found in target/qemu/target_main.c | ||
The profiler will run only on the 'run_profile()' function. All other code in main() will run 1st. | ||
|
||
Be aware that the STM32 QEMU is not feature complete. It does not support SPI, and the Clock and Timer support is limited. | ||
Trying to run a full image is likely to be futile. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
FILESYSTEMS := common base_fonts 320x240x16 | ||
|
||
SCREENSIZE := 320x240x16 | ||
FONTS = filesystem/$(FILESYSTEM)/media/15ascii.fon \ | ||
filesystem/$(FILESYSTEM)/media/23bold.fon | ||
|
||
ifndef BUILD_TARGET | ||
CROSS = arm-none-eabi- | ||
|
||
ALL = $(LIBOPENCM3) $(TARGET).bin | ||
|
||
NUM_MODELS ?= 10 | ||
|
||
LINKFILE = $(SDIR)/target/$(TARGET)/$(TARGET).ld | ||
LIBOPENCM3 = $(SDIR)/libopencm3/lib/libopencm3_stm32f1.a | ||
|
||
SRC_C := $(wildcard $(SDIR)/target/$(TARGET)/*.c) \ | ||
$(wildcard $(SDIR)/target/common/stm32/*.c) \ | ||
$(wildcard $(SDIR)/target/common/filesystems/*.c) \ | ||
$(wildcard $(SDIR)/target/common/filesystems/devofs/*.c) \ | ||
$(wildcard $(SDIR)/target/common/filesystems/petit_fat/*.c) \ | ||
$(wildcard $(SDIR)/target/common/devo/msc2/*.c) \ | ||
$(wildcard $(SDIR)/target/common/devo/msc2/lib/*.c) \ | ||
$(wildcard $(SDIR)/target/common/devo/hid/*.c) \ | ||
$(wildcard $(SDIR)/target/common/devo/protocol/*.c) \ | ||
$(wildcard $(SDIR)/target/common/devo/uart.c) | ||
|
||
SRC_C := $(filter-out $(SDIR)/target/common/stm32/spi_flash.c, $(SRC_C)) | ||
|
||
CFLAGS = -DPROFILE -D"assert_param(x)=" -DSTM32F10X_HD -DSTM32F1 -mcpu=cortex-m3 -mthumb -mfix-cortex-m3-ldrd -fdata-sections -ffunction-sections -I$(SDIR)/target/common/devo/msc2/lib -I$(SDIR)/target/common/devo/msc2 -I$(SDIR)/libopencm3/include -I$(SDIR)/target/common/filesystems -fno-builtin-printf -Os --specs=nano.specs | ||
MODULE_FLAGS = -fno-builtin | ||
|
||
LFLAGS = -nostartfiles -Wl,-gc-sections -Wl,-Map=$(TARGET).map,--cref -lc -lnosys -L$(SDIR) -Lobjs/$(TARGET) | ||
LFLAGS2 = -Wl,-T$(LINKFILE) | ||
|
||
|
||
else #BUILD_TARGET | ||
|
||
$(TARGET).bin: $(TARGET).elf | ||
$(CP) -O binary $< $@ | ||
$(DUMP) -S $< > $(TARGET).list | ||
|
||
$(LIBOPENCM3): | ||
+$(MAKE) -C $(SDIR)/libopencm3 TARGETS=stm32/f1 lib | ||
|
||
|
||
endif #BUILD_TARGET |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* | ||
This project is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
|
||
Deviation is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
|
||
You should have received a copy of the GNU General Public License | ||
along with Deviation. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#include <libopencm3/stm32/gpio.h> | ||
#include <libopencm3/stm32/rcc.h> | ||
#include <libopencm3/stm32/timer.h> | ||
#include "common.h" | ||
|
||
// FIXME introduce constants for Timer (TIM4), compare (TIM_Oc4) | ||
// timer clock (RCC_APB1ENR_TIM4EN), Port (GPIOB), Pin (GPIO9) | ||
// Port clock RCC_APB2ENR_IOPBEN | ||
|
||
void BACKLIGHT_Init() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cna u just include another real implementation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. some of these may be able to use a real implementation, but the qemu target does not have all STM32 features, and without proper SPI or clock support a lot of things won't work. Before I merge this, I'll clean up what can be done though. |
||
{ | ||
// Pin PB9, Timer 4 channel 4 | ||
rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPBEN); | ||
//Turn off backlight | ||
gpio_set_mode(GPIOB, GPIO_MODE_INPUT, | ||
GPIO_CNF_INPUT_FLOAT, GPIO9); | ||
|
||
//Configure Backlight PWM | ||
rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_TIM4EN); | ||
timer_set_mode(TIM4, TIM_CR1_CKD_CK_INT, | ||
TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); | ||
timer_set_period(TIM4, 0x2CF); | ||
timer_set_prescaler(TIM4, 0); | ||
timer_generate_event(TIM4, TIM_EGR_UG); | ||
//timer_set_repetition_counter(TIM3, 0); | ||
timer_set_oc_mode(TIM4, TIM_OC4, TIM_OCM_PWM1); | ||
timer_enable_oc_preload(TIM4, TIM_OC4); | ||
|
||
timer_set_oc_polarity_high(TIM4, TIM_OC4); | ||
timer_enable_oc_output(TIM4, TIM_OC4); | ||
|
||
timer_enable_preload(TIM4); | ||
} | ||
|
||
void BACKLIGHT_Brightness(unsigned brightness) | ||
{ | ||
timer_disable_counter(TIM4); | ||
if (brightness == 0) { | ||
// Turn off Backlight | ||
gpio_set_mode(GPIOB, GPIO_MODE_INPUT, | ||
GPIO_CNF_INPUT_FLOAT, GPIO9); | ||
} else if(brightness > 9) { | ||
// Turn on Backlight full | ||
gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, | ||
GPIO_CNF_OUTPUT_PUSHPULL, GPIO9); | ||
gpio_set(GPIOB, GPIO9); | ||
} else { | ||
gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, | ||
GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9); | ||
u32 duty_cycle = 720 * brightness / 10 ; | ||
timer_set_oc_value(TIM4, TIM_OC4, duty_cycle); | ||
timer_enable_counter(TIM4); | ||
} | ||
} | ||
|
||
void LCD_Contrast(unsigned contrast) | ||
{ | ||
(void)contrast; // dummy method for devo8. Only valid in devo10 now | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#ifdef CHANDEF | ||
CHANDEF(AILERON) | ||
CHANDEF(ELEVATOR) | ||
CHANDEF(THROTTLE) | ||
CHANDEF(RUDDER) | ||
CHANDEF(AUX4) | ||
CHANDEF(AUX5) | ||
CHANDEF(AUX6) | ||
CHANDEF(AUX7) | ||
CHANDEF(SWA0) | ||
CHANDEF(SWA1) | ||
CHANDEF(SWB0) | ||
CHANDEF(SWB1) | ||
CHANDEF(SWC0) | ||
CHANDEF(SWC1) | ||
CHANDEF(SWC2) | ||
CHANDEF(SWD0) | ||
CHANDEF(SWD1) | ||
CHANDEF(SWE0) | ||
CHANDEF(SWE1) | ||
CHANDEF(SWE2) | ||
CHANDEF(SWF0) | ||
CHANDEF(SWF1) | ||
CHANDEF(SWG0) | ||
CHANDEF(SWG1) | ||
CHANDEF(SWG2) | ||
CHANDEF(SWH0) | ||
CHANDEF(SWH1) | ||
#endif | ||
|
||
#ifdef UNDEF_INP | ||
#define INP_RUD_DR0 INP_SWA0 | ||
#define INP_RUD_DR1 INP_SWA1 | ||
#define INP_ELE_DR0 INP_SWB0 | ||
#define INP_ELE_DR1 INP_SWB1 | ||
#define INP_AIL_DR0 INP_SWC0 | ||
#define INP_AIL_DR1 INP_SWC1 | ||
#define INP_FMOD0 INP_SWD0 | ||
#define INP_MIX0 INP_SWE0 | ||
#define INP_MIX1 INP_SWE1 | ||
#define INP_MIX2 INP_SWE2 | ||
#define INP_GEAR0 INP_SWF0 | ||
#define INP_GEAR1 INP_SWF1 | ||
#endif | ||
|
||
|
||
#ifdef BUTTONDEF | ||
BUTTONDEF(TRIM_LV_NEG) /* LEFT-VERTICAL */ | ||
BUTTONDEF(TRIM_LV_POS) | ||
BUTTONDEF(TRIM_RV_NEG) /* RIGHT-VERTICAL */ | ||
BUTTONDEF(TRIM_RV_POS) | ||
BUTTONDEF(TRIM_LH_NEG) /* LEFT-HORIZONTAL */ | ||
BUTTONDEF(TRIM_LH_POS) | ||
BUTTONDEF(TRIM_RH_NEG) /* RIGHT-HORIZONTAL */ | ||
BUTTONDEF(TRIM_RH_POS) | ||
BUTTONDEF(LEFT) | ||
BUTTONDEF(RIGHT) | ||
BUTTONDEF(DOWN) | ||
BUTTONDEF(UP) | ||
BUTTONDEF(ENTER) | ||
BUTTONDEF(EXIT) | ||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/* | ||
This project is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
|
||
Deviation is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
|
||
You should have received a copy of the GNU General Public License | ||
along with Deviation. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
#include <libopencm3/stm32/rcc.h> | ||
#include <libopencm3/stm32/gpio.h> | ||
#include "common.h" | ||
#include "mixer.h" | ||
#include "config/tx.h" | ||
#include "../common/devo/devo.h" | ||
|
||
//Order is MODE1: AIL, ELE, THR, RUD, LeftDial, Right Dial, Left Shoulder, Right Shoulder, Vdd, Voltage | ||
// PA0 PA1, PA2, PA3, PA5, PA6, PB0 PA4, PB1 | ||
const u8 adc_chan_sel[NUM_ADC_CHANNELS] = | ||
{0, 1, 2, 3, 5, 6, 8, 4, 16, 9}; | ||
|
||
void CHAN_Init() | ||
{ | ||
rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPAEN); | ||
rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPBEN); | ||
rcc_peripheral_enable_clock(&RCC_APB2ENR, RCC_APB2ENR_IOPCEN); | ||
ADC_Init(); | ||
|
||
/* configure channels for analog */ | ||
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, GPIO0); | ||
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, GPIO1); | ||
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, GPIO2); | ||
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, GPIO3); | ||
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, GPIO4); | ||
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, GPIO5); | ||
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, GPIO6); | ||
/* Enable Voltage measurement */ | ||
gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, GPIO0); | ||
gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, GPIO1); | ||
|
||
/* configure switches for digital I/O */ | ||
gpio_set_mode(GPIOC, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, | ||
GPIO0 | GPIO1 | GPIO2 | GPIO3 | GPIO4 | | ||
GPIO5 | GPIO6 | GPIO7 | GPIO8 | GPIO9); | ||
gpio_set(GPIOC, | ||
GPIO0 | GPIO1 | GPIO2 | GPIO3 | GPIO4 | | ||
GPIO5 | GPIO6 | GPIO7 | GPIO8 | GPIO9); | ||
gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO8); | ||
gpio_set(GPIOA, GPIO8); | ||
} | ||
|
||
s32 CHAN_ReadRawInput(int channel) | ||
{ | ||
s32 value = 0; | ||
switch(channel) { | ||
case INP_AILERON: value = adc_array_raw[0]; break; // bug fix: right vertical | ||
case INP_ELEVATOR: value = adc_array_raw[1]; break; // bug fix: right horizon | ||
case INP_THROTTLE: value = adc_array_raw[2]; break; // bug fix: left horizon | ||
case INP_RUDDER: value = adc_array_raw[3]; break; // bug fix: left vertical | ||
case INP_AUX4: value = adc_array_raw[4]; break; | ||
case INP_AUX5: value = adc_array_raw[5]; break; | ||
case INP_AUX6: value = adc_array_raw[6]; break; | ||
case INP_AUX7: value = adc_array_raw[7]; break; | ||
case INP_SWA0: value = gpio_get(GPIOC, GPIO0); break; | ||
case INP_SWA1: value = ! gpio_get(GPIOC, GPIO0); break; | ||
case INP_SWB0: value = gpio_get(GPIOC, GPIO1); break; | ||
case INP_SWB1: value = ! gpio_get(GPIOC, GPIO1); break; | ||
case INP_SWC0: value = ! gpio_get(GPIOC, GPIO2); break; | ||
case INP_SWC1: value = (gpio_get(GPIOC, GPIO2) && gpio_get(GPIOC, GPIO3)); break; | ||
case INP_SWC2: value = ! gpio_get(GPIOC, GPIO3); break; | ||
case INP_SWD0: value = gpio_get(GPIOC, GPIO6); break; | ||
case INP_SWD1: value = ! gpio_get(GPIOC, GPIO6); break; | ||
case INP_SWE0: value = ! gpio_get(GPIOC, GPIO4); break; | ||
case INP_SWE1: value = (gpio_get(GPIOC, GPIO4) && gpio_get(GPIOC, GPIO5)); break; | ||
case INP_SWE2: value = ! gpio_get(GPIOC, GPIO5); break; | ||
case INP_SWF0: value = gpio_get(GPIOC, GPIO7); break; | ||
case INP_SWF1: value = ! gpio_get(GPIOC, GPIO7); break; | ||
case INP_SWG0: value = ! gpio_get(GPIOC, GPIO8); break; | ||
case INP_SWG1: value = (gpio_get(GPIOC, GPIO8) && gpio_get(GPIOC, GPIO9)); break; | ||
case INP_SWG2: value = ! gpio_get(GPIOC, GPIO9); break; | ||
case INP_SWH0: value = gpio_get(GPIOA, GPIO8); break; | ||
case INP_SWH1: value = ! gpio_get(GPIOA, GPIO8); break; | ||
} | ||
return value; | ||
} | ||
s32 CHAN_ReadInput(int channel) | ||
{ | ||
s32 value = CHAN_ReadRawInput(channel); | ||
if(channel <= INP_HAS_CALIBRATION) { | ||
s32 max = Transmitter.calibration[channel - 1].max; | ||
s32 min = Transmitter.calibration[channel - 1].min; | ||
s32 zero = Transmitter.calibration[channel - 1].zero; | ||
if(! zero) { | ||
//If this input doesn't have a zero, calculate from max/min | ||
zero = ((u32)max + min) / 2; | ||
} | ||
// Derate min and max by 1% to ensure we can get all the way to 100% | ||
max = (max - zero) * 99 / 100; | ||
min = (min - zero) * 99 / 100; | ||
if(value >= zero) { | ||
value = (value - zero) * CHAN_MAX_VALUE / max; | ||
} else { | ||
value = (value - zero) * CHAN_MIN_VALUE / min; | ||
} | ||
//Bound output | ||
if (value > CHAN_MAX_VALUE) | ||
value = CHAN_MAX_VALUE; | ||
if (value < CHAN_MIN_VALUE) | ||
value = CHAN_MIN_VALUE; | ||
} else { | ||
value = value ? CHAN_MAX_VALUE : CHAN_MIN_VALUE; | ||
} | ||
if (channel == INP_AILERON || channel == INP_ELEVATOR || | ||
channel == INP_THROTTLE || channel == INP_RUDDER) | ||
{ | ||
value = -value; | ||
} | ||
return value; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should create a Makefile.inc for all STM32 related parts. this makefile, the one under devo/common, and the one under at9 are largely duplicatd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes probably. This was only meant to be a proof-of-concept. certainly not ready for inclusion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we also need an instruction on qemu as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some documentation now.