CH552 microcontroller analog button input - Page 1
Forum section: electronics / beginners

author: Sdeininger
author_rank: Newbie
timestamp: May 20, 2023, 09:43:07 am
content: Hello everyone!I'm new to the forum trying to find help with following problem.I have built a simple LED dimmer using a CH552 microcontroller from WCH. The arduino code is as follows:const int LED_PIN = 30;const int BUTTON_PIN = 32;void setup() {pinMode(LED_PIN, OUTPUT);pinMode(BUTTON_PIN, INPUT_PULLUP);digitalWrite(LED_PIN, LOW); // Make sure LED is off}void loop() {static int duty_cycle = 0;if(digitalRead(BUTTON_PIN) == LOW) {duty_cycle += 1;if(duty_cycle > 3) duty_cycle = 0;// Set PWM duty cycleswitch(duty_cycle) {case 0:analogWrite(LED_PIN, 0);delay(500);break;case 1:analogWrite(LED_PIN, 102);delay(500);break;case 2:analogWrite(LED_PIN, 191);delay(500);break;case 3:analogWrite(LED_PIN, 255);delay(500);break;}}}The LED_PIN is connected to a MOSFET gate (IRLB8748PbF) which drives the current for the LED (12V).The issue is, when I press the button the LED cycles through all states until the duty_cycle turns to 0 again. This can be reproduced also by disconnecting the button and touching the input pin with a metallic object. If I touch the input pin shortly I can succesfully cycle through the cases as intended. The wire length to the button and back to GND is about 1.5m. As the input pin can be used as a capacitive touch input I am concerned that this option is not disabled and thus creates the mentioned behaviour.Any help solving this problem would be much appreciated. If you need more information feel free to ask. Thanks in advance.Kind regardsSdeininger

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 20, 2023, 05:19:52 pm
content: Your logic is "while button is depressed, change mode".What you want to do is software debouncing and triggering on the button press event.Move the delay to the loop, saydelay(1), so the loop iterates at most 1000 times per second (1ms per iteration, 1000 ms = 1 second).We'll use a single 32-bit variable to track the last 32 samples of the button, and only act on it whether they are all (consecutively) the same.We also use a boolean flag to indicate whether the current button press event has been done.Code:constexpr int LED_PIN = 30;constexpr int BUTTON_PIN = 32;// Number of consecutive states needed to detect a button press.// Minimum is 1, maximum is 32.constexpr int CONSECUTIVE_STATES = 32;constexpr uint32_t STATE_MASK = UINT32_C(0xFFFFFFFF) >> (32-CONSECUTIVE_STATES);// Duty cycles per 255, for each modeconstexpr int MODE_PWM = { 0, 102, 192, 255 };uint32_t button_states;bool button_pressed;int mode;void setup() {pinMode(LED_PIN, OUTPUT);pinMode(BUTTON_PIN, INPUT_PULLUP);digitalWrite(LED_PIN, LOW); // Make sure LED is offbutton_states = 0;button_pressed = false;mode = 0;}void loop() {// Update button state mask with the current state. Set bits indicate active button.button_states = (button_states << 1) | (digitalRead(BUTTON_PIN) == LOW);if (button_pressed == false && (button_states & STATE_MASK) == STATE_MASK) {// Button press event!button_pressed = true;// Cycle mode through 0..3, i.e. four modes.mode = (mode + 1) % 4;// Set LED PWM based on mode.analogWrite(LED_PIN, MODE_PWM);} elseif (button_pressed == true && (button_states & STATE_MASK) == 0) {// Button release event!button_pressed = false;}// Limit iteration rate to 1000ms/1ms = 1000 iterations per second.delay(1);}Because this requires 32 consecutive same states to recognize a change, it ignores spurious button presses that take less than 32ms, and it requires the button to be released for at least 32ms before the button becomes sensitive to a new press.

author: Sdeininger
author_rank: Newbie
timestamp: May 21, 2023, 06:52:01 am
content: Thanks for the quick reply. It seems that you are right. I will need to verify that.

