This has been a fun and crazy ride. Unfortunately time is ticking away faster than progress is being made on this project. So, I thought that I should wrap this one up.
Here are a list of the previous posts:
DMX diagnostic tool - Getting started
DMX diagnostic tool - Sending and Receiving Data
DMX diagnostics tool - Hardware, Software and Mechanical updates
DMX diagnostics tool - And the wheels come off the bus!
Given the state of things from the last update, there seemed like no where to go but up. Well, unfortunately that was not the case. My next step forward on the project was to integrate the power pack (Walky the Biped Robot - Power pack ) with the Arduino. My plan was to used the +5 volt output of the Arduino when it was USB powered to feed the charger circuit on the power pack. Oops, that did not go too well. The +5 output on the Nano Classic is less the 4.5V with no load and with a load it drops down to 4.2V or less. This is not enough voltage to properly charge a Li-Ion battery. With a nearly fully charged battery (4.0V), the charger would not start the charging sequence due to as the Under Voltage Lock Out (UVLO) triggered and shutdown the device. I then decided that I would need to have a USB jack (bypassing the Arduino) in order to fully charge the Li-Ion power pack.
The attached charging cable is terminated with a female Micro-B USB jack. I modified the PCB to remove the connection from the Arudino (lifted the in-line diode) and tacked a header to the pins attaching the power pack to the motherboard.
With all the changes on this relatively simple board, I decided that I should update the documentation. This is how I should have built it:
I had one additional part that I needed to fabricate, a riser for the joystick, positioning it at the proper height to be flush with the top panel. As I went to find the Fusion360 drawing files today, I could not find any of my recent parts (I hope this is a temporary problem). So I have included the *.STL file that I use to generate the gcode (in CURA). Here is the CURA view of the joystick riser along with a picture of the built part:
In between all of the issues that I was dealing with, I was continuing to work on the code (not complete yet), here are some of the new features I have added:
else if (commPort) { // uart send/receive processing // serial command processing if (GetSerialInputCount()) { serialChar = GetSerialByte(); if (serialChar == 0x0A) { *iptr++ = 0; // terminate string // command received - decode command if (decodeCommand()) { PutSerialString(outBound); // clear outbound string outBound[0] = 0; } else { PutSerialString(unknownCommand); } } else if(serialChar == 0x0D) { // ignore CR } else { *iptr++ = serialChar; } } }
In side of the loop() function, I added a new mode that allowed me to use the UART as a USB communications port along side of using it as a DMX port. The two modes are mutually exclusive as the device can not be remotely connected while the DMX is sending or receiving. I could not use the Arduino standard fuctions of Serial, as this would create multiple definitions of the interrupt vectors of the UART. Here are the low level functions in support of the comm port.
unsigned char decodeCommand(void) { unsigned char retval = 0; unsigned char *params; unsigned char data[32]; unsigned int Address; unsigned char numBytes; unsigned char ctemp; params = strchr(inBound, ','); if (params) { *params++ = 0; // terminate command string (params now points to data) } // look for a comma separator in the command, if found, terminate string and point to remaining data switch(inBound[0]) { case 'D': if (strcmp(inBound, "DATA?") == 0) { // send reply itoa(data, outBound, 10); strcat( outBound, "\r\n"); retval = 1; } case 'H': if (strcmp(inBound, "HELLO?") == 0) { // send OK reply strcat( outBound, "OK\r\n"); retval = 1; } break; case 'R': if (strcmp(inBound, "READ") == 0) { // "READ,%d,%hhu", Address, count ctemp = sscanf( params, "%d, %hhu", &Address, &numBytes); if (ctemp == 2) { ctemp = readEEPROM(data, numBytes, Address); } } break; } iptr = inBound; *iptr = 0; return retval; }
This is the function to decode commands (not complete)
static unsigned int DMXindex; static unsigned char DMXBusy; // USART Transmitter buffer #define TX_BUFFER_SIZE 32 char tx_buffer[TX_BUFFER_SIZE]; unsigned char tx_wr_index = 0; unsigned char tx_rd_index = 0; unsigned char tx_counter = 0; // USART0 Transmitter interrupt service routine ISR(USART_TX_vect) { if (commPort) { if (tx_counter) { --tx_counter; UDR0 = tx_buffer[tx_rd_index++]; if (tx_rd_index == TX_BUFFER_SIZE) { tx_rd_index = 0; } } } else { DMXindex++; if (DMXindex < 513) { if (DMXindex == DMXAddress) // ToDo: extend logic to allow multiple active DMX addresses { UDR0 = DMXdata; } else { UDR0 = 0; } } else { // disable xmitter - not sending another character DMXBusy = 0; } } } // Get a character from the USART Receiver buffer void PutSerialByte(unsigned char data) { if (tx_counter == TX_BUFFER_SIZE) return; if (tx_counter || ((UCSR0A & (1<<UDRE0)) == 0)) { //#asm("cli") noInterrupts(); tx_buffer[tx_wr_index++] = data; if (tx_wr_index == TX_BUFFER_SIZE) { tx_wr_index = 0; } tx_counter++; // #asm("sei") interrupts(); } else { UDR0 = data; } } void PutSerialString(unsigned char *data) { while (*data) { PutSerialByte(*data); data++; } }
This is the transmit side of the UART (including the DMX code), along with the supporting routines to 'put' data.
static unsigned char status; #define FRAMING_ERROR (1<<FE0) #define PARITY_ERROR (1<<UPE0) #define DATA_OVERRUN (1<<DOR0) #define RX_BUFFER_SIZE 32 char rx_buffer[RX_BUFFER_SIZE]; unsigned char rx_wr_index = 0; unsigned char rx_rd_index = 0; unsigned char rx_counter = 0; // This flag is set on USART Receiver buffer overflow unsigned char rx_buffer_overflow; ISR(USART_RX_vect) { char data; char done = 0; status = UCSR0A; data = UDR0; if ((status & ((1<<FE0) | (1<<UPE0) | (1<<DOR0))) == 0) { if (commPort) { rx_buffer[rx_wr_index++] = data; if (rx_wr_index == RX_BUFFER_SIZE) { rx_wr_index = 0; } if (++rx_counter == RX_BUFFER_SIZE) { rx_counter = 0; rx_buffer_overflow = 1; } } else { if (DMX_byte_count == 0) { if (data == 0) { PORTB |= 0x02; // setup packet decoder for normal packet // pDMXAddressess = DMX_AddrMap; // pDMXData = DMX_DataVals; } } // else if (DMX_byte_count == *pDMXAddressess) // { // *pDMXData = data; // pDMXAddressess++; // pDMXData++; // } // else // { // } // monitor DMX byte count if (DMX_byte_count >= 512) { done = 1; DMXdataReady = 1; } else { DMX_byte_count++; } } } else { if (commPort) { } else { // insert code to flag/log errors PORTB &= 0xFC; done = 1; } } if (done) { // disableSerialRecv(); UCSR0B = (0<<RXCIE0) | (1<<TXCIE0) | (0<<UDRIE0) | (0<<RXEN0) | (0<<TXEN0) | (0<<UCSZ02) | (0<<RXB80) | (0<<TXB80); // clear Timer1 TOV flag (all flags) TIFR1 = 0x27; TCNT1 = 0; // enable Interrupt on any change on pins PCINT16 PCICR = (1<<PCIE2) | (0<<PCIE1) | (0<<PCIE0); PCMSK2 = (0<<PCINT23) | (0<<PCINT22) | (0<<PCINT21) | (0<<PCINT20) | (0<<PCINT19) | (0<<PCINT18) | (0<<PCINT17) | (1<<PCINT16); PCIFR = (1<<PCIF2) | (0<<PCIF1) | (0<<PCIF0); DMX_state = 0; PORTB &= 0xFC; } } // Get a character from the USART Receiver buffer unsigned char GetSerialByte(void) { char data; if (rx_counter == 0) return 0; data = rx_buffer[rx_rd_index++]; if (rx_rd_index == RX_BUFFER_SIZE) { rx_rd_index = 0; } //#asm("cli") noInterrupts(); --rx_counter; // #asm("sei") interrupts(); return data; } unsigned char GetSerialInputCount(void) { return rx_counter; } unsigned char GetSerialStatus(void) { return (status & ((1<<FE0) | (1<<UPE0) | (1<<DOR0))); }
This is the receive side of the UART along with the functions to 'get' data.
Another area of code were block read and write functions for the serial EEPROM. Here is that code:
unsigned char writeEEPROM(byte *buffer, unsigned char count, unsigned int address) { unsigned char bytesLeftInPage; unsigned char byteCount; unsigned char loopCount; unsigned char needStop; byteCount = 0; needStop = 0; bytesLeftInPage = 0x20 - (lowByte(address) & 0x1F); if (count == 0) { return 0; } // start writing with the preample Wire.beginTransmission(EEPROM_ID); Wire.write((int)highByte(address)); Wire.write((int)lowByte(address)); for (loopCount = 0; loopCount < count; loopCount++) { Wire.write((int) *buffer++); needStop = 1; if (--bytesLeftInPage == 0) { // page boundary, end block Wire.endTransmission(); delay(5); if (loopCount > (count-1)) // is there more data? { bytesLeftInPage = 0x20; // set in page bytes to full page address = (address & 0x3fc0) + 0x20; // point to the beginning of the next block // begin new block by writing the preample Wire.beginTransmission(EEPROM_ID); Wire.write((int)highByte(address)); Wire.write((int)lowByte(address)); } else { needStop = 0; } } } if (needStop) { // page boundary, end block Wire.endTransmission(); delay(5); } return count; } unsigned char readEEPROM(byte *buffer, unsigned char count, unsigned int address) { unsigned char loopCount; loopCount = 0; // start writing with the preample Wire.beginTransmission(EEPROM_ID); Wire.write((int)highByte(address)); Wire.write((int)lowByte(address)); Wire.endTransmission(); // request the appropriate number of bytes Wire.requestFrom(EEPROM_ID, (byte)count); while(Wire.available()) // slave may send less than requested { if (loopCount < count) { *buffer++ = Wire.read(); // receive a byte as character loopCount++; } } return loopCount; }
The full Ardunio code will be attached as a zip file at the end of this blog.
Lessons learned in the Project
As with all good projects, there is some learning going on along side of the creating. Even with more the 40 years of experience, I am still learning (albeit at a slower rate than earlier portions of my career). Here is some of what I learned on this project:
- I learned my way around the Arduino development tools and process flow. This was both a joy and at times a curse. I am so used to being able to see everything (assembly listing, maps, etc.) that help me understand how the low level pieces work (I know, not everybody's cup of tea, but for me this is were I am most comfortable, understanding how everything works). I liken my experience with Arduino with that of Visual Basic, the framework allows you to make tons of progress on the front side of the project, but then gets in the way as the complexity rises. I ran into some many things that did not seem to work as described (like using PROGMEM).
- I was surprised to find that the CONST keyword actually created variables that eat up the precious RAM on these tiny processors. Further the documentation admits to this but warns you against using #defines. Not sure that I understand that piece of wisdom. All of which brings me to the next lesson.
- My menuing system used way too much memory (I started getting "Low memory available, stability problems may occur." messages during compiles). As I pick up the pieces and move forward, I need to rethink the embedding of strings into the structure/union elements of the menuing system. I could instead embed an index into the elements, pointing to a string array, thus allowing me to share common strings and lessen the penalty of short or null strings inside the elements.
- On the hardware side of things, and this is not really a new lesson, but a reminder.... prior to ordering boards, double check everything. I can not believe how many mistakes that I made on this super simple PCB.
Here is a short proof video, showing the simple operation of my project.
Many thanks to the element14 crew for continuing to hosts interesting project competitions!
Note: Updated 5/15/2020 to add BOM information to the Zip file.
Top Comments