wagiminator / MCU-Templates

Templates for bare-metal firmware development for some entry-level microcontrollers
Other
48 stars 6 forks source link

Missing defines #4

Open dzanis opened 1 month ago

dzanis commented 1 month ago

I used some examples from your repository (oled_gfx, i2c_dma, uart) But I needed code for ADC and timer, and it turned out that many defines were missing, for example for timer configuration:

void TIM2_Init(uint32_t frequency)
{
    RCC->APB1PCENR |= RCC_TIM2EN; // Enable clock for TIM2

    TIM2->CTLR1 |= TIM_CounterMode_Up | TIM_CKD_DIV1; // Set counter mode to up and no clock division
    TIM2->CTLR2 = TIM_MMS_1; // Use update event as trigger

    uint32_t prescaler = (SystemCoreClock / 1000000) - 1; // Calculate prescaler for 1 MHz
    TIM2->PSC = prescaler; // Set prescaler

    uint32_t period = (1000000 / frequency) - 1; // Calculate period for the given frequency
    TIM2->ATRLR = period; // Set period

    TIM2->SWEVGR = TIM_PSCReloadMode_Immediate; // Immediate load of the new prescaler value
    TIM2->INTFR = ~TIM_FLAG_Update; // Clear update flag
    TIM2->DMAINTENR |= TIM_IT_Update; // Enable update interrupt
    TIM2->CTLR1 |= TIM_CEN; // Enable TIM2
}

I'm not very familiar with these registers myself. At first I used openwch/ch32v003 and had no problems until I ran out of memory. Your development, on the other hand, turned out to be very compact. In general, due to the lack of these defines (TIM_CounterMode_Up, TIM_PSCReloadMode_Immediate, etc.), they have to be "patched" in somehow, which is not very elegant and creates confusion. I wanted to ask if you are going to expand your development, for example, there are not enough examples of ADC and timers?

I use Windows 11 and VSCode to compile ch32v003 using your development. The platformio.ini file helps with this.

; see https://docs.platformio.org/en/latest/projectconf/index.html
[platformio]
src_dir = .
include_dir = .

[env:genericCH32V003F4P6]
platform = https://github.com/Community-PIO-CH32V/platform-ch32v.git
; or ch32v003f4p6_evt_r0 or whatever, see platform link
board = genericCH32V003F4P6
monitor_speed = 115200

board_build.ldscript = linker/ch32v003.ld
build_flags = -flto -I/usr/include/newlib -I./include -lgcc -D F_CPU=48000000
dzanis commented 1 month ago

I tried to include ch32v003fun.h instead of ch32v003.h in system.h and got a bunch of compilation errors (for example, error: redefinition of 'SetVTFIRQ' or 'STK' undeclared). There is no compatibility, so this is not an option.

wagiminator commented 1 month ago

Hi, the templates work exclusively with direct register manipulation. All HAL extensions are deliberately not included, as they bloat the code immensely (but maybe the relevant libraries can be included). So, you may have to (or get to) manage without them. I will certainly program and upload new examples from time to time, depending on my spare time, mood, and inclination.

By the way, thanks for the tip about the platformio.ini file. I wanted to try compiling everything on Windows when I get the chance.

dzanis commented 1 month ago

This is what I did using your libraries. This program functions as an oscilloscope for CH32V003. It captures and displays signal waveforms over time, likely from some kind of input device, to analyze the signal behavior.

// ===================================================================================
// Libraries, Definitions and Macros
// ===================================================================================
#include <system.h>                               // system functions
#include <oled_gfx.h>                             // OLED graphics functions
#include <millis.h>
#include <uart.h>

#define SAMPLING_FREQ 200000 //sampling frequency 200KHz
#define ADC_BUF_SIZE (OLED_WIDTH * 2) // for stable drawing of the signal sine wave, use an ADC buffer twice the width of the display

/* Global Variable */
uint16_t adcBuf[ADC_BUF_SIZE];

