Skip to content

Commit 58467af

Browse files
authored
ch32v: Add get_freqs, USART hal (ZigEmbeddedGroup#762)
1 parent edc591d commit 58467af

File tree

9 files changed

+682
-3
lines changed

9 files changed

+682
-3
lines changed

examples/wch/ch32v/build.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub fn build(b: *std.Build) void {
3232
.{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_systick_ch32v203", .file = "src/blinky_systick.zig" },
3333
.{ .target = mb.ports.ch32v.boards.ch32v203.suzuduino_uno_v1b, .name = "suzuduino_blinky", .file = "src/board_blinky.zig" },
3434
.{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_ws2812", .file = "src/ws2812.zig" },
35+
.{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_uart_log", .file = "src/uart_log.zig" },
3536

3637
// CH32V30x
3738
.{ .target = mb.ports.ch32v.chips.ch32v303xb, .name = "empty_ch32v303", .file = "src/empty.zig" },
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
const std = @import("std");
2+
const microzig = @import("microzig");
3+
const hal = microzig.hal;
4+
const time = hal.time;
5+
const gpio = hal.gpio;
6+
7+
const RCC = microzig.chip.peripherals.RCC;
8+
const AFIO = microzig.chip.peripherals.AFIO;
9+
10+
const usart = hal.usart.instance.USART2;
11+
const usart_tx_pin = gpio.Pin.init(0, 2); // PA2
12+
13+
pub const microzig_options = microzig.Options{
14+
.log_level = .debug,
15+
.logFn = hal.usart.log,
16+
};
17+
18+
pub fn main() !void {
19+
// Board brings up clocks and time
20+
microzig.board.init();
21+
22+
// Enable peripheral clocks for USART2 and GPIOA
23+
RCC.APB2PCENR.modify(.{
24+
.IOPAEN = 1, // Enable GPIOA clock
25+
.AFIOEN = 1, // Enable AFIO clock
26+
});
27+
RCC.APB1PCENR.modify(.{
28+
.USART2EN = 1, // Enable USART2 clock
29+
});
30+
31+
// Ensure USART2 is NOT remapped (default PA2/PA3, not PD5/PD6)
32+
AFIO.PCFR1.modify(.{ .USART2_RM = 0 });
33+
34+
// Configure PA2 as alternate function push-pull for USART2 TX
35+
usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz);
36+
37+
// Initialize USART2 at 115200 baud
38+
usart.apply(.{ .baud_rate = 115200 });
39+
40+
hal.usart.init_logger(usart);
41+
42+
var i: u32 = 0;
43+
while (true) : (i += 1) {
44+
std.log.info("what {}", .{i});
45+
time.sleep_ms(1000);
46+
}
47+
}

port/wch/ch32v/src/hals/ch32v103.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub const pins = @import("pins.zig");
33
pub const gpio = @import("gpio.zig");
44
pub const clocks = @import("clocks.zig");
55
pub const time = @import("time.zig");
6+
pub const usart = @import("usart.zig");
67

78
/// Initialize HAL subsystems used by default
89
/// CH32V103: set clock to 48 MHz via HSI PLL; SysTick driver differs on 103, so time is not enabled here.

port/wch/ch32v/src/hals/ch32v20x.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub const pins = @import("pins.zig");
44
pub const gpio = @import("gpio.zig");
55
pub const clocks = @import("clocks.zig");
66
pub const time = @import("time.zig");
7+
pub const usart = @import("usart.zig");
78

89
pub const default_interrupts: microzig.cpu.InterruptOptions = .{
910
// Default TIM2 handler provided by the HAL for 1ms timekeeping
@@ -18,4 +19,5 @@ test "hal tests" {
1819
_ = clocks;
1920
_ = gpio;
2021
_ = time;
22+
_ = usart;
2123
}

port/wch/ch32v/src/hals/ch32v30x.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub const pins = @import("pins.zig");
33
pub const gpio = @import("gpio.zig");
44
pub const clocks = @import("clocks.zig");
55
pub const time = @import("time.zig");
6+
pub const usart = @import("usart.zig");
67

78
/// Default interrupt handlers provided by the HAL
89
pub const default_interrupts: microzig.cpu.InterruptOptions = .{

port/wch/ch32v/src/hals/clocks.zig

Lines changed: 182 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,85 @@
11
const microzig = @import("microzig");
22

3+
const RCC = microzig.chip.peripherals.RCC;
4+
const EXTEND = microzig.chip.peripherals.EXTEND;
5+
6+
const gpioBlock = enum {
7+
A,
8+
B,
9+
C,
10+
D,
11+
};
12+
13+
pub fn enable_gpio_clock(block: gpioBlock) void {
14+
// TODO: How do we know if we need to set the AFIOEN?
15+
switch (block) {
16+
.A => RCC.APB2PCENR.modify(.{
17+
.IOPAEN = 1,
18+
.AFIOEN = 1,
19+
}),
20+
.B => RCC.APB2PCENR.modify(.{
21+
.IOPBEN = 1,
22+
.AFIOEN = 1,
23+
}),
24+
.C => RCC.APB2PCENR.modify(.{
25+
.IOPCEN = 1,
26+
.AFIOEN = 1,
27+
}),
28+
.D => RCC.APB2PCENR.modify(.{
29+
.IOPDEN = 1,
30+
.AFIOEN = 1,
31+
}),
32+
}
33+
}
34+
35+
const peripheral = enum {
36+
// APB2 peripherals
37+
USART1,
38+
SPI1,
39+
ADC1,
40+
ADC2,
41+
TIM1,
42+
43+
// APB1 peripherals
44+
USART2,
45+
USART3,
46+
I2C1,
47+
I2C2,
48+
SPI2,
49+
TIM2,
50+
TIM3,
51+
TIM4,
52+
};
53+
54+
pub fn enable_peripheral_clock(p: peripheral) void {
55+
switch (p) {
56+
// APB2 peripherals (high-speed bus)
57+
.USART1 => RCC.APB2PCENR.modify(.{ .USART1EN = 1 }),
58+
.SPI1 => RCC.APB2PCENR.modify(.{ .SPI1EN = 1 }),
59+
.ADC1 => RCC.APB2PCENR.modify(.{ .ADC1EN = 1 }),
60+
.ADC2 => RCC.APB2PCENR.modify(.{ .ADC2EN = 1 }),
61+
.TIM1 => RCC.APB2PCENR.modify(.{ .TIM1EN = 1 }),
62+
63+
// APB1 peripherals (low-speed bus)
64+
.USART2 => RCC.APB1PCENR.modify(.{ .USART2EN = 1 }),
65+
.USART3 => RCC.APB1PCENR.modify(.{ .USART3EN = 1 }),
66+
.I2C1 => RCC.APB1PCENR.modify(.{ .I2C1EN = 1 }),
67+
.I2C2 => RCC.APB1PCENR.modify(.{ .I2C2EN = 1 }),
68+
.SPI2 => RCC.APB1PCENR.modify(.{ .SPI2EN = 1 }),
69+
.TIM2 => RCC.APB1PCENR.modify(.{ .TIM2EN = 1 }),
70+
.TIM3 => RCC.APB1PCENR.modify(.{ .TIM3EN = 1 }),
71+
.TIM4 => RCC.APB1PCENR.modify(.{ .TIM4EN = 1 }),
72+
}
73+
}
74+
75+
/// Enable AFIO (Alternate Function I/O) clock
76+
/// Required for pin remapping and external interrupt configuration
77+
pub fn enable_afio_clock() void {
78+
RCC.APB2PCENR.modify(.{ .AFIOEN = 1 });
79+
}
80+
381
/// Initialize system clock to 48 MHz using HSI
482
pub fn init_48mhz_hsi() void {
5-
const RCC = microzig.chip.peripherals.RCC;
6-
const EXTEND = microzig.chip.peripherals.EXTEND;
783
// Enable PLL HSI prescaler (HSIPRE bit)
884
EXTEND.EXTEND_CTR.modify(.{ .HSIPRE = 1 });
985

@@ -29,3 +105,107 @@ pub fn init_48mhz_hsi() void {
29105
// Wait for PLL to be used as system clock (SWS should be 2)
30106
while (RCC.CFGR0.read().SWS != 2) {}
31107
}
108+
109+
const ClockSpeeds = struct {
110+
sysclk: u32,
111+
hclk: u32,
112+
pclk1: u32,
113+
pclk2: u32,
114+
adcclk: u32,
115+
};
116+
117+
// Clock constants
118+
const HSI_VALUE: u32 = 8_000_000; // 8 MHz internal oscillator
119+
const HSE_VALUE: u32 = 8_000_000; // 8 MHz external oscillator (board-dependent)
120+
121+
// Prescaler lookup table for AHB/APB buses
122+
// Index into this table with the HPRE/PPRE bits, result is the shift amount
123+
const prescaler_table = [16]u8{ 0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9 };
124+
125+
// ADC prescaler lookup table (divisor values)
126+
const adc_prescaler_table = [4]u8{ 2, 4, 6, 8 };
127+
128+
/// Get the current clock frequencies by reading RCC registers
129+
/// Based on WCH's RCC_GetClocksFreq() implementation
130+
pub fn get_freqs() ClockSpeeds {
131+
var sysclk: u32 = 0;
132+
133+
// Determine system clock source from SWS (System Clock Switch Status)
134+
const sws = RCC.CFGR0.read().SWS;
135+
136+
switch (sws) {
137+
0 => {
138+
// HSI used as system clock
139+
sysclk = HSI_VALUE;
140+
},
141+
1 => {
142+
// HSE used as system clock
143+
sysclk = HSE_VALUE;
144+
},
145+
2 => {
146+
// PLL used as system clock
147+
const cfgr0 = RCC.CFGR0.read();
148+
const pllmul_bits = cfgr0.PLLMUL;
149+
const pllsrc = cfgr0.PLLSRC;
150+
151+
// PLL multiplication factor: PLLMUL bits + 2
152+
// Special case: if result is 17, it's actually 18
153+
var pllmul: u32 = @as(u32, pllmul_bits) + 2;
154+
if (pllmul == 17) pllmul = 18;
155+
156+
if (pllsrc == 0) {
157+
// PLL source is HSI
158+
const hsipre = EXTEND.EXTEND_CTR.read().HSIPRE;
159+
if (hsipre == 1) {
160+
// HSI not divided
161+
sysclk = HSI_VALUE * pllmul;
162+
} else {
163+
// HSI divided by 2
164+
sysclk = (HSI_VALUE >> 1) * pllmul;
165+
}
166+
} else {
167+
// PLL source is HSE
168+
const pllxtpre = cfgr0.PLLXTPRE;
169+
if (pllxtpre == 1) {
170+
// HSE divided by 2 before PLL
171+
sysclk = (HSE_VALUE >> 1) * pllmul;
172+
} else {
173+
// HSE not divided
174+
sysclk = HSE_VALUE * pllmul;
175+
}
176+
}
177+
},
178+
else => {
179+
// Default to HSI
180+
sysclk = HSI_VALUE;
181+
},
182+
}
183+
184+
// Calculate AHB clock (HCLK) from SYSCLK using HPRE prescaler
185+
const hpre = RCC.CFGR0.read().HPRE;
186+
const hpre_shift = prescaler_table[hpre];
187+
const hclk = sysclk >> @as(u5, @intCast(hpre_shift));
188+
189+
// Calculate APB1 clock (PCLK1) from HCLK using PPRE1 prescaler
190+
const ppre1 = RCC.CFGR0.read().PPRE1;
191+
const ppre1_shift = prescaler_table[ppre1];
192+
const pclk1 = hclk >> @as(u5, @intCast(ppre1_shift));
193+
194+
// Calculate APB2 clock (PCLK2) from HCLK using PPRE2 prescaler
195+
const ppre2 = RCC.CFGR0.read().PPRE2;
196+
const ppre2_shift = prescaler_table[ppre2];
197+
const pclk2 = hclk >> @as(u5, @intCast(ppre2_shift));
198+
199+
// Calculate ADC clock from PCLK2 using ADCPRE
200+
const adcpre = RCC.CFGR0.read().ADCPRE;
201+
const adc_divisor: u32 = adc_prescaler_table[adcpre];
202+
const adcclk = pclk2 / adc_divisor;
203+
204+
return .{
205+
.sysclk = sysclk,
206+
.hclk = hclk,
207+
.pclk1 = pclk1,
208+
.pclk2 = pclk2,
209+
.adcclk = adcclk,
210+
};
211+
}

port/wch/ch32v/src/hals/gpio.zig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const microzig = @import("microzig");
22
pub const peripherals = microzig.chip.peripherals;
3+
const clocks = microzig.hal.clocks;
34

45
const GPIOA = peripherals.GPIOA;
56
const GPIOB = peripherals.GPIOB;
@@ -44,6 +45,11 @@ pub const Pull = enum {
4445
down,
4546
};
4647

48+
pub const AlternateFunctionMode = enum {
49+
push_pull,
50+
open_drain,
51+
};
52+
4753
// NOTE: With this current setup, every time we want to do anythting we go through a switch
4854
// Do we want this?
4955
// NOTE: How about to use port_config_mask, _value as previous apply function?
@@ -109,6 +115,31 @@ pub const Pin = packed struct(u8) {
109115
gpio.write_pin_config(config);
110116
}
111117

118+
pub inline fn enable_clock(gpio: Pin) void {
119+
// TODO: Cleanup!
120+
clocks.enable_gpio_clock(switch (gpio.get_port()) {
121+
GPIOA => .A,
122+
GPIOB => .B,
123+
GPIOC => .C,
124+
GPIOD => .D,
125+
else => unreachable,
126+
});
127+
}
128+
129+
/// Configure pin for alternate function use (e.g., USART, I2C, SPI)
130+
/// Combines enable_clock + set_output_mode for convenience
131+
pub inline fn configure_alternate_function(
132+
gpio: Pin,
133+
mode: AlternateFunctionMode,
134+
speed: Speed,
135+
) void {
136+
gpio.enable_clock();
137+
switch (mode) {
138+
.push_pull => gpio.set_output_mode(.alternate_function_push_pull, speed),
139+
.open_drain => gpio.set_output_mode(.alternate_function_open_drain, speed),
140+
}
141+
}
142+
112143
pub inline fn set_pull(gpio: Pin, pull: Pull) void {
113144
var port = gpio.get_port();
114145
switch (pull) {

port/wch/ch32v/src/hals/pins.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ pub const GlobalConfiguration = struct {
224224
const used_gpios = comptime input_gpios | output_gpios;
225225

226226
if (used_gpios != 0) {
227+
// Figure out IO port enable bit from name
227228
const offset = @as(u3, @intFromEnum(@field(Port, port_field.name))) + 2;
228229
RCC.APB2PCENR.raw |= @as(u32, 1 << offset);
229230
}
@@ -242,7 +243,7 @@ pub const GlobalConfiguration = struct {
242243
}
243244
}
244245

245-
// Set upll-up and pull-down.
246+
// Set pull-up and pull-down.
246247
if (input_gpios != 0) {
247248
inline for (@typeInfo(Port.Configuration).@"struct".fields) |field|
248249
if (@field(port_config, field.name)) |pin_config| {

0 commit comments

Comments
 (0)