Why does this state machine not maintain its state? - c

Hey there StackOverflow!
In the following code I have a simple state machine that changes the operation of some external lighting device (as the comments imply). The state is changed via the pressing of the button connected to GP1. The circuit connected to GP1 is a comparator debouncing circuit that compares VDD to 0.6VDD (I've also tried an RC/diode/schmitt trigger circuit), which then forces the signal LO. On a scope, we see a clean square wave when the button is actuated rapidly.
The current (and undesirable) behavior of the PIC10F200 is as follows:
Switch is pressed (state = 0)
State machine variable increments (state = 1)
Lighting goes to case 1, and turns on
Lighting remains on for at least a second
Lighting turns off
System remains in this state until button is actuated again or
powered off
The question is: Why does it behave like this? And how if possible, do I fix it such that a single press of the button equates to a single state increment, which the PIC then maintains for as long as the system is powered and the button is not actuated again?
#define SYS_FREQ 8000000L
#define FCY SYS_FREQ/4
#define _XTAL_FREQ 4000000
/******************************************************************************/
/* User Global Variable Declaration */
/******************************************************************************/
/******************************************************************************/
/* Main Program */
/******************************************************************************/
__CONFIG(MCLRE_ON & CP_OFF & OSC_IntRC);
void main(void)
{
TRIS = 0b111110;
unsigned char state = 0;
while(1)
{
switch (state)
{
case 0: // IDLE/OFF
if (GPIObits.GP0) GPIObits.GP0 = 0;
break;
case 1: // ON
if (!GPIObits.GP0) GPIObits.GP0 = 1;
break;
case 2: // BLINK (slow)
GPIObits.GP0 = !GPIObits.GP0;
__delay_ms(100);
break;
case 3: // BLINK (fast)
GPIObits.GP0 = !GPIObits.GP0;
__delay_ms(50);
break;
case 4: // BEAT DETECT
GPIObits.GP0 = GPIObits.GP2;
break;
default:
state = 0;
break;
}
if (!GPIObits.GP1)
{
__delay_ms(250);
state++;
}
}
}
UPDATE: Since there seems to be a little confusion as to what I am trying to accomplish with this code/system, lets provide the full context. This microcontroller, the PIC10F200 is part of an overall board design for an electroluminescent (EL) wire driver. The miconcontroller simply controls whether or not the driver circuit is enabled by connecting GP0 to the EN port of the driver IC. The system has four modes of operation, the wire is constantly on, the wire is blinking, the wire is blinking faster, and the wire blinks whenever a low-frequency beat is detected (another circuit in the system). The transition from these modes of operation is governed by a pushbutton (on momentarily) switch to be mounted on the PCB. This necessitates that state in the code above remains stable between button actuations. It currently does not do this and behaves as described in the original part of this post. As the question title states, why isn't state stable currently, and how do I make it so?
UPDATE (2014-03-08): Solution
The following settings need to be set assuming GP0 is the output, GP2 is your T0CKI and you have a switch that drives the signal to LO when actuated.
TRIS = 0b111110;
OPTION = 0b11101111;
Whether or not bits 0-3 for OPTION really matter is a judgement call and whether or not you choose to use the WDT module.
Additionally, the implementation for the button release detection is a simple counter mechanism that resets upon GP2 being LO at any point during the count.
if (TMR0 > 0)
{
while (count < 20)
{
if (!GPIObits.GP2) count = 0;
__delay_ms(10);
count++;
}
TMR0 = 0;
state++;
}

You have a hardware/software design problem!
When your program is in delay loop than your key button is not
checked!
You are checking only on key press event, but you must also on key
relase.
My purpose is that you can use GP2 (T0CKI) pin instead GP1 for key buttom. This pin has schmitt trigger input if is used as counter TMR0 input. After that configure your MCPU TMR0 as counter with external clock on GP2 (T0CKI) pin. You must also set the T0SE bit to configure counter that will increment on high-to-low transition on the T0CKI pin.
In program after any loop check the TMR0 content if biger than 0 the key was pressed.
Wait some ms and check if key was relased if relased than increase the state variable and clear TMR0 content.

move your
if (!GPIObits.GP1){
if(isPressed == false){
//__delay_ms(250); //you dont need the delay
state++;
if(state == 5){state = 0;}
isPressed = true;
}
}
else{isPressed = false;}
before the switch statement. char isPressed = false; before the while loop
valter

__delay_ms(250); <-- too small delay
While you are pressing the button slowly, the loop may rotate several times. Try increasing it to 1000ms.
Update
You should run the PIC with WDT (watch dog timer) disabled, otherwise it will reset the pic in few seconds. That is similar to your observation. You can blink a LED at the beginning of main function to check if this happen or you can initialization unsigned char state = 1; and see the behavior then.
Try this __CONFIG(MCLRE_ON & CP_OFF & OSC_IntRC & WDT_OFF);

Related

Avoid Input Latency while reading input from keyboard loop

I'm just starting win STM32 and am trying to write some code which tries to accept input from user keypress, and vary the blink-rate of 2 LEDs or blink them alternatively, or at the same time.
So, I have 3 Keys(2 keys each for inc/dec, and one key for mode), and 2 LEDs.
The loop section looks something like this:
/* USER CODE BEGIN WHILE */
const uint32_t step = 50u;
const uint32_t max_interval = 500u;
uint32_t interval = max_interval;
short mode = 0;
while (1)
{
volatile GPIO_PinState wakeup = HAL_GPIO_ReadPin(WAKEUP_GPIO_Port, WAKEUP_Pin);
mode = (wakeup == GPIO_PIN_RESET? 0: 1);
if(mode == 1) {
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
}
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED3_Pin);
volatile GPIO_PinState key0 = HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin);
volatile GPIO_PinState key1 = HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);
if(key0 == GPIO_PIN_RESET)
interval -= (interval==step? 0: step);
if(key1 == GPIO_PIN_RESET)
interval += (interval==max_interval? 0: step);
HAL_Delay(interval);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
Now, depending on the HAL_Delay(interval), the loop would get a chance to check for key-input, whose purpose is to control the blink rate. Is there some way, I can untie this latency for key-input? The microcontroller in question is STM32F407VET6, and I'm using CubeIDE. It would be nice to have a single-threaded solution.
Some delay for key input is unavoidable, unless you have some hardware-debounced keys. Normally, when hitting a key, the transition is not a single edge, but some burst of level changes until the mechanics settle. One way to do debouncing would be to have a periodic interval timer (e.g. at 1khz rate) and you check each time the level of a key. If it is high, you count a counter up. If it is low, you count a counter down and then you have 2 thresholds (hysteresis) in the count values, when you consider it a button down or button up transition. Since all that works in the interrupt, you could then push the key event into a FIFO (queue) and in your "main thread", you can pull the events at convenient occasions.
If there is also a programmable timer on your hardware, you could use that to toggle the output pins for the LED and your main loop would then simply be along the lines:
void mainloop() {
while (true) {
KeyEvent_t key;
nextKeyEvent(&key);
switch (key) {
case BUTTON1_DOWN:
reduceBlinkRate();
break;
case BUTTON2_DOWN:
increaseBlinkRate();
break;
default:
// skillful doing nothing (e.g. to save power)
break;
}
}
}

