PWM Support for atmega1280
Add arduino mega 1280 PWM test
Этот коммит содержится в:
		
							родитель
							
								
									5707022951
								
							
						
					
					
						коммит
						cb886a35c9
					
				
					 3 изменённых файлов: 809 добавлений и 1 удалений
				
			
		
							
								
								
									
										4
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
										
									
									
									
								
							|  | @ -378,7 +378,9 @@ ifneq ($(AVR), 0) | ||||||
| 	@$(MD5SUM) test.hex | 	@$(MD5SUM) test.hex | ||||||
| 	$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks  examples/blinky1 | 	$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks  examples/blinky1 | ||||||
| 	@$(MD5SUM) test.hex | 	@$(MD5SUM) test.hex | ||||||
| 	$(TINYGO) build -size short -o test.hex -target=arduino-mega1280             examples/blinky1 | 	$(TINYGO) build -size short -o test.hex -target=arduino-mega1280    examples/blinky1 | ||||||
|  | 	@$(MD5SUM) test.hex | ||||||
|  | 	$(TINYGO) build -size short -o test.hex -target=arduino-mega1280    examples/pwm | ||||||
| 	@$(MD5SUM) test.hex | 	@$(MD5SUM) test.hex | ||||||
| 	$(TINYGO) build -size short -o test.hex -target=arduino-nano        examples/blinky1 | 	$(TINYGO) build -size short -o test.hex -target=arduino-nano        examples/blinky1 | ||||||
| 	@$(MD5SUM) test.hex | 	@$(MD5SUM) test.hex | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								src/examples/pwm/arduino-mega1280.go
									
										
									
									
									
										Обычный файл
									
								
							
							
						
						
									
										12
									
								
								src/examples/pwm/arduino-mega1280.go
									
										
									
									
									
										Обычный файл
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | // +build arduino_mega1280 | ||||||
|  | 
 | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import "machine" | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// Configuration on an Arduino Uno. | ||||||
