diff --git a/docs/library/framebuf.rst b/docs/library/framebuf.rst
index 74c9f85649b8f64cb38f565b889b0dd887f9e1de..4f0026e3807444d4cf460bfc54b4ace20d63aa03 100644
--- a/docs/library/framebuf.rst
+++ b/docs/library/framebuf.rst
@@ -148,6 +148,10 @@ Constants
 
     Red Green Blue (16-bit, 5+6+5) color format
 
+.. data:: framebuf.GS2_HMSB
+
+    Grayscale (2-bit) color format
+
 .. data:: framebuf.GS4_HMSB
 
     Grayscale (4-bit) color format
diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c
index 20e40d57919519341f972e88d8b035d880f522ea..f2dc4e858ec0705ccfce8bbe60f2bb771b4a9884 100644
--- a/extmod/modframebuf.c
+++ b/extmod/modframebuf.c
@@ -54,6 +54,7 @@ typedef struct _mp_framebuf_p_t {
 // constants for formats
 #define FRAMEBUF_MVLSB    (0)
 #define FRAMEBUF_RGB565   (1)
+#define FRAMEBUF_GS2_HMSB (5)
 #define FRAMEBUF_GS4_HMSB (2)
 #define FRAMEBUF_MHLSB    (3)
 #define FRAMEBUF_MHMSB    (4)
@@ -130,6 +131,30 @@ STATIC void rgb565_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, i
     }
 }
 
