diff --git a/examples/i2c_slave/Makefile b/examples/i2c_slave/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..49b91ce536597c14d0d8273010cba332c68baef1 --- /dev/null +++ b/examples/i2c_slave/Makefile @@ -0,0 +1,9 @@ +all : flash + +TARGET:=i2c_slave + +include ../../ch32v003fun/ch32v003fun.mk + +flash : cv_flash +clean : cv_clean + diff --git a/examples/i2c_slave/README.md b/examples/i2c_slave/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cfdbd1b46fb2f58e9bea2c7b4ce8d2aa0e4ee367 --- /dev/null +++ b/examples/i2c_slave/README.md @@ -0,0 +1,9 @@ +# I2C peripheral in slave mode + +This library and example show how to use the I2C peripheral in slave mode. + +The library uses a one-byte address, allowing for up to 256 registers to be defined. + +The first byte written to the device within a transaction determines the offset for following reads and writes, emulating a simple EEPROM. + +The example will turn on a LED connected to PD0 when the LSB of register 0 is set to 1 and off when it's set to 0. diff --git a/examples/i2c_slave/i2c_slave.c b/examples/i2c_slave/i2c_slave.c new file mode 100644 index 0000000000000000000000000000000000000000..dbfaf80ec8448a67b4f5ab3e5ece38b1bb6c02d3 --- /dev/null +++ b/examples/i2c_slave/i2c_slave.c @@ -0,0 +1,30 @@ +#define SYSTEM_CORE_CLOCK 48000000 + +#include "ch32v003fun.h" +#include "i2c_slave.h" +#include <stdio.h> + +// The I2C slave library uses a one byte address so you can extend the size of this array up to 256 registers +// note that the register set is modified by interrupts, to prevent the compiler from accidently optimizing stuff +// away make sure to declare the register array volatile + +volatile uint8_t i2c_registers[32] = {0x00}; + +int main() { + SystemInit48HSI(); + SetupDebugPrintf(); + SetupI2CSlave(0x9, i2c_registers, sizeof(i2c_registers)); + + // Enable GPIOD and set pin 0 to output + RCC->APB2PCENR |= RCC_APB2Periph_GPIOD; + GPIOD->CFGLR &= ~(0xf<<(4*0)); + GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0); + + while (1) { + if (i2c_registers[0] & 1) { // Turn on LED (PD0) if bit 1 of register 0 is set + GPIOD-> BSHR |= 1 << 16; + } else { + GPIOD-> BSHR |= 1; + } + } +} diff --git a/examples/i2c_slave/i2c_slave.h b/examples/i2c_slave/i2c_slave.h new file mode 100644 index 0000000000000000000000000000000000000000..a488eaf8ea046d5ca80e395a2715650d9213dbeb --- /dev/null +++ b/examples/i2c_slave/i2c_slave.h @@ -0,0 +1,150 @@ +/* + * Single-File-Header for using the I2C peripheral in slave mode + * + * MIT License + * + * Copyright (c) 2023 Renze Nicolai + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __I2C_SLAVE_H +#define __I2C_SLAVE_H + +#include <stdint.h> + +#define APB_CLOCK SYSTEM_CORE_CLOCK + +struct _i2c_slave_state { + uint8_t first_write; + uint8_t offset; + uint8_t position; + volatile uint8_t* volatile registers; + uint8_t size; +} i2c_slave_state; + +void SetupI2CSlave(uint8_t address, volatile uint8_t* registers, uint8_t size) { + i2c_slave_state.first_write = 1; + i2c_slave_state.offset = 0; + i2c_slave_state.position = 0; + i2c_slave_state.registers = registers; + i2c_slave_state.size = size; + + // Enable GPIOC and I2C + RCC->APB2PCENR |= RCC_APB2Periph_GPIOC; + RCC->APB1PCENR |= RCC_APB1Periph_I2C1; + + // PC1 is SDA, 10MHz Output, alt func, open-drain + GPIOC->CFGLR &= ~(0xf<<(4*1)); + GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*1); + + // PC2 is SCL, 10MHz Output, alt func, open-drain + GPIOC->CFGLR &= ~(0xf<<(4*2)); + GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*2); + + // Reset I2C1 to init all regs + RCC->APB1PRSTR |= RCC_APB1Periph_I2C1; + RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1; + + I2C1->CTLR1 |= I2C_CTLR1_SWRST; + I2C1->CTLR1 &= ~I2C_CTLR1_SWRST; + + // Set module clock frequency + uint32_t prerate = 2000000; // I2C Logic clock rate, must be higher than the bus clock rate + I2C1->CTLR2 |= (APB_CLOCK/prerate) & I2C_CTLR2_FREQ; + + // Enable interrupts + I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN; + I2C1->CTLR2 |= I2C_CTLR2_ITEVTEN; // Event interrupt + I2C1->CTLR2 |= I2C_CTLR2_ITERREN; // Error interrupt + + NVIC_EnableIRQ(I2C1_EV_IRQn); // Event interrupt + NVIC_SetPriority(I2C1_EV_IRQn, 2 << 4); + NVIC_EnableIRQ(I2C1_ER_IRQn); // Error interrupt + + // Set clock configuration + uint32_t clockrate = 1000000; // I2C Bus clock rate, must be lower than the logic clock rate + I2C1->CKCFGR = ((APB_CLOCK/(3*clockrate))&I2C_CKCFGR_CCR) | I2C_CKCFGR_FS; // Fast mode 33% duty cycle + //I2C1->CKCFGR = ((APB_CLOCK/(25*clockrate))&I2C_CKCFGR_CCR) | I2C_CKCFGR_DUTY | I2C_CKCFGR_FS; // Fast mode 36% duty cycle + //I2C1->CKCFGR = (APB_CLOCK/(2*clockrate))&I2C_CKCFGR_CCR; // Standard mode good to 100kHz + + // Set I2C address + I2C1->OADDR1 = address << 1; + + // Enable I2C + I2C1->CTLR1 |= I2C_CTLR1_PE; + + // Acknowledge the first address match event when it happens + I2C1->CTLR1 |= I2C_CTLR1_ACK; +} + +void I2C1_EV_IRQHandler(void) __attribute__((interrupt)); +void I2C1_EV_IRQHandler(void) { + uint16_t STAR1, STAR2 __attribute__((unused)); + STAR1 = I2C1->STAR1; + STAR2 = I2C1->STAR2; + + I2C1->CTLR1 |= I2C_CTLR1_ACK; + + if (STAR1 & I2C_STAR1_ADDR) { // Start event + i2c_slave_state.first_write = 1; // Next write will be the offset + i2c_slave_state.position = i2c_slave_state.offset; // Reset position + } + + if (STAR1 & I2C_STAR1_RXNE) { // Write event + if (i2c_slave_state.first_write) { // First byte written, set the offset + i2c_slave_state.offset = I2C1->DATAR; + i2c_slave_state.position = i2c_slave_state.offset; + i2c_slave_state.first_write = 0; + } else { // Normal register write + if (i2c_slave_state.position < i2c_slave_state.size) { + i2c_slave_state.registers[i2c_slave_state.position] = I2C1->DATAR; + i2c_slave_state.position++; + } + } + } + + if (STAR1 & I2C_STAR1_TXE) { // Read event + if (i2c_slave_state.position < i2c_slave_state.size) { + I2C1->DATAR = i2c_slave_state.registers[i2c_slave_state.position]; + i2c_slave_state.position++; + } else { + I2C1->DATAR = 0x00; + } + } +} + +void I2C1_ER_IRQHandler(void) __attribute__((interrupt)); +void I2C1_ER_IRQHandler(void) { + uint16_t STAR1 = I2C1->STAR1; + + if (STAR1 & I2C_STAR1_BERR) { // Bus error + I2C1->STAR1 &= ~(I2C_STAR1_BERR); // Clear error + } + + if (STAR1 & I2C_STAR1_ARLO) { // Arbitration lost error + I2C1->STAR1 &= ~(I2C_STAR1_ARLO); // Clear error + } + + if (STAR1 & I2C_STAR1_AF) { // Acknowledge failure + I2C1->STAR1 &= ~(I2C_STAR1_AF); // Clear error + } +} + +#endif diff --git a/examples/tim2_pwm/.gdbinit b/examples/tim2_pwm/.gdbinit new file mode 100644 index 0000000000000000000000000000000000000000..427595ae0e4d2332e7e580facfd2c4327e8441c6 --- /dev/null +++ b/examples/tim2_pwm/.gdbinit @@ -0,0 +1,2 @@ +file tim1_pwm.elf +target extended-remote localhost:3333 diff --git a/examples/tim2_pwm/Makefile b/examples/tim2_pwm/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..7b3d0c031265e2b750553f45da8dfce649a99c68 --- /dev/null +++ b/examples/tim2_pwm/Makefile @@ -0,0 +1,11 @@ +all : flash + +TARGET:=tim2_pwm + +CFLAGS+=-DSTDOUT_UART + +include ../../ch32v003fun/ch32v003fun.mk + +flash : cv_flash +clean : cv_clean + diff --git a/examples/tim2_pwm/README.md b/examples/tim2_pwm/README.md new file mode 100644 index 0000000000000000000000000000000000000000..36c81a1f41d8414c953a7060f43aaeb94e3282ce --- /dev/null +++ b/examples/tim2_pwm/README.md @@ -0,0 +1,6 @@ +# Demonstration of PWM using Timer 2 +This example shows how to set up Timer 2 (the General Purpose Timer) to generate Pulse-Width Modulation (PWM) on two output pins. +PWM is frequently used to do variable brightness on LEDs or for digital-to-analog conversion when combined with a suitable lowpass filter. + +## Use +Connect GPIO pins PD04 and PD3 to LEDs (with appropriate current limiting) and observe that they fade at the same speed, but phase-rotatd by 180°. diff --git a/examples/tim2_pwm/debug.sh b/examples/tim2_pwm/debug.sh new file mode 100755 index 0000000000000000000000000000000000000000..bb05a9465959cb153fad95bba4b2175667830528 --- /dev/null +++ b/examples/tim2_pwm/debug.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# before running this you should start OOCD server +#../../../MRS_Toolchain_Linux_x64_V1.70/OpenOCD/bin/openocd -f ../../../MRS_Toolchain_Linux_x64_V1.70/OpenOCD/bin/wch-riscv.cfg + +../../../MRS_Toolchain_Linux_x64_V1.70/RISC-V\ Embedded\ GCC/bin/riscv-none-embed-gdb diff --git a/examples/tim2_pwm/tim2_pwm.c b/examples/tim2_pwm/tim2_pwm.c new file mode 100644 index 0000000000000000000000000000000000000000..37e949d727dfd01883b1271ff9bb9e41bac54b36 --- /dev/null +++ b/examples/tim2_pwm/tim2_pwm.c @@ -0,0 +1,131 @@ +/* + * Example for using Advanced Control Timer (TIM2) for PWM generation + * 03-28-2023 E. Brombaugh + * 05-29-2023 recallmenot adapted from Timer1 to Timer2 + */ + +/* +Timer 2 pin mappings by AFIO->PCFR1 + 00 (default) + D4 T2CH1ETR + D3 T2CH2 + C0 T2CH3 + D7 T2CH4 + 01 + C5 T2CH1ETR_ + C2 T2CH2_ + D2 T2CH3_ + C1 T2CH4_ + 10 + C1 T2CH1ETR_ + D3 T2CH2 + C0 T2CH3 + D7 T2CH4 + 11 + C1 T2CH1ETR_ + C7 T2CH2_ + D6 T2CH3_ + D5 T2CH4_ +*/ + +// Could be defined here, or in the processor defines. +#define SYSTEM_CORE_CLOCK 48000000 +#define APB_CLOCK SYSTEM_CORE_CLOCK + +#include "ch32v003fun.h" +#include <stdio.h> + +/* + * initialize TIM2 for PWM + */ +void t2pwm_init( void ) +{ + // Enable GPIOD and TIM2 + RCC->APB2PCENR |= RCC_APB2Periph_GPIOD; + RCC->APB1PCENR |= RCC_APB1Periph_TIM2; + + // PD4 is T2CH1, 10MHz Output alt func, push-pull + GPIOD->CFGLR &= ~(0xf<<(4*4)); + GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*4); + + // PD3 is T2CH2, 10MHz Output alt func, push-pull + GPIOD->CFGLR &= ~(0xf<<(4*3)); + GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*3); + + // Reset TIM2 to init all regs + RCC->APB1PRSTR |= RCC_APB1Periph_TIM2; + RCC->APB1PRSTR &= ~RCC_APB1Periph_TIM2; + + // SMCFGR: default clk input is CK_INT + // set TIM2 clock prescaler divider + TIM2->PSC = 0x0000; + // set PWM total cycle width + TIM2->ATRLR = 255; + + // for channel 1 and 2, let CCxS stay 00 (output), set OCxM to 110 (PWM I) + // enabling preload causes the new pulse width in compare capture register only to come into effect when UG bit in SWEVGR is set (= initiate update) (auto-clears) + TIM2->CHCTLR1 |= TIM_OC1M_2 | TIM_OC1M_1 | TIM_OC1PE | TIM_OC2M_2 | TIM_OC2M_1 | TIM_OC2PE; + + // CTLR1: default is up, events generated, edge align + // enable auto-reload of preload + TIM2->CTLR1 |= TIM_ARPE; + + // Enable CH1 output, positive pol + TIM2->CCER |= TIM_CC1E | TIM_CC1P; + // Enable CH2 output, positive pol + TIM2->CCER |= TIM_CC2E | TIM_CC2P; + + // initialize counter + TIM2->SWEVGR |= TIM_UG; + + // Enable TIM2 + TIM2->CTLR1 |= TIM_CEN; +} + + +/* + * set timer channel PW + */ +void t2pwm_setpw(uint8_t chl, uint16_t width) +{ + switch(chl&3) + { + case 0: TIM2->CH1CVR = width; break; + case 1: TIM2->CH2CVR = width; break; + case 2: TIM2->CH3CVR = width; break; + case 3: TIM2->CH4CVR = width; break; + } + TIM2->SWEVGR |= TIM_UG; // load new value in compare capture register +} + + + +/* + * entry + */ +int main() +{ + uint32_t count = 0; + + SystemInit48HSI(); + + // start serial @ default 115200bps + SetupUART( UART_BRR ); + Delay_Ms( 100 ); + printf("\r\r\n\ntim2_pwm example\n\r"); + + // init TIM2 for PWM + printf("initializing tim2..."); + t2pwm_init(); + printf("done.\n\r"); + + printf("looping...\n\r"); + while(1) + { + t2pwm_setpw(0, count); // Chl 1 + t2pwm_setpw(1, (count + 128)&255); // Chl 2 180° out-of-phase + count++; + count &= 255; + Delay_Ms( 5 ); + } +}