|  | 	pwm  = machine.Timer3 | ||||||
|  | 	pinA = machine.PH3 // pin 6 on the Mega | ||||||
|  | 	pinB = machine.PH4 // pin 7 on the Mega | ||||||
|  | ) | ||||||
|  | @ -5,6 +5,7 @@ package machine | ||||||
| import ( | import ( | ||||||
| 	"device/avr" | 	"device/avr" | ||||||
| 	"runtime/volatile" | 	"runtime/volatile" | ||||||
|  | 	"runtime/interrupt" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const irq_USART0_RX = avr.IRQ_USART0_RX | const irq_USART0_RX = avr.IRQ_USART0_RX | ||||||
|  | @ -127,6 +128,799 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // PWM is one PWM peripheral, which consists of a counter and two output | ||||||
|  | // channels (that can be connected to two fixed pins). You can set the frequency | ||||||
|  | // using SetPeriod, but only for all the channels in this PWM peripheral at | ||||||
|  | // once. | ||||||
|  | type PWM struct { | ||||||
|  | 	num uint8 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	Timer0 = PWM{0} // 8 bit timer for PB7 and PG5 | ||||||
|  | 	Timer1 = PWM{1} // 16 bit timer for PB5 and PB6 | ||||||
|  | 	Timer2 = PWM{2} // 8 bit timer for PB4 and PH6 | ||||||
|  | 	Timer3 = PWM{3} // 16 bit timer for PE3, PE4 and PE5 | ||||||
|  | 	Timer4 = PWM{4} // 16 bit timer for PH3, PH4 and PH5 | ||||||
|  | 	Timer5 = PWM{5} // 16 bit timer for PL3, PL4 and PL5 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Configure enables and configures this PWM. | ||||||
|  | // | ||||||
|  | // For the two 8 bit timers, there is only a limited number of periods | ||||||
|  | // available, namely the CPU frequency divided by 256 and again divided by 1, 8, | ||||||
|  | // 64, 256, or 1024. For a MCU running at 16MHz, this would be a period of 16µs, | ||||||
|  | // 128µs, 1024µs, 4096µs, or 16384µs. | ||||||
|  | func (pwm PWM) Configure(config PWMConfig) error { | ||||||
|  | 
 | ||||||
|  | 	switch pwm.num { | ||||||
|  | 	case 0, 2: // 8-bit timers (Timer/counter 0 and Timer/counter 2) | ||||||
|  | 		// Calculate the timer prescaler. | ||||||
|  | 		// While we could configure a flexible top, that would sacrifice one of | ||||||
|  | 		// the PWM output compare registers and thus a PWM channel. I've chosen | ||||||
|  | 		// to instead limit this timer to a fixed number of frequencies. | ||||||
|  | 		var prescaler uint8 | ||||||
|  | 		switch config.Period { | ||||||
|  | 		case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()): | ||||||
|  | 			prescaler = 1 | ||||||
|  | 		case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()): | ||||||
|  | 			prescaler = 2 | ||||||
|  | 		case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()): | ||||||
|  | 			prescaler = 3 | ||||||
|  | 		case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()): | ||||||
|  | 			prescaler = 4 | ||||||
|  | 		case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()): | ||||||
|  | 			prescaler = 5 | ||||||
|  | 		default: | ||||||
|  | 			return ErrPWMPeriodTooLong | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if pwm.num == 0 { | ||||||
|  | 			avr.TCCR0B.Set(prescaler) | ||||||
|  | 			// Set the PWM mode to fast PWM (mode = 3). | ||||||
|  | 			avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) | ||||||
|  | 		} else { | ||||||
|  | 			avr.TCCR2B.Set(prescaler) | ||||||
|  | 			// Set the PWM mode to fast PWM (mode = 3). | ||||||
|  | 			avr.TCCR2A.Set(avr.TCCR2A_WGM20 | avr.TCCR2A_WGM21) | ||||||
|  | 		} | ||||||
|  | 	case 1, 3, 4, 5: | ||||||
|  | 		// The top value is the number of PWM ticks a PWM period takes. It is | ||||||
|  | 		// initially picked assuming an unlimited counter top and no PWM | ||||||
|  | 		// prescaler. | ||||||
|  | 		var top uint64 | ||||||
|  | 		if config.Period == 0 { | ||||||
|  | 			// Use a top appropriate for LEDs. Picking a relatively low period | ||||||
|  | 			// here (0xff) for consistency with the other timers. | ||||||
|  | 			top = 0xff | ||||||
|  | 		} else { | ||||||
|  | 			// The formula below calculates the following formula, optimized: | ||||||
|  | 			//     top = period * (CPUFrequency() / 1e9) | ||||||
|  | 			// By dividing the CPU frequency first (an operation that is easily | ||||||
|  | 			// optimized away) the period has less chance of overflowing. | ||||||
|  | 			top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 		// The ideal PWM period may be larger than would fit in the PWM counter, | ||||||
|  | 		// which is 16 bits (see maxTop). Therefore, try to make the PWM clock | ||||||
|  | 		// speed lower with a prescaler to make the top value fit the maximum | ||||||
|  | 		// top value. | ||||||
|  | 
 | ||||||
|  | 		const maxTop = 0x10000 | ||||||
|  | 		var prescalingTop uint8 | ||||||
|  | 		switch { | ||||||
|  | 		case top <= maxTop: | ||||||
|  | 			prescalingTop = 3<<3 | 1 // no prescaling | ||||||
|  | 		case top/8 <= maxTop: | ||||||
|  | 			prescalingTop = 3<<3 | 2 // divide by 8 | ||||||
|  | 			top /= 8 | ||||||
|  | 		case top/64 <= maxTop: | ||||||
|  | 			prescalingTop = 3<<3 | 3 // divide by 64 | ||||||
|  | 			top /= 64 | ||||||
|  | 		case top/256 <= maxTop: | ||||||
|  | 			prescalingTop = 3<<3 | 4 // divide by 256 | ||||||
|  | 			top /= 256 | ||||||
|  | 		case top/1024 <= maxTop: | ||||||
|  | 			prescalingTop = 3<<3 | 5 // divide by 1024 | ||||||
|  | 			top /= 1024 | ||||||
|  | 		default: | ||||||
|  | 			return ErrPWMPeriodTooLong | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// A top of 0x10000 is at 100% duty cycle. Subtract one because the | ||||||
|  | 		// counter counts from 0, not 1 (avoiding an off-by-one). | ||||||
|  | 		top -= 1 | ||||||
|  | 
 | ||||||
|  | 		switch pwm.num { | ||||||
|  | 		case 1: | ||||||
|  | 			avr.TCCR1A.Set(avr.TCCR1A_WGM11) | ||||||
|  | 			avr.TCCR1B.Set(prescalingTop) | ||||||
|  | 			avr.ICR1H.Set(uint8(top >> 8)) | ||||||
|  | 			avr.ICR1L.Set(uint8(top)) | ||||||
|  | 		case 3: | ||||||
|  | 			avr.TCCR3A.Set(avr.TCCR3A_WGM31) | ||||||
|  | 			avr.TCCR3B.Set(prescalingTop) | ||||||
|  | 			avr.ICR3H.Set(uint8(top >> 8)) | ||||||
|  | 			avr.ICR3L.Set(uint8(top)) | ||||||
|  | 		case 4: | ||||||
|  | 			avr.TCCR4A.Set(avr.TCCR4A_WGM41) | ||||||
|  | 			avr.TCCR4B.Set(prescalingTop) | ||||||
|  | 			avr.ICR4H.Set(uint8(top >> 8)) | ||||||
|  | 			avr.ICR4L.Set(uint8(top)) | ||||||
|  | 		case 5: | ||||||
|  | 			avr.TCCR5A.Set(avr.TCCR5A_WGM51) | ||||||
|  | 			avr.TCCR5B.Set(prescalingTop) | ||||||
|  | 			avr.ICR5H.Set(uint8(top >> 8)) | ||||||
|  | 			avr.ICR5L.Set(uint8(top)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetPeriod updates the period of this PWM peripheral. | ||||||
|  | // To set a particular frequency, use the following formula: | ||||||
|  | // | ||||||
|  | //     period = 1e9 / frequency | ||||||
|  | // | ||||||
|  | // If you use a period of 0, a period that works well for LEDs will be picked. | ||||||
|  | // | ||||||
|  | // SetPeriod will not change the prescaler, but also won't change the current | ||||||
|  | // value in any of the channels. This means that you may need to update the | ||||||
|  | // value for the particular channel. | ||||||
|  | // | ||||||
|  | // Note that you cannot pick any arbitrary period after the PWM peripheral has | ||||||
|  | // been configured. If you want to switch between frequencies, pick the lowest | ||||||
|  | // frequency (longest period) once when calling Configure and adjust the | ||||||
|  | // frequency here as needed. | ||||||
|  | func (pwm PWM) SetPeriod(period uint64) error { | ||||||
|  | 	if pwm.num == 0 || pwm.num == 2 { | ||||||
|  | 		return ErrPWMPeriodTooLong // TODO better error message | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// The top value is the number of PWM ticks a PWM period takes. It is | ||||||
|  | 	// initially picked assuming an unlimited counter top and no PWM | ||||||
|  | 	// prescaler. | ||||||
|  | 	var top uint64 | ||||||
|  | 	if period == 0 { | ||||||
|  | 		// Use a top appropriate for LEDs. Picking a relatively low period | ||||||
|  | 		// here (0xff) for consistency with the other timers. | ||||||
|  | 		top = 0xff | ||||||
|  | 	} else { | ||||||
|  | 		// The formula below calculates the following formula, optimized: | ||||||
|  | 		//     top = period * (CPUFrequency() / 1e9) | ||||||
|  | 		// By dividing the CPU frequency first (an operation that is easily | ||||||
|  | 		// optimized away) the period has less chance of overflowing. | ||||||
|  | 		top = period * (uint64(CPUFrequency()) / 1000000) / 1000 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var prescaler uint8 | ||||||
|  | 
 | ||||||
|  | 	switch pwm.num { | ||||||
|  | 	case 1: | ||||||
|  | 		prescaler = avr.TCCR1B.Get() & 0x7 | ||||||
|  | 	case 3: | ||||||
|  | 		prescaler = avr.TCCR3B.Get() & 0x7 | ||||||
|  | 	case 4: | ||||||
|  | 		prescaler = avr.TCCR4B.Get() & 0x7 | ||||||
|  | 	case 5: | ||||||
|  | 		prescaler = avr.TCCR5B.Get() & 0x7 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch prescaler { | ||||||
|  | 	case 1: | ||||||
|  | 		top /= 1 | ||||||
|  | 	case 2: | ||||||
|  | 		top /= 8 | ||||||
|  | 	case 3: | ||||||
|  | 		top /= 64 | ||||||
|  | 	case 4: | ||||||
|  | 		top /= 256 | ||||||
|  | 	case 5: | ||||||
|  | 		top /= 1024 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// A top of 0x10000 is at 100% duty cycle. Subtract one because the counter | ||||||
|  | 	// counts from 0, not 1 (avoiding an off-by-one). | ||||||
|  | 	top -= 1 | ||||||
|  | 
 | ||||||
|  | 	if top > 0xffff { | ||||||
|  | 		return ErrPWMPeriodTooLong | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch pwm.num { | ||||||
|  | 	case 1: | ||||||
|  | 		// Warning: this change is not atomic! | ||||||
|  | 		avr.ICR1H.Set(uint8(top >> 8)) | ||||||
|  | 		avr.ICR1L.Set(uint8(top)) | ||||||
|  | 
 | ||||||
|  | 		// ... and because of that, set the counter back to zero to avoid most of | ||||||
|  | 		// the effects of this non-atomicity. | ||||||
|  | 		avr.TCNT1H.Set(0) | ||||||
|  | 		avr.TCNT1L.Set(0) | ||||||
|  | 	case 3: | ||||||
|  | 		// Warning: this change is not atomic! | ||||||
|  | 		avr.ICR3H.Set(uint8(top >> 8)) | ||||||
|  | 		avr.ICR3L.Set(uint8(top)) | ||||||
|  | 
 | ||||||
|  | 		// ... and because of that, set the counter back to zero to avoid most of | ||||||
|  | 		// the effects of this non-atomicity. | ||||||
|  | 		avr.TCNT3H.Set(0) | ||||||
|  | 		avr.TCNT3L.Set(0) | ||||||
|  | 	case 4: | ||||||
|  | 		// Warning: this change is not atomic! | ||||||
|  | 		avr.ICR4H.Set(uint8(top >> 8)) | ||||||
|  | 		avr.ICR4L.Set(uint8(top)) | ||||||
|  | 
 | ||||||
|  | 		// ... and because of that, set the counter back to zero to avoid most of | ||||||
|  | 		// the effects of this non-atomicity. | ||||||
|  | 		avr.TCNT4H.Set(0) | ||||||
|  | 		avr.TCNT4L.Set(0) | ||||||
|  | 	case 5: | ||||||
|  | 		// Warning: this change is not atomic! | ||||||
|  | 		avr.ICR5H.Set(uint8(top >> 8)) | ||||||
|  | 		avr.ICR5L.Set(uint8(top)) | ||||||
|  | 
 | ||||||
|  | 		// ... and because of that, set the counter back to zero to avoid most of | ||||||
|  | 		// the effects of this non-atomicity. | ||||||
|  | 		avr.TCNT5H.Set(0) | ||||||
|  | 		avr.TCNT5L.Set(0) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Top returns the current counter top, for use in duty cycle calculation. It | ||||||
|  | // will only change with a call to Configure or SetPeriod, otherwise it is | ||||||
|  | // constant. | ||||||
|  | // | ||||||
|  | // The value returned here is hardware dependent. In general, it's best to treat | ||||||
|  | // it as an opaque value that can be divided by some number and passed to Set | ||||||
|  | // (see Set documentation for more information). | ||||||
|  | func (pwm PWM) Top() uint32 { | ||||||
|  | 	switch pwm.num { | ||||||
|  | 	case 1: | ||||||
|  | 		// Timer 1 has a configurable top value. | ||||||
|  | 		low := avr.ICR1L.Get() | ||||||
|  | 		high := avr.ICR1H.Get() | ||||||
|  | 		return uint32(high)<<8 | uint32(low) + 1 | ||||||
|  | 	case 3: | ||||||
|  | 		// Timer 3 has a configurable top value. | ||||||
|  | 		low := avr.ICR3L.Get() | ||||||
|  | 		high := avr.ICR3H.Get() | ||||||
|  | 		return uint32(high)<<8 | uint32(low) + 1 | ||||||
|  | 	case 4: | ||||||
|  | 		// Timer 4 has a configurable top value. | ||||||
|  | 		low := avr.ICR4L.Get() | ||||||
|  | 		high := avr.ICR4H.Get() | ||||||
|  | 		return uint32(high)<<8 | uint32(low) + 1 | ||||||
|  | 	case 5: | ||||||
|  | 		// Timer 5 has a configurable top value. | ||||||
|  | 		low := avr.ICR5L.Get() | ||||||
|  | 		high := avr.ICR5H.Get() | ||||||
|  | 		return uint32(high)<<8 | uint32(low) + 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Other timers go from 0 to 0xff (0x100 or 256 in total). | ||||||
|  | 	return 256 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Counter returns the current counter value of the timer in this PWM | ||||||
|  | // peripheral. It may be useful for debugging. | ||||||
|  | func (pwm PWM) Counter() uint32 { | ||||||
|  | 	switch pwm.num { | ||||||
|  | 	case 0: | ||||||
|  | 		return uint32(avr.TCNT0.Get()) | ||||||
|  | 	case 1: | ||||||
|  | 		mask := interrupt.Disable() | ||||||
|  | 		low := avr.TCNT1L.Get() | ||||||
|  | 		high := avr.TCNT1H.Get() | ||||||
|  | 		interrupt.Restore(mask) | ||||||
|  | 		return uint32(high)<<8 | uint32(low) | ||||||
|  | 	case 2: | ||||||
|  | 		return uint32(avr.TCNT2.Get()) | ||||||
|  | 	case 3: | ||||||
|  | 		mask := interrupt.Disable() | ||||||
|  | 		low := avr.TCNT3L.Get() | ||||||
|  | 		high := avr.TCNT3H.Get() | ||||||
|  | 		interrupt.Restore(mask) | ||||||
|  | 		return uint32(high)<<8 | uint32(low) | ||||||
|  | 	case 4: | ||||||
|  | 		mask := interrupt.Disable() | ||||||
|  | 		low := avr.TCNT4L.Get() | ||||||
|  | 		high := avr.TCNT4H.Get() | ||||||
|  | 		interrupt.Restore(mask) | ||||||
|  | 		return uint32(high)<<8 | uint32(low) | ||||||
|  | 	case 5: | ||||||
|  | 		mask := interrupt.Disable() | ||||||
|  | 		low := avr.TCNT5L.Get() | ||||||
|  | 		high := avr.TCNT5H.Get() | ||||||
|  | 		interrupt.Restore(mask) | ||||||
|  | 		return uint32(high)<<8 | uint32(low) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Unknown PWM. | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Period returns the used PWM period in nanoseconds. It might deviate slightly | ||||||
|  | // from the configured period due to rounding. | ||||||
|  | func (pwm PWM) Period() uint64 { | ||||||
|  | 	var prescaler uint8 | ||||||
|  | 	switch pwm.num { | ||||||
|  | 	case 0: | ||||||
|  | 		prescaler = avr.TCCR0B.Get() & 0x7 | ||||||
|  | 	case 1: | ||||||
|  | 		prescaler = avr.TCCR1B.Get() & 0x7 | ||||||
|  | 	case 2: | ||||||
|  | 		prescaler = avr.TCCR2B.Get() & 0x7 | ||||||
|  | 	case 3: | ||||||
|  | 		prescaler = avr.TCCR3B.Get() & 0x7 | ||||||
|  | 	case 4: | ||||||
|  | 		prescaler = avr.TCCR4B.Get() & 0x7 | ||||||
|  | 	case 5: | ||||||
|  | 		prescaler = avr.TCCR5B.Get() & 0x7 | ||||||
|  | 	} | ||||||
|  | 	top := uint64(pwm.Top()) | ||||||
|  | 	switch prescaler { | ||||||
|  | 	case 1: // prescaler 1 | ||||||
|  | 		return 1 * top * 1000 / uint64(CPUFrequency()/1e6) | ||||||
|  | 	case 2: // prescaler 8 | ||||||
|  | 		return 8 * top * 1000 / uint64(CPUFrequency()/1e6) | ||||||
|  | 	case 3: // prescaler 64 | ||||||
|  | 		return 64 * top * 1000 / uint64(CPUFrequency()/1e6) | ||||||
|  | 	case 4: // prescaler 256 | ||||||
|  | 		return 256 * top * 1000 / uint64(CPUFrequency()/1e6) | ||||||
|  | 	case 5: // prescaler 1024 | ||||||
|  | 		return 1024 * top * 1000 / uint64(CPUFrequency()/1e6) | ||||||
|  | 	default: // unknown clock source | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Channel returns a PWM channel for the given pin. | ||||||
|  | func (pwm PWM) Channel(pin Pin) (uint8, error) { | ||||||
|  | 	pin.Configure(PinConfig{Mode: PinOutput}) | ||||||
|  | 	pin.Low() | ||||||
|  | 	switch pwm.num { | ||||||
|  | 	case 0: | ||||||
|  | 		switch pin { | ||||||
|  | 		case PB7: // channel A | ||||||
|  | 			avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) | ||||||
|  | 			return 0, nil | ||||||
|  | 		case PG5: // channel B | ||||||
|  | 			avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) | ||||||
|  | 			return 1, nil | ||||||
|  | 		} | ||||||
|  | 	case 1: | ||||||
|  | 		switch pin { | ||||||
|  | 		case PB5: // channel A | ||||||
|  | 			avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) | ||||||
|  | 			return 0, nil | ||||||
|  | 		case PB6: // channel B | ||||||
|  | 			avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) | ||||||
|  | 			return 1, nil | ||||||
|  | 		} | ||||||
|  | 	case 2: | ||||||
|  | 		switch pin { | ||||||
|  | 		case PB4: // channel A | ||||||
|  | 			avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) | ||||||
|  | 			return 0, nil | ||||||
|  | 		case PH6: // channel B | ||||||
|  | 			avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) | ||||||
|  | 			return 1, nil | ||||||
|  | 		} | ||||||
|  | 	case 3: | ||||||
|  | 		switch pin { | ||||||
|  | 		case PE3: // channel A | ||||||
|  | 			avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1) | ||||||
|  | 			return 0, nil | ||||||
|  | 		case PE4: //channel B | ||||||
|  | 			avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1) | ||||||
|  | 			return 1, nil | ||||||
|  | 		case PE5: //channel C | ||||||
|  | 			avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1) | ||||||
|  | 			return 2, nil | ||||||
|  | 		} | ||||||
|  | 	case 4: | ||||||
|  | 		switch pin { | ||||||
|  | 		case PH3: // channel A | ||||||
|  | 			avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1) | ||||||
|  | 			return 0, nil | ||||||
|  | 		case PH4: //channel B | ||||||
|  | 			avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1) | ||||||
|  | 			return 1, nil | ||||||
|  | 		case PH5: //channel C | ||||||
|  | 			avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1) | ||||||
|  | 			return 2, nil | ||||||
|  | 		} | ||||||
|  | 	case 5: | ||||||
|  | 		switch pin { | ||||||
|  | 		case PL3: // channel A | ||||||
|  | 			avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1) | ||||||
|  | 			return 0, nil | ||||||
|  | 		case PL4: //channel B | ||||||
|  | 			avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1) | ||||||
|  | 			return 1, nil | ||||||
|  | 		case PL5: //channel C | ||||||
|  | 			avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1) | ||||||
|  | 			return 2, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return 0, ErrInvalidOutputPin | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetInverting sets whether to invert the output of this channel. | ||||||
|  | // Without inverting, a 25% duty cycle would mean the output is high for 25% of | ||||||
|  | // the time and low for the rest. Inverting flips the output as if a NOT gate | ||||||
|  | // was placed at the output, meaning that the output would be 25% low and 75% | ||||||
|  | // high with a duty cycle of 25%. | ||||||
|  | // | ||||||
|  | // Note: the invert state may not be applied on the AVR until the next call to | ||||||
|  | // ch.Set(). | ||||||
|  | func (pwm PWM) SetInverting(channel uint8, inverting bool) { | ||||||
|  | 	switch pwm.num { | ||||||
|  | 	case 0: | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A, PB7 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTB.SetBits(1 << 7) // PB7 high | ||||||
|  | 				avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0) | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTB.ClearBits(1 << 7) // PB7 low | ||||||
|  | 				avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0) | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B, PG5 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTG.SetBits(1 << 5) // PG5 high | ||||||
|  | 				avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0) | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTG.ClearBits(1 << 5) // PG5 low | ||||||
|  | 				avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case 1: | ||||||
|  | 		// Note: the COM1A0/COM1B0 bit is not set with the configuration below. | ||||||
|  | 		// It will be set the following call to Set(), however. | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A, PB5 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTB.SetBits(1 << 5) // PB5 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTB.ClearBits(1 << 5) // PB5 low | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B, PB6 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTB.SetBits(1 << 6) // PB6 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTB.ClearBits(1 << 6) // PB6 low | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case 2: | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A, PB4 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTB.SetBits(1 << 4) // PB4 high | ||||||
|  | 				avr.TCCR2A.SetBits(avr.TCCR2A_COM2A0) | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTB.ClearBits(1 << 4) // PB4 low | ||||||
|  | 				avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A0) | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B, PH6 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTH.SetBits(1 << 6) // PH6 high | ||||||
|  | 				avr.TCCR2A.SetBits(avr.TCCR2A_COM2B0) | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTH.ClearBits(1 << 6) // PH6 low | ||||||
|  | 				avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B0) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case 3: | ||||||
|  | 		// Note: the COM3A0/COM3B0 bit is not set with the configuration below. | ||||||
|  | 		// It will be set the following call to Set(), however. | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A, PE3 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTE.SetBits(1 << 3) // PE3 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTE.ClearBits(1 << 3) // PE3 low | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B, PE4 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTE.SetBits(1 << 4) // PE4 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTE.ClearBits(1 << 4) // PE4 low | ||||||
|  | 			} | ||||||
|  | 		case 2: // channel C, PE5 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTE.SetBits(1 << 5) // PE4 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTE.ClearBits(1 << 5) // PE4 low | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case 4: | ||||||
|  | 		// Note: the COM3A0/COM3B0 bit is not set with the configuration below. | ||||||
|  | 		// It will be set the following call to Set(), however. | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A, PH3 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTH.SetBits(1 << 3) // PH3 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTH.ClearBits(1 << 3) // PH3 low | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B, PH4 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTH.SetBits(1 << 4) // PH4 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTH.ClearBits(1 << 4) // PH4 low | ||||||
|  | 			} | ||||||
|  | 		case 2: // channel C, PH5 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTH.SetBits(1 << 5) // PH4 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTH.ClearBits(1 << 5) // PH4 low | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case 5: | ||||||
|  | 		// Note: the COM3A0/COM3B0 bit is not set with the configuration below. | ||||||
|  | 		// It will be set the following call to Set(), however. | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A, PL3 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTL.SetBits(1 << 3) // PL3 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTL.ClearBits(1 << 3) // PL3 low | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B, PL4 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTL.SetBits(1 << 4) // PL4 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTL.ClearBits(1 << 4) // PL4 low | ||||||
|  | 			} | ||||||
|  | 		case 2: // channel C, PH5 | ||||||
|  | 			if inverting { | ||||||
|  | 				avr.PORTL.SetBits(1 << 5) // PL4 high | ||||||
|  | 			} else { | ||||||
|  | 				avr.PORTL.ClearBits(1 << 5) // PL4 low | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Set updates the channel value. This is used to control the channel duty | ||||||
|  | // cycle, in other words the fraction of time the channel output is high (or low | ||||||
|  | // when inverted). For example, to set it to a 25% duty cycle, use: | ||||||
|  | // | ||||||
|  | //     pwm.Set(channel, pwm.Top() / 4) | ||||||
|  | // | ||||||
|  | // pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, | ||||||
|  | // pwm.Top()) will set the output to high, assuming the output isn't inverted. | ||||||
|  | func (pwm PWM) Set(channel uint8, value uint32) { | ||||||
|  | 	switch pwm.num { | ||||||
|  | 	case 0: | ||||||
|  | 		value := uint16(value) | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1) | ||||||
|  | 			} else { | ||||||
|  | 				avr.OCR0A.Set(uint8(value - 1)) | ||||||
|  | 				avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1) | ||||||
|  | 			} else { | ||||||
|  | 				avr.OCR0B.Set(uint8(value) - 1) | ||||||
|  | 				avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case 1: | ||||||
|  | 		mask := interrupt.Disable() | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A, PB5 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR1A.ClearBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR1AH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR1AL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTB.HasBits(1 << 5) { // is PB1 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B, PB6 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR1A.ClearBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR1BH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR1BL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTB.HasBits(1 << 6) { // is PB6 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		interrupt.Restore(mask) | ||||||
|  | 	case 2: | ||||||
|  | 		value := uint16(value) | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A1) | ||||||
|  | 			} else { | ||||||
|  | 				avr.OCR2A.Set(uint8(value - 1)) | ||||||
|  | 				avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B1) | ||||||
|  | 			} else { | ||||||
|  | 				avr.OCR2B.Set(uint8(value - 1)) | ||||||
|  | 				avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case 3: | ||||||
|  | 		mask := interrupt.Disable() | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A, PE3 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR3A.ClearBits(avr.TCCR3A_COM3A1 | avr.TCCR3A_COM3A0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR3AH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR3AL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTE.HasBits(1 << 3) { // is PE3 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1 | avr.TCCR3A_COM3A0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B, PE4 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR3A.ClearBits(avr.TCCR3A_COM3B1 | avr.TCCR3A_COM3B0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR3BH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR3BL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTE.HasBits(1 << 4) { // is PE4 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1 | avr.TCCR3A_COM3B0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		case 2: // channel C, PE5 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR3A.ClearBits(avr.TCCR3A_COM3C1 | avr.TCCR3A_COM3C0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR3CH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR3CL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTE.HasBits(1 << 5) { // is PE5 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1 | avr.TCCR3A_COM3C0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		interrupt.Restore(mask) | ||||||
|  | 	case 4: | ||||||
|  | 		mask := interrupt.Disable() | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A, PH3 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR4A.ClearBits(avr.TCCR4A_COM4A1 | avr.TCCR4A_COM4A0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR4AH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR4AL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTH.HasBits(1 << 3) { // is PH3 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1 | avr.TCCR4A_COM4A0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B, PH4 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR4A.ClearBits(avr.TCCR4A_COM4B1 | avr.TCCR4A_COM4B0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR4BH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR4BL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTH.HasBits(1 << 4) { // is PH4 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1 | avr.TCCR4A_COM4B0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		case 2: // channel C, PH5 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR4A.ClearBits(avr.TCCR4A_COM4C1 | avr.TCCR4A_COM4C0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR4CH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR4CL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTH.HasBits(1 << 5) { // is PH5 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1 | avr.TCCR4A_COM4C0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		interrupt.Restore(mask) | ||||||
|  | 	case 5: | ||||||
|  | 		mask := interrupt.Disable() | ||||||
|  | 		switch channel { | ||||||
|  | 		case 0: // channel A, PL3 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR5A.ClearBits(avr.TCCR5A_COM5A1 | avr.TCCR5A_COM5A0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR5AH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR5AL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTL.HasBits(1 << 3) { // is PL3 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1 | avr.TCCR5A_COM5A0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		case 1: // channel B, PL4 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR5A.ClearBits(avr.TCCR5A_COM5B1 | avr.TCCR5A_COM5B0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR5BH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR5BL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTL.HasBits(1 << 4) { // is PL4 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1 | avr.TCCR5A_COM5B0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		case 2: // channel C, PL5 | ||||||
|  | 			if value == 0 { | ||||||
|  | 				avr.TCCR5A.ClearBits(avr.TCCR5A_COM5C1 | avr.TCCR5A_COM5C0) | ||||||
|  | 			} else { | ||||||
|  | 				value := uint16(value) - 1 // yes, this is safe (it relies on underflow) | ||||||
|  | 				avr.OCR5CH.Set(uint8(value >> 8)) | ||||||
|  | 				avr.OCR5CL.Set(uint8(value)) | ||||||
|  | 				if avr.PORTL.HasBits(1 << 5) { // is PL5 high? | ||||||
|  | 					// Yes, set the inverting bit. | ||||||
|  | 					avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1 | avr.TCCR5A_COM5C0) | ||||||
|  | 				} else { | ||||||
|  | 					// No, output is non-inverting. | ||||||
|  | 					avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		interrupt.Restore(mask) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // SPI configuration | // SPI configuration | ||||||
| var SPI0 = SPI{ | var SPI0 = SPI{ | ||||||
| 	spcr: avr.SPCR, | 	spcr: avr.SPCR, | ||||||
|  |  | ||||||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 developer
						developer