diff --git a/I2C_Slave_Tiny.ino b/I2C_Slave_Tiny.ino deleted file mode 100644 index 18ac5da..0000000 --- a/I2C_Slave_Tiny.ino +++ /dev/null @@ -1,179 +0,0 @@ - -#define F_CPU 8000000 - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -#include "TinyWireS.h" -//#include "usiTwiSlave.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -#define I2C_SLAVE_ADDR 0x2F -#define PIN_2__PB_3 3 // ATtiny Pin 2 // used by adc3 -#define PIN_3__PB_4 4 // ATtiny Pin 3 // used as pwm -#define PIN_6__PB_1 1 // ATtiny Pin 6 - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -bool bTrigger[] = {false, false}; -unsigned int uiPwm[] = {0, 0}; -uint8_t u8Received = 0; -uint8_t u8Command = 0; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -void initADC() { - - ADCSRA = (1 << ADPS2) | // set prescaler to 64, bit 2 ~~ for 8MHz below is valid - (1 << ADPS1) | // set prescaler to 64, bit 1 ~~ 1 1 0 = ps64 = 125kHz - (0 << ADPS0); // set prescaler to 64, bit 0 ~~ 1 1 1 = ps128 = 62.5kHz - - ADCSRA |= (1 << ADATE); // set auto trigger enable - ADCSRA |= (0 << ADTS2) | // set free running mode, bit 2 - (0 << ADTS1) | // set free running mode, bit 1 - (0 << ADTS0); // set free running mode, bit 0 - - - ADMUX = (1 << REFS2) | // Sets ref. voltage to VCC, bit 3 ~~ i2c makes use of ADREF pin, only internal vref can be used - (1 << REFS1) | // Sets ref. voltage to VCC, bit 1 ~~ 0 1 0 Internal 1.1V Voltage Reference. - (0 << REFS0); // Sets ref. voltage to VCC, bit 0 ~~ 1 1 0 Internal 2.56V Voltage Reference without external bypass capacitor, disconnected from PB0 (AREF) - - ADMUX = (0 << MUX3) | // MUX bit 3 ~~ 0 0 0 0 = ADC0 --> PB4 | Pin3, needs RESET fuse disabled, mcu can then not be reprogrammed. - (0 << MUX2) | // MUX bit 2 ~~ 0 0 0 1 = ADC1 --> PB2 | Pin7 - (1 << MUX1) | // MUX bit 1 ~~ 0 0 1 0 = ADC2 --> PB4 | Pin3 - (1 << MUX0); // MUX bit 0 ~~ 0 0 1 1 = ADC3 --> PB3 | Pin2 - - ADCSRA |= (1 << ADEN); // Enable ADC - - ADCSRA |= (1 << ADSC); // start ADC measurement - ADCL;ADCH; // The first ADC conversion result after switching voltage reference source may be inaccurate, and the user is advised to discard this result. -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -void initPWM() { - - //DDRB |= (1 << PB4) | (1 << PB1); // same as pinMode(PIN_3__PB_4, OUTPUT); ?? - - // PWM pins are 3 (OC1B), 5 (OC0A), and 6 (OC0B) - - TCCR0A = - (0 << COM0A1) | // set OC0A on compare match, clear at BOTTOM - (0 << COM0A0) | // set OC0A on compare match, clear at BOTTOM - (1 << COM0B1) | // set OC0B on compare match, clear at BOTTOM - (0 << COM0B0) | // set OC0B on compare match, clear at BOTTOM - //(-) - //(-) - (1 << WGM01) | // 0 1 1 = Fast PWM mode (1/3) - (1 << WGM00); // 0 1 1 = Fast PWM mode (2/3) - - // 1001 0001 - - TCCR0B = - (0 << FOC0A) | - (0 << FOC0B) | - //(-) - //(-) - (0 << WGM02) | // 0 1 1 = Fast PWM mode (3/3) - (1 << CS02) | - (0 << CS01) | - (0 << CS00); - // 0 0 1 -- No prescaling) - // 0 1 0 -- 8 (From prescaler) - // 0 1 1 -- 64 (From prescaler) - // 1 0 0 -- 256 (From prescaler) - // 1 0 1 -- 1024 (From prescaler) - - TCCR1 = 0< - /* //<--- Free Running Conversion - ADMUX = (0 << MUX3) | // MUX bit 3 ~~ 0 0 0 0 = ADC0 --> PB4 | Pin3, needs RESET fuse disabled, mcu can then not be reprogrammed. - (0 << MUX2) | // MUX bit 2 ~~ 0 0 0 1 = ADC1 --> PB2 | Pin7 - (1 << MUX1) | // MUX bit 1 ~~ 0 0 1 0 = ADC2 --> PB4 | Pin3 - (1 << MUX0); // MUX bit 0 ~~ 0 0 1 1 = ADC3 --> PB3 | Pin2 - ADCSRA |= (1 << ADSC); // start ADC measurement, needed when auto trigger enable is 0 (= single conversion) - */ //<--- Free Running Conversion - // <--- Single Conversion on ADC 2 - - /* - TinyWireS.send(ADCL); // read lower byte - TinyWireS.send(ADCH); // read upper byte - */ - - TinyWireS.send(lowByte(uiPwm[0])); - TinyWireS.send(highByte(uiPwm[0])); - } - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - else if (u8Command == 0x04) { - /* - // single conversion on ADC 0 ---> - / * //<--- Free Running Conversion - ADMUX = (0 << MUX3) | // MUX bit 3 ~~ 0 0 0 0 = ADC0 --> PB4 | Pin3, needs RESET fuse disabled, mcu can then not be reprogrammed. - (0 << MUX2) | // MUX bit 2 ~~ 0 0 0 1 = ADC1 --> PB2 | Pin7 - (0 << MUX1) | // MUX bit 1 ~~ 0 0 1 0 = ADC2 --> PB4 | Pin3 - (0 << MUX0); // MUX bit 0 ~~ 0 0 1 1 = ADC3 --> PB3 | Pin2 - ADCSRA |= (1 << ADSC); // start ADC measurement, needed when auto trigger enable is 0 (= single conversion) - * / //<--- Free Running Conversion - // <--- single conversion on ADC 0 - - TinyWireS.send(ADCL); // read lower byte - TinyWireS.send(ADCH); // read upper byte - */ - - TinyWireS.send(lowByte(uiPwm[1])); - TinyWireS.send(highByte(uiPwm[1])); - } - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - } -} diff --git a/I2C_Slave_Tiny/I2C_Slave_Tiny.ino b/I2C_Slave_Tiny/I2C_Slave_Tiny.ino new file mode 100644 index 0000000..55ff0a2 --- /dev/null +++ b/I2C_Slave_Tiny/I2C_Slave_Tiny.ino @@ -0,0 +1,212 @@ + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#include "TinyWireS.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#define I2C_SLAVE_ADDR 0x2F + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +uint8_t u8Pwm0 = 0; +uint8_t u8Pwm1 = 0; +uint8_t u8Command = 0; + +const uint8_t adc_is_freerunning = 0; // don't use freerunning when using multiple adc's + // changing ADMUX while freerunning may make things difficult + // and single conversion seems to work just fine. + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +void initADC() { + + ADCSRA = (1 << ADPS2) | // set prescaler to 64, bit 2 ~~ for 8MHz below is valid + (1 << ADPS1) | // set prescaler to 64, bit 1 ~~ 1 1 0 = ps64 = 125kHz + (0 << ADPS0); // set prescaler to 64, bit 0 ~~ 1 1 1 = ps128 = 62.5kHz + + ADCSRA |= (adc_is_freerunning << ADATE); // set auto trigger enable + ADCSRA |= (0 << ADTS2) | // set free running mode, bit 2 + (0 << ADTS1) | // set free running mode, bit 1 + (0 << ADTS0); // set free running mode, bit 0 + + + ADMUX = (1 << REFS2) | // Sets ref. voltage to VCC, bit 3 ~~ i2c makes use of ADREF pin, only internal vref can be used + (1 << REFS1) | // Sets ref. voltage to VCC, bit 1 ~~ 0 1 0 Internal 1.1V Voltage Reference. + (0 << REFS0); // Sets ref. voltage to VCC, bit 0 ~~ 1 1 0 Internal 2.56V Voltage Reference without external bypass capacitor, disconnected from PB0 (AREF) + + ADMUX = (0 << MUX3) | // MUX bit 3 ~~ 0 0 0 0 = ADC0 --> PB4 | Pin3, needs RESET fuse disabled, mcu can then not be reprogrammed. + (0 << MUX2) | // MUX bit 2 ~~ 0 0 0 1 = ADC1 --> PB2 | Pin7 + (1 << MUX1) | // MUX bit 1 ~~ 0 0 1 0 = ADC2 --> PB4 | Pin3 + (1 << MUX0); // MUX bit 0 ~~ 0 0 1 1 = ADC3 --> PB3 | Pin2 + + ADCSRA |= (1 << ADEN); // Enable ADC + + ADCSRA |= (1 << ADSC); // Start ADC measurement + ADCL; ADCH; // "The first ADC conversion result after switching voltage reference source may be inaccurate, and the user is advised to discard this result." +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +void initPWM() { + + DDRB |= (1 << PB1) | (1 << PB4); + + // Buzzer is on Counter 0 ( OC0B ) PB1 = pin 6 + // LED is on Counter 1 ( OC1B compl. ) PB1 = pin 6 + // OC0A cannot be used, because PB0 = pin 5 is used by I2C (SDA), this frees up OCR0A which we will use as TOP for Counter 0 (via WGM0x) to get a variable frequency for the Buzzer + // OC1A might have been used on PB1 = pin 6 for the Buzzer, but luckily OC0B did the job just fine so that this didn't get tested. + // I guess that using OC1A wouldn't work, as we then couldn't use OCR1A as TOP for itself, since OCRA's are the only TOP that can be used in WGM0x. But it's just a guess. + + TCCR0A = // ------------ Timer/Counter Control Register A ------------ + (0 << COM0A1) | // Normal port operation, OC0A disconnected. | OC0A Pin is used by I2C and we will use OC0A's register as the top value + (0 << COM0A0) | // .. | for the Counter 0 to set the pitch of PWM OC0B + (1 << COM0B1) | // Clear OC0B on Compare Match when up-counting. Set OC0B on Compare Match when down-counting. <---- + (0 << COM0B0) | // .. <---- + //(-) + //(-) + (0 << WGM01) | // (1) 0 1 = PWM, Phase Correct with -->OCR0A<-- as TOP (1/3) <---- + (1 << WGM00); // (1) 0 1 = PWM, Phase Correct with -->OCR0A<-- as TOP (2/3) <---- + + TCCR0B = // ------------ Timer/Counter Control Register B ------------ + (0 << FOC0A) | + (0 << FOC0B) | + //(-) + //(-) + (1 << WGM02) | // 1 (0 1) = PWM, Phase Correct with -->OCR0A<-- as TOP (3/3) <---- + (1 << CS02) | // Prescaler used to pick the base frequency <--- + (0 << CS01) | // of the OC0B. <--- + (0 << CS00); // <--- + // 2/1/0 + // 0 0 1 No prescaling - ok + // 0 1 0 8 (From prescaler) - not ok + // 0 1 1 64 (From prescaler) - ok + // 1 0 0 256 (From prescaler) - ok + // 1 0 1 1024 (From prescaler) - ok + + TCCR1 = // ------------ Timer/Counter1 Control Register ------------ + (0 << CTC1) | + (0 << PWM1A) | + (0 << COM1A1) | + (0 << COM1A0) | + (1 << CS13) | // Prescaler used to pick the fixed <---- + (0 << CS12) | // frequency of the OCR1B PWM <---- + (0 << CS11) | // <---- + (1 << CS10); // <---- + // 3/2/1/0 + // 0 0 0 0 T/C1 stopped T/C1 stopped + // 0 0 0 1 P + // 0 0 1 0 2 + // 0 0 1 1 4 + // 0 1 0 0 8s + // 0 1 0 1 16 + // 0 1 1 0 32 + // 0 1 1 1 64 <---- more limited 'dynamic range' + // 1 0 0 1 256 <---- limited 'dynamic range' + // 1 0 0 1 256 <---- no flickering on LED, good 'dynamic range' + // 1 0 1 0 512 <---- noticeable flickering + // 1 0 1 1 1024 <---- more noticeable flickering + // 1 1 0 0 2048 + // 1 1 0 1 4096 + // 1 1 1 0 8192 + // 1 1 1 1 16384 + + GTCCR = // ------------ General Timer/Counter1 Control Register ------------ + (0 << TSM) | + (1 << PWM1B) | + (1 << COM1B1) | // 0 1 - PB4 (pin3) - COM1B 0=0V + (0 << COM1B0) | // 1 0 - PB3 (pin2) - COM1B complementary 0=5V <---- + (0 << FOC1B) | + (0 << FOC1A) | + (0 << PSR1) | + (0 << PSR0); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +void setup(){ + initADC(); + initPWM(); + TinyWireS.begin(I2C_SLAVE_ADDR); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +void loop() { + if (TinyWireS.available()) { + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + u8Command = TinyWireS.receive(); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + if (u8Command == 0x00) {} // Nothing done here, maybe implemet a reset ? + else if (u8Command == 0x01) {} // Nothing done here... + else if (u8Command == 0x02) { // Write to PWM0 = OCR1B, which is the PWM of the LED + u8Pwm0 = TinyWireS.receive(); + OCR1B = u8Pwm0; + return; + } + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + else if (u8Command == 0x03) { // Read from PWM0 = OCR1B, which is the PWM of the LED + TinyWireS.send(u8Pwm0); + return; + } + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + else if (u8Command == 0x04) { // Write to PWM1 = OCR0B and OCR0A, which is the PWM and looping point of the Buzzer + // OCR0A sets looping point on the counter: frequency + // OCR0B sets the pulse width, = OCR0A / 2 obviously. + u8Pwm1 = TinyWireS.receive(); + if (!u8Pwm1) { + OCR0B = 0; + OCR0A = 1; // just in case, force this to 1, may not be neccesary + } + else { + OCR0B = u8Pwm1 * 0.5; + OCR0A = u8Pwm1; + } + return; + } + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + else if (u8Command == 0x05) { // Read from PWM1 = OCR0B and OCR0A, which is the PWM and looping point of the Buzzer + TinyWireS.send(u8Pwm1); + return; + } + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + else if (u8Command == 0x06) { // Read from ADC2 (PB4 | Pin3) + if (!adc_is_freerunning) { // ADC 2, single conversion + ADMUX = (0 << MUX3) | // MUX bit 3 ~~ 0 0 0 0 = ADC0 --> PB4 | Pin3, needs RESET fuse disabled, mcu can then not be reprogrammed. + (0 << MUX2) | // MUX bit 2 ~~ 0 0 0 1 = ADC1 --> PB2 | Pin7 + (1 << MUX1) | // MUX bit 1 ~~ 0 0 1 0 = ADC2 --> PB4 | Pin3 + (1 << MUX0); // MUX bit 0 ~~ 0 0 1 1 = ADC3 --> PB3 | Pin2 + ADCSRA |= (1 << ADSC); // start ADC measurement, needed when auto trigger enable is 0 (= single conversion) + while (ADCSRA & (1 << ADSC)); // wait until conversion is done + } + TinyWireS.send(ADCL); + TinyWireS.send(ADCH); + return; + } + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + else if (u8Command == 0x07) { + // Apparently the only free ADC is ADC0 | PB4 | Pin3, but in order to use it, the RESET fuse needs to get disabled. + // This will most probably result in the inability to reprogram this chip, but if you know that you won't need to do + // that again, you gain one ADC channel. You won't be able to change the I2C_SLAVE_ADDR then, so keep that in mind. + // But I2C_SLAVE_ADDR could be written to EEPROM and be writable by the host... + /* + if (!adc_is_freerunning) { // ADC 0, single conversion + ADMUX = (0 << MUX3) | // MUX bit 3 ~~ 0 0 0 0 = ADC0 --> PB4 | Pin3, needs RESET fuse disabled, mcu can then not be reprogrammed. + (0 << MUX2) | // MUX bit 2 ~~ 0 0 0 1 = ADC1 --> PB2 | Pin7 + (0 << MUX1) | // MUX bit 1 ~~ 0 0 1 0 = ADC2 --> PB4 | Pin3 + (0 << MUX0); // MUX bit 0 ~~ 0 0 1 1 = ADC3 --> PB3 | Pin2 + ADCSRA |= (1 << ADSC); // start ADC measurement, needed when auto trigger enable is 0 (= single conversion) + while (ADCSRA & (1 << ADSC)); // wait until conversion is done + } + TinyWireS.send(ADCL); + TinyWireS.send(ADCH); + */ + TinyWireS.send(0); // Not implemented, return 0 + TinyWireS.send(0); // Not implemented, return 0 + return; + } + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + } +} diff --git a/README b/README new file mode 100644 index 0000000..6c89968 --- /dev/null +++ b/README @@ -0,0 +1,14 @@ + + +Here's an ATtiny45 with one 10-bit ADC pin and two 8-bit PWM pins. The ADC is used to read the value of a potentiometer. One PWM pin is used to control a LED-driver, the other one a piezo buzzer. + +The ATtiny is an I2C slave to a Raspberry Pi, which can read out the ATtiny's ADC and set/get the PWM. One PWM pin has a fixed frequency and the pulse width is modulated, this is used to dim the LED, the other PWM pin has a variable frequency, so that the buzzer can buzz at different frequencies. A second ADC could be added on the reset pin, but that would cause the chip not to be reprogrammable anymore. + +The I2C communication seems to be unstable, as "IOError: [Errno 5] Input/output error" do occur every now and then. In the video you can notice this by a stuck note. + +The Raspberry Pi is hosting a Twisted server which, when a certain page of it gets requested, runs the following sequence: Read the 10-bit ADC, divide it by 4 and write that value to the LED pin, then play a fixed sequence of 3 notes. + +The ATtiny45 gets programmed live (no wiring need to be changed) through an Arduino UNO which has the ArduinoIDE sketch loaded onto it. + +https://www.youtube.com/watch?v=QWF8_Mfo1ek + diff --git a/gpio_server/Makefile b/gpio_server/Makefile new file mode 100644 index 0000000..5d1c1f4 --- /dev/null +++ b/gpio_server/Makefile @@ -0,0 +1,21 @@ +.PHONY: all +all: pi-blaster + +pi-blaster: pi-blaster.c + gcc -Wall -g -O2 -o $@ $< + +clean: + rm -f pi-blaster + +install: pi-blaster + cp -f pi-blaster.boot.sh /etc/init.d/pi-blaster + chmod +x /etc/init.d/pi-blaster + cp -f pi-blaster /usr/sbin/pi-blaster + update-rc.d pi-blaster defaults + /etc/init.d/pi-blaster start + +uninstall: + -/etc/init.d/pi-blaster stop + rm /usr/sbin/pi-blaster + rm /etc/init.d/pi-blaster + update-rc.d pi-blaster remove diff --git a/gpio_server/README.md b/gpio_server/README.md new file mode 100644 index 0000000..915c7c7 --- /dev/null +++ b/gpio_server/README.md @@ -0,0 +1,141 @@ +pi-blaster +========== + +This project enables PWM on all the GPIO pins of a Raspberry Pi. The technique used is extremely efficient: does not use the CPU and gives very stable pulses. + +This project is based on the excellent work of Richard Hirst for ServoBlaster: https://github.com/richardghirst/PiBits + +## How to build and install + +This project is only distributed as source files. Building it is extremely simple. + + make + +To start pi-blaster and have it relaunched automatically on every reboot: + + sudo make install + +## How to start manually + +To start pi-blaster manually run: + + sudo ./pi-blaster + +## How to uninstall + +Simply run: + + sudo make uninstall + +This will stop pi-blaster and prevent it from starting automatically on the next reboot. + +## How to use + +pi-blaster creates a special file (FIFO) in `/dev/pi-blaster`. Any application on your Raspberry Pi can write to it (this means that only pi-blaster needs to be root, your application can run as a normal user). + +**Important: when using pi-blaster, all the GPIO pins are configured as output.** + +To set the value of a PIN, you write a command to `/dev/pi-blaster` in the form `=` where `` must be a number between 0 and 1 (included). + + Channel number GPIO number Pin in P1 header + 0 4 P1-7 + 1 17 P1-11 + 2 18 P1-12 + 3 21 P1-13 + 4 22 P1-15 + 5 23 P1-16 + 6 24 P1-18 + 7 25 P1-22 + +Examples: + * To completely turn off pin0: + + echo "0=0" > /dev/pi-blaster + + * To completely turn on pin1: + + echo "1=1" > /dev/pi-blaster + + * To set pin1 to a PWM of 20% + + echo "1=0.2" > /dev/pi-blaster + +### NodeJS Library + +NodeJS users can use [pi-blaster.js](https://github.com/sarfata/pi-blaster.js). + +### C# + +A C# example was contributed by [Vili Volcini](https://plus.google.com/109312219443477679717/posts). It is available on [this stackoverflow thread](http://stackoverflow.com/questions/17241071/writing-to-fifo-file-linux-monoc). + +## How to adjust the frequency and the resolution of the PWM + +On startup, pi-blaster gives you the frequency of the PWM, the number of steps that you can control, the maximum and the minimum period of a pulse. + + sudo ./pi-blaster + Using hardware: PWM + Number of channels: 8 + PWM frequency: 100 Hz + PWM steps: 1000 + Maximum period (100 %): 10000us + Minimum period (0.100%): 10us + +You can adjust those by changing a few defines at the top of the source code: + + * `NUM_SAMPLES`: The number of steps + * `SAMPLE_US`: The time of one step (minimum period) + +If you do not neet a resolution of 1000 steps (approximately equivalent to a 10 bit DAC), then you can reduce the number of samples or increase the duration of the steps. + +Richard Hirst who wrote the original code recommended not going below 2us for `SAMPLE_US`. + +## Options + +To use the BCM2835's PCM peripheral instead of its PWM peripheral to time the DMA transfers, pass the option: + + --pcm + +This is useful if you are already using the chip's PWM peripheral, for example for audio output. + +To invert the pulse (off = pin HIGH, pulse = pin LOW), use: + + --invert + +This can be useful for common anode LEDs or other devices that expect an active-low signal. + +To view help or version information, use: + + --help + + --version + +## Warnings and other caveats + +**All the pins will be configured as outputs. Do not plug something on an input or you might destroy it!** + +This daemon uses the hardware PWM generator of the raspberry pi to get precise timings. This might interfere with your sound card output. +There is experimental support for a PCM time-source. If you are interested, I suggest you look at Richard Hirst original project (ServoBlaster) and try the `--pcm` option. + +## A practical example: high-power RGB lighting + +This library was developed for TBideas high power LED driver. You can read more about this project on [our blog][blog]. + +## Contributors + +Pete Nelson (https://github.com/petiepooo) + +## License + +Released under The MIT License. + +Note: This project was initially released by Richard Hist under the GPL v3 License. Richard gave me explicit permission to distribute this derivative work under the MIT License. + + Copyright (c) 2013 Thomas Sarlandie - Richard Hirst + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +[blog]: http://www.tbideas.com/blog/2013/02/controling-a-high-power-rgb-led-with-a-raspberry-pi/ diff --git a/gpio_server/index.html b/gpio_server/index.html new file mode 100644 index 0000000..a181709 --- /dev/null +++ b/gpio_server/index.html @@ -0,0 +1,81 @@ + + + + + + http://10.10.10.16:8080/ + + + + + + + + +
+
+ + + diff --git a/gpio_server/pi-blaster b/gpio_server/pi-blaster new file mode 100644 index 0000000..caf49f6 Binary files /dev/null and b/gpio_server/pi-blaster differ diff --git a/gpio_server/pi-blaster.boot.sh b/gpio_server/pi-blaster.boot.sh new file mode 100644 index 0000000..41549a9 --- /dev/null +++ b/gpio_server/pi-blaster.boot.sh @@ -0,0 +1,126 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: pi-blaster +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Daemon for PWM control of the Raspberry PI GPIO pins +# Description: This daemon will turn all the GPIO pins into outputs and +# lets application control the value on the pin by writing to +# /dev/pi-blaster commands such as "0=0.3" to set the first pin +# to 30% PWM. +### END INIT INFO + +# Author: Thomas Sarlandie + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="Daemon for PWM control of the Raspberry PI GPIO pins" +NAME=pi-blaster +DAEMON=/usr/sbin/$NAME +DAEMON_ARGS="" +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + return "$RETVAL" +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/gpio_server/pi-blaster.c b/gpio_server/pi-blaster.c new file mode 100644 index 0000000..1f1f75e --- /dev/null +++ b/gpio_server/pi-blaster.c @@ -0,0 +1,643 @@ +/* + * pi-blaster.c Multiple PWM for the Raspberry Pi + * Copyright (c) 2013 Thomas Sarlandie + * + * Based on the most excellent servod.c by Richard Hirst + * Copyright (c) 2013 Richard Hirst + * + * This program provides very similar functionality to servoblaster, except + * that rather than implementing it as a kernel module, servod implements + * the functionality as a usr space daemon. + * + */ +/* + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char VERSION[] = "0.1.0"; + +static uint8_t pin2gpio[] = { + //4, // P1-7 + //17, // P1-11 + //18, // P1-12 + //21, // P1-13 + //22, // P1-15 + //23, // P1-16 + 24, // P1-18 + 25, // P1-22 +}; + +#define NUM_CHANNELS (sizeof(pin2gpio)/sizeof(pin2gpio[0])) + +#define DEVFILE "/dev/pi-blaster" + +#define PAGE_SIZE 4096 +#define PAGE_SHIFT 12 + +// PERIOD_TIME_US is the period of the PWM signal in us. +// Typically it should be 20ms, or 20000us. + +// SAMPLE_US is the pulse width increment granularity, again in microseconds. +// Setting SAMPLE_US too low will likely cause problems as the DMA controller +// will use too much memory bandwidth. 10us is a good value, though you +// might be ok setting it as low as 2us. + +// -- 0.02 +//#define CYCLE_TIME_US 5000 +//#define SAMPLE_US 5 + + +// -- 0.010-0.012 +#define CYCLE_TIME_US 10000 +#define SAMPLE_US 2 +#define NUM_SAMPLES (CYCLE_TIME_US/SAMPLE_US) +#define NUM_CBS (NUM_SAMPLES*2) + +#define NUM_PAGES ((NUM_CBS * 32 + NUM_SAMPLES * 4 + PAGE_SIZE - 1) >> PAGE_SHIFT) + +#define DMA_BASE 0x20007000 +#define DMA_LEN 0x24 +#define PWM_BASE 0x2020C000 +#define PWM_LEN 0x28 +#define CLK_BASE 0x20101000 +#define CLK_LEN 0xA8 +#define GPIO_BASE 0x20200000 +#define GPIO_LEN 0x100 +#define PCM_BASE 0x20203000 +#define PCM_LEN 0x24 + +#define DMA_NO_WIDE_BURSTS (1<<26) +#define DMA_WAIT_RESP (1<<3) +#define DMA_D_DREQ (1<<6) +#define DMA_PER_MAP(x) ((x)<<16) +#define DMA_END (1<<1) +#define DMA_RESET (1<<31) +#define DMA_INT (1<<2) + +#define DMA_CS (0x00/4) +#define DMA_CONBLK_AD (0x04/4) +#define DMA_DEBUG (0x20/4) + +#define GPIO_FSEL0 (0x00/4) +#define GPIO_SET0 (0x1c/4) +#define GPIO_CLR0 (0x28/4) +#define GPIO_LEV0 (0x34/4) +#define GPIO_PULLEN (0x94/4) +#define GPIO_PULLCLK (0x98/4) + +#define GPIO_MODE_IN 0 +#define GPIO_MODE_OUT 1 + +#define PWM_CTL (0x00/4) +#define PWM_DMAC (0x08/4) +#define PWM_RNG1 (0x10/4) +#define PWM_FIFO (0x18/4) + +#define PWMCLK_CNTL 40 +#define PWMCLK_DIV 41 + +#define PWMCTL_MODE1 (1<<1) +#define PWMCTL_PWEN1 (1<<0) +#define PWMCTL_CLRF (1<<6) +#define PWMCTL_USEF1 (1<<5) + +#define PWMDMAC_ENAB (1<<31) +#define PWMDMAC_THRSHLD ((15<<8)|(15<<0)) + +#define PCM_CS_A (0x00/4) +#define PCM_FIFO_A (0x04/4) +#define PCM_MODE_A (0x08/4) +#define PCM_RXC_A (0x0c/4) +#define PCM_TXC_A (0x10/4) +#define PCM_DREQ_A (0x14/4) +#define PCM_INTEN_A (0x18/4) +#define PCM_INT_STC_A (0x1c/4) +#define PCM_GRAY (0x20/4) + +#define PCMCLK_CNTL 38 +#define PCMCLK_DIV 39 + +#define DELAY_VIA_PWM 0 +#define DELAY_VIA_PCM 1 + +typedef struct { + uint32_t info, src, dst, length, + stride, next, pad[2]; +} dma_cb_t; + +struct ctl { + uint32_t sample[NUM_SAMPLES]; + dma_cb_t cb[NUM_CBS]; +}; + +typedef struct { + uint8_t *virtaddr; + uint32_t physaddr; +} page_map_t; + +page_map_t *page_map; + +static uint8_t *virtbase; + +static volatile uint32_t *pwm_reg; +static volatile uint32_t *pcm_reg; +static volatile uint32_t *clk_reg; +static volatile uint32_t *dma_reg; +static volatile uint32_t *gpio_reg; + +static int delay_hw = DELAY_VIA_PWM; +static int invert_mode = 0; + +static float channel_pwm[NUM_CHANNELS]; + +static void set_pwm(int channel, float value); +static void update_pwm(); + +static void +gpio_set_mode(uint32_t pin, uint32_t mode) +{ + uint32_t fsel = gpio_reg[GPIO_FSEL0 + pin/10]; + + fsel &= ~(7 << ((pin % 10) * 3)); + fsel |= mode << ((pin % 10) * 3); + gpio_reg[GPIO_FSEL0 + pin/10] = fsel; +} + +static void +gpio_set(int pin, int level) +{ + if (level) + gpio_reg[GPIO_SET0] = 1 << pin; + else + gpio_reg[GPIO_CLR0] = 1 << pin; +} + +static void +udelay(int us) +{ + struct timespec ts = { 0, us * 1000 }; + + nanosleep(&ts, NULL); +} + +static void +terminate(int dummy) +{ + int i; + + if (dma_reg && virtbase) { + for (i = 0; i < NUM_CHANNELS; i++) + channel_pwm[i] = 0; + update_pwm(); + udelay(CYCLE_TIME_US); + dma_reg[DMA_CS] = DMA_RESET; + udelay(10); + } + unlink(DEVFILE); + exit(1); +} + +static void +fatal(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + terminate(0); +} + +static uint32_t +mem_virt_to_phys(void *virt) +{ + uint32_t offset = (uint8_t *)virt - virtbase; + + return page_map[offset >> PAGE_SHIFT].physaddr + (offset % PAGE_SIZE); +} + +static void * +map_peripheral(uint32_t base, uint32_t len) +{ + int fd = open("/dev/mem", O_RDWR | O_SYNC); + void * vaddr; + + if (fd < 0) + fatal("pi-blaster: Failed to open /dev/mem: %m\n"); + vaddr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, base); + if (vaddr == MAP_FAILED) + fatal("pi-blaster: Failed to map peripheral at 0x%08x: %m\n", base); + close(fd); + + return vaddr; +} + +static void +set_pwm(int channel, float width) +{ + channel_pwm[channel] = width; + update_pwm(); +} + + +/* + * What we need to do here is: + * First DMA command turns on the pins that are >0 + * All the other packets turn off the pins that are not used + * + * For the cpb packets (The DMA control packet) + * -> cbp[0]->dst = gpset0: set the pwms that are active + * -> cbp[*]->dst = gpclr0: clear when the sample has a value + * + * For the samples (The value that is written by the DMA command to cbp[n]->dst) + * -> dp[0] = mask of the pwms that are active + * -> dp[n] = mask of the pwm to stop at time n + * + * We dont really need to reset the cb->dst each time but I believe it helps a lot + * in code readability in case someone wants to generate more complex signals. + */ +static void +update_pwm() +{ + uint32_t phys_gpclr0 = 0x7e200000 + 0x28; + uint32_t phys_gpset0 = 0x7e200000 + 0x1c; + struct ctl *ctl = (struct ctl *)virtbase; + int i, j; + uint32_t mask; + + /* First we turn on the channels that need to be on */ + /* Take the first DMA Packet and set it's target to start pulse */ + if (invert_mode) + ctl->cb[0].dst = phys_gpclr0; + else + ctl->cb[0].dst = phys_gpset0; + + /* Now create a mask of all the pins that should be on */ + mask = 0; + for (i = 0; i < NUM_CHANNELS; i++) { + if (channel_pwm[i] > 0) { + mask |= 1 << pin2gpio[i]; + } + } + /* And give that to the DMA controller to write */ + ctl->sample[0] = mask; + + /* Now we go through all the samples and turn the pins off when needed */ + for (j = 1; j < NUM_SAMPLES; j++) { + if (invert_mode) + ctl->cb[j*2].dst = phys_gpset0; + else + ctl->cb[j*2].dst = phys_gpclr0; + mask = 0; + for (i = 0; i < NUM_CHANNELS; i++) { + if ((float)j/NUM_SAMPLES > channel_pwm[i]) + mask |= 1 << pin2gpio[i]; + } + ctl->sample[j] = mask; + } +} + +static void +make_pagemap(void) +{ + int i, fd, memfd, pid; + char pagemap_fn[64]; + + page_map = malloc(NUM_PAGES * sizeof(*page_map)); + if (page_map == 0) + fatal("pi-blaster: Failed to malloc page_map: %m\n"); + memfd = open("/dev/mem", O_RDWR); + if (memfd < 0) + fatal("pi-blaster: Failed to open /dev/mem: %m\n"); + pid = getpid(); + sprintf(pagemap_fn, "/proc/%d/pagemap", pid); + fd = open(pagemap_fn, O_RDONLY); + if (fd < 0) + fatal("pi-blaster: Failed to open %s: %m\n", pagemap_fn); + if (lseek(fd, (uint32_t)virtbase >> 9, SEEK_SET) != + (uint32_t)virtbase >> 9) { + fatal("pi-blaster: Failed to seek on %s: %m\n", pagemap_fn); + } + for (i = 0; i < NUM_PAGES; i++) { + uint64_t pfn; + page_map[i].virtaddr = virtbase + i * PAGE_SIZE; + // Following line forces page to be allocated + page_map[i].virtaddr[0] = 0; + if (read(fd, &pfn, sizeof(pfn)) != sizeof(pfn)) + fatal("pi-blaster: Failed to read %s: %m\n", pagemap_fn); + if (((pfn >> 55) & 0x1bf) != 0x10c) + fatal("pi-blaster: Page %d not present (pfn 0x%016llx)\n", i, pfn); + page_map[i].physaddr = (uint32_t)pfn << PAGE_SHIFT | 0x40000000; + } + close(fd); + close(memfd); +} + +static void +setup_sighandlers(void) +{ + int i; + + // Catch all signals possible - it is vital we kill the DMA engine + // on process exit! + for (i = 0; i < 64; i++) { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = terminate; + sigaction(i, &sa, NULL); + } +} + +static void +init_ctrl_data(void) +{ + struct ctl *ctl = (struct ctl *)virtbase; + dma_cb_t *cbp = ctl->cb; + uint32_t phys_fifo_addr; + uint32_t phys_gpclr0 = 0x7e200000 + 0x28; + uint32_t phys_gpset0 = 0x7e200000 + 0x1c; + uint32_t mask; + int i; + + if (delay_hw == DELAY_VIA_PWM) + phys_fifo_addr = (PWM_BASE | 0x7e000000) + 0x18; + else + phys_fifo_addr = (PCM_BASE | 0x7e000000) + 0x04; + + memset(ctl->sample, 0, sizeof(ctl->sample)); + + // Calculate a mask to turn off all the servos + mask = 0; + for (i = 0; i < NUM_CHANNELS; i++) + mask |= 1 << pin2gpio[i]; + for (i = 0; i < NUM_SAMPLES; i++) + ctl->sample[i] = mask; + + /* Initialize all the DMA commands. They come in pairs. + * - 1st command copies a value from the sample memory to a destination + * address which can be either the gpclr0 register or the gpset0 register + * - 2nd command waits for a trigger from an external source (PWM or PCM) + */ + for (i = 0; i < NUM_SAMPLES; i++) { + /* First DMA command */ + cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP; + cbp->src = mem_virt_to_phys(ctl->sample + i); + if (invert_mode) + cbp->dst = phys_gpset0; + else + cbp->dst = phys_gpclr0; + cbp->length = 4; + cbp->stride = 0; + cbp->next = mem_virt_to_phys(cbp + 1); + cbp++; + /* Second DMA command */ + if (delay_hw == DELAY_VIA_PWM) + cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(5); + else + cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(2); + cbp->src = mem_virt_to_phys(ctl); // Any data will do + cbp->dst = phys_fifo_addr; + cbp->length = 4; + cbp->stride = 0; + cbp->next = mem_virt_to_phys(cbp + 1); + cbp++; + } + cbp--; + cbp->next = mem_virt_to_phys(ctl->cb); +} + +static void +init_hardware(void) +{ + struct ctl *ctl = (struct ctl *)virtbase; + + if (delay_hw == DELAY_VIA_PWM) { + // Initialise PWM + pwm_reg[PWM_CTL] = 0; + udelay(10); + clk_reg[PWMCLK_CNTL] = 0x5A000006; // Source=PLLD (500MHz) + udelay(100); + clk_reg[PWMCLK_DIV] = 0x5A000000 | (50<<12); // set pwm div to 50, giving 10MHz + udelay(100); + clk_reg[PWMCLK_CNTL] = 0x5A000016; // Source=PLLD and enable + udelay(100); + pwm_reg[PWM_RNG1] = SAMPLE_US * 10; + udelay(10); + pwm_reg[PWM_DMAC] = PWMDMAC_ENAB | PWMDMAC_THRSHLD; + udelay(10); + pwm_reg[PWM_CTL] = PWMCTL_CLRF; + udelay(10); + pwm_reg[PWM_CTL] = PWMCTL_USEF1 | PWMCTL_PWEN1; + udelay(10); + } else { + // Initialise PCM + pcm_reg[PCM_CS_A] = 1; // Disable Rx+Tx, Enable PCM block + udelay(100); + clk_reg[PCMCLK_CNTL] = 0x5A000006; // Source=PLLD (500MHz) + udelay(100); + clk_reg[PCMCLK_DIV] = 0x5A000000 | (50<<12); // Set pcm div to 50, giving 10MHz + udelay(100); + clk_reg[PCMCLK_CNTL] = 0x5A000016; // Source=PLLD and enable + udelay(100); + pcm_reg[PCM_TXC_A] = 0<<31 | 1<<30 | 0<<20 | 0<<16; // 1 channel, 8 bits + udelay(100); + pcm_reg[PCM_MODE_A] = (SAMPLE_US * 10 - 1) << 10; + udelay(100); + pcm_reg[PCM_CS_A] |= 1<<4 | 1<<3; // Clear FIFOs + udelay(100); + pcm_reg[PCM_DREQ_A] = 64<<24 | 64<<8; // DMA Req when one slot is free? + udelay(100); + pcm_reg[PCM_CS_A] |= 1<<9; // Enable DMA + udelay(100); + } + + // Initialise the DMA + dma_reg[DMA_CS] = DMA_RESET; + udelay(10); + dma_reg[DMA_CS] = DMA_INT | DMA_END; + dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ctl->cb); + dma_reg[DMA_DEBUG] = 7; // clear debug error flags + dma_reg[DMA_CS] = 0x10880001; // go, mid priority, wait for outstanding writes + + if (delay_hw == DELAY_VIA_PCM) { + pcm_reg[PCM_CS_A] |= 1<<2; // Enable Tx + } +} + +static void +init_channel_pwm(void) +{ + int i; + for (i = 0; i < NUM_CHANNELS; i++) + channel_pwm[i] = 0; +} + +static void +go_go_go(void) +{ + FILE *fp; + + if ((fp = fopen(DEVFILE, "r+")) == NULL) + fatal("pi-blaster: Failed to open %s: %m\n", DEVFILE); + + char *lineptr = NULL, nl; + size_t linelen; + + for (;;) { + int n, servo; + float value; + + if ((n = getline(&lineptr, &linelen, fp)) < 0) + continue; + //fprintf(stderr, "[%d]%s", n, lineptr); + n = sscanf(lineptr, "%d=%f%c", &servo, &value, &nl); + if (n !=3 || nl != '\n') { + fprintf(stderr, "Bad input: %s", lineptr); + } else if (servo < 0 || servo >= NUM_CHANNELS) { + fprintf(stderr, "Invalid channel number %d\n", servo); + } else if (value < 0 || value > 1) { + fprintf(stderr, "Invalid value %f\n", value); + } else { + set_pwm(servo, value); + } + } +} + +void +parseargs(int argc, char **argv) +{ + int index; + int c; + + static struct option longopts[] = + { + {"help", no_argument, 0, 'h'}, + {"invert", no_argument, 0, 'i'}, + {"pcm", no_argument, 0, 'p'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + + while (1) + { + + index = 0; + c = getopt_long(argc, argv, "hipv", longopts, &index); + + if (c == -1) + break; + + switch (c) + { + case 0: + /* handle flag options (array's 3rd field non-0) */ + break; + + case 'h': + fprintf(stderr, "%s version %s\n", argv[0], VERSION); + fprintf(stderr, "Usage: %s [-hipv]\n" + "-h (--help) - this information\n" + "-i (--invert) - invert pin output (pulse LOW)\n" + "-p (--pcm) - use pcm for dmascheduling\n" + "-v (--version) - version information\n" + , argv[0]); + exit(-1); + + case 'i': + invert_mode = 1; + break; + + case 'p': + delay_hw = DELAY_VIA_PCM; + break; + + case 'v': + fprintf(stderr, "%s version %s\n", argv[0], VERSION); + exit(-1); + + case '?': + /* getopt_long already reported error? */ + exit(-1); + + default: + exit(-1); + } + } +} + +int +main(int argc, char **argv) +{ + int i; + + parseargs(argc, argv); + + printf("Using hardware: %5s\n", delay_hw == DELAY_VIA_PWM ? "PWM" : "PCM"); + printf("Number of channels: %5d\n", NUM_CHANNELS); + printf("PWM frequency: %5d Hz\n", 1000000/CYCLE_TIME_US); + printf("PWM steps: %5d\n", NUM_SAMPLES); + printf("Maximum period (100 %%): %5dus\n", CYCLE_TIME_US); + printf("Minimum period (%1.3f%%): %5dus\n", 100.0*SAMPLE_US / CYCLE_TIME_US, SAMPLE_US); + + setup_sighandlers(); + + dma_reg = map_peripheral(DMA_BASE, DMA_LEN); + pwm_reg = map_peripheral(PWM_BASE, PWM_LEN); + pcm_reg = map_peripheral(PCM_BASE, PCM_LEN); + clk_reg = map_peripheral(CLK_BASE, CLK_LEN); + gpio_reg = map_peripheral(GPIO_BASE, GPIO_LEN); + + virtbase = mmap(NULL, NUM_PAGES * PAGE_SIZE, PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_ANONYMOUS|MAP_NORESERVE|MAP_LOCKED, + -1, 0); + if (virtbase == MAP_FAILED) + fatal("pi-blaster: Failed to mmap physical pages: %m\n"); + if ((unsigned long)virtbase & (PAGE_SIZE-1)) + fatal("pi-blaster: Virtual address is not page aligned\n"); + + make_pagemap(); + + for (i = 0; i < NUM_CHANNELS; i++) { + gpio_set(pin2gpio[i], invert_mode); + gpio_set_mode(pin2gpio[i], GPIO_MODE_OUT); + } + + init_ctrl_data(); + init_hardware(); + init_channel_pwm(); + + unlink(DEVFILE); + if (mkfifo(DEVFILE, 0666) < 0) + fatal("pi-blaster: Failed to create %s: %m\n", DEVFILE); + if (chmod(DEVFILE, 0666) < 0) + fatal("pi-blaster: Failed to set permissions on %s: %m\n", DEVFILE); + + if (daemon(0,1) < 0) + fatal("pi-blaster: Failed to daemonize process: %m\n"); + + go_go_go(); + + return 0; +} + diff --git a/gpio_server/server.py b/gpio_server/server.py new file mode 100644 index 0000000..0513b85 --- /dev/null +++ b/gpio_server/server.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +import re +import sys +#=============================================== +import time +import datetime +import dateutil.parser +import pytz +utc = pytz.utc +ber = pytz.timezone('Europe/Berlin') +#=============================================== + +from twisted.python import log +from twisted.internet import reactor +from twisted.python import usage, log +from twisted.internet.task import LoopingCall +from twisted.web.server import Site +from twisted.web.resource import Resource +from twisted.web.static import File + +########################################## +#---------- Website + +import server_web_handler +class Website(Resource): + + isLeaf = False + addSlash = True + + def __init__(self): + self.storage = {} + Resource.__init__(self) + + def getChild(self, name, request): + try: + reload(server_web_handler) + return server_web_handler.get_child(self, name, request) + except: + import traceback + traceback.print_exc() + return self + + def render(self, request): + try: + return server_web_handler.render(self, request) + except Exception, e: + import traceback + traceback.print_exc() + raise + +#---------- Website +########################################## + +print 'starting site' + +if __name__ == '__main__': + + web = Site(Website()) + reactor.listenTCP(8080, web) + reactor.run() diff --git a/gpio_server/server_web_handler.py b/gpio_server/server_web_handler.py new file mode 100644 index 0000000..61512a2 --- /dev/null +++ b/gpio_server/server_web_handler.py @@ -0,0 +1,305 @@ +# -*- coding: utf-8 -*- + +# 好 <- my Unicode warning character + +# beware, there's a lot of testing junk in this file + +#-------------------------------------------------- +import jinja2 +env = jinja2.Environment(loader=jinja2.FileSystemLoader('')) +from twisted.web.static import File +#-------------------------------------------------- +import json +import uuid +import urllib2 +import glob + +import os +import re +import subprocess +import sys +import traceback + +#=============================================== +import re +import time +import datetime +import dateutil.parser +#=============================================== +import pytz +utc = pytz.utc +ber = pytz.timezone('Europe/Berlin') +#----------------------------------------------- +def to_iso8601(when=None, tz=ber): + if not when: + import datetime + when = datetime.datetime.now(tz) + _when = when.strftime("%Y-%m-%dT%H:%M:%S.%f%z") + return _when[:-8] + _when[-5:] +#----------------------------------------------- +def from_iso8601(when=None, tz=ber): + import dateutil.parser + _when = dateutil.parser.parse(when) + return _when +#=============================================== + +################################################################## + +def get_child(self, name, request): + + if request.path[:8] == '/static/' or request.path[:7] == '/static': + return File("./static") + + elif request.path[:9] == '/scripts/' or request.path[:8] == '/scripts': + request.setHeader('Content-Type', 'text/javascript; charset=UTF-8') + request.setHeader('Access-Control-Allow-Origin', '') + return File(path="./scripts") + + return self + +import smbus +import math +import RPi.GPIO as GPIO +def InitGPIO(): + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(25, GPIO.OUT) + IOPort = GPIO.PWM(25, 150) + IOPort.start(0) + return IOPort + +################################################################## + +def render(self, request): + + ############################################################################# + if request.path == "/": + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + _time = datetime.datetime.now(ber).strftime("%Y-%m-%dT%H:%M:%S.%f%z") + _time = "".join(re.split('(\.[0-9]{3})[0-9]{3}?', _time)) + print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + print _time + + request.setHeader('Content-Type', 'text/html; charset=UTF-8') + return env.get_template('index.html').render(time=_time).encode('utf-8') + + ############################################################################# + # you care about this + ############################################################################# + elif request.path == "/i2c": + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + if 'counter' not in self.storage: + self.storage['counter'] = 0 + + bus = smbus.SMBus(1) + addr = 0x2F + try: + responses = [] + + ################################################################################### + + print 'writing to', addr + + ################################################################################### + + increment = 1 + #self.storage['counter'] = 0 + if self.storage['counter'] > 255: + self.storage['counter'] = 32 + + for i in range(1): + + ################################################################################### + val1 = self.storage['counter'] + self.storage['counter'] += increment + ################################################################################### + + ################################################################################### + bus.write_byte(addr, 0x06) # read adc on pin 3 + adc0l = bus.read_byte(addr) + adc0h = bus.read_byte(addr) + adc0 = (adc0h << 8) + adc0l + #adc0 = bus.read_word_data(addr, 0x06) + responses.append(str(adc0)) + ################################################################################### + + val1 = adc0/4 + + ################################################################################### + ''' + bus.write_byte(addr, 0x02) + bus.write_byte(addr, val1) + ''' + bus.write_byte_data(addr, 0x02, val1) + time.sleep(0.02) + ################################################################################### + + + ################################################################################### + ''' + bus.write_byte(addr, 0x04) + bus.write_byte(addr, 16) + bus.write_byte(addr, 0) + time.sleep(0.2) + bus.write_byte(addr, 0x04) + bus.write_byte(addr, 4) + bus.write_byte(addr, 0) + ''' + bus.write_byte_data(addr, 0x04, 4) + time.sleep(0.05) + bus.write_byte_data(addr, 0x04, 0) + time.sleep(0.05) + bus.write_byte_data(addr, 0x04, 16) + time.sleep(0.05) + bus.write_byte_data(addr, 0x04, 0) + time.sleep(0.05) + bus.write_byte_data(addr, 0x04, 8) + time.sleep(0.05) + bus.write_byte_data(addr, 0x04, 0) + ''' + bus.write_byte(addr, 0x04) + bus.write_byte(addr, 0) + bus.write_byte(addr, 0) + ''' + ################################################################################### + + print 'done writing to', addr + ################################################################################### + return 'ok
' + ' | '.join(responses) + ################################################################################### + + except: + + time.sleep(0.1) + bus.write_byte(addr, 0x01) + bus.write_byte(addr, 0) + bus.write_byte(addr, 0) + + ################################################################################### + traceback.print_exc() + return 'err: ' + str('') + ################################################################################### + + return 'ok' + + ############################################################################# + # and about this + ############################################################################# + elif request.path == "/tiny": + + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + PWM_value = int(request.args['pwm'][0]) + PWM_address = int(request.args['address'][0]) + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + #PWM_value = PWM_value * 100.0 + #if 'gpio' not in self.storage: + # self.storage['gpio'] = InitGPIO() + #self.storage['gpio'].ChangeDutyCycle(PWM_value) + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + bus = smbus.SMBus(1) + addr = 0x2F + ''' + bus.write_byte(addr, PWM_address & 0xFF); + bus.write_byte(addr, PWM_value) + ''' + bus.write_byte_data(addr, PWM_address, PWM_value) + #os.system('i2cset -y 1 0x2F ' + str(PWM_address) + ' ' + str(PWM_value) + ' b') + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + return '' + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ############################################################################# + elif request.path == "/push": + + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + PWM_value = float(request.args['pwm'][0]) + PWM_value = PWM_value / 100.0 + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + #PWM_value = PWM_value * 100.0 + #if 'gpio' not in self.storage: + # self.storage['gpio'] = InitGPIO() + #self.storage['gpio'].ChangeDutyCycle(PWM_value) + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + PWM_value = math.pow(PWM_value,2) + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + PWM_value = '{0:.4f}'.format(PWM_value) + f = open('/dev/pi-blaster', 'w') + f.write('1='+PWM_value+'\n') + f.close() + #print PWM_value + return '' + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ############################################################################# + elif request.path == "/omx": + p = subprocess.Popen(["omxplayer", "/home/pi/Desktop/rooster.mp3"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + return '' + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ############################################################################# + elif request.path == "/snd": + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + PWM_pin = 23 + GPIO.cleanup() + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(PWM_pin, GPIO.OUT) + IOPort = GPIO.PWM(PWM_pin, 100) + #for i in range(1000,4000,100): + # print i + # IOPort.ChangeFrequency(i) + # time.sleep(0.25) + + PWM_duration = 0.025 + PWM_pause = 0.075 + + tones = [(4000,1), (8000,1), (16000,1)] + tones = [(4000,1), (8000,1), (32000,1)] + tones = [(4000,10), (4000,1)] + tones = [(32000,10), (8000,1), (4000,1)] + for tone in tones: + IOPort.ChangeFrequency(tone[0]) + IOPort.start(50.0) + time.sleep(PWM_duration*tone[1]) + IOPort.stop() + time.sleep(PWM_pause) + + + GPIO.cleanup() + print 'ok' + + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ''' + PWM_value = '{0:.4f}'.format(PWM_value) + f = open('/dev/pi-blaster', 'w') + f.write('1='+PWM_value+'\n') + f.close() + print PWM_value + ''' + return '' + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ############################################################################# + else: + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + _time = datetime.datetime.now(ber).strftime("%Y-%m-%dT%H:%M:%S.%f%z") + _time = "".join(re.split('(\.[0-9]{3})[0-9]{3}?', _time)) + print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + print _time + + request.setHeader('Content-Type', 'text/plain') + request.setHeader('Server', 'Test') + ret = "" + ret += str(request.method) + "\n" + ret += str(request.host) + "\n" + ret += str(request.client) + "\n" + ret += str(request.client.host) + "\n" + ret += str(request.path) + "\n" + ret += str(request.args) + "\n" + ret += str(request.received_headers) + "\n" + print ret + return ret + str(request.getAllHeaders()) + +##################################################################