Skip to content

Commit 7b2e2d1

Browse files
committed
Add touchscreen (over I2C) to the LVGL demo
1 parent dc18937 commit 7b2e2d1

File tree

9 files changed

+1383
-10
lines changed

9 files changed

+1383
-10
lines changed

stm32-lvgl/Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ let package = Package(
2424
// SVD2Swift \
2525
// --input Tools/SVDs/stm32f7x6.patched.svd \
2626
// --output stm32-lvgl/Sources/STM32F7x6 \
27-
// --peripherals FLASH LTDC RCC PWR FMC SCB DBGMCU USART1 STK NVIC \
27+
// --peripherals FLASH LTDC RCC PWR FMC SCB DBGMCU USART1 STK NVIC SYSCFG \
2828
// GPIOA GPIOB GPIOC GPIOD GPIOE GPIOF GPIOG GPIOH GPIOI GPIOJ GPIOK \
29+
// I2C1 I2C2 I2C3 I2C4 \
2930
// --access-level public
3031
.target(name: "Registers",
3132
dependencies: [
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors.
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import MMIO
13+
import Registers
14+
import Support
15+
16+
struct I2C {
17+
let i2c: I2C1
18+
let deviceAddress: UInt8
19+
20+
enum Interface: Int {
21+
case i2c1 = 0
22+
case i2c2
23+
case i2c3
24+
case i2c4
25+
}
26+
27+
init(interface: Interface, deviceAddress: UInt8) {
28+
self.deviceAddress = deviceAddress
29+
30+
switch interface {
31+
case .i2c1:
32+
self.i2c = i2c1
33+
rcc.apb1enr.modify { $0.raw.i2c1en = 1 } // Enable clock
34+
case .i2c2:
35+
self.i2c = i2c2
36+
rcc.apb1enr.modify { $0.raw.i2c2en = 1 } // Enable clock
37+
case .i2c3:
38+
self.i2c = i2c3
39+
rcc.apb1enr.modify { $0.raw.i2c3en = 1 } // Enable clock
40+
case .i2c4:
41+
self.i2c = i2c4
42+
rcc.apb1enr.modify { $0.raw.i2c4en = 1 } // Enable clock
43+
}
44+
45+
// Reset I2C peripheral
46+
i2c.cr1.write { $0.raw.pe = 0 }
47+
48+
// Configure I2C timing for 100KHz (based on 50MHz APB1 clock)
49+
i2c.timingr.write {
50+
$0.raw.presc = 0x09 // Prescaler
51+
$0.raw.scldel = 0x04 // Data setup time
52+
$0.raw.sdadel = 0x02 // Data hold time
53+
$0.raw.sclh = 0x0F // SCL high period
54+
$0.raw.scll = 0x13 // SCL low period
55+
}
56+
57+
// Enable I2C peripheral
58+
i2c.cr1.modify {
59+
$0.raw.pe = 1 // Enable peripheral
60+
$0.raw.anfoff = 0 // Analog filter enabled
61+
$0.raw.dnf = 0 // Digital filter disabled
62+
}
63+
}
64+
65+
func write(data: [UInt8]) -> Bool {
66+
// Wait until I2C is not busy
67+
while i2c.isr.read().raw.busy == 1 { /* busy wait */ }
68+
69+
// Set slave address
70+
i2c.cr2.write {
71+
$0.raw.sadd = UInt32(deviceAddress) << 1 // 7-bit address, shifted left by 1
72+
$0.raw.nbytes = UInt32(data.count)
73+
$0.raw.autoend = 1 // Automatic end mode
74+
$0.raw.start = 1 // Generate start condition
75+
$0.raw.rd_wrn = 0 // Write transfer
76+
}
77+
78+
// Send data bytes
79+
for byte in data {
80+
// Wait until TXIS is set (transmit register empty)
81+
while i2c.isr.read().raw.txis == 0 {
82+
// Check for errors
83+
let isr = i2c.isr.read().raw
84+
if isr.nackf == 1 {
85+
// NACK received
86+
i2c.icr.write { $0.raw.nackcf = 1 } // Clear NACK flag
87+
return false
88+
}
89+
}
90+
91+
// Write data to TXDR
92+
i2c.txdr.write { $0.raw.txdata = UInt32(byte) }
93+
}
94+
95+
// Wait for transfer complete
96+
while i2c.isr.read().raw.tc == 0 && i2c.isr.read().raw.tcr == 0 && i2c.isr.read().raw.stopf == 0 {
97+
// Check for errors
98+
if i2c.isr.read().raw.nackf == 1 {
99+
i2c.icr.write { $0.raw.nackcf = 1 } // Clear NACK flag
100+
return false
101+
}
102+
}
103+
104+
// Clear flags
105+
let isr = i2c.isr.read().raw
106+
i2c.icr.write {
107+
$0.raw.stopcf = isr.stopf
108+
$0.raw.nackcf = isr.nackf
109+
}
110+
111+
return true
112+
}
113+
114+
func read(buffer: inout [UInt8], length: Int) -> Bool {
115+
// Wait until I2C is not busy
116+
while i2c.isr.read().raw.busy == 1 { /* busy wait */ }
117+
118+
// Set slave address and read operation
119+
i2c.cr2.write {
120+
$0.raw.sadd = UInt32(deviceAddress) << 1 // 7-bit address, shifted left by 1
121+
$0.raw.nbytes = UInt32(length)
122+
$0.raw.autoend = 1 // Automatic end mode
123+
$0.raw.rd_wrn = 1 // Read transfer
124+
$0.raw.start = 1 // Generate start condition
125+
}
126+
127+
// Read data
128+
for i in 0..<length {
129+
// Wait until RXNE is set (receive register not empty)
130+
while i2c.isr.read().raw.rxne == 0 {
131+
// Check for errors
132+
let isr = i2c.isr.read().raw
133+
if isr.nackf == 1 {
134+
// NACK received
135+
i2c.icr.write { $0.raw.nackcf = 1 } // Clear NACK flag
136+
return false
137+
}
138+
}
139+
140+
// Read data from RXDR
141+
buffer[i] = UInt8(i2c.rxdr.read().raw.rxdata & 0xFF)
142+
}
143+
144+
// Wait for transfer complete
145+
while i2c.isr.read().raw.tc == 0 && i2c.isr.read().raw.stopf == 0 { /* busy wait */ }
146+
147+
// Clear flags
148+
let isr = i2c.isr.read().raw
149+
i2c.icr.write {
150+
$0.raw.stopcf = isr.stopf
151+
$0.raw.nackcf = isr.nackf
152+
}
153+
154+
return true
155+
}
156+
}

stm32-lvgl/Sources/Application/Main.swift

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,27 @@ struct Main {
2727
initUartOutput()
2828

2929
withInterruptsDisabled {
30-
log("initClock")
30+
log("initClock()")
3131
initClocks()
3232

33-
log("initInterrupts")
33+
log("initInterrupts()")
3434
initInterrupts()
3535

3636
// Reset the vector table to where it should be.
3737
log("init VTOR")
3838
scb.vtor.modify { $0.storage = 0x0800_0000 }
3939
}
4040

41-
log("systemSetCoreClock")
41+
log("systemSetCoreClock()")
4242
systemSetCoreClock()
4343

4444
// We are running at 200 MHz CPU frequency now. Set the UART baud rate to
4545
// 115200 again based on the new CPU frequency.
4646
usart1.brr.modify { $0.raw.storage = 100_000_000 / 115_200 }
4747

48-
log("initGpio")
48+
log("initGpio()")
4949
initGpio()
50-
log("initSdram")
50+
log("initSdram()")
5151
initSdram()
5252
log("Lcd.initialize()")
5353
Lcd.initialize()
@@ -57,11 +57,14 @@ struct Main {
5757

5858
// Everything is initialized now. Run application logic.
5959

60-
//log("dramTest")
60+
//log("dramTest()")
6161
//dramTest()
6262

63-
log("lvglTest")
64-
lvglTest()
63+
log("TouchPanel.initialize()")
64+
TouchPanel.initialize()
65+
66+
log("lvglDemo()")
67+
lvglDemo()
6568
}
6669

6770
static func dramTest() {
@@ -94,7 +97,11 @@ struct Main {
9497
fillAndCheckWithIndexXor(pattern: 0xFFFF_FFFF)
9598
}
9699

97-
static func lvglTest() {
100+
static var clickCount = 0
101+
static var button: OpaquePointer! = nil
102+
static var buttonLabel: OpaquePointer! = nil
103+
104+
static func lvglDemo() {
98105
lv_init()
99106
lv_tick_set_cb({ UInt32(uptimeInMs) })
100107

@@ -127,6 +134,23 @@ struct Main {
127134
// the lv_display_flush_ready() will happen in the LCD frame interrupt.
128135
})
129136

137+
let touch = lv_indev_create()
138+
lv_indev_set_type(touch, LV_INDEV_TYPE_POINTER)
139+
lv_indev_set_read_cb(
140+
touch,
141+
{ indev, data in
142+
let touchData = TouchPanel.readTouchData()
143+
if touchData.numberOfTouchPoints > 0 {
144+
data!.pointee.point.x = Int32(touchData.x)
145+
data!.pointee.point.y = Int32(touchData.y)
146+
data!.pointee.state = LV_INDEV_STATE_PRESSED
147+
print("pressed: \(touchData.x) \(touchData.y)")
148+
} else {
149+
data!.pointee.state = LV_INDEV_STATE_RELEASED
150+
print("released")
151+
}
152+
})
153+
130154
// Get the active screen
131155
let screen = lv_screen_active()
132156

@@ -152,6 +176,20 @@ struct Main {
152176
// Apply the style to the screen
153177
lv_obj_add_style(screen, &style, 0)
154178

179+
// Create a button in the top right
180+
button = lv_button_create(screen)
181+
lv_obj_set_size(button, 120, 50)
182+
lv_obj_align(button, LV_ALIGN_TOP_RIGHT, -10, 10)
183+
buttonLabel = lv_label_create(button)
184+
lv_label_set_text(buttonLabel, "Click me")
185+
lv_obj_center(buttonLabel)
186+
lv_obj_add_event_cb(
187+
button,
188+
{ event in
189+
Self.clickCount += 1
190+
lv_label_set_text(Self.buttonLabel, "Clicked \(Self.clickCount)")
191+
}, LV_EVENT_CLICKED, nil)
192+
155193
// Create a label
156194
let label = lv_label_create(screen)
157195
lv_label_set_text(label, "Hello LVGL!")
@@ -175,6 +213,21 @@ struct Main {
175213
// Configure spinner animation
176214
lv_spinner_set_anim_params(spinner, 2_000, 200) // anim time, angle
177215

216+
// Create a dropdown menu
217+
let dropdown = lv_dropdown_create(screen)
218+
lv_dropdown_set_options(dropdown, "Option 1\nOption 2\nOption 3\nOption 4")
219+
lv_obj_set_size(dropdown, 150, 40)
220+
lv_obj_align(dropdown, LV_ALIGN_TOP_LEFT, 10, 10)
221+
222+
// Add event handler for dropdown selection
223+
lv_obj_add_event_cb(
224+
dropdown,
225+
{ event in
226+
let dropdown = lv_event_get_target_obj(event)
227+
let selectedIndex = lv_dropdown_get_selected(dropdown)
228+
print("Selected option \(selectedIndex)")
229+
}, LV_EVENT_VALUE_CHANGED, nil)
230+
178231
// Add a label under the spinner
179232
let spinnerLabel = lv_label_create(screen)
180233
lv_label_set_text(spinnerLabel, "Loading...")
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors.
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
struct TouchData {
13+
var numberOfTouchPoints: Int
14+
var x: Int
15+
var y: Int
16+
}
17+
18+
enum TouchPanel {
19+
static let touchPanel = I2C.init(interface: .i2c3, deviceAddress: 0x38)
20+
21+
static func initialize() {
22+
// Init the touch controller
23+
guard touchPanel.write(data: [0xA4, 0x00]) else {
24+
print("Init failed")
25+
return
26+
}
27+
guard touchPanel.write(data: [0xA5, 0x00]) else {
28+
print("Init failed")
29+
return
30+
}
31+
}
32+
33+
static func readTouchData() -> TouchData {
34+
// Read device registers 0x00 through 0x06:
35+
// 0x00 DEVIDE_MODE Device Mode (bits 4...6)
36+
// 0x01 GEST_ID Gesture ID
37+
// 0x02 TD_STATUS Number of touch points (bits 0...3)
38+
// 0x03 TOUCH1_XH 1st Event Flag (bits 6...7), 1st Touch X Position Hi Bits (bits 0...3)
39+
// 0x04 TOUCH1_XL 1st Touch X Position Lo Bits
40+
// 0x05 TOUCH1_YH 1st Touch ID (bits 4...7), 1st Touch Y Position Hi Bits (bits 0...3)
41+
// 0x06 TOUCH1_YL 1st Touch Y Position Lo Bits
42+
43+
guard touchPanel.write(data: [0x00]) else {
44+
print("Send failed")
45+
return TouchData(numberOfTouchPoints: 0, x: 0, y: 0)
46+
}
47+
48+
var buf = [UInt8](repeating: 0, count: 7)
49+
guard touchPanel.read(buffer: &buf, length: 7) else {
50+
print("Read failed")
51+
return TouchData(numberOfTouchPoints: 0, x: 0, y: 0)
52+
}
53+
54+
let x = Int(buf[3] & 0b1111) << 8 | Int(buf[4])
55+
let y = Int(buf[5] & 0b1111) << 8 | Int(buf[6])
56+
57+
// On STM32F746G discovery board, the x and y are swapped.
58+
return TouchData(numberOfTouchPoints: Int(buf[2] & 0b1111), x: y, y: x)
59+
}
60+
61+
}

stm32-lvgl/Sources/Registers/Device.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ public let gpioj = GPIOJ(unsafeAddress: 0x40022400)
4444
/// General-purpose I/Os
4545
public let gpiok = GPIOK(unsafeAddress: 0x40022800)
4646

47+
/// Inter-integrated circuit
48+
public let i2c1 = I2C1(unsafeAddress: 0x40005400)
49+
50+
/// Inter-integrated circuit
51+
public let i2c2 = I2C2(unsafeAddress: 0x40005800)
52+
53+
/// Inter-integrated circuit
54+
public let i2c3 = I2C3(unsafeAddress: 0x40005c00)
55+
56+
/// Inter-integrated circuit
57+
public let i2c4 = I2C4(unsafeAddress: 0x40006000)
58+
4759
/// LCD-TFT Controller
4860
public let ltdc = LTDC(unsafeAddress: 0x40016800)
4961

0 commit comments

Comments
 (0)