+// Functions for GS2_HMSB format
+
+STATIC void gs2_hmsb_setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_t col) {
+    uint8_t *pixel = &((uint8_t*)fb->buf)[(x + y * fb->stride) >> 2];
+    uint8_t shift = (x & 0x3) << 1;
+    uint8_t mask = 0x3 << shift;
+    uint8_t color = (col & 0x3) << shift;
+    *pixel = color | (*pixel & (~mask));
+}
+
+STATIC uint32_t gs2_hmsb_getpixel(const mp_obj_framebuf_t *fb, int x, int y) {
+    uint8_t pixel = ((uint8_t*)fb->buf)[(x + y * fb->stride) >> 2];
+    uint8_t shift = (x & 0x3) << 1;
+    return (pixel >> shift) & 0x3;
+}
+
+STATIC void gs2_hmsb_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w, int h, uint32_t col) {
+    for (int xx=x; xx < x+w; xx++) {
+        for (int yy=y; yy < y+h; yy++) {
+            gs2_hmsb_setpixel(fb, xx, yy, col);
+        }
+    }
+}
+
 // Functions for GS4_HMSB format
 
 STATIC void gs4_hmsb_setpixel(const mp_obj_framebuf_t *fb, int x, int y, uint32_t col) {
@@ -184,6 +209,7 @@ STATIC void gs4_hmsb_fill_rect(const mp_obj_framebuf_t *fb, int x, int y, int w,
 STATIC mp_framebuf_p_t formats[] = {
     [FRAMEBUF_MVLSB] = {mvlsb_setpixel, mvlsb_getpixel, mvlsb_fill_rect},
     [FRAMEBUF_RGB565] = {rgb565_setpixel, rgb565_getpixel, rgb565_fill_rect},
+    [FRAMEBUF_GS2_HMSB] = {gs2_hmsb_setpixel, gs2_hmsb_getpixel, gs2_hmsb_fill_rect},
     [FRAMEBUF_GS4_HMSB] = {gs4_hmsb_setpixel, gs4_hmsb_getpixel, gs4_hmsb_fill_rect},
     [FRAMEBUF_MHLSB] = {mono_horiz_setpixel, mono_horiz_getpixel, mono_horiz_fill_rect},
     [FRAMEBUF_MHMSB] = {mono_horiz_setpixel, mono_horiz_getpixel, mono_horiz_fill_rect},
@@ -240,6 +266,9 @@ STATIC mp_obj_t framebuf_make_new(const mp_obj_type_t *type, size_t n_args, size
         case FRAMEBUF_MHMSB:
             o->stride = (o->stride + 7) & ~7;
             break;
+        case FRAMEBUF_GS2_HMSB:
+            o->stride = (o->stride + 3) & ~3;
+            break;
         case FRAMEBUF_GS4_HMSB:
             o->stride = (o->stride + 1) & ~1;
             break;
@@ -579,6 +608,7 @@ STATIC const mp_rom_map_elem_t framebuf_module_globals_table[] = {
     { MP_ROM_QSTR(MP_QSTR_MVLSB), MP_ROM_INT(FRAMEBUF_MVLSB) },
     { MP_ROM_QSTR(MP_QSTR_MONO_VLSB), MP_ROM_INT(FRAMEBUF_MVLSB) },
     { MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(FRAMEBUF_RGB565) },
+    { MP_ROM_QSTR(MP_QSTR_GS2_HMSB), MP_ROM_INT(FRAMEBUF_GS2_HMSB) },
     { MP_ROM_QSTR(MP_QSTR_GS4_HMSB), MP_ROM_INT(FRAMEBUF_GS4_HMSB) },
     { MP_ROM_QSTR(MP_QSTR_MONO_HLSB), MP_ROM_INT(FRAMEBUF_MHLSB) },
     { MP_ROM_QSTR(MP_QSTR_MONO_HMSB), MP_ROM_INT(FRAMEBUF_MHMSB) },
diff --git a/tests/extmod/framebuf2.py b/tests/extmod/framebuf2.py
new file mode 100644
index 0000000000000000000000000000000000000000..a313170eb5e42a4dcb5797894ac93c75dc5ed6d2
--- /dev/null
+++ b/tests/extmod/framebuf2.py
@@ -0,0 +1,62 @@
+try:
+    import framebuf
+except ImportError:
+    print("SKIP")
+    raise SystemExit
+
+def printbuf():
+    print("--8<--")
+    for y in range(h):
+        for x in range(w):
+            print('%u' % ((buf[(x + y * w) // 4] >> ((x & 3) << 1)) & 3), end='')
+        print()
+    print("-->8--")
+
+w = 8
+h = 5
+buf = bytearray(w * h // 4)
+fbuf = framebuf.FrameBuffer(buf, w, h, framebuf.GS2_HMSB)
+
+# fill
+fbuf.fill(3)
+printbuf()
+fbuf.fill(0)
+printbuf()
+
+# put pixel
+fbuf.pixel(0, 0, 1)
+fbuf.pixel(3, 0, 2)
+fbuf.pixel(0, 4, 3)
+fbuf.pixel(3, 4, 2)
+printbuf()
+
+# get pixel
+print(fbuf.pixel(0, 4), fbuf.pixel(1, 1))
+
+# scroll
+fbuf.fill(0)
+fbuf.pixel(2, 2, 3)
+printbuf()
+fbuf.scroll(0, 1)
+printbuf()
+fbuf.scroll(1, 0)
+printbuf()
+fbuf.scroll(-1, -2)
+printbuf()
+
+w2 = 2
+h2 = 3
+buf2 = bytearray(w2 * h2 // 4)
+fbuf2 = framebuf.FrameBuffer(buf2, w2, h2, framebuf.GS2_HMSB)
+
+# blit
+fbuf2.fill(0)
+fbuf2.pixel(0, 0, 1)
+fbuf2.pixel(0, 2, 2)
+fbuf2.pixel(1, 0, 1)
+fbuf2.pixel(1, 2, 2)
+fbuf.fill(3)
+fbuf.blit(fbuf2, 3, 3, 0)
+fbuf.blit(fbuf2, -1, -1, 0)
+fbuf.blit(fbuf2, 16, 16, 0)
+printbuf()
diff --git a/tests/extmod/framebuf2.py.exp b/tests/extmod/framebuf2.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..c53e518a6e92d13bbc1ba946e25091b045e807d7
--- /dev/null
+++ b/tests/extmod/framebuf2.py.exp
@@ -0,0 +1,57 @@
+--8<--
+33333333
+33333333
+33333333
+33333333
+33333333
+-->8--
+--8<--
+00000000
+00000000
+00000000
+00000000
+00000000
+-->8--
+--8<--
+10020000
+00000000
+00000000
+00000000
+30020000
+-->8--
+3 0
+--8<--
+00000000
+00000000
+00300000
+00000000
+00000000
+-->8--
+--8<--
+00000000
+00000000
+00000000
+00300000
+00000000
+-->8--
+--8<--
+00000000
+00000000
+00000000
+00030000
+00000000
+-->8--
+--8<--
+00000000
+00300000
+00000000
+00030000
+00000000
+-->8--
+--8<--
+33333333
+23333333
+33333333
+33311333
+33333333
+-->8--