Jump to content
roadrunner84

Nano project: fix an IKEA toy

Recommended Posts

That's one of the reasons I don't like using hard delays to do anything on the msp430.

I'll post my code using denouncing when done with it (having kids permits me less hobby time), but you won't see any delay constructs in my code :-)

Share this post


Link to post
Share on other sites

For some heavily overdimensioned code:

#include <msp430.h>
#include <stdint.h>
#ifndef __cplusplus__
#include <stdbool.h>
#endif /* __cplusplus__ */

#define P2MAP (BIT6 | BIT7) /* set to 0xFF for 20-pin device */
#define BUTTON0 BIT2
#define BUTTON1 BIT5
#define PLATE0  BIT6
#define PLATE1  BIT7

#define CHECK_MSEC    10
#define PRESS_MSEC    40
#define RELEASE_MSEC 100
#define TIMEOUT     6000 /* in CHECK_MSEC periods */

volatile bool pwm0 = false, pwm1 = false;

bool debounce(bool state, bool read, uint16_t* count);

int main(void)
{
  // Stop WDT
  WDTCTL = WDTPW | WDTHOLD; // Stop WDT
  BCSCTL1 = DIVA_2; // ACLK /= 4
  BCSCTL2 = SELM_0; // MCLK = SMCLK = DCO
  BCSCTL3 = LFXT1S_2; // LFXT1 = VLO (ACLK = 3kHz)

  // Set up I/O (buttons as interrupt, LEDs off)
  P2SEL = 0; // Set crystal I/O to GPIO
  P1REN = ~(PLATE0 | PLATE1); // All pin pull up/down, except ouputs
  P2REN = P2MAP; // Also on port 2
  P1DIR = ~(BUTTON0 | BUTTON1); // disable all input, except inputs
  P2DIR = P2MAP; // Also on port 2
  P1OUT = BUTTON0 | BUTTON1; // pull up button inputs
  P1IES = BUTTON0 | BUTTON1; // high->low transition
  P1IFG = 0; // clear interrupt flags
  P1IE = BUTTON0 | BUTTON1; // enable interrupts

  // Set up timer for 10ms interval and CCR1 for 30% duty cycle
  TACCR0 = (10 * 3) - 1; // 10ms
  TACCR1 = ( 1 * 3) - 1; //  1ms
  TACCTL1 = CCIE; // Set TA0.1 to 30% duty cycle 10ms PWM
  TACCTL0 = CCIE;
  TACTL = TASSEL_1 | ID_0 | MC_1 | TACLR;// | TAIE; // Enable 3kHz timer interrupt

  __enable_interrupt();
  LPM3;
  __no_operation();
}

#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR(void)
{
  P1IFG = 0;
  // Leave LPM4
  __bic_SR_register_on_exit(OSCOFF); // Enable ACLK after ISR (LPM4 -> LPM3)
}

#pragma vector=TIMER0_A1_VECTOR
__interrupt void TIMER0_A1_ISR(void)
{
  TACCTL1 &= ~CCIFG; // clear interrupt flag
  if (!(pwm0 || pwm1)) return;
  switch(TACCR1)
  {
    case (1 * 3) - 1:
      if (pwm0) P1OUT |= PLATE0;
      TACCR1 = (4 * 3) - 1;
      break;
    case (4 * 3) - 1:
      P1OUT &= ~PLATE0;
      TACCR1 = (6 * 3) - 1;
      break;
    case (6 * 3) - 1:
      if (pwm1) P1OUT |= PLATE1;
      TACCR1 = (9 * 3) - 1;
      break;
    case (9 * 3) - 1:
      P1OUT &= ~PLATE1;
      TACCR1 = (1 * 3) - 1;
      break;
    default:
      TAR = 0;
      break;
  }
}

#pragma vector=TIMER0_A0_VECTOR
__interrupt void TIMER0_A0_ISR(void)
{
  static bool state0 = false, state1 = false;
  static uint16_t count0 = 0, count1 = 0;
  static uint16_t timeout;

  TACCTL0 &= ~CCIFG; // clear interrupt flag
  // Handle button 0
  if (debounce(state0, P1IN & BUTTON0, &count0))
  {
    state0 = !state0;
    if (!state0) pwm0 = !pwm0;
    timeout = TIMEOUT;
  }
  // Handle button 1
  if (debounce(state1, P1IN & BUTTON1, &count1))
  {
    state1 = !state1;
    if (!state1) pwm1 = !pwm1;
    timeout = TIMEOUT;
  }
  // Handle timeout
  if (--timeout == 0)
  {
    pwm0 = pwm1 = false;
    P1OUT &= ~(PLATE0 | PLATE1);
  }
  // Handle low power mode
  if (!pwm0 && !pwm1 && state0 && state1 && ((P1IN & (BUTTON0 | BUTTON1)) == (BUTTON0 | BUTTON1)))
  {
    // if plates off and buttons up
    // Enter LPM4
    __bis_SR_register_on_exit(OSCOFF); // Disable ACLK after ISR (LPM3 -> LPM4)
  }
}

bool debounce(bool state, bool read, uint16_t* count)
{
  if (read == state)
  {
    *count = !state?(RELEASE_MSEC / CHECK_MSEC):(PRESS_MSEC / CHECK_MSEC);
  }
  else
  {
    if ((--*count) + 1 == 0)
    {
      *count = !read?(RELEASE_MSEC / CHECK_MSEC):(PRESS_MSEC / CHECK_MSEC);
      return true;
    }
  }
  return false;
}

Debouncing two buttons and PWMing two LEDs with a 180 degrees phase shift. While spending as much time as possible in LPM4, or LPM3 otherwise.

This code compiles to well over 600 bytes of code, so it won't fit on the 1/2 kB devices without tweaking. But the 8pin value line chips are both 2kB, so no harm there :)

 

