Introduction: Four Bit Oscilloscope

It is a for-fun-project just to see how far in speeds I could push a MAX7219 dot matrix display. And instead of having it run the "game of life", I decided to make a "scope" with it. As you will understand from the title, this isn't a replacement for a real oscilloscope :-).

As I do not plan to use this in any serious way, I will not make a printed circuit board for it. Maybe, just maybe I'll put it on a perf-board but for now it is, and will stay, on a breadboard. Also there is no input amplifier/attenuator, you have to supply a signal between 0 and 3.3V, do not go negative or over 3.3V as you may damage the microcontroller.

Step 1: Hardware

It is cheap, very cheap when you buy the parts in China via ebay or similar sites.
It uses a STM32F103C8 development board, sometimes called a "blue pill" that I purchased for around 2 euro (or USD, they are almost the same value, end 2018), two 8x8x4 dot-matrix displays with MAX7219 chips on it, bought for 5 euro a piece and a rotary encoder of about 1 euro.

Needed of course is a power supply delivering 3.3V at a few hundred milliamps. The voltage regulator on the STM32F103C8 development board is not used, it cannot provide enough current for the displays.
The datasheet for the MAX7219 specifies the operating supply voltage should be between 4.0 and 5.5V but it runs fine on 3.3V, perhaps not when you use it in a very hot or cold environment, but at 20 Celsius it is fine. And now I do not have to use level-converters between the microcontroller and the displayboards.

Step 2: Build

When you look at the picture you might see that I use the power lines on the breadboards in a non conventional way, both lines on top are the positive rail and both on the bottom are the ground rail. It is the way I'm used to do it and it works well, it makes the setup look a bit more like the schematics I draw. Also, I have made a lot of small boards with parts on that I can plug into the breadboard to speed up things and they are all configured to use the two top lines as positive and the lower lines as ground.
As I said, the resolution is 4 bit (16 levels), and as there are 4x8 leds next to each other there are just 32 sample points (pts). Compare that with a Rigol Rigol DS1054Z (8 bit and 12Mpts) and you'll see that this is hardly a toy. What the actual bandwidth is, I don't know, I have tested it up to 10kHz and that works fine.

Step 3: Programs

The IDE I use is Atollic TrueStudio which as of the beginning of this year (2018) was adopted by ST Micro Electronics and is available for free, no time limit, no code size limit, no nag-screens. Together with it, I use STM32CubeMX, a program that supplies me with the starting code and generates the initialisation of all peripherals. And it has a display of all the pins of the microcontroller and their use. Even if you do not use STM32CubeMX for generating code, this is very handy.
One thing I do not like is the so called HAL which is the default of STM32CubeMX. I prefer the LowLayer method of working.

To program the microcontroller I use either the ST-Link programmer/debugger from ST Micro Electronics or the J-Link made by Segger. Both these devices are not free, although you can buy Chinese copies of them for a few euros.

Step 4: About the Code