author: Sdeininger
author_rank: Newbie
timestamp: May 21, 2023, 12:32:18 pm
content: Hello again!So I have tried without success. The fault stays the same. It cycles through every three cases and remains on 0% till the button is pressed again. On the breadbord it works though with an LED directly driven by the pwm output pin.Kind regardsSdeininger

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 21, 2023, 01:29:21 pm
content: Edit: Also tested working.Code:#define DEBOUNCE_TIME 50const uint8_t duty[] = { 0, 102, 191, 255 };void loop() {static int duty_cycle = 0;static char btn_pressed=0;if(btn_pressed){ // Wait for button releaseif(digitalRead(BUTTON_PIN)){ // If releaseddelay(DEBOUNCE_TIME ); // debounce timeif(digitalRead(BUTTON_PIN)){ // Check againbtn_pressed = 0; // Released}}}else if(!digitalRead(BUTTON_PIN)){ // Not waiting for release, check for presseddelay(DEBOUNCE_TIME ); // deboucingif(!digitalRead(BUTTON_PIN)) { // Check againbtn_pressed=1; // Set pressed flag and proceedif(++duty_cycle > 3){duty_cycle = 0;}analogWrite(LED_PIN, duty); // Set PWM duty cycle}}}No need for delay at the end, as now the button is properly debounced, it won't make anything until it does the full release/pressed cycle again.Not rocket science code but tryign to keep it simple.Dumb delays are always bad, wasting cpu time, but I suspect your code isn't going to be too large

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 21, 2023, 01:50:23 pm
content: And thefancyproperway with millis(), not wasting time in delays.Edit: Even simpler, tested and working perfect:Code:#define DEBOUNCE_TIME 50const uint8_t duty[] = { 0, 102, 191, 255 };typedef enum { btn_pressed, btn_idle } btn_state_t;void loop() {static btn_state_t state = btn_idle;static bool stable=btn_idle, last=btn_idle; // Assumes pressed button reads 0!static int duty_cycle = 0;static uint32_t time;bool now = digitalRead(BUTTON_PIN);switch(state){ // Button state handlecase btn_pressed: // New press detectedstate = btn_idle; // Reset stateif(++duty_cycle > 3)duty_cycle = 0;analogWrite(LED_PIN, duty); // Set PWM duty cyclebreak;case btn_idle: // Idling, check for changesif(last != now){ // Last value different than last reading (Unstable)last = now; // Update last valuetime = millis(); // Update change time}else if(stable != now && millis()-time > DEBOUNCE_TIME ){ // Input is stable, update if last stable different and debounce time passedstate = stable = now; // Update value. 0=btn_pressed, 1=btn_idle}break;}}

