Indulging my whimsical penchant for quirky tech I recently took delivery of the enigmatic CH552G. It turns out to have a lot of stuff locked up in its monolithic shell! From the datasheet comes the following:
Core: Enhanced E8051 core compatible with MCS51 command set, 79% of its commands are single-byte single-cycle commands, and the average command speed is 8 ~ 15 times faster than that of the standard MCS51, with special XRAM data fast copy command, and double DPTR pointers.
ROM: Non-volatile memory ROM that can be programmed for many times, with the capacity of 16KB, can albe used for program storage. Or it can be divided into a 14KB program storage area and a 2KB BootLoader/ISP program area.
DataFlash: 128-byte non-volatile data memory that can be erased for multiple times and supports rewrite data in unit of byte.
RAM: 256-byte internal iRAM, which can be used for fast temporary storage of data and stack. 1KB on-chip xRAM, which can be used for temporary storage of large amount of data and direct memory access (DMA).
USB: Built-in USB controller and USB transceiver, support USB-Device mode, support USB type-C master-slave detection, support USB 2.0 full-speed (12Mbps) and low-speed (1.5Mbps) traffic.
Support data packet of up to 64 bytes, built-in FIFO, and support DMA.
Timer: 3 sets of timers (T0/T1/T2), which are standard MCS51 timers.
Capture: Timer T2 is extended to support 2-channel signal capture.
PWM: 2 PWM outputs, PWM1 and PWM2, are 2-channel 8-bit PWM output.
UART: 2 sets of UARTs. Both support higher communication baud rate. UART0 is a standard MCS51 serial port.
SPI: The SPI controller has built-in FIFO, and the clock frequency can reach half of the system dominant frequency Fsys. It supports simplex multiplex of serial data input and output, and Master/Slave mode.
ADC: 4-channel 8-bit A/D converter. It supports voltage comparison.
Touch-key: 6-channel capacitance detection. It supports up to 15 touch keys, and supports independent timing interrupt.
GPIO: Up to 17 GPIO pins (including XI/XO and RST as well as USB signal pins).
Interrupt: It supports 14 sets of interrupt signal sources, including 6 sets of interrupts compatible with the standard MCS51 (INT0, T0, INT1, T1, UART0, T2), and 8 sets of extended interrupts (SPI0, TKEY, USB, ADC, UART1, PWMX, GPIO, WDOG). And GPIO interrupt can be selected from 7 pins.
Watch-Dog: 8-bit presettable watchdog timer WDOG, support timing interrupt.
Reset: 4 kinds of reset signal sources. Built-in power on reset, software reset, watchdog overflow reset and optional pin external input reset.
Clock: Built-in 24MHz clock source, which can support external crystals by multiplexing GPIO pins.
Power: Built-in 5V to 3.3V low dropout voltage regulator. It supports 5V or 3.3V or even 2.8V supply voltage. Support low-power sleep mode and external wake-up of USB, UART0, UART1, SPI0 and part of GPIOs.
Built-in unique ID.
The highlighted dot points above are particular features that I would like to explore with this little chip. The first of these (touch channels) is the subject of this blog and video.
In the diagram below any pins labelled TIN are touch channel pins.
The libraries/core files needed to program the device using the Arduino IDE can be accessed via a github site. This includes a couple of examples using touch detection so I just borrowed my initial code from that repository.
I wanted to blink lights (of course!) when a touch was detected, but it becomes difficult if a light is blinking (and thus employing a pause or delay in processing) to detect a touch.
If we had, for instance, a blink of 1 second on, 1 second off the microcontroller is effectively "frozen" waiting for the delay to finish. A touch detection could be missed while the chip waits for this delay to be over.
This is a fairly normal programming dilemma for single core microcontrollers. As there is no multi-threading etc to split the program, I used a standard technique to simulate multi-tasking.
Effectively we let the clock keep running and the loop stays in motion. There are no delays used in the program to "pause" the instructions. Instead, we use timing markers and we do comparisons each time the loop is executed.
If a previously defined timer length is tripped, then something happens. For instance, if we are "waiting" one second for an LED to turn off, then a timer comparison between when the LED went on with the current time (an "if" statement with a subtraction) would trip that event.
There are plenty of good explanations for this method already online, and you could also step through the code provided below (and watch the video) to see how it is applied in this case.
/* * Touch PIN code for CH552G * OneCircuit Sat 24 Sep 2022 16:52:26 AEST */#include <TouchKey.h>// timing variables: how long since last touch, since// last fast touch and since fast slow touchlong touchtime = 0;
long fast = 0;
long slow = 0;
long currenttime = 0;
int turnofftime = 15000; // no touch time, turn off LEDs// were any wires touched?
boolean notouch = false;
// timing and status for LEDsint slowtime = 500; // millisecondsint fasttime = 100;
boolean slowstatus = false; // lit or not?
boolean faststatus = false;
boolean usingfast = false; // flashing fast or slow?
boolean usingslow = false;
// the led pins#define LEDslow 30#define LEDfast 11void setup() {
// output and write low
pinMode(LEDslow, OUTPUT);
pinMode(LEDfast, OUTPUT);
digitalWrite(LEDfast, LOW);
digitalWrite(LEDslow, LOW);
// setup Touch key for TIN2 and TIN3
delay(50);
TouchKey_begin( (1 << 2) | (1 << 3) ); // Enable TIN2(P1.4), TIN3(P1.5)
delay(50);
}
// function that checks if wire has been touched// then returns that wireuint8_t checktouch() {
uint8_t anytouch = 0;
uint8_t stoptouch = 1;
TouchKey_Process();
anytouch = TouchKey_Get();
// if you hold the wire, nothing happens until// you let go, even dousing the lightsif (anytouch != 0) {
digitalWrite(LEDfast, LOW);
digitalWrite(LEDslow, LOW);
while (stoptouch != 0) {
TouchKey_Process();
stoptouch = TouchKey_Get();
}
}
return anytouch;
}
void loop() {
// check if wires are touched and check// current time
notouch = checktouch();
currenttime = millis();
// we've been touched, so turn on the appropriate LEDif (notouch != 0) {
touchtime = millis(); // start the timing count// it was a fast touchif ((notouch) & (1 << 2)) {
usingfast = true;
usingslow = false;
digitalWrite(LEDfast, HIGH);
digitalWrite(LEDslow, LOW);
fast = millis();
slowstatus = false;
faststatus = true;
notouch == 0;
}
// it was a slow touchif ((notouch) & (1 << 3)) {
usingslow = true;
usingfast = false;
digitalWrite(LEDslow, HIGH);
digitalWrite(LEDfast, LOW);
slow = millis();
faststatus = false;
slowstatus = true;
notouch = 0;
}
}
// no touch so just check blinking time etc// are we past the turnoff time?if (currenttime - touchtime < turnofftime) {
// fast led timing on and offif ((usingfast) && (currenttime - fast > fasttime)) {
if (faststatus) {
digitalWrite(LEDfast, LOW);
fast = millis();
faststatus = false;
}
else {
digitalWrite(LEDfast, HIGH);
fast = millis();
faststatus = true;
}
}
// slow led timing on and offif ((usingslow) && (currenttime - slow > slowtime)) {
if (slowstatus) {
digitalWrite(LEDslow, LOW);
slow = millis();
slowstatus = false;
}
else {
digitalWrite(LEDslow, HIGH);
slow = millis();
slowstatus = true;
}
}
}
// past turn off time, so turn off everythingelse {
digitalWrite(LEDfast, LOW);
digitalWrite(LEDslow, LOW);
slowstatus = false;
faststatus = false;
}
}
In the end the program worked out really well and due to the "fake multitasking" the pins were very responsive to touch.
I would be keen to implement interrupts as well and streamline the code a little bit.
I really enjoyed working with this little wonder, and soon I'll take a look at the "Built in unique ID" which seems interesting as well!
"I'm always chasing rainbows!" - nice lyrics from Thomas Joseph McCarthy and in my case it could be better phrased as "I'm always chasing low clock speeds and energy requirements!"
It's a little fruitless and frustrating at times but I'm a big fan of Thomas Edison who said a couple of things that resonate in these strange pursuits:
1. "Opportunity is missed by most people because it is dressed in overalls and looks like work.", and
2. "I have not failed 10,000 times. I have successfully found 10,000 ways that will not work."
So in this project the plan was to take a successful light design and change out the NiMH battery for a super-capacitor. The reason is that NiMH batteries have a much smaller cycle lifetime compared to super capacitors.
As well I want to wind down the clock speed on the Padauk PFS154 and see how low it can go.
Also I will be using the 5050 Leds that I've used before to minimise the energy requirements.
The super-capacitor used in the last video was a "paltry" 4 Farads. The current version is 2x30 Farad capacitors, and I have some bigger ones coming - er, because see 2nd quote from Edison above!
The code side was where the big gains have been made - I was able to successfully wind down the clock to around 17000Hz (!) and still run the "candle".
/* Candle with Three PWM Pseudo-random flickering to simulate a candle. Output is via 3xPWM channels, variables can be changed to alter the simulation Wed 12 Oct 2022 20:46:06 AEDT*/#include <stdint.h>#include <stdlib.h>#include "../device.h"#include "../easy-pdk/calibrate.h"#include "../auto_sysclock.h"#include "../delay.h"#include <stdbool.h>#define LED4_BIT 4#define LED0_BIT 0#define LED3_BIT 3uint16_t myrand = 2901; // happy birthday// global variables randomised later for flickering, using the// "waveslow" and "wavefast" arraysuint8_t slowcounter = 0;
uint8_t medcounter = 0;
uint8_t fastcounter = 0;
uint8_t slowstart = 0;
uint8_t slowend = 0;
uint8_t medstart = 0;
uint8_t medend = 0;
uint8_t faststart = 0;
uint8_t fastend = 0;
uint8_t faster = 0;
uint8_t waveslow[] = {50, 100, 170, 200}; // 1 leds on PA4uint8_t wavemed[] = {40, 120, 140, 220}; // 1 led on PA3uint8_t wavefast[] = {40, 80, 150, 240}; // 1 led on PA0// booleans to keep track of "fading up" or "fading down"// in each of the slow and fast cyclesbool fastup = true;
bool slowup = true;
bool medup = true;
void mydelay(uint8_t counter) {
for (uint8_t thiscount = 0; thiscount <= counter; thiscount++) {
_delay_us(1);
}
}
uint16_t gimmerand(uint16_t small, uint16_t big) {
myrand ^= (myrand << 13);
myrand ^= (myrand >> 9);
myrand ^= (myrand << 7);
return abs(myrand) % 23 * (big - small) / 23 + small;
}
void getnewslow() {
slowstart = gimmerand(waveslow[0], waveslow[1]);
slowend = gimmerand(waveslow[2], waveslow[3]);
}
void getnewmed() {
medstart = gimmerand(wavemed[0], wavemed[1]);
medend = gimmerand(wavemed[2], wavemed[3]);
}
// initialise a new fast cycle including the new speed of cyclevoid getnewfast() {
faststart = gimmerand(wavefast[0], wavefast[1]);
fastend = gimmerand(wavefast[2], wavefast[3]);
faster = gimmerand(1, 8);
}
// Main programvoid main() {
// Initialize hardware// Set LED as output (all pins are input by default)
PAC |= (1 << LED4_BIT) | (1 << LED0_BIT) | (1 << LED3_BIT);
PWMG1DTL = 0x00; // Clear the LED PWM duty value
PWMG1DTH = 0x00;
PWMG1CUBL = 0xff; // set PWM counter upper bound to 0xffff
PWMG1CUBH = 0xff;
PWMG1C = 0x87;
PWMG1S = 0b00000000; // prescaler=4, divider=1, no interrupt
PWMG0DTL = 0x00; // Clear the LED PWM duty value
PWMG0DTH = 0x00;
PWMG0CUBL = 0xff; // set PWM counter upper bound to 0xffff
PWMG0CUBH = 0xff;
// PWMG0C = (uint8_t)(PWMG0C_ENABLE | PWMG0C_INVERT_OUT | PWMG0C_OUT_PA0 | PWMG0C_CLK_IHRC);
PWMG0C = 0x87;
PWMG0S = 0b00000000; // prescaler=4, divider=1, no interrupt
PWMG2DTL = 0x00; // Clear the LED PWM duty value
PWMG2DTH = 0x00;
PWMG2CUBL = 0xff; // set PWM counter upper bound to 0xffff
PWMG2CUBH = 0xff;
PWMG2C = 0x87;
PWMG2S = 0b00000000; // prescaler=4, divider=1, no interrupt
getnewfast();
getnewslow();
getnewmed();
slowcounter = slowstart;
fastcounter = faststart;
medcounter = medstart;
// Main processing loopwhile (1) {
// ramp up slowif (slowup) {
slowcounter++;
if (slowcounter > slowend) { // ramp finished so switch boolean
slowup = !slowup;
}
}
else {
// ramp down slow
slowcounter--;
if (slowcounter < slowstart) { // ramp finished so switch boolean
slowup = !slowup;
getnewslow();
}
}
// ramp up medif (medup) {
medcounter++;
if (medcounter > medend) { // ramp finished so switch boolean
medup = !medup;
}
}
else {
// ramp down med
medcounter--;
if (medcounter < medstart) { // ramp finished so switch boolean
medup = !medup;
getnewmed();
}
}
// ramp up fastif (fastup) {
fastcounter = fastcounter + faster;
if (fastcounter > fastend) { // ramp finished so switch boolean
fastup = !fastup;
}
}
else {
// ramp down fast
fastcounter = fastcounter - faster;
if (fastcounter < faststart) { // ramp finished so switch boolean
fastup = !fastup;
getnewfast();
}
}
// delay + a re-purposed random for ramp speeds
mydelay(3+faster);
PWMG1DTL = slowcounter & 255;
PWMG1DTH = slowcounter;
PWMG0DTL = fastcounter & 255;
PWMG0DTH = fastcounter;
PWMG2DTL = medcounter & 255;
PWMG2DTH = medcounter;
}
}
// Startup code - Setup/calibrate system clockunsignedchar _sdcc_external_startup(void) {
// Initialize the system clock (CLKMD register) with the IHRC, ILRC, or EOSC clock source and correct divider.// The AUTO_INIT_SYSCLOCK() macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC clock source and divider.// Alternatively, replace this with the more specific PDK_SET_SYSCLOCK(...) macro from pdk/sysclock.h
PDK_SET_SYSCLOCK(SYSCLOCK_ILRC_DIV4);
// Insert placeholder code to tell EasyPdkProg to calibrate the IHRC or ILRC internal oscillator.// The AUTO_CALIBRATE_SYSCLOCK(...) macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC oscillator.// Alternatively, replace this with the more specific EASY_PDK_CALIBRATE_IHRC(...) or EASY_PDK_CALIBRATE_ILRC(...) macro from easy-pdk/calibrate.h
EASY_PDK_CALIBRATE_ILRC(17000,3000);
return0; // Return 0 to inform SDCC to continue with normal initialization.
}
Compiling was fine and oh so nice to have 2k available instead of the 1k ATTiny13 (although I am still keen to learn Padauk assembler and make this a bit smaller).
Left to do? Basically I want to keep on "Edisoning" this thing as follows:
a) Change the code to wind back the power of the ramping (e.g. instead of changing PWM from 40 to 220, try 30 to 160 or similar)
b) Upscale the super-caps. At the moment I'm getting around 4 hours of light - I think 6-8 hours is a reasonable goal.
c) Change some or all of the code from C to Assembler. This could be very scary given that assembler programming (which I love) is a pretty good path to madness.
I follow PCBWay on Twitter (@PCBWayOfficial), in part because they follow and support me (@peckmaths)!
A few months ago I saw the following piece of information floating down the information superhighway on one of their posts:
Taste The Code (https://www.tastethecode.com/) had designed an extra tasty Arduino Uno shield that allowed piggybacking of three ATTiny85 chips for simultaneous programming.
It's seemed like an intriguing idea which I'd only seen before in nature.
I ordered the PCB (PCBWay made it easy to order from their site) by following the links and instructions on the blog, and soon had the boards in my hands.
Normally I would do a mailbag video when a package arrives, and then come back to the PCB at some later stage, but these boards looked so nice I wanted to jump straight in and see if it worked.
It was a very straight forward process to solder up the board with a ZIF socket and a couple of other bits and pieces.
There was a bit of crowding on the back of the shield (see possums above), but in the end the board worked fine and the video below shows both ATTiny85s and also ATTiny13s able to be programmed up fine, three at a time! That's a win for me and definitely a win for Taste The Code.