//go:build avr && !avrtiny package machine import ( "device/avr" "runtime/volatile" "unsafe" ) const deviceName = avr.DEVICE const ( PinInput PinMode = iota PinInputPullup PinOutput ) // In all the AVRs I've looked at, the PIN/DDR/PORT registers followed a regular // pattern: PINx, DDRx, PORTx in this order without registers in between. // Therefore, if you know any of them, you can calculate the other two. // // For now, I've chosen to let the PORTx register be the one that is returned // for each specific chip and to calculate the others from that one. Setting an // output port (done using PORTx) is likely the most common operation and the // one that is the most time critical. For others, the PINx and DDRx register // can trivially be calculated using a subtraction. // Configure sets the pin to input or output. func (p Pin) Configure(config PinConfig) { port, mask := p.getPortMask() // The DDRx register can be found by subtracting one from the PORTx // register, as this appears to be the case for many (most? all?) AVR chips. ddr := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 1)) if config.Mode == PinOutput { // set output bit ddr.SetBits(mask) // Note: if the pin was PinInputPullup before, it'll now be high. // Otherwise it will be low. } else { // configure input: clear output bit ddr.ClearBits(mask) if config.Mode == PinInput { // No pullup (floating). // The transition may be one of the following: // output high -> input pullup -> input (safe: output high and input pullup are similar) // output low -> input -> input (safe: no extra transition) port.ClearBits(mask) } else { // Pullup. // The transition may be one of the following: // output high -> input pullup -> input pullup (safe: no extra transition) // output low -> input -> input pullup (possibly problematic) // For the last transition (output low -> input -> input pullup), // the transition may be problematic in some cases because there is // an intermediate floating state (which may cause irratic // interrupts, for example). If this is a problem, the application // should set the pin high before configuring it as PinInputPullup. // We can't do that here because setting it to high as an // intermediate state may have other problems. port.SetBits(mask) } } } // Get returns the current value of a GPIO pin when the pin is configured as an // input or as an output. func (p Pin) Get() bool { port, mask := p.getPortMask() // As noted above, the PINx register is always two registers below the PORTx // register, so we can find it simply by subtracting two from the PORTx // register address. pin := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 2)) // PINA, PINB, etc return (pin.Get() & mask) > 0 } // Set changes the value of the GPIO pin. The pin must be configured as output. func (p Pin) Set(value bool) { if value { // set bits port, mask := p.PortMaskSet() port.Set(mask) } else { // clear bits port, mask := p.PortMaskClear() port.Set(mask) } } // Return the register and mask to enable a given GPIO pin. This can be used to // implement bit-banged drivers. // // Warning: there are no separate pin set/clear registers on the AVR. The // returned mask is only valid as long as no other pin in the same port has been // changed. func (p Pin) PortMaskSet() (*volatile.Register8, uint8) { port, mask := p.getPortMask() return port, port.Get() | mask } // Return the register and mask to disable a given port. This can be used to // implement bit-banged drivers. // // Warning: there are no separate pin set/clear registers on the AVR. The // returned mask is only valid as long as no other pin in the same port has been // changed. func (p Pin) PortMaskClear() (*volatile.Register8, uint8) { port, mask := p.getPortMask() return port, port.Get() &^ mask } // InitADC initializes the registers needed for ADC. func InitADC() { // set a2d prescaler so we are inside the desired 50-200 KHz range at 16MHz. avr.ADCSRA.SetBits(avr.ADCSRA_ADPS2 | avr.ADCSRA_ADPS1 | avr.ADCSRA_ADPS0) // enable a2d conversions avr.ADCSRA.SetBits(avr.ADCSRA_ADEN) } // Configure configures a ADCPin to be able to be used to read data. func (a ADC) Configure(ADCConfig) { return // no pin specific setup on AVR machine. } // Get returns the current value of a ADC pin, in the range 0..0xffff. The AVR // has an ADC of 10 bits precision so the lower 6 bits will be zero. func (a ADC) Get() uint16 { // set the analog reference (high two bits of ADMUX) and select the // channel (low 4 bits), masked to only turn on one ADC at a time. // set the ADLAR bit (left-adjusted result) to get a value scaled to 16 // bits. This has the same effect as shifting the return value left by 6 // bits. avr.ADMUX.Set(avr.ADMUX_REFS0 | avr.ADMUX_ADLAR | (uint8(a.Pin) & 0x07)) // start the conversion avr.ADCSRA.SetBits(avr.ADCSRA_ADSC) // ADSC is cleared when the conversion finishes for ok := true; ok; ok = avr.ADCSRA.HasBits(avr.ADCSRA_ADSC) { } return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8 } // linked from runtime.adjustMonotonicTimer func adjustMonotonicTimer() // linked from runtime.initMonotonicTimer func initMonotonicTimer()