This project uses the Orange Pi Zero, MSP430 chip, and I2C communication to control a dial thermostat through a phone. After the IP address of the Orange Pi is found, a phone can ssh into the Orange Pi Zero via a terminal app such as Terminus (free download). Once the phone has connected to the Orange Pi, sudo single_byte (the executable file) can be run to start the temperature setting program. 'P' outputs the current reading from the thermistor. 'S' allows the user to type in their desired temperature. After the temperature is set, the MSP430 chip controls the servo motor to turn on/off the dial heater according to the thermistor reading.
#include <stdio.h>
#include <math.h>
#include "i2c.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
#define ADDR 0x25
// functions declared at bottom of this file
int mygetch(void);
int main(void) {
int bus; //data communication number
unsigned char tx=0; //data to transfer
unsigned char rx=0; //data to receive
int index = 0;
char buffer[200]; // used by fgets to read character string typed by user.
int mychar = 0; // input character
int tempSet = 0; // temperature to set transfered by tx
int tempReading = 0; //current temperature received from rx
int inputdone = 0;
float tempfloat = 0;
bus = i2c_start_bus(1);//start data communication
tx += 1;
while (!inputdone) {//while we haven't received any characters from the user
printf("\n\n");
printf("Menu of Selections\n");
printf("DO NOT PRESS CTRL-C when in this menu selection\n");
printf("e - Exit Application\n");
printf("s - enter Desired Temperature\n");
printf("p - print current temp\n");
mychar = (char) mygetch(); //when input is received, set mychar to character from user
switch (mychar) {
case 'p': //print current temperature
printf("Print current Temp \n");
tx = (unsigned char)tempSet; //used unsigned char so that we do not send a value greater than 255
//Because of how we wrote the code on the MSP we must send garbage data so that we can receive data
i2c_write_bytes(bus, ADDR, &tx,1);//sends tx to receive rx (thats how we wrote the code in code composer for MSP)
i2c_read_bytes(bus, ADDR,&rx,1);//receives back the current temp
printf("Current Temp: %03d\n", (int)rx);//print out
usleep(200*1000); //pause
break;
case 'e':
inputdone = 1; //exits the while loop
break;
case 's':
printf("Enter Desired Temp Setpoint\n");
fgets(buffer,190,stdin); //receives the character form the user
buffer[strlen(buffer)-1] = '\0'; // get ride of '\n' in returned string
if (buffer[0] != '\0') { //
if (sscanf(buffer,"%f",&tempfloat) != 0) { // check that it was a number entered
tempSet = tempfloat; //tempfloat was set to the enter character in the above line
//and int the line above, tempSet is set to the value of tempfloat
printf("Desired Temp = %d\n",tempSet);
} else {
printf("Error: Non numerical value typed\n");
}
} else {
printf("Error: TempSet not changed\n");
}
tx = (unsigned char)tempSet; //not greater than 255
i2c_write_bytes(bus, ADDR, &tx,1);//sends tx , bus is the I2C connection, ADDR is the address of the slave (0x25),
//&tx is the address of tx, 1 is the number of bytes
i2c_read_bytes(bus, ADDR,&rx,1);//receives back the current temperature
printf("TX: %03d RX:%03d\n", (int)tx, (int)rx);
usleep(200*1000); //pause
break;
default:
break;
}
}
i2c_close_bus(bus); //stop data communication
return 0;
}
int mygetch(void)
{
struct termios oldt,
newt;
int ch;
tcgetattr( STDIN_FILENO, &oldt );
newt = oldt;
newt.c_lflag &= ~( ICANON | ECHO );
tcsetattr( STDIN_FILENO, TCSANOW, &newt );
ch = getchar();
tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
return ch;
}
#include <stdio.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
int i2c_start_bus(unsigned char bus);
void i2c_close_bus(int fd);
int i2c_write(int file, unsigned char addr, unsigned char reg, unsigned char *value, unsigned char num);
int i2c_write_byte(int file, unsigned char addr, unsigned char value);
int i2c_read(int file, unsigned char addr, unsigned char reg, unsigned char *value, unsigned char num);
int i2c_read_byte(int file, unsigned char addr,unsigned char *value);
int i2c_write_bytes(int file, unsigned char addr, unsigned char *value, unsigned char num);
int i2c_read_bytes(int file, unsigned char addr, unsigned char *value, unsigned char num);
#include "i2c.h"
int i2c_start_bus(unsigned char bus) {
char filename[11];
int fd;
sprintf(filename, "/dev/i2c-%d", bus);
fd = open(filename, O_RDWR);
if (fd < 0)
fprintf(stderr, "Failed to communicate with I2C bus #%d.\n", bus);
return fd;
}
void i2c_close_bus(int fd) {
close(fd);
}
int i2c_write_byte(int file, unsigned char addr, unsigned char value) {
int i;
unsigned char outbuf;
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[1];
messages[0].addr = addr;
messages[0].flags = 0;
messages[0].len = 1;
messages[0].buf = &outbuf;
outbuf = value;
/* Transfer the i2c packets to the kernel and verify it worked */
packets.msgs = messages;
packets.nmsgs = 1;
if(ioctl(file, I2C_RDWR, &packets) < 0) {
perror("Unable to send data");
return 1;
}
return 0;
}
int i2c_write_bytes(int file, unsigned char addr, unsigned char *value, unsigned char num) {
int i;
unsigned char outbuf[256];
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[1];
messages[0].addr = addr;
messages[0].flags = 0;
messages[0].len = num;
messages[0].buf = outbuf;
if (num > 255) {
perror("Maximum Send num is 255");
return 1;
}
for (i = 0; i <= num; i++)
outbuf[i] = value[i];
/* Transfer the i2c packets to the kernel and verify it worked */
packets.msgs = messages;
packets.nmsgs = 1;
if(ioctl(file, I2C_RDWR, &packets) < 0) {
perror("Unable to send data");
return 1;
}
return 0;
}
int i2c_write(int file, unsigned char addr, unsigned char reg, unsigned char *value, unsigned char num) {
int i;
unsigned char outbuf[256];
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[1];
messages[0].addr = addr;
messages[0].flags = 0;
messages[0].len = num + 1;
messages[0].buf = outbuf;
/* The first byte indicates which register we'll write */
outbuf[0] = reg;
/*
* The remaining bytes indicate the values that will be written.
* The maximum number of data bytes allowed is 255.
*/
for (i = 1; i <= num; i++)
outbuf[i] = value[i - 1];
/* Transfer the i2c packets to the kernel and verify it worked */
packets.msgs = messages;
packets.nmsgs = 1;
if(ioctl(file, I2C_RDWR, &packets) < 0) {
perror("Unable to send data");
return 1;
}
return 0;
}
int i2c_read(int file, unsigned char addr, unsigned char reg, unsigned char *value, unsigned char num) {
unsigned char outbuf;
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[2];
/*
* In order to read a register, we first do a "dummy write" by writing
* 0 bytes to the register we want to read from. This is similar to
* the packet in set_i2c_register, except it's 1 byte rather than 2.
*/
outbuf = reg;
messages[0].addr = addr;
messages[0].flags = 0;
messages[0].len = sizeof(outbuf);
messages[0].buf = &outbuf;
/* The data will get returned in this structure */
messages[1].addr = addr;
messages[1].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
messages[1].len = num;
messages[1].buf = value;
/* Send the request to the kernel and get the result back */
packets.msgs = messages;
packets.nmsgs = 2;
if(ioctl(file, I2C_RDWR, &packets) < 0) {
perror("Unable to send data");
return 1;
}
return 0;
}
int i2c_read_byte(int file, unsigned char addr,unsigned char *value) {
unsigned char outbuf;
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[1];
/* The data will get returned in this structure */
messages[0].addr = addr;
messages[0].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
messages[0].len = 1;
messages[0].buf = value;
/* Send the request to the kernel and get the result back */
packets.msgs = messages;
packets.nmsgs = 1;
if(ioctl(file, I2C_RDWR, &packets) < 0) {
perror("Unable to send data");
return 1;
}
return 0;
}
int i2c_read_bytes(int file, unsigned char addr, unsigned char *value, unsigned char num) {
unsigned char outbuf;
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[1];
/* The data will get returned in this structure */
messages[0].addr = addr;
messages[0].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
messages[0].len = num;
messages[0].buf = value;
/* Send the request to the kernel and get the result back */
packets.msgs = messages;
packets.nmsgs = 1;
if(ioctl(file, I2C_RDWR, &packets) < 0) {
perror("Unable to send data");
return 1;
}
return 0;
}
/******************************************************************************
MSP430G2553 Project Creator
SE 423 - Dan Block
Spring(2019)
Written(by) : Steve(Keres)
College of Engineering Control Systems Lab
University of Illinois at Urbana-Champaign
*******************************************************************************/
#include "msp430g2553.h"
#include "UART.h"
void print_every(int rate);
char newprint = 0;
int timecheck = 0;
int RXData = 0;
int TXData = 0;
int timecount= 0;
int tempSet = 85;
int tempReading = 0;
int ADC0_value = 0; //NO FLOATS OR DOUBLES because runs really slow
void main(void) {
WDTCTL = WDTPW + WDTHOLD; // Stop WDT
if (CALBC1_16MHZ ==0xFF || CALDCO_16MHZ == 0xFF) while(1);
DCOCTL = CALDCO_16MHZ; // Set uC to run at approximately 16 Mhz
BCSCTL1 = CALBC1_16MHZ;
//Initialize I2C for Orange Pi
//http://coecsl.ece.illinois.edu/ge423/datasheets/MSP430Ref_Guides/MSP430x2xx_usersguide.pdf
P1SEL |= 0xC0;
P1SEL2 |= 0xC0;
UCB0CTL1 = UCSSEL_3 + UCSWRST;
UCB0CTL0 = UCSYNC + UCMODE_3;
UCB0I2COA = 0x25;
UCB0CTL1 &= ~UCSWRST;
//Interrupts
IFG2 &= ~UCB0RXIFG; //clear in source
IE2 |= UCB0RXIE;
//Initializing servo motor ports
P2DIR |= BIT4; //P2.4 output
P2SEL |= BIT4; //P2.4 --> TA1.2 option
P2SEL2 &= ~(BIT4);//P2.4 TA1.2 option (0 by default)
TA1CCTL0 = 0x0; // Set everything to default
TA1CCTL2 = OUTMOD_7; //TA1CCR2 reset/set for PWM wave
TA1CCR0 = 40000; //Compares to get 50Hz frequency
TA1CTL = TASSEL_2 + MC_1 + ID_3; //SMCLK, up mode (ID_3 divide by 8)
//Initialized the ADC10 Controller (To read temperature sensors)
ADC10CTL0 = ADC10SHT_1 + ADC10ON + ADC10IE + ADC10DIV_0;//ADC10SHT_1 sample and hold time; ADC10ON turn on ADC; ADC10 SC start conversion and sampling; ADC10DIV_0 clock divider 0
ADC10CTL1 = INCH_3; //input A3 sample for the MUX so that we can read the DC values
ADC10AE0 |= 0x08; // PA.0 ADC option select (input analog enable)
// Timer A Config
TACCTL0 = CCIE; // Enable Periodic interrupt
TACCR0 = 16000; // period = 1ms
TACTL = TASSEL_2 + MC_1; // source SMCLK, up mode
Init_UART(115200,1); // Initialize UART for 115200 baud serial communication
_BIS_SR(GIE); // Enable global interrupt
while(1) { // Low priority Slow computation items go inside this while loop. Very few (if anyt) items in the HWs will go inside this while loop
// for use if you want to use a method of receiving a string of chars over the UART see USCI0RX_ISR below
// if(newmsg) {
// newmsg = 0;
// }
// The newprint variable is set to 1 inside the function "print_every(rate)" at the given rate
if ( (newprint == 1) && (senddone == 1) ) { // senddone is set to 1 after UART transmission is complete
// only one UART_printf can be called every 15ms
UART_printf("A%d R%d S%d\n\r",ADC10MEM,tempReading,tempSet);
newprint = 0;
}
}
}
// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A (void)
{
ADC10CTL0 |= ADC10SC + ENC; //start conversion and ENC allows the MUX to set for the AE0
}
// ADC 10 ISR - Called when a sequence of conversions (A7-A0) have completed
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void) {
timecheck++; // Keep track of time for main while loop.
print_every(500); // units determined by the rate Timer_A ISR is called, print every "rate" calls to this function
ADC0_value = (int)((((double)ADC10MEM)*81.0/183.0));//conversion of 0-1023 from ADC10MEM to 0-3300milliVolts
//Changes the outputs of the motors according to the phone values
tempReading = ADC0_value;
if(tempReading > tempSet+4){ //has on offset of +/- 4 so that the motor isn't quickly fluctuating
TA1CCR2 = 4500; //Turn off heat
}
else if(tempReading<tempSet-4)
{
TA1CCR2 = 2500;//Turn on heat
}
}
// USCI Transmit ISR - Called when TXBUF is empty (ready to accept another character)
#pragma vector=USCIAB0TX_VECTOR
__interrupt void USCI0TX_ISR(void) {
if((IFG2&UCA0TXIFG) && (IE2&UCA0TXIE)) { // USCI_A0 requested TX interrupt
if(printf_flag) {
if (currentindex == txcount) {
senddone = 1;
printf_flag = 0;
IFG2 &= ~UCA0TXIFG;
} else {
UCA0TXBUF = printbuff[currentindex];
currentindex++;
}
} else if(UART_flag) {
if(!donesending) {
UCA0TXBUF = txbuff[txindex];
if(txbuff[txindex] == 255) {
donesending = 1;
txindex = 0;
} else {
txindex++;
}
}
}
IFG2 &= ~UCA0TXIFG;
}
if((IFG2&UCB0RXIFG) && (IE2&UCB0RXIE)) { // USCI_B0 RX interrupt occurs here for I2C
//RX code here.
RXData =UCB0RXBUF; //Receiving data from the OrangePI off the RX buffer
tempSet = RXData; //Saving the received temperature value to a new variable(change of variable for convenience)
TXData = tempReading; //Setting the data to be send out to be the current temperature
IFG2 &= ~UCB0RXIFG; //clear interrupt source
IE2 &= ~UCB0RXIE; //USCI_B0 receive interrupt disabled
IE2 |= UCB0TXIE;//Enable transfer buffer
} else if ((IFG2&UCB0TXIFG) && (IE2&UCB0TXIE)) { // USCI_B0 TX interrupt
//TX code here.
UCB0TXBUF = TXData; //Writing the current reading the the transfer buffer.
IE2 |= UCB0RXIE; //RX interrupt enabled
IE2 &= ~UCB0TXIE;//disable transfer buffer
}
}
// USCI Receive ISR - Called when shift register has been transferred to RXBUF
// Indicates completion of TX/RX operation
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void) {
if((IFG2&UCA0RXIFG) && (IE2&UCA0RXIE)) { // USCI_A0 requested RX interrupt (UCA0RXBUF is full)
if(!started) { // Haven't started a message yet
if(UCA0RXBUF == 253) {
started = 1;
newmsg = 0;
}
} else { // In process of receiving a message
if((UCA0RXBUF != 255) && (msgindex < (MAX_NUM_FLOATS*5))) {
rxbuff[msgindex] = UCA0RXBUF;
msgindex++;
} else { // Stop char received or too much data received
if(UCA0RXBUF == 255) { // Message completed
newmsg = 1;
rxbuff[msgindex] = 255; // "Null"-terminate the array
}
started = 0;
msgindex = 0;
}
}
IFG2 &= ~UCA0RXIFG;
}
if((UCB0I2CIE&UCNACKIE) && (UCB0STAT&UCNACKIFG)) { // I2C NACK interrupt
UCB0STAT &= ~UCNACKIFG;
}
if((UCB0I2CIE&UCSTPIE) && (UCB0STAT&UCSTPIFG)) { // I2C Stop interrupt
UCB0STAT &= ~UCSTPIFG;
}
if((UCB0I2CIE&UCSTTIE) && (UCB0STAT&UCSTTIFG)) { // I2C Start interrupt
UCB0STAT &= ~UCSTTIFG;
}
if((UCB0I2CIE&UCALIE) && (UCB0STAT&UCALIFG)) { // I2C Arbitration Lost interrupt
UCB0STAT &= ~UCALIFG;
}
}
// This function takes care of all the timing for printing to UART
// Rate determined by how often the function is called in Timer ISR
int print_timecheck = 0;
void print_every(int rate) {
if (rate < 15) {
rate = 15;
}
if (rate > 10000) {
rate = 10000;
}
print_timecheck++;
if (print_timecheck == rate) {
print_timecheck = 0;
newprint = 1;
}
}
Comments