少废话,先上代码,本程序仅支持字母和数字

//This version was tesed on Windows X64
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
/*
get a char , Example 'A' is 65 (Space 26th)
Get the index of list , 'A' - 65 = 0
The structure of one element in this list(uint8_t)
    0000    |0000
    morse   |length
    code    |of code
For example, Morse code of A is ._, we denote it by 01,so the left 4-bit part is 0100
The length od morse code of A is 2, so the right 4-bit part is 0010
In the end 0100 0010 = 0x42
*/
using namespace std;
// put your name here
string name = "A BCDEFGHIJKLMNOPQRSTUVWXYZ";
// get the length of your name 
unsigned int name_len = name.length();
uint8_t morse_list[] = {0x42, 0x84, 0xa4, 0x83, 0x01, 0x24, 0xc3, 0x04, 0x02,
                  0x74, 0xa3, 0x44, 0xc2, 0x82, 0xe3, 0x64, 0xd4, 0x43,
                  0x03, 0x81, 0x23, 0x14, 0x63, 0x94, 0xb4, 0xc4, 0xf1};
// put function declarations here:
void blinker(uint8_t rawChar);
// begin setup
int main()
{
    //find a way to wake uc up 
    // or we use a flag-var to show whether this system is enabled
    //then start to bink 
    transform(name.begin(), name.end(), name.begin(), ::toupper);
    //name.toUpperCase();// change all chars to upper case
    for(unsigned int i=0;i<name_len;i++)
    {
        cout <<name[i] << ":" ;
        blinker((uint8_t)(morse_list[name[i]-65])); // Get index in morse_list of current char, 'A' is 65 in ASCII. 
        //delay(TIMEUNIT*2);
    }
    return 0;
}

void blinker(uint8_t rawChar)
{
    if (rawChar == 0xF1) //Space, we defined space is 0XF1
    {
        // wait 5 units because the total space between two words is 7 units
        //delay(TIMEUNIT*5);
        cout << "space";
        return;
    }
    uint8_t digit = 0X0F & rawChar; // get length of the morse, 
    //because the lower four bits store the length
    for (uint8_t i = 0; i < digit; i++)
    {
        uint8_t bit = rawChar & 0x80;
        // get the highest bit of rawChar
        if (bit == 0)// the highest bit is 0, this is a dot
        {
            cout << ".";
            //digitalWrite(LEDPINOUT,HIGH);
            //delay(TIMEUNIT);
        }
        else// this is a dash
        {
            cout << "_";
            //digitalWrite(LEDPINOUT,HIGH);
            //delay(TIMEUNIT*3);
        }
        rawChar = rawChar << 1;
        //digitalWrite(LEDPINOUT,LOW);
        //delay(TIMEUNIT);
    }
    cout << endl;
}

我们先来看一下摩斯码这个东西。

每一个字母都有一个点dot和杠dash的组合,而且长度不定。

其实也没什么奥妙,就是记录一下怎么想到的这个方法。

社团让我做一个闪光灯,能把名字闪成摩斯码。我就想灯的开关是1和0,然后想一个杠是三个点,就是储存成3个点,也就是三个1,用一长串布尔数组储存,另一个数组储存摩斯码长度信息算了。但是想来想去,如果这么干,数组会非常长,一旦轮到后面,会让cpu白转好几圈,而且仔细想想,虽然这样比较高效,实际上浪费很多空间,比如空格是浪费的,一个杠也就是三个点也是浪费的(有个优点匀速的,时间复杂度确定,如果对于时间要求不严格这个优点可以忽略)。

然后我就搜索资料,看看有没有直接计算摩斯码的方法,显然没有,这是统计学统计出来的。不过百科书给了我一个灵感,摩斯码的点和杠,是可以编排到二叉树也是哈夫曼树的!而且因为我们只要支持字母和空格就行了,树的深度也就是摩斯码长最多就是4。但是在一个avr像这种attiny这种只有512字节的东西上实现二叉树真的不现实。比如E就是左走一步,就是一个点,其他类推。

然后我继续搜索,突然想起来,也算是发现,对于树的左节点和右节点,可以规定左节点是1,右节点是零,或者反过来。然后我恍然大悟,那直接把点和杠分别记作0和1不就好了!字母摩斯码最长的位数是4,是半个字节,一下子CSAPP的记忆也唤醒了, 人家寄存器可以拆着用高半段低半段,我一个字节也可以这样玩啊!另外我需要储存摩斯码的长度,最长位数是4,最大也就是0100,也就用三位!然后为了方便,我直接划分一个字节的高4位储存长度,低四位储存摩斯码,存到一个字节就不需要额外变量了!而且我们有16进制,也方便我把数据人工输入和打表,毕竟一个字节就是两个16进制数嘛。

编码和储存有了,再播放就可以了。怎样播放呢?用位运算!对于c程序员,位运算从来不是问题!用&1111 0000取高四位,用&0000 1111取低四位,再用二进制中只有一个1 的char 取特定位就好了!

很快发现问题和优化空间。如果采用这种储存方式,要么摩斯码那四位反向储存,要么要从特定位开始位移,要多位移一次。而且对于摩斯码长度,放到了高四位,通过&1111 0000之后的结果还得左移四位移到最低位才能在10进制表示长度,这很难受,感觉很冗余。接着我转念一想,哎?既然高低都有问题,而且都反着,那把他们交换,问题不久都解决了!低四位直接&0000 1111取摩斯码长度,高四位也不用处理了,直接原变量读取一次最高位,是0就是点否则就是杠,读一次左移一次,直到长度计数器自减到0。

然后代码就完成了!而且事后我还想到一个优点,(其实也算极端过度思考了,因为ASCII码中字母和数字不连续,对应关系会难找)就是这种方法完全可以扩展到长度是5的莫斯码和部分长度为6,最后一位是杠的摩斯码,也就是支持所有的字符还有数字甚至部分符号!因为5的二进制只需要101也就是三位,我们就有五位来存摩斯码长度;支持到6是因为6是110,也就是把最高位那个1既当成摩斯码数据位也当成长度数据位。

主动体验了一次编码和信息的压缩和调制,很有意思。

Views: 25

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.