PIC18 Global Interrupt Enable bit toggling when keypad button pressed

Objective
I am trying to interface a 4x3 matrix keypad and 7 segment LED display to PIC18f4550 microcontroller. When I press buttons on keypad, I want the 7 segment display to show the number accordingly.
What I have done so far
Based on my research, I can either use scanning (continuously polling) or use interrupts to interface the keypad with the MCU. I decided to use interrupts as that way the microcontroller can be free up for other operations.
The following is the connection of keypad to MCU.
I am using RB0-2 as input to MCU and RB4-7 are set as output from MCU. RB4-7 are permanently set high so when the user press on the keypad button, it will trigger the RB0-2(INT0-INT2) interrupt to process.
For simplicity in this post, I will only discuss about the Column 1 of the keypad.
This is how I initialize and setup the registers.
void main(void)
{
OSCCON = 0x72;
TRISD = 0;
LATD = 0;
ADCON1 = 0x0F;
TRISB = 0x07;
LATB = 0xF0;// keep the RB4-7 high
INTCONbits.GIE = 1;
INTCONbits.INT0IF = 0;
INTCONbits.INT0IE = 1;
INTCON2bits.INTEDG0 = 1;
while (1);
}
My interrupt handling is as below:
if(INTCONbits.INT0IF == 1)
{
for(char scan=0x10; scan>=0x80; scan <<= 1) // send 1 to each row starting from RB4 till RB7
{
LATB = scan;
if (PORTBbits.RB0 = 1)
{
if(scan == 0x10)
{
display_number(1);
}
if(scan == 0x20)
{
display_number(4);
}
if(scan == 0x40)
{
display_number(7);
}
}
}
LATB = 0xF0;
INTCONbits.INT0IF == 0;
My problem
When I run the simulation, I noticed that the GIE bit keep toggling very fast between 0 and 1 as soon as I press on any button in Column 1 and no display of any number on 7segment as well. I added the watch window screenshot and highlighted the GIE bit that is toggling.
What have I done wrong? Is my logic of handling interrupt flawed?
UPDATE 1
As DavidHoadley suggested, I have changed to use use unsigned char instead of char.
I have also corrected the for loop condition.
What I observed was, If I keep the loop inside the interrupt routine, the loop will get stuck for some reason.
For now, I have given up trying to use loop inside the interrupt function and instead resort to have a function in main while loop to output high at each row sequentially forever and the interrupt function is only used to check the output using switch statement.
Your for loop end condition looks like the loop will immediately exit. The line:
for(char scan=0x10; scan>=0x80; scan <<= 1)
Should probably be:
for(char scan=0x10; scan<=0x80; scan <<= 1)
Can’t test this - hope it works
It is a normal behaviour for PIC micros. When PIC goes to the interrrupt vector the GIE bit reset by hardware and when it finishes servicing the interrupt, it returns with the RETFIE assembly instruction by setting the GIE bit which is not visible in C code. So there is no fault in your code for this matter, this is not even a matter.
I see in your code you are using only INT0 interrupt to detect presses and the rest of INTx pins are not activated. That's why the PIC micro will be able to detect the changes of the only first column. I suggest you to use interrupt on change (IOC) which features on RB<7:4> bits. This way you can free the INTx pins for other purposes. And you can move the 3 pins to a PORT other than PORTB. Here is the procedure or my suggestion if you interest:
Configure RB<7:4> pins as inputs and enable their IOC feature.
Configure any 3 pins as output of any port.
If you use the positive logic set the 3 pins high otherwise, low.
In your ISR poll the RBIF to know if there is a change on RB<7:4> pins.
If so make a button scan to detect the pressed key.
Unfortunately can't tell you anything for your display issue since you haven't shared the codes and the configuration of display.

Problem reading rotary encoder with atmega8

I am trying to read a rotary encoder as part of a larger project. I am using a Atmega8L and MPLab X with the XC8 compiler.
The issue I am having, is that the encoder does not work reliable and only seems to be able to count up and not down.
Due to some other aspects of the circuit the encoder pins could not be connected to interrupt-capable pins of the atmega8.
Here is my code, that reads the encoder.
#include <xc.h>
#include "pinmapping.h"
#include "main.h"
static inputEvent lastEvent;
static int aLastState;
static int aState;
//get the last Events data for use elsewhere
inputEvent getLastInputEvent(){
return lastEvent;
}
void initEncoder(){
//get our current button state and set rotation change to 0
if((PINB & ENCODER_SW) == 0){
//also save that to our Event
lastEvent.buttonPressed = 1;
}
else{
lastEvent.buttonPressed = 0;
}
lastEvent.rotationChange = 0;
//also save the current state of the A pin for the first read
aLastState = (PINB & ENCODER_A);
if(aLastState >= 1){
aLastState = 1;
}
}
//this should run regularly eg. on a timer interrupt
void readEncoder(){
//first get the state of both pins
aState = (PINB & ENCODER_A);
int bState = (PINB & ENCODER_B);
if(aState >= 1){
aState = 1;
}
if(bState >= 1){
bState = 1;
}
//check if the button is pressed
if((PINB & ENCODER_SW) == 0){
//save that to our Event
lastEvent.buttonPressed = 1;
}
else{
lastEvent.buttonPressed = 0;
}
//check if the state has changed since last time
if(aState != aLastState){
//check if A and B have the same state -> positive rotation
if(bState != aState){
//save the rotation change to our Event
lastEvent.rotationChange = 1;
}
else{
lastEvent.rotationChange = -1;
}
//save the current state of the A pin for next time
aLastState = aState;
}
else{
//if no rotation change happend, save that to our Event
lastEvent.rotationChange = 0;
}
}
The button press is working properly, but the rotation change is never -1 and always 1 or 0.
The ENCODER_SW, ENCODER_A and ENCODER_B are bitmasks, that are all 0s and a single 1 at the bit corresponding to the correct IO-pin.
In the main, first the initEncoder is called (after IO-setup) and then in the infinite loop a function is called, which in turn calls readEncoder() and then getLastInputEvent() and then does some more stuff before returning to the main.
I have also tried running the readEncoder() function on a timer interrupt roughly every ms. This however results in it not working at all.
What is the problem? Why isn't it working?
Edit: Updated the code after I made some changes.
I am using a ALPS EC12E with pushbutton.
The rotationChange is used to increment/decrement a number that is printed to an LCD.
Edit 2: I now think, it is a timing problem, since I tried running basically the same code, just without anything else on an Arduino and printed the values to the serial console and it worked basically perfectly.
I checked what else is done and realized that in my current code the readEncoder() function only runs about once every 98 or so ms.
When I run the function on a timer interrupt every 1 or 2 ms, it just doesn't work at all. I don't even get a reaction to the button push anymore, which is something I do not understand at all.
In fact the timer does not seem to run at all, or at least the interrupt service routine is never executed. (I tried turning on an LED in the setup and turning it off in the isr, but it stays on indefinitely.)
So I guess theres a new problem now...
Here is the timer initialization:
ASSR = 0x00; //make sure we are not in asynchronous mode
OCR2 = 3; //Output compare register
TCNT2 = 0; //Reset counter to 0
TCCR2 = 0b00001111; //Timer 2 CTC mode, clkio/1024
This is done inside of initDecoder(). The OCIE2 bit is already set earlier in the main setup, enabling the timer 2 interrupt.
The isr than just looks like this:
void __interrupt (TIMER2_COMP_vect_num) timer2_isr(){
//Just read the encoder
readEncoder();
}
The interrupt flag gets cleared by hardware when the isr is executed.
The TIMER2_COMP_vect_num is definitely correct as well, as MPLab recognizes it and I can even look at its definition.

How to create a timer/clock that can send its value back,stored in a variable, ONLY made with delays in C & on mikroC

As a C beginner, I struggle alot to solve this problem of mine :
I am working on a project where I basicly have to program a PIC (microchip) in C using the mikroC platform.
What I'm trying to achieve is with only one switch/button, I'll have to switch from three different "modules" (as I call them "modules", they correspond to different lighting effects created by LEDs) depending on the time spent pressing the switch/button. In my case, after 500 ms module 1 is up, after 1500 ms module 2 goes up and after 3500 ms module 3 is up (and the whole thing has to be in an infinitely repeating loop since I have to be able to change a module at ANYTIME during the operation).
My only problem is getting the timer/clock to start runnning at the beginning of the program and keep counting time until it reaches a stop signal (like the end of a loop or something).
This may not be appropriate to ask to this community but here I am nonetheless.
I am concious that this is more of a 'algorithmics/logic' problem than anything but I have been trying for the last week without any clue on how to get past this problem...
No results since the code isn't ready at all.
There is a mikroC library for handling button presses. The following example (from the link.) provides a skeletal example of detecting a button push...
bit oldstate; // Old state flag
void main() {
ANSEL = 0; // Configure AN pins as digital I/O
ANSELH = 0;
C1ON_bit = 0; // Disable comparators
C2ON_bit = 0;
TRISB0_bit = 1; // set RB0 pin as input
TRISC = 0x00; // Configure PORTC as output
PORTC = 0xAA; // Initial PORTC value
oldstate = 0;
do {
if (Button(&PORTB, 0, 1, 1)) { // Detect logical one
oldstate = 1; // Update flag
}
if (oldstate && Button(&PORTB, 0, 1, 0)) { // Detect one-to-zero transition
PORTC = ~PORTC; // Invert PORTC
oldstate = 0; // Update flag
}
} while(1); // Endless loop
}
There is also a collection of MicroE Examples which include timer examples such as these, and this one. Each of these provide code base examples that might be adapted to create a function that can be wrapped around sections in the button press code to obtain time durations.
I hope this helps.
When the button gets pressed, you need to initialize timer module of respective microcontroller check the interrupt flag, and put it in a loop. increment the value until the button is released, and after that compare the resultant value with the specified limits, this was you can decide for how long a button is pressed, and consecutively you will be able to switch your output. This is the only way if you want to precisely monitor the delay without interrupting the current execution of the program.

Program Working on Old Device and New Device's Simulator but not on New Device

I'm hitting a roadblock in porting over a program I previously wrote for the PIC10F200 (see this related SO question). Turns out another component on the board needed to be swapped out for something that needed to be communicated with through I2C, so ergo, I'm porting the program to the PIC12LF1552.
This is how the program currently works (at least on the PIC10F200)
Program starts at IDLE (no lights on)
Press the button, the T0CKI pin (in the PIC12LF1552's case, RA2) is already pulled up to VDD (~3.3V) by an external pull-up resistor, the button is connected to ground and the pin, ergo pulling T0CKI's signal to LO.
TMR0 increments (supposedly)
After a time period where PORTAbits.RA2 settles, state increments
switch block moves to turn the external LED on, in the previous version of the circuit, this would also activate the IC this pin is connected to.
Rinse repeat for the other 3 states.
I have re-verified this functionality through using MPLAB X's simulator where I fired T0CKI (again, configured as RA2, again confirmed by the simulator) to HI and then to LO to engage the TMR0 code. state increments, and everybody is happy.
When I program the device using either MPLAB X connected to my PICkit3 or the included standalone programmer MPLAB IPE, the program does not work as intended. I have re-verified all connections, the programming port is connected as it should be. The button is attached the right pin. The test LED is attached to the right pin. For the time being we are not considering any I2C communication, I just want to see my status LED (RA4) toggle.
I know that now that I've transitioned to a midrange PIC, I can use the interrupt features, but for the time being I want to get what I know has worked on a much simpler device working on the current one.
My question then is, why doesn't this program work when programmed to the PIC12LF1552, but yet works on the PIC12LF1552 simulator AND its previous incarnation worked on the PIC10F200 (both programmed and simulated)?
Thanks in advance everyone!
The following is the entire program:
#if defined(__XC)
#include <xc.h> /* XC8 General Include File */
#endif
#include <stdint.h> /* For uint8_t definition */
#include <stdbool.h> /* For true/false definition */
#include <stdio.h>
#include <stdlib.h>
/******************************************************************************/
/* Defines */
/******************************************************************************/
//#define SYS_FREQ 16000000L
//#define FCY SYS_FREQ/4
#define _XTAL_FREQ 500000
__CONFIG
(
MCLRE_ON &
CP_OFF &
BOREN_OFF &
WDTE_OFF &
PWRTE_OFF &
FOSC_INTOSC
);
void main(void)
{
TRISA = 0b101111;
OPTION_REG = 0b01111111;
APFCONbits.SDSEL = 1;
unsigned char state = 0;
unsigned char count = 0;
while(1)
{
switch (state)
{
case 0: // IDLE/OFF
if (LATAbits.LATA4) LATAbits.LATA4 = 0;
break;
case 1: // ON
if (!LATAbits.LATA4) LATAbits.LATA4 = 1;
break;
case 2: // BLINK (slow)
LATAbits.LATA4 = !LATAbits.LATA4;
__delay_ms(100);
break;
case 3: // BLINK (fast)
LATAbits.LATA4 = !LATAbits.LATA4;
__delay_ms(50);
break;
case 4: // BEAT DETECT
LATAbits.LATA4 = LATAbits.LATA5;
break;
default:
state = 0;
break;
}
if (TMR0 > 0)
{
while (count < 20)
{
if (!PORTAbits.RA2) count = 0;
__delay_ms(10);
count++;
}
TMR0 = 0;
state++;
}
}
}
Some of the pins may be configured as Analog Inputs.
From the Datasheet for this device
"The operation of pin RA4 as analog is selected by setting the ANS3
bit in the ANSEL register which is the default set-ting after a
Power-on Reset".
If you do not set the ANSEL register the pin cannot be used as output as it is configured as an analog input.
This applies to all the pins that can be A/D inputs.
I do not see any configuration bit setup in your code.
Setting ANSEL and ANSELH to 0 should do the trick.
According to the this documentation , on page 93 about the ANSELA register
"The ANSELA bits default to the Analog mode after Reset. To use any
pins as digital general purpose or peripheral inputs, the
corresponding ANSEL bits must be initialized to ‘0’ by user software."
If you don't plan to use analog inputs, you may add something like ANSELA=0;

Resources