Open dzanis opened 6 months 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.
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.
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
}
}
}
coooool
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:
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.