On an extended subject; (almost?) all MSP430 devices carry an addictional 4x64 byte "information flash". Let's assume we loose info_a to the TLV data and upper 32 bytes flash to vectors. This leaves us with a systematic 160 bytes extra (above the specified size) flash which remain unused in a lot of applications. Couldn't we repurpose that memory for the application?

This would deliver us over 30% of "free" flash on the smallest devices. (or almost 8% on the 2kB devices)

Share this post


Link to post
Share on other sites

@roadrunner84

Wow, that's really some sophisticated code for this application.

I also had some time to work on this today and I kind of went the other way and rather did chose the analog approach over the digital one.

I just increased the resistors R1 and R8 to 560 Ohms and used my initial code without the PWM.

Now I

Share this post


Link to post
Share on other sites

Msp430 is misleadingly large on code, some instructions are "only" 2 bytes, others are 4.

I noticed out takes a whopping 60 bytes if you have not all variables initialized to 0! This is because if you have variables initialized to any non-zero value, the linker hauls on an entire memcopy function to preload the variables.

I'll benchmark the current usage as soon possible... which will probably be Friday next week. :-(

Share this post


Link to post
Share on other sites

I'd say there is no shorter instruction than 1 word on a 16b CPU and the 430 is quite good from a code density perspective. The compiler is a different story though. Guess that's why I was only programming assembly until 2 years ago. But I have to admit that the time to get something working is much faster on C.

Which compiler do you use?

 

That's a good compilation of instructions and code length:

http://www.ti.com/sc/docs/products/micro/msp430/userguid/as_5.pdf

Share this post


Link to post
Share on other sites

I'm using IAR, it is on low optimisation now, but high optimisation doesn't make it a lot smaller.

C was actually invented as a "portable assembler", though the low level stuff we're doing on the msp430 makes out hardly portable.

Share this post


Link to post
Share on other sites

I successfully fixed our kitchen today using a MSP430G2211, roadrunner84's code and CCS. There's a pitfall in roadrunner84's code, though:

 

if (debounce(state0, P1IN & BUTTON0, &count0)) 


The second argument does not evaluate to 'true' in (at least my) CCS but to the integer representation of

P1IN & BUTTON0 

 

Thus,

if (read == state) 

does not work as intended. In my version of the code I fixed it like this:

 

if (debounce(state0, (P1IN & BUTTON0) > 0, &count0)) 

I know that this is also far from safe in a C90 kind of way, but it works.

 

 

Oh, and by the way:

 

if ((--*count) + 1 == 0) 

is the ugliest piece of C I have seen in quite some time. Maybe I'm just too much accustomed to quality code... ;-)

 

Anyways, thank you very much for sharing! It saved quite an amount of my free time. :smile:

Share this post


Link to post
Share on other sites

P1IN & BUTTON0 may not evaluate to true or false, but as it is passed as a bool argument to the debounce function, it should be implicitly casted to a boolean. Maybe CCS doesn't evaluate it that way, but IAR does with low optimisation.

 

(--*count) + 1 looks very ugly, I aimed at compact coding, and I couldn't find a way to execute postfix decrement on a dereferenced variable. I designed it this way te avoid initialising any variable to a non-zero value (as it would link memcopy in and size would be larger.)

I finished my fixing too, I'll post pictures of it soon. Idle current is not measurable with my 200uA multimeter (which has 0.1uA resolution, so actually my ilde current is <150nA)

Share this post


Link to post
Share on other sites

So, it turned out I had the two buttons switched, thanks to define it is as easy as switching a 2 and a 5 to get it working properly.

 

post-3-0-49113400-1363621498_thumb.jpg

 

 

It actually looks messier than it does in real life. I had no SMD resistor/capacitor around, so I settled with PTH components instead. The SOIC8 chips is deadbug style on top of the DIP8 plate, which plugs into a DIP8 socket which I placed on the board.

As my earlier design showed for only 6 used pins of the 8 in a DIP8 formfactor, I repurposed the other two as SBW breakout. These pins aren't routed on the IKEA PCB anyways. Ofcourse, those pins do not map on the normal launchpad pins at all, so I used female to female jumper wires to patch the lower half of the 20 pin socket to vcc, gnd, test and reset.

 

post-3-0-52171000-1363621497_thumb.jpg

Edited by bluehash
No imgur links. They expire. All pics must be uploaded to 43oh.

Share this post


Link to post
Share on other sites

wow, i found this old thread because i have the same kids toy on my bench right now.
mine has failed because  water got into it and corroded the two buttons.

another design failure. having this thing right next to the sink, where kids play with water.
they did take this into account somehow, because all the electronics is elevated and there are drain holes for water, but they still did not waterproof the buttions.
the only place where there is electronics right underneath.

long story short, i opend it up and my first thought was straight away, that this thing is unneccessary complicated, and due to the lack of a power switch it will drain the batteries even if the LED's are off.

but you guys just took it even further. i have to admit, putting a microcontroller in there was one of my first thougts too,
but my goal was to prevent more water damage in the future. the micro could have handled some capacitive touch sensor buttons.
that way i could have sealed the whole thing off. but it would have constantly draind the power as well.

so i think will go simpler, instead of even more complicated.
first thought was just to repair the buttons as they are and to add a real power switch on the botttom to turn it off for good, if not in use.

but now i lean to the most simplistic way there is. just remove all the electronics, add real (waterproof) on-off switches instead of the push buttons and wire the LED's directly to the batteries.
it will only use power if it is really on, and there is nothing else in there to fail. (i will also add hotglue to the remaining solderpoints and wires to waterproof them too.)

even has the added bennefit of teaching the kids to turn off the stove after use, and not to rely on it to turn of by itself.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...