long map(long x, long in_min, long in_max, long out_min, long out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

/*
This function finds the point in the buffer where the value crosses the given trigger level from below to above.
This allows for stabilizing the start of the sine wave drawing.
*/
uint8_t find_trigger_point(uint16_t* buffer, uint16_t size, uint16_t level)
{
    for (uint8_t i = 1; i < size; i++)
    {
        if (buffer[i-1] < level && buffer[i] >= level)
        {
            return i;
        }
    }
    return 0; // If the trigger point is not found, return 0
}

#define ADC_NbrOfChannel 1 // Number of channels to be converted (1 channel)
#define ADC_Rank 1 // Rank 1
#define ADC_Channel 2 // Second ADC channel (PC4)
#define ADC_ExternalTrigConv_T2_TRGO                   ((uint32_t)0x00060000)

/*
Configure the ADC in single conversion mode with trigger from TIM2
*/
void ADC_Function_Init(void)
{
    // Enable clocking for GPIOC and ADC1
    RCC->APB2PCENR |= (RCC_IOPCEN | RCC_ADC1EN);

    // Configure GPIOC port for analog input (PC4)
    GPIOC->CFGLR &= ~(0xF << (4 * 4));  // Set analog input mode

    ADC1->CTLR2 &= ~ADC_ADON;     // Disable ADC1 to change settings

    // Reset all ADC1 settings to default values
    RCC->APB2PRSTR |= RCC_ADC1EN;
    RCC->APB2PRSTR &= ~RCC_ADC1EN;

    // Set the ADC clock frequency (PCLK2 divided by 8)
    RCC->CFGR0 &= ~RCC_ADCPRE;
    RCC->CFGR0 |= RCC_ADCPRE_DIV8;

    // Configure ADC1
    ADC1->CTLR1 = 0x0;                            // Independent mode, no scan
    ADC1->CTLR2 = (ADC_ADON |                     // Enable ADC1
                   ADC_DMA |                      // Enable DMA for ADC1
                   ADC_EXTTRIG |                  // Enable external trigger
                   ADC_ExternalTrigConv_T2_TRGO); // External trigger from TIM2
    ADC1->RSQR1 = (ADC_NbrOfChannel-1) << 20;  // One channel in regular sequence
    ADC1->RSQR3 = (ADC_Channel<<(5*(ADC_Rank-1)));  // Channel 2, rank 1

    // Configure the sample time for channel 2 (57 cycles)
    // 0:7 => 3/9/15/30/43/57/73/241 cycles
    // Sample time 57 cycles for channel 2
    ADC1->SAMPTR2 = (5 << (3 * ADC_Channel));   // 5 => cycles offset, 3 => offset per channel

    // Reset and calibrate the ADC
    ADC1->CTLR2 |= ADC_RSTCAL;
    while (ADC1->CTLR2 & ADC_RSTCAL);  // Wait for reset calibration to complete

    ADC1->CTLR2 |= ADC_CAL;
    while (ADC1->CTLR2 & ADC_CAL);    // Wait for calibration to complete

    // Start the ADC (begin conversions)
    ADC1->CTLR2 |= ADC_SWSTART;
}

#define TIM_CounterMode_Up                 ((uint16_t)0x0000)
#define TIM_CKD_DIV1                       ((uint16_t)0x0000)
#define TIM_PSCReloadMode_Immediate        ((uint16_t)0x0001)
#define TIM_FLAG_Update                    ((uint16_t)0x0001)
#define TIM_IT_Update                      ((uint16_t)0x0001)

/*
Initialize TIM2 to generate the required ADC sampling frequency
*/
void TIM2_Init(uint32_t frequency)
{
    RCC->APB1PCENR |= RCC_TIM2EN; // Enable clocking for TIM2

    TIM2->CTLR1 |= TIM_CounterMode_Up | TIM_CKD_DIV1; // Set count-up mode and no clock division
    TIM2->CTLR2 = TIM_MMS_1;                          // Use update event as trigger
    // Calculate the prescaler to get a 1 MHz frequency
    uint32_t prescaler = (F_CPU / 1000000) - 1;
    TIM2->PSC = prescaler;
    // Calculate the period for the given frequency
    uint32_t period = (1000000 / frequency) - 1;
    TIM2->ATRLR = period;
    TIM2->SWEVGR = TIM_PSCReloadMode_Immediate; // Immediate loading of the new prescaler value
    TIM2->INTFR = ~TIM_FLAG_Update;             // Reset the update flag
    TIM2->DMAINTENR |= TIM_IT_Update;           // Enable update interrupt
    TIM2->CTLR1 |= TIM_CEN;                     // Enable TIM2
}

/*
Configure TIM1 to capture the input signal frequency on pin PD2
*/
void TIM1_Capture_Init(void)
{
    // Enable clocking for GPIOD and TIM1
    RCC->APB2PCENR |= RCC_IOPDEN;
    RCC->APB2PCENR |= RCC_TIM1EN;

    // Initialize GPIO for signal capture (PD2)
    GPIOD->CFGLR &= ~(0xF << (2 * 4)); // Clear configuration for pin PD2
    GPIOD->CFGLR |= (0x4 << (2 * 4));  // Set input floating mode (IN_FLOATING)

    // Configure basic parameters of TIM1
    TIM1->ATRLR = 0xFFFF;                        // Maximum timer period
    TIM1->PSC = (F_CPU / 1000000) - 1; // Prescaler for 1µs resolution (48 MHz / (47 + 1) -> 1 MHz)

    // Configure input capture for TIM1
    TIM1->CHCTLR1 = 0;           // Clear channel 1 configuration register
    TIM1->CHCTLR1 |= TIM_CC1S_0; // Channel 1 as input
    TIM1->CCER = 0;              // Clear CCER register
    TIM1->CCER |= TIM_CC1E;      // Enable channel 1, capture on rising edge

    // Enable TIM1
    TIM1->CTLR1 |= TIM_CEN;

    // Enable interrupts for channel 1
    NVIC_EnableIRQ(TIM1_CC_IRQn);
    TIM1->DMAINTENR |= TIM_CC1IE;

    // Clear the capture interrupt flag for channel 1
    TIM1->INTFR = ~TIM_CC1IF;
}

volatile uint32_t capture1 = 0, capture2 = 0;
volatile uint8_t capture_flag = 0;

/*
Handle TIM1 capture interrupt, record two captures, and calculate the signal period
*/
void TIM1_CC_IRQHandler(void) __attribute__((interrupt));
void TIM1_CC_IRQHandler(void)
{
    if (TIM1->INTFR & TIM_CC1IF)
    {
        if (capture_flag == 0)
        {
            capture1 = TIM1->CH1CVR;
            capture_flag = 1;
        }
        else if (capture_flag == 1)
        {
            capture2 = TIM1->CH1CVR;
            capture_flag = 2;
        }
        TIM1->INTFR = ~TIM_CC1IF;
    }
}

/*
Calculate the frequency based on the signal period.
*/
uint32_t calculate_frequency(void)
{
    if (capture_flag == 2)
    {
        uint32_t period = (capture2 > capture1) ? (capture2 - capture1) : (0xFFFF - capture1 + capture2 + 1);
        uint32_t frequency = 1000000 / period;
        capture_flag = 0;
        return frequency;
    }
    return 0;
}

/*
Configure DMA, convenient to set channel and other parameters
*/
void DMA_Tx_Init(DMA_Channel_TypeDef *DMA_CHx, uint32_t ppadr, uint32_t memadr, uint16_t bufsize)
{
    RCC->AHBPCENR |= RCC_DMA1EN; // Enable clocking for DMA1
    DMA_CHx->CFGR &= ~DMA_CFGR1_EN; // Stop the selected DMA channel before configuration
    DMA_CHx->PADDR = ppadr; // Set the peripheral base address
    DMA_CHx->MADDR = memadr; // Set the memory base address (data buffer)
    DMA_CHx->CNTR = bufsize; // Specify the buffer size

    // Configure the CFGR configuration register
    DMA_CHx->CFGR = DMA_CFGR1_MINC            // Enable memory address increment
                  | DMA_CFGR1_TCIE            // Enable transfer complete interrupt
                  | DMA_CFGR1_CIRC            // Circular mode (if needed)
                  | DMA_CFGR1_PL_1;           // High priority transfer

    // Set peripheral and memory data size to 16 bits
    DMA_CHx->CFGR |= DMA_CFGR1_PSIZE_0 | DMA_CFGR1_MSIZE_0; // 16-bit Peripheral size (PSIZE = 01), 16-bit Memory size (MSIZE = 01)

    // Clear the transfer complete interrupt flag for the selected channel
    DMA1->INTFCR = DMA_CGIF1 << ((uint32_t)(DMA_CHx - DMA1_Channel1) / 0x14);

    // Enable the selected DMA channel interrupt in the NVIC
    NVIC_EnableIRQ(DMA1_Channel1_IRQn + ((uint32_t)(DMA_CHx - DMA1_Channel1) / 0x14));

    // Enable the selected DMA channel
    DMA_CHx->CFGR |= DMA_CFGR1_EN;
}

volatile uint8_t dma_complete_flag = 0;

/* DMA interrupt */
void DMA1_Channel1_IRQHandler(void) __attribute__((interrupt));
void DMA1_Channel1_IRQHandler(void)
{
    if (DMA1->INTFR & DMA_TCIF1)
    {
        DMA1->INTFCR = DMA_CGIF1; // Clear the interrupt flag
        TIM2->CTLR1 &= ~TIM_CEN;  // Disable TIM2
        dma_complete_flag = 1; // Set the DMA complete flag
    }
}

int main(void)
{
    UART_init();
    STK_init();
    MIL_init();
    OLED_init();
    OLED_home(0, 0);
    OLED_clear();
    OLED_print(0, 0, "oscill", 1, 3);
    OLED_refresh();
    DLY_ms(1000);
    TIM1_Capture_Init(); // Initialize TIM1 to capture the input signal frequency
    TIM2_Init(SAMPLING_FREQ); // Initialize TIM2 with the required sampling frequency
    ADC_Function_Init();
    DMA_Tx_Init(DMA1_Channel1, (uint32_t)&ADC1->RDATAR, (uint32_t)adcBuf, ADC_BUF_SIZE);

    uint32_t signal_frequency = 0; // input signal frequency
    uint32_t ms_counter  = 0; // millis counter

    while (1)
    {
        if (dma_complete_flag)
        {
            if (ms_counter < MIL_read())
            {
                ms_counter += 1000;
                signal_frequency = calculate_frequency();
                UART_printf("signal_frequency: %u\n", signal_frequency);
            }

            // Use the trigger function to find the starting point for drawing to stabilize the image
            int start_pos = find_trigger_point(adcBuf, OLED_WIDTH, 1023 / 2);
            // Scale the height to fit the display height
            for (int i = 0; i < OLED_WIDTH; i++)
            {
                adcBuf[i] = map(adcBuf[start_pos++], 0, 1023, OLED_HEIGHT - 1, 0);
            }

            OLED_clear();
            for (uint8_t x = 1; x < OLED_WIDTH; ++x)
            {
                OLED_drawLine(x - 1, adcBuf[x - 1], x, adcBuf[x], 1);
            }
            OLED_cursor(0,0, 1,1);
            OLED_printf("%dHz", signal_frequency );
            OLED_refresh();
            DLY_ms(20); // without this delay, drawing artifacts were noticed
            dma_complete_flag = 0; // Reset the flag
            TIM2->CTLR1 |= TIM_CEN; // start TIM2 to continue ADC sampling
        }
    }
}
wagiminator commented 1 month ago

coooool