The MAX7219's address the LEDs in what I call a horizontal fashion, 8 leds next to each other. For an oscilloscope 8 LED's on top of each other would have been easier, so I made a simple frame-buffer that is written to with data in a vertical way, and read out in the required horizontal way. The MAX7219 uses a 16bit code per 8 LEDs, where the first byte is used to address the selected line. And as there are four of these modules stacked next to each other, with their inputs connected to the outputs of the module before it, you have to send those 16bits four times to reach the last module. (I hope I'm making things clear...)
The data is send to the MAX7219 using SPI, a simple but very fast protocol. This is what I was experimenting with, how fast can you go in sending the data to the MAX7219. In the end, I switched back to 9 MHz just below the maximum speed the datasheet specifies.

I use two of the four available timers of the STM32F103C8, one for the generation of the timebase and the other for reading out the rotary encoder, which sets the timebase. TIMER3 generates the timebase, it does it by dividing the clock by 230, updating the counter every 3.2 uS. Witch the rotary encoder you can select to have the counter count from 2 clock pulses up to 2000 clock pulses. Let's say you choose 100. TIMER3 then generates an EVENT every 320 uS. This EVENT triggers the ADC to record a sample of the input signal, and as the are 32 samples to take for one screenful, this will complete after aprox. 10 mS. In 10mS you can fit one wavelength of 100 Hz, or two of 200 Hz, and so on. Going over 3 waves per screen makes it rather hard to recognise the waveform though.

For the rest, I can only refer you to the code, it isn't hard to follow even if you only have some experience with an Arduino. In fact, you could make the same thing with an Arduino, although I doubt that it would work as fast a "blue pill". The STM32F103C8 is a 32bit microcontroller running at 72 MHz, it has two SPI peripherals and a very fast ADC.

Step 5: Main.h


#ifndef __MAIN_H__
#define __MAIN_H__
#include "stm32f1xx_ll_adc.h"
#include "stm32f1xx_ll_rcc.h"
#include "stm32f1xx_ll_bus.h"
#include "stm32f1xx_ll_system.h"
#include "stm32f1xx_ll_exti.h"
#include "stm32f1xx_ll_cortex.h"
#include "stm32f1xx_ll_utils.h"
#include "stm32f1xx_ll_pwr.h"
#include "stm32f1xx_ll_dma.h"
#include "stm32f1xx_ll_spi.h"
#include "stm32f1xx_ll_tim.h"
#include "stm32f1xx.h"
#include "stm32f1xx_ll_gpio.h"
#ifndef NVIC_PRIORITYGROUP_0
#define NVIC_PRIORITYGROUP_0         ((uint32_t)0x00000007)
#define NVIC_PRIORITYGROUP_1         ((uint32_t)0x00000006)
#define NVIC_PRIORITYGROUP_2         ((uint32_t)0x00000005)
#define NVIC_PRIORITYGROUP_3         ((uint32_t)0x00000004)
#define NVIC_PRIORITYGROUP_4         ((uint32_t)0x00000003)
#endif
#ifdef __cplusplus
 extern "C" {
#endif
void _Error_Handler(char *, int);
#define Error_Handler() _Error_Handler(__FILE__, __LINE__)
#ifdef __cplusplus
}
#endif
#endif

Step 6: Main.c

#include "main.h"
static void LL_Init(void); void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_ADC1_Init(void); static void MX_SPI1_Init(void); static void MX_SPI2_Init(void); static void MX_TIM3_Init(void); static void MX_TIM4_Init(void);
uint16_t SPI1_send64(uint16_t data3, uint16_t data2, uint16_t data1, uint16_t data0);
uint16_t SPI2_send64(uint16_t data3, uint16_t data2, uint16_t data1, uint16_t data0);
void MAX7219_1_init();
void MAX7219_2_init();
void erase_frame_buffer(void);
void fill_frame_buffer(void);
void display_frame_buffer(void);
void set_timebase(void);
uint8_t upper_display[4][8];        //vier bytes naast elkaar, acht onder elkaar
uint8_t lower_display[4][8];        //deze twee samen vormen de frame-buffer
uint8_t sample_buffer[32];            //buffer voor de resultaten van de ADC
int main(void)
{
    LL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();
    MX_SPI1_Init();
    MX_SPI2_Init();
    MX_TIM3_Init();
    MX_TIM4_Init();
    LL_SPI_Enable(SPI1);
    LL_SPI_Enable(SPI2);
    LL_TIM_EnableCounter(TIM3);
    LL_TIM_EnableCounter(TIM4);
    LL_ADC_Enable(ADC1);
    LL_ADC_REG_StartConversionSWStart(ADC1);
    LL_ADC_EnableIT_EOS(ADC1);
    LL_mDelay(500);                                //MAX7219 needs some time after power on
    MAX7219_1_init();
    MAX7219_2_init();
    //LL_TIM_SetAutoReload(TIM3, 9);
    while (1)
    {
        set_timebase();
        erase_frame_buffer();
        fill_frame_buffer();
        display_frame_buffer();
    }
}
void erase_frame_buffer(void)
{
    int8_t x;
    int8_t y;
    for (x = 0; x < 4; x++)                    //kolom_bytes
    {
        for (y = 0; y < 8; y++)                //lijnen
        {
            upper_display[x][y] = 0;        //alle bitjes op nul
            lower_display[x][y] = 0;
        }
    }
}
void fill_frame_buffer(void)
{
    uint8_t y = 0;                            //voltage
    uint8_t tijd = 0;                        //tijd
    uint8_t display_byte;                    //steeds 8 bits naast elkaar en dat 4 maal op een lijn
    uint8_t display_bit;
    for (tijd = 0; tijd < 32; tijd++)
    {
        display_byte = tijd / 8;
        display_bit = 7 - (tijd % 8);
        y = sample_buffer[tijd];
        if (y > 7)                            //in upper display schrijven
        {
            upper_display[display_byte][15-y] |= (1 << display_bit);
        }
        else                                //in lower display schrijven
        {
            lower_display[display_byte][7-y] |= (1 << display_bit);
        }
    }
}
void display_frame_buffer(void)
{
    uint8_t y;                                //acht lijnen boven elkaar (per display)
    uint16_t yl;                            //lijnnummer voor de MAX7219
    for (y = 0; y < 8; y++)
    {
        yl = (y+1) << 8;                    //MAX7219 heeft lijnnummer in de upper 8 bits van 16 bits woord
        SPI2_send64( (yl | upper_display[0][y]),  (yl | upper_display[1][y]),  (yl | upper_display[2][y]),  (yl | upper_display[3][y]) );
        SPI1_send64( (yl | lower_display[0][y]),  (yl | lower_display[1][y]),  (yl | lower_display[2][y]),  (yl | lower_display[3][y]) );
    }
}
void set_timebase(void)
{
    uint8_t timebase_knop;
    timebase_knop = LL_TIM_GetCounter(TIM4) / 2;
    switch (timebase_knop)
    {
    case 0:
        LL_TIM_SetAutoReload(TIM3, 1999);
        break;
    case 1:
        LL_TIM_SetAutoReload(TIM3, 999);
        break;
    case 2:
        LL_TIM_SetAutoReload(TIM3, 499);
        break;
    case 3:
        LL_TIM_SetAutoReload(TIM3, 199);
        break;
    case 4:
        LL_TIM_SetAutoReload(TIM3, 99);
        break;
    case 5:
        LL_TIM_SetAutoReload(TIM3, 49);
        break;
    case 6:
        LL_TIM_SetAutoReload(TIM3, 19);
        break;
    case 7:
        LL_TIM_SetAutoReload(TIM3, 9);
        break;
    case 8:
        LL_TIM_SetAutoReload(TIM3, 4);
        break;
    case 9:
        LL_TIM_SetAutoReload(TIM3, 1);
        break;
    default:
        LL_TIM_SetAutoReload(TIM3, 99);
        break;
    }
}
void MAX7219_1_init()
{
    SPI1_send64(0x0000, 0x0000, 0x0000, 0x0000);    //nop
    SPI1_send64(0x0C00, 0x0C00, 0x0C00, 0x0C00);    //shutdown on
    SPI1_send64(0x0000, 0x0000, 0x0000, 0x0000);    //nop
    SPI1_send64(0x0F00, 0x0F00, 0x0F00, 0x0F00);    //testmode off
    SPI1_send64(0x0C01, 0x0C01, 0x0C01, 0x0C01);    //shutdown off, normal operation
    SPI1_send64(0x0900, 0x0900, 0x0900, 0x0900);    //no 7seg decode, 64 pixels
    SPI1_send64(0x0A07, 0x0A07, 0x0A07, 0x0A07);    //intensity 50%
    SPI1_send64(0x0B07, 0x0B07, 0x0B07, 0x0B07);    //all rows on
}
void MAX7219_2_init()
{
    SPI2_send64(0x0000, 0x0000, 0x0000, 0x0000);    //nop
    SPI2_send64(0x0C00, 0x0C00, 0x0C00, 0x0C00);    //shutdown on
    SPI2_send64(0x0000, 0x0000, 0x0000, 0x0000);    //nop
    SPI2_send64(0x0F00, 0x0F00, 0x0F00, 0x0F00);    //testmode off
    SPI2_send64(0x0C01, 0x0C01, 0x0C01, 0x0C01);    //shutdown off, normal operation
    SPI2_send64(0x0900, 0x0900, 0x0900, 0x0900);    //no 7seg decode, 64 pixels
    SPI2_send64(0x0A07, 0x0A07, 0x0A07, 0x0A07);    //intensity 50%
    SPI2_send64(0x0B07, 0x0B07, 0x0B07, 0x0B07);    //all rows on
}
uint16_t SPI1_send64(uint16_t data3, uint16_t data2, uint16_t data1, uint16_t data0)
{
    LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_4);
    LL_SPI_TransmitData16(SPI1, data3);
    while (LL_SPI_IsActiveFlag_TXE(SPI1) == 0) {}
    LL_SPI_TransmitData16(SPI1, data2);
    while (LL_SPI_IsActiveFlag_TXE(SPI1) == 0) {}
    LL_SPI_TransmitData16(SPI1, data1);
    while (LL_SPI_IsActiveFlag_TXE(SPI1) == 0) {}
    LL_SPI_TransmitData16(SPI1, data0);
    while (LL_SPI_IsActiveFlag_BSY(SPI1) == 1) {}
    LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_4);
    return LL_SPI_ReceiveData16(SPI1);
}
uint16_t SPI2_send64(uint16_t data3, uint16_t data2, uint16_t data1, uint16_t data0)
{
    LL_GPIO_ResetOutputPin(GPIOB, LL_GPIO_PIN_12);
    LL_SPI_TransmitData16(SPI2, data3);
    while (LL_SPI_IsActiveFlag_TXE(SPI2) == 0) {}
    LL_SPI_TransmitData16(SPI2, data2);
    while (LL_SPI_IsActiveFlag_TXE(SPI2) == 0) {}
    LL_SPI_TransmitData16(SPI2, data1);
    while (LL_SPI_IsActiveFlag_TXE(SPI2) == 0) {}
    LL_SPI_TransmitData16(SPI2, data0);
    while (LL_SPI_IsActiveFlag_BSY(SPI2) == 1) {}
    LL_GPIO_SetOutputPin(GPIOB, LL_GPIO_PIN_12);
    return LL_SPI_ReceiveData16(SPI2);
}
void ADC1_2_IRQHandler(void)
{
    static uint8_t sample_counter;
    uint8_t trigger;
    static uint8_t previous_trigger;
    if (LL_ADC_IsActiveFlag_EOS(ADC1) != RESET)
    {
        if (sample_counter < 32)
        {
            sample_buffer[sample_counter] = LL_ADC_REG_ReadConversionData32(ADC1) / 256;
            if (sample_counter < 32)  sample_counter++;
            else sample_counter = 0;
        }
        else
        {
            trigger = LL_ADC_REG_ReadConversionData32(ADC1) / 256;
            if ( (trigger == 7)  && ( previous_trigger < trigger) )            //gaat niet helemaal goed bij blokgolven...
            {
                sample_counter = 0;
            }
            previous_trigger = trigger;
        }
        LL_GPIO_TogglePin(GPIOC, LL_GPIO_PIN_13);
        LL_ADC_ClearFlag_EOS(ADC1);
    }
}
static void LL_Init(void)
{
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_AFIO);
    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
    NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
    NVIC_SetPriority(MemoryManagement_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    NVIC_SetPriority(BusFault_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    NVIC_SetPriority(UsageFault_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    NVIC_SetPriority(SVCall_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    NVIC_SetPriority(DebugMonitor_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    NVIC_SetPriority(PendSV_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    LL_GPIO_AF_Remap_SWJ_NOJTAG();
}
void SystemClock_Config(void)
{
    LL_FLASH_SetLatency(LL_FLASH_LATENCY_2);
    if(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_2) Error_Handler();
    LL_RCC_HSE_Enable();
    while(LL_RCC_HSE_IsReady() != 1);
    LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSE_DIV_1, LL_RCC_PLL_MUL_9);
    LL_RCC_PLL_Enable();
    while(LL_RCC_PLL_IsReady() != 1);
    LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
    LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_2);
    LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1);
    LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);
    while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL);
    LL_Init1msTick(72000000);
    LL_SYSTICK_SetClkSource(LL_SYSTICK_CLKSOURCE_HCLK);
    LL_SetSystemCoreClock(72000000);
    LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSRC_PCLK2_DIV_6);
    NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
}
static void MX_ADC1_Init(void)
{
    LL_ADC_InitTypeDef ADC_InitStruct;
    LL_ADC_CommonInitTypeDef ADC_CommonInitStruct;
    LL_ADC_REG_InitTypeDef ADC_REG_InitStruct;
    LL_GPIO_InitTypeDef GPIO_InitStruct;
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC1);
    GPIO_InitStruct.Pin = LL_GPIO_PIN_0;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    NVIC_SetPriority(ADC1_2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    NVIC_EnableIRQ(ADC1_2_IRQn);
    ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
    ADC_InitStruct.SequencersScanMode = LL_ADC_SEQ_SCAN_DISABLE;
    LL_ADC_Init(ADC1, &ADC_InitStruct);
    ADC_CommonInitStruct.Multimode = LL_ADC_MULTI_INDEPENDENT;
    LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(ADC1), &ADC_CommonInitStruct);
    ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_EXT_TIM3_TRGO;
    ADC_REG_InitStruct.SequencerLength = 1;
    ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
    ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE;
    ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_NONE;
    LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
    LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_0, LL_ADC_SAMPLINGTIME_41CYCLES_5);
}
static void MX_SPI1_Init(void)
{
    LL_SPI_InitTypeDef SPI_InitStruct;
    LL_GPIO_InitTypeDef GPIO_InitStruct;
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);
    GPIO_InitStruct.Pin = LL_GPIO_PIN_5|LL_GPIO_PIN_7;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
    GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
    LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    //NVIC_SetPriority(SPI1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    //NVIC_EnableIRQ(SPI1_IRQn);
    SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
    SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
    SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_16BIT;
    SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW;
    SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE;
    SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
    SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8;
    SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;
    SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
    SPI_InitStruct.CRCPoly = 10;
    LL_SPI_Init(SPI1, &SPI_InitStruct);
}
static void MX_SPI2_Init(void)
{
    LL_SPI_InitTypeDef SPI_InitStruct;
    LL_GPIO_InitTypeDef GPIO_InitStruct;
    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI2);
    GPIO_InitStruct.Pin = LL_GPIO_PIN_13|LL_GPIO_PIN_15;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
    GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
    LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    //NVIC_SetPriority(SPI2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
    //NVIC_EnableIRQ(SPI2_IRQn);
    SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
    SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
    SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_16BIT;
    SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW;
    SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE;
    SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
    SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV4;
    SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;
    SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
    SPI_InitStruct.CRCPoly = 10;
    LL_SPI_Init(SPI2, &SPI_InitStruct);
}
static void MX_TIM3_Init(void)
{
    LL_TIM_InitTypeDef TIM_InitStruct;
    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM3);
    TIM_InitStruct.Prescaler = 229;
    TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
    TIM_InitStruct.Autoreload = 9;
    TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
    LL_TIM_Init(TIM3, &TIM_InitStruct);
    LL_TIM_DisableARRPreload(TIM3);
    LL_TIM_SetClockSource(TIM3, LL_TIM_CLOCKSOURCE_INTERNAL);
    LL_TIM_SetTriggerOutput(TIM3, LL_TIM_TRGO_UPDATE);
    LL_TIM_EnableMasterSlaveMode(TIM3);
}
static void MX_TIM4_Init(void)
{
    LL_TIM_InitTypeDef TIM_InitStruct;
    LL_GPIO_InitTypeDef GPIO_InitStruct;
    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM4);
    GPIO_InitStruct.Pin = LL_GPIO_PIN_6|LL_GPIO_PIN_7;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING;
    LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    LL_TIM_SetEncoderMode(TIM4, LL_TIM_ENCODERMODE_X2_TI1);
    LL_TIM_IC_SetActiveInput(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI);
    LL_TIM_IC_SetPrescaler(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1);
    LL_TIM_IC_SetFilter(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1);
    LL_TIM_IC_SetPolarity(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING);
    LL_TIM_IC_SetActiveInput(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI);
    LL_TIM_IC_SetPrescaler(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1);
    LL_TIM_IC_SetFilter(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV1);
    LL_TIM_IC_SetPolarity(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING);
    TIM_InitStruct.Prescaler = 0;
    TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
    TIM_InitStruct.Autoreload = 19;
    TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
    LL_TIM_Init(TIM4, &TIM_InitStruct);
    LL_TIM_DisableARRPreload(TIM4);
    LL_TIM_SetTriggerOutput(TIM4, LL_TIM_TRGO_RESET);
    LL_TIM_DisableMasterSlaveMode(TIM4);
}
static void MX_GPIO_Init(void)
{
    LL_GPIO_InitTypeDef GPIO_InitStruct;
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOC);
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOD);
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOB);
    LL_GPIO_SetOutputPin(GPIOC, LL_GPIO_PIN_13);
    LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_4);
    LL_GPIO_SetOutputPin(GPIOB, LL_GPIO_PIN_12);
    GPIO_InitStruct.Pin = LL_GPIO_PIN_13;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
    GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
    LL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    GPIO_InitStruct.Pin = LL_GPIO_PIN_4;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
    GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
    LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    GPIO_InitStruct.Pin = LL_GPIO_PIN_12;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
    GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
    LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void _Error_Handler(char *file, int line)
{
    while(1)
    {
    }
}
#ifdef  USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{ 
}
#endif