diff --git a/GNUmakefile b/GNUmakefile index 2661fbd52..de298bbe5 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -523,6 +523,8 @@ smoketest: testchdir # regression test for #2563 cd tests/os/smoke && $(TINYGO) test -c -target=pybadge && rm smoke.test # test all examples (except pwm) + $(TINYGO) build -size short -o test.hex -target=pga2350 examples/echo + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc diff --git a/src/machine/board_pga2350.go b/src/machine/board_pga2350.go new file mode 100644 index 000000000..710f14d85 --- /dev/null +++ b/src/machine/board_pga2350.go @@ -0,0 +1,98 @@ +//go:build pga2350 + +package machine + +// PGA2350 pin definitions. +const ( + GP0 = GPIO0 + GP1 = GPIO1 + GP2 = GPIO2 + GP3 = GPIO3 + GP4 = GPIO4 + GP5 = GPIO5 + GP6 = GPIO6 + GP7 = GPIO7 + GP8 = GPIO8 + GP9 = GPIO9 + GP10 = GPIO10 + GP11 = GPIO11 + GP12 = GPIO12 + GP13 = GPIO13 + GP14 = GPIO14 + GP15 = GPIO15 + GP16 = GPIO16 + GP17 = GPIO17 + GP18 = GPIO18 + GP19 = GPIO19 + GP20 = GPIO20 + GP21 = GPIO21 + GP22 = GPIO22 + GP26 = GPIO26 + GP27 = GPIO27 + GP28 = GPIO28 + GP29 = GPIO29 + GP30 = GPIO30 // peripherals: PWM7 channel A + GP31 = GPIO31 // peripherals: PWM7 channel B + GP32 = GPIO32 // peripherals: PWM8 channel A + GP33 = GPIO33 // peripherals: PWM8 channel B + GP34 = GPIO34 // peripherals: PWM9 channel A + GP35 = GPIO35 // peripherals: PWM9 channel B + GP36 = GPIO36 // peripherals: PWM10 channel A + GP37 = GPIO37 // peripherals: PWM10 channel B + GP38 = GPIO38 // peripherals: PWM11 channel A + GP39 = GPIO39 // peripherals: PWM11 channel B + GP40 = GPIO40 // peripherals: PWM8 channel A + GP41 = GPIO41 // peripherals: PWM8 channel B + GP42 = GPIO42 // peripherals: PWM9 channel A + GP43 = GPIO43 // peripherals: PWM9 channel B + GP44 = GPIO44 // peripherals: PWM10 channel A + GP45 = GPIO45 // peripherals: PWM10 channel B + GP46 = GPIO46 // peripherals: PWM11 channel A + GP47 = GPIO47 // peripherals: PWM11 channel B + +) + +var DefaultUART = UART0 + +// Peripheral defaults. +const ( + xoscFreq = 12 // MHz + + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 + + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx + + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +// USB identifiers +const ( + usb_STRING_PRODUCT = "PGA2350" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/src/machine/machine_rp2_2350.go b/src/machine/machine_rp2_2350.go index 32b739c62..5ef5098c6 100644 --- a/src/machine/machine_rp2_2350.go +++ b/src/machine/machine_rp2_2350.go @@ -33,16 +33,6 @@ const ( rp.PADS_BANK0_GPIO0_ISO_Msk ) -// Analog pins on RP2350. -const ( - ADC0 Pin = GPIO26 - ADC1 Pin = GPIO27 - ADC2 Pin = GPIO28 - ADC3 Pin = GPIO29 - - thermADC = 30 -) - const ( PinOutput PinMode = iota PinInput diff --git a/src/machine/machine_rp2_2350a.go b/src/machine/machine_rp2_2350a.go new file mode 100644 index 000000000..09ec8a119 --- /dev/null +++ b/src/machine/machine_rp2_2350a.go @@ -0,0 +1,14 @@ +//go:build rp2350 && !rp2350b + +package machine + +// Analog pins on RP2350a. +const ( + ADC0 Pin = GPIO26 + ADC1 Pin = GPIO27 + ADC2 Pin = GPIO28 + ADC3 Pin = GPIO29 + + // fifth ADC channel. + thermADC = 30 +) diff --git a/src/machine/machine_rp2_2350b.go b/src/machine/machine_rp2_2350b.go index c1e4a3a66..bd5ceebe4 100644 --- a/src/machine/machine_rp2_2350b.go +++ b/src/machine/machine_rp2_2350b.go @@ -35,7 +35,7 @@ const ( ADC5 Pin = GPIO45 ADC6 Pin = GPIO46 ADC7 Pin = GPIO47 - + // Ninth ADC channel. thermADC = 48 ) diff --git a/src/machine/machine_rp2_clocks.go b/src/machine/machine_rp2_clocks.go index ba5bb67af..9dd2774b9 100644 --- a/src/machine/machine_rp2_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -137,6 +137,8 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { } +const pllsysFB, pllsysPD1, pllsysPD2 uint32 = 125, 6, 2 // RP2040 running 125MHz with 1500MHz VCO. + // init initializes the clock hardware. // // Must be called before any other clock function. @@ -163,8 +165,8 @@ func (clks *clocksType) init() { // REF FBDIV VCO POSTDIV // pllSys: 12 / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz // pllUSB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz - pllSys.init(1, 1500*MHz, 6, 2) - pllUSB.init(1, 480*MHz, 5, 2) + pllSys.init(1, uint32(pllsysFB), uint32(pllsysPD1), uint32(pllsysPD2)) + pllUSB.init(1, 40, 5, 2) // Configure clocks // clkRef = xosc (12MHz) / 1 = 12MHz diff --git a/src/machine/machine_rp2_pll.go b/src/machine/machine_rp2_pll.go index f409768ab..dcc654ab0 100644 --- a/src/machine/machine_rp2_pll.go +++ b/src/machine/machine_rp2_pll.go @@ -4,6 +4,9 @@ package machine import ( "device/rp" + "errors" + "math" + "math/bits" "runtime/volatile" "unsafe" ) @@ -29,12 +32,11 @@ var ( // Post Divider 1, postDiv1 with range 1-7 and be >= postDiv2. // // Post Divider 2, postDiv2 with range 1-7. -func (pll *pll) init(refdiv, vcoFreq, postDiv1, postDiv2 uint32) { +func (pll *pll) init(refdiv, fbdiv, postDiv1, postDiv2 uint32) { refFreq := xoscFreq / refdiv // What are we multiplying the reference clock by to get the vco freq // (The regs are called div, because you divide the vco output and compare it to the refclk) - fbdiv := vcoFreq / (refFreq * MHz) // Check fbdiv range if !(fbdiv >= 16 && fbdiv <= 320) { @@ -54,13 +56,14 @@ func (pll *pll) init(refdiv, vcoFreq, postDiv1, postDiv2 uint32) { panic("postdiv1 should be greater than or equal to postdiv2") } - // Check that reference frequency is no greater than vco / 16 + // Check that reference frequency is no greater than vcoFreq / 16 + vcoFreq := calcVCO(xoscFreq, fbdiv, refdiv) if refFreq > vcoFreq/16 { panic("reference frequency should not be greater than vco frequency divided by 16") } // div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10 - pdiv := postDiv1< maxVCO { + break + } + calcPD12 := vco / targetFreq + if calcPD12 < 1 { + calcPD12 = 1 + } else if calcPD12 > 49 { + calcPD12 = 49 + } + iters++ + pd1 = pdTable[calcPD12].hivco[0] + pd2 = pdTable[calcPD12].hivco[1] + fout, err := pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) + found := false + margin := abs(int64(fout) - int64(targetFreq)) + if err == nil && margin <= bestMargin { + found = true + bestFreq = fout + bestFbdiv = fbdiv + bestpd1 = pd1 + bestpd2 = pd2 + bestRefdiv = refdiv + bestMargin = margin + } + pd1 = pdTable[calcPD12].lovco[0] + pd2 = pdTable[calcPD12].lovco[1] + fout, err = pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) + margin = abs(int64(fout) - int64(targetFreq)) + if err == nil && margin <= bestMargin { + found = true + bestFreq = fout + bestFbdiv = fbdiv + bestpd1 = pd1 + bestpd2 = pd2 + bestRefdiv = refdiv + bestMargin = margin + } + if found && ps.LowerVCO { + break + } + } + } + if bestFreq == 0 { + return fbdiv, refdiv, pd1, pd2, errors.New("no best frequency found") + } + return bestFbdiv, bestRefdiv, bestpd1, bestpd2, nil +} + +func abs(a int64) int64 { + if a == math.MinInt64 { + return math.MaxInt64 + } else if a < 0 { + return -a + } + return a +} + +func pllFreqOutPostdiv(xosc, fbdiv, MHz uint64, refdiv, postdiv1, postdiv2 uint8) (foutpostdiv uint64, err error) { + // testing grounds. + const ( + mhz = 1 + cfref = 12 * mhz // given by crystal oscillator selection. + crefd = 1 + cfbdiv = 100 + cvco = cfref * cfbdiv / crefd + cpd1 = 6 + cpd2 = 2 + foutpd = (cfref / crefd) * cfbdiv / (cpd1 * cpd2) + ) + refFreq := xosc / uint64(refdiv) + overflow, vco := bits.Mul64(xosc, fbdiv) + vco /= uint64(refdiv) + foutpostdiv = vco / uint64(postdiv1*postdiv2) + switch { + case refdiv < 1 || refdiv > 63: + err = errors.New("reference divider out of range") + case fbdiv < 16 || fbdiv > 320: + err = errors.New("feedback divider out of range") + case postdiv1 < 1 || postdiv1 > 7: + err = errors.New("postdiv1 out of range") + case postdiv2 < 1 || postdiv2 > 7: + err = errors.New("postdiv2 out of range") + case postdiv1 < postdiv2: + err = errors.New("user error: use higher value for postdiv1 for lower power consumption") + case vco < 750*MHz || vco > 1600*MHz: + err = errors.New("VCO out of range") + case refFreq < 5*MHz: + err = errors.New("minimum reference frequency breach") + case refFreq > vco/16: + err = errors.New("maximum reference frequency breach") + case vco > 1200*MHz && vco < 1600*MHz && xosc < 75*MHz && refdiv != 1: + err = errors.New("refdiv should be 1 for given VCO and reference frequency") + case overflow != 0: + err = errVCOOverflow + } + if err != nil { + return 0, err + } + return foutpostdiv, nil +} + +func calcVCO(xoscFreq, fbdiv, refdiv uint32) uint32 { + const maxXoscMHz = math.MaxUint32 / 320 / MHz // 13MHz maximum xosc apparently. + if fbdiv > 320 || xoscFreq > math.MaxUint32/320 { + panic("invalid VCO calculation args") + } + return xoscFreq * fbdiv / refdiv +} + +var pdTable = [50]struct { + hivco [2]uint8 + lovco [2]uint8 +}{} + +func genTable() { + if pdTable[1].hivco[1] != 0 { + return // Already generated. + } + for product := 1; product < len(pdTable); product++ { + bestProdhi := 255 + bestProdlo := 255 + for pd1 := 7; pd1 > 0; pd1-- { + for pd2 := pd1; pd2 > 0; pd2-- { + gotprod := pd1 * pd2 + if abs(int64(gotprod-product)) < abs(int64(bestProdlo-product)) { + bestProdlo = gotprod + pdTable[product].lovco[0] = uint8(pd1) + pdTable[product].lovco[1] = uint8(pd2) + } + } + } + for pd1 := 1; pd1 < 8; pd1++ { + for pd2 := 1; pd2 <= pd1; pd2++ { + gotprod := pd1 * pd2 + if abs(int64(gotprod-product)) < abs(int64(bestProdhi-product)) { + bestProdhi = gotprod + pdTable[product].hivco[0] = uint8(pd1) + pdTable[product].hivco[1] = uint8(pd2) + } + } + } + } +} diff --git a/src/machine/machine_rp2_pwm.go b/src/machine/machine_rp2_pwm.go index 1a511daf1..772811e36 100644 --- a/src/machine/machine_rp2_pwm.go +++ b/src/machine/machine_rp2_pwm.go @@ -146,13 +146,14 @@ func (p *pwmGroup) Counter() uint32 { // Period returns the used PWM period in nanoseconds. func (p *pwmGroup) Period() uint64 { - freq := CPUFrequency() + // Lines below can overflow if operations done without care. + // maxInt=255, maxFrac=15, maxTop=65536, maxPHC=1 => maxProduct= (16*255+15) * (65536*2*1e9) = 5.3673e17 < MaxUint64=1.8e19 (close call.) + const compileTimeCheckPeriod uint64 = (255*16 + 15) * (65535 + 1) * 2 * 1e9 + freq := uint64(CPUFrequency()) top := p.getWrap() phc := p.getPhaseCorrect() Int, frac := p.getClockDiv() - // Lines below can overflow if operations done without care. - term2 := 16 * uint64((top+1)*(phc+1)) * 1e9 / uint64(freq) // 1e9/freq == CPU period in nanoseconds. - return (uint64(Int) + uint64(frac)) * term2 / 16 // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) + return (16*uint64(Int) + uint64(frac)) * uint64((top+1)*(phc+1)*1e9) / (16 * freq) // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) } // SetInverting sets whether to invert the output of this channel. @@ -267,6 +268,9 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // Maximum Period is 268369920ns on rp2040, given by (16*255+15)*8*(1+0xffff)*(1+1)/16 // With no phase shift max period is half of this value. maxPeriod = 268 * milliseconds + // This will be a compile time error if this method is at risk of overflowing. cpufreq=155MHz for typical RP2350. + maxCPUFreq = 4 * GHz // Can go up to 4GHz without overflowing :) + compileTimeCheckSetPeriod uint64 = 16 * maxPeriod * maxCPUFreq ) if period > maxPeriod || period < 8 { @@ -280,11 +284,13 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // DIV_INT + DIV_FRAC/16 = cycles / ( (TOP+1) * (CSRPHCorrect+1) ) // DIV_FRAC/16 is always 0 in this equation // where cycles must be converted to time: // target_period = cycles * period_per_cycle ==> cycles = target_period/period_per_cycle - freq := uint64(CPUFrequency()) - phc := uint64(pwm.getPhaseCorrect()) - rhs := 16e9 * period / ((1 + phc) * freq * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided - whole := rhs / 16 - frac := rhs % 16 + var ( + freq = uint64(CPUFrequency()) + phc = uint64(pwm.getPhaseCorrect()) + rhs = 16 * period * freq / ((1 + phc) * 1e9 * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided + whole = rhs / 16 + frac = rhs % 16 + ) switch { case whole > 0xff: whole = 0xff @@ -297,7 +303,7 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // Step 2 is acquiring a better top value. Clearing the equation: // TOP = cycles / ( (DIVINT+DIVFRAC/16) * (CSRPHCorrect+1) ) - 1 - top := 16e9*period/((16*whole+frac)*freq*(1+phc)) - 1 + top := 16*period*freq/((1+phc)*1e9*(16*whole+frac)) - 1 if top > maxTop { top = maxTop } diff --git a/targets/pga2350.json b/targets/pga2350.json new file mode 100644 index 000000000..3ff72c26c --- /dev/null +++ b/targets/pga2350.json @@ -0,0 +1,4 @@ +{ + "inherits": ["rp2350b"], + "build-tags": ["pga2350"] +} \ No newline at end of file