author: Sdeininger
author_rank: Newbie
timestamp: May 21, 2023, 04:35:12 pm
content: The problem is not the debouncing I figured. The wires to the button run up in metallic tube which somehow interferes with the input signal on the pin. I removed the wiring from the tube and tested and it worked. Even if I lay the wires on the tube the effect reappears. No idea how to solve that.

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 21, 2023, 04:55:49 pm
content: Have you even tried the examples?Your original code would never work properly:Code:void loop() {if(digitalRead(BUTTON_PIN) == LOW) {// Execute everything non-stop while button is low, not caring about button releasing, debouncing or anything}}

author: Sdeininger
author_rank: Newbie
timestamp: May 21, 2023, 05:56:06 pm
content: Hi!I did try your examples without succes. In my original code the "debounce" is taking place in the switch table. Each case has a delay of 500ms. This should be enough to release the button without problems. My hand is not touching the button anymore and the cycling through cases continues till the dutycycle is 0 again. If debouncing was the issue, shouldn't the program switch to a "random" case when the button is released? Momentarily I'm running following code having implemented the debouncing method mentioned here:https://docs.arduino.cc/built-in-examples/digital/DebounceThis is my code at the moment:const int LED_PIN = 30;const int BUTTON_PIN = 32;unsigned long lastDebounceTime = 0;unsigned long debounceDelay = 300;void setup() {pinMode(LED_PIN, OUTPUT);pinMode(BUTTON_PIN, INPUT_PULLUP);digitalWrite(LED_PIN, LOW); // Make sure LED is off}void loop() {static int duty_cycle = 0;int reading = digitalRead(BUTTON_PIN);if (reading == LOW && (millis() - lastDebounceTime) > debounceDelay) {duty_cycle++;if (duty_cycle > 3) {duty_cycle = 0;}switch (duty_cycle) {case 0:analogWrite(LED_PIN, 0);break;case 1:analogWrite(LED_PIN, 102);break;case 2:analogWrite(LED_PIN, 191);break;case 3:analogWrite(LED_PIN, 255);break;}lastDebounceTime = millis();}}I really appreciate your time and effort.Kind regardsSdeininger

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 21, 2023, 06:43:06 pm
content: Start with minimal code, so you can isolate the problem.Code:constexpr int LED_PIN = 30;constexpr int BUTTON_PIN = 32;constexpr unsigned long INTERVAL_MS = 100;unsigned long last_millis;void setup() {pinMode(LED_PIN, OUTPUT);pinMode(BUTTON_PIN, INPUT_PULLUP);digitalWrite(LED_PIN, LOW); // Make sure LED is offlast_millis = millis();}void loop() {if ((millis() - last_millis) >= INTERVAL_MS) {last_millis = millis();if (digitalRead(BUTTON_PIN) == LOW) {analogWrite(LED_PIN, 255);} else {analogWrite(LED_PIN, 0);}}}The above simply examines the button state every INTERVAL_MS milliseconds, and sets the LED state according to the button state.If the LED flickers on even when the button is not being pressed, you have spurious high signals.If the LED flickers off when the button is being pressed, you have spurious low signals.If the LED does not turn on when the button is being pressed, the signal does not reach ground (ground issue at button, or pull-up is too strong).If the LED does not turn off when the button is released, the signal does not reach 3.3V (pull-up is too weak).Repeat the above with INTERVAL_MS first say 100 (for ten updates per second), and then say 10 (for hundred updates per second).What do you see? What happens when you press the button briefly? What happens when you keep the button depressed?

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 21, 2023, 07:03:18 pm
content: Add an external pullup, the built-in one is usually pretty weak, about 40-60K in most MCUs, good for a butto in a small board, but not for long wires catching noise.Try connecting a 10K or 1K resistor between the button and VCC.And use Code tags!

author: Sdeininger
author_rank: Newbie
timestamp: May 21, 2023, 07:21:38 pm
content: I am using the internal pullup. I couldn't find information on the value in the datasheet. But I agree that this might be the problem. I will try with an external resistor and report back. Thanks for the input. If that fails I will try the debugging steps mentioned. I will report back. Have a nice evening!

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 21, 2023, 08:43:55 pm
content: From the page 49:Code:VCC=5V Min Typ MaxIUP5 Pull-up resistor input of the input current 35 70 140 uAVCC=3.3V Min Typ MaxIUP3 Pull-up resistor input of the input current 15 30 60 uASo doing V/I to get R:Code:Min Typ MaxVCC=5V 36K 71K 142KVCC=3.3V 55K 110K 220K

author: Sdeininger
author_rank: Newbie
timestamp: May 27, 2023, 08:04:46 am
content: Hi there!It's been a while. Adding a lower resistance pullup didn't change the behaviour. I think I found the source of the problem though. The PWM output is interfering with the button as the button wires and the lamp wires run in the same tube. This explains why the mode switching stops at dutycycle 0 where there is no interference with the wire. I was thinking of adding a low pass filter to the button wire to eliminate the high frequency interference of the PWM. This should solve the problem, right? Or is there any other way you can think of to stop this beaviour from occuring? Thanks for the help.Kind regardsSdeininger

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 27, 2023, 12:43:56 pm
content: I suspected something like that but with mains-induced interference at 50/60 Hz; being caused by PWM output makes much more sense.The best option would be to also use a shielded cable, like standard earphone cable, which has two insulated wires wrapped in a foil or mesh and another layer of insulation. The foil or mesh is grounded only at one end (the microcontroller end). The two wires are the button signal and ground. If you have up to four or seven buttons, you can reuse cheap standard Ethernet cable: if max. four buttons, use every second one as a ground for that button, so that each twisted pair is a signal-ground pair. With up to seven buttons, just pick one as a ground and the seven others as a signal. This shields against both capacitive coupling (which is what you have now, straight wires sitting next to each other) and inductive coupling.At the microcontroller pin, connect the pull-up resistor to VCC (say, 10 kOhm), and a capacitor to ground (say, 1 uF). This gives you an effective signal rise-time of about 10ms (considering the CH552 logic high is max. 2.4V at 5V VCC, or 1.9V at 3.3V VCC), but filters out higher-frequency noise (the 3dB cutoff being about 16 Hz). We can assume your PWM frequency is at least 100 Hz, in which case the capacitive coupling you now have shows up at the pin as a small ripple, not large enough to change the pin logic level (due to hysteresis and the low-pass nature of the RC filter).Button press detection should be immediate (as the resistance to ground should be at most a few Ohms, thus the decay RC constant tiny).The abovementioned rise time (to 1.9V-2.4V or thereabouts, depending) determines the time after the physical button is first released (including contact bounce) that the logic signal stays low and no new press is detected.

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 27, 2023, 03:02:54 pm
content: No pun intended, but keep doing whatever you want and then asking for help/wasting other's time with this...We already said "Do proper debouncing" and even posted several examples, explaining what was wrong in your code.But you keep modifying just few lines of code that are still doing the wrong thing.I posted two working exampleshereandhere, these won't cause weird behaviour on input spikes.If still having issues, check other things, but keep the good code until working.Deboucing isn't "Hey, input is low, go for it and then wait 500ms", because any spurious will trigger the thing.Instead you must ensure the button state is stable for at least xx ms, then proceed, ignoring any fast changes.This is not debouncing. Any fast glitch will trigger this if debounce time passed away.Keeping the button low, or a single random spike, will trigger it forever everydebounceDelaytime.Code:if (reading == LOW && (millis() - lastDebounceTime) > debounceDelay)Ahh I'm hearing the woke police! "Ahh don't humilliate people". I'm not! Just telling the true LOL.Show us your wiring scheme, don't use common ground for power and the button, instead wire the button with its own gnd, and twist the pair to provide better noise inmunity.Also add a 100nF capacitor to the arduino pin to further noise protection.The stronger pullup is a must!

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 27, 2023, 05:27:11 pm
content: Quote from: DavidAlfa on May 27, 2023, 03:02:54 pmHaving a common shield (foil or mesh) around this pair, twisted or not, will make a bigger difference since we're talking about capacitive coupling at relatively low frequencies, whereas twisting pairwise shields better from higher frequencies (100 kHz+) and inductive coupling. It is important that the shield is grounded (preferably at the microcontroller end), though.If cable thickness is not an issue, I do recommend using standard shielded ethernet cables (SF/UTP or F/UTP) for this kind of use. It is cheap, has four twisted pairs inside, with a common shield (foil or mesh). Old cables that have broken connectors are my favourite to reuse; just cut off the connectors when they break. A new 10m SF/UTP Cat5e cable costs about 15€ here (Finland 2023-05-27); for much longer, it makes sense to buy a full roll. 100m SF/UTP Cat5e costs about 55€ (Finland 2023-05-27).

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 27, 2023, 06:07:12 pm
content: Thats why most industrial sensors work in current mode or in much higher voltage ranges.Inducing 20mA through capacitive/inductive coupling is not easy!Noise will have a hard time pulling a 100R pullup down.