Skip to content

NRF52: implement ADC and PWM interfaces #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/machine/board_pca10040.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,13 @@ const (
UART_TX_PIN = 6
UART_RX_PIN = 8
)

// ADC pins
const (
ADC0 = 3
ADC1 = 4
ADC2 = 28
ADC3 = 29
ADC4 = 30
ADC5 = 31
)
154 changes: 154 additions & 0 deletions src/machine/machine_nrf52.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// +build nrf52

package machine

import (
"device/nrf"
"unsafe"
)

// InitADC initializes the registers needed for ADC.
func InitADC() {
return // no specific setup on nrf52 machine.
}

// Configure configures an ADC pin to be able to read analog data.
func (a ADC) Configure() {
return // no pin specific setup on nrf52 machine.
}

// Get returns the current value of a ADC pin in the range 0..0xffff.
func (a ADC) Get() uint16 {
var pwmPin uint32
var value int16

switch a.Pin {
case 2:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput0

case 3:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput1

case 4:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput2

case 5:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput3

case 28:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput4

case 29:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput5

case 30:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput6

case 31:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput7

default:
return 0
}

nrf.SAADC.RESOLUTION = nrf.SAADC_RESOLUTION_VAL_12bit

// Enable ADC.
nrf.SAADC.ENABLE = (nrf.SAADC_ENABLE_ENABLE_Enabled << nrf.SAADC_ENABLE_ENABLE_Pos)
for i := 0; i < 8; i++ {
nrf.SAADC.CH[i].PSELN = nrf.SAADC_CH_PSELP_PSELP_NC
nrf.SAADC.CH[i].PSELP = nrf.SAADC_CH_PSELP_PSELP_NC
}

// Configure ADC.
nrf.SAADC.CH[0].CONFIG = ((nrf.SAADC_CH_CONFIG_RESP_Bypass << nrf.SAADC_CH_CONFIG_RESP_Pos) & nrf.SAADC_CH_CONFIG_RESP_Msk) |
((nrf.SAADC_CH_CONFIG_RESP_Bypass << nrf.SAADC_CH_CONFIG_RESN_Pos) & nrf.SAADC_CH_CONFIG_RESN_Msk) |
((nrf.SAADC_CH_CONFIG_GAIN_Gain1_5 << nrf.SAADC_CH_CONFIG_GAIN_Pos) & nrf.SAADC_CH_CONFIG_GAIN_Msk) |
((nrf.SAADC_CH_CONFIG_REFSEL_Internal << nrf.SAADC_CH_CONFIG_REFSEL_Pos) & nrf.SAADC_CH_CONFIG_REFSEL_Msk) |
((nrf.SAADC_CH_CONFIG_TACQ_3us << nrf.SAADC_CH_CONFIG_TACQ_Pos) & nrf.SAADC_CH_CONFIG_TACQ_Msk) |
((nrf.SAADC_CH_CONFIG_MODE_SE << nrf.SAADC_CH_CONFIG_MODE_Pos) & nrf.SAADC_CH_CONFIG_MODE_Msk)

// Set pin to read.
nrf.SAADC.CH[0].PSELN = nrf.RegValue(pwmPin)
nrf.SAADC.CH[0].PSELP = nrf.RegValue(pwmPin)

// Destination for sample result.
nrf.SAADC.RESULT.PTR = nrf.RegValue(uintptr(unsafe.Pointer(&value)))
nrf.SAADC.RESULT.MAXCNT = 1 // One sample

// Start tasks.
nrf.SAADC.TASKS_START = 1
for nrf.SAADC.EVENTS_STARTED == 0 {
}
nrf.SAADC.EVENTS_STARTED = 0x00

// Start the sample task.
nrf.SAADC.TASKS_SAMPLE = 1

// Wait until the sample task is done.
for nrf.SAADC.EVENTS_END == 0 {
}
nrf.SAADC.EVENTS_END = 0x00

// Stop the ADC
nrf.SAADC.TASKS_STOP = 1
for nrf.SAADC.EVENTS_STOPPED == 0 {
}
nrf.SAADC.EVENTS_STOPPED = 0

// Disable the ADC.
nrf.SAADC.ENABLE = (nrf.SAADC_ENABLE_ENABLE_Disabled << nrf.SAADC_ENABLE_ENABLE_Pos)

if value < 0 {
value = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually happen, and if it does, under what conditions?

}

// Return 16-bit result from 12-bit value.
return uint16(value << 4)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this would have been undefined behavior in C, as you're shifting more than the bit width (15 bits without the sign, and the value can be bigger than 15 bits after the shift). Luckily this is not C so it is well defined. That is, I haven't seen anything in the Go spec about shift overflow but I know it is well defined in TinyGo as most integers are treated as unsigned by LLVM unless it is told otherwise.

It might be slightly more obvious what is going on here when you cast first to 16 bits before shifting.

See e.g. this tweet for a related oddity in C.

}

// PWM
var (
pwmChannelPins = [3]uint32{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}
pwms = [3]*nrf.PWM_Type{nrf.PWM0, nrf.PWM1, nrf.PWM2}
pwmChannelSequence [3]uint16
)

// InitPWM initializes the registers needed for PWM.
func InitPWM() {
return
}

// Configure configures a PWM pin for output.
func (pwm PWM) Configure() {
}

// Set turns on the duty cycle for a PWM pin using the provided value.
func (pwm PWM) Set(value uint16) {
for i := 0; i < 3; i++ {
if pwmChannelPins[i] == 0xFFFFFFFF || pwmChannelPins[i] == uint32(pwm.Pin) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're picking the first available channel here? That's quite smart, although a bit magical.
I haven't tested it, but you might be able to avoid the need for the pwmChannelPins global (with the additional RAM usage) if you're directly checking pwms[i].PSEL.OUT[0]. The RAM overhead is quite small (12 bytes on a 64kB chip) so it isn't essential but it might be interesting.

pwmChannelPins[i] = uint32(pwm.Pin)
pwmChannelSequence[i] = (value >> 2) | 0x8000 // set bit 15 to invert polarity

p := pwms[i]

p.PSEL.OUT[0] = nrf.RegValue(pwm.Pin)
p.PSEL.OUT[1] = nrf.RegValue(pwm.Pin)
p.PSEL.OUT[2] = nrf.RegValue(pwm.Pin)
p.PSEL.OUT[3] = nrf.RegValue(pwm.Pin)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why you're setting all four channels?
Especially as you seem to be configuring only one channel below.

p.ENABLE = (nrf.PWM_ENABLE_ENABLE_Enabled << nrf.PWM_ENABLE_ENABLE_Pos)
p.PRESCALER = nrf.PWM_PRESCALER_PRESCALER_DIV_2
p.MODE = nrf.PWM_MODE_UPDOWN_Up
p.COUNTERTOP = 16384 // frequency
p.LOOP = 0
p.DECODER = (nrf.PWM_DECODER_LOAD_Common << nrf.PWM_DECODER_LOAD_Pos) | (nrf.PWM_DECODER_MODE_RefreshCount << nrf.PWM_DECODER_MODE_Pos)
p.SEQ[0].PTR = nrf.RegValue(uintptr(unsafe.Pointer(&pwmChannelSequence[i])))
p.SEQ[0].CNT = 1
p.SEQ[0].REFRESH = 1
p.SEQ[0].ENDDELAY = 0
p.TASKS_SEQSTART[0] = 1

